home *** CD-ROM | disk | FTP | other *** search
- //=============================================================================
- //
- // Copyright (C) 1996,1997,1998 by Paul S. McCarthy and Eric Sunshine.
- // Written by Paul S. McCarthy and Eric Sunshine.
- // All Rights Reserved.
- //
- // This notice may not be removed from this source code.
- //
- // This object is included in the MiscKit by permission from the authors
- // and its use is governed by the MiscKit license, found in the file
- // "License.rtf" in the MiscKit distribution. Please refer to that file
- // for a list of all applicable permissions and restrictions.
- //
- //=============================================================================
- //-----------------------------------------------------------------------------
- // MiscTableScrollIncSearch.M
- //
- // Incremental searching methods for MiscTableScroll.
- //
- // FIXME *1*: Need to handle Unichars.
- //
- //-----------------------------------------------------------------------------
- //-----------------------------------------------------------------------------
- // $Id: MiscTableScrollISearch.M,v 1.14 98/03/30 01:02:39 sunshine Exp $
- // $Log: MiscTableScrollISearch.M,v $
- // Revision 1.14 98/03/30 01:02:39 sunshine
- // v138.1: Ported back to OPENSTEP 4.1. Compiler was complaining about
- // using enumeral and non-enumeral in conditional expression.
- //
- // Revision 1.13 98/03/23 11:12:16 sunshine
- // v136.1: Applied v0.136 NEXTSTEP 3.3 diffs.
- //
- // Revision 1.12 97/06/22 10:23:32 sunshine
- // v127.1: Fixed bug: Incremental-search feedback wasn't being clipped, thus
- // drew outside of the MiscTableScroll's frame and was never erased.
- //-----------------------------------------------------------------------------
- #import <MiscTableScroll/MiscTableScroll.h>
- #import "MiscBorderCell.h"
- #import "MiscTableBorder.h"
- #import "MiscTableScrollPrivate.h"
- #import "MiscTableView.h"
-
- extern "Objective-C" {
- #import <AppKit/NSApplication.h>
- #import <AppKit/NSCursor.h>
- }
-
- static float const ISEARCH_TIMEOUT = 2.5; // seconds
-
- enum MiscKeyAction
- {
- MISC_KEY_IGNORE, // Ignore this event, get next.
- MISC_KEY_SPACE, // Space character, not valid as first char.
- MISC_KEY_APPEND, // Append a character to the template.
- MISC_KEY_EXPAND, // Expand common-prefix.
- MISC_KEY_DELETE, // Delete the last character from template.
- MISC_KEY_CLEAR, // Clear (empty) the template.
- MISC_KEY_SELECT, // Stop, Select the first entry.
- MISC_KEY_EXECUTE, // Stop, Select and perform double action.
- MISC_KEY_STOP, // Stop, don't select, consume keystroke.
- MISC_KEY_ABORT, // Stop, don't select, don't consume.
- };
-
-
- //-----------------------------------------------------------------------------
- // Feedback
- //-----------------------------------------------------------------------------
- class MISC_IS_Feedback
- {
- private:
- MiscTableScroll* scroll;
- BOOL drawn;
- MiscBorderCell* cell;
- NSRect drawRect;
- void init_cell();
-
- public:
- MISC_IS_Feedback( MiscTableScroll* s ) : scroll(s),drawn(NO),cell(0) {}
- ~MISC_IS_Feedback() { if (cell != 0) [cell release]; }
- void draw( NSString* str );
- void erase();
- };
-
- void MISC_IS_Feedback::init_cell()
- {
- if (cell == 0)
- {
- cell = [[MiscBorderCell allocWithZone:[scroll zone]] initTextCell:@""];
- [cell setAlignment:NSLeftTextAlignment];
- }
- }
-
- void MISC_IS_Feedback::erase()
- {
- if (drawn)
- {
- drawn = NO;
- [scroll displayRect:drawRect];
- }
- }
-
-
- void MISC_IS_Feedback::draw( NSString* str )
- {
- init_cell();
- [cell setStringValue:str];
-
- NSSize size = [cell cellSize];
- NSRect clipFrame = [scroll documentClipRect];
- NSRect r = NSMakeRect( NSMinX(clipFrame), NSMaxY(clipFrame) - size.height,
- size.width, size.height );
- r = NSIntersectionRect( clipFrame, r );
-
- [scroll lockFocus];
- [cell drawWithFrame:r inView:scroll];
- [scroll unlockFocus];
-
- drawn = YES;
- drawRect = r;
- }
-
-
- //-----------------------------------------------------------------------------
- // classify_event
- //-----------------------------------------------------------------------------
- static MiscKeyAction classify_event(NSEvent* p)
- {
- int const BAD_FLAGS = (NSCommandKeyMask | NSHelpKeyMask | NSControlKeyMask);
- MiscKeyAction rc = MISC_KEY_ABORT;
- unichar const K_DEL = '\x7f';
- NSEventType type = [p type];
-
- if ([p type] == NSKeyDown)
- {
- if (([p modifierFlags] & BAD_FLAGS) == 0)
- {
- unichar const ch =
- [[p charactersIgnoringModifiers] characterAtIndex:0];
- if (ch == K_DEL || ch == '\b')
- rc = MISC_KEY_DELETE;
- else if (ch == '\t')
- rc = MISC_KEY_EXPAND;
- else if (ch > ' ' && ch < K_DEL) // FIXME *1*
- rc = MISC_KEY_APPEND;
- else if (ch == ' ')
- rc = MISC_KEY_SPACE;
- }
- }
- else if (type == NSKeyUp || type == NSFlagsChanged)
- {
- rc = MISC_KEY_IGNORE;
- }
-
- return rc;
- }
-
-
-
- //-----------------------------------------------------------------------------
- // map
- // Conditionally apply a mapping vector to an index value.
- //-----------------------------------------------------------------------------
- inline static int map( int const* a, int x ) { return a ? a[x] : x; }
-
-
- //-----------------------------------------------------------------------------
- // DATA RETRIEVAL AND COMPARE SUPPORT
- //-----------------------------------------------------------------------------
-
- typedef NSString* (*Datafunc)( id obj, SEL cmd,
- MiscCoord_P row, MiscCoord_P col );
-
- struct MiscSortTypeInfo
- {
- SEL const* data_sel; // Selector to use to retrieve string.
- BOOL fold_case; // Case insensitive compare?
- };
-
- #define SEL_STRV &@selector(stringValueAtRow:column:)
- #define SEL_TITL &@selector(titleAtRow:column:)
-
- static MiscSortTypeInfo SORT_TYPE_INFO[ MISC_SORT_TYPE_MAX + 1 ] =
- {
- { SEL_STRV, YES }, // MISC_SORT_STRING_CASE_INSENSITIVE
- { SEL_STRV, NO }, // MISC_SORT_STRING_CASE_SENSITIVE
- { 0, NO }, // MISC_SORT_INT
- { 0, NO }, // MISC_SORT_UNSIGNED_INT
- { 0, NO }, // MISC_SORT_TAG
- { 0, NO }, // MISC_SORT_UNSIGNED_TAG
- { 0, NO }, // MISC_SORT_FLOAT
- { 0, NO }, // MISC_SORT_DOUBLE
- { 0, NO }, // MISC_SORT_SKIP
- { SEL_TITL, YES }, // MISC_SORT_TITLE_CASE_INSENSITIVE
- { SEL_TITL, NO }, // MISC_SORT_TITLE_CASE_SENSITIVE
- { 0, NO }, // MISC_SORT_STATE
- { 0, NO }, // MISC_SORT_UNSIGNED_STATE
- };
-
- #undef SEL_STRV
- #undef SEL_TITL
-
-
- //-----------------------------------------------------------------------------
- // MISC_IS_bsearch
- //-----------------------------------------------------------------------------
- static int MISC_IS_bsearch(
- MiscCoord_V lo, MiscCoord_V hi,
- BOOL upper_bound, BOOL descending,
- MiscTableScroll* scroll,
- NSString* buff,
- MiscCoord_P col, MiscCoord_P const* v2p,
- Datafunc data_func, SEL data_sel, unsigned int cmp_mask )
- {
- int const buff_len = [buff length];
- while (lo <= hi)
- {
- MiscCoord_V const mid = (lo + hi) >> 1;
- MiscCoord_P const row = map( v2p, mid );
- NSString* s = (*data_func)( scroll, data_sel, row, col );
- if (s == 0)
- s = @"";
- int const s_len = [s length];
- NSRange r = NSMakeRange( 0, s_len < buff_len ? s_len : buff_len );
- int cmp = [s compare:buff options:cmp_mask range:r];
- if (descending)
- cmp = -cmp;
- if (cmp < 0 || upper_bound && cmp == 0)
- lo = mid + 1;
- else
- hi = mid - 1;
- }
- return (upper_bound ? hi : lo);
- }
-
-
- //-----------------------------------------------------------------------------
- // peek_event
- //-----------------------------------------------------------------------------
- inline static NSEvent* peek_event( float timeout )
- {
- return [NSApp nextEventMatchingMask:NSAnyEventMask
- untilDate:[NSDate dateWithTimeIntervalSinceNow:timeout]
- inMode:NSDefaultRunLoopMode
- dequeue:NO];
- }
-
-
- //=============================================================================
- // IMPLEMENTATION
- //=============================================================================
- @implementation MiscTableScroll(IncrementalSearch)
-
- //-----------------------------------------------------------------------------
- // -doIncrementalSearch:column: FIXME *1*
- //-----------------------------------------------------------------------------
- - (BOOL)doIncrementalSearch:(NSEvent*)p column:(int)col
- {
- BOOL handled = YES; // Event was handled here.
- BOOL descending = NO;
- if (col < 0)
- {
- col = ~col;
- descending = YES;
- }
- if ([self border:MISC_COL_BORDER slotSortDirection:col] ==
- MISC_SORT_DESCENDING)
- descending = !descending;
-
- MiscSortType type = [self border:MISC_COL_BORDER slotSortType:col];
- MiscSortTypeInfo const& INFO = SORT_TYPE_INFO[type];
- unsigned int const cmp_mask =
- INFO.fold_case ? (unsigned int)NSCaseInsensitiveSearch : 0;
-
- if (INFO.data_sel == 0 ||
- [self border:MISC_COL_BORDER slotSortFunction:col] != 0)
- return NO; // *** RETURN ***
-
- SEL const data_sel = *(INFO.data_sel);
- Datafunc const data_func = (Datafunc) [self methodForSelector:data_sel];
- MiscCoord_P const* const v2p = rowInfo.border->getV2PMap();
-
- MISC_IS_Feedback feedback( self );
- MiscKeyAction ka;
- int const MARGIN = 2;
- int const BUFF_MAX = 61;
- int buff_len = 0;
- char margin[ MARGIN + BUFF_MAX + 1 ];
- char* buff = margin + MARGIN;
- MiscCoord_V first_stk[ BUFF_MAX + 1 ], last_stk[ BUFF_MAX + 1 ];
- MiscCoord_V first = 0, last = [self numberOfRows] - 1;
- MiscCoord_V prev_first = -1;
- BOOL update_cursor = ([self trackingBy] == MISC_ROW_BORDER);
- BOOL changed = NO;
-
- for (int i = 0; i < MARGIN; i++)
- margin[i] = ' ';
-
- float delay = 0.25;
-
- first_stk[0] = first;
- last_stk [0] = last;
-
- ka = classify_event( p );
- while (1)
- {
- [NSCursor setHiddenUntilMouseMoves:YES];
- switch (ka)
- {
- case MISC_KEY_IGNORE:
- break;
- case MISC_KEY_SPACE:
- case MISC_KEY_APPEND:
- if (buff_len < BUFF_MAX && first <= last)
- {
- unichar ch = [[p characters] characterAtIndex:0];
- buff[ buff_len++ ] = char( ch );
- buff[ buff_len ] = '\0';
- NSString* s = [NSString stringWithCString:buff];
-
- MiscCoord_V const new_first = MISC_IS_bsearch(
- first, last, NO, descending, self, s,
- col, v2p, data_func, data_sel, cmp_mask );
-
- MiscCoord_V const new_last = MISC_IS_bsearch(
- new_first, last, YES, descending, self, s,
- col, v2p, data_func, data_sel, cmp_mask );
-
- if (new_first <= new_last)
- {
- first = new_first;
- last = new_last;
- first_stk[ buff_len ] = first;
- last_stk [ buff_len ] = last;
- changed = YES;
- }
- else // Do not add "invalid" characters.
- {
- buff[ --buff_len ] = '\0';
- if (ka == MISC_KEY_SPACE)
- {
- ka = MISC_KEY_SELECT;
- goto finished; // *** GOTO ***
- }
- else if (buff_len == 0)
- { // Couldn't even add 1st char,
- handled = NO; // so don't go modal.
- goto finished; // *** GOTO ***
- }
- else
- NSBeep();
- }
- }
- else
- NSBeep();
- break;
- case MISC_KEY_EXPAND:
- {
- if (first <= last)
- {
- NSString* s1 =
- (*data_func)( self, data_sel, map( v2p, first ), col );
- NSString* s2 =
- (*data_func)( self, data_sel, map( v2p, last ), col );
- NSString* s =
- [s1 commonPrefixWithString:s2 options:cmp_mask];
- int i = [s length];;
- if (i > buff_len)
- {
- for (int j = buff_len + 1; j <= i; j++)
- {
- first_stk[j] = first;
- last_stk[j] = last;
- }
- buff_len = i;
- strncpy( buff, [s cString], buff_len );
- buff[ buff_len ] = '\0';
- changed = YES;
- }
- }
- }
- break;
- case MISC_KEY_DELETE:
- if (buff_len > 0)
- {
- buff[ --buff_len ] = '\0';
- first = first_stk[ buff_len ];
- last = last_stk[ buff_len ];
- changed = YES;
- }
- else
- goto finished; // *** GOTO ***
- break;
- case MISC_KEY_CLEAR:
- case MISC_KEY_SELECT:
- case MISC_KEY_EXECUTE:
- case MISC_KEY_STOP:
- case MISC_KEY_ABORT:
- goto finished; // *** GOTO ***
- }
-
- if ((p = peek_event( delay )) == 0)
- {
- if (changed)
- {
- changed = NO;
- NSWindow* win = [self window];
- [win disableFlushWindow];
- feedback.erase();
- if (prev_first != first)
- {
- prev_first = first;
- MiscCoord_P const r = map( v2p, first );
- [self border:MISC_ROW_BORDER setFirstVisibleSlot:r];
- if (update_cursor)
- [self border:MISC_ROW_BORDER setCursorSlot:r];
- [self displayIfNeeded];
- }
- NSString* s = [NSString stringWithCString:margin];
- feedback.draw( s );
- [win enableFlushWindow];
- [win flushWindow];
-
- if (buff_len == 0)
- break; // *** BREAK ***
- }
-
- if ((p = peek_event( ISEARCH_TIMEOUT )) == 0)
- break; // *** BREAK *** inactivity timeout.
- }
-
- delay = 0.0;
- ka = classify_event( p );
-
- if (ka != MISC_KEY_ABORT) // "Eat" the event.
- [NSApp nextEventMatchingMask:NSAnyEventMask
- untilDate:[NSDate dateWithTimeIntervalSinceNow:ISEARCH_TIMEOUT]
- inMode:NSDefaultRunLoopMode
- dequeue:YES];
- }
-
- finished:
- feedback.erase();
-
- if (ka==MISC_KEY_SELECT || ka==MISC_KEY_EXECUTE)
- {
- MiscCoord_P const phys_first = map( v2p, first );
- if (prev_first != first)
- {
- [self border:MISC_ROW_BORDER setFirstVisibleSlot:phys_first];
- if (update_cursor)
- [self border:MISC_ROW_BORDER setCursorSlot:phys_first];
- }
- if (update_cursor)
- [self keyboardSelect:p];
- if ([self isEnabled])
- {
- [self sendAction];
- if (ka == MISC_KEY_EXECUTE)
- [self sendDoubleAction];
- }
- }
-
- return handled;
- }
-
-
- //-----------------------------------------------------------------------------
- // -doGetISearchColumn:
- // Default implementation. If auto-sort-rows is on, and the first
- // non-skip column is string-based, use that column, else fail.
- //-----------------------------------------------------------------------------
- - (BOOL)doGetISearchColumn:(int*)col
- {
- if ([self autoSortRows])
- {
- int const lim = colInfo.border->count();
- int const* v2p = colInfo.border->getV2PMap();
- for (int i = 0; i < lim; i++)
- {
- int const j = map( v2p, i );
- if ([self border:MISC_COL_BORDER slotSortFunction:j] != 0)
- return NO;
- MiscSortType const t = [self border:MISC_COL_BORDER slotSortType:j];
- if (t != MISC_SORT_SKIP)
- {
- *col = j;
- return (SORT_TYPE_INFO[t].data_sel != 0);
- }
- }
- }
- return NO;
- }
-
-
- //-----------------------------------------------------------------------------
- // -getISearchColumn:
- //-----------------------------------------------------------------------------
- - (BOOL)getISearchColumn:(int*)col
- {
- id del = [self responsibleDelegate:MiscDelegateFlags::DEL_GET_ISEARCH_COL];
- if (del != 0)
- return [del tableScroll:self getISearchColumn:col];
- return [self doGetISearchColumn:col];
- }
-
-
- //-----------------------------------------------------------------------------
- // -incrementalSearch:
- //-----------------------------------------------------------------------------
- - (BOOL)incrementalSearch:(NSEvent*)p
- {
- if ([self numberOfRows] > 0 && [self numberOfColumns] > 0)
- {
- MiscKeyAction const ka = classify_event( p );
- if (ka == MISC_KEY_APPEND)
- {
- int col;
- if ([self getISearchColumn:&col])
- return [self doIncrementalSearch:p column:col];
- }
- }
- return NO; // Event was not handled here.
- }
-
- @end
-