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.