DepotController.m #10

  • //
  • guest/
  • jeff_argast/
  • P4Cocoa/
  • source/
  • Controllers/
  • DepotController.m
  • View
  • Commits
  • Open Download .zip Download (19 KB)
/*

Copyright (C) 2002-2003, Jeffrey D. Argast.

The authors make NO WARRANTY or representation, either express or implied,
with respect to this software, its quality, accuracy, merchantability, or
fitness for a particular purpose.  This software is provided "AS IS", and you,
its user, assume the entire risk as to its quality and accuracy.

Permission is hereby granted to use, copy, modify, and distribute this
software or portions thereof for any purpose, without fee, subject to these
conditions:

(1) If any part of the source code is distributed, then this
statement must be included, with this copyright and no-warranty notice
unaltered.

(2) Permission for use of this software is granted only if the user accepts
full responsibility for any undesirable consequences; the authors accept
NO LIABILITY for damages of any kind.

*/

#import "DepotController.h"
#import "PerforceActionDepots.h"
#import "OutlineItem.h"
#import "PerforceDirectory.h"
#import "PerforceDepot.h"
#import "RevisionsController.h"
#import "TextFieldDialogController.h"
#import "PendingChangesController.h"
#import "AppDefaults.h"
#import "MessageDefs.h"
#import "AppUtils.h"
#import "PerforceActionAdd.h"
#import "DepotViewSelection.h"
#import "PerforceActionFstat.h"
#import "PerforceFile.h"
#import "AppController.h"

@implementation DepotController

- (void) appFinishedLaunching
{
    // minor HACK: see comment down below
    fAwake = YES;
    

    [fOutlineView reloadData];


}

- (void) dealloc
{
    NSNotificationCenter* nCenter = [NSNotificationCenter defaultCenter];

    [nCenter removeObserver:self];

    [fDepots release];
    
    [fTextFieldDialogController release];
        
	[fCachedSelection release];
	
    [super dealloc];
}

/*
 Asynchronous depot building
*/

- (void) resultFromPerforceDepots: (PerforceActionDepots*) action
{
	if ( [action wasSuccess] )
	{
		[fDepots addObjectsFromArray:[action getDepots]];
	
		[fOutlineView reloadData];
	}
}

/*
 End Async
*/

- (void) buildDepots
{
	fDepots = [[NSMutableArray alloc] init];
	
	[PerforceAction abortAllOwnersActions:self];

	[PerforceActionDepots defaultRunFor:self selector:@selector(resultFromPerforceDepots:)];
}


