/[gnustep]/gnustep/core/gui/Source/NSBrowser.m
ViewVC logotype

Contents of /gnustep/core/gui/Source/NSBrowser.m

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.101 - (show annotations) (download)
Wed Aug 20 12:43:52 2003 UTC (20 years, 8 months ago) by alexm
Branch: MAIN
CVS Tags: gui-0_8_9
Changes since 1.100: +6 -1 lines
Don't do initialization that requires the shared application object/backend too early (ie. move it from +initialize to other methods). Add some asserts to try to catch these errors in the future.

1 /** <title>NSBrowser</title>
2
3 <abstract>Control to display and select from hierarchal lists</abstract>
4
5 Copyright (C) 1996, 1997, 2002 Free Software Foundation, Inc.
6
7 Author: Scott Christley <scottc@net-community.com>
8 Date: 1996
9 Author: Felipe A. Rodriguez <far@ix.netcom.com>
10 Date: August 1998
11 Author: Franck Wolff <wolff@cybercable.fr>
12 Date: November 1999
13 Author: Mirko Viviani <mirko.viviani@rccr.cremona.it>
14 Date: September 2000
15 Author: Fred Kiefer <FredKiefer@gmx.de>
16 Date: September 2002
17
18 This file is part of the GNUstep GUI Library.
19
20 This library is free software; you can redistribute it and/or
21 modify it under the terms of the GNU Library General Public
22 License as published by the Free Software Foundation; either
23 version 2 of the License, or (at your option) any later version.
24
25 This library is distributed in the hope that it will be useful,
26 but WITHOUT ANY WARRANTY; without even the implied warranty of
27 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
28 Library General Public License for more details.
29
30 You should have received a copy of the GNU Library General Public
31 License along with this library; see the file COPYING.LIB.
32 If not, write to the Free Software Foundation,
33 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
34 */
35
36 #include <math.h> // (float)rintf(float x)
37 #include "config.h"
38 #include <Foundation/NSArray.h>
39 #include <Foundation/NSDebug.h>
40 #include <Foundation/NSException.h>
41 #include "AppKit/NSBrowser.h"
42 #include "AppKit/NSBrowserCell.h"
43 #include "AppKit/AppKitExceptions.h"
44 #include "AppKit/NSScroller.h"
45 #include "AppKit/NSCell.h"
46 #include "AppKit/NSColor.h"
47 #include "AppKit/NSFont.h"
48 #include "AppKit/NSScrollView.h"
49 #include "AppKit/NSGraphics.h"
50 #include "AppKit/NSMatrix.h"
51 #include "AppKit/NSTableHeaderCell.h"
52 #include "AppKit/NSEvent.h"
53 #include "AppKit/NSWindow.h"
54 #include "AppKit/NSBezierPath.h"
55
56 DEFINE_RINT_IF_MISSING
57
58 /* Cache */
59 static float scrollerWidth; // == [NSScroller scrollerWidth]
60 static NSTextFieldCell *titleCell;
61
62 #define NSBR_COLUMN_SEP 4
63 #define NSBR_VOFFSET 2
64
65 #define NSBR_COLUMN_IS_VISIBLE(i) \
66 (((i)>=_firstVisibleColumn)&&((i)<=_lastVisibleColumn))
67
68 //
69 // Internal class for maintaining information about columns
70 //
71 @interface NSBrowserColumn : NSObject <NSCoding>
72 {
73 @public
74 BOOL _isLoaded;
75 id _columnScrollView;
76 id _columnMatrix;
77 NSString *_columnTitle;
78 }
79
80 - (void) setIsLoaded: (BOOL)flag;
81 - (BOOL) isLoaded;
82 - (void) setColumnScrollView: (id)aView;
83 - (id) columnScrollView;
84 - (void) setColumnMatrix: (id)aMatrix;
85 - (id) columnMatrix;
86 - (void) setColumnTitle: (NSString *)aString;
87 - (NSString *) columnTitle;
88 @end
89
90 @implementation NSBrowserColumn
91
92 - (id) init
93 {
94 [super init];
95
96 _isLoaded = NO;
97
98 return self;
99 }
100
101 - (void) dealloc
102 {
103 TEST_RELEASE(_columnScrollView);
104 TEST_RELEASE(_columnMatrix);
105 TEST_RELEASE(_columnTitle);
106 [super dealloc];
107 }
108
109 - (void) setIsLoaded: (BOOL)flag
110 {
111 _isLoaded = flag;
112 }
113
114 - (BOOL) isLoaded
115 {
116 return _isLoaded;
117 }
118
119 - (void) setColumnScrollView: (id)aView
120 {
121 ASSIGN(_columnScrollView, aView);
122 }
123
124 - (id) columnScrollView
125 {
126 return _columnScrollView;
127 }
128
129 - (void) setColumnMatrix: (id)aMatrix
130 {
131 ASSIGN(_columnMatrix, aMatrix);
132 }
133
134 - (id) columnMatrix
135 {
136 return _columnMatrix;
137 }
138
139 - (void) setColumnTitle: (NSString *)aString
140 {
141 if (!aString)
142 aString = @"";
143
144 ASSIGN(_columnTitle, aString);
145 }
146
147 - (NSString *) columnTitle
148 {
149 return _columnTitle;
150 }
151
152 - (void) encodeWithCoder: (NSCoder *)aCoder
153 {
154 int dummy = 0;
155
156 [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_isLoaded];
157 [aCoder encodeObject: _columnScrollView];
158 [aCoder encodeObject: _columnMatrix];
159 [aCoder encodeValueOfObjCType: @encode(int) at: &dummy];
160 [aCoder encodeObject: _columnTitle];
161 }
162
163 - (id) initWithCoder: (NSCoder *)aDecoder
164 {
165 int dummy = 0;
166
167 [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_isLoaded];
168 _columnScrollView = [aDecoder decodeObject];
169 if (_columnScrollView)
170 RETAIN(_columnScrollView);
171 _columnMatrix = [aDecoder decodeObject];
172 if (_columnMatrix)
173 RETAIN(_columnMatrix);
174 [aDecoder decodeValueOfObjCType: @encode(int) at: &dummy];
175 _columnTitle = [aDecoder decodeObject];
176 if (_columnTitle)
177 RETAIN(_columnTitle);
178 return self;
179 }
180
181 @end
182
183 // NB: this is used in the NSFontPanel too
184 @interface GSBrowserTitleCell: NSTableHeaderCell
185 @end
186
187 @implementation GSBrowserTitleCell
188 - (void) drawWithFrame: (NSRect)cellFrame inView: (NSView*)controlView
189 {
190 if (NSIsEmptyRect (cellFrame) || ![controlView window])
191 {
192 return;
193 }
194
195 NSDrawGrayBezel (cellFrame, NSZeroRect);
196 [self drawInteriorWithFrame: cellFrame inView: controlView];
197 }
198 @end
199
200 //
201 // Private NSBrowser methods
202 //
203 @interface NSBrowser (Private)
204 - (NSString *) _getTitleOfColumn: (int)column;
205 - (void) _performLoadOfColumn: (int)column;
206 - (void) _remapColumnSubviews: (BOOL)flag;
207 - (void) _setColumnTitlesNeedDisplay;
208 @end
209
210 //
211 // NSBrowser implementation
212 //
213 @implementation NSBrowser
214
215 /** Returns the NSBrowserCell class (regardless of whether a
216 setCellClass: message has been sent to a particular instance). This
217 method is not meant to be used by applications.
218 */
219
220 + (Class) cellClass
221 {
222 return [NSBrowserCell class];
223 }
224
225 /** Sets the class of NSCell used in the columns of the NSBrowser. */
226 - (void) setCellClass: (Class)classId
227 {
228 NSCell *aCell;
229
230 aCell = [[classId alloc] init];
231 // set the prototype for the new class
232 [self setCellPrototype: aCell];
233 RELEASE(aCell);
234 }
235
236 /** Returns the NSBrowser's prototype NSCell instance.*/
237 - (id) cellPrototype
238 {
239 return _browserCellPrototype;
240 }
241
242 /** Sets the NSCell instance copied to display items in the columns of
243 NSBrowser. */
244 - (void) setCellPrototype: (NSCell *)aCell
245 {
246 ASSIGN(_browserCellPrototype, aCell);
247 }
248
249 /** Returns the class of NSMatrix used in the NSBrowser's columns. */
250 - (Class) matrixClass
251 {
252 return _browserMatrixClass;
253 }
254
255 /** Sets the matrix class (NSMatrix or an NSMatrix subclass) used in the
256 NSBrowser's columns. */
257 - (void) setMatrixClass: (Class)classId
258 {
259 _browserMatrixClass = classId;
260 }
261
262 /*
263 * Getting matrices, cells, and rows
264 */
265
266 /** Returns the last (rightmost and lowest) selected NSCell. */
267 - (id) selectedCell
268 {
269 int i;
270 id matrix;
271
272 // Nothing selected
273 if ((i = [self selectedColumn]) == -1)
274 {
275 return nil;
276 }
277
278 if (!(matrix = [self matrixInColumn: i]))
279 {
280 return nil;
281 }
282
283 return [matrix selectedCell];
284 }
285
286 /** Returns the last (lowest) NSCell that's selected in column. */
287 - (id) selectedCellInColumn: (int)column
288 {
289 id matrix;
290
291 if (!(matrix = [self matrixInColumn: column]))
292 {
293 return nil;
294 }
295
296 return [matrix selectedCell];
297 }
298
299 /** Returns all cells selected in the rightmost column. */
300 - (NSArray *) selectedCells
301 {
302 int i;
303 id matrix;
304
305 // Nothing selected
306 if ((i = [self selectedColumn]) == -1)
307 {
308 return nil;
309 }
310
311 if (!(matrix = [self matrixInColumn: i]))
312 {
313 return nil;
314 }
315
316 return [matrix selectedCells];
317 }
318
319 /** Selects all NSCells in the last column of the NSBrowser. */
320 - (void) selectAll: (id)sender
321 {
322 id matrix;
323
324 if (!(matrix = [self matrixInColumn: _lastColumnLoaded]))
325 {
326 return;
327 }
328
329 [matrix selectAll: sender];
330 }
331
332 /** Returns the row index of the selected cell in the column specified by
333 index column. */
334 - (int) selectedRowInColumn: (int)column
335 {
336 id matrix;
337
338 if (!(matrix = [self matrixInColumn: column]))
339 {
340 return -1;
341 }
342
343 return [matrix selectedRow];
344 }
345
346 /** Selects the cell at index row in the column identified by index column. */
347 - (void) selectRow: (int)row inColumn: (int)column
348 {
349 id matrix;
350 id cell;
351 BOOL didSelect;
352
353 if ((matrix = [self matrixInColumn: column]) == nil)
354 {
355 return;
356 }
357
358 if ((cell = [matrix cellAtRow: row column: 0]) == nil)
359 {
360 return;
361 }
362
363 [self setLastColumn: column];
364
365 if (_allowsMultipleSelection == NO)
366 {
367 [matrix deselectAllCells];
368 }
369
370 if ([_browserDelegate respondsToSelector:
371 @selector(browser:selectRow:inColumn:)])
372 {
373 didSelect = [_browserDelegate browser: self
374 selectRow: row
375 inColumn: column];
376 }
377 else
378 {
379 [matrix selectCellAtRow: row column: 0];
380 didSelect = YES;
381 }
382
383 if (didSelect && [cell isLeaf] == NO)
384 {
385 [self addColumn];
386 }
387 }
388
389 /** Loads if necessary and returns the NSCell at row in column. */
390 /* if you change this code, you may want to look at the _loadColumn
391 method in which the following code is integrated (for speed) */
392 - (id) loadedCellAtRow: (int)row
393 column: (int)column
394 {
395 NSMatrix *matrix;
396 id cell;
397
398 if ((matrix = [self matrixInColumn: column]) == nil)
399 {
400 return nil;
401 }
402
403 // Get the cell
404 if ((cell = [matrix cellAtRow: row column: 0]) == nil)
405 {
406 return nil;
407 }
408
409 // Load if not already loaded
410 if ([cell isLoaded])
411 {
412 return cell;
413 }
414 else
415 {
416 if (_passiveDelegate || [_browserDelegate respondsToSelector:
417 @selector(browser:willDisplayCell:atRow:column:)])
418 {
419 [_browserDelegate browser: self willDisplayCell: cell
420 atRow: row column: column];
421 }
422 [cell setLoaded: YES];
423 }
424
425 return cell;
426 }
427
428 /** Returns the matrix located in the column identified by index column. */
429 - (NSMatrix *) matrixInColumn: (int)column
430 {
431 NSBrowserColumn *bc;
432
433 if (column < 0 || column > _lastColumnLoaded)
434 {
435 return nil;
436 }
437
438 bc = [_browserColumns objectAtIndex: column];
439
440 if ((bc == nil) || !(bc->_isLoaded))
441 {
442 return nil;
443 }
444
445 return bc->_columnMatrix;
446 }
447
448 /*
449 * Getting and setting paths
450 */
451
452 /** Returns the browser's current path. */
453 - (NSString *) path
454 {
455 return [self pathToColumn: _lastColumnLoaded + 1];
456 }
457
458 /**
459 * <p>Parses path and selects corresponding items in the NSBrowser columns.
460 * </p>
461 * <p>This is the primary mechanism for programmatically updating the
462 * selection of a browser. It should result in the browser cells
463 * corresponding to the components being selected, and the
464 * browser columns up to the end of path (and just beyond if the
465 * last selected cell's [NSBrowserCell-isLeaf] returns YES).<br />
466 * It does <em>not</em> result in the browsers action being sent to its
467 * target, just in a change to the browser selection and display.
468 * </p>
469 * <p>If path begins with the -pathSeparator then it is taken to be absolute
470 * and the first component in it is expected to denote a cell in column
471 * zero. Otherwise it is taken to be relative to the currently selected
472 * column.
473 * </p>
474 * <p>Empty components (ie where a -pathSeparator occurs immediately
475 * after another or at the end of path) are simply ignored.
476 * </p>
477 * <p>The receivers delegate is asked to select each cell in turn
478 * using the -browser:selectCellWithString:inColumn: method (if it
479 * implements it). If this call to the delegate returns NO then
480 * the attempt to set the path fails.<br />
481 * If the delegate does not implement the method, the browser attempts
482 * to locate and select the cell itsself, and the method fails if it
483 * is unable to locate the cell by matching its [NSCell-stringValue] with
484 * the component of the path.
485 * </p>
486 * <p>The method returns YES if path contains no components or if a cell
487 * corresponding to the path was found. Otherwise it returns NO.
488 * </p>
489 */
490 - (BOOL) setPath: (NSString *)path
491 {
492 NSMutableArray *subStrings;
493 unsigned numberOfSubStrings;
494 unsigned indexOfSubStrings;
495 int column;
496 BOOL useDelegate = NO;
497
498 if ([_browserDelegate respondsToSelector:
499 @selector(browser:selectCellWithString:inColumn:)])
500 {
501 useDelegate = YES;
502 }
503
504 /*
505 * Ensure that our starting column is loaded.
506 */
507 if (_lastColumnLoaded < 0)
508 {
509 [self loadColumnZero];
510 }
511
512 /*
513 * Decompose the path.
514 */
515 subStrings = [[path componentsSeparatedByString: _pathSeparator] mutableCopy];
516 [subStrings removeObject: @""];
517 numberOfSubStrings = [subStrings count];
518
519 if ([path hasPrefix: _pathSeparator])
520 {
521 int i;
522 /*
523 * If the path begins with a separator, start at column 0.
524 * Otherwise start at the currently selected column.
525 */
526
527 column = 0;
528 /*
529 * Optimisation. If there are columns loaded, it may be that the
530 * specified path is already partially selected. If this is the
531 * case, we can avoid redrawing those columns.
532 */
533 for (i = 0; i <= _lastColumnLoaded && i < numberOfSubStrings; i++)
534 {
535 NSString *c = [[self selectedCellInColumn: i] stringValue];
536
537 if ([c isEqualToString: [subStrings objectAtIndex: i]])
538 {
539 column = i;
540 }
541 else
542 {
543 break;
544 }
545 }
546
547 [self setLastColumn: column];
548 indexOfSubStrings = column;
549 }
550 else
551 {
552 column = _lastColumnLoaded;
553 indexOfSubStrings = 0;
554 }
555
556 // cycle thru str's array created from path
557 while (indexOfSubStrings < numberOfSubStrings)
558 {
559 NSString *aStr = [subStrings objectAtIndex: indexOfSubStrings];
560 NSBrowserColumn *bc = [_browserColumns objectAtIndex: column];
561 NSMatrix *matrix = [bc columnMatrix];
562 NSBrowserCell *selectedCell = nil;
563 BOOL found = NO;
564
565 if (useDelegate == YES)
566 {
567 if ([_browserDelegate browser: self
568 selectCellWithString: aStr
569 inColumn: column])
570 {
571 found = YES;
572 selectedCell = [matrix selectedCell];
573 }
574 }
575 else
576 {
577 unsigned numOfRows = [matrix numberOfRows];
578 int row;
579
580 // find the cell in the browser matrix which is equal to aStr
581 for (row = 0; row < numOfRows; row++)
582 {
583 selectedCell = [matrix cellAtRow: row column: 0];
584
585 if ([[selectedCell stringValue] isEqualToString: aStr])
586 {
587 [matrix selectCellAtRow: row column: 0];
588 found = YES;
589 break;
590 }
591 }
592 }
593
594 if (found)
595 {
596 indexOfSubStrings++;
597 }
598 else
599 {
600 // if unable to find a cell whose title matches aStr return NO
601 NSDebugLLog (@"NSBrowser",
602 @"unable to find cell '%@' in column %d\n",
603 aStr, column);
604 break;
605 }
606
607 // if the cell is a leaf, we are finished setting the path
608 if ([selectedCell isLeaf])
609 {
610 break;
611 }
612
613 // else, it is not a leaf: get a column in the browser for it
614 [self addColumn];
615 column++;
616 }
617
618 if (indexOfSubStrings == numberOfSubStrings)
619 {
620 return YES;
621 }
622 else
623 {
624 return NO;
625 }
626 }
627
628 /** Returns a string representing the path from the first column up to,
629 but not including, the column at index column. */
630 - (NSString *) pathToColumn: (int)column
631 {
632 NSMutableString *s = [_pathSeparator mutableCopy];
633 NSString *string;
634 int i;
635
636 /*
637 * Cannot go past the number of loaded columns
638 */
639 if (column > _lastColumnLoaded)
640 {
641 column = _lastColumnLoaded + 1;
642 }
643
644 for (i = 0; i < column; ++i)
645 {
646 id c = [self selectedCellInColumn: i];
647
648 if (i != 0)
649 {
650 [s appendString: _pathSeparator];
651 }
652
653 string = [c stringValue];
654
655 if (string == nil)
656 {
657 /* This should happen only when c == nil, in which case it
658 doesn't make sense to go with the path */
659 break;
660 }
661 else
662 {
663 [s appendString: string];
664 }
665 }
666 /*
667 * We actually return a mutable string, but that's ok since a mutable
668 * string is a string and the documentation specifically says that
669 * people should not depend on methods that return strings to return
670 * immutable strings.
671 */
672
673 return AUTORELEASE (s);
674 }
675
676 /** Returns the path separator. The default is "/". */
677 - (NSString *) pathSeparator
678 {
679 return _pathSeparator;
680 }
681
682 /** Sets the path separator to newString. */
683 - (void) setPathSeparator: (NSString *)aString
684 {
685 ASSIGN(_pathSeparator, aString);
686 }
687
688
689 /*
690 * Manipulating columns
691 */
692 - (NSBrowserColumn *) _createColumn
693 {
694 NSBrowserColumn *bc;
695 NSScrollView *sc;
696 NSRect rect = {{0, 0}, {100, 100}};
697
698 bc = [[NSBrowserColumn alloc] init];
699
700 // Create a scrollview
701 sc = [[NSScrollView alloc] initWithFrame: rect];
702 [sc setHasHorizontalScroller: NO];
703 [sc setHasVerticalScroller: YES];
704
705 if (_separatesColumns)
706 {
707 [sc setBorderType: NSBezelBorder];
708 }
709 else
710 {
711 [sc setBorderType: NSNoBorder];
712 }
713
714 [bc setColumnScrollView: sc];
715 [self addSubview: sc];
716 RELEASE(sc);
717
718 [_browserColumns addObject: bc];
719 RELEASE(bc);
720
721 return bc;
722 }
723
724 /** Adds a column to the right of the last column. */
725 - (void) addColumn
726 {
727 int i;
728
729 if ((unsigned)(_lastColumnLoaded + 1) >= [_browserColumns count])
730 {
731 i = [_browserColumns indexOfObject: [self _createColumn]];
732 }
733 else
734 {
735 i = _lastColumnLoaded + 1;
736 }
737
738 if (i < 0)
739 {
740 i = 0;
741 }
742
743 [self _performLoadOfColumn: i];
744 [self setLastColumn: i];
745
746 _isLoaded = YES;
747
748 [self tile];
749
750 if (i > 0 && i - 1 == _lastVisibleColumn)
751 {
752 [self scrollColumnsRightBy: 1];
753 }
754 }
755
756 - (BOOL) acceptsFirstResponder
757 {
758 return YES;
759 }
760
761 - (BOOL) becomeFirstResponder
762 {
763 NSMatrix *matrix;
764 int selectedColumn;
765
766 selectedColumn = [self selectedColumn];
767 if (selectedColumn == -1)
768 matrix = [self matrixInColumn: 0];
769 else
770 matrix = [self matrixInColumn: selectedColumn];
771
772 if (matrix)
773 [_window makeFirstResponder: matrix];
774
775 return YES;
776 }
777
778 /** Updates the NSBrowser to display all loaded columns. */
779 - (void) displayAllColumns
780 {
781 [self tile];
782 }
783
784 /** Updates the NSBrowser to display the column with the given index. */
785 - (void) displayColumn: (int)column
786 {
787 id bc, sc;
788
789 // If not visible then nothing to display
790 if ((column < _firstVisibleColumn) || (column > _lastVisibleColumn))
791 {
792 return;
793 }
794
795 [self tile];
796
797 // Update and display title of column
798 if (_isTitled)
799 {
800 [self lockFocus];
801 [self drawTitleOfColumn: column
802 inRect: [self titleFrameOfColumn: column]];
803 [self unlockFocus];
804 }
805
806 // Display column
807 if (!(bc = [_browserColumns objectAtIndex: column]))
808 return;
809 if (!(sc = [bc columnScrollView]))
810 return;
811
812 /* FIXME: why the following ? Are we displaying now, or marking for
813 * later display ?? Given the name, I think we are displaying
814 * now. */
815 [sc setNeedsDisplay: YES];
816 }
817
818 /** Returns the column number in which matrix is located. */
819 - (int) columnOfMatrix: (NSMatrix *)matrix
820 {
821 int i, count;
822
823 // Loop through columns and compare matrixes
824 count = [_browserColumns count];
825 for (i = 0; i < count; ++i)
826 {
827 if (matrix == [self matrixInColumn: i])
828 return i;
829 }
830
831 // Not found
832 return -1;
833 }
834
835 /** Returns the index of the last column with a selected item. */
836 - (int) selectedColumn
837 {
838 int i;
839 id matrix;
840
841 for (i = _lastColumnLoaded; i >= 0; i--)
842 {
843 if (!(matrix = [self matrixInColumn: i]))
844 continue;
845 if ([matrix selectedCell])
846 return i;
847 }
848
849 return -1;
850 }
851
852 /** Returns the index of the last column loaded. */
853 - (int) lastColumn
854 {
855 return _lastColumnLoaded;
856 }
857
858 /** Sets the last column to column. */
859 - (void) setLastColumn: (int)column
860 {
861 int i, count, num;
862 id bc, sc;
863
864 if (column > _lastColumnLoaded)
865 {
866 return;
867 }
868
869 if (column < 0)
870 {
871 column = -1;
872 _isLoaded = NO;
873 }
874
875 _lastColumnLoaded = column;
876
877 // Unloads columns.
878 count = [_browserColumns count];
879 num = [self numberOfVisibleColumns];
880
881 for (i = column + 1; i < count; ++i)
882 {
883 bc = [_browserColumns objectAtIndex: i];
884 sc = [bc columnScrollView];
885
886 if ([bc isLoaded])
887 {
888 // Make the column appear empty by removing the matrix
889 if (sc)
890 {
891 [sc setDocumentView: nil];
892 }
893 [bc setIsLoaded: NO];
894 [self setTitle: nil ofColumn: i];
895 }
896
897 if (!_reusesColumns && i > _lastVisibleColumn)
898 {
899 [sc removeFromSuperview];
900 [_browserColumns removeObject: bc];
901 count--;
902 i--;
903 }
904 }
905
906 [self scrollColumnToVisible:column];
907 }
908
909 /** Returns the index of the first visible column. */
910 - (int) firstVisibleColumn
911 {
912 return _firstVisibleColumn;
913 }
914
915 /** Returns the number of columns visible. */
916 - (int) numberOfVisibleColumns
917 {
918 int num;
919
920 num = _lastVisibleColumn - _firstVisibleColumn + 1;
921
922 return (num > 0 ? num : 1);
923 }
924
925 /** Returns the index of the last visible column. */
926 - (int) lastVisibleColumn
927 {
928 return _lastVisibleColumn;
929 }
930
931 /** Invokes delegate method browser:isColumnValid: for visible columns. */
932 - (void) validateVisibleColumns
933 {
934 int i;
935
936 // If delegate doesn't care, just return
937 if (![_browserDelegate respondsToSelector:
938 @selector(browser:isColumnValid:)])
939 {
940 return;
941 }
942
943 // Loop through the visible columns
944 for (i = _firstVisibleColumn; i <= _lastVisibleColumn; ++i)
945 {
946 // Ask delegate if the column is valid and if not
947 // then reload the column
948 if (![_browserDelegate browser: self isColumnValid: i])
949 {
950 [self reloadColumn: i];
951 }
952 }
953 }
954
955
956 /*
957 * Loading columns
958 */
959
960 /** Returns whether column zero is loaded. */
961 - (BOOL) isLoaded
962 {
963 return _isLoaded;
964 }
965
966 /** Loads column zero; unloads previously loaded columns. */
967 - (void) loadColumnZero
968 {
969 // set last column loaded
970 [self setLastColumn: -1];
971
972 // load column 0
973 [self addColumn];
974
975 [self _remapColumnSubviews: YES];
976 [self _setColumnTitlesNeedDisplay];
977 }
978
979 /** Reloads column if it is loaded; sets it as the last column.
980 Reselects previously selected cells, if they remain. */
981 - (void) reloadColumn: (int)column
982 {
983 NSArray *selectedCells;
984 NSEnumerator *selectedCellsEnumerator;
985 NSMatrix *matrix;
986 NSCell *cell;
987
988 matrix = [self matrixInColumn: column];
989 if (matrix == nil)
990 {
991 return;
992 }
993
994 // Get the previously selected cells
995 selectedCells = [[matrix selectedCells] copy];
996
997 // Perform the data load
998 [self _performLoadOfColumn: column];
999 // set last column loaded
1000 [self setLastColumn: column];
1001
1002 // Restore the selected cells
1003 matrix = [self matrixInColumn: column];
1004 selectedCellsEnumerator = [selectedCells objectEnumerator];
1005 while ((cell = [selectedCellsEnumerator nextObject]) != nil)
1006 {
1007 int sRow, sColumn;
1008
1009 if ([matrix getRow: &sRow column: &sColumn ofCell: cell])
1010 {
1011 [matrix selectCellAtRow: sRow column: sColumn];
1012 }
1013 }
1014 RELEASE(selectedCells);
1015 }
1016
1017
1018 /*
1019 * Setting selection characteristics
1020 */
1021
1022 /** Returns whether the user can select branch items when multiple selection
1023 is enabled. */
1024 - (BOOL) allowsBranchSelection
1025 {
1026 return _allowsBranchSelection;
1027 }
1028
1029 /** Sets whether the user can select branch items when multiple selection
1030 is enabled. */
1031 - (void) setAllowsBranchSelection: (BOOL)flag
1032 {
1033 _allowsBranchSelection = flag;
1034 }
1035
1036 /** Returns whether there can be nothing selected. */
1037 - (BOOL) allowsEmptySelection
1038 {
1039 return _allowsEmptySelection;
1040 }
1041
1042 /** Sets whether there can be nothing selected. */
1043 - (void) setAllowsEmptySelection: (BOOL)flag
1044 {
1045 _allowsEmptySelection = flag;
1046 }
1047
1048 /** Returns whether the user can select multiple items. */
1049 - (BOOL) allowsMultipleSelection
1050 {
1051 return _allowsMultipleSelection;
1052 }
1053
1054 /** Sets whether the user can select multiple items. */
1055 - (void) setAllowsMultipleSelection: (BOOL)flag
1056 {
1057 _allowsMultipleSelection = flag;
1058 }
1059
1060
1061 /*
1062 * Setting column characteristics
1063 */
1064
1065 /** Returns YES if NSMatrix objects aren't freed when their columns
1066 are unloaded. */
1067 - (BOOL) reusesColumns
1068 {
1069 return _reusesColumns;
1070 }
1071
1072 /** If flag is YES, prevents NSMatrix objects from being freed when
1073 their columns are unloaded, so they can be reused. */
1074 - (void) setReusesColumns: (BOOL)flag
1075 {
1076 _reusesColumns = flag;
1077 }
1078
1079 /** Returns the maximum number of visible columns. */
1080 - (int) maxVisibleColumns
1081 {
1082 return _maxVisibleColumns;
1083 }
1084
1085 /** Sets the maximum number of columns displayed. */
1086 - (void) setMaxVisibleColumns: (int)columnCount
1087 {
1088 if ((columnCount < 1) || (_maxVisibleColumns == columnCount))
1089 return;
1090
1091 _maxVisibleColumns = columnCount;
1092
1093 // Redisplay
1094 [self tile];
1095 }
1096
1097 /** Returns the minimum column width in pixels. */
1098 - (int) minColumnWidth
1099 {
1100 return _minColumnWidth;
1101 }
1102
1103 /** Sets the minimum column width in pixels. */
1104 - (void) setMinColumnWidth: (int)columnWidth
1105 {
1106 float sw;
1107
1108 sw = scrollerWidth;
1109 // Take the border into account
1110 if (_separatesColumns)
1111 sw += 2 * (_sizeForBorderType (NSBezelBorder)).width;
1112
1113 // Column width cannot be less than scroller and border
1114 if (columnWidth < sw)
1115 _minColumnWidth = sw;
1116 else
1117 _minColumnWidth = columnWidth;
1118
1119 [self tile];
1120 }
1121
1122 /** Returns whether columns are separated by bezeled borders. */
1123 - (BOOL) separatesColumns
1124 {
1125 return _separatesColumns;
1126 }
1127
1128 /** Sets whether to separate columns with bezeled borders. */
1129 - (void) setSeparatesColumns: (BOOL)flag
1130 {
1131 NSBrowserColumn *bc;
1132 NSScrollView *sc;
1133 NSBorderType bt;
1134 int i, columnCount;
1135
1136 // if this flag already set or browser is titled -- do nothing
1137 if (_separatesColumns == flag || _isTitled)
1138 return;
1139
1140 columnCount = [_browserColumns count];
1141 bt = flag ? NSBezelBorder : NSNoBorder;
1142 for (i = 0; i < columnCount; i++)
1143 {
1144 bc = [_browserColumns objectAtIndex: i];
1145 sc = [bc columnScrollView];
1146 [sc setBorderType:bt];
1147 }
1148
1149 _separatesColumns = flag;
1150 [self setNeedsDisplay:YES];
1151 [self tile];
1152 }
1153
1154 /** Returns YES if the title of a column is set to the string value of
1155 the selected NSCell in the previous column.*/
1156 - (BOOL) takesTitleFromPreviousColumn
1157 {
1158 return _takesTitleFromPreviousColumn;
1159 }
1160
1161 /** Sets whether the title of a column is set to the string value of the
1162 selected NSCell in the previous column. */
1163 - (void) setTakesTitleFromPreviousColumn: (BOOL)flag
1164 {
1165 if (_takesTitleFromPreviousColumn != flag)
1166 {
1167 _takesTitleFromPreviousColumn = flag;
1168 [self setNeedsDisplay: YES];
1169 }
1170 }
1171
1172
1173 /*
1174 * Manipulating column titles
1175 */
1176
1177 /** Returns the title displayed for the column at index column. */
1178 - (NSString *) titleOfColumn: (int)column
1179 {
1180 NSBrowserColumn *bc;
1181
1182 bc = [_browserColumns objectAtIndex: column];
1183
1184 return bc->_columnTitle;
1185 }
1186
1187 /** Sets the title of the column at index column to aString. */
1188 - (void) setTitle: (NSString *)aString
1189 ofColumn: (int)column
1190 {
1191 NSBrowserColumn *bc;
1192
1193 bc = [_browserColumns objectAtIndex: column];
1194
1195 [bc setColumnTitle: aString];
1196
1197 // If column is not visible then nothing to redisplay
1198 if (!_isTitled || !NSBR_COLUMN_IS_VISIBLE(column))
1199 return;
1200
1201 [self setNeedsDisplayInRect: [self titleFrameOfColumn: column]];
1202 }
1203
1204 /** Returns whether columns display titles. */
1205 - (BOOL) isTitled
1206 {
1207 return _isTitled;
1208 }
1209
1210 /** Sets whether columns display titles. */
1211 - (void) setTitled: (BOOL)flag
1212 {
1213 if (_isTitled == flag || !_separatesColumns)
1214 return;
1215
1216 _isTitled = flag;
1217 [self tile];
1218 [self setNeedsDisplay: YES];
1219 }
1220
1221 - (void) drawTitleOfColumn: (int)column
1222 inRect: (NSRect)aRect
1223 {
1224 [self drawTitle: [self titleOfColumn: column]
1225 inRect: aRect
1226 ofColumn: column];
1227 }
1228
1229 /** Draws the title for the column at index column within the rectangle
1230 defined by aRect. */
1231 - (void) drawTitle: (NSString *)title
1232 inRect: (NSRect)aRect
1233 ofColumn: (int)column
1234 {
1235 if (!_isTitled || !NSBR_COLUMN_IS_VISIBLE(column))
1236 return;
1237
1238 [titleCell setStringValue: title];
1239 [titleCell drawWithFrame: aRect inView: self];
1240 }
1241
1242 /** Returns the height of column titles. */
1243 - (float) titleHeight
1244 {
1245 // Nextish look requires 21 here
1246 return 21;
1247 }
1248
1249 /** Returns the bounds of the title frame for the column at index column. */
1250 - (NSRect) titleFrameOfColumn: (int)column
1251 {
1252 // Not titled then no frame
1253 if (!_isTitled)
1254 {
1255 return NSZeroRect;
1256 }
1257 else
1258 {
1259 // Number of columns over from the first
1260 int n = column - _firstVisibleColumn;
1261 int h = [self titleHeight];
1262 NSRect r;
1263
1264 // Calculate origin
1265 if (_separatesColumns)
1266 {
1267 r.origin.x = n * (_columnSize.width + NSBR_COLUMN_SEP);
1268 }
1269 else
1270 {
1271 r.origin.x = n * _columnSize.width;
1272 }
1273 r.origin.y = _frame.size.height - h;
1274
1275 // Calculate size
1276 if (column == _lastVisibleColumn)
1277 {
1278 r.size.width = _frame.size.width - r.origin.x;
1279 }
1280 else
1281 {
1282 r.size.width = _columnSize.width;
1283 }
1284 r.size.height = h;
1285
1286 return r;
1287 }
1288 }
1289
1290
1291 /*
1292 * Scrolling an NSBrowser
1293 */
1294
1295 /** Scrolls to make the column at index column visible. */
1296 - (void) scrollColumnToVisible: (int)column
1297 {
1298 // If its the last visible column then we are there already
1299 if (_lastVisibleColumn < column)
1300 {
1301 [self scrollColumnsRightBy: (column - _lastVisibleColumn)];
1302 }
1303 else if (_firstVisibleColumn > column)
1304 {
1305 [self scrollColumnsLeftBy: (_firstVisibleColumn - column)];
1306 }
1307 }
1308
1309 /** Scrolls columns left by shiftAmount columns. */
1310 - (void) scrollColumnsLeftBy: (int)shiftAmount
1311 {
1312 // Cannot shift past the zero column
1313 if ((_firstVisibleColumn - shiftAmount) < 0)
1314 shiftAmount = _firstVisibleColumn;
1315
1316 // No amount to shift then nothing to do
1317 if (shiftAmount <= 0)
1318 return;
1319
1320 // Notify the delegate
1321 if ([_browserDelegate respondsToSelector: @selector(browserWillScroll:)])
1322 [_browserDelegate browserWillScroll: self];
1323
1324 // Shift
1325 _firstVisibleColumn = _firstVisibleColumn - shiftAmount;
1326 _lastVisibleColumn = _lastVisibleColumn - shiftAmount;
1327
1328 // Update the scroller
1329 [self updateScroller];
1330
1331 // Update the scrollviews
1332 [self tile];
1333 [self _remapColumnSubviews: YES];
1334 [self _setColumnTitlesNeedDisplay];
1335
1336 // Notify the delegate
1337 if ([_browserDelegate respondsToSelector: @selector(browserDidScroll:)])
1338 [_browserDelegate browserDidScroll: self];
1339 }
1340
1341 /** Scrolls columns right by shiftAmount columns. */
1342 - (void) scrollColumnsRightBy: (int)shiftAmount
1343 {
1344 // Cannot shift past the last loaded column
1345 if ((shiftAmount + _lastVisibleColumn) > _lastColumnLoaded)
1346 shiftAmount = _lastColumnLoaded - _lastVisibleColumn;
1347
1348 // No amount to shift then nothing to do
1349 if (shiftAmount <= 0)
1350 return;
1351
1352 // Notify the delegate
1353 if ([_browserDelegate respondsToSelector: @selector(browserWillScroll:)])
1354 [_browserDelegate browserWillScroll: self];
1355
1356 // Shift
1357 _firstVisibleColumn = _firstVisibleColumn + shiftAmount;
1358 _lastVisibleColumn = _lastVisibleColumn + shiftAmount;
1359
1360 // Update the scroller
1361 [self updateScroller];
1362
1363 // Update the scrollviews
1364 [self tile];
1365 [self _remapColumnSubviews: NO];
1366 [self _setColumnTitlesNeedDisplay];
1367
1368 // Notify the delegate
1369 if ([_browserDelegate respondsToSelector: @selector(browserDidScroll:)])
1370 [_browserDelegate browserDidScroll: self];
1371 }
1372
1373 /** Updates the horizontal scroller to reflect column positions. */
1374 - (void) updateScroller
1375 {
1376 int num;
1377
1378 num = [self numberOfVisibleColumns];
1379
1380 // If there are not enough columns to scroll with
1381 // then the column must be visible
1382 if ((_lastColumnLoaded == 0) ||
1383 (_lastColumnLoaded <= (num - 1)))
1384 {
1385 [_horizontalScroller setEnabled: NO];
1386 }
1387 else
1388 {
1389 if (!_skipUpdateScroller)
1390 {
1391 float prop = (float)num / (float)(_lastColumnLoaded + 1);
1392 float i = _lastColumnLoaded - num + 1;
1393 float f = 1 + ((_lastVisibleColumn - _lastColumnLoaded) / i);
1394
1395 [_horizontalScroller setFloatValue: f knobProportion: prop];
1396 }
1397 [_horizontalScroller setEnabled: YES];
1398 }
1399
1400 [_horizontalScroller setNeedsDisplay: YES];
1401 }
1402
1403 /** Scrolls columns left or right based on an NSScroller. */
1404 - (void) scrollViaScroller: (NSScroller *)sender
1405 {
1406 NSScrollerPart hit;
1407
1408 if ([sender class] != [NSScroller class])
1409 return;
1410
1411 hit = [sender hitPart];
1412
1413 switch (hit)
1414 {
1415 // Scroll to the left
1416 case NSScrollerDecrementLine:
1417 case NSScrollerDecrementPage:
1418 [self scrollColumnsLeftBy: 1];
1419 break;
1420
1421 // Scroll to the right
1422 case NSScrollerIncrementLine:
1423 case NSScrollerIncrementPage:
1424 [self scrollColumnsRightBy: 1];
1425 break;
1426
1427 // The knob or knob slot
1428 case NSScrollerKnob:
1429 case NSScrollerKnobSlot:
1430 {
1431 float f = [sender floatValue];
1432
1433 _skipUpdateScroller = YES;
1434 [self scrollColumnToVisible: rintf(f * _lastColumnLoaded)];
1435 _skipUpdateScroller = NO;
1436 }
1437 break;
1438
1439 // NSScrollerNoPart ???
1440 default:
1441 break;
1442 }
1443 }
1444
1445
1446 /*
1447 * Showing a horizontal scroller
1448 */
1449
1450 /** Returns whether an NSScroller is used to scroll horizontally. */
1451 - (BOOL) hasHorizontalScroller
1452 {
1453 return _hasHorizontalScroller;
1454 }
1455
1456 /** Sets whether an NSScroller is used to scroll horizontally. */
1457 - (void) setHasHorizontalScroller: (BOOL)flag
1458 {
1459 if (_hasHorizontalScroller != flag)
1460 {
1461 _hasHorizontalScroller = flag;
1462 if (!flag)
1463 [_horizontalScroller removeFromSuperview];
1464 else
1465 [self addSubview: _horizontalScroller];
1466 [self tile];
1467 [self setNeedsDisplay: YES];
1468 }
1469 }
1470
1471
1472 /*
1473 * Setting the behavior of arrow keys
1474 */
1475
1476 /** Returns YES if the arrow keys are enabled. */
1477 - (BOOL) acceptsArrowKeys
1478 {
1479 return _acceptsArrowKeys;
1480 }
1481
1482 /** Enables or disables the arrow keys as used for navigating within
1483 and between browsers. */
1484 - (void) setAcceptsArrowKeys: (BOOL)flag
1485 {
1486 _acceptsArrowKeys = flag;
1487 }
1488
1489 /** Returns NO if pressing an arrow key only scrolls the browser, YES if
1490 it also sends the action message specified by setAction:. */
1491 - (BOOL) sendsActionOnArrowKeys
1492 {
1493 return _sendsActionOnArrowKeys;
1494 }
1495
1496 /** Sets whether pressing an arrow key will cause the action message
1497 to be sent (in addition to causing scrolling). */
1498 - (void) setSendsActionOnArrowKeys: (BOOL)flag
1499 {
1500 _sendsActionOnArrowKeys = flag;
1501 }
1502
1503
1504 /*
1505 * Getting column frames
1506 */
1507
1508 /** Returns the rectangle containing the column at index column. */
1509 - (NSRect) frameOfColumn: (int)column
1510 {
1511 NSRect r = NSZeroRect;
1512 NSSize bs = _sizeForBorderType (NSBezelBorder);
1513 int n;
1514
1515 // Number of columns over from the first
1516 n = column - _firstVisibleColumn;
1517
1518 // Calculate the frame
1519 r.size = _columnSize;
1520 r.origin.x = n * _columnSize.width;
1521
1522 if (_separatesColumns)
1523 {
1524 r.origin.x += n * NSBR_COLUMN_SEP;
1525 }
1526 else
1527 {
1528 if (column == _firstVisibleColumn)
1529 r.origin.x = (n * _columnSize.width) + 2;
1530 else
1531 r.origin.x = (n * _columnSize.width) + (n + 2);
1532 }
1533
1534 // Adjust for horizontal scroller
1535 if (_hasHorizontalScroller)
1536 {
1537 if (_separatesColumns)
1538 r.origin.y = (scrollerWidth - 1) + (2 * bs.height) + NSBR_VOFFSET;
1539 else
1540 r.origin.y = scrollerWidth + bs.width;
1541 }
1542
1543 // Padding : _columnSize.width is rounded in "tile" method
1544 if (column == _lastVisibleColumn)
1545 {
1546 if (_separatesColumns)
1547 r.size.width = _frame.size.width - r.origin.x;
1548 else
1549 r.size.width = _frame.size.width
1550 - (r.origin.x + (2 * bs.width) + ([self numberOfVisibleColumns] - 1));
1551 }
1552
1553 if (r.size.width < 0)
1554 {
1555 r.size.width = 0;
1556 }
1557 if (r.size.height < 0)
1558 {
1559 r.size.height = 0;
1560 }
1561
1562 return r;
1563 }
1564
1565 /** Returns the rectangle containing the column at index column, */
1566 // not including borders.
1567 - (NSRect) frameOfInsideOfColumn: (int)column
1568 {
1569 // xxx what does this one do?
1570 return [self frameOfColumn: column];
1571 }
1572
1573
1574 /*
1575 * Arranging browser components
1576 */
1577
1578 /** Adjusts the various subviews of NSBrowser-scrollers, columns,
1579 titles, and so on-without redrawing. Your code shouldn't send this
1580 message. It's invoked any time the appearance of the NSBrowser
1581 changes. */
1582 - (void) tile
1583 {
1584 NSSize bs = _sizeForBorderType (NSBezelBorder);
1585 int i, num, columnCount, delta;
1586 float frameWidth;
1587
1588 _columnSize.height = _frame.size.height;
1589
1590 // Titles (there is no real frames to resize)
1591 if (_isTitled)
1592 {
1593 _columnSize.height -= [self titleHeight] + NSBR_VOFFSET;
1594 }
1595
1596 // Horizontal scroller
1597 if (_hasHorizontalScroller)
1598 {
1599 _scrollerRect.origin.x = bs.width;
1600 _scrollerRect.origin.y = bs.height - 1;
1601 _scrollerRect.size.width = (_frame.size.width - (2 * bs.width)) + 1;
1602 _scrollerRect.size.height = scrollerWidth;
1603
1604 if (_separatesColumns)
1605 _columnSize.height -= (scrollerWidth - 1) + (2 * bs.height)
1606 + NSBR_VOFFSET;
1607 else
1608 _columnSize.height -= scrollerWidth + (2 * bs.height);
1609
1610 if (!NSEqualRects(_scrollerRect, [_horizontalScroller frame]))
1611 {
1612 [_horizontalScroller setFrame: _scrollerRect];
1613 }
1614 }
1615 else
1616 {
1617 _scrollerRect = NSZeroRect;
1618 }
1619
1620 num = _lastVisibleColumn - _firstVisibleColumn + 1;
1621
1622 if (_minColumnWidth > 0)
1623 {
1624 float colWidth = _minColumnWidth + scrollerWidth;
1625
1626 if ((int)(_frame.size.width > _minColumnWidth))
1627 {
1628 if (_separatesColumns)
1629 colWidth += NSBR_COLUMN_SEP;
1630
1631 columnCount = (int)(_frame.size.width / colWidth);
1632 }
1633 else
1634 columnCount = 1;
1635 }
1636 else
1637 columnCount = num;
1638
1639 if (_maxVisibleColumns > 0 && columnCount > _maxVisibleColumns)
1640 columnCount = _maxVisibleColumns;
1641
1642 if (columnCount != num)
1643 {
1644 if (num > 0)
1645 delta = columnCount - num;
1646 else
1647 delta = columnCount - 1;
1648
1649 if ((delta > 0) && (_lastVisibleColumn <= _lastColumnLoaded))
1650 {
1651 _firstVisibleColumn = (_firstVisibleColumn - delta > 0) ?
1652 _firstVisibleColumn - delta : 0;
1653 }
1654
1655 for (i = [_browserColumns count]; i < columnCount; i++)
1656 [self _createColumn];
1657
1658 _lastVisibleColumn = _firstVisibleColumn + columnCount - 1;
1659 }
1660
1661 // Columns
1662 if (_separatesColumns)
1663 frameWidth = _frame.size.width - ((columnCount - 1) * NSBR_COLUMN_SEP);
1664 else
1665 frameWidth = _frame.size.width - (columnCount + (2 * bs.width));
1666
1667 _columnSize.width = (int)(frameWidth / (float)columnCount);
1668
1669 if (_columnSize.height < 0)
1670 _columnSize.height = 0;
1671
1672 for (i = _firstVisibleColumn; i <= _lastVisibleColumn; i++)
1673 {
1674 id bc, sc;
1675 id matrix;
1676
1677 // FIXME: in some cases the column is not loaded
1678 while (i >= [_browserColumns count]) [self _createColumn];
1679
1680 bc = [_browserColumns objectAtIndex: i];
1681
1682 if (!(sc = [bc columnScrollView]))
1683 {
1684 NSLog(@"NSBrowser error, sc != [bc columnScrollView]");
1685 return;
1686 }
1687
1688 [sc setFrame: [self frameOfColumn: i]];
1689 matrix = [bc columnMatrix];
1690
1691 // Adjust matrix to fit in scrollview if column has been loaded
1692 if (matrix && [bc isLoaded])
1693 {
1694 NSSize cs, ms;
1695
1696 cs = [sc contentSize];
1697 ms = [matrix cellSize];
1698 ms.width = cs.width;
1699 [matrix setCellSize: ms];
1700 [sc setDocumentView: matrix];
1701 }
1702 }
1703
1704 if (columnCount != num)
1705 {
1706 [self updateScroller];
1707 [self _remapColumnSubviews: YES];
1708 // [self _setColumnTitlesNeedDisplay];
1709 [self setNeedsDisplay: YES];
1710 }
1711 }
1712
1713 /*
1714 * Setting the delegate
1715 */
1716
1717 /** Returns the NSBrowser's delegate. */
1718 - (id) delegate
1719 {
1720 return _browserDelegate;
1721 }
1722
1723 /** Sets the NSBrowser's delegate to anObject. Raises
1724 NSBrowserIllegalDelegateException if the delegate specified by
1725 anObject doesn't respond to browser:willDisplayCell:atRow:column: (if
1726 passive) and either of the methods browser:numberOfRowsInColumn: or
1727 browser:createRowsForColumn:inMatrix:. */
1728 - (void) setDelegate: (id)anObject
1729 {
1730 BOOL flag = NO;
1731
1732 if ([anObject respondsToSelector:
1733 @selector(browser:numberOfRowsInColumn:)])
1734 {
1735 _passiveDelegate = YES;
1736 flag = YES;
1737 if (![anObject respondsToSelector:
1738 @selector(browser:willDisplayCell:atRow:column:)])
1739 [NSException raise: NSBrowserIllegalDelegateException
1740 format: @"(Passive) Delegate does not respond to %s\n",
1741 "browser: willDisplayCell: atRow: column: "];
1742 }
1743
1744 if ([anObject respondsToSelector:
1745 @selector(browser:createRowsForColumn:inMatrix:)])
1746 {
1747 _passiveDelegate = NO;
1748
1749 // If flag is already set
1750 // then delegate must respond to both methods
1751 if (flag)
1752 {
1753 [NSException raise: NSBrowserIllegalDelegateException
1754 format: @"Delegate responds to both %s and %s\n",
1755 "browser: numberOfRowsInColumn: ",
1756 "browser: createRowsForColumn: inMatrix: "];
1757 }
1758
1759 flag = YES;
1760 }
1761
1762 if (!flag)
1763 [NSException raise: NSBrowserIllegalDelegateException
1764 format: @"Delegate does not respond to %s or %s\n",
1765 "browser: numberOfRowsInColumn: ",
1766 "browser: createRowsForColumn: inMatrix: "];
1767
1768 _browserDelegate = anObject;
1769 }
1770
1771
1772 /*
1773 * Target and action
1774 */
1775
1776 /** Returns the NSBrowser's double-click action method. */
1777 - (SEL) doubleAction
1778 {
1779 return _doubleAction;
1780 }
1781
1782 /** Sets the NSBrowser's double-click action to aSelector. */
1783 - (void) setDoubleAction: (SEL)aSelector
1784 {
1785 _doubleAction = aSelector;
1786 }
1787
1788 /** Sends the action message to the target. Returns YES upon success,
1789 NO if no target for the message could be found. */
1790 - (BOOL) sendAction
1791 {
1792 return [self sendAction: [self action] to: [self target]];
1793 }
1794
1795
1796 /*
1797 * Event handling
1798 */
1799
1800 /** Responds to (single) mouse clicks in a column of the NSBrowser. */
1801 - (void) doClick: (id)sender
1802 {
1803 NSArray *a;
1804 NSMutableArray *selectedCells;
1805 NSEnumerator *enumerator;
1806 NSBrowserCell *cell;
1807 int column, aCount, selectedCellsCount;
1808
1809 if ([sender class] != _browserMatrixClass)
1810 return;
1811
1812 column = [self columnOfMatrix: sender];
1813 // If the matrix isn't ours then just return
1814 if (column < 0 || column > _lastColumnLoaded)
1815 return;
1816
1817 a = [sender selectedCells];
1818 aCount = [a count];
1819 if(aCount == 0)
1820 return;
1821
1822 selectedCells = [a mutableCopy];
1823
1824 enumerator = [a objectEnumerator];
1825 while ((cell = [enumerator nextObject]))
1826 {
1827 if (_allowsBranchSelection == NO && [cell isLeaf] == NO)
1828 {
1829 [selectedCells removeObject: cell];
1830 }
1831 }
1832
1833 if ([selectedCells count] == 0 && [sender selectedCell] != nil)
1834 [selectedCells addObject: [sender selectedCell]];
1835
1836 selectedCellsCount = [selectedCells count];
1837
1838 if (selectedCellsCount == 0)
1839 {
1840 // If we should not select the cell then deselect it
1841
1842 [sender deselectAllCells];
1843 }
1844 else if (selectedCellsCount < aCount)
1845 {
1846 [sender deselectSelectedCell];
1847
1848 enumerator = [selectedCells objectEnumerator];
1849 while ((cell = [enumerator nextObject]))
1850 [sender selectCell: cell];
1851 }
1852
1853 [self setLastColumn: column];
1854 // Single selection
1855 if (selectedCellsCount == 1)
1856 {
1857 cell = [selectedCells objectAtIndex: 0];
1858
1859 // If the cell is not a leaf we need to load a column
1860 if (![cell isLeaf])
1861 {
1862 [self addColumn];
1863 }
1864
1865 [sender scrollCellToVisibleAtRow: [sender selectedRow] column: 0];
1866 }
1867
1868 // Send the action to target
1869 [self sendAction];
1870
1871 RELEASE(selectedCells);
1872 }
1873
1874 /** Responds to double-clicks in a column of the NSBrowser. */
1875 - (void) doDoubleClick: (id)sender
1876 {
1877 // We have already handled the single click
1878 // so send the double action
1879
1880 [self sendAction: _doubleAction to: [self target]];
1881 }
1882
1883 + (void) initialize
1884 {
1885 if (self == [NSBrowser class])
1886 {
1887 // Initial version
1888 [self setVersion: 1];
1889 scrollerWidth = [NSScroller scrollerWidth];
1890 }
1891 }
1892
1893 /*
1894 * Override superclass methods
1895 */
1896
1897 /** Setups browser with frame 'rect'. */
1898 - (id) initWithFrame: (NSRect)rect
1899 {
1900 NSSize bs;
1901 //NSScroller *hs;
1902
1903 /* Created the shared titleCell if it hasn't been created already. */
1904 if (!titleCell)
1905 {
1906 titleCell = [GSBrowserTitleCell new];
1907 }
1908
1909 self = [super initWithFrame: rect];
1910
1911 // Class setting
1912 _browserCellPrototype = [[[NSBrowser cellClass] alloc] init];
1913 _browserMatrixClass = [NSMatrix class];
1914
1915 // Default values
1916 _pathSeparator = @"/";
1917 _allowsBranchSelection = YES;
1918 _allowsEmptySelection = YES;
1919 _allowsMultipleSelection = YES;
1920 _reusesColumns = NO;
1921 _separatesColumns = YES;
1922 _isTitled = YES;
1923 _takesTitleFromPreviousColumn = YES;
1924 _hasHorizontalScroller = YES;
1925 _isLoaded = NO;
1926 _acceptsArrowKeys = YES;
1927 _acceptsAlphaNumericalKeys = YES;
1928 _lastKeyPressed = 0.;
1929 _charBuffer = nil;
1930 _sendsActionOnArrowKeys = YES;
1931 _sendsActionOnAlphaNumericalKeys = YES;
1932 _browserDelegate = nil;
1933 _passiveDelegate = YES;
1934 _doubleAction = NULL;
1935 bs = _sizeForBorderType (NSBezelBorder);
1936 _minColumnWidth = scrollerWidth + (2 * bs.width);
1937 if (_minColumnWidth < 100.0)
1938 _minColumnWidth = 100.0;
1939
1940 // Horizontal scroller
1941 _scrollerRect.origin.x = bs.width;
1942 _scrollerRect.origin.y = bs.height;
1943 _scrollerRect.size.width = _frame.size.width - (2 * bs.width);
1944 _scrollerRect.size.height = scrollerWidth;
1945 _horizontalScroller = [[NSScroller alloc] initWithFrame: _scrollerRect];
1946 [_horizontalScroller setTarget: self];
1947 [_horizontalScroller setAction: @selector(scrollViaScroller:)];
1948 [self addSubview: _horizontalScroller];
1949 _skipUpdateScroller = NO;
1950
1951 // Columns
1952 _browserColumns = [[NSMutableArray alloc] init];
1953
1954 // Create a single column
1955 _lastColumnLoaded = -1;
1956 _firstVisibleColumn = 0;
1957 _lastVisibleColumn = 0;
1958 _maxVisibleColumns = 3;
1959 [self _createColumn];
1960
1961 return self;
1962 }
1963
1964 - (void) dealloc
1965 {
1966 RELEASE(_browserCellPrototype);
1967 RELEASE(_pathSeparator);
1968 RELEASE(_horizontalScroller);
1969 RELEASE(_browserColumns);
1970 TEST_RELEASE(_charBuffer);
1971
1972 [super dealloc];
1973 }
1974
1975
1976
1977 /*
1978 * Target-actions
1979 */
1980
1981 /** Set target to 'target' */
1982 - (void) setTarget: (id)target
1983 {
1984 _target = target;
1985 }
1986
1987 /** Return current target. */
1988 - (id) target
1989 {
1990 return _target;
1991 }
1992
1993 /** Set action to 's'. */
1994 - (void) setAction: (SEL)s
1995 {
1996 _action = s;
1997 }
1998
1999 /** Return current action. */
2000 - (SEL) action
2001 {
2002 return _action;
2003 }
2004
2005
2006
2007 /*
2008 * Events handling
2009 */
2010
2011 - (void) drawRect: (NSRect)rect
2012 {
2013 NSRectClip(rect);
2014 [[_window backgroundColor] set];
2015 NSRectFill(rect);
2016
2017 // Load the first column if not already done
2018 if (!_isLoaded)
2019 {
2020 [self loadColumnZero];
2021 }
2022
2023 // Draws titles
2024 if (_isTitled)
2025 {
2026 int i;
2027
2028 for (i = _firstVisibleColumn; i <= _lastVisibleColumn; ++i)
2029 {
2030 NSRect titleRect = [self titleFrameOfColumn: i];
2031 if (NSIntersectsRect (titleRect, rect) == YES)
2032 {
2033 [self drawTitleOfColumn: i
2034 inRect: titleRect];
2035 }
2036 }
2037 }
2038
2039 // Draws scroller border
2040 if (_hasHorizontalScroller)
2041 {
2042 NSRect scrollerBorderRect = _scrollerRect;
2043 NSSize bs = _sizeForBorderType (NSBezelBorder);
2044
2045 scrollerBorderRect.origin.x = 0;
2046 scrollerBorderRect.origin.y = 0;
2047 scrollerBorderRect.size.width += 2 * bs.width - 1;
2048 scrollerBorderRect.size.height += (2 * bs.height) - 1;
2049
2050 if ((NSIntersectsRect (scrollerBorderRect, rect) == YES) && _window)
2051 {
2052 NSDrawGrayBezel (scrollerBorderRect, rect);
2053 }
2054 }
2055
2056 if (!_separatesColumns)
2057 {
2058 NSPoint p1,p2;
2059 NSRect browserRect;
2060 int i, visibleColumns;
2061
2062 // Columns borders
2063 browserRect = NSMakeRect(0, 0, rect.size.width, rect.size.height);
2064 NSDrawGrayBezel (browserRect, rect);
2065
2066 [[NSColor blackColor] set];
2067 visibleColumns = [self numberOfVisibleColumns];
2068 for (i = 1; i < visibleColumns; i++)
2069 {
2070 p1 = NSMakePoint((_columnSize.width * i) + 2 + (i-1),
2071 _columnSize.height + scrollerWidth + 2);
2072 p2 = NSMakePoint((_columnSize.width * i) + 2 + (i-1), scrollerWidth + 3);
2073 [NSBezierPath strokeLineFromPoint: p1 toPoint: p2];
2074 }
2075
2076 // Horizontal scroller border
2077 p1 = NSMakePoint(2, scrollerWidth + 2);
2078 p2 = NSMakePoint(rect.size.width - 2, scrollerWidth + 2);
2079 [NSBezierPath strokeLineFromPoint: p1 toPoint: p2];
2080 }
2081 }
2082
2083 /* Informs the receivers's subviews that the receiver's bounds
2084 rectangle size has changed from oldFrameSize. */
2085 - (void) resizeSubviewsWithOldSize: (NSSize)oldSize
2086 {
2087 [self tile];
2088 }
2089
2090
2091 /* Override NSControl handler (prevents highlighting). */
2092 - (void) mouseDown: (NSEvent *)theEvent
2093 {
2094 }
2095
2096 - (void) moveLeft: (id)sender
2097 {
2098 if (_acceptsArrowKeys)
2099 {
2100 NSMatrix *matrix;
2101 int selectedColumn;
2102
2103 matrix = (NSMatrix *)[_window firstResponder];
2104 selectedColumn = [self columnOfMatrix:matrix];
2105 if (selectedColumn == -1)
2106 {
2107 selectedColumn = [self selectedColumn];
2108 matrix = [self matrixInColumn: selectedColumn];
2109 }
2110 if (selectedColumn > 0)
2111 {
2112 [matrix deselectAllCells];
2113 [matrix scrollCellToVisibleAtRow:0 column:0];
2114 [self setLastColumn: selectedColumn];
2115
2116 selectedColumn--;
2117 [self scrollColumnToVisible: selectedColumn];
2118 matrix = [self matrixInColumn: selectedColumn];
2119 [_window makeFirstResponder: matrix];
2120
2121 if (_sendsActionOnArrowKeys == YES)
2122 {
2123 [super sendAction: _action to: _target];
2124 }
2125 }
2126 }
2127 }
2128
2129 - (void) moveRight: (id)sender
2130 {
2131 if (_acceptsArrowKeys)
2132 {
2133 NSMatrix *matrix;
2134 int selectedColumn;
2135
2136 matrix = (NSMatrix *)[_window firstResponder];
2137 selectedColumn = [self columnOfMatrix:matrix];
2138 if (selectedColumn == -1)
2139 {
2140 selectedColumn = [self selectedColumn];
2141 matrix = [self matrixInColumn: selectedColumn];
2142 }
2143 if (selectedColumn == -1)
2144 {
2145 selectedColumn = 0;
2146 matrix = [self matrixInColumn: 0];
2147
2148 if ([[matrix cells] count])
2149 {
2150 [matrix selectCellAtRow: 0 column: 0];
2151 }
2152 }
2153 else
2154 {
2155 // if there is one selected cell and it is a leaf, move right
2156 // (column is already loaded)
2157 if (![[matrix selectedCell] isLeaf]
2158 && [[matrix selectedCells] count] == 1)
2159 {
2160 selectedColumn++;
2161 matrix = [self matrixInColumn: selectedColumn];
2162 if ([[matrix cells] count] && [matrix selectedCell] == nil)
2163 {
2164 [matrix selectCellAtRow: 0 column: 0];
2165 }
2166 // if selected cell is a leaf, we need to add a column
2167 if (![[matrix selectedCell] isLeaf]
2168 && [[matrix selectedCells] count] == 1)
2169 {
2170 [self addColumn];
2171 }
2172 }
2173 }
2174
2175 [_window makeFirstResponder: matrix];
2176
2177 if (_sendsActionOnArrowKeys == YES)
2178 {
2179 [super sendAction: _action to: _target];
2180 }
2181 }
2182 }
2183
2184 - (void) keyDown: (NSEvent *)theEvent
2185 {
2186 NSString *characters = [theEvent characters];
2187 unichar character = 0;
2188
2189 if ([characters length] > 0)
2190 {
2191 character = [characters characterAtIndex: 0];
2192 }
2193
2194 if (_acceptsArrowKeys)
2195 {
2196 switch (character)
2197 {
2198 case NSUpArrowFunctionKey:
2199 case NSDownArrowFunctionKey:
2200 return;
2201 case NSLeftArrowFunctionKey:
2202 [self moveLeft:self];
2203 return;
2204 case NSRightArrowFunctionKey:
2205 [self moveRight:self];
2206 return;
2207 case NSTabCharacter:
2208 {
2209 if ([theEvent modifierFlags] & NSShiftKeyMask)
2210 {
2211 [_window selectKeyViewPrecedingView: self];
2212 }
2213 else
2214 {
2215 [_window selectKeyViewFollowingView: self];
2216 }
2217 }
2218 return;
2219 break;
2220 }
2221 }
2222
2223 if (_acceptsAlphaNumericalKeys && (character < 0xF700)
2224 && ([characters length] > 0))
2225 {
2226 NSMatrix *matrix;
2227 NSString *sv;
2228 int i, n, s;
2229 int selectedColumn;
2230 SEL lcarcSel = @selector(loadedCellAtRow:column:);
2231 IMP lcarc = [self methodForSelector: lcarcSel];
2232
2233 selectedColumn = [self selectedColumn];
2234 if(selectedColumn != -1)
2235 {
2236 matrix = [self matrixInColumn: selectedColumn];
2237 n = [matrix numberOfRows];
2238 s = [matrix selectedRow];
2239
2240 if (!_charBuffer)
2241 {
2242 _charBuffer = [characters substringToIndex: 1];
2243 RETAIN(_charBuffer);
2244 }
2245 else
2246 {
2247 if (([theEvent timestamp] - _lastKeyPressed < 2000.0)
2248 && (_alphaNumericalLastColumn == selectedColumn))
2249 {
2250 NSString *transition;
2251 transition = [_charBuffer
2252 stringByAppendingString:
2253 [characters substringToIndex: 1]];
2254 RELEASE(_charBuffer);
2255 _charBuffer = transition;
2256 RETAIN(_charBuffer);
2257 }
2258 else
2259 {
2260 RELEASE(_charBuffer);
2261 _charBuffer = [characters substringToIndex: 1];
2262 RETAIN(_charBuffer);
2263 }
2264 }
2265
2266 _alphaNumericalLastColumn = selectedColumn;
2267 _lastKeyPressed = [theEvent timestamp];
2268
2269 sv = [((*lcarc)(self, lcarcSel, s, selectedColumn))
2270 stringValue];
2271
2272 if (([sv length] > 0)
2273 && ([sv hasPrefix: _charBuffer]))
2274 return;
2275
2276 for (i = s+1; i < n; i++)
2277 {
2278 sv = [((*lcarc)(self, lcarcSel, i, selectedColumn))
2279 stringValue];
2280 if (([sv length] > 0)
2281 && ([sv hasPrefix: _charBuffer]))
2282 {
2283 [self selectRow: i
2284 inColumn: selectedColumn];
2285 [matrix scrollCellToVisibleAtRow: i column: 0];
2286 [matrix performClick: self];
2287 return;
2288 }
2289 }
2290 for (i = 0; i < s; i++)
2291 {
2292 sv = [((*lcarc)(self, lcarcSel, i, selectedColumn))
2293 stringValue];
2294 if (([sv length] > 0)
2295 && ([sv hasPrefix: _charBuffer]))
2296 {
2297 [self selectRow: i
2298 inColumn: selectedColumn];
2299 [matrix scrollCellToVisibleAtRow: i column: 0];
2300 [matrix performClick: self];
2301 return;
2302 }
2303 }
2304 }
2305 _lastKeyPressed = 0.;
2306 }
2307
2308 [super keyDown: theEvent];
2309 }
2310
2311 /*
2312 * NSCoding protocol
2313 *
2314 * We do not encode most of the instance variables except the Browser columns
2315 * because they are internal objects (though not transportable). So we just
2316 * encode enoguh information to rebuild identical columns on the decoder
2317 * side. Same for the Horizontal Scroller
2318 */
2319
2320 - (void) encodeWithCoder: (NSCoder*)aCoder
2321 {
2322 [super encodeWithCoder: aCoder];
2323
2324 // Here to keep compatibility with old version
2325 [aCoder encodeObject: nil];
2326 [aCoder encodeObject:_browserCellPrototype];
2327 [aCoder encodeObject: NSStringFromClass (_browserMatrixClass)];
2328
2329 [aCoder encodeObject:_pathSeparator];
2330 [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_isLoaded];
2331 [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_allowsBranchSelection];
2332 [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_allowsEmptySelection];
2333 [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_allowsMultipleSelection];
2334 [aCoder encodeValueOfObjCType: @encode(int) at: &_maxVisibleColumns];
2335 [aCoder encodeValueOfObjCType: @encode(float) at: &_minColumnWidth];
2336 [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_reusesColumns];
2337 [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_separatesColumns];
2338 [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_takesTitleFromPreviousColumn];
2339 [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_isTitled];
2340
2341
2342 [aCoder encodeObject:_horizontalScroller];
2343 [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_hasHorizontalScroller];
2344 [aCoder encodeRect: _scrollerRect];
2345 [aCoder encodeSize: _columnSize];
2346
2347 [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_acceptsArrowKeys];
2348 [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_sendsActionOnArrowKeys];
2349 [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_acceptsAlphaNumericalKeys];
2350 [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_sendsActionOnAlphaNumericalKeys];
2351
2352 [aCoder encodeConditionalObject:_browserDelegate];
2353
2354 [aCoder encodeValueOfObjCType: @encode(SEL) at: &_doubleAction];
2355 [aCoder encodeConditionalObject: _target];
2356 [aCoder encodeValueOfObjCType: @encode(SEL) at: &_action];
2357
2358 [aCoder encodeObject: _browserColumns];
2359
2360 // Just encode the number of columns and the first visible
2361 // and rebuild the browser columns on the decoding side
2362 {
2363 int colCount = [_browserColumns count];
2364 [aCoder encodeValueOfObjCType: @encode(int) at: &colCount];
2365 [aCoder encodeValueOfObjCType: @encode(int) at: &_firstVisibleColumn];
2366 }
2367
2368 }
2369
2370 - (id) initWithCoder: (NSCoder*)aDecoder
2371 {
2372 int colCount;
2373 id dummy;
2374
2375 [super initWithCoder: aDecoder];
2376 // Here to keep compatibility with old version
2377 dummy = [aDecoder decodeObject];
2378 _browserCellPrototype = RETAIN([aDecoder decodeObject]);
2379 _browserMatrixClass = NSClassFromString ((NSString *)[aDecoder decodeObject]);
2380
2381 [self setPathSeparator: [aDecoder decodeObject]];
2382
2383 [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_isLoaded];
2384 [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_allowsBranchSelection];
2385 [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_allowsEmptySelection];
2386 [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_allowsMultipleSelection];
2387 [aDecoder decodeValueOfObjCType: @encode(int) at: &_maxVisibleColumns];
2388 [aDecoder decodeValueOfObjCType: @encode(float) at: &_minColumnWidth];
2389 [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_reusesColumns];
2390 [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_separatesColumns];
2391 [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_takesTitleFromPreviousColumn];
2392 [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_isTitled];
2393
2394 //NSBox *_horizontalScrollerBox;
2395 _horizontalScroller = RETAIN([aDecoder decodeObject]);
2396 [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_hasHorizontalScroller];
2397 _scrollerRect = [aDecoder decodeRect];
2398 _columnSize = [aDecoder decodeSize];
2399
2400 _skipUpdateScroller = NO;
2401 /*
2402 _horizontalScroller = [[NSScroller alloc] initWithFrame: _scrollerRect];
2403 [_horizontalScroller setTarget: self];
2404 [_horizontalScroller setAction: @selector(scrollViaScroller:)];
2405 */
2406 [self setHasHorizontalScroller: _hasHorizontalScroller];
2407
2408 [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_acceptsArrowKeys];
2409 [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_sendsActionOnArrowKeys];
2410 [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_acceptsAlphaNumericalKeys];
2411 [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_sendsActionOnAlphaNumericalKeys];
2412 _lastKeyPressed = 0;
2413 _charBuffer = nil;
2414 // Skip: int _alphaNumericalLastColumn;
2415
2416 _browserDelegate = [aDecoder decodeObject];
2417 if (_browserDelegate != nil)
2418 [self setDelegate:_browserDelegate];
2419 else
2420 _passiveDelegate = YES;
2421
2422
2423 [aDecoder decodeValueOfObjCType: @encode(SEL) at: &_doubleAction];
2424 _target = [aDecoder decodeObject];
2425 [aDecoder decodeValueOfObjCType: @encode(SEL) at: &_action];
2426
2427
2428 // Do the minimal thing to initiate the browser...
2429 /*
2430 _lastColumnLoaded = -1;
2431 _firstVisibleColumn = 0;
2432 _lastVisibleColumn = 0;
2433 [self _createColumn];
2434 */
2435 _browserColumns = RETAIN([aDecoder decodeObject]);
2436 // ..and rebuild any existing browser columns
2437 [aDecoder decodeValueOfObjCType: @encode(int) at: &colCount];
2438 [aDecoder decodeValueOfObjCType: @encode(int) at: &_firstVisibleColumn];
2439
2440 // Display even if there isn't any column
2441 _isLoaded = NO;
2442 [self tile];
2443 return self;
2444 }
2445
2446
2447
2448 /*
2449 * Div.
2450 */
2451
2452 - (BOOL) isOpaque
2453 {
2454 return YES; // See drawRect.
2455 }
2456
2457 @end
2458
2459 @implementation NSBrowser (GNUstepExtensions)
2460 /*
2461 * Setting the behavior of arrow keys
2462 */
2463
2464 /** Returns YES if the alphanumerical keys are enabled. */
2465 - (BOOL) acceptsAlphaNumericalKeys
2466 {
2467 return _acceptsAlphaNumericalKeys;
2468 }
2469
2470 /** Enables or disables the arrow keys as used for navigating within
2471 and between browsers. */
2472 - (void) setAcceptsAlphaNumericalKeys: (BOOL)flag
2473 {
2474 _acceptsAlphaNumericalKeys = flag;
2475 }
2476
2477 /** Returns NO if pressing an arrow key only scrolls the browser, YES if
2478 it also sends the action message specified by setAction:. */
2479 - (BOOL) sendsActionOnAlphaNumericalKeys
2480 {
2481 return _sendsActionOnAlphaNumericalKeys;
2482 }
2483
2484 /** Sets whether pressing an arrow key will cause the action message
2485 to be sent (in addition to causing scrolling). */
2486 - (void) setSendsActionOnAlphaNumericalKeys: (BOOL)flag
2487 {
2488 _sendsActionOnAlphaNumericalKeys = flag;
2489 }
2490
2491 @end
2492
2493
2494 /*
2495 *
2496 * PRIVATE METHODS
2497 *
2498 */
2499 @implementation NSBrowser (Private)
2500
2501 - (void) _remapColumnSubviews: (BOOL)fromFirst
2502 {
2503 id bc, sc;
2504 int i, count;
2505 id firstResponder = nil;
2506 BOOL setFirstResponder = NO;
2507
2508 // Removes all column subviews.
2509 count = [_browserColumns count];
2510 for (i = 0; i < count; i++)
2511 {
2512 bc = [_browserColumns objectAtIndex: i];
2513 sc = [bc columnScrollView];
2514
2515 if (!firstResponder && [bc columnMatrix] == [_window firstResponder])
2516 {
2517 firstResponder = [bc columnMatrix];
2518 }
2519 if (sc)
2520 {
2521 [sc removeFromSuperviewWithoutNeedingDisplay];
2522 }
2523 }
2524
2525 if (_firstVisibleColumn > _lastVisibleColumn)
2526 return;
2527
2528 // Sets columns subviews order according to fromFirst (display order...).
2529 // All added subviews are automaticaly marked as needing display (->
2530 // NSView).
2531 if (fromFirst)
2532 {
2533 for (i = _firstVisibleColumn; i <= _lastVisibleColumn; i++)
2534 {
2535 bc = [_browserColumns objectAtIndex: i];
2536 sc = [bc columnScrollView];
2537 [self addSubview: sc];
2538
2539 if ([bc columnMatrix] == firstResponder)
2540 {
2541 [_window makeFirstResponder: firstResponder];
2542 setFirstResponder = YES;
2543 }
2544 }
2545
2546 if (firstResponder && setFirstResponder == NO)
2547 {
2548 [_window makeFirstResponder:
2549 [[_browserColumns objectAtIndex: _firstVisibleColumn]
2550 columnMatrix]];
2551 }
2552 }
2553 else
2554 {
2555 for (i = _lastVisibleColumn; i >= _firstVisibleColumn; i--)
2556 {
2557 bc = [_browserColumns objectAtIndex: i];
2558 sc = [bc columnScrollView];
2559 [self addSubview: sc];
2560
2561 if ([bc columnMatrix] == firstResponder)
2562 {
2563 [_window makeFirstResponder: firstResponder];
2564 setFirstResponder = YES;
2565 }
2566 }
2567
2568 if (firstResponder && setFirstResponder == NO)
2569 {
2570 [_window makeFirstResponder:
2571 [[_browserColumns objectAtIndex: _lastVisibleColumn]
2572 columnMatrix]];
2573 }
2574 }
2575 }
2576
2577 /* Loads column 'column' (asking the delegate). */
2578 - (void) _performLoadOfColumn: (int)column
2579 {
2580 id bc, sc, matrix;
2581 int i, rows, cols;
2582
2583 if (_passiveDelegate)
2584 {
2585 // Ask the delegate for the number of rows
2586 rows = [_browserDelegate browser: self numberOfRowsInColumn: column];
2587 cols = 1;
2588 }
2589 else
2590 {
2591 rows = 0;
2592 cols = 0;
2593 }
2594
2595 bc = [_browserColumns objectAtIndex: column];
2596
2597 if (!(sc = [bc columnScrollView]))
2598 return;
2599
2600 matrix = [bc columnMatrix];
2601
2602 if (_reusesColumns && matrix)
2603 {
2604 [matrix renewRows: rows columns: cols];
2605
2606 // Mark all the cells as unloaded
2607 for (i = 0; i < rows; i++)
2608 {
2609 [[matrix cellAtRow: i column: 0] setLoaded: NO];
2610 }
2611 }
2612 else
2613 {
2614 NSRect matrixRect = {{0, 0}, {100, 100}};
2615 NSSize matrixIntercellSpace = {0, 0};
2616
2617 // create a new col matrix
2618 matrix = [[_browserMatrixClass alloc]
2619 initWithFrame: matrixRect
2620 mode: NSListModeMatrix
2621 prototype: _browserCellPrototype
2622 numberOfRows: rows
2623 numberOfColumns: cols];
2624 [matrix setIntercellSpacing: matrixIntercellSpace];
2625 [matrix setAllowsEmptySelection: _allowsEmptySelection];
2626 [matrix setAutoscroll: YES];
2627 if (!_allowsMultipleSelection)
2628 {
2629 [matrix setMode: NSRadioModeMatrix];
2630 }
2631 [matrix setTarget: self];
2632 [matrix setAction: @selector(doClick:)];
2633 [matrix setDoubleAction: @selector(doDoubleClick:)];
2634
2635 // set new col matrix and release old
2636 [bc setColumnMatrix: matrix];
2637 RELEASE (matrix);
2638 }
2639 [sc setDocumentView: matrix];
2640
2641 // Loading is different based upon passive/active delegate
2642 if (_passiveDelegate)
2643 {
2644 // Now loop through the cells and load each one
2645 id aCell;
2646 SEL sel1 = @selector(browser:willDisplayCell:atRow:column:);
2647 IMP imp1 = [_browserDelegate methodForSelector: sel1];
2648 SEL sel2 = @selector(cellAtRow:column:);
2649 IMP imp2 = [matrix methodForSelector: sel2];
2650
2651 for (i = 0; i < rows; i++)
2652 {
2653 aCell = (*imp2)(matrix, sel2, i, 0);
2654 if (![aCell isLoaded])
2655 {
2656 (*imp1)(_browserDelegate, sel1, self, aCell, i,
2657 column);
2658 [aCell setLoaded: YES];
2659 }
2660 }
2661 }
2662 else
2663 {
2664 // Tell the delegate to create the rows
2665 [_browserDelegate browser: self
2666 createRowsForColumn: column
2667 inMatrix: matrix];
2668 }
2669
2670 [sc setNeedsDisplay: YES];
2671 [bc setIsLoaded: YES];
2672
2673 if (column > _lastColumnLoaded)
2674 {
2675 _lastColumnLoaded = column;
2676 }
2677
2678 /* Determine the height of a cell in the matrix, and set that as the
2679 cellSize of the matrix. */
2680 {
2681 NSSize cs, ms;
2682 NSBrowserCell *b = [matrix cellAtRow: 0 column: 0];
2683
2684 if (b != nil)
2685 {
2686 ms = [b cellSize];
2687 }
2688 else
2689 {
2690 ms = [matrix cellSize];
2691 }
2692 cs = [sc contentSize];
2693 ms.width = cs.width;
2694 [matrix setCellSize: ms];
2695 }
2696
2697 // Get the title even when untitled, as this may change later.
2698 [self setTitle: [self _getTitleOfColumn: column] ofColumn: column];
2699 }
2700
2701 /* Get the title of a column. */
2702 - (NSString *) _getTitleOfColumn: (int)column
2703 {
2704 // Ask the delegate for the column title
2705 if ([_browserDelegate respondsToSelector:
2706 @selector(browser:titleOfColumn:)])
2707 {
2708 return [_browserDelegate browser: self titleOfColumn: column];
2709 }
2710
2711
2712 // Check if we take title from previous column
2713 if (_takesTitleFromPreviousColumn)
2714 {
2715 id c;
2716
2717 // If first column then use the path separator
2718 if (column == 0)
2719 {
2720 return _pathSeparator;
2721 }
2722
2723 // Get the selected cell
2724 // Use its string value as the title
2725 // Only if it is not a leaf
2726 if(_allowsMultipleSelection == NO)
2727 {
2728 c = [self selectedCellInColumn: column - 1];
2729 }
2730 else
2731 {
2732 NSMatrix *matrix;
2733 NSArray *selectedCells;
2734
2735 if (!(matrix = [self matrixInColumn: column - 1]))
2736 return @"";
2737
2738 selectedCells = [matrix selectedCells];
2739
2740 if([selectedCells count] == 1)
2741 {
2742 c = [selectedCells objectAtIndex:0];
2743 }
2744 else
2745 {
2746 return @"";
2747 }
2748 }
2749
2750 if ([c isLeaf])
2751 {
2752 return @"";
2753 }
2754 else
2755 {
2756 NSString *value = [c stringValue];
2757
2758 if (value != nil)
2759 {
2760 return value;
2761 }
2762 else
2763 {
2764 return @"";
2765 }
2766 }
2767 }
2768 return @"";
2769 }
2770
2771 /* Marks all titles as needing to be redrawn. */
2772 - (void) _setColumnTitlesNeedDisplay
2773 {
2774 if (_isTitled)
2775 {
2776 NSRect r = [self titleFrameOfColumn: _firstVisibleColumn];
2777
2778 r.size.width = _frame.size.width;
2779 [self setNeedsDisplayInRect: r];
2780 }
2781 }
2782
2783 @end

savannah-hackers-public@gnu.org
ViewVC Help
Powered by ViewVC 1.1.26