This is ToDoController.m in view mode; [Download] [Up]
/* * 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
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.