- (void) awakeFromNib
{
    NSTableColumn *tableColumn = nil;
    NSBrowserCell *browserCell = nil;

    tableColumn = [fOutlineView tableColumnWithIdentifier:@"1"];
    
    browserCell = [[[NSBrowserCell alloc] init] autorelease];
    
    [browserCell setEditable:NO];
    
    [browserCell setLeaf:YES];
    
    [tableColumn setDataCell:browserCell];

    {
        NSNotificationCenter* nCenter = [NSNotificationCenter defaultCenter];
    
        [nCenter addObserver:self selector:@selector(handleDefaultsChanged:) name:kDefaultsUserChanged object:nil];
        [nCenter addObserver:self selector:@selector(handleDefaultsChanged:) name:kDefaultsClientChanged object:nil];
        [nCenter addObserver:self selector:@selector(handleDefaultsChanged:) name:kDefaultsPortChanged object:nil];
        [nCenter addObserver:self selector:@selector(handleDefaultsChanged:) name:kDefaultsShowEntireDepotChanged object:nil];
        [nCenter addObserver:self selector:@selector(handleDefaultsChanged:) name:kDefaultsShowDeletedFilesChanged object:nil];
        [nCenter addObserver:self selector:@selector(handleDefaultsChanged:) name:kGlobalSyncComplete object:nil];
        [nCenter addObserver:self selector:@selector(handleDefaultsChanged:) name:kDefaultsConfigurationsChanged object:nil];

        [nCenter addObserver:self selector:@selector(handlePerforceActionComplete:) name:kActionSyncCommandComplete object:nil];
        [nCenter addObserver:self selector:@selector(handlePerforceActionComplete:) name:kActionEditCommandComplete object:nil];
        [nCenter addObserver:self selector:@selector(handlePerforceActionComplete:) name:kActionDeleteCommandComplete object:nil];
        [nCenter addObserver:self selector:@selector(handlePerforceActionComplete:) name:kActionRevertCommandComplete object:nil];
        [nCenter addObserver:self selector:@selector(handlePerforceActionComplete:) name:kActionAddCommandComplete object:nil];

		[nCenter addObserver:self selector:@selector(handleRefreshView:) name:kActionSubmitCommandComplete object:nil]; 
        [nCenter addObserver:self selector:@selector(handleRefreshView:) name:kRefreshAllViews object:nil];
        [nCenter addObserver:self selector:@selector(handleRefreshView:) name:kDefaultsShowLocalFilesChanged object:nil];

        [nCenter addObserver:self selector:@selector(handleDepotChildChanged:) name:kDepotControllerChildChanged object:nil];
        [nCenter addObserver:self selector:@selector(handleDepotExpandChild:) name:kDepotControllerExpandChild object:nil];
    }  

	[fOutlineView setTarget:self];
	[fOutlineView setDoubleAction:@selector(doubleAction:)];
	
	[fOutlineView setVerticalMotionCanBeginDrag:NO];
	
	[fOutlineView registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
}

- (NSArray*) getSelectedItems
{
	// plus one because the num items selected may be 0
	NSMutableArray* objects = [NSMutableArray arrayWithCapacity:([fOutlineView numberOfSelectedRows] + 1)];
	
	NSEnumerator* items = [fOutlineView selectedRowEnumerator];
	NSNumber*     rowNum;
	
	while ( rowNum = [items nextObject] )
	{
		int rowIdx = [rowNum intValue];
		
		[objects addObject:[fOutlineView itemAtRow:rowIdx]];
	}
	
	return objects;
}

- (void) setSelectedItems:(NSArray*)objects
{
	// now select the items that were selected before the expansion
	[fOutlineView deselectAll:self];
		
	int numItems = [objects count];
	int n;
	
	for ( n = 0; n < numItems; n++ )
	{
		int rowIndex = [fOutlineView rowForItem:[objects objectAtIndex:n]];
		
		if ( rowIndex >= 0 )
		{
			[fOutlineView selectRow:rowIndex byExtendingSelection:YES];	
		}
	}
}	

- (void) handleDepotExpandChild: (NSNotification*) notification
{
	id item = [notification object];
	
	[fOutlineView expandItem:item];
	
	NSRect colRect = [[fOutlineView superview] bounds];
	colRect = [fOutlineView convertRect:colRect fromView:[fOutlineView superview]];
	NSRange rowRange = [fOutlineView rowsInRect:colRect];
	
	int rowForItem = [fOutlineView rowForItem:item];
	// this puts it about in the middle if it can
	int rowToScrollTo = rowForItem + (rowRange.length / 2);
	int numRows = [fOutlineView numberOfRows];
	
	if ( rowToScrollTo > numRows )
	{
		rowToScrollTo = numRows - 1;
	}
	
	// First put it at a known location to make sure it is on the screen
	// because the rowToScrollTo could already be visible and 
	// rowForItem isn't
	[fOutlineView scrollRowToVisible:rowForItem];
	[fOutlineView scrollRowToVisible:rowToScrollTo];
	
	// not sure about blowing away their selection, but this helps to find
	// the expanded item
	[fOutlineView selectRow:rowForItem byExtendingSelection:NO];
}

- (void) handleDepotChildChanged: (NSNotification*) notification
{
	NSArray* selectedItems = [self getSelectedItems];
	
	[fOutlineView reloadData];

	[self setSelectedItems:selectedItems];
}

- (void) handleDefaultsChanged: (NSNotification*) notification
{
    [self refreshData];
}

- (void) handleRefreshView: (NSNotification*) notification
{
	[fOutlineView deselectAll:self];
	
	if ( fDepots )
	{
		int count = [fDepots count];
		int n;
		
		for ( n = 0; n < count; n++ )
		{
			PerforceDepot* depot = [fDepots objectAtIndex:n];
			
			[depot releaseChildren];
		}
	}
	
	[fOutlineView reloadData];
}

- (void) pathChanged:(NSString*)depotPath theFile:(PerforceFile**)theFile
{
	int numDepots = [fDepots count];
	int n;
	
	for ( n = 0; (n < numDepots) && (*theFile == nil); n++ )
	{
		id<DepotViewProtocol> child = [fDepots objectAtIndex:n];

		[child pathChanged:depotPath theFile:theFile];
	}
}

- (void) resultFromPerforceFstat: (PerforceActionFstat*) action
{
	if ( [action wasSuccess] )
	{
		NSArray* oldFiles = (NSArray*) [action getPrivateData];
		NSArray* newFiles = (NSArray*) [action getFiles];
		
		NSSet* newFileSet = nil;
		
		if ( newFiles )
		{
			newFileSet = [NSSet setWithArray:newFiles];
		}
		else
		{
			newFileSet = [NSSet set];
		}
		
		int numFiles = [oldFiles count];
		int n;
		
		
		for ( n = 0; n < numFiles; n++ )
		{
			PerforceFile* oldFile = [oldFiles objectAtIndex:n];
			PerforceFile* newFile = (PerforceFile*) [newFileSet member:oldFile];
			
			if ( !newFile )
			{
				newFile = [PerforceFile fileFromLocalPath:[oldFile clientPath]];
			}
			
			[oldFile copyFrom:newFile];
		}

		SendNotificationWithObject (kDepotControllerChildChanged, self);
	}
}


- (void) handlePerforceActionComplete:(NSNotification*)aNotification
{
	NSMutableArray* fileArray = [NSMutableArray arrayWithCapacity:10]; // 10 is simply arbitrary
	NSMutableArray* fstatPathArray = [NSMutableArray arrayWithCapacity:10]; // 10 is simply arbitrary
	NSArray* pathArray = [aNotification object];

	int numItems = [pathArray count];
	int n;
	
	for ( n = 0; n < numItems; n++ )
	{
		NSString* pathItem = [pathArray objectAtIndex:n];
		
		PerforceFile* theFile = nil;
		
		[self pathChanged:pathItem theFile:&theFile];
		
		if ( theFile )
		{
			[fileArray addObject:theFile];
			[fstatPathArray addObject:pathItem];
		}
	}

	if ( [fstatPathArray count] > 0 )
	{
		[PerforceActionFstat defaultRunFor:self 
							selector:@selector(resultFromPerforceFstat:) 
							withPaths:fstatPathArray
							privateData:fileArray 
							includeDeletes:YES];
	}
	
	SendNotificationWithObject (kDepotControllerChildChanged, self);
}


- (void) doubleAction: (id) sender
{

	NSWindow* window 	= [fOutlineView window];

	NSPoint clickPoint 	= [NSEvent mouseLocation];
	clickPoint 			= [window convertScreenToBase:clickPoint];
	clickPoint 			= [fOutlineView convertPoint:clickPoint fromView:nil];

	int rowIndex 		= [fOutlineView rowAtPoint:clickPoint];

	if ( rowIndex >= 0 )
	{
		id<DepotViewProtocol> item = [fOutlineView itemAtRow:rowIndex];
		
		if ( [item canViewInEditor] )
		{
			[item viewInEditor];
		}
	}
}

/*
** The outline view datasource protocol
*/

// outlineView:child:ofItem:
- (id) outlineView: (NSOutlineView *) outlineView 
       child: 		(int) index 
       ofItem:		(id) item
{
    if ( item )
    {
        return [item getChildAtIndex: index];
    }
    
    if ( !fDepots )
    {
        [self buildDepots];
    }
    
    return [fDepots objectAtIndex: index];
}

// outlineView:isItemExpandable:
- (BOOL) outlineView: 		(NSOutlineView *) outlineView 
         isItemExpandable: 	(id) item
{
    if ( item )
    {
        return [item hasChildren];
    }
    
    return YES;
}

// outlineView:numberOfChildrenOfItem:
- (int) outlineView: 			(NSOutlineView*) outlineView 
        numberOfChildrenOfItem: (id) item
{
    // minor HACK: we don't want to do any of this until the contoller
    // is awoken from nib. In other words, this method gets called 
    // before all of the contollers are hooked up with their respective
    // views.  If we don't have this check then the initial commands are
    // not shown in the NSTextView console window that is connected to
    // the ConsoleController
    if ( !fAwake )
        return 0;

    if ( item )
    {
        return [item getNumberOfChildren];
    }

    if ( !fDepots )
    {
        [self buildDepots];
    }

    return [fDepots count];
}

// outlineView:objectValueForTableColumn:byItem:
- (id) outlineView: 				(NSOutlineView *) outlineView 
       objectValueForTableColumn: 	(NSTableColumn *) tableColumn
       byItem: 						(id) item
{
    return [item getCellText];
}
    

/*
** outline view delegate methods
*/

- (void) 	outlineView: 		(NSOutlineView *) olv 
            willDisplayCell:	(NSCell *) cell 
            forTableColumn:		(NSTableColumn *) tableColumn 
            item:				(id) item 
{  
    if ( [[tableColumn identifier] isEqualToString:@"1"] ) 
    {
        [(NSBrowserCell*)cell setImage:[item getCellImage]];
    }
}

- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
	// WEIRD: Must implement this delegate function and return NO in order for
	// double click to work
	return NO;
}

