home *** CD-ROM | disk | FTP | other *** search
/ Nebula / nebula.bin / SourceCode / MiniExamples / AskMe / Controller.m < prev    next >
Text File  |  1991-07-04  |  10KB  |  437 lines

  1.  
  2. /* Controller.m
  3.  * 
  4.  *
  5.  * This subclass of Object handles all the user interface actions.
  6.  * 
  7.  *
  8.  * You may freely copy, distribute, and reuse the code in this example.
  9.  * NeXT disclaims any warranty of any kind, expressed or implied, as to its
  10.  * fitness for any particular use.
  11.  *
  12.  */
  13.  
  14. #import "Controller.h"
  15. #import "Localization.h"    // Localization routines
  16. #import "ClockView.h"        // Clock display routines
  17.  
  18. #import <appkit/Matrix.h>
  19. #import <appkit/Cell.h>
  20. #import <appkit/NXBrowser.h>
  21. #import <appkit/NXBrowserCell.h>
  22. #import <appkit/ScrollView.h>
  23. #import <appkit/Text.h>
  24. #import <appkit/Panel.h>
  25.  
  26.  
  27. #import <sys/dir.h> //for getdirentries()
  28. #import <libc.h> 
  29. #import <string.h>
  30.  
  31.  
  32. /* Static Functions to be defined later    */
  33.  
  34. static char **addFile(const char *file, int length, char **list, int count);     
  35. static void freeList(char **list);
  36. static BOOL isOk(const char *s);
  37. static int caseInsensitiveCompare(void *arg1, void *arg2);
  38. static char **fileList;
  39.  
  40. #define MAX_TIME_CHARS 100
  41. #define FILE_NOT_FOUND_MSG LocalString("File %s not found.", NULL, "The message the user receives if the given file is not found. This is normally an internal error")
  42.         
  43. @implementation Controller
  44.  
  45.  
  46. /* Register some meaningful default values for the system, in case the user starts with
  47.  * a virgin one. The following defaults are mostly used for strftime().
  48.  */
  49. + initialize
  50. {
  51.     
  52.     
  53.     static   NXDefaultsVector AskMeDefaults = {
  54.     {"NXDateAndTime", "%a %b %d %H:%M:%S %Z %Y"},
  55.     {"NXDate", "%a %b %d %Y"},
  56.     {"NXTime", "%H:%M:%S %Z"},
  57.     {"NXShortDays", "Sun Mon Tue Wed Thu Fri Sat"},
  58.     {"NXLongDays", "Sunday Monday Tuesday Wednesday Thursday Friday Saturday"
  59. },
  60.     {"NXShortMonths", "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"
  61. },
  62.     {"NXLongMonths", "January February March April May June July August September October November December"},
  63.     {NULL}
  64.     };
  65.  
  66.     NXRegisterDefaults("AskMe", AskMeDefaults);
  67.     
  68.     return self;
  69. }
  70.  
  71.  
  72. /* appDidInit: does miscellaneous initialization */
  73. - appDidInit:sender
  74. {
  75.     
  76.     char timeBuffer[MAX_TIME_CHARS];
  77.     time_t curtime;
  78.     const char *menuTitle, *welcomeMsg;
  79.     NXRect    fieldRect;
  80.     
  81.     
  82.         /* Save window frame for later resizing */
  83.     [myWindow getFrame:&windowFrame];
  84.     
  85.         /* Find the AskMeText directory    */    
  86.     sprintf(&textDirectory[0],"%s",findLocalDir());
  87.  
  88.         /* Change the menu title */
  89.     menuTitle = LocalStringFromTable("Init", "Ask Me", NULL,
  90.          "main menu title");
  91.     [[NXApp mainMenu] setTitle:menuTitle];
  92.  
  93.         /* Set up the split view    */
  94.     [self initSplitView];
  95.     
  96.         /* Set up the text browser and bring up welcome message    */
  97.     [myBrowser setDelegate:self];
  98.     [myBrowser loadColumnZero];
  99.     welcomeMsg = LocalStringFromTable("Init", "Welcome", NULL,
  100.          "welcome message");
  101.     [self showTextFile:welcomeMsg:0:textDirectory];
  102.     
  103.     /* Show current date and time - Note: date and time are not
  104.      * localized yet (ie they are still English strings, or they follow
  105.      * the GLOBAL settings of the language preference you chose last).
  106.      * One could possibly perform a translation
  107.      * before displaying here.
  108.      */
  109.     
  110.     curtime = time(0);
  111.     LocalDate(timeBuffer, MAX_TIME_CHARS, &curtime);
  112.     [dateField setStringValue:&timeBuffer[0]];
  113.     
  114.     /* Set up a timed entry to update the time periodically */
  115.     [timeOfDayField getFrame:&fieldRect];
  116.     timeOfDayField = [ [ClockView alloc]initFrame:&fieldRect];
  117.     
  118.     return self;
  119. }
  120.  
  121. - appWillTerminate: sender
  122. {
  123.     
  124.     /* remove the timed entry */
  125.     [timeOfDayField stopTimedEntry];
  126.         
  127.     return self;
  128. }
  129.  
  130.  
  131.  
  132.  
  133. /* splitview support */
  134.  
  135. - initSplitView
  136. {
  137.     [mySplitView setDelegate:self];
  138.     
  139.       [mySplitView addSubview:myBrowser];
  140.     [mySplitView addSubview:myScrollView];
  141.     [mySplitView display];
  142.     
  143.     return self;
  144. }
  145.  
  146.  
  147.  
  148.  
  149.  
  150. /* Adjust the subviews inside the splitview when the window resizes.  
  151.  * Make sure that the upper view doesn't get too small.
  152.  */
  153.  
  154. - splitView:sender 
  155.     resizeSubviews:(const NXSize *)oldSize
  156. {
  157.     NXRect lower, upper;
  158.     float delta;
  159.     
  160.     [[sender window] disableDisplay];
  161.     [sender adjustSubviews];
  162.     
  163.     [myBrowser getFrame:&upper];
  164.     [myScrollView getFrame:&lower];
  165.     if (upper.size.height < 100.0) {
  166.         delta = 100.0 - upper.size.height;
  167.         upper.size.height=100.0;
  168.         lower.size.height-=delta;
  169.         [myBrowser setFrame:&upper];
  170.         [myScrollView setFrame:&lower];
  171.         }
  172.         
  173.     [[sender window] reenableDisplay];
  174.     [[sender window] display];
  175.  
  176.     return self;
  177. }
  178.  
  179.  
  180. /* Constrain the y coordinate limits of the splitview divider  */
  181.  
  182. - splitView:sender getMinY:(NXCoord *)minY maxY:(NXCoord *)maxY
  183.   ofSubviewAt:(int)offset
  184. {
  185.    NXRect    rect;
  186.    
  187.     offset = 0; 
  188.     [mySplitView getBounds:&rect];
  189.     *minY = 100.0;
  190.     *maxY = rect.size.height - 100.0;
  191.     if ( *maxY < 100.0 ) *maxY = 100.0;
  192.     return self;
  193.  
  194. }
  195.  
  196. /* Browser support */
  197.  
  198. - showTextFile:(const char *)filename:(int)column:(const char*)directoryname
  199. {
  200.     NXStream *stream;
  201.    char textFile[MAXPATHLEN];
  202.    static NXPoint origin = {0.0,0.0};
  203.  
  204.  
  205.     if ( ! [self browser:myBrowser selectCell:filename inColumn:column]  )    {
  206.           NXRunAlertPanel(NULL, FILE_NOT_FOUND_MSG, NULL, NULL,
  207.         NULL,filename);
  208.         return self;
  209.     }
  210.     sprintf(textFile,"%s/%s",directoryname,filename);
  211.     if ((stream = NXMapFile(textFile,NX_READONLY)) == NULL)    {
  212.         NXRunAlertPanel(NULL, FILE_NOT_FOUND_MSG, NULL, NULL,
  213.         NULL,filename);
  214.         return self;
  215.     }
  216.  
  217.     if (stream != NULL) {
  218.            [myWindow disableFlushWindow];
  219.         [[myScrollView docView] readRichText:stream]; 
  220.         [[myScrollView docView] scrollPoint:&origin];
  221.         [[myWindow reenableFlushWindow] flushWindow];
  222.         NXCloseMemory(stream,NX_FREEBUFFER);
  223.     }
  224.     [myWindow orderFront:self];
  225.     return self;
  226. }
  227.  
  228.  
  229. /* This is the target/action method from the AskMe browser.  When
  230.  * a topic is selected, this method will show the text file for that
  231.  * topic.
  232.  */
  233.  
  234. - browserHit: sender
  235. {
  236.     [self showTextFile:[[[sender matrixInColumn:0] selectedCell]                         stringValue]:0:textDirectory ];
  237.     return self;
  238.  
  239. }
  240.  
  241.  
  242. /*    BROWSER DELEGATE METHODS
  243.  */
  244.  
  245.  
  246. - (int)browser:sender fillMatrix:matrix inColumn:(int)column
  247. /* This delegate method goes out to the text directory and gets a list
  248.  * of all the files in that directory.  It creates a list of file names
  249.  * for the static variable fileList, and will load the filenames into the 
  250.  * browser on demand (lazy loading).
  251.  */
  252. {
  253.     long basep;
  254.     char *buf;
  255.     struct direct *dp;
  256.     char **list = NULL;
  257.     int cc, fd, fileCount = 0;
  258.     char dirbuf[8192];
  259.     
  260.     if ((fd = open(textDirectory, O_RDONLY, 0644)) > 0) {
  261.     cc = getdirentries(fd, (buf = dirbuf), 8192, &basep);
  262.     while (cc) {
  263.         dp = (struct direct *)buf;
  264.         if (isOk(dp->d_name)) {
  265.         list = addFile(dp->d_name, dp->d_namlen, list, fileCount++);
  266.         }
  267.         buf += dp->d_reclen;
  268.         if (buf >= dirbuf + cc) {
  269.         cc = getdirentries(fd, (buf = dirbuf), 8192, &basep);
  270.         }
  271.     }
  272.     close(fd);
  273.     if (list) qsort(list,fileCount,sizeof(char *),caseInsensitiveCompare);
  274.     }
  275.     freeList(fileList);
  276.     fileList = list;
  277.     return fileCount;
  278. }
  279.  
  280. - browser:sender loadCell:cell atRow:(int)row inColumn:(int)column
  281. /* This delegate method loads the cell for a given row.  The stringValue
  282.  * for that row comes from the fileList.
  283.  */
  284. {
  285.     if (fileList) {
  286.         [cell setStringValueNoCopy:fileList[row]];
  287.         [cell setLeaf:YES];
  288.     }
  289.     return self;
  290. }
  291.  
  292.  
  293. - (BOOL)browser:sender selectCell:(const char *)title inColumn:(int)column
  294. /* This delegate method selects the cell with the given title.  If it finds
  295.  * a cell with that title, it verifies that it has a file entry in the 
  296.  * fileList, forces the loading of the cell, selects it (highlights) and
  297.  * scrolls the browser so the cell is visible.  It returns a boolean value
  298.  * which indicates whether the cell was found.
  299.  */
  300. {
  301.     int row;
  302.     id matrix;
  303.  
  304.     if (title) {
  305.     matrix = [sender matrixInColumn:column];
  306.     if (!fileList) return NO;
  307.     for (row = [matrix cellCount]-1; row >= 0; row--) {
  308.         if (fileList[row] && !strcmp(title, fileList[row])) {
  309.             [sender getLoadedCellAtRow:row inColumn:column];
  310.             [matrix selectCellAt:row :0];
  311.             [matrix scrollCellToVisible:row :0];
  312.             return YES;
  313.         }
  314.     }
  315.     }
  316.     return NO;
  317. }
  318.  
  319.  
  320. /* INTERNAL ROUTINES TO HANDLE FILE OPERATIONS */
  321.  
  322. #define CHUNK 127
  323.  
  324. static char **addFile(const char *file, int length, char **list, int count)
  325. /* Adds the specified filename to the list of filenames.  It allocates 
  326.  * more memory in chunks as needed.
  327.  */
  328. {
  329.     char *suffix;
  330.         
  331.     if (!list) list = (char **)malloc(CHUNK*sizeof(char *));
  332.     if (suffix = rindex(file,'.')) 
  333.         *suffix  = '\0';     /* strip rtf suffix */
  334.     list[count] = (char *)malloc((length+1)*sizeof(char));
  335.     strcpy(list[count], file);
  336.     count++;
  337.     if (!(count% CHUNK)) {
  338.         list = (char **)realloc(list,(((count/CHUNK)+1)*CHUNK)*sizeof(char *));
  339.     }
  340.     list[count] = NULL;
  341.     return list;
  342. }
  343.  
  344. static void freeList(char **list)
  345. /* Frees the array of filenames
  346.  */
  347.  {
  348.     char **strings;
  349.  
  350.     if (list) {
  351.         strings = list;
  352.         while (*strings) free(*strings++);
  353.         free(list);
  354.        }
  355. }
  356.  
  357. static BOOL isOk(const char *s)
  358. /* checks to make sure the filename is not NULL and to verify that it is
  359.  * not a "dot"--hidden file.
  360.  */
  361. {
  362.     return (!s[0] || s[0] == '.') ? NO : YES;
  363. }
  364.  
  365. static int caseInsensitiveCompare(void *arg1, void *arg2)
  366. /* Compares the two arguments without regard for case using strcasecmp().
  367. */
  368. {
  369.     char *string1, *string2;
  370.  
  371.     string1 = *((char **)arg1);
  372.     string2 = *((char **)arg2);
  373.     return strcasecmp(string1,string2);
  374. }
  375.  
  376.  
  377.  
  378.  
  379.  
  380.  
  381. /* Methods to load separate nib sections: help panel, info panel    */
  382.  
  383. - help:sender
  384. {
  385.     if (helpPanel == NULL) {
  386.          helpPanel = LoadLocalNib("Help.nib", self);
  387.       }
  388.     return[helpPanel makeKeyAndOrderFront:sender];
  389.   
  390. }
  391.  
  392. - info:sender
  393. {
  394.     
  395.     if (infoPanel == NULL) {
  396.         infoPanel = LoadLocalNib("Info.nib", self);
  397.       }
  398.     return [infoPanel makeKeyAndOrderFront:sender];
  399.    
  400.   
  401. }
  402.  
  403.  
  404. /* window support */
  405.  
  406. - windowWillResize:sender toSize:(NXSize *)frameSize
  407. {
  408.     /* Limit the height resizing to the initial height to preserve 
  409.      * data in splitview. Limit its width to max twice its original width.
  410.      */
  411.     if (frameSize->width < 490.0) {
  412.         frameSize->width = 490.0;
  413.     }
  414.     if (frameSize->width > 1000.0) {
  415.         frameSize->width = 1000.0;
  416.     }
  417.     
  418.     if (frameSize->height < windowFrame.size.height ) {
  419.         frameSize->height = windowFrame.size.height;
  420.     }
  421.     
  422.     return self;
  423. }
  424.  
  425. - windowWillClose: sender
  426. {
  427.     
  428.     if (sender == infoPanel)
  429.         infoPanel = NULL;
  430.     else if (sender == helpPanel)
  431.         helpPanel = NULL;
  432.     return self;
  433. }
  434.  
  435.  
  436.  
  437. @end