home *** CD-ROM | disk | FTP | other *** search
/ Big Green CD 8 / BGCD_8_Dev.iso / YellowBox / Kits / MiscTableScroll-138.1 / Palettes / MiscTableScroll / Framework / MiscTableScrollISearch.M < prev    next >
Encoding:
Text File  |  1998-03-31  |  14.8 KB  |  526 lines

  1. //=============================================================================
  2. //
  3. //    Copyright (C) 1996,1997,1998 by Paul S. McCarthy and Eric Sunshine.
  4. //        Written by Paul S. McCarthy and Eric Sunshine.
  5. //                All Rights Reserved.
  6. //
  7. //    This notice may not be removed from this source code.
  8. //
  9. //    This object is included in the MiscKit by permission from the authors
  10. //    and its use is governed by the MiscKit license, found in the file
  11. //    "License.rtf" in the MiscKit distribution.  Please refer to that file
  12. //    for a list of all applicable permissions and restrictions.
  13. //    
  14. //=============================================================================
  15. //-----------------------------------------------------------------------------
  16. // MiscTableScrollIncSearch.M
  17. //
  18. //    Incremental searching methods for MiscTableScroll.
  19. //
  20. //    FIXME *1*: Need to handle Unichars.
  21. //
  22. //-----------------------------------------------------------------------------
  23. //-----------------------------------------------------------------------------
  24. // $Id: MiscTableScrollISearch.M,v 1.14 98/03/30 01:02:39 sunshine Exp $
  25. // $Log:    MiscTableScrollISearch.M,v $
  26. //  Revision 1.14  98/03/30  01:02:39  sunshine
  27. //  v138.1: Ported back to OPENSTEP 4.1.  Compiler was complaining about
  28. //  using enumeral and non-enumeral in conditional expression.
  29. //  
  30. //  Revision 1.13  98/03/23  11:12:16  sunshine
  31. //  v136.1: Applied v0.136 NEXTSTEP 3.3 diffs.
  32. //  
  33. //  Revision 1.12  97/06/22  10:23:32  sunshine
  34. //  v127.1: Fixed bug: Incremental-search feedback wasn't being clipped, thus
  35. //  drew outside of the MiscTableScroll's frame and was never erased.
  36. //-----------------------------------------------------------------------------
  37. #import <MiscTableScroll/MiscTableScroll.h>
  38. #import "MiscBorderCell.h"
  39. #import "MiscTableBorder.h"
  40. #import "MiscTableScrollPrivate.h"
  41. #import "MiscTableView.h"
  42.  
  43. extern "Objective-C" {
  44. #import    <AppKit/NSApplication.h>
  45. #import <AppKit/NSCursor.h>
  46. }
  47.  
  48. static float const ISEARCH_TIMEOUT = 2.5;    // seconds
  49.  
  50. enum MiscKeyAction
  51.     {
  52.     MISC_KEY_IGNORE,    // Ignore this event, get next.
  53.     MISC_KEY_SPACE,        // Space character, not valid as first char.
  54.     MISC_KEY_APPEND,    // Append a character to the template.
  55.     MISC_KEY_EXPAND,    // Expand common-prefix.
  56.     MISC_KEY_DELETE,    // Delete the last character from template.
  57.     MISC_KEY_CLEAR,        // Clear (empty) the template.
  58.     MISC_KEY_SELECT,    // Stop, Select the first entry.
  59.     MISC_KEY_EXECUTE,    // Stop, Select and perform double action.
  60.     MISC_KEY_STOP,        // Stop, don't select, consume keystroke.
  61.     MISC_KEY_ABORT,        // Stop, don't select, don't consume.
  62.     };
  63.  
  64.  
  65. //-----------------------------------------------------------------------------
  66. // Feedback
  67. //-----------------------------------------------------------------------------
  68. class MISC_IS_Feedback
  69.     {
  70. private:
  71.     MiscTableScroll* scroll;
  72.     BOOL drawn;
  73.     MiscBorderCell* cell;
  74.     NSRect drawRect;
  75.     void init_cell();
  76.  
  77. public:
  78.     MISC_IS_Feedback( MiscTableScroll* s ) : scroll(s),drawn(NO),cell(0) {}
  79.     ~MISC_IS_Feedback() { if (cell != 0) [cell release]; }
  80.     void draw( NSString* str );
  81.     void erase();
  82.     };
  83.  
  84. void MISC_IS_Feedback::init_cell()
  85.     {
  86.     if (cell == 0)
  87.     {
  88.     cell = [[MiscBorderCell allocWithZone:[scroll zone]] initTextCell:@""];
  89.     [cell setAlignment:NSLeftTextAlignment];
  90.     }
  91.     }
  92.  
  93. void MISC_IS_Feedback::erase()
  94.     {
  95.     if (drawn)
  96.     {
  97.     drawn = NO;
  98.     [scroll displayRect:drawRect];
  99.     }
  100.     }
  101.  
  102.  
  103. void MISC_IS_Feedback::draw( NSString* str )
  104.     {
  105.     init_cell();
  106.     [cell setStringValue:str];
  107.  
  108.     NSSize size = [cell cellSize];
  109.     NSRect clipFrame = [scroll documentClipRect];
  110.     NSRect r = NSMakeRect( NSMinX(clipFrame), NSMaxY(clipFrame) - size.height,
  111.                 size.width, size.height );
  112.     r = NSIntersectionRect( clipFrame, r );
  113.  
  114.     [scroll lockFocus];
  115.     [cell drawWithFrame:r inView:scroll];
  116.     [scroll unlockFocus];
  117.  
  118.     drawn = YES;
  119.     drawRect = r;
  120.     }
  121.  
  122.  
  123. //-----------------------------------------------------------------------------
  124. // classify_event
  125. //-----------------------------------------------------------------------------
  126. static MiscKeyAction classify_event(NSEvent* p)
  127.     {
  128.     int const BAD_FLAGS = (NSCommandKeyMask | NSHelpKeyMask | NSControlKeyMask);
  129.     MiscKeyAction rc = MISC_KEY_ABORT;
  130.     unichar const K_DEL = '\x7f';
  131.     NSEventType type = [p type];
  132.  
  133.     if ([p type] == NSKeyDown)
  134.     {
  135.     if (([p modifierFlags] & BAD_FLAGS) == 0)
  136.         {
  137.         unichar const ch =
  138.             [[p charactersIgnoringModifiers] characterAtIndex:0];
  139.         if (ch == K_DEL || ch == '\b')
  140.         rc = MISC_KEY_DELETE;
  141.         else if (ch == '\t')
  142.         rc = MISC_KEY_EXPAND;
  143.         else if (ch > ' ' && ch < K_DEL)            // FIXME *1*
  144.         rc = MISC_KEY_APPEND;
  145.         else if (ch == ' ')
  146.         rc = MISC_KEY_SPACE;
  147.         }
  148.     }
  149.     else if (type == NSKeyUp || type == NSFlagsChanged)
  150.     {
  151.     rc = MISC_KEY_IGNORE;
  152.     }
  153.  
  154.     return rc;
  155.     }
  156.  
  157.  
  158.  
  159. //-----------------------------------------------------------------------------
  160. // map
  161. //    Conditionally apply a mapping vector to an index value.
  162. //-----------------------------------------------------------------------------
  163. inline static int map( int const* a, int x ) { return a ? a[x] : x; }
  164.  
  165.  
  166. //-----------------------------------------------------------------------------
  167. // DATA RETRIEVAL AND COMPARE SUPPORT
  168. //-----------------------------------------------------------------------------
  169.  
  170. typedef NSString* (*Datafunc)( id obj, SEL cmd,
  171.                 MiscCoord_P row, MiscCoord_P col );
  172.  
  173. struct MiscSortTypeInfo
  174.     {
  175.     SEL const* data_sel;    // Selector to use to retrieve string.
  176.     BOOL fold_case;        // Case insensitive compare?
  177.     };
  178.  
  179. #define SEL_STRV &@selector(stringValueAtRow:column:)
  180. #define SEL_TITL &@selector(titleAtRow:column:)
  181.  
  182. static MiscSortTypeInfo SORT_TYPE_INFO[ MISC_SORT_TYPE_MAX + 1 ] =
  183.     {
  184.     { SEL_STRV, YES },    // MISC_SORT_STRING_CASE_INSENSITIVE
  185.     { SEL_STRV, NO },    // MISC_SORT_STRING_CASE_SENSITIVE
  186.     { 0, NO },        // MISC_SORT_INT
  187.     { 0, NO },        // MISC_SORT_UNSIGNED_INT
  188.     { 0, NO },        // MISC_SORT_TAG
  189.     { 0, NO },        // MISC_SORT_UNSIGNED_TAG
  190.     { 0, NO },        // MISC_SORT_FLOAT
  191.     { 0, NO },        // MISC_SORT_DOUBLE
  192.     { 0, NO },        // MISC_SORT_SKIP
  193.     { SEL_TITL, YES },    // MISC_SORT_TITLE_CASE_INSENSITIVE
  194.     { SEL_TITL, NO },    // MISC_SORT_TITLE_CASE_SENSITIVE
  195.     { 0, NO },        // MISC_SORT_STATE
  196.     { 0, NO },        // MISC_SORT_UNSIGNED_STATE
  197.     };
  198.  
  199. #undef SEL_STRV
  200. #undef SEL_TITL
  201.  
  202.  
  203. //-----------------------------------------------------------------------------
  204. // MISC_IS_bsearch
  205. //-----------------------------------------------------------------------------
  206. static int MISC_IS_bsearch(
  207.     MiscCoord_V lo, MiscCoord_V hi,
  208.     BOOL upper_bound, BOOL descending,
  209.     MiscTableScroll* scroll,
  210.     NSString* buff,
  211.     MiscCoord_P col, MiscCoord_P const* v2p,
  212.     Datafunc data_func, SEL data_sel, unsigned int cmp_mask )
  213.     {
  214.     int const buff_len = [buff length];
  215.     while (lo <= hi)
  216.     {
  217.     MiscCoord_V const mid = (lo + hi) >> 1;
  218.     MiscCoord_P const row = map( v2p, mid );
  219.     NSString* s = (*data_func)( scroll, data_sel, row, col );
  220.     if (s == 0)
  221.         s = @"";
  222.     int const s_len = [s length];
  223.     NSRange r = NSMakeRange( 0, s_len < buff_len ? s_len : buff_len );
  224.     int cmp = [s compare:buff options:cmp_mask range:r];
  225.     if (descending)
  226.         cmp = -cmp;
  227.     if (cmp < 0 || upper_bound && cmp == 0)
  228.         lo = mid + 1;
  229.     else
  230.         hi = mid - 1;
  231.     }
  232.     return (upper_bound ? hi : lo);
  233.     }
  234.  
  235.  
  236. //-----------------------------------------------------------------------------
  237. // peek_event
  238. //-----------------------------------------------------------------------------
  239. inline static NSEvent* peek_event( float timeout )
  240.     {
  241.     return [NSApp nextEventMatchingMask:NSAnyEventMask
  242.         untilDate:[NSDate dateWithTimeIntervalSinceNow:timeout]
  243.         inMode:NSDefaultRunLoopMode
  244.         dequeue:NO];
  245.     }
  246.  
  247.  
  248. //=============================================================================
  249. // IMPLEMENTATION
  250. //=============================================================================
  251. @implementation MiscTableScroll(IncrementalSearch)
  252.  
  253. //-----------------------------------------------------------------------------
  254. // -doIncrementalSearch:column:    FIXME *1*
  255. //-----------------------------------------------------------------------------
  256. - (BOOL)doIncrementalSearch:(NSEvent*)p column:(int)col
  257.     {
  258.     BOOL handled = YES;        // Event was handled here.
  259.     BOOL descending = NO;
  260.     if (col < 0)
  261.     {
  262.     col = ~col;
  263.     descending = YES;
  264.     }
  265.     if ([self border:MISC_COL_BORDER slotSortDirection:col] ==
  266.         MISC_SORT_DESCENDING)
  267.     descending = !descending;
  268.  
  269.     MiscSortType type = [self border:MISC_COL_BORDER slotSortType:col];
  270.     MiscSortTypeInfo const& INFO = SORT_TYPE_INFO[type];
  271.     unsigned int const cmp_mask =
  272.     INFO.fold_case ? (unsigned int)NSCaseInsensitiveSearch : 0;
  273.  
  274.     if (INFO.data_sel == 0 ||
  275.     [self border:MISC_COL_BORDER slotSortFunction:col] != 0)
  276.     return NO;                    // *** RETURN ***
  277.  
  278.     SEL const data_sel = *(INFO.data_sel);
  279.     Datafunc const data_func = (Datafunc) [self methodForSelector:data_sel];
  280.     MiscCoord_P const* const v2p = rowInfo.border->getV2PMap();
  281.  
  282.     MISC_IS_Feedback feedback( self );
  283.     MiscKeyAction ka;
  284.     int const MARGIN = 2;
  285.     int const BUFF_MAX = 61;
  286.     int buff_len = 0;
  287.     char margin[ MARGIN + BUFF_MAX + 1 ];
  288.     char* buff = margin + MARGIN;
  289.     MiscCoord_V first_stk[ BUFF_MAX + 1 ], last_stk[ BUFF_MAX + 1 ];
  290.     MiscCoord_V first = 0, last = [self numberOfRows] - 1;
  291.     MiscCoord_V prev_first = -1;
  292.     BOOL update_cursor = ([self trackingBy] == MISC_ROW_BORDER);
  293.     BOOL changed = NO;
  294.  
  295.     for (int i = 0; i < MARGIN; i++)
  296.     margin[i] = ' ';
  297.  
  298.     float delay = 0.25;
  299.  
  300.     first_stk[0] = first;
  301.     last_stk [0] = last;
  302.  
  303.     ka = classify_event( p );
  304.     while (1)
  305.     {
  306.     [NSCursor setHiddenUntilMouseMoves:YES];
  307.     switch (ka)
  308.         {
  309.     case MISC_KEY_IGNORE:
  310.         break;
  311.     case MISC_KEY_SPACE:
  312.     case MISC_KEY_APPEND:
  313.         if (buff_len < BUFF_MAX && first <= last)
  314.             {
  315.             unichar ch = [[p characters] characterAtIndex:0];
  316.             buff[ buff_len++ ] = char( ch );
  317.             buff[ buff_len ] = '\0';
  318.             NSString* s = [NSString stringWithCString:buff];
  319.  
  320.             MiscCoord_V const new_first = MISC_IS_bsearch(
  321.                 first, last, NO, descending, self, s,
  322.                 col, v2p, data_func, data_sel, cmp_mask );
  323.  
  324.             MiscCoord_V const new_last = MISC_IS_bsearch(
  325.                 new_first, last, YES, descending, self, s,
  326.                 col, v2p, data_func, data_sel, cmp_mask );
  327.  
  328.             if (new_first <= new_last)
  329.             {
  330.             first = new_first;
  331.             last  = new_last;
  332.             first_stk[ buff_len ] = first;
  333.             last_stk [ buff_len ] = last;
  334.             changed = YES;
  335.             }
  336.             else        // Do not add "invalid" characters.
  337.             {
  338.             buff[ --buff_len ] = '\0';
  339.             if (ka == MISC_KEY_SPACE)
  340.                 {
  341.                 ka = MISC_KEY_SELECT;
  342.                 goto finished;    // *** GOTO ***
  343.                 }
  344.             else if (buff_len == 0)
  345.                 {            // Couldn't even add 1st char,
  346.                 handled = NO;    // so don't go modal.
  347.                 goto finished;    // *** GOTO ***
  348.                 }
  349.             else
  350.                 NSBeep();
  351.             }
  352.             }
  353.         else
  354.             NSBeep();
  355.         break;
  356.     case MISC_KEY_EXPAND:
  357.         {
  358.         if (first <= last)
  359.             {
  360.             NSString* s1 =
  361.             (*data_func)( self, data_sel, map( v2p, first ), col );
  362.             NSString* s2 =
  363.             (*data_func)( self, data_sel, map( v2p, last  ), col );
  364.             NSString* s =
  365.             [s1 commonPrefixWithString:s2 options:cmp_mask];
  366.             int i = [s length];;
  367.             if (i > buff_len)
  368.             {
  369.             for (int j = buff_len + 1; j <= i; j++)
  370.                 {
  371.                 first_stk[j] = first;
  372.                 last_stk[j] = last;
  373.                 }
  374.             buff_len = i;
  375.             strncpy( buff, [s cString], buff_len );
  376.             buff[ buff_len ] = '\0';
  377.             changed = YES;
  378.             }
  379.             }
  380.         }
  381.         break;
  382.     case MISC_KEY_DELETE:
  383.         if (buff_len > 0)
  384.             {
  385.             buff[ --buff_len ] = '\0';
  386.             first = first_stk[ buff_len ];
  387.             last  = last_stk[ buff_len ];
  388.             changed = YES;
  389.             }
  390.         else
  391.             goto finished;    // *** GOTO ***
  392.         break;
  393.     case MISC_KEY_CLEAR:
  394.     case MISC_KEY_SELECT:
  395.     case MISC_KEY_EXECUTE:
  396.     case MISC_KEY_STOP:
  397.     case MISC_KEY_ABORT:
  398.         goto finished;        // *** GOTO ***
  399.         }
  400.  
  401.     if ((p = peek_event( delay )) == 0)
  402.         {
  403.         if (changed)
  404.         {
  405.         changed = NO;
  406.         NSWindow* win = [self window];
  407.         [win disableFlushWindow];
  408.         feedback.erase();
  409.         if (prev_first != first)
  410.             {
  411.             prev_first = first;
  412.             MiscCoord_P const r = map( v2p, first );
  413.             [self border:MISC_ROW_BORDER setFirstVisibleSlot:r];
  414.             if (update_cursor)
  415.             [self border:MISC_ROW_BORDER setCursorSlot:r];
  416.             [self displayIfNeeded];
  417.             }
  418.         NSString* s = [NSString stringWithCString:margin];
  419.         feedback.draw( s );
  420.         [win enableFlushWindow];
  421.         [win flushWindow];
  422.     
  423.         if (buff_len == 0)
  424.             break;        // *** BREAK ***
  425.         }
  426.     
  427.         if ((p = peek_event( ISEARCH_TIMEOUT )) == 0)
  428.         break;            // *** BREAK *** inactivity timeout.
  429.         }
  430.  
  431.     delay = 0.0;
  432.     ka = classify_event( p );
  433.  
  434.     if (ka != MISC_KEY_ABORT)    // "Eat" the event.
  435.         [NSApp nextEventMatchingMask:NSAnyEventMask
  436.         untilDate:[NSDate dateWithTimeIntervalSinceNow:ISEARCH_TIMEOUT]
  437.         inMode:NSDefaultRunLoopMode
  438.         dequeue:YES];
  439.     }
  440.  
  441. finished:
  442.     feedback.erase();
  443.  
  444.     if (ka==MISC_KEY_SELECT || ka==MISC_KEY_EXECUTE)
  445.     {
  446.     MiscCoord_P const phys_first = map( v2p, first );
  447.     if (prev_first != first)
  448.         {
  449.         [self border:MISC_ROW_BORDER setFirstVisibleSlot:phys_first];
  450.         if (update_cursor)
  451.         [self border:MISC_ROW_BORDER setCursorSlot:phys_first];
  452.         }
  453.     if (update_cursor)
  454.         [self keyboardSelect:p];
  455.     if ([self isEnabled])
  456.         {
  457.         [self sendAction];
  458.         if (ka == MISC_KEY_EXECUTE)
  459.         [self sendDoubleAction];
  460.         }
  461.     }
  462.  
  463.     return handled;
  464.     }
  465.  
  466.  
  467. //-----------------------------------------------------------------------------
  468. // -doGetISearchColumn:
  469. //    Default implementation.  If auto-sort-rows is on, and the first
  470. //    non-skip column is string-based, use that column, else fail.
  471. //-----------------------------------------------------------------------------
  472. - (BOOL)doGetISearchColumn:(int*)col
  473.     {
  474.     if ([self autoSortRows])
  475.     {
  476.     int const lim = colInfo.border->count();
  477.     int const* v2p = colInfo.border->getV2PMap();
  478.     for (int i = 0; i < lim; i++)
  479.         {
  480.         int const j = map( v2p, i );
  481.         if ([self border:MISC_COL_BORDER slotSortFunction:j] != 0)
  482.         return NO;
  483.         MiscSortType const t = [self border:MISC_COL_BORDER slotSortType:j];
  484.         if (t != MISC_SORT_SKIP)
  485.         {
  486.         *col = j;
  487.         return (SORT_TYPE_INFO[t].data_sel != 0);
  488.         }
  489.         }
  490.     }
  491.     return NO;
  492.     }
  493.  
  494.  
  495. //-----------------------------------------------------------------------------
  496. // -getISearchColumn:
  497. //-----------------------------------------------------------------------------
  498. - (BOOL)getISearchColumn:(int*)col
  499.     {
  500.     id del = [self responsibleDelegate:MiscDelegateFlags::DEL_GET_ISEARCH_COL];
  501.     if (del != 0)
  502.     return [del tableScroll:self getISearchColumn:col];
  503.     return [self doGetISearchColumn:col];
  504.     }
  505.  
  506.  
  507. //-----------------------------------------------------------------------------
  508. // -incrementalSearch:
  509. //-----------------------------------------------------------------------------
  510. - (BOOL)incrementalSearch:(NSEvent*)p 
  511.     {
  512.     if ([self numberOfRows] > 0 && [self numberOfColumns] > 0)
  513.     {
  514.     MiscKeyAction const ka = classify_event( p );
  515.     if (ka == MISC_KEY_APPEND)
  516.         {
  517.         int col;
  518.         if ([self getISearchColumn:&col])
  519.         return [self doIncrementalSearch:p column:col];
  520.         }
  521.     }
  522.     return NO;    // Event was not handled here.
  523.     }
  524.  
  525. @end
  526.