//
// getSelection
//
- (DepotViewSelection*) getSelection
{
	if ( !fCachedSelection )
	{
		fCachedSelection = [[DepotViewSelection alloc] initWithItems:[self getSelectedItems]];
	}
	
	return fCachedSelection;
}

- (void)outlineViewSelectionDidChange:(NSNotification *)notification
{
	[fCachedSelection release];
	
	fCachedSelection = nil;
}

//
//
//

- (void) expandPath:(NSString*)depotPath
{
	int numDepots = [fDepots count];
	int n;
	
	for ( n = 0; n < numDepots; n++ )
	{
		id<DepotViewProtocol> child = [fDepots objectAtIndex:n];

		[child expandPath:depotPath];
	}
}


//
//
//

- (void) refreshData
{
    if ( fDepots )
    {
        [fDepots release];
    
        fDepots = nil;
    
        [fOutlineView reloadData];
    }
}

//
// showRevisionHistory
//
- (IBAction) showRevisionHistory: (id) sender
{
    DepotViewSelection* item = [self getSelection];
    
    if ( item )
    {   
		[item showRevisionHistory];
    }
}

- (IBAction) addFile: (id) sender;
{
	DepotViewSelection* item = [self getSelection];
   
	if ( item )
	{   
        [item addFile];
    }
}

- (IBAction) syncToHead: (id) sender
{
	DepotViewSelection* item = [self getSelection];
   
	if ( item )
	{   
        [item syncToHead];
    }
}

- (IBAction) syncToNone: (id) sender
{
	DepotViewSelection* item = [self getSelection];
   
	if ( item )
	{   
        [item syncToNone];
	}
}

- (void) syncUsing: (NSString*) dialogTitle withSep: (NSString*) separator
{
    DepotViewSelection* item = [self getSelection];
    
    if ( item )
    {   
        if ( !fTextFieldDialogController )
        {
            fTextFieldDialogController = [[TextFieldDialogController alloc] init];
        }
    
        if ( [fTextFieldDialogController showDialog:dialogTitle] )
        {
            NSMutableString* rev = [NSMutableString stringWithString:separator];
            
            NSString* revValue = [fTextFieldDialogController getData];
            
            if ( [revValue length] > 0 )
            {
                [rev appendString:revValue];
                
                [item syncTo:rev];
                
                if ( [fOutlineView isItemExpanded:item] )
                {
                    [fOutlineView collapseItem:item];
                }
            }
        }
    }
}

- (IBAction) syncToRevision: (id) sender
{
    [self syncUsing:@"File Revision Number" withSep:@"#"];
}

- (IBAction) syncToChangelist: (id) sender
{
    [self syncUsing:@"Changelist Number" withSep:@"@"];
}

- (IBAction) syncToLabel: (id) sender
{
    [self syncUsing:@"Label Name" withSep:@"@"];
}

- (IBAction) edit: (id) sender
{
	DepotViewSelection* item = [self getSelection];
   
	if ( item )
	{   
        [item edit];
    }
}

- (IBAction) delete: (id) sender
{
	DepotViewSelection* item = [self getSelection];
   
	if ( item )
	{   
        [item delete];
    }
}

- (IBAction) resolve: (id) sender
{
	DepotViewSelection* item = [self getSelection];
   
	if ( item )
	{   
        [item resolve];
    }
}

- (IBAction) revert: (id) sender
{
	DepotViewSelection* item = [self getSelection];
   
	if ( item )
	{   
        [item revert];
    }
}

// revert -a
- (IBAction) revertUnchanged: (id) sender
{
	DepotViewSelection* item = [self getSelection];
   
	if ( item )
	{   
		[item revertUnchanged];
	}
}


- (IBAction) diff: (id) sender
{
	DepotViewSelection* item = [self getSelection];
   
	if ( item )
	{   
        [item diff];
    }
}

- (IBAction) revealInFinder: (id) sender
{
	DepotViewSelection* item = [self getSelection];

	if ( item )
	{   
        [item revealInFinder];
    }
}

- (IBAction) viewInEditor: (id) sender
{
	DepotViewSelection* item = [self getSelection];

	if ( item )
	{   
        [item viewInEditor];
    }
}

- (IBAction) addBookmark: (id) sender
{
	NSArray* items = [self getSelectedItems];
	
	if ( [items count] == 1 )
	{
		id<DepotViewProtocol> item = [items objectAtIndex:0];
		
		[[NSApp delegate] addDepotBookmark:[item getDepotPath]];
	}
}

- (IBAction) clientViewOfDepot: (id) sender
{
    [[AppDefaults defaultAppDefaults] setShowEntireDepot:NO];
}

- (IBAction) entireViewOfDepot: (id) sender
{
    [[AppDefaults defaultAppDefaults] setShowEntireDepot:YES];
}

- (IBAction) showDeletedFiles: (id) sender
{
	[[AppDefaults defaultAppDefaults] setShowDeletedFiles:YES];
}

- (IBAction) hideDeletedFiles: (id) sender
{
    [[AppDefaults defaultAppDefaults] setShowDeletedFiles:NO];
}

- (IBAction) showLocalFiles: (id) sender
{
	[[AppDefaults defaultAppDefaults] setShowLocalFiles:YES];
}

- (IBAction) hideLocalFiles: (id) sender
{
    [[AppDefaults defaultAppDefaults] setShowLocalFiles:NO];
}


//
// Drag and drop
//

// Source, i.e. starting a drag
- (BOOL)outlineView:(NSOutlineView *)olv writeItems:(NSArray*)items toPasteboard:(NSPasteboard*)pb 
{
	int numItems = [items count];
	int n;
	
	NSMutableString* pathList = [NSMutableString string];
	
	for ( n = 0; n < numItems; n++ )
	{
		id<DepotViewProtocol> item = [items objectAtIndex:n];
		
		[pathList appendString:[item getPasteboardData]];
		[pathList appendString:@"\n"];
	}
	
	WriteStringToPasteboard (pathList, pb);
		
    return YES;
}

// Destination, i.e. target of drag
- (NSDragOperation)outlineView:(NSOutlineView*)olv 
				   validateDrop:(id <NSDraggingInfo>)info 
				   proposedItem:(id)item 
				   proposedChildIndex:(int)index
{
	[olv setDropItem:nil dropChildIndex:NSOutlineViewDropOnItemIndex];
	
	return NSDragOperationCopy;
}

// Destination, i.e. target of drag
- (BOOL)outlineView:(NSOutlineView*)olv acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(int)index
{
	NSPasteboard* pb = [info draggingPasteboard];
	
	NSArray* value = nil;
	NSString* type;
	
	type = [pb availableTypeFromArray: [NSArray arrayWithObject:NSFilenamesPboardType]];
	
	if ( type )
	{
		value = [pb propertyListForType:NSFilenamesPboardType];

		if ( value )
		{
			NSFileManager* fileManager  = [NSFileManager defaultManager];
			NSMutableArray* filesToAdd  = [[[NSMutableArray alloc] init] autorelease];
			NSMutableString* pathPrefix = [[[NSMutableString alloc] init] autorelease];
			
			int num = [value count];
			int n;
			
			for ( n = 0; n < num; n++)
			{
				NSString* fileName; 
				NSString* valueItem = [value objectAtIndex:n];
				BOOL isDir = NO;
				
				[fileManager fileExistsAtPath:valueItem isDirectory:&isDir];
				
				if ( !isDir )
				{
					[filesToAdd addObject:valueItem];
				}
				else
				{
					[pathPrefix setString:valueItem];
					[pathPrefix appendString:@"/"];
					
					NSDirectoryEnumerator* dirEnumerator = [fileManager enumeratorAtPath:valueItem];
					
					while ( fileName = [dirEnumerator nextObject] )
					{
						NSString* actualFileName = [fileName lastPathComponent];
						
						// skip files that start with .
						if ( [actualFileName characterAtIndex:0] != '.' )
						{						
							NSMutableString* fullPath = [[NSMutableString alloc] initWithString:pathPrefix];
												
							[fullPath appendString:fileName];
						
							[filesToAdd addObject:fullPath];
						
							[fullPath release];
						}
					}
				}
			}
			
			if ( [filesToAdd count] > 0 )
			{
				[PerforceActionAdd defaultAddFiles:filesToAdd];
				
				return YES;
			}
		}
	}

	return NO;
}

//
//
//

@end
# Change User Description Committed
#10 4225 Jeff Argast Added resolve support, reveal in finder, drag and drop edit,
show local files, and showing added files.
#9 3149 Jeff Argast Add default changelist to pending list always even if default is empty
#8 3134 Jeff Argast Added copy/paste support in the depot view
Added expand path to depot view
Added bookmarks
#7 3130 Jeff Argast Added double click support to the depot view and pending changelist
view.  Added View File In Editor item on the pending changeist
context menu.
#6 3114 Jeff Argast Fixed retaining the correct selection after a perforce action
completed executing and updated the outline view.
#5 3113 Jeff Argast Reduced the times the depot view completely collapses.
Now it won't collapse on refresh views or submit, but
still collapses when the defaults change.
#4 3111 Jeff Argast Made multiple selection smarter by operating on the entire selection as an atomic
operation with the server. Also partially fixed the read only window to not wrap
at the window boundary.  I did the same for the editable window, but now the problem
appears to be that p4 change -o is breaking its output at some character location
before the string gets into the editor (at least I think that is the problem).
#3 2803 Jeff Argast Added submit default changelist to the changelist menu
Made the out window selectable
Changed the tabs in a few places
#2 2737 Jeff Argast Added several 0.15 revision functionality
#1 2732 Jeff Argast Initial submission of P4Cocoa