mirror of
https://github.com/kennethreitz-archive/gitx.git
synced 2026-06-05 15:30:18 +00:00
17a4935f66
Commit on CMD+ENTER Added images for group items in sidebar menu
502 lines
15 KiB
Objective-C
502 lines
15 KiB
Objective-C
//
|
|
// PBGitSidebar.m
|
|
// GitX
|
|
//
|
|
// Created by Pieter de Bie on 9/8/09.
|
|
// Copyright 2009 __MyCompanyName__. All rights reserved.
|
|
//
|
|
|
|
#import "PBGitSidebarController.h"
|
|
#import "PBSourceViewItems.h"
|
|
#import "PBGitHistoryController.h"
|
|
#import "PBGitCommitController.h"
|
|
#import "PBRefController.h"
|
|
#import "PBSourceViewCell.h"
|
|
#import "NSOutlineViewExt.h"
|
|
#import "PBAddRemoteSheet.h"
|
|
#import "PBGitDefaults.h"
|
|
#import "PBHistorySearchController.h"
|
|
#import "PBGitMenuItem.h"
|
|
|
|
#import "PBStashCommandFactory.h"
|
|
#import "PBRemoteCommandFactory.h"
|
|
#import "PBCommandMenuItem.h"
|
|
#import "PBGitStash.h"
|
|
#import "PBGitSubmodule.h"
|
|
|
|
static NSString * const kObservingContextStashes = @"stashesChanged";
|
|
static NSString * const kObservingContextSubmodules = @"submodulesChanged";
|
|
|
|
@interface PBGitSidebarController ()
|
|
|
|
- (void)populateList;
|
|
- (void)addRevSpec:(PBGitRevSpecifier *)revSpec;
|
|
- (PBSourceViewItem *) itemForRev:(PBGitRevSpecifier *)rev;
|
|
- (void) removeRevSpec:(PBGitRevSpecifier *)rev;
|
|
- (void) updateActionMenu;
|
|
- (void) updateRemoteControls;
|
|
@end
|
|
|
|
@implementation PBGitSidebarController
|
|
@synthesize items;
|
|
@synthesize sourceListControlsView;
|
|
|
|
- (id)initWithRepository:(PBGitRepository *)theRepository superController:(PBGitWindowController *)controller
|
|
{
|
|
self = [super initWithRepository:theRepository superController:controller];
|
|
[sourceView setDelegate:self];
|
|
items = [NSMutableArray array];
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)awakeFromNib
|
|
{
|
|
[super awakeFromNib];
|
|
window.contentView = self.view;
|
|
[self populateList];
|
|
|
|
historyViewController = [[PBGitHistoryController alloc] initWithRepository:repository superController:superController];
|
|
commitViewController = [[PBGitCommitController alloc] initWithRepository:repository superController:superController];
|
|
|
|
[repository addObserver:self forKeyPath:@"currentBranch" options:0 context:@"currentBranchChange"];
|
|
[repository addObserver:self forKeyPath:@"branches" options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:@"branchesModified"];
|
|
[repository addObserver:self forKeyPath:@"stashes" options:NSKeyValueObservingOptionNew context:kObservingContextStashes];
|
|
[repository addObserver:self forKeyPath:@"submodules" options:NSKeyValueObservingOptionNew context:kObservingContextSubmodules];
|
|
|
|
|
|
[self menuNeedsUpdate:[actionButton menu]];
|
|
|
|
if ([PBGitDefaults showStageView])
|
|
[self selectStage];
|
|
else
|
|
[self selectCurrentBranch];
|
|
}
|
|
|
|
- (void)closeView
|
|
{
|
|
[historyViewController closeView];
|
|
[commitViewController closeView];
|
|
|
|
[repository removeObserver:self forKeyPath:@"currentBranch"];
|
|
[repository removeObserver:self forKeyPath:@"branches"];
|
|
[repository removeObserver:self forKeyPath:@"stashes"];
|
|
[repository removeObserver:self forKeyPath:@"submodules"];
|
|
|
|
[super closeView];
|
|
}
|
|
|
|
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
|
|
{
|
|
if ([@"currentBranchChange" isEqualToString:context]) {
|
|
[sourceView reloadData];
|
|
[self selectCurrentBranch];
|
|
} else if ([@"branchesModified" isEqualToString:context]) {
|
|
NSInteger changeKind = [(NSNumber *)[change objectForKey:NSKeyValueChangeKindKey] intValue];
|
|
|
|
if (changeKind == NSKeyValueChangeInsertion) {
|
|
NSArray *newRevSpecs = [change objectForKey:NSKeyValueChangeNewKey];
|
|
for (PBGitRevSpecifier *rev in newRevSpecs) {
|
|
[self addRevSpec:rev];
|
|
PBSourceViewItem *item = [self itemForRev:rev];
|
|
[sourceView PBExpandItem:item expandParents:YES];
|
|
}
|
|
}
|
|
else if (changeKind == NSKeyValueChangeRemoval) {
|
|
NSArray *removedRevSpecs = [change objectForKey:NSKeyValueChangeOldKey];
|
|
for (PBGitRevSpecifier *rev in removedRevSpecs)
|
|
[self removeRevSpec:rev];
|
|
}
|
|
} else if ([kObservingContextStashes isEqualToString:context]) { // isEqualToString: is not needed here
|
|
[stashes.children removeAllObjects];
|
|
NSArray *newStashes = [change objectForKey:NSKeyValueChangeNewKey];
|
|
|
|
PBGitMenuItem *lastItem = nil;
|
|
for (PBGitStash *stash in newStashes) {
|
|
PBGitMenuItem *item = [[PBGitMenuItem alloc] initWithSourceObject:stash];
|
|
[stashes addChild:item];
|
|
[item release];
|
|
lastItem = item;
|
|
}
|
|
if (lastItem) {
|
|
[sourceView PBExpandItem:lastItem expandParents:YES];
|
|
}
|
|
[sourceView reloadData];
|
|
} else if ([kObservingContextSubmodules isEqualToString:context]) {
|
|
[submodules.children removeAllObjects];
|
|
NSArray *newSubmodules = [change objectForKey:NSKeyValueChangeNewKey];
|
|
|
|
for (PBGitSubmodule *submodule in newSubmodules) {
|
|
PBGitMenuItem *item = [[PBGitMenuItem alloc] initWithSourceObject:submodule];
|
|
|
|
BOOL added = NO;
|
|
for (PBGitMenuItem *addedItems in [submodules children]) {
|
|
if ([[submodule path] hasPrefix:[(id)[addedItems sourceObject] path]]) {
|
|
[addedItems addChild:item];
|
|
added = YES;
|
|
}
|
|
}
|
|
if (!added) {
|
|
[submodules addChild:item];
|
|
}
|
|
[sourceView PBExpandItem:item expandParents:YES];
|
|
}
|
|
[sourceView reloadData];
|
|
} else {
|
|
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
|
}
|
|
}
|
|
|
|
- (PBSourceViewItem *) selectedItem
|
|
{
|
|
NSInteger index = [sourceView selectedRow];
|
|
PBSourceViewItem *item = [sourceView itemAtRow:index];
|
|
|
|
return item;
|
|
}
|
|
|
|
- (void) selectStage
|
|
{
|
|
NSIndexSet *index = [NSIndexSet indexSetWithIndex:[sourceView rowForItem:stage]];
|
|
[sourceView selectRowIndexes:index byExtendingSelection:NO];
|
|
}
|
|
|
|
- (void) selectCurrentBranch
|
|
{
|
|
PBGitRevSpecifier *rev = repository.currentBranch;
|
|
if (!rev) {
|
|
[repository reloadRefs];
|
|
[repository readCurrentBranch];
|
|
return;
|
|
}
|
|
|
|
PBSourceViewItem *item = nil;
|
|
for (PBSourceViewItem *it in items)
|
|
if ( (item = [it findRev:rev]) != nil )
|
|
break;
|
|
|
|
if (!item) {
|
|
[self addRevSpec:rev];
|
|
// Try to find the just added item again.
|
|
// TODO: refactor with above.
|
|
for (PBSourceViewItem *it in items)
|
|
if ( (item = [it findRev:rev]) != nil )
|
|
break;
|
|
}
|
|
|
|
[sourceView PBExpandItem:item expandParents:YES];
|
|
NSIndexSet *index = [NSIndexSet indexSetWithIndex:[sourceView rowForItem:item]];
|
|
|
|
[sourceView selectRowIndexes:index byExtendingSelection:NO];
|
|
}
|
|
|
|
- (PBSourceViewItem *) itemForRev:(PBGitRevSpecifier *)rev
|
|
{
|
|
PBSourceViewItem *foundItem = nil;
|
|
for (PBSourceViewItem *item in items)
|
|
if ( (foundItem = [item findRev:rev]) != nil )
|
|
return foundItem;
|
|
return nil;
|
|
}
|
|
|
|
- (void)addRevSpec:(PBGitRevSpecifier *)rev
|
|
{
|
|
if (![rev isSimpleRef]) {
|
|
[others addChild:[PBSourceViewItem itemWithRevSpec:rev]];
|
|
[sourceView reloadData];
|
|
return;
|
|
}
|
|
|
|
NSArray *pathComponents = [[rev simpleRef] componentsSeparatedByString:@"/"];
|
|
if ([pathComponents count] < 2)
|
|
[branches addChild:[PBSourceViewItem itemWithRevSpec:rev]];
|
|
else if ([[pathComponents objectAtIndex:1] isEqualToString:@"heads"])
|
|
[branches addRev:rev toPath:[pathComponents subarrayWithRange:NSMakeRange(2, [pathComponents count] - 2)]];
|
|
else if ([[rev simpleRef] hasPrefix:@"refs/tags/"])
|
|
[tags addRev:rev toPath:[pathComponents subarrayWithRange:NSMakeRange(2, [pathComponents count] - 2)]];
|
|
else if ([[rev simpleRef] hasPrefix:@"refs/remotes/"])
|
|
[remotes addRev:rev toPath:[pathComponents subarrayWithRange:NSMakeRange(2, [pathComponents count] - 2)]];
|
|
|
|
[sourceView reloadData];
|
|
}
|
|
|
|
- (void) removeRevSpec:(PBGitRevSpecifier *)rev
|
|
{
|
|
PBSourceViewItem *item = [self itemForRev:rev];
|
|
|
|
if (!item)
|
|
return;
|
|
|
|
PBSourceViewItem *parent = item.parent;
|
|
[parent removeChild:item];
|
|
[sourceView reloadData];
|
|
}
|
|
|
|
- (void)setHistorySearch:(NSString *)searchString mode:(NSInteger)mode
|
|
{
|
|
[historyViewController.searchController setHistorySearch:searchString mode:mode];
|
|
}
|
|
|
|
#pragma mark NSOutlineView delegate methods
|
|
|
|
- (void)outlineViewSelectionDidChange:(NSNotification *)notification
|
|
{
|
|
NSInteger index = [sourceView selectedRow];
|
|
PBSourceViewItem *item = [sourceView itemAtRow:index];
|
|
|
|
if ([item revSpecifier]) {
|
|
if (![repository.currentBranch isEqual:[item revSpecifier]])
|
|
repository.currentBranch = [item revSpecifier];
|
|
[superController changeContentController:historyViewController];
|
|
[PBGitDefaults setShowStageView:NO];
|
|
}
|
|
|
|
if (item == stage) {
|
|
[superController changeContentController:commitViewController];
|
|
[PBGitDefaults setShowStageView:YES];
|
|
}
|
|
|
|
[self updateActionMenu];
|
|
[self updateRemoteControls];
|
|
}
|
|
|
|
#pragma mark NSOutlineView delegate methods
|
|
- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item
|
|
{
|
|
return [item isGroupItem];
|
|
}
|
|
|
|
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(PBSourceViewCell *)cell forTableColumn:(NSTableColumn *)tableColumn item:(PBSourceViewItem *)item
|
|
{
|
|
cell.isCheckedOut = [item.revSpecifier isEqual:[repository headRef]];
|
|
BOOL showsActionButton = NO;
|
|
if ([item respondsToSelector:@selector(showsActionButton)]) {
|
|
showsActionButton = [item showsActionButton];
|
|
[cell setTarget:self];
|
|
cell.iInfoButtonAction = @selector(infoButtonAction:);
|
|
}
|
|
cell.showsActionButton = showsActionButton;
|
|
|
|
[cell setImage:[item icon]];
|
|
}
|
|
|
|
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item
|
|
{
|
|
return ![item isGroupItem];
|
|
}
|
|
|
|
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldTrackCell:(NSCell *)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item {
|
|
return [item isGroupItem];
|
|
}
|
|
|
|
//
|
|
// The next method is necessary to hide the triangle for uncollapsible items
|
|
// That is, items which should always be displayed, such as the Project group.
|
|
// This also moves the group item to the left edge.
|
|
- (BOOL) outlineView:(NSOutlineView *)outlineView shouldShowOutlineCellForItem:(id)item
|
|
{
|
|
return ![item isUncollapsible];
|
|
}
|
|
|
|
- (void)populateList
|
|
{
|
|
PBSourceViewItem *project = [PBSourceViewItem groupItemWithTitle:[repository projectName]];
|
|
project.isUncollapsible = YES;
|
|
|
|
stage = [PBGitSVStageItem stageItem];
|
|
[project addChild:stage];
|
|
|
|
|
|
branches = [PBSourceViewItem groupItemWithTitle:@"Branches"];
|
|
branches.showsActionButton = YES;
|
|
remotes = [PBSourceViewItem groupItemWithTitle:@"Remotes"];
|
|
remotes.showsActionButton = YES;
|
|
tags = [PBSourceViewItem groupItemWithTitle:@"Tags"];
|
|
others = [PBSourceViewItem groupItemWithTitle:@"Other"];
|
|
stashes = [PBSourceViewItem groupItemWithTitle:@"Stashes"];
|
|
stashes.showsActionButton = YES;
|
|
submodules = [PBSourceViewItem groupItemWithTitle:@"Submodules"];
|
|
submodules.showsActionButton = YES;
|
|
|
|
for (PBGitRevSpecifier *rev in repository.branches)
|
|
[self addRevSpec:rev];
|
|
|
|
[items addObject:project];
|
|
[items addObject:branches];
|
|
[items addObject:remotes];
|
|
[items addObject:tags];
|
|
[items addObject:others];
|
|
[items addObject:stashes];
|
|
[items addObject:submodules];
|
|
|
|
[sourceView reloadData];
|
|
[sourceView expandItem:project];
|
|
[sourceView expandItem:branches expandChildren:YES];
|
|
[sourceView expandItem:remotes];
|
|
//[sourceView expandItem:submodules expandChildren:YES];
|
|
|
|
[sourceView reloadItem:nil reloadChildren:YES];
|
|
}
|
|
|
|
#pragma mark NSOutlineView Datasource methods
|
|
|
|
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
|
|
{
|
|
if (!item)
|
|
return [items objectAtIndex:index];
|
|
|
|
return [[(PBSourceViewItem *)item children] objectAtIndex:index];
|
|
}
|
|
|
|
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
|
|
{
|
|
return [[(PBSourceViewItem *)item children] count];
|
|
}
|
|
|
|
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
|
|
{
|
|
if (!item)
|
|
return [items count];
|
|
|
|
return [[(PBSourceViewItem *)item children] count];
|
|
}
|
|
|
|
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
|
|
{
|
|
return [(PBSourceViewItem *)item title];
|
|
}
|
|
|
|
|
|
#pragma mark Menus
|
|
|
|
- (void) updateActionMenu
|
|
{
|
|
[actionButton setEnabled:([[self selectedItem] ref] != nil)];
|
|
}
|
|
|
|
- (void) addMenuItemsForRef:(PBGitRef *)ref toMenu:(NSMenu *)menu
|
|
{
|
|
if (!ref)
|
|
return;
|
|
|
|
for (NSMenuItem *menuItem in [historyViewController.refController menuItemsForRef:ref])
|
|
[menu addItem:menuItem];
|
|
}
|
|
|
|
- (NSMenuItem *) actionIconItem
|
|
{
|
|
NSMenuItem *actionIconItem = [[NSMenuItem alloc] initWithTitle:@"" action:NULL keyEquivalent:@""];
|
|
NSImage *actionIcon = [NSImage imageNamed:@"NSActionTemplate"];
|
|
[actionIcon setSize:NSMakeSize(12, 12)];
|
|
[actionIconItem setImage:actionIcon];
|
|
|
|
return actionIconItem;
|
|
}
|
|
|
|
- (NSMenu *) menuForRow:(NSInteger)row
|
|
{
|
|
PBSourceViewItem *viewItem = [sourceView itemAtRow:row];
|
|
if ([viewItem isKindOfClass:[PBGitMenuItem class]] || [[viewItem title] isEqualToString:@"STASHES"]) {
|
|
PBGitMenuItem *stashItem = nil;//(PBGitMenuItem *) viewItem;
|
|
NSMutableArray *commands = [[NSMutableArray alloc] init];
|
|
[commands addObjectsFromArray:[PBStashCommandFactory commandsForObject:[stashItem sourceObject] repository:historyViewController.repository]];
|
|
[commands addObjectsFromArray:[PBRemoteCommandFactory commandsForObject:[stashItem sourceObject] repository:historyViewController.repository]];
|
|
NSMenu *menu = [[NSMenu alloc] init];
|
|
[menu setAutoenablesItems:NO];
|
|
for (PBCommand *command in commands) {
|
|
PBCommandMenuItem *item = [[PBCommandMenuItem alloc] initWithCommand:command];
|
|
[menu addItem:item];
|
|
[item release];
|
|
}
|
|
[menu addItem:[[NSMenuItem alloc] initWithTitle:@"hello" action:@selector(hello:) keyEquivalent:@""]];
|
|
if ([[viewItem title] isEqualToString:@"STASHES"]){
|
|
NSEvent *mouseEvent = [NSEvent mouseEventWithType:NSLeftMouseDown location:[NSEvent mouseLocation] modifierFlags:0 timestamp:[NSDate timeIntervalSinceReferenceDate] windowNumber:0 context:nil eventNumber:0 clickCount:1 pressure:0.1];
|
|
NSPoint conv = [sourceView convertPoint:[mouseEvent locationInWindow] fromView:nil];
|
|
[menu popUpMenuPositioningItem:nil atLocation:conv inView:sourceView];
|
|
return nil;
|
|
}
|
|
return menu;
|
|
}
|
|
|
|
PBGitRef *ref = [viewItem ref];
|
|
if (!ref)
|
|
return nil;
|
|
|
|
NSMenu *menu = [[NSMenu alloc] init];
|
|
[menu setAutoenablesItems:NO];
|
|
[self addMenuItemsForRef:ref toMenu:menu];
|
|
|
|
return menu;
|
|
}
|
|
|
|
// delegate of the action menu
|
|
- (void) menuNeedsUpdate:(NSMenu *)menu
|
|
{
|
|
[actionButton removeAllItems];
|
|
[menu addItem:[self actionIconItem]];
|
|
|
|
PBGitRef *ref = [[self selectedItem] ref];
|
|
[self addMenuItemsForRef:ref toMenu:menu];
|
|
}
|
|
|
|
|
|
#pragma mark Remote controls
|
|
|
|
enum {
|
|
kAddRemoteSegment = 0,
|
|
kFetchSegment,
|
|
kPullSegment,
|
|
kPushSegment
|
|
};
|
|
|
|
- (void) updateRemoteControls
|
|
{
|
|
BOOL hasRemote = NO;
|
|
|
|
PBGitRef *ref = [[self selectedItem] ref];
|
|
if ([ref isRemote] || ([ref isBranch] && [[repository remoteRefForBranch:ref error:NULL] remoteName]))
|
|
hasRemote = YES;
|
|
|
|
[remoteControls setEnabled:hasRemote forSegment:kFetchSegment];
|
|
[remoteControls setEnabled:hasRemote forSegment:kPullSegment];
|
|
[remoteControls setEnabled:hasRemote forSegment:kPushSegment];
|
|
}
|
|
|
|
- (IBAction) fetchPullPushAction:(id)sender
|
|
{
|
|
NSInteger selectedSegment = [sender selectedSegment];
|
|
|
|
if (selectedSegment == kAddRemoteSegment) {
|
|
[PBAddRemoteSheet beginAddRemoteSheetForRepository:repository];
|
|
return;
|
|
}
|
|
|
|
NSInteger index = [sourceView selectedRow];
|
|
PBSourceViewItem *item = [sourceView itemAtRow:index];
|
|
PBGitRef *ref = [[item revSpecifier] ref];
|
|
|
|
if (!ref && (item.parent == remotes))
|
|
ref = [PBGitRef refFromString:[kGitXRemoteRefPrefix stringByAppendingString:[item title]]];
|
|
|
|
if (![ref isRemote] && ![ref isBranch])
|
|
return;
|
|
|
|
PBGitRef *remoteRef = [repository remoteRefForBranch:ref error:NULL];
|
|
if (!remoteRef)
|
|
return;
|
|
|
|
if (selectedSegment == kFetchSegment)
|
|
[repository beginFetchFromRemoteForRef:ref];
|
|
else if (selectedSegment == kPullSegment)
|
|
[repository beginPullFromRemote:remoteRef forRef:ref];
|
|
else if (selectedSegment == kPushSegment) {
|
|
if ([ref isRemote])
|
|
[historyViewController.refController showConfirmPushRefSheet:nil remote:remoteRef];
|
|
else if ([ref isBranch])
|
|
[historyViewController.refController showConfirmPushRefSheet:ref remote:remoteRef];
|
|
}
|
|
}
|
|
|
|
|
|
@end
|