mirror of
https://github.com/objective-see/TaskExplorer.git
synced 2026-03-22 07:02:39 +00:00
909 lines
20 KiB
Objective-C
909 lines
20 KiB
Objective-C
//
|
|
// ItemTableController.m
|
|
// TaskExplorer
|
|
//
|
|
// Created by Patrick Wardle on 2/18/15.
|
|
// Copyright (c) 2015 Objective-See. All rights reserved.
|
|
//
|
|
|
|
#import "KKRow.h"
|
|
#import "Binary.h"
|
|
#import "Consts.h"
|
|
#import "ItemBase.h"
|
|
#import "ItemView.h"
|
|
#import "VTButton.h"
|
|
#import "kkRowCell.h"
|
|
#import "Utilities.h"
|
|
#import "AppDelegate.h"
|
|
#import "TaskTableController.h"
|
|
#import "InfoWindowController.h"
|
|
|
|
@implementation TaskTableController
|
|
|
|
@synthesize itemView;
|
|
@synthesize isFiltered;
|
|
@synthesize tableItems;
|
|
@synthesize selectedRow;
|
|
@synthesize isBottomPane;
|
|
@synthesize filteredItems;
|
|
@synthesize ignoreSelection;
|
|
@synthesize vtWindowController;
|
|
@synthesize infoWindowController;
|
|
|
|
@synthesize didInit;
|
|
|
|
-(void)awakeFromNib
|
|
{
|
|
//single time init
|
|
if(YES != self.didInit)
|
|
{
|
|
//init selected row
|
|
self.selectedRow = 0;
|
|
|
|
//alloc array for filtered items
|
|
filteredItems = [NSMutableArray array];
|
|
|
|
//extand tree view
|
|
if(YES == [self.itemView isKindOfClass:[NSOutlineView class]])
|
|
{
|
|
//expand
|
|
[(NSOutlineView*)self.itemView expandItem:nil expandChildren:YES];
|
|
}
|
|
|
|
//set flag
|
|
self.didInit = YES;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
//table delegate
|
|
// ->return number of rows, which is just number of items in the currently selected plugin
|
|
-(NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
|
|
{
|
|
//rows
|
|
NSUInteger rows = 0;
|
|
|
|
//tasks
|
|
OrderedDictionary* tasks = nil;
|
|
|
|
//for top pane
|
|
// ->use tasks from enumerator
|
|
if(YES != self.isBottomPane)
|
|
{
|
|
//when not filtered
|
|
// ->use all tasks
|
|
if(YES != isFiltered)
|
|
{
|
|
//get tasks
|
|
tasks = taskEnumerator.tasks;
|
|
|
|
//set count
|
|
rows = tasks.count;
|
|
}
|
|
//when filtered
|
|
// ->use filtered items
|
|
else
|
|
{
|
|
//set count
|
|
rows = self.filteredItems.count;
|
|
}
|
|
}
|
|
//bottom pane uses 'tableItems' iVar
|
|
else
|
|
{
|
|
//when not filtered
|
|
// ->use all items
|
|
if(YES != isFiltered)
|
|
{
|
|
//set row count
|
|
rows = self.tableItems.count;
|
|
}
|
|
//when filtered
|
|
// ->use filtered items
|
|
else
|
|
{
|
|
//set count
|
|
rows = self.filteredItems.count;
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
|
|
}
|
|
|
|
//automatically invoked when user selects row
|
|
// ->only care about for top pane, trigger load bottom view
|
|
-(void)tableViewSelectionDidChange:(NSNotification *)notification
|
|
{
|
|
//only handle user selections
|
|
// ->not those from 'reloadData'
|
|
if(YES != self.ignoreSelection)
|
|
{
|
|
//handle selection
|
|
[self handleRowSelection];
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
//table delegate method
|
|
// ->return cell for row
|
|
-(NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
|
|
{
|
|
//tasks
|
|
OrderedDictionary* tasks = nil;
|
|
|
|
//item obj
|
|
// ->contains data for view
|
|
id item = nil;
|
|
|
|
//row view
|
|
NSView* rowView = nil;
|
|
|
|
//for TOP PANE
|
|
// ->use tasks from enumerator or filtered items
|
|
if(YES != self.isBottomPane)
|
|
{
|
|
//when not filtered
|
|
// ->use tasks
|
|
if(YES != isFiltered)
|
|
{
|
|
//grab tasks
|
|
tasks = taskEnumerator.tasks;
|
|
|
|
//sanity check
|
|
// ->make sure there is table item for row
|
|
if(tasks.count <= row)
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//get task object
|
|
// ->by index to get key, then by key
|
|
item = tasks[[tasks keyAtIndex:row]];
|
|
|
|
}
|
|
|
|
//when filtered
|
|
// ->use filtered items
|
|
else
|
|
{
|
|
//sanity check
|
|
// ->make sure there is table item for row
|
|
if(self.filteredItems.count <= row)
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//get task object
|
|
item = self.filteredItems[row];
|
|
}
|
|
}
|
|
|
|
//for BOTTOM PANE
|
|
else
|
|
{
|
|
|
|
//when not filtered
|
|
// ->use all items
|
|
if(YES != isFiltered)
|
|
{
|
|
//sanity check
|
|
// ->make sure there is table item for row
|
|
if(self.tableItems.count <= row)
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//grab item
|
|
// ->dylib/file/network item
|
|
item = [self.tableItems objectAtIndex:row];
|
|
}
|
|
|
|
//when filtered
|
|
// ->use filtered items
|
|
else
|
|
{
|
|
//sanity check
|
|
// ->make sure there is table item for row
|
|
if(self.filteredItems.count <= row)
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//set count
|
|
item = [self.filteredItems objectAtIndex:row];
|
|
}
|
|
|
|
}
|
|
|
|
//create custom item view
|
|
if(nil != item)
|
|
{
|
|
//create
|
|
rowView = createItemView(tableView, self, item);
|
|
}
|
|
|
|
return rowView;
|
|
|
|
|
|
//bail
|
|
bail:
|
|
|
|
return nil;
|
|
}
|
|
|
|
//automatically invoked
|
|
// ->create custom (sub-classed) NSTableRowView
|
|
-(NSTableRowView *)tableView:(NSTableView *)tableView rowViewForRow:(NSInteger)row
|
|
{
|
|
//row view
|
|
KKRow* rowView = nil;
|
|
|
|
//row ID
|
|
static NSString* const kRowIdentifier = @"TableRowView";
|
|
|
|
//try grab existing row view
|
|
rowView = [tableView makeViewWithIdentifier:kRowIdentifier owner:self];
|
|
|
|
//make new if needed
|
|
if(nil == rowView)
|
|
{
|
|
//create new
|
|
// ->size doesn't matter
|
|
rowView = [[KKRow alloc] initWithFrame:NSZeroRect];
|
|
|
|
//set row ID
|
|
rowView.identifier = kRowIdentifier;
|
|
}
|
|
|
|
return rowView;
|
|
}
|
|
|
|
|
|
//automatically invoked when mouse entered
|
|
// ->highlight button
|
|
-(void)mouseEntered:(NSEvent*)theEvent
|
|
{
|
|
//mouse entered
|
|
// ->highlight (visual) state
|
|
buttonAppearance(self.itemView, theEvent, NO);
|
|
|
|
return;
|
|
}
|
|
|
|
//automatically invoked when mouse exits
|
|
// ->unhighlight/reset button
|
|
-(void)mouseExited:(NSEvent*)theEvent
|
|
{
|
|
//mouse exited
|
|
// ->so reset button to original (visual) state
|
|
buttonAppearance(self.itemView, theEvent, YES);
|
|
|
|
return;
|
|
}
|
|
|
|
//scroll back up to top of table
|
|
-(void)scrollToTop
|
|
{
|
|
//scroll if more than 1 row
|
|
if([self.itemView numberOfRows] > 0)
|
|
{
|
|
//top
|
|
[self.itemView scrollRowToVisible:0];
|
|
}
|
|
}
|
|
|
|
//reload table
|
|
-(void)reloadTable
|
|
{
|
|
//reload table
|
|
[self.itemView reloadData];
|
|
|
|
//scroll to top
|
|
[self scrollToTop];
|
|
|
|
return;
|
|
}
|
|
|
|
//custom reload
|
|
// ->ensures selected row remains selected
|
|
-(void)refresh
|
|
{
|
|
//tasks
|
|
OrderedDictionary* tasks = nil;
|
|
|
|
//selected task
|
|
Task* selectedTask = nil;
|
|
|
|
//task index after reload
|
|
NSUInteger taskIndex = 0;
|
|
|
|
//grab tasks
|
|
tasks = taskEnumerator.tasks;
|
|
|
|
//get task
|
|
selectedTask = [self taskForRow:nil];
|
|
|
|
//ignore selection change though
|
|
self.ignoreSelection = YES;
|
|
|
|
//always reload
|
|
[self.itemView reloadData];
|
|
|
|
//don't ignore selection
|
|
self.ignoreSelection = NO;
|
|
|
|
//when an item was selected
|
|
// ->get its index and make sure that's still selected
|
|
if(nil != selectedTask)
|
|
{
|
|
//get task's index
|
|
// ->flat view, can do a straight lookup
|
|
if(YES != [self.itemView isKindOfClass:[NSOutlineView class]])
|
|
{
|
|
//get index
|
|
taskIndex = [tasks indexOfKey:selectedTask.pid];
|
|
}
|
|
//get task's index
|
|
// ->outline view, use 'rowForItem' method
|
|
else
|
|
{
|
|
taskIndex = [(NSOutlineView*)self.itemView rowForItem:selectedTask];
|
|
}
|
|
|
|
//(re)select task's row
|
|
// ->but only if task still exists (e.g. didn't exit)
|
|
if(NSNotFound != taskIndex)
|
|
{
|
|
//begin updates
|
|
[self.itemView beginUpdates];
|
|
|
|
//(re)select
|
|
[self.itemView selectRowIndexes:[NSIndexSet indexSetWithIndex:taskIndex] byExtendingSelection:NO];
|
|
|
|
//end updates
|
|
[self.itemView endUpdates];
|
|
}
|
|
}
|
|
|
|
/*
|
|
//otherwise select first row
|
|
else
|
|
{
|
|
//selected row cell
|
|
NSTableCellView* rowView = nil;
|
|
|
|
//default to first row
|
|
self.selectedRow = 0;
|
|
|
|
//sanity check
|
|
if(0 == [self numberOfRowsInTableView:self.itemView])
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//get first row
|
|
rowView = [self.itemView viewAtColumn:0 row:0 makeIfNecessary:YES];
|
|
|
|
//extract task
|
|
// ->pid of task is view's id :)
|
|
Task* task = tasks[[NSNumber numberWithInteger:(rowView.tag - PID_TAG_DELTA)]];
|
|
|
|
//save task
|
|
((AppDelegate*)[[NSApplication sharedApplication] delegate]).currentTask = task;
|
|
|
|
//reload bottom pane
|
|
[((AppDelegate*)[[NSApplication sharedApplication] delegate]) selectBottomPaneContent:nil];
|
|
}
|
|
*/
|
|
|
|
|
|
//bail
|
|
bail:
|
|
|
|
return;
|
|
}
|
|
|
|
//grab a task at a row
|
|
-(Task*)taskForRow:(id)sender
|
|
{
|
|
//index of row
|
|
NSInteger taskRow = 0;
|
|
|
|
//selected row cell
|
|
NSTableCellView* rowView = nil;
|
|
|
|
//tasks
|
|
OrderedDictionary* tasks = nil;
|
|
|
|
//task
|
|
Task* task = nil;
|
|
|
|
//grab tasks
|
|
tasks = taskEnumerator.tasks;
|
|
|
|
//use sender if provided
|
|
if(nil != sender)
|
|
{
|
|
//grab row
|
|
taskRow = [self.itemView rowForView:sender];
|
|
}
|
|
//otherwise use selected row
|
|
else
|
|
{
|
|
//grab row
|
|
taskRow = [self.itemView selectedRow];
|
|
}
|
|
|
|
//sanity check(s)
|
|
// ->make sure row is decent
|
|
if( (-1 == taskRow) ||
|
|
((YES != self.isFiltered) && (tasks.count < taskRow)) ||
|
|
((YES == self.isFiltered) && (self.filteredItems.count < taskRow)) )
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//get row that's about to be selected
|
|
rowView = [self.itemView viewAtColumn:0 row:taskRow makeIfNecessary:YES];
|
|
|
|
//extract task
|
|
// ->pid of task is view's id :)
|
|
task = tasks[[NSNumber numberWithInteger:(rowView.tag - PID_TAG_DELTA)]];
|
|
|
|
//bail
|
|
bail:
|
|
|
|
return task;
|
|
}
|
|
|
|
|
|
//grab an item at a row
|
|
-(ItemBase*)itemForRow:(id)sender
|
|
{
|
|
//index of row
|
|
NSInteger itemRow = 0;
|
|
|
|
//item
|
|
ItemBase* item = nil;
|
|
|
|
//grab row
|
|
itemRow = [self.itemView rowForView:sender];
|
|
|
|
//sanity check(s)
|
|
// ->make sure row has item
|
|
if( (-1 == itemRow) ||
|
|
((YES != self.isFiltered) && (self.tableItems.count < itemRow)) ||
|
|
((YES == self.isFiltered) && (self.filteredItems.count < itemRow)) )
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//when not filtered
|
|
// ->just grab item
|
|
if(YES != self.isFiltered)
|
|
{
|
|
//get item
|
|
item = self.tableItems[itemRow];
|
|
}
|
|
//when filtered
|
|
// ->grab from filtered item
|
|
else
|
|
{
|
|
//get item
|
|
item = self.filteredItems[itemRow];
|
|
}
|
|
|
|
//bail
|
|
bail:
|
|
|
|
return item;
|
|
}
|
|
|
|
|
|
//automatically invoked when user clicks the 'show in finder' icon
|
|
// ->open Finder to show item
|
|
-(IBAction)showInFinder:(id)sender
|
|
{
|
|
//item
|
|
// ->task, dylib, file, etc
|
|
id item = nil;
|
|
|
|
//path to binary
|
|
NSString* path = nil;
|
|
|
|
//file open error alert
|
|
NSAlert* errorAlert = nil;
|
|
|
|
//for top pane
|
|
// ->get task
|
|
if(YES != self.isBottomPane)
|
|
{
|
|
//get task
|
|
item = [self taskForRow:sender];
|
|
|
|
//get path
|
|
path = ((Task*)item).binary.path;
|
|
}
|
|
|
|
//bottom pane
|
|
else
|
|
{
|
|
//get item
|
|
item = [self itemForRow:sender];
|
|
|
|
//get path
|
|
path = ((Binary*)item).path;
|
|
}
|
|
|
|
//sanity check
|
|
if(nil == path)
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//open item in Finder
|
|
// ->error alert shown if file open fails
|
|
if(YES != [[NSWorkspace sharedWorkspace] selectFile:path inFileViewerRootedAtPath:@""])
|
|
{
|
|
//alloc/init alert
|
|
errorAlert = [NSAlert alertWithMessageText:[NSString stringWithFormat:@"ERROR:\nfailed to open %@", path] defaultButton:@"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:@"errno value: %d", errno];
|
|
|
|
//show it
|
|
[errorAlert runModal];
|
|
}
|
|
|
|
|
|
//bail
|
|
bail:
|
|
|
|
return;
|
|
}
|
|
|
|
//automatically invoked when user clicks the 'info' icon
|
|
// ->create/configure/display info window for task/dylib/file/etc
|
|
-(IBAction)showInfo:(id)sender
|
|
{
|
|
//item
|
|
// ->task, dylib, file, etc
|
|
id item = nil;
|
|
|
|
//for top pane
|
|
// ->get task
|
|
if(YES != self.isBottomPane)
|
|
{
|
|
//get task
|
|
item = [self taskForRow:sender];
|
|
}
|
|
|
|
//bottom pane
|
|
else
|
|
{
|
|
//get item
|
|
item = [self itemForRow:sender];
|
|
}
|
|
|
|
//alloc/init info window
|
|
infoWindowController = [[InfoWindowController alloc] initWithItem:item];
|
|
|
|
//show it
|
|
[self.infoWindowController.windowController showWindow:self];
|
|
|
|
//bail
|
|
bail:
|
|
|
|
return;
|
|
}
|
|
|
|
//invoked when the user clicks 'virus total' icon
|
|
// ->launch browser and browse to virus total's page
|
|
-(void)showVTInfo:(id)sender
|
|
{
|
|
//item
|
|
// ->task, dylib, file, etc
|
|
Binary* binary = nil;
|
|
|
|
//for top pane
|
|
// ->get binary from task
|
|
if(YES != self.isBottomPane)
|
|
{
|
|
//get task's binary
|
|
binary = [(Task*)[self taskForRow:sender] binary];
|
|
}
|
|
|
|
//bottom pane
|
|
// ->straight assign
|
|
else
|
|
{
|
|
//get item
|
|
binary = (Binary*)[self itemForRow:sender];
|
|
}
|
|
|
|
//bail on nil binaries
|
|
if(nil == binary)
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//alloc/init info window
|
|
vtWindowController = [[VTInfoWindowController alloc] initWithItem:binary];
|
|
|
|
//show it
|
|
[self.vtWindowController.windowController showWindow:self];
|
|
|
|
|
|
//bail
|
|
bail:
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//OUTLINE VIEW STUFFZ
|
|
|
|
-(NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
|
|
{
|
|
//tasks
|
|
OrderedDictionary* tasks = nil;
|
|
|
|
//grab tasks
|
|
tasks = taskEnumerator.tasks;
|
|
|
|
//number of children
|
|
NSUInteger numberOfChildren = 0;
|
|
|
|
//when item is nil
|
|
// ->count is number of root items children
|
|
if(nil == item)
|
|
{
|
|
//kids
|
|
numberOfChildren = ((Task*)[tasks objectForKey:@0]).children.count;
|
|
}
|
|
//otherwise
|
|
// ->give number of item's kids
|
|
else
|
|
{
|
|
//kids
|
|
numberOfChildren = [((Task*)item).children count];
|
|
}
|
|
|
|
return numberOfChildren;
|
|
}
|
|
|
|
-(BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
|
|
{
|
|
//only no for leafs
|
|
// ->items w/o kids
|
|
if( (nil != item) &&
|
|
(0 == [[item children] count]) )
|
|
{
|
|
return NO;
|
|
}
|
|
else
|
|
{
|
|
return YES;
|
|
}
|
|
//return !item ? YES : [[item children] count] != 0;
|
|
}
|
|
|
|
|
|
//return child
|
|
-(id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
|
|
{
|
|
//tasks
|
|
OrderedDictionary* tasks = nil;
|
|
|
|
//task
|
|
Task* task = nil;
|
|
|
|
//grab all tasks
|
|
tasks = taskEnumerator.tasks;
|
|
|
|
//root item
|
|
if(nil == item)
|
|
{
|
|
//root
|
|
task = [tasks objectForKey:@0];
|
|
}
|
|
//non-root items
|
|
// ->return *their* child!
|
|
else
|
|
{
|
|
task = [tasks objectForKey:[(Task*)item children][index]];
|
|
}
|
|
|
|
return task;
|
|
}
|
|
|
|
|
|
//automatically ccalled when row is selected in outline view
|
|
// ->invoke helper function to handle selection
|
|
-(void)outlineViewSelectionDidChange:(NSNotification *)notification
|
|
{
|
|
//handle selection
|
|
[self handleRowSelection];
|
|
|
|
return;
|
|
}
|
|
|
|
//table delegate method
|
|
// ->return cell for row
|
|
-(NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
|
|
{
|
|
return createItemView(outlineView, self, (Task*)item);
|
|
}
|
|
|
|
//automatically invoked
|
|
// ->create custom (sub-classed) NSTableRowView
|
|
-(NSTableRowView *)outlineView:(NSOutlineView *)outlineView rowViewForItem:(id)item
|
|
{
|
|
//row view
|
|
KKRow* rowView = nil;
|
|
|
|
//row ID
|
|
static NSString* const kRowIdentifier = @"OutlineRowView";
|
|
|
|
//try grab existing row view
|
|
rowView = [outlineView makeViewWithIdentifier:kRowIdentifier owner:self];
|
|
|
|
//make new if needed
|
|
if(nil == rowView)
|
|
{
|
|
//create new
|
|
// ->size doesn't matter
|
|
rowView = [[KKRow alloc] initWithFrame:NSZeroRect];
|
|
|
|
//set row ID
|
|
rowView.identifier = kRowIdentifier;
|
|
}
|
|
|
|
return rowView;
|
|
}
|
|
|
|
/* LOGIC FOR BOTH */
|
|
|
|
//handle when user clicks row
|
|
// ->update bottom pane w/ task's dylibs/files/etc
|
|
-(void)handleRowSelection
|
|
{
|
|
//tasks
|
|
__block OrderedDictionary* tasks = nil;
|
|
|
|
//task
|
|
Task* task = nil;
|
|
|
|
//newly selected row (index)
|
|
__block NSInteger newlySelectedRow = -1;
|
|
|
|
//selected row cell
|
|
NSTableCellView* selectedView = nil;
|
|
|
|
//ignore events for bottom-pane
|
|
if(YES == self.isBottomPane)
|
|
{
|
|
//ignore
|
|
goto bail;
|
|
}
|
|
|
|
//grab tasks
|
|
tasks = taskEnumerator.tasks;
|
|
|
|
//get index of newly selected row
|
|
newlySelectedRow = [self.itemView selectedRow];
|
|
|
|
//sanity check
|
|
if( (-1 == newlySelectedRow) ||
|
|
((YES != self.isFiltered) && (newlySelectedRow >= tasks.count)) ||
|
|
((YES == self.isFiltered) && (newlySelectedRow >= self.filteredItems.count)) )
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//get view that's about to be selected
|
|
selectedView = [self.itemView viewAtColumn:0 row:newlySelectedRow makeIfNecessary:YES];
|
|
|
|
//extract task
|
|
// ->pid of task is view's id :)
|
|
task = tasks[[NSNumber numberWithInteger:(selectedView.tag - PID_TAG_DELTA)]];
|
|
|
|
//check if task is dead
|
|
if(YES != isAlive([task.pid intValue]))
|
|
{
|
|
//make it red
|
|
((kkRowCell*)selectedView).color = [NSColor redColor];
|
|
|
|
//draw
|
|
[selectedView setNeedsDisplay:YES];
|
|
|
|
//make hidden it after .33 second
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.33 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
|
|
//reset color
|
|
((kkRowCell*)selectedView).color = nil;
|
|
|
|
//remove task from task emumerator
|
|
[taskEnumerator removeTask:task];
|
|
|
|
//when filtering
|
|
// ->remove deceased task from filter tasks
|
|
if(YES == isFiltered)
|
|
{
|
|
//remove
|
|
[self.filteredItems removeObject:task];
|
|
}
|
|
|
|
//reload table
|
|
[self.itemView reloadData];
|
|
|
|
//sanity check
|
|
// ->make sure removed task wasn't last
|
|
if(tasks.count <= newlySelectedRow)
|
|
{
|
|
//reset to (new) last
|
|
newlySelectedRow = tasks.count - 1;
|
|
}
|
|
//sanity check
|
|
// ->reset to first item on other errors
|
|
else if(-1 == newlySelectedRow)
|
|
{
|
|
//set to first
|
|
newlySelectedRow = 0;
|
|
}
|
|
|
|
//begin updates
|
|
[self.itemView beginUpdates];
|
|
|
|
//re-select
|
|
[self.itemView selectRowIndexes:[NSIndexSet indexSetWithIndex:newlySelectedRow] byExtendingSelection:NO];
|
|
|
|
//end updates
|
|
[self.itemView endUpdates];
|
|
|
|
//reload bottom pane
|
|
[((AppDelegate*)[[NSApplication sharedApplication] delegate]) selectBottomPaneContent:nil];
|
|
|
|
});
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//ignore if row selection and task didn't change
|
|
if( (newlySelectedRow == self.selectedRow) &&
|
|
(((AppDelegate*)[[NSApplication sharedApplication] delegate]).currentTask == task) )
|
|
{
|
|
//ignore
|
|
goto bail;
|
|
}
|
|
|
|
//save newly selected row index
|
|
self.selectedRow = [self.itemView selectedRow];
|
|
|
|
//save currently selected task
|
|
((AppDelegate*)[[NSApplication sharedApplication] delegate]).currentTask = task;
|
|
|
|
//reload bottom pane
|
|
[((AppDelegate*)[[NSApplication sharedApplication] delegate]) selectBottomPaneContent:nil];
|
|
|
|
|
|
bail:
|
|
;
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
@end
|