/* EditorWindowController.m - editor controller class for Popup.app Copyright (C) 2003, 2004 Rob Burns This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02111, USA. */ #include "EditorWindowController.h" #include "StackModel.h" #include "CardInspectorController.h" #include "Constants.h" @implementation EditorWindowController - (void) dealloc { RELEASE(stack); RELEASE(foundRows); TEST_RELEASE(_cc); TEST_RELEASE(_ec); TEST_RELEASE(_to); [super dealloc]; } - (id) initWithStack: (StackModel *)aStack { if( (self = [super initWithWindowNibName: @"Editor" owner: self]) ) { ASSIGN(stack, aStack); foundRows = [[NSMutableArray alloc] initWithCapacity: 1]; displayingAllCards = YES; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(scheduledQuizAlert:) name: @"ScheduledQuizAlertNotification" object: nil]; return self; } return nil; } - (void) awakeFromNib; { [scheduleView setDataSource: [stack scheduler]]; [scheduleView selectRow: 0 byExtendingSelection: NO]; [scheduleView setAutoresizesAllColumnsToFit: YES]; [self _setupCardsView]; [self _updateStatus]; [searchButton setStringValue: _(@"Search")]; [[[scheduleView tableColumnWithIdentifier: @"TimeSlots"] headerCell] setStringValue: _(@"Schedule")]; [notes setString: [stack notes]]; //[quizButton setImage: [NSImage imageNamed: @"QuizImage"]]; //[practiceButton setImage: [NSImage imageNamed: @"PracticeImage"]]; [addButton setImage: [NSImage imageNamed: @"AddEntry"]]; [deleteButton setImage: [NSImage imageNamed: @"DeleteEntry"]]; //[insertButton setImage: [NSImage imageNamed: @"InsertSched"]]; //[removeButton setImage: [NSImage imageNamed: @"RemoveSched"]]; } - (void) scheduledQuizAlert: (NSNotification *) notification { if( [[self window] isVisible] ) { [scheduleView reloadItem: [notification object] reloadChildren: NO]; NSBeep(); } } // **************** // Property methods // **************** - (StackModel *) cardStack { return stack; } - (BOOL) displayingAllCards { return displayingAllCards; } - (NSTableView *)cardsView { return cardsView; } // *************** // Action methods // *************** - (void) showInspector: (id)sender { CardInspectorController *inspector; inspector = [CardInspectorController sharedInspector]; [[inspector window] makeKeyAndOrderFront: self]; [[NSNotificationCenter defaultCenter] postNotificationName: @"StackChangedNotification" object: stack]; if( [cardsView numberOfSelectedRows] == 1 ) { CardModel *card = [[stack cards] objectAtIndex: [[[stack filteredCards] objectAtIndex: [cardsView selectedRow]] intValue]]; [[NSNotificationCenter defaultCenter] postNotificationName: @"CardChangedNotification" object: card]; } } - (void) reloadScheduleView: (id) sender; { [scheduleView selectRow: 0 byExtendingSelection: NO]; [scheduleView reloadData]; } - (void) findString: (id)sender { NSString *string; NSArray *cards; int i, row, x; string = [NSString stringWithString: [searchField stringValue]]; cards = [NSArray arrayWithArray: [stack filteredCards]]; row = 0; [foundRows removeAllObjects]; [cardsView deselectAll: self]; for(i=row;i<[cards count];i++) { x = [[cards objectAtIndex: i] intValue]; if([[[[stack cards] objectAtIndex: x] front] rangeOfString: string options: NSCaseInsensitiveSearch].location != NSNotFound || [[[[stack cards] objectAtIndex: x] back] rangeOfString: string options: NSCaseInsensitiveSearch].location != NSNotFound) { [cardsView selectRow: i byExtendingSelection: YES]; [foundRows addObject: [NSNumber numberWithInt: i]]; } } if([foundRows count] >= 1) { [cardsView scrollRowToVisible: [[foundRows objectAtIndex: 0] intValue]]; if([foundRows count] > 1) { currentFoundIndex = 0; [searchButton setStringValue: _(@"Next")]; [searchButton setAction: @selector(_nextFound:)]; } } else { NSBeep(); } } - (void) addEntry: (id)sender { CardModel *aCard; if( !displayingAllCards ) [scheduleView selectRow: 0 byExtendingSelection: NO]; aCard = [[CardModel alloc] init]; [stack addCard: aCard atIndex: 0]; [stack clearFilter]; [stack sortFilteredCardsBySlot]; [cardsView reloadData]; [cardsView selectRow: 0 byExtendingSelection: NO]; [cardsView editColumn: 0 row: [cardsView selectedRow] withEvent: nil select: YES]; [scheduleView reloadItem: [scheduleView itemAtRow: 0] reloadChildren: NO]; } - (void) deleteEntry: (id)sender { NSEnumerator *selectedRows; NSNumber *rowN = nil; NSMutableArray *delArr = [NSMutableArray arrayWithCapacity: 1]; int row = 0; // row index, and reused as Cards index int i = 0; // loop counter int y = 0; // 'last' row selected if( [cardsView numberOfSelectedRows] > 0 ) { if([cardsView editedRow] >= 0) { [cardsView validateEditing]; [cardsView abortEditing]; } selectedRows = [cardsView selectedRowEnumerator]; while( (rowN = [selectedRows nextObject]) ) { if( [rowN intValue] > y ) y = [rowN intValue]; rowN = [[stack filteredCards] objectAtIndex: [rowN intValue]]; [delArr addObject: rowN]; } [delArr sortUsingSelector: @selector(compare:)]; selectedRows = [delArr objectEnumerator]; while( (rowN = [selectedRows nextObject]) ) { row = [rowN intValue]-i; [stack deleteCardAtIndex: row]; i++; } [stack filterForItem: [scheduleView itemAtRow: [scheduleView selectedRow]]]; if( displayingAllCards ) [stack sortFilteredCardsBySlot]; [cardsView reloadData]; if( [cardsView numberOfRows] > 0 ) { y = y-i+1; if( y >= [cardsView numberOfRows] ) y = y-1; [cardsView selectRow: y byExtendingSelection: NO]; } [self _updateStatus]; [scheduleView reloadItem: [scheduleView itemAtRow: [scheduleView selectedRow]] reloadChildren: NO]; [scheduleView reloadItem: [scheduleView itemAtRow: 0] reloadChildren: NO]; [[self document] updateChangeCount: NSChangeDone]; } } - (void) cut: (id)sender { [self copy: sender]; [self deleteEntry: sender]; [[self document] updateChangeCount: NSChangeDone]; } - (void) copy: (id)sender { [stack writeRows: [[cardsView selectedRowEnumerator] allObjects] toPasteboard: [NSPasteboard generalPasteboard]]; } - (void) paste: (id)sender { NSPasteboard *pboard = [NSPasteboard generalPasteboard]; NSArray *theData = [[pboard propertyListForType: PopupCardsPboardType] objectForKey: @"Cards"]; if( [theData count] > 0 ) { [stack insertData: theData atRow: 0 inTableView: cardsView]; } } - (void) insertCardsAtTimeZero: (id) sender { NSMutableDictionary *scheduleEntry; NSNumber *row; NSEnumerator *selectedRows; int index = 0; row = [NSNumber new]; if([cardsView editedRow] >= 0) { [cardsView validateEditing]; [cardsView abortEditing]; } if( [cardsView numberOfSelectedRows] > 0 ) { if( ![[stack scheduler] hasSlotZero] ) { [[stack scheduler] createSlotZero]; [scheduleView reloadData]; } index = [[[stack filteredCards] objectAtIndex: [cardsView selectedRow]] intValue]; scheduleEntry = [[[stack scheduler] state] objectAtIndex: 1]; selectedRows = [cardsView selectedRowEnumerator]; while((row = [selectedRows nextObject])) { index = [[[stack filteredCards] objectAtIndex:[row intValue]] intValue]; if( [[[stack cards] objectAtIndex: index] slot] != 0 ) { [[[stack cards] objectAtIndex: index] setSlot: 0]; [[stack scheduler] incrementCardsInEntry: scheduleEntry]; } } [scheduleView reloadItem: [scheduleView itemAtRow: 1] reloadChildren: NO]; [[self document] updateChangeCount: NSChangeDone]; } } // *************** // Private methods // *************** - (void) _updateStatus { [status setStringValue:[NSString stringWithFormat: _(@"%1$i entries, %2$i selected"), [[stack filteredCards] count], [cardsView numberOfSelectedRows]]]; } - (void) _editHeader: (id) sender { NSText *t; NSTableColumn *tc; if((sender == cardsView) && [cardsView clickedRow] < 0) { t = [[self window] fieldEditor: YES forObject: self]; if ([t superview] != nil) { if ([t resignFirstResponder] == NO) { return; } } _to = nil; tc = [[cardsView tableColumns] objectAtIndex: [cardsView clickedColumn]]; _ec = [[tc headerCell] copy]; _cc = [tc headerCell]; [_ec setStringValue: [[tc headerCell] stringValue]]; [_ec setEditable: YES]; [(NSTableHeaderCell *)_ec setTextColor: [NSColor blackColor]]; [(NSTableHeaderCell *)_ec setBackgroundColor: [NSColor whiteColor]]; _to = [_ec setUpFieldEditorAttributes: t]; [_ec editWithFrame: [[cardsView headerView] headerRectOfColumn: [cardsView clickedColumn]] inView: [cardsView headerView] editor: _to delegate: self event: nil]; } } - (void) _nextFound: (id) sender { if(currentFoundIndex < [foundRows count]) { currentFoundIndex++; [cardsView scrollRowToVisible: [[foundRows objectAtIndex: currentFoundIndex] intValue]]; if( currentFoundIndex == [foundRows count]-1) { currentFoundIndex=0; [searchButton setStringValue: _(@"Search")]; [searchButton setAction: @selector(findString:)]; } } } - (void) _setupCardsView { [cardsView setDataSource: stack]; [[[cardsView tableColumnWithIdentifier: @"Front"] headerCell] setStringValue:[stack frontTitle]]; [[[cardsView tableColumnWithIdentifier: @"Back"] headerCell] setStringValue: [stack backTitle]]; #ifndef __APPLE__ [cardsView setRowHeight: [[NSFont systemFontOfSize: [NSFont systemFontSize]] boundingRectForFont].size.height+5]; #endif [cardsView setDoubleAction: @selector(_editHeader:)]; [cardsView setAutoresizesAllColumnsToFit: YES]; [cardsView registerForDraggedTypes: [NSArray arrayWithObject: PopupCardsPboardType]]; if( [cardsView numberOfRows] > 0 ) { [cardsView selectRow: 0 byExtendingSelection: NO]; [[NSNotificationCenter defaultCenter] postNotificationName: NSTableViewSelectionDidChangeNotification object: cardsView]; } } // *************************** // NSWindowController methdods // *************************** - (NSString *) windowTitleForDocumentDisplayName: (NSString *)displayName { if( [[self document] fileName] != nil ) { return [NSString stringWithFormat: @"%@ - %@", displayName, [[[self document] fileName] stringByDeletingLastPathComponent]]; } return displayName; } - (BOOL) shouldCloseDocument { return YES; } - (BOOL) validateMenuItem: (id )menuitem { NSDictionary *item = nil; if( [menuitem tag] >= 51 && [menuitem tag] <=54 ) { if( [scheduleView numberOfSelectedRows] == 1 ) { item = [scheduleView itemAtRow: [scheduleView selectedRow]]; if( [item objectForKey: @"Title"] == nil || [[item objectForKey: @"Title"] isEqualToString: _(@"Time Zero")] ) return YES; } return NO; } return YES; } - (void) windowDidBecomeKey: (NSNotification *)aNotification { if( [aNotification object] == [self window] ); { [[NSNotificationCenter defaultCenter] postNotificationName: @"StackChangedNotification" object: stack]; if( [[stack filteredCards] count] > 0 && [cardsView selectedRow] > -1 ) { CardModel *card = [[stack cards] objectAtIndex: [[[stack filteredCards] objectAtIndex: [cardsView selectedRow]] intValue]]; [[NSNotificationCenter defaultCenter] postNotificationName: @"CardChangedNotification" object: card]; } } } // **************************** // NSTableView delegate methods // **************************** - (void) tableViewSelectionDidChange:(NSNotification *)aNotification { if( [aNotification object] == cardsView ) { if( [cardsView editedRow] >= 0 ) { [cardsView validateEditing]; [cardsView abortEditing]; } if( [[searchButton stringValue] isEqualToString: _(@"Next")] ) { [searchButton setStringValue: _(@"Search")]; [searchButton setAction: @selector(findString:)]; } if( [cardsView numberOfSelectedRows] == 1 ) { CardModel *card = [[stack cards] objectAtIndex: [[[stack filteredCards] objectAtIndex: [cardsView selectedRow]] intValue]]; [[NSNotificationCenter defaultCenter] postNotificationName: @"CardChangedNotification" object: card]; } [cardsView setNeedsDisplay: YES]; [self _updateStatus]; } } - (BOOL) tableView: (NSTableView *) aTableView shouldEditTableColumn: (NSTableColumn *) aTableColumn row: (int) index { if( aTableView == cardsView ) return YES; else return NO; } - (void) tableView: (NSTableView *) aTableView willDisplayCell: (id) aCell forTableColumn: (NSTableColumn *) aTableColumn row: (int) rowIndex { int x; if( displayingAllCards ) { x = [[[stack filteredCards] objectAtIndex: rowIndex] intValue]; if( [[[stack cards] objectAtIndex: x] slot] != NOSLOT) { [aCell setTextColor: [NSColor darkGrayColor]]; } else { [aCell setTextColor: [NSColor blackColor]]; } } else { [aCell setTextColor: [NSColor blackColor]]; } } // ****************************** // NSOutlineView delegate methods // ****************************** - (void) outlineView: (NSOutlineView *) aOutlineView willDisplayCell: (id) aCell forTableColumn: (NSTableColumn *) aTableColumn item: (id) item { if( [item objectForKey: @"Title"] ) [aCell setFont: [NSFont boldSystemFontOfSize: 0]]; else [aCell setFont: [NSFont systemFontOfSize: 0]]; if( [[item objectForKey: @"Title"] isEqual: @"All Cards"] ) [aCell setTextColor: [NSColor colorWithCalibratedRed: 0.003 green: 0.003 blue: 0.550 alpha: 1]]; else if( [item objectForKey: @"Time"] && [[item objectForKey: @"Time"] timeIntervalSinceNow] <= 1.5 ) [aCell setTextColor: [NSColor colorWithCalibratedRed: 0.550 green: 0.003 blue: 0.003 alpha: 1]]; else [aCell setTextColor: [NSColor blackColor]]; } - (void) outlineViewSelectionDidChange: (NSNotification *) aNotification { [stack filterForItem: [scheduleView itemAtRow: [scheduleView selectedRow]]]; displayingAllCards = NO; if( [scheduleView selectedRow] == 0 ) { [stack sortFilteredCardsBySlot]; displayingAllCards = YES; } [cardsView reloadData]; if( [cardsView numberOfRows] > 0 ) { [cardsView selectRow: 0 byExtendingSelection: NO]; [[NSNotificationCenter defaultCenter] postNotificationName: NSTableViewSelectionDidChangeNotification object: cardsView]; } [self _updateStatus]; if( [cardsView numberOfRows] > 0 && ([[scheduleView itemAtRow: [scheduleView selectedRow]] objectForKey: @"Title"] == nil || [[[scheduleView itemAtRow: [scheduleView selectedRow]] objectForKey: @"Title"] isEqualToString: _(@"Time Zero")]) ) { [quizButton setEnabled: YES]; [practiceButton setEnabled: YES]; } else { [quizButton setEnabled: NO]; [practiceButton setEnabled: NO]; } } // *************************************** // NSTextField/NSTextView delagate methods // *************************************** - (void) textDidEndEditing: (NSNotification *)aNotification { if( [aNotification object] == _to ) { [_ec endEditing: _to]; [_cc setStringValue: [[_to string] copy]]; RELEASE(_ec); [stack setFrontTitle: [[[cardsView tableColumnWithIdentifier: @"Front"] headerCell] stringValue]]; [stack setBackTitle: [[[cardsView tableColumnWithIdentifier: @"Back"] headerCell] stringValue]]; [[self document] updateChangeCount: NSChangeDone]; } } - (void) textDidChange: (NSNotification *)aNotification { if( [aNotification object] == notes ) { [stack setNotes: [notes string]]; [[self document] updateChangeCount: NSChangeDone]; } } - (void) controlTextDidChange: (NSNotification *)aNotification { if( [aNotification object] == searchField ) { [searchButton setStringValue: _(@"Search")]; [searchButton setAction: @selector(findString:)]; } } @end