Files
gitx/PBGitSidebarController.m
T
Nathan Kinsinger 53d92fb73e Cleanup the views when the repository window closes and stop memory leaks.
- make sure to remove themselves from KV and notification center observers
    - add the PBWebHistoryController to PBHistoryController so it can be told to close
    - replaced the -removeView methods with -closeView (-removeView was not being used)
    - clear any obj-c objects set in web scripting objects

This last item seems to be the reason that the web controllers and the current commit did not get collected which then held the repository document from being collected as well.
2010-07-04 09:46:22 -06:00

407 lines
11 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"
@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"];
[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"];
[super closeView];
}
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([@"currentBranchChange" isEqualToString:context]) {
[sourceView reloadData];
[self selectCurrentBranch];
return;
}
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];
}
return;
}
[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])
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])
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])
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];
}
#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]];
[cell setImage:[item icon]];
}
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(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"];
remotes = [PBSourceViewItem groupItemWithTitle:@"Remotes"];
tags = [PBSourceViewItem groupItemWithTitle:@"Tags"];
others = [PBSourceViewItem groupItemWithTitle:@"Other"];
for (PBGitRevSpecifier *rev in repository.branches)
[self addRevSpec:rev];
[items addObject:project];
[items addObject:branches];
[items addObject:remotes];
[items addObject:tags];
[items addObject:others];
[sourceView reloadData];
[sourceView expandItem:project];
[sourceView expandItem:branches expandChildren:YES];
[sourceView expandItem:remotes];
[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];
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