home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1 / Nebula One.iso / Misc / RZToDoList / Source / ToDoController.m < prev    next >
Encoding:
Text File  |  1995-06-12  |  16.6 KB  |  829 lines

  1. /* 
  2.  * ToDoInspector - controller for the ToDoList application
  3.  *
  4.  * You may freely copy, distribute and reuse the code in this example.
  5.  * This code is provided AS IS without warranty of any kind, expressed 
  6.  * or implied, as to its fitness for any particular use.
  7.  *
  8.  * Copyright 1995 Ralph Zazula (rzazula@next.com).  All Rights Reserved.
  9.  *
  10.  */
  11.  
  12. #import "ToDoController.h"
  13. #import "ToDoItem.h"
  14. #import "ToDoList.h"
  15. #import "ToDoBrowserCell.h"
  16. #import "ToDoInspector.h"
  17. #import "_ToDoInspector.h"
  18. #import "ToDoMatrix.h"
  19. #import "ToDoServer.h"
  20. #import "lock_file.h"
  21. #import "ObjectError.h"
  22.  
  23. //static DPSTimedEntry updateEntry;
  24.  
  25. #define MATRIX [browser matrixInColumn:0]
  26.  
  27. @implementation ToDoController
  28.  
  29. void teUpdate(DPSTimedEntry tag, double now, void *self)
  30. {
  31. //    [(ToDoController *)self update];
  32. }
  33.  
  34. + initialize
  35. {
  36.     char path[MAXPATHLEN+1];
  37.     struct stat st;
  38.     static NXDefaultsVector Defaults = {
  39.         {"ShowPrivate","NO"},
  40.         {"ShowCompleted","NO"},
  41.         {"ShowPending","YES"},
  42.         {NULL}
  43.     };
  44.     
  45.     if([self class] == [ToDoController class]) {
  46.         [self setVersion:4];
  47.  
  48.         NXRegisterDefaults([NXApp appName], Defaults);
  49.     
  50.         //
  51.         // create the path ~/Library/RZToDo
  52.         //
  53.         sprintf(path, "%s/Library/RZToDo", NXHomeDirectory());
  54.         if(stat(path, &st) != 0) {
  55.             if(!mkdir(path, 0700) || errno == EEXIST) {
  56.             } else {
  57.                 NXRunAlertPanel("Library","Error creating folder: RZToDo in: %s/Library",
  58.                     0,0,0,NXHomeDirectory);
  59.             }
  60.         }
  61.     }
  62.     return self;
  63. }
  64.  
  65. - appDidInit:sender
  66. {
  67.     char path[MAXPATHLEN+1];
  68.     NXTypedStream *ts;
  69.     id prototypeCell = [[ToDoBrowserCell alloc] init];
  70.     const char *sendTypes[3];
  71.     const char *returnTypes[3];
  72.     sendTypes[0] = NXAsciiPboardType;
  73.     sendTypes[1] = NXRTFPboardType;
  74.     sendTypes[2] = NULL;
  75.     returnTypes[0] = NXAsciiPboardType;
  76.     returnTypes[1] = NXRTFPboardType;
  77.     returnTypes[2] = NULL;
  78.     
  79.     [ObjectError setup];
  80.  
  81.     sprintf(path, TODO_FILE, NXHomeDirectory());
  82.     
  83.     if(lock_file(path) == LOCK_EXIST) {
  84.         if(NXRunAlertPanel("Warning", "The ToDo List is locked!", 
  85.             "Open Anyway", "Quit", NULL) != NX_ALERTDEFAULT) {
  86.             [sender terminate:self];
  87.         }
  88.     }
  89.         
  90.     ts = NXOpenTypedStreamForFile(path, NX_READONLY);
  91.     if(ts) {
  92.         NX_DURING
  93.         todoList = NXReadObject(ts);
  94.         NXCloseTypedStream(ts);
  95.         NX_HANDLER
  96.         NX_ENDHANDLER
  97.     }
  98.     
  99.     if(todoList) {
  100.         if(![todoList isKindOf:[ToDoList class]]) {
  101.             id newList = [[ToDoList alloc] init];
  102.             [newList appendList:todoList];
  103.             [todoList free];
  104.             todoList = newList;
  105.         }
  106.     } else {
  107.         todoList = [[ToDoList alloc] init];
  108.     }
  109.  
  110.     [prototypeCell clearTabs];
  111.     [prototypeCell addFixedTab:4];
  112.     [prototypeCell addFixedTab:24];        
  113.     [prototypeCell addFixedTab:-85];
  114.     [browser setCellPrototype:prototypeCell];
  115.         
  116.     [splitView addSubview:browser];
  117.     [splitView addSubview:bodyBox];
  118.     [theText setDelegate:self];
  119.             
  120.     [browser setMatrixClass:[ToDoMatrix class]];
  121.     [browser setDoubleAction:@selector(doubleClick:)];
  122.  
  123.     showPending = !strcmp(NXGetDefaultValue([NXApp appName], "ShowPending"), "YES");
  124.     showCompleted = !strcmp(NXGetDefaultValue([NXApp appName], "ShowCompleted"), "YES");
  125.     showPrivate = !strcmp(NXGetDefaultValue([NXApp appName], "ShowPrivate"), "YES");
  126.  
  127.     [self update];
  128.     
  129.     [window setFrameUsingName:"ToDoDocument"];
  130.     [window setFrameAutosaveName:"ToDoDocument"];
  131.     [window makeKeyAndOrderFront:nil];
  132.     [NXApp setAutoupdate:YES];
  133.     if([self selectedItems]) {
  134.         [self displayItem:[[self selectedItems] lastObject]];
  135.     }
  136.     
  137. //    updateEntry = DPSAddTimedEntry(1.0, teUpdate, self, NX_BASETHRESHOLD);
  138.  
  139.     /* start up the DO server */
  140.     [[[ToDoServer alloc] init] _setController:self];
  141.     
  142.     /* services support */
  143.     [NXApp registerServicesMenuSendTypes:sendTypes andReturnTypes:returnTypes];
  144.     [[NXApp appListener] setServicesDelegate:self];
  145.  
  146.     return self;
  147. }
  148.  
  149. - appWillTerminate:sender
  150. {
  151.     char path[MAXPATHLEN+1];
  152.  
  153.     if([window isDocEdited]) {
  154.         switch(NXRunAlertPanel("Quit","Save changes to to-do list?", 
  155.             "Yes", "No", "Cancel")) {
  156.             case NX_ALERTDEFAULT :
  157.                 [self save:nil];
  158.                 break;
  159.                 
  160.             case NX_ALERTALTERNATE :
  161.                 break;
  162.                 
  163.             case NX_ALERTOTHER :
  164.                 return nil;
  165.         }
  166.     }
  167.     
  168.     sprintf(path, TODO_FILE, NXHomeDirectory());
  169.     unlock_file(path);
  170.     
  171.     return self;
  172. }
  173.  
  174. - info:sender
  175. {
  176.     if(!infoPanel) {
  177.         [NXApp loadNibSection:"Info.nib" owner:self withNames:NO];
  178.         [infoPanel setFrameUsingName:"ToDoInfo"];
  179.         [infoPanel setFrameAutosaveName:"ToDoInfo"];
  180.     }
  181.     [infoPanel makeKeyAndOrderFront:self];
  182.     return self;
  183. }
  184.  
  185. - inspector:sender
  186. {
  187.     if(!inspector) {
  188.         inspector = [[ToDoInspector alloc] init];
  189.         [inspector _setController:self];
  190.     }
  191.     [[inspector window] orderFront:nil];
  192.     return self;
  193. }
  194.  
  195. - dirty:sender
  196. {
  197.     [window setDocEdited:YES];
  198.     return self;
  199. }
  200.  
  201. - clean:sender
  202. {
  203.     [window setDocEdited:NO];
  204.     return self;
  205. }
  206.  
  207. - disableEditing
  208. {
  209.     [subjectField setEditable:NO];
  210.     [theText setEditable:NO];
  211.     [dateField setEditable:NO];
  212.     return self;
  213. }
  214.  
  215. - enableEditing
  216. {
  217.     [subjectField setEditable:YES];
  218.     [theText setEditable:YES];
  219.     [dateField setEditable:YES];
  220.     return self;
  221. }
  222.  
  223. - (BOOL)showPending                { return showPending; }
  224. - (BOOL)showCompleted            { return showCompleted; }
  225. - (BOOL)showPrivate                { return showPrivate; }
  226. - setShowPending:(BOOL)flag    { showPending = flag; return self; }
  227. - setShowCompleted:(BOOL)flag    { showCompleted = flag; return self; }
  228. - setShowPrivate:(BOOL)flag    { showPrivate = flag; return self; }
  229.  
  230. - save:sender
  231. {
  232.     char path[MAXPATHLEN+1];
  233.     NXTypedStream *ts;
  234.     BOOL failed = NO;
  235.     
  236.     sprintf(path, TODO_FILE, NXHomeDirectory());
  237.     
  238.     /* force saving of any edits in progress */
  239.     if([window makeFirstResponder:window]) {
  240.         [window endEditingFor:nil];
  241.     }
  242.     
  243.     if(todoList) {
  244.         NX_DURING
  245.         ts = NXOpenTypedStreamForFile(path, NX_WRITEONLY);
  246.         NXWriteRootObject(ts, todoList);
  247.         NXCloseTypedStream(ts);
  248.         NX_HANDLER
  249.         failed = YES;
  250.         NX_ENDHANDLER
  251.         if(!failed) {
  252.             [self clean:nil];
  253.         } else {
  254.             NXRunAlertPanel("Error","Save Failed!",NULL,NULL,NULL);
  255.         }
  256.     }
  257.         
  258.     return self;
  259. }
  260.  
  261. - (long)dueDateFrom:(const char *)s
  262. {
  263.     struct tm newTime;
  264.     long curTime;
  265.     char *slash;
  266.     const char *c;
  267.         
  268.     if(!s) {
  269.         return 0;
  270.     }
  271.     
  272.     c = s;
  273.     curTime = time(NULL);
  274.     newTime = *localtime(&curTime);
  275.     newTime.tm_min++;
  276.  
  277.     newTime.tm_mon = atoi(c)-1;
  278.     
  279.     slash = index(c, '/');
  280.     if(slash) {
  281.         c = slash+1;
  282.         newTime.tm_mday = atoi(c);
  283.         slash = index(c, '/');
  284.         if(slash) {
  285.             c = slash+1;
  286.             newTime.tm_year = atoi(c);
  287.         }
  288.     }
  289.     
  290.     return mktime(&newTime);
  291.     
  292.     /* cheesey parsing */
  293.     
  294.     if(sscanf(s, "%d/%d/%d", 
  295.         &newTime.tm_mon, &newTime.tm_mday, &newTime.tm_year) != EOF) {
  296.     
  297.         
  298.         return  mktime(&newTime);
  299.     }
  300.     
  301.     return 0;
  302. }
  303.  
  304. - new:sender
  305. {
  306.     id item = [[ToDoItem alloc] init];
  307.     
  308.     /* force saving of any edits in progress */
  309.     if([window makeFirstResponder:window]) {
  310.         [window endEditingFor:nil];
  311.     }
  312.  
  313.     if(!todoList) {
  314.         todoList = [[ToDoList alloc] init];
  315.     }
  316.     [item setSubject:"New Item"];
  317.     [todoList addObject:item];
  318.     [self dirty:self];
  319.     [MATRIX clearSelectedCell];
  320.     [self update];
  321.     [self selectItem:item];
  322.     [self displayItem:item];
  323.     [subjectField selectText:self];
  324.     
  325.    return self;
  326. }
  327.  
  328.  
  329. - clear:sender
  330. {
  331.     [[browser window] disableFlushWindow];
  332.     [subjectField setStringValue:""];
  333.     [dateField setStringValue:""];
  334. //    [theText selectAll:nil];
  335. //    [theText clear:nil];
  336.     [theText setText:""];
  337.     [[[browser window] reenableFlushWindow] flushWindow];
  338.    return self;
  339. }
  340.  
  341. - doubleClick:sender
  342. {
  343.     [subjectField selectText:self];
  344.    return self;
  345. }
  346.  
  347. - modify:sender
  348. {
  349.     id itemList = [self selectedItems];
  350.     id item;
  351.     
  352.     if(!itemList) {
  353.         NXBeep();
  354.         return nil;
  355.     }
  356.     
  357.     if([itemList count] > 1) {
  358.         NXBeep();
  359.         return nil;
  360.     }
  361.     
  362.     item = [itemList lastObject];
  363.     
  364.     [item setSubject:[subjectField stringValue]];
  365.     [item setDueDate:[self dueDateFrom:[dateField stringValue]]];
  366.     [item setDataFromText:theText];
  367.     [self dirty:self];
  368.     [self update];
  369.    return self;
  370. }
  371.  
  372. - remove:sender
  373. {
  374.     id itemList = [self selectedItems];
  375.     int i;
  376.  
  377.     if(!itemList) {
  378.         NXBeep();
  379.         return nil;
  380.     }
  381.     
  382.     if(NXRunAlertPanel("Delete","Really delete selected item(s)?",
  383.         "Delete","Cancel",NULL) == NX_ALERTALTERNATE) {
  384.         return self;
  385.     }
  386.         
  387.     for(i=0; i<[itemList count]; i++) {
  388.         [[todoList removeObject:[itemList objectAt:i]] free];
  389.     }
  390.     
  391.     [MATRIX clearSelectedCell];
  392.     [self clear:nil];
  393.     [self dirty:self];
  394.     [self update];
  395.     
  396.    return self;
  397. }
  398.  
  399. - timestamp:sender
  400. /* insert a time stamp */
  401. {
  402.     char buf[28];
  403.     time_t t = time(NULL);
  404.     NXSelPt selBegin, selEnd;
  405.     id itemList;
  406.     
  407.     /* only valid for a single selection */
  408.     itemList = [self selectedItems];
  409.     
  410.     if(!itemList || [itemList count] > 1) {
  411.         NXBeep();
  412.         return nil;
  413.     }
  414.  
  415.     sprintf(buf, "[%s", ctime(&t));
  416.     buf[25] = ']';        
  417.     buf[26] = '\n';        
  418.     buf[27] = '\0';
  419.     
  420.     /* check to see if the text has a selection */
  421.     [theText getSel:&selBegin :&selEnd];
  422.     if(selBegin.cp < 0) {
  423.         /* no selection, make one */
  424.         int textlength = [theText textLength];
  425.         [theText setSel:textlength :textlength];
  426.     } else if(selBegin.cp != selEnd.cp) {
  427.         /* something is selected, don't nuke it! */
  428.         [theText setSel:selEnd.cp :selEnd.cp];
  429.     }
  430.     [theText replaceSel:buf];
  431.     /* buggy Text doesn't send textDidChange: after the above... */
  432.     [self textDidChange:theText];
  433.         
  434.     return self;
  435. }
  436.  
  437. - displayItem:(ToDoItem *)item
  438. {
  439.     char *data;
  440.     int len;
  441.     NXStream *stream;
  442.  
  443.     if(item) {
  444.         [[browser window] disableFlushWindow];
  445.         [subjectField setStringValue:[item subject]];
  446.         [dateField setStringValue:[item asciiDueDate]];
  447.         [item getData:&data length:&len];
  448.         if(len) {
  449.             stream = NXOpenMemory(NULL, 0, NX_READWRITE);
  450.             NXWrite(stream, data, len);
  451.             NXSeek(stream, 0, NX_FROMSTART);
  452.             [theText readRichText:stream];
  453.             NXCloseMemory(stream, NX_FREEBUFFER);
  454.         } else {
  455.             [theText setText:""];
  456.         }
  457.         [[[browser window] reenableFlushWindow] flushWindow];
  458.     } else {
  459.         [self clear:nil];
  460.     }
  461.     return self;
  462. }
  463.  
  464. - selectItem:(ToDoItem *)item
  465. {
  466.     id cellList = nil;
  467.     id cell;
  468.     int i;
  469.     
  470.     if(item) {
  471.         cellList = [MATRIX cellList];
  472.         for(i=0; i<[cellList count]; i++) {
  473.             cell = [cellList objectAt:i];
  474.             if([cell item] == item) {
  475.                 [MATRIX selectCell:cell];
  476.                 [MATRIX scrollCellToVisible:i:0];
  477.                 break;
  478.             }
  479.         }
  480.     } else {
  481.         [MATRIX clearSelectedCell];
  482.     }
  483.     
  484.     return self;
  485. }
  486.  
  487. - singleClick:sender
  488. {
  489.     id itemList = [self selectedItems];
  490.     id item;
  491.     
  492.     if(!itemList || ([itemList count] > 1) || ![itemList count]) {
  493.         [self clear:self];
  494.         [self disableEditing];
  495.         return self;
  496.     }
  497.     
  498.     item = [itemList lastObject];
  499.     
  500.     if(!item) {
  501.         return self;
  502.     }
  503.     
  504.     [self enableEditing];
  505.     [self displayItem:item];
  506.     
  507.     /* 
  508.      * selecting the subject text field here makes things like services->mail
  509.      * mail the text field contents instead of the ToDoItem...  Don't do it
  510.      * if you get services implemented.
  511.      */
  512. //    [subjectField selectText:self];
  513.     
  514.    return self;
  515. }
  516.  
  517. - update
  518. /*
  519.  * redisplay the browser
  520.  */
  521. {
  522.     id itemList = [self selectedItems];
  523.     id cellList;
  524.     int i,j;
  525.     id cell;
  526.     NXSize cellSize;
  527.     BOOL madeSelection = NO;
  528.     
  529.     [[browser window] disableFlushWindow];
  530.  
  531.     [browser loadColumnZero];
  532.     [[browser matrixInColumn:0] getCellSize:&cellSize];
  533.     cellSize.height = 18;
  534.     [[browser matrixInColumn:0] setCellSize:&cellSize];
  535.     [[browser matrixInColumn:0] sizeToCells];
  536.     
  537.     cellList = [MATRIX cellList];
  538.     for(i=0; i<[itemList count]; i++) {
  539.         for(j=0; j<[cellList count]; j++) {
  540.             cell = [cellList objectAt:j];
  541.             if([cell item] == [itemList objectAt:i]) {
  542.                 [MATRIX selectCell:cell];
  543.                 [MATRIX scrollCellToVisible:j:0];
  544.                 madeSelection = YES;
  545.             }
  546.         }
  547.     }
  548.     [browser display];
  549.     [[[browser window] reenableFlushWindow] flushWindow];
  550.  
  551.     if(!madeSelection) {
  552.         [self clear:nil];
  553.     }
  554.  
  555.     return self;
  556. }
  557.  
  558. /*** as the browser's delegate ***/
  559.  
  560. - _fillCell:theCell forItem:(ToDoItem *)item
  561. {
  562.     char    font,color;
  563.     
  564.     if([item isPrivate]) {
  565.         color = FONT_DKGRAY;
  566.     } else {
  567.         color = FONT_BLACK;
  568.     }
  569.     if([item isCompleted]) {
  570.         font = FONT_ITALIC;
  571.     } else {
  572.         font = FONT_BOLD;
  573.     }
  574.     
  575.     switch([item type]) {
  576.         case TODO_TYPE_NORMAL :
  577.             [theCell setImageNamed:"i-2do-item" at:0];
  578.             break;
  579.         case TODO_TYPE_APPOINTMENT :
  580.             [theCell setImageNamed:"Appointment" at:0];
  581.             break;
  582.         case TODO_TYPE_LOWPRIORITY :
  583.             [theCell setImageNamed:"LowPriority" at:0];
  584.             break;
  585.         case TODO_TYPE_HIGHPRIORITY :
  586.             [theCell setImageNamed:"HighPriority" at:0];
  587.             break;
  588.     }
  589.     
  590.     [theCell setText:[item subject] at:1 font:font color:color];
  591.     if([item isCompleted]) {
  592.         [theCell setText:"Fin: %s" at:2 color:color, [item asciiCompletedDate]];
  593.     } else {
  594.         if([item type] == TODO_TYPE_APPOINTMENT) {
  595.             [theCell setText:"On: %s" at:2 color:color, [item asciiDueDate]];
  596.         } else {
  597.             [theCell setText:"Due: %s" at:2 color:color, [item asciiDueDate]];
  598.         }
  599.     }
  600.     [theCell setItem:item];
  601.     
  602.     return self;
  603. }
  604.  
  605. - (int)browser:sender fillMatrix:matrix inColumn:(int)column
  606. {
  607.     int    i, count = 0;
  608.     ToDoItem *item;
  609.     id        theCell = nil;
  610.     
  611.     [todoList makeObjectsPerform:@selector(adjustPriority)];
  612.     [todoList sort];
  613.     
  614.     /* Set the matrix to have the right number of cells. */
  615.     [matrix renewRows:0 cols:1];
  616.  
  617.     /*
  618.      * For each cell set its value, set whether it is a leaf
  619.      * or not and mark it loaded.
  620.      */
  621.     for (i=0; i<[todoList count]; i++) {
  622.         item = [todoList objectAt:i];
  623.         if((([item isPrivate] && showPrivate) || ![item isPrivate]) &&
  624.             ([item isCompleted] ? showCompleted : showPending)) {
  625.             [matrix addRow];
  626.             theCell = [matrix cellAt:count :0];
  627.             [self _fillCell:theCell forItem:item];
  628.             [theCell setLeaf:YES];
  629.             [theCell setLoaded:YES];
  630.             [theCell setTag:i];
  631.             count++;
  632.         }
  633.     }
  634.         
  635.     /* Return the number of rows. */
  636.     return count;
  637. }
  638.  
  639. /*** as the window's delegate ***/
  640.  
  641. - windowWillClose:sender
  642. {
  643.     [NXApp terminate:sender];
  644.     return nil;
  645. }
  646.  
  647. - selectedItems
  648. {
  649.     static id itemList = nil;
  650.     id cellList = nil;
  651.     int i;
  652.     
  653.     
  654.     cellList = [MATRIX getSelectedCells:nil];
  655.     
  656.     if(cellList ? [cellList count] : 0) {
  657.         if(itemList) {
  658.             [itemList empty];
  659.         } else {
  660.             itemList = [[List alloc] initCount:[cellList count]];
  661.         }
  662.         for(i=0; i<[cellList count]; i++) {
  663.             [itemList addObject:[[cellList objectAt:i] item]];
  664.         }
  665.         [cellList free];
  666.     } else {
  667.         return nil;
  668.     }
  669.     
  670.     return itemList;
  671. }
  672.  
  673. /*** for the DO server ***/
  674.  
  675. - toDoList         { return todoList; }
  676.  
  677. - addItem:(id <ToDoItems>)anItem
  678. {
  679.     [todoList addObject:[[ToDoItem alloc] initFromItem:anItem]];
  680.     [self dirty:self];
  681.     [self update];
  682.     return self;
  683. }
  684.  
  685. - removeItem:anItem
  686. {
  687.     return self;
  688. }
  689.         
  690. /*** as the SplitView's delegate ***/
  691.  
  692. #define SPLITVIEWSIZE 80.0
  693.  
  694. - splitView:sender getMinY:(NXCoord *)minY maxY:(NXCoord *)maxY ofSubviewAt:(int)offset
  695. {
  696.     *minY = SPLITVIEWSIZE;
  697.     *maxY -= SPLITVIEWSIZE - 20;
  698.     if ( *maxY < SPLITVIEWSIZE  - 20)
  699.         *maxY = SPLITVIEWSIZE - 20;
  700.     return self;
  701. }
  702.  
  703. - splitView:sender resizeSubviews:(const NXSize *)oldSize
  704. {
  705.     NXRect lower, upper;
  706.     float delta;
  707.     
  708.     [[sender window] disableDisplay];
  709.     [sender adjustSubviews];
  710.     [browser getFrame:&upper];
  711.     [bodyBox getFrame:&lower];
  712.     if (upper.size.height < SPLITVIEWSIZE) {
  713.         delta = SPLITVIEWSIZE - upper.size.height;
  714.         upper.size.height=SPLITVIEWSIZE;
  715.         lower.size.height-=delta;
  716.         [browser setFrame:&upper];
  717.         [bodyBox setFrame:&lower];
  718.         }
  719.      
  720.     [[sender window] reenableDisplay];
  721.     [[sender window] display];
  722.  
  723.     return self;
  724. }
  725.  
  726. - splitViewDidResizeSubviews:sender
  727. {
  728.     NXRect upper;
  729.     [browser getFrame:&upper];
  730.     if (floor((upper.size.height + upper.origin.y)/2) != 
  731.          (upper.size.height + upper.origin.y)/2) {
  732.             upper.size.height--;
  733.         [browser setFrame:&upper];
  734.     }
  735.     return self;
  736. }
  737.  
  738. /*** as the text delegate for the UI ***/
  739.  
  740. static BOOL _changed = NO;
  741.  
  742. - textDidChange:sender
  743. {
  744.     if(([sender superview] == subjectField) || ([sender superview] == dateField) ||
  745.         (sender == theText)) {
  746.         _changed = YES;
  747.         [self dirty:self];
  748.     }
  749.     return self;
  750. }
  751.  
  752. - (BOOL)textWillEnd:sender
  753. {
  754.     id itemList = nil;
  755.     id item;
  756.     BOOL dateChanged = NO;
  757.     
  758.     if(!_changed) {
  759.         return NO;
  760.     }
  761.  
  762.     itemList = [self selectedItems];
  763.     
  764.     if(!itemList || [itemList count] > 1) {
  765.         return NO;
  766.     }
  767.         
  768.     item = [itemList lastObject];
  769.     
  770.     if([sender superview] == subjectField) {
  771.         [item setSubject:[subjectField stringValue]];
  772.     } else if ([sender superview] == dateField) {
  773.         [item setDueDate:[self dueDateFrom:[dateField stringValue]]];
  774.         dateChanged = YES;
  775.     } else if (sender == theText) {
  776.         [item setDataFromText:theText];
  777.     }
  778.     
  779.     if(!dateChanged) {
  780.         /* no reordering */
  781.         [self _fillCell:[MATRIX selectedCell] forItem:item];
  782.         [browser display];
  783.     } else {
  784.         /* call update in case new date causes re-ordering */
  785.         [self update];
  786.     }
  787.     
  788.     _changed = NO;
  789.     
  790.     return NO;
  791. }
  792.  
  793. /* services support */
  794.  
  795. extern BOOL IncludesType(const NXAtom *types, NXAtom type);
  796.  
  797. - addItem:(id)pasteboard userData:(const char *)userData error:(char **)msg
  798. {
  799.     [self pasteFromPasteboard:pasteboard];
  800.     return self;
  801. }
  802.  
  803. - validRequestorForSendType:(NXAtom)sendType andReturnType:(NXAtom)returnType
  804.  {
  805.     id itemList = nil;
  806.      NXAtom validSendTypes[] = {NXAsciiPboardType, NXRTFPboardType, NULL};        
  807.  
  808.     itemList = [self selectedItems];
  809.     
  810.     if(!itemList || ![itemList count]) {
  811.         return NO;
  812.     }
  813.  
  814.     if(!returnType || !*returnType) {
  815.         /* no return type, only good for valid send type */
  816.         if(IncludesType(validSendTypes, sendType)) {
  817.             return self;
  818.         }
  819.     }
  820.     return nil;
  821. }
  822.  
  823. - (BOOL)writeSelectionToPasteboard:(Pasteboard *)pboard types:(NXAtom *)types
  824. {
  825.     return ([self copyToPasteboard:pboard] ? YES : NO);
  826. }
  827.  
  828. @end
  829.