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 && (unsigned)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 |
int 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 |
/** Override from NSControl. Don't do anything to change the size of the |
1714 |
browser. */ |
1715 |
- (void) sizeToFit |
1716 |
{ |
1717 |
} |
1718 |
|
1719 |
/* |
1720 |
* Setting the delegate |
1721 |
*/ |
1722 |
|
1723 |
/** Returns the NSBrowser's delegate. */ |
1724 |
- (id) delegate |
1725 |
{ |
1726 |
return _browserDelegate; |
1727 |
} |
1728 |
|
1729 |
/** Sets the NSBrowser's delegate to anObject. Raises |
1730 |
NSBrowserIllegalDelegateException if the delegate specified by |
1731 |
anObject doesn't respond to browser:willDisplayCell:atRow:column: (if |
1732 |
passive) and either of the methods browser:numberOfRowsInColumn: or |
1733 |
browser:createRowsForColumn:inMatrix:. */ |
1734 |
- (void) setDelegate: (id)anObject |
1735 |
{ |
1736 |
BOOL flag = NO; |
1737 |
|
1738 |
if ([anObject respondsToSelector: |
1739 |
@selector(browser:numberOfRowsInColumn:)]) |
1740 |
{ |
1741 |
_passiveDelegate = YES; |
1742 |
flag = YES; |
1743 |
if (![anObject respondsToSelector: |
1744 |
@selector(browser:willDisplayCell:atRow:column:)]) |
1745 |
[NSException raise: NSBrowserIllegalDelegateException |
1746 |
format: @"(Passive) Delegate does not respond to %s\n", |
1747 |
"browser: willDisplayCell: atRow: column: "]; |
1748 |
} |
1749 |
|
1750 |
if ([anObject respondsToSelector: |
1751 |
@selector(browser:createRowsForColumn:inMatrix:)]) |
1752 |
{ |
1753 |
_passiveDelegate = NO; |
1754 |
|
1755 |
// If flag is already set |
1756 |
// then delegate must respond to both methods |
1757 |
if (flag) |
1758 |
{ |
1759 |
[NSException raise: NSBrowserIllegalDelegateException |
1760 |
format: @"Delegate responds to both %s and %s\n", |
1761 |
"browser: numberOfRowsInColumn: ", |
1762 |
"browser: createRowsForColumn: inMatrix: "]; |
1763 |
} |
1764 |
|
1765 |
flag = YES; |
1766 |
} |
1767 |
|
1768 |
if (!flag) |
1769 |
[NSException raise: NSBrowserIllegalDelegateException |
1770 |
format: @"Delegate does not respond to %s or %s\n", |
1771 |
"browser: numberOfRowsInColumn: ", |
1772 |
"browser: createRowsForColumn: inMatrix: "]; |
1773 |
|
1774 |
_browserDelegate = anObject; |
1775 |
} |
1776 |
|
1777 |
|
1778 |
/* |
1779 |
* Target and action |
1780 |
*/ |
1781 |
|
1782 |
/** Returns the NSBrowser's double-click action method. */ |
1783 |
- (SEL) doubleAction |
1784 |
{ |
1785 |
return _doubleAction; |
1786 |
} |
1787 |
|
1788 |
/** Sets the NSBrowser's double-click action to aSelector. */ |
1789 |
- (void) setDoubleAction: (SEL)aSelector |
1790 |
{ |
1791 |
_doubleAction = aSelector; |
1792 |
} |
1793 |
|
1794 |
/** Sends the action message to the target. Returns YES upon success, |
1795 |
NO if no target for the message could be found. */ |
1796 |
- (BOOL) sendAction |
1797 |
{ |
1798 |
return [self sendAction: [self action] to: [self target]]; |
1799 |
} |
1800 |
|
1801 |
|
1802 |
/* |
1803 |
* Event handling |
1804 |
*/ |
1805 |
|
1806 |
/** Responds to (single) mouse clicks in a column of the NSBrowser. */ |
1807 |
- (void) doClick: (id)sender |
1808 |
{ |
1809 |
NSArray *a; |
1810 |
NSMutableArray *selectedCells; |
1811 |
NSEnumerator *enumerator; |
1812 |
NSBrowserCell *cell; |
1813 |
int column, aCount, selectedCellsCount; |
1814 |
|
1815 |
if ([sender class] != _browserMatrixClass) |
1816 |
return; |
1817 |
|
1818 |
column = [self columnOfMatrix: sender]; |
1819 |
// If the matrix isn't ours then just return |
1820 |
if (column < 0 || column > _lastColumnLoaded) |
1821 |
return; |
1822 |
|
1823 |
a = [sender selectedCells]; |
1824 |
aCount = [a count]; |
1825 |
if(aCount == 0) |
1826 |
return; |
1827 |
|
1828 |
selectedCells = [a mutableCopy]; |
1829 |
|
1830 |
enumerator = [a objectEnumerator]; |
1831 |
while ((cell = [enumerator nextObject])) |
1832 |
{ |
1833 |
if (_allowsBranchSelection == NO && [cell isLeaf] == NO) |
1834 |
{ |
1835 |
[selectedCells removeObject: cell]; |
1836 |
} |
1837 |
} |
1838 |
|
1839 |
if ([selectedCells count] == 0 && [sender selectedCell] != nil) |
1840 |
[selectedCells addObject: [sender selectedCell]]; |
1841 |
|
1842 |
selectedCellsCount = [selectedCells count]; |
1843 |
|
1844 |
if (selectedCellsCount == 0) |
1845 |
{ |
1846 |
// If we should not select the cell then deselect it |
1847 |
|
1848 |
[sender deselectAllCells]; |
1849 |
} |
1850 |
else if (selectedCellsCount < aCount) |
1851 |
{ |
1852 |
[sender deselectSelectedCell]; |
1853 |
|
1854 |
enumerator = [selectedCells objectEnumerator]; |
1855 |
while ((cell = [enumerator nextObject])) |
1856 |
[sender selectCell: cell]; |
1857 |
} |
1858 |
|
1859 |
[self setLastColumn: column]; |
1860 |
// Single selection |
1861 |
if (selectedCellsCount == 1) |
1862 |
{ |
1863 |
cell = [selectedCells objectAtIndex: 0]; |
1864 |
|
1865 |
// If the cell is not a leaf we need to load a column |
1866 |
if (![cell isLeaf]) |
1867 |
{ |
1868 |
[self addColumn]; |
1869 |
} |
1870 |
|
1871 |
[sender scrollCellToVisibleAtRow: [sender selectedRow] column: 0]; |
1872 |
} |
1873 |
|
1874 |
// Send the action to target |
1875 |
[self sendAction]; |
1876 |
|
1877 |
RELEASE(selectedCells); |
1878 |
} |
1879 |
|
1880 |
/** Responds to double-clicks in a column of the NSBrowser. */ |
1881 |
- (void) doDoubleClick: (id)sender |
1882 |
{ |
1883 |
// We have already handled the single click |
1884 |
// so send the double action |
1885 |
|
1886 |
[self sendAction: _doubleAction to: [self target]]; |
1887 |
} |
1888 |
|
1889 |
+ (void) initialize |
1890 |
{ |
1891 |
if (self == [NSBrowser class]) |
1892 |
{ |
1893 |
// Initial version |
1894 |
[self setVersion: 1]; |
1895 |
scrollerWidth = [NSScroller scrollerWidth]; |
1896 |
} |
1897 |
} |
1898 |
|
1899 |
/* |
1900 |
* Override superclass methods |
1901 |
*/ |
1902 |
|
1903 |
/** Setups browser with frame 'rect'. */ |
1904 |
- (id) initWithFrame: (NSRect)rect |
1905 |
{ |
1906 |
NSSize bs; |
1907 |
//NSScroller *hs; |
1908 |
|
1909 |
/* Created the shared titleCell if it hasn't been created already. */ |
1910 |
if (!titleCell) |
1911 |
{ |
1912 |
titleCell = [GSBrowserTitleCell new]; |
1913 |
} |
1914 |
|
1915 |
self = [super initWithFrame: rect]; |
1916 |
|
1917 |
// Class setting |
1918 |
_browserCellPrototype = [[[NSBrowser cellClass] alloc] init]; |
1919 |
_browserMatrixClass = [NSMatrix class]; |
1920 |
|
1921 |
// Default values |
1922 |
_pathSeparator = @"/"; |
1923 |
_allowsBranchSelection = YES; |
1924 |
_allowsEmptySelection = YES; |
1925 |
_allowsMultipleSelection = YES; |
1926 |
_reusesColumns = NO; |
1927 |
_separatesColumns = YES; |
1928 |
_isTitled = YES; |
1929 |
_takesTitleFromPreviousColumn = YES; |
1930 |
_hasHorizontalScroller = YES; |
1931 |
_isLoaded = NO; |
1932 |
_acceptsArrowKeys = YES; |
1933 |
_acceptsAlphaNumericalKeys = YES; |
1934 |
_lastKeyPressed = 0.; |
1935 |
_charBuffer = nil; |
1936 |
_sendsActionOnArrowKeys = YES; |
1937 |
_sendsActionOnAlphaNumericalKeys = YES; |
1938 |
_browserDelegate = nil; |
1939 |
_passiveDelegate = YES; |
1940 |
_doubleAction = NULL; |
1941 |
bs = _sizeForBorderType (NSBezelBorder); |
1942 |
_minColumnWidth = scrollerWidth + (2 * bs.width); |
1943 |
if (_minColumnWidth < 100.0) |
1944 |
_minColumnWidth = 100.0; |
1945 |
|
1946 |
// Horizontal scroller |
1947 |
_scrollerRect.origin.x = bs.width; |
1948 |
_scrollerRect.origin.y = bs.height; |
1949 |
_scrollerRect.size.width = _frame.size.width - (2 * bs.width); |
1950 |
_scrollerRect.size.height = scrollerWidth; |
1951 |
_horizontalScroller = [[NSScroller alloc] initWithFrame: _scrollerRect]; |
1952 |
[_horizontalScroller setTarget: self]; |
1953 |
[_horizontalScroller setAction: @selector(scrollViaScroller:)]; |
1954 |
[self addSubview: _horizontalScroller]; |
1955 |
_skipUpdateScroller = NO; |
1956 |
|
1957 |
// Columns |
1958 |
_browserColumns = [[NSMutableArray alloc] init]; |
1959 |
|
1960 |
// Create a single column |
1961 |
_lastColumnLoaded = -1; |
1962 |
_firstVisibleColumn = 0; |
1963 |
_lastVisibleColumn = 0; |
1964 |
_maxVisibleColumns = 3; |
1965 |
[self _createColumn]; |
1966 |
|
1967 |
return self; |
1968 |
} |
1969 |
|
1970 |
- (void) dealloc |
1971 |
{ |
1972 |
RELEASE(_browserCellPrototype); |
1973 |
RELEASE(_pathSeparator); |
1974 |
RELEASE(_horizontalScroller); |
1975 |
RELEASE(_browserColumns); |
1976 |
TEST_RELEASE(_charBuffer); |
1977 |
|
1978 |
[super dealloc]; |
1979 |
} |
1980 |
|
1981 |
|
1982 |
|
1983 |
/* |
1984 |
* Target-actions |
1985 |
*/ |
1986 |
|
1987 |
/** Set target to 'target' */ |
1988 |
- (void) setTarget: (id)target |
1989 |
{ |
1990 |
_target = target; |
1991 |
} |
1992 |
|
1993 |
/** Return current target. */ |
1994 |
- (id) target |
1995 |
{ |
1996 |
return _target; |
1997 |
} |
1998 |
|
1999 |
/** Set action to 's'. */ |
2000 |
- (void) setAction: (SEL)s |
2001 |
{ |
2002 |
_action = s; |
2003 |
} |
2004 |
|
2005 |
/** Return current action. */ |
2006 |
- (SEL) action |
2007 |
{ |
2008 |
return _action; |
2009 |
} |
2010 |
|
2011 |
|
2012 |
|
2013 |
/* |
2014 |
* Events handling |
2015 |
*/ |
2016 |
|
2017 |
- (void) drawRect: (NSRect)rect |
2018 |
{ |
2019 |
NSRectClip(rect); |
2020 |
[[_window backgroundColor] set]; |
2021 |
NSRectFill(rect); |
2022 |
|
2023 |
// Load the first column if not already done |
2024 |
if (!_isLoaded) |
2025 |
{ |
2026 |
[self loadColumnZero]; |
2027 |
} |
2028 |
|
2029 |
// Draws titles |
2030 |
if (_isTitled) |
2031 |
{ |
2032 |
int i; |
2033 |
|
2034 |
for (i = _firstVisibleColumn; i <= _lastVisibleColumn; ++i) |
2035 |
{ |
2036 |
NSRect titleRect = [self titleFrameOfColumn: i]; |
2037 |
if (NSIntersectsRect (titleRect, rect) == YES) |
2038 |
{ |
2039 |
[self drawTitleOfColumn: i |
2040 |
inRect: titleRect]; |
2041 |
} |
2042 |
} |
2043 |
} |
2044 |
|
2045 |
// Draws scroller border |
2046 |
if (_hasHorizontalScroller) |
2047 |
{ |
2048 |
NSRect scrollerBorderRect = _scrollerRect; |
2049 |
NSSize bs = _sizeForBorderType (NSBezelBorder); |
2050 |
|
2051 |
scrollerBorderRect.origin.x = 0; |
2052 |
scrollerBorderRect.origin.y = 0; |
2053 |
scrollerBorderRect.size.width += 2 * bs.width - 1; |
2054 |
scrollerBorderRect.size.height += (2 * bs.height) - 1; |
2055 |
|
2056 |
if ((NSIntersectsRect (scrollerBorderRect, rect) == YES) && _window) |
2057 |
{ |
2058 |
NSDrawGrayBezel (scrollerBorderRect, rect); |
2059 |
} |
2060 |
} |
2061 |
|
2062 |
if (!_separatesColumns) |
2063 |
{ |
2064 |
NSPoint p1,p2; |
2065 |
NSRect browserRect; |
2066 |
int i, visibleColumns; |
2067 |
|
2068 |
// Columns borders |
2069 |
browserRect = NSMakeRect(0, 0, rect.size.width, rect.size.height); |
2070 |
NSDrawGrayBezel (browserRect, rect); |
2071 |
|
2072 |
[[NSColor blackColor] set]; |
2073 |
visibleColumns = [self numberOfVisibleColumns]; |
2074 |
for (i = 1; i < visibleColumns; i++) |
2075 |
{ |
2076 |
p1 = NSMakePoint((_columnSize.width * i) + 2 + (i-1), |
2077 |
_columnSize.height + scrollerWidth + 2); |
2078 |
p2 = NSMakePoint((_columnSize.width * i) + 2 + (i-1), scrollerWidth + 3); |
2079 |
[NSBezierPath strokeLineFromPoint: p1 toPoint: p2]; |
2080 |
} |
2081 |
|
2082 |
// Horizontal scroller border |
2083 |
p1 = NSMakePoint(2, scrollerWidth + 2); |
2084 |
p2 = NSMakePoint(rect.size.width - 2, scrollerWidth + 2); |
2085 |
[NSBezierPath strokeLineFromPoint: p1 toPoint: p2]; |
2086 |
} |
2087 |
} |
2088 |
|
2089 |
/* Informs the receivers's subviews that the receiver's bounds |
2090 |
rectangle size has changed from oldFrameSize. */ |
2091 |
- (void) resizeSubviewsWithOldSize: (NSSize)oldSize |
2092 |
{ |
2093 |
[self tile]; |
2094 |
} |
2095 |
|
2096 |
|
2097 |
/* Override NSControl handler (prevents highlighting). */ |
2098 |
- (void) mouseDown: (NSEvent *)theEvent |
2099 |
{ |
2100 |
} |
2101 |
|
2102 |
- (void) moveLeft: (id)sender |
2103 |
{ |
2104 |
if (_acceptsArrowKeys) |
2105 |
{ |
2106 |
NSMatrix *matrix; |
2107 |
int selectedColumn; |
2108 |
|
2109 |
matrix = (NSMatrix *)[_window firstResponder]; |
2110 |
selectedColumn = [self columnOfMatrix:matrix]; |
2111 |
if (selectedColumn == -1) |
2112 |
{ |
2113 |
selectedColumn = [self selectedColumn]; |
2114 |
matrix = [self matrixInColumn: selectedColumn]; |
2115 |
} |
2116 |
if (selectedColumn > 0) |
2117 |
{ |
2118 |
[matrix deselectAllCells]; |
2119 |
[matrix scrollCellToVisibleAtRow:0 column:0]; |
2120 |
[self setLastColumn: selectedColumn]; |
2121 |
|
2122 |
selectedColumn--; |
2123 |
[self scrollColumnToVisible: selectedColumn]; |
2124 |
matrix = [self matrixInColumn: selectedColumn]; |
2125 |
[_window makeFirstResponder: matrix]; |
2126 |
|
2127 |
if (_sendsActionOnArrowKeys == YES) |
2128 |
{ |
2129 |
[super sendAction: _action to: _target]; |
2130 |
} |
2131 |
} |
2132 |
} |
2133 |
} |
2134 |
|
2135 |
- (void) moveRight: (id)sender |
2136 |
{ |
2137 |
if (_acceptsArrowKeys) |
2138 |
{ |
2139 |
NSMatrix *matrix; |
2140 |
int selectedColumn; |
2141 |
|
2142 |
matrix = (NSMatrix *)[_window firstResponder]; |
2143 |
selectedColumn = [self columnOfMatrix:matrix]; |
2144 |
if (selectedColumn == -1) |
2145 |
{ |
2146 |
selectedColumn = [self selectedColumn]; |
2147 |
matrix = [self matrixInColumn: selectedColumn]; |
2148 |
} |
2149 |
if (selectedColumn == -1) |
2150 |
{ |
2151 |
selectedColumn = 0; |
2152 |
matrix = [self matrixInColumn: 0]; |
2153 |
|
2154 |
if ([[matrix cells] count]) |
2155 |
{ |
2156 |
[matrix selectCellAtRow: 0 column: 0]; |
2157 |
} |
2158 |
} |
2159 |
else |
2160 |
{ |
2161 |
// if there is one selected cell and it is a leaf, move right |
2162 |
// (column is already loaded) |
2163 |
if (![[matrix selectedCell] isLeaf] |
2164 |
&& [[matrix selectedCells] count] == 1) |
2165 |
{ |
2166 |
selectedColumn++; |
2167 |
matrix = [self matrixInColumn: selectedColumn]; |
2168 |
if ([[matrix cells] count] && [matrix selectedCell] == nil) |
2169 |
{ |
2170 |
[matrix selectCellAtRow: 0 column: 0]; |
2171 |
} |
2172 |
// if selected cell is a leaf, we need to add a column |
2173 |
if (![[matrix selectedCell] isLeaf] |
2174 |
&& [[matrix selectedCells] count] == 1) |
2175 |
{ |
2176 |
[self addColumn]; |
2177 |
} |
2178 |
} |
2179 |
} |
2180 |
|
2181 |
[_window makeFirstResponder: matrix]; |
2182 |
|
2183 |
if (_sendsActionOnArrowKeys == YES) |
2184 |
{ |
2185 |
[super sendAction: _action to: _target]; |
2186 |
} |
2187 |
} |
2188 |
} |
2189 |
|
2190 |
- (void) keyDown: (NSEvent *)theEvent |
2191 |
{ |
2192 |
NSString *characters = [theEvent characters]; |
2193 |
unichar character = 0; |
2194 |
|
2195 |
if ([characters length] > 0) |
2196 |
{ |
2197 |
character = [characters characterAtIndex: 0]; |
2198 |
} |
2199 |
|
2200 |
if (_acceptsArrowKeys) |
2201 |
{ |
2202 |
switch (character) |
2203 |
{ |
2204 |
case NSUpArrowFunctionKey: |
2205 |
case NSDownArrowFunctionKey: |
2206 |
return; |
2207 |
case NSLeftArrowFunctionKey: |
2208 |
[self moveLeft:self]; |
2209 |
return; |
2210 |
case NSRightArrowFunctionKey: |
2211 |
[self moveRight:self]; |
2212 |
return; |
2213 |
case NSTabCharacter: |
2214 |
{ |
2215 |
if ([theEvent modifierFlags] & NSShiftKeyMask) |
2216 |
{ |
2217 |
[_window selectKeyViewPrecedingView: self]; |
2218 |
} |
2219 |
else |
2220 |
{ |
2221 |
[_window selectKeyViewFollowingView: self]; |
2222 |
} |
2223 |
} |
2224 |
return; |
2225 |
break; |
2226 |
} |
2227 |
} |
2228 |
|
2229 |
if (_acceptsAlphaNumericalKeys && (character < 0xF700) |
2230 |
&& ([characters length] > 0)) |
2231 |
{ |
2232 |
NSMatrix *matrix; |
2233 |
NSString *sv; |
2234 |
int i, n, s; |
2235 |
int selectedColumn; |
2236 |
SEL lcarcSel = @selector(loadedCellAtRow:column:); |
2237 |
IMP lcarc = [self methodForSelector: lcarcSel]; |
2238 |
|
2239 |
selectedColumn = [self selectedColumn]; |
2240 |
if(selectedColumn != -1) |
2241 |
{ |
2242 |
matrix = [self matrixInColumn: selectedColumn]; |
2243 |
n = [matrix numberOfRows]; |
2244 |
s = [matrix selectedRow]; |
2245 |
|
2246 |
if (!_charBuffer) |
2247 |
{ |
2248 |
_charBuffer = [characters substringToIndex: 1]; |
2249 |
RETAIN(_charBuffer); |
2250 |
} |
2251 |
else |
2252 |
{ |
2253 |
if (([theEvent timestamp] - _lastKeyPressed < 2000.0) |
2254 |
&& (_alphaNumericalLastColumn == selectedColumn)) |
2255 |
{ |
2256 |
NSString *transition; |
2257 |
transition = [_charBuffer |
2258 |
stringByAppendingString: |
2259 |
[characters substringToIndex: 1]]; |
2260 |
RELEASE(_charBuffer); |
2261 |
_charBuffer = transition; |
2262 |
RETAIN(_charBuffer); |
2263 |
} |
2264 |
else |
2265 |
{ |
2266 |
RELEASE(_charBuffer); |
2267 |
_charBuffer = [characters substringToIndex: 1]; |
2268 |
RETAIN(_charBuffer); |
2269 |
} |
2270 |
} |
2271 |
|
2272 |
_alphaNumericalLastColumn = selectedColumn; |
2273 |
_lastKeyPressed = [theEvent timestamp]; |
2274 |
|
2275 |
sv = [((*lcarc)(self, lcarcSel, s, selectedColumn)) |
2276 |
stringValue]; |
2277 |
|
2278 |
if (([sv length] > 0) |
2279 |
&& ([sv hasPrefix: _charBuffer])) |
2280 |
return; |
2281 |
|
2282 |
for (i = s+1; i < n; i++) |
2283 |
{ |
2284 |
sv = [((*lcarc)(self, lcarcSel, i, selectedColumn)) |
2285 |
stringValue]; |
2286 |
if (([sv length] > 0) |
2287 |
&& ([sv hasPrefix: _charBuffer])) |
2288 |
{ |
2289 |
[self selectRow: i |
2290 |
inColumn: selectedColumn]; |
2291 |
[matrix scrollCellToVisibleAtRow: i column: 0]; |
2292 |
[matrix performClick: self]; |
2293 |
return; |
2294 |
} |
2295 |
} |
2296 |
for (i = 0; i < s; i++) |
2297 |
{ |
2298 |
sv = [((*lcarc)(self, lcarcSel, i, selectedColumn)) |
2299 |
stringValue]; |
2300 |
if (([sv length] > 0) |
2301 |
&& ([sv hasPrefix: _charBuffer])) |
2302 |
{ |
2303 |
[self selectRow: i |
2304 |
inColumn: selectedColumn]; |
2305 |
[matrix scrollCellToVisibleAtRow: i column: 0]; |
2306 |
[matrix performClick: self]; |
2307 |
return; |
2308 |
} |
2309 |
} |
2310 |
} |
2311 |
_lastKeyPressed = 0.; |
2312 |
} |
2313 |
|
2314 |
[super keyDown: theEvent]; |
2315 |
} |
2316 |
|
2317 |
/* |
2318 |
* NSCoding protocol |
2319 |
* |
2320 |
* We do not encode most of the instance variables except the Browser columns |
2321 |
* because they are internal objects (though not transportable). So we just |
2322 |
* encode enoguh information to rebuild identical columns on the decoder |
2323 |
* side. Same for the Horizontal Scroller |
2324 |
*/ |
2325 |
|
2326 |
- (void) encodeWithCoder: (NSCoder*)aCoder |
2327 |
{ |
2328 |
[super encodeWithCoder: aCoder]; |
2329 |
|
2330 |
// Here to keep compatibility with old version |
2331 |
[aCoder encodeObject: nil]; |
2332 |
[aCoder encodeObject:_browserCellPrototype]; |
2333 |
[aCoder encodeObject: NSStringFromClass (_browserMatrixClass)]; |
2334 |
|
2335 |
[aCoder encodeObject:_pathSeparator]; |
2336 |
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &_isLoaded]; |
2337 |
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &_allowsBranchSelection]; |
2338 |
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &_allowsEmptySelection]; |
2339 |
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &_allowsMultipleSelection]; |
2340 |
[aCoder encodeValueOfObjCType: @encode(int) at: &_maxVisibleColumns]; |
2341 |
[aCoder encodeValueOfObjCType: @encode(float) at: &_minColumnWidth]; |
2342 |
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &_reusesColumns]; |
2343 |
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &_separatesColumns]; |
2344 |
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &_takesTitleFromPreviousColumn]; |
2345 |
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &_isTitled]; |
2346 |
|
2347 |
|
2348 |
[aCoder encodeObject:_horizontalScroller]; |
2349 |
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &_hasHorizontalScroller]; |
2350 |
[aCoder encodeRect: _scrollerRect]; |
2351 |
[aCoder encodeSize: _columnSize]; |
2352 |
|
2353 |
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &_acceptsArrowKeys]; |
2354 |
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &_sendsActionOnArrowKeys]; |
2355 |
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &_acceptsAlphaNumericalKeys]; |
2356 |
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &_sendsActionOnAlphaNumericalKeys]; |
2357 |
|
2358 |
[aCoder encodeConditionalObject:_browserDelegate]; |
2359 |
|
2360 |
[aCoder encodeValueOfObjCType: @encode(SEL) at: &_doubleAction]; |
2361 |
[aCoder encodeConditionalObject: _target]; |
2362 |
[aCoder encodeValueOfObjCType: @encode(SEL) at: &_action]; |
2363 |
|
2364 |
[aCoder encodeObject: _browserColumns]; |
2365 |
|
2366 |
// Just encode the number of columns and the first visible |
2367 |
// and rebuild the browser columns on the decoding side |
2368 |
{ |
2369 |
int colCount = [_browserColumns count]; |
2370 |
[aCoder encodeValueOfObjCType: @encode(int) at: &colCount]; |
2371 |
[aCoder encodeValueOfObjCType: @encode(int) at: &_firstVisibleColumn]; |
2372 |
} |
2373 |
|
2374 |
} |
2375 |
|
2376 |
- (id) initWithCoder: (NSCoder*)aDecoder |
2377 |
{ |
2378 |
int colCount; |
2379 |
id dummy; |
2380 |
|
2381 |
[super initWithCoder: aDecoder]; |
2382 |
// Here to keep compatibility with old version |
2383 |
dummy = [aDecoder decodeObject]; |
2384 |
_browserCellPrototype = RETAIN([aDecoder decodeObject]); |
2385 |
_browserMatrixClass = NSClassFromString ((NSString *)[aDecoder decodeObject]); |
2386 |
|
2387 |
[self setPathSeparator: [aDecoder decodeObject]]; |
2388 |
|
2389 |
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_isLoaded]; |
2390 |
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_allowsBranchSelection]; |
2391 |
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_allowsEmptySelection]; |
2392 |
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_allowsMultipleSelection]; |
2393 |
[aDecoder decodeValueOfObjCType: @encode(int) at: &_maxVisibleColumns]; |
2394 |
[aDecoder decodeValueOfObjCType: @encode(float) at: &_minColumnWidth]; |
2395 |
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_reusesColumns]; |
2396 |
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_separatesColumns]; |
2397 |
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_takesTitleFromPreviousColumn]; |
2398 |
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_isTitled]; |
2399 |
|
2400 |
//NSBox *_horizontalScrollerBox; |
2401 |
_horizontalScroller = RETAIN([aDecoder decodeObject]); |
2402 |
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_hasHorizontalScroller]; |
2403 |
_scrollerRect = [aDecoder decodeRect]; |
2404 |
_columnSize = [aDecoder decodeSize]; |
2405 |
|
2406 |
_skipUpdateScroller = NO; |
2407 |
/* |
2408 |
_horizontalScroller = [[NSScroller alloc] initWithFrame: _scrollerRect]; |
2409 |
[_horizontalScroller setTarget: self]; |
2410 |
[_horizontalScroller setAction: @selector(scrollViaScroller:)]; |
2411 |
*/ |
2412 |
[self setHasHorizontalScroller: _hasHorizontalScroller]; |
2413 |
|
2414 |
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_acceptsArrowKeys]; |
2415 |
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_sendsActionOnArrowKeys]; |
2416 |
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_acceptsAlphaNumericalKeys]; |
2417 |
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_sendsActionOnAlphaNumericalKeys]; |
2418 |
_lastKeyPressed = 0; |
2419 |
_charBuffer = nil; |
2420 |
// Skip: int _alphaNumericalLastColumn; |
2421 |
|
2422 |
_browserDelegate = [aDecoder decodeObject]; |
2423 |
if (_browserDelegate != nil) |
2424 |
[self setDelegate:_browserDelegate]; |
2425 |
else |
2426 |
_passiveDelegate = YES; |
2427 |
|
2428 |
|
2429 |
[aDecoder decodeValueOfObjCType: @encode(SEL) at: &_doubleAction]; |
2430 |
_target = [aDecoder decodeObject]; |
2431 |
[aDecoder decodeValueOfObjCType: @encode(SEL) at: &_action]; |
2432 |
|
2433 |
|
2434 |
// Do the minimal thing to initiate the browser... |
2435 |
/* |
2436 |
_lastColumnLoaded = -1; |
2437 |
_firstVisibleColumn = 0; |
2438 |
_lastVisibleColumn = 0; |
2439 |
[self _createColumn]; |
2440 |
*/ |
2441 |
_browserColumns = RETAIN([aDecoder decodeObject]); |
2442 |
// ..and rebuild any existing browser columns |
2443 |
[aDecoder decodeValueOfObjCType: @encode(int) at: &colCount]; |
2444 |
[aDecoder decodeValueOfObjCType: @encode(int) at: &_firstVisibleColumn]; |
2445 |
|
2446 |
// Display even if there isn't any column |
2447 |
_isLoaded = NO; |
2448 |
[self tile]; |
2449 |
return self; |
2450 |
} |
2451 |
|
2452 |
|
2453 |
|
2454 |
/* |
2455 |
* Div. |
2456 |
*/ |
2457 |
|
2458 |
- (BOOL) isOpaque |
2459 |
{ |
2460 |
return YES; // See drawRect. |
2461 |
} |
2462 |
|
2463 |
@end |
2464 |
|
2465 |
@implementation NSBrowser (GNUstepExtensions) |
2466 |
/* |
2467 |
* Setting the behavior of arrow keys |
2468 |
*/ |
2469 |
|
2470 |
/** Returns YES if the alphanumerical keys are enabled. */ |
2471 |
- (BOOL) acceptsAlphaNumericalKeys |
2472 |
{ |
2473 |
return _acceptsAlphaNumericalKeys; |
2474 |
} |
2475 |
|
2476 |
/** Enables or disables the arrow keys as used for navigating within |
2477 |
and between browsers. */ |
2478 |
- (void) setAcceptsAlphaNumericalKeys: (BOOL)flag |
2479 |
{ |
2480 |
_acceptsAlphaNumericalKeys = flag; |
2481 |
} |
2482 |
|
2483 |
/** Returns NO if pressing an arrow key only scrolls the browser, YES if |
2484 |
it also sends the action message specified by setAction:. */ |
2485 |
- (BOOL) sendsActionOnAlphaNumericalKeys |
2486 |
{ |
2487 |
return _sendsActionOnAlphaNumericalKeys; |
2488 |
} |
2489 |
|
2490 |
/** Sets whether pressing an arrow key will cause the action message |
2491 |
to be sent (in addition to causing scrolling). */ |
2492 |
- (void) setSendsActionOnAlphaNumericalKeys: (BOOL)flag |
2493 |
{ |
2494 |
_sendsActionOnAlphaNumericalKeys = flag; |
2495 |
} |
2496 |
|
2497 |
@end |
2498 |
|
2499 |
|
2500 |
/* |
2501 |
* |
2502 |
* PRIVATE METHODS |
2503 |
* |
2504 |
*/ |
2505 |
@implementation NSBrowser (Private) |
2506 |
|
2507 |
- (void) _remapColumnSubviews: (BOOL)fromFirst |
2508 |
{ |
2509 |
id bc, sc; |
2510 |
int i, count; |
2511 |
id firstResponder = nil; |
2512 |
BOOL setFirstResponder = NO; |
2513 |
|
2514 |
// Removes all column subviews. |
2515 |
count = [_browserColumns count]; |
2516 |
for (i = 0; i < count; i++) |
2517 |
{ |
2518 |
bc = [_browserColumns objectAtIndex: i]; |
2519 |
sc = [bc columnScrollView]; |
2520 |
|
2521 |
if (!firstResponder && [bc columnMatrix] == [_window firstResponder]) |
2522 |
{ |
2523 |
firstResponder = [bc columnMatrix]; |
2524 |
} |
2525 |
if (sc) |
2526 |
{ |
2527 |
[sc removeFromSuperviewWithoutNeedingDisplay]; |
2528 |
} |
2529 |
} |
2530 |
|
2531 |
if (_firstVisibleColumn > _lastVisibleColumn) |
2532 |
return; |
2533 |
|
2534 |
// Sets columns subviews order according to fromFirst (display order...). |
2535 |
// All added subviews are automaticaly marked as needing display (-> |
2536 |
// NSView). |
2537 |
if (fromFirst) |
2538 |
{ |
2539 |
for (i = _firstVisibleColumn; i <= _lastVisibleColumn; i++) |
2540 |
{ |
2541 |
bc = [_browserColumns objectAtIndex: i]; |
2542 |
sc = [bc columnScrollView]; |
2543 |
[self addSubview: sc]; |
2544 |
|
2545 |
if ([bc columnMatrix] == firstResponder) |
2546 |
{ |
2547 |
[_window makeFirstResponder: firstResponder]; |
2548 |
setFirstResponder = YES; |
2549 |
} |
2550 |
} |
2551 |
|
2552 |
if (firstResponder && setFirstResponder == NO) |
2553 |
{ |
2554 |
[_window makeFirstResponder: |
2555 |
[[_browserColumns objectAtIndex: _firstVisibleColumn] |
2556 |
columnMatrix]]; |
2557 |
} |
2558 |
} |
2559 |
else |
2560 |
{ |
2561 |
for (i = _lastVisibleColumn; i >= _firstVisibleColumn; i--) |
2562 |
{ |
2563 |
bc = [_browserColumns objectAtIndex: i]; |
2564 |
sc = [bc columnScrollView]; |
2565 |
[self addSubview: sc]; |
2566 |
|
2567 |
if ([bc columnMatrix] == firstResponder) |
2568 |
{ |
2569 |
[_window makeFirstResponder: firstResponder]; |
2570 |
setFirstResponder = YES; |
2571 |
} |
2572 |
} |
2573 |
|
2574 |
if (firstResponder && setFirstResponder == NO) |
2575 |
{ |
2576 |
[_window makeFirstResponder: |
2577 |
[[_browserColumns objectAtIndex: _lastVisibleColumn] |
2578 |
columnMatrix]]; |
2579 |
} |
2580 |
} |
2581 |
} |
2582 |
|
2583 |
/* Loads column 'column' (asking the delegate). */ |
2584 |
- (void) _performLoadOfColumn: (int)column |
2585 |
{ |
2586 |
id bc, sc, matrix; |
2587 |
int i, rows, cols; |
2588 |
|
2589 |
if (_passiveDelegate) |
2590 |
{ |
2591 |
// Ask the delegate for the number of rows |
2592 |
rows = [_browserDelegate browser: self numberOfRowsInColumn: column]; |
2593 |
cols = 1; |
2594 |
} |
2595 |
else |
2596 |
{ |
2597 |
rows = 0; |
2598 |
cols = 0; |
2599 |
} |
2600 |
|
2601 |
bc = [_browserColumns objectAtIndex: column]; |
2602 |
|
2603 |
if (!(sc = [bc columnScrollView])) |
2604 |
return; |
2605 |
|
2606 |
matrix = [bc columnMatrix]; |
2607 |
|
2608 |
if (_reusesColumns && matrix) |
2609 |
{ |
2610 |
[matrix renewRows: rows columns: cols]; |
2611 |
|
2612 |
// Mark all the cells as unloaded |
2613 |
for (i = 0; i < rows; i++) |
2614 |
{ |
2615 |
[[matrix cellAtRow: i column: 0] setLoaded: NO]; |
2616 |
} |
2617 |
} |
2618 |
else |
2619 |
{ |
2620 |
NSRect matrixRect = {{0, 0}, {100, 100}}; |
2621 |
NSSize matrixIntercellSpace = {0, 0}; |
2622 |
|
2623 |
// create a new col matrix |
2624 |
matrix = [[_browserMatrixClass alloc] |
2625 |
initWithFrame: matrixRect |
2626 |
mode: NSListModeMatrix |
2627 |
prototype: _browserCellPrototype |
2628 |
numberOfRows: rows |
2629 |
numberOfColumns: cols]; |
2630 |
[matrix setIntercellSpacing: matrixIntercellSpace]; |
2631 |
[matrix setAllowsEmptySelection: _allowsEmptySelection]; |
2632 |
[matrix setAutoscroll: YES]; |
2633 |
if (!_allowsMultipleSelection) |
2634 |
{ |
2635 |
[matrix setMode: NSRadioModeMatrix]; |
2636 |
} |
2637 |
[matrix setTarget: self]; |
2638 |
[matrix setAction: @selector(doClick:)]; |
2639 |
[matrix setDoubleAction: @selector(doDoubleClick:)]; |
2640 |
|
2641 |
// set new col matrix and release old |
2642 |
[bc setColumnMatrix: matrix]; |
2643 |
RELEASE (matrix); |
2644 |
} |
2645 |
[sc setDocumentView: matrix]; |
2646 |
|
2647 |
// Loading is different based upon passive/active delegate |
2648 |
if (_passiveDelegate) |
2649 |
{ |
2650 |
// Now loop through the cells and load each one |
2651 |
id aCell; |
2652 |
SEL sel1 = @selector(browser:willDisplayCell:atRow:column:); |
2653 |
IMP imp1 = [_browserDelegate methodForSelector: sel1]; |
2654 |
SEL sel2 = @selector(cellAtRow:column:); |
2655 |
IMP imp2 = [matrix methodForSelector: sel2]; |
2656 |
|
2657 |
for (i = 0; i < rows; i++) |
2658 |
{ |
2659 |
aCell = (*imp2)(matrix, sel2, i, 0); |
2660 |
if (![aCell isLoaded]) |
2661 |
{ |
2662 |
(*imp1)(_browserDelegate, sel1, self, aCell, i, |
2663 |
column); |
2664 |
[aCell setLoaded: YES]; |
2665 |
} |
2666 |
} |
2667 |
} |
2668 |
else |
2669 |
{ |
2670 |
// Tell the delegate to create the rows |
2671 |
[_browserDelegate browser: self |
2672 |
createRowsForColumn: column |
2673 |
inMatrix: matrix]; |
2674 |
} |
2675 |
|
2676 |
[sc setNeedsDisplay: YES]; |
2677 |
[bc setIsLoaded: YES]; |
2678 |
|
2679 |
if (column > _lastColumnLoaded) |
2680 |
{ |
2681 |
_lastColumnLoaded = column; |
2682 |
} |
2683 |
|
2684 |
/* Determine the height of a cell in the matrix, and set that as the |
2685 |
cellSize of the matrix. */ |
2686 |
{ |
2687 |
NSSize cs, ms; |
2688 |
NSBrowserCell *b = [matrix cellAtRow: 0 column: 0]; |
2689 |
|
2690 |
if (b != nil) |
2691 |
{ |
2692 |
ms = [b cellSize]; |
2693 |
} |
2694 |
else |
2695 |
{ |
2696 |
ms = [matrix cellSize]; |
2697 |
} |
2698 |
cs = [sc contentSize]; |
2699 |
ms.width = cs.width; |
2700 |
[matrix setCellSize: ms]; |
2701 |
} |
2702 |
|
2703 |
// Get the title even when untitled, as this may change later. |
2704 |
[self setTitle: [self _getTitleOfColumn: column] ofColumn: column]; |
2705 |
} |
2706 |
|
2707 |
/* Get the title of a column. */ |
2708 |
- (NSString *) _getTitleOfColumn: (int)column |
2709 |
{ |
2710 |
// Ask the delegate for the column title |
2711 |
if ([_browserDelegate respondsToSelector: |
2712 |
@selector(browser:titleOfColumn:)]) |
2713 |
{ |
2714 |
return [_browserDelegate browser: self titleOfColumn: column]; |
2715 |
} |
2716 |
|
2717 |
|
2718 |
// Check if we take title from previous column |
2719 |
if (_takesTitleFromPreviousColumn) |
2720 |
{ |
2721 |
id c; |
2722 |
|
2723 |
// If first column then use the path separator |
2724 |
if (column == 0) |
2725 |
{ |
2726 |
return _pathSeparator; |
2727 |
} |
2728 |
|
2729 |
// Get the selected cell |
2730 |
// Use its string value as the title |
2731 |
// Only if it is not a leaf |
2732 |
if(_allowsMultipleSelection == NO) |
2733 |
{ |
2734 |
c = [self selectedCellInColumn: column - 1]; |
2735 |
} |
2736 |
else |
2737 |
{ |
2738 |
NSMatrix *matrix; |
2739 |
NSArray *selectedCells; |
2740 |
|
2741 |
if (!(matrix = [self matrixInColumn: column - 1])) |
2742 |
return @""; |
2743 |
|
2744 |
selectedCells = [matrix selectedCells]; |
2745 |
|
2746 |
if([selectedCells count] == 1) |
2747 |
{ |
2748 |
c = [selectedCells objectAtIndex:0]; |
2749 |
} |
2750 |
else |
2751 |
{ |
2752 |
return @""; |
2753 |
} |
2754 |
} |
2755 |
|
2756 |
if ([c isLeaf]) |
2757 |
{ |
2758 |
return @""; |
2759 |
} |
2760 |
else |
2761 |
{ |
2762 |
NSString *value = [c stringValue]; |
2763 |
|
2764 |
if (value != nil) |
2765 |
{ |
2766 |
return value; |
2767 |
} |
2768 |
else |
2769 |
{ |
2770 |
return @""; |
2771 |
} |
2772 |
} |
2773 |
} |
2774 |
return @""; |
2775 |
} |
2776 |
|
2777 |
/* Marks all titles as needing to be redrawn. */ |
2778 |
- (void) _setColumnTitlesNeedDisplay |
2779 |
{ |
2780 |
if (_isTitled) |
2781 |
{ |
2782 |
NSRect r = [self titleFrameOfColumn: _firstVisibleColumn]; |
2783 |
|
2784 |
r.size.width = _frame.size.width; |
2785 |
[self setNeedsDisplayInRect: r]; |
2786 |
} |
2787 |
} |
2788 |
|
2789 |
@end |