home *** CD-ROM | disk | FTP | other *** search
- /*
- * ToDoInspector - controller for the ToDoList application
- *
- * You may freely copy, distribute and reuse the code in this example.
- * This code is provided AS IS without warranty of any kind, expressed
- * or implied, as to its fitness for any particular use.
- *
- * Copyright 1995 Ralph Zazula (rzazula@next.com). All Rights Reserved.
- *
- */
-
- #import "ToDoController.h"
- #import "ToDoItem.h"
- #import "ToDoList.h"
- #import "ToDoBrowserCell.h"
- #import "ToDoInspector.h"
- #import "_ToDoInspector.h"
- #import "ToDoMatrix.h"
- #import "ToDoServer.h"
- #import "lock_file.h"
- #import "ObjectError.h"
-
- //static DPSTimedEntry updateEntry;
-
- #define MATRIX [browser matrixInColumn:0]
-
- @implementation ToDoController
-
- void teUpdate(DPSTimedEntry tag, double now, void *self)
- {
- // [(ToDoController *)self update];
- }
-
- + initialize
- {
- char path[MAXPATHLEN+1];
- struct stat st;
- static NXDefaultsVector Defaults = {
- {"ShowPrivate","NO"},
- {"ShowCompleted","NO"},
- {"ShowPending","YES"},
- {NULL}
- };
-
- if([self class] == [ToDoController class]) {
- [self setVersion:4];
-
- NXRegisterDefaults([NXApp appName], Defaults);
-
- //
- // create the path ~/Library/RZToDo
- //
- sprintf(path, "%s/Library/RZToDo", NXHomeDirectory());
- if(stat(path, &st) != 0) {
- if(!mkdir(path, 0700) || errno == EEXIST) {
- } else {
- NXRunAlertPanel("Library","Error creating folder: RZToDo in: %s/Library",
- 0,0,0,NXHomeDirectory);
- }
- }
- }
- return self;
- }
-
- - appDidInit:sender
- {
- char path[MAXPATHLEN+1];
- NXTypedStream *ts;
- id prototypeCell = [[ToDoBrowserCell alloc] init];
- const char *sendTypes[3];
- const char *returnTypes[3];
- sendTypes[0] = NXAsciiPboardType;
- sendTypes[1] = NXRTFPboardType;
- sendTypes[2] = NULL;
- returnTypes[0] = NXAsciiPboardType;
- returnTypes[1] = NXRTFPboardType;
- returnTypes[2] = NULL;
-
- [ObjectError setup];
-
- sprintf(path, TODO_FILE, NXHomeDirectory());
-
- if(lock_file(path) == LOCK_EXIST) {
- if(NXRunAlertPanel("Warning", "The ToDo List is locked!",
- "Open Anyway", "Quit", NULL) != NX_ALERTDEFAULT) {
- [sender terminate:self];
- }
- }
-
- ts = NXOpenTypedStreamForFile(path, NX_READONLY);
- if(ts) {
- NX_DURING
- todoList = NXReadObject(ts);
- NXCloseTypedStream(ts);
- NX_HANDLER
- NX_ENDHANDLER
- }
-
- if(todoList) {
- if(![todoList isKindOf:[ToDoList class]]) {
- id newList = [[ToDoList alloc] init];
- [newList appendList:todoList];
- [todoList free];
- todoList = newList;
- }
- } else {
- todoList = [[ToDoList alloc] init];
- }
-
- [prototypeCell clearTabs];
- [prototypeCell addFixedTab:4];
- [prototypeCell addFixedTab:24];
- [prototypeCell addFixedTab:-85];
- [browser setCellPrototype:prototypeCell];
-
- [splitView addSubview:browser];
- [splitView addSubview:bodyBox];
- [theText setDelegate:self];
-
- [browser setMatrixClass:[ToDoMatrix class]];
- [browser setDoubleAction:@selector(doubleClick:)];
-
- showPending = !strcmp(NXGetDefaultValue([NXApp appName], "ShowPending"), "YES");
- showCompleted = !strcmp(NXGetDefaultValue([NXApp appName], "ShowCompleted"), "YES");
- showPrivate = !strcmp(NXGetDefaultValue([NXApp appName], "ShowPrivate"), "YES");
-
- [self update];
-
- [window setFrameUsingName:"ToDoDocument"];
- [window setFrameAutosaveName:"ToDoDocument"];
- [window makeKeyAndOrderFront:nil];
- [NXApp setAutoupdate:YES];
- if([self selectedItems]) {
- [self displayItem:[[self selectedItems] lastObject]];
- }
-
- // updateEntry = DPSAddTimedEntry(1.0, teUpdate, self, NX_BASETHRESHOLD);
-
- /* start up the DO server */
- [[[ToDoServer alloc] init] _setController:self];
-
- /* services support */
- [NXApp registerServicesMenuSendTypes:sendTypes andReturnTypes:returnTypes];
- [[NXApp appListener] setServicesDelegate:self];
-
- return self;
- }
-
- - appWillTerminate:sender
- {
- char path[MAXPATHLEN+1];
-
- if([window isDocEdited]) {
- switch(NXRunAlertPanel("Quit","Save changes to to-do list?",
- "Yes", "No", "Cancel")) {
- case NX_ALERTDEFAULT :
- [self save:nil];
- break;
-
- case NX_ALERTALTERNATE :
- break;
-
- case NX_ALERTOTHER :
- return nil;
- }
- }
-
- sprintf(path, TODO_FILE, NXHomeDirectory());
- unlock_file(path);
-
- return self;
- }
-
- - info:sender
- {
- if(!infoPanel) {
- [NXApp loadNibSection:"Info.nib" owner:self withNames:NO];
- [infoPanel setFrameUsingName:"ToDoInfo"];
- [infoPanel setFrameAutosaveName:"ToDoInfo"];
- }
- [infoPanel makeKeyAndOrderFront:self];
- return self;
- }
-
- - inspector:sender
- {
- if(!inspector) {
- inspector = [[ToDoInspector alloc] init];
- [inspector _setController:self];
- }
- [[inspector window] orderFront:nil];
- return self;
- }
-
- - dirty:sender
- {
- [window setDocEdited:YES];
- return self;
- }
-
- - clean:sender
- {
- [window setDocEdited:NO];
- return self;
- }
-
- - disableEditing
- {
- [subjectField setEditable:NO];
- [theText setEditable:NO];
- [dateField setEditable:NO];
- return self;
- }
-
- - enableEditing
- {
- [subjectField setEditable:YES];
- [theText setEditable:YES];
- [dateField setEditable:YES];
- return self;
- }
-
- - (BOOL)showPending { return showPending; }
- - (BOOL)showCompleted { return showCompleted; }
- - (BOOL)showPrivate { return showPrivate; }
- - setShowPending:(BOOL)flag { showPending = flag; return self; }
- - setShowCompleted:(BOOL)flag { showCompleted = flag; return self; }
- - setShowPrivate:(BOOL)flag { showPrivate = flag; return self; }
-
- - save:sender
- {
- char path[MAXPATHLEN+1];
- NXTypedStream *ts;
- BOOL failed = NO;
-
- sprintf(path, TODO_FILE, NXHomeDirectory());
-
- /* force saving of any edits in progress */
- if([window makeFirstResponder:window]) {
- [window endEditingFor:nil];
- }
-
- if(todoList) {
- NX_DURING
- ts = NXOpenTypedStreamForFile(path, NX_WRITEONLY);
- NXWriteRootObject(ts, todoList);
- NXCloseTypedStream(ts);
- NX_HANDLER
- failed = YES;
- NX_ENDHANDLER
- if(!failed) {
- [self clean:nil];
- } else {
- NXRunAlertPanel("Error","Save Failed!",NULL,NULL,NULL);
- }
- }
-
- return self;
- }
-
- - (long)dueDateFrom:(const char *)s
- {
- struct tm newTime;
- long curTime;
- char *slash;
- const char *c;
-
- if(!s) {
- return 0;
- }
-
- c = s;
- curTime = time(NULL);
- newTime = *localtime(&curTime);
- newTime.tm_min++;
-
- newTime.tm_mon = atoi(c)-1;
-
- slash = index(c, '/');
- if(slash) {
- c = slash+1;
- newTime.tm_mday = atoi(c);
- slash = index(c, '/');
- if(slash) {
- c = slash+1;
- newTime.tm_year = atoi(c);
- }
- }
-
- return mktime(&newTime);
-
- /* cheesey parsing */
-
- if(sscanf(s, "%d/%d/%d",
- &newTime.tm_mon, &newTime.tm_mday, &newTime.tm_year) != EOF) {
-
-
- return mktime(&newTime);
- }
-
- return 0;
- }
-
- - new:sender
- {
- id item = [[ToDoItem alloc] init];
-
- /* force saving of any edits in progress */
- if([window makeFirstResponder:window]) {
- [window endEditingFor:nil];
- }
-
- if(!todoList) {
- todoList = [[ToDoList alloc] init];
- }
- [item setSubject:"New Item"];
- [todoList addObject:item];
- [self dirty:self];
- [MATRIX clearSelectedCell];
- [self update];
- [self selectItem:item];
- [self displayItem:item];
- [subjectField selectText:self];
-
- return self;
- }
-
-
- - clear:sender
- {
- [[browser window] disableFlushWindow];
- [subjectField setStringValue:""];
- [dateField setStringValue:""];
- // [theText selectAll:nil];
- // [theText clear:nil];
- [theText setText:""];
- [[[browser window] reenableFlushWindow] flushWindow];
- return self;
- }
-
- - doubleClick:sender
- {
- [subjectField selectText:self];
- return self;
- }
-
- - modify:sender
- {
- id itemList = [self selectedItems];
- id item;
-
- if(!itemList) {
- NXBeep();
- return nil;
- }
-
- if([itemList count] > 1) {
- NXBeep();
- return nil;
- }
-
- item = [itemList lastObject];
-
- [item setSubject:[subjectField stringValue]];
- [item setDueDate:[self dueDateFrom:[dateField stringValue]]];
- [item setDataFromText:theText];
- [self dirty:self];
- [self update];
- return self;
- }
-
- - remove:sender
- {
- id itemList = [self selectedItems];
- int i;
-
- if(!itemList) {
- NXBeep();
- return nil;
- }
-
- if(NXRunAlertPanel("Delete","Really delete selected item(s)?",
- "Delete","Cancel",NULL) == NX_ALERTALTERNATE) {
- return self;
- }
-
- for(i=0; i<[itemList count]; i++) {
- [[todoList removeObject:[itemList objectAt:i]] free];
- }
-
- [MATRIX clearSelectedCell];
- [self clear:nil];
- [self dirty:self];
- [self update];
-
- return self;
- }
-
- - timestamp:sender
- /* insert a time stamp */
- {
- char buf[28];
- time_t t = time(NULL);
- NXSelPt selBegin, selEnd;
- id itemList;
-
- /* only valid for a single selection */
- itemList = [self selectedItems];
-
- if(!itemList || [itemList count] > 1) {
- NXBeep();
- return nil;
- }
-
- sprintf(buf, "[%s", ctime(&t));
- buf[25] = ']';
- buf[26] = '\n';
- buf[27] = '\0';
-
- /* check to see if the text has a selection */
- [theText getSel:&selBegin :&selEnd];
- if(selBegin.cp < 0) {
- /* no selection, make one */
- int textlength = [theText textLength];
- [theText setSel:textlength :textlength];
- } else if(selBegin.cp != selEnd.cp) {
- /* something is selected, don't nuke it! */
- [theText setSel:selEnd.cp :selEnd.cp];
- }
- [theText replaceSel:buf];
- /* buggy Text doesn't send textDidChange: after the above... */
- [self textDidChange:theText];
-
- return self;
- }
-
- - displayItem:(ToDoItem *)item
- {
- char *data;
- int len;
- NXStream *stream;
-
- if(item) {
- [[browser window] disableFlushWindow];
- [subjectField setStringValue:[item subject]];
- [dateField setStringValue:[item asciiDueDate]];
- [item getData:&data length:&len];
- if(len) {
- stream = NXOpenMemory(NULL, 0, NX_READWRITE);
- NXWrite(stream, data, len);
- NXSeek(stream, 0, NX_FROMSTART);
- [theText readRichText:stream];
- NXCloseMemory(stream, NX_FREEBUFFER);
- } else {
- [theText setText:""];
- }
- [[[browser window] reenableFlushWindow] flushWindow];
- } else {
- [self clear:nil];
- }
- return self;
- }
-
- - selectItem:(ToDoItem *)item
- {
- id cellList = nil;
- id cell;
- int i;
-
- if(item) {
- cellList = [MATRIX cellList];
- for(i=0; i<[cellList count]; i++) {
- cell = [cellList objectAt:i];
- if([cell item] == item) {
- [MATRIX selectCell:cell];
- [MATRIX scrollCellToVisible:i:0];
- break;
- }
- }
- } else {
- [MATRIX clearSelectedCell];
- }
-
- return self;
- }
-
- - singleClick:sender
- {
- id itemList = [self selectedItems];
- id item;
-
- if(!itemList || ([itemList count] > 1) || ![itemList count]) {
- [self clear:self];
- [self disableEditing];
- return self;
- }
-
- item = [itemList lastObject];
-
- if(!item) {
- return self;
- }
-
- [self enableEditing];
- [self displayItem:item];
-
- /*
- * selecting the subject text field here makes things like services->mail
- * mail the text field contents instead of the ToDoItem... Don't do it
- * if you get services implemented.
- */
- // [subjectField selectText:self];
-
- return self;
- }
-
- - update
- /*
- * redisplay the browser
- */
- {
- id itemList = [self selectedItems];
- id cellList;
- int i,j;
- id cell;
- NXSize cellSize;
- BOOL madeSelection = NO;
-
- [[browser window] disableFlushWindow];
-
- [browser loadColumnZero];
- [[browser matrixInColumn:0] getCellSize:&cellSize];
- cellSize.height = 18;
- [[browser matrixInColumn:0] setCellSize:&cellSize];
- [[browser matrixInColumn:0] sizeToCells];
-
- cellList = [MATRIX cellList];
- for(i=0; i<[itemList count]; i++) {
- for(j=0; j<[cellList count]; j++) {
- cell = [cellList objectAt:j];
- if([cell item] == [itemList objectAt:i]) {
- [MATRIX selectCell:cell];
- [MATRIX scrollCellToVisible:j:0];
- madeSelection = YES;
- }
- }
- }
- [browser display];
- [[[browser window] reenableFlushWindow] flushWindow];
-
- if(!madeSelection) {
- [self clear:nil];
- }
-
- return self;
- }
-
- /*** as the browser's delegate ***/
-
- - _fillCell:theCell forItem:(ToDoItem *)item
- {
- char font,color;
-
- if([item isPrivate]) {
- color = FONT_DKGRAY;
- } else {
- color = FONT_BLACK;
- }
- if([item isCompleted]) {
- font = FONT_ITALIC;
- } else {
- font = FONT_BOLD;
- }
-
- switch([item type]) {
- case TODO_TYPE_NORMAL :
- [theCell setImageNamed:"i-2do-item" at:0];
- break;
- case TODO_TYPE_APPOINTMENT :
- [theCell setImageNamed:"Appointment" at:0];
- break;
- case TODO_TYPE_LOWPRIORITY :
- [theCell setImageNamed:"LowPriority" at:0];
- break;
- case TODO_TYPE_HIGHPRIORITY :
- [theCell setImageNamed:"HighPriority" at:0];
- break;
- }
-
- [theCell setText:[item subject] at:1 font:font color:color];
- if([item isCompleted]) {
- [theCell setText:"Fin: %s" at:2 color:color, [item asciiCompletedDate]];
- } else {
- if([item type] == TODO_TYPE_APPOINTMENT) {
- [theCell setText:"On: %s" at:2 color:color, [item asciiDueDate]];
- } else {
- [theCell setText:"Due: %s" at:2 color:color, [item asciiDueDate]];
- }
- }
- [theCell setItem:item];
-
- return self;
- }
-
- - (int)browser:sender fillMatrix:matrix inColumn:(int)column
- {
- int i, count = 0;
- ToDoItem *item;
- id theCell = nil;
-
- [todoList makeObjectsPerform:@selector(adjustPriority)];
- [todoList sort];
-
- /* Set the matrix to have the right number of cells. */
- [matrix renewRows:0 cols:1];
-
- /*
- * For each cell set its value, set whether it is a leaf
- * or not and mark it loaded.
- */
- for (i=0; i<[todoList count]; i++) {
- item = [todoList objectAt:i];
- if((([item isPrivate] && showPrivate) || ![item isPrivate]) &&
- ([item isCompleted] ? showCompleted : showPending)) {
- [matrix addRow];
- theCell = [matrix cellAt:count :0];
- [self _fillCell:theCell forItem:item];
- [theCell setLeaf:YES];
- [theCell setLoaded:YES];
- [theCell setTag:i];
- count++;
- }
- }
-
- /* Return the number of rows. */
- return count;
- }
-
- /*** as the window's delegate ***/
-
- - windowWillClose:sender
- {
- [NXApp terminate:sender];
- return nil;
- }
-
- - selectedItems
- {
- static id itemList = nil;
- id cellList = nil;
- int i;
-
-
- cellList = [MATRIX getSelectedCells:nil];
-
- if(cellList ? [cellList count] : 0) {
- if(itemList) {
- [itemList empty];
- } else {
- itemList = [[List alloc] initCount:[cellList count]];
- }
- for(i=0; i<[cellList count]; i++) {
- [itemList addObject:[[cellList objectAt:i] item]];
- }
- [cellList free];
- } else {
- return nil;
- }
-
- return itemList;
- }
-
- /*** for the DO server ***/
-
- - toDoList { return todoList; }
-
- - addItem:(id <ToDoItems>)anItem
- {
- [todoList addObject:[[ToDoItem alloc] initFromItem:anItem]];
- [self dirty:self];
- [self update];
- return self;
- }
-
- - removeItem:anItem
- {
- return self;
- }
-
- /*** as the SplitView's delegate ***/
-
- #define SPLITVIEWSIZE 80.0
-
- - splitView:sender getMinY:(NXCoord *)minY maxY:(NXCoord *)maxY ofSubviewAt:(int)offset
- {
- *minY = SPLITVIEWSIZE;
- *maxY -= SPLITVIEWSIZE - 20;
- if ( *maxY < SPLITVIEWSIZE - 20)
- *maxY = SPLITVIEWSIZE - 20;
- return self;
- }
-
- - splitView:sender resizeSubviews:(const NXSize *)oldSize
- {
- NXRect lower, upper;
- float delta;
-
- [[sender window] disableDisplay];
- [sender adjustSubviews];
- [browser getFrame:&upper];
- [bodyBox getFrame:&lower];
- if (upper.size.height < SPLITVIEWSIZE) {
- delta = SPLITVIEWSIZE - upper.size.height;
- upper.size.height=SPLITVIEWSIZE;
- lower.size.height-=delta;
- [browser setFrame:&upper];
- [bodyBox setFrame:&lower];
- }
-
- [[sender window] reenableDisplay];
- [[sender window] display];
-
- return self;
- }
-
- - splitViewDidResizeSubviews:sender
- {
- NXRect upper;
- [browser getFrame:&upper];
- if (floor((upper.size.height + upper.origin.y)/2) !=
- (upper.size.height + upper.origin.y)/2) {
- upper.size.height--;
- [browser setFrame:&upper];
- }
- return self;
- }
-
- /*** as the text delegate for the UI ***/
-
- static BOOL _changed = NO;
-
- - textDidChange:sender
- {
- if(([sender superview] == subjectField) || ([sender superview] == dateField) ||
- (sender == theText)) {
- _changed = YES;
- [self dirty:self];
- }
- return self;
- }
-
- - (BOOL)textWillEnd:sender
- {
- id itemList = nil;
- id item;
- BOOL dateChanged = NO;
-
- if(!_changed) {
- return NO;
- }
-
- itemList = [self selectedItems];
-
- if(!itemList || [itemList count] > 1) {
- return NO;
- }
-
- item = [itemList lastObject];
-
- if([sender superview] == subjectField) {
- [item setSubject:[subjectField stringValue]];
- } else if ([sender superview] == dateField) {
- [item setDueDate:[self dueDateFrom:[dateField stringValue]]];
- dateChanged = YES;
- } else if (sender == theText) {
- [item setDataFromText:theText];
- }
-
- if(!dateChanged) {
- /* no reordering */
- [self _fillCell:[MATRIX selectedCell] forItem:item];
- [browser display];
- } else {
- /* call update in case new date causes re-ordering */
- [self update];
- }
-
- _changed = NO;
-
- return NO;
- }
-
- /* services support */
-
- extern BOOL IncludesType(const NXAtom *types, NXAtom type);
-
- - addItem:(id)pasteboard userData:(const char *)userData error:(char **)msg
- {
- [self pasteFromPasteboard:pasteboard];
- return self;
- }
-
- - validRequestorForSendType:(NXAtom)sendType andReturnType:(NXAtom)returnType
- {
- id itemList = nil;
- NXAtom validSendTypes[] = {NXAsciiPboardType, NXRTFPboardType, NULL};
-
- itemList = [self selectedItems];
-
- if(!itemList || ![itemList count]) {
- return NO;
- }
-
- if(!returnType || !*returnType) {
- /* no return type, only good for valid send type */
- if(IncludesType(validSendTypes, sendType)) {
- return self;
- }
- }
- return nil;
- }
-
- - (BOOL)writeSelectionToPasteboard:(Pasteboard *)pboard types:(NXAtom *)types
- {
- return ([self copyToPasteboard:pboard] ? YES : NO);
- }
-
- @end
-