2 // "$Id: Fl_Browser_.cxx 7903 2010-11-28 21:06:39Z matt $"
4 // Base Browser widget class for the Fast Light Tool Kit (FLTK).
6 // Copyright 1998-2010 by Bill Spitzak and others.
8 // This library is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU Library General Public
10 // License as published by the Free Software Foundation; either
11 // version 2 of the License, or (at your option) any later version.
13 // This library is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 // Library General Public License for more details.
18 // You should have received a copy of the GNU Library General Public
19 // License along with this library; if not, write to the Free Software
20 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
23 // Please report all bugs and problems on the following page:
25 // http://www.fltk.org/str.php
28 #define DISPLAY_SEARCH_BOTH_WAYS_AT_ONCE
32 #include <FL/Fl_Widget.H>
33 #include <FL/Fl_Browser_.H>
34 #include <FL/fl_draw.H>
37 // This is the base class for browsers. To be useful it must be
38 // subclassed and several virtual functions defined. The
39 // Forms-compatible browser and the file chooser's browser are
40 // subclassed off of this.
42 // Yes, I know this should be a template...
44 // This has been designed so that the subclass has complete control
45 // over the storage of the data, although because next() and prev()
46 // functions are used to index, it works best as a linked list or as a
47 // large block of characters in which the line breaks must be searched
50 // A great deal of work has been done so that the "height" of a data
51 // object does not need to be determined until it is drawn. This was
52 // done for the file chooser, because the height requires doing stat()
53 // to see if the file is a directory, which can be annoyingly slow
57 1 = redraw children (the scrollbar)
58 2 = redraw one or two items
62 static void scrollbar_callback(Fl_Widget
* s
, void*) {
63 ((Fl_Browser_
*)(s
->parent()))->position(int(((Fl_Scrollbar
*)s
)->value()));
66 static void hscrollbar_callback(Fl_Widget
* s
, void*) {
67 ((Fl_Browser_
*)(s
->parent()))->hposition(int(((Fl_Scrollbar
*)s
)->value()));
70 // return where to draw the actual box:
72 Returns the bounding box for the interior of the list's display window, inside
74 \param[out] X,Y,W,H The returned bounding box.\n
75 (The original contents of these parameters are overwritten)
77 void Fl_Browser_::bbox(int& X
, int& Y
, int& W
, int& H
) const {
78 int scrollsize
= scrollbar_size_
? scrollbar_size_
: Fl::scrollbar_size();
79 Fl_Boxtype b
= box() ? box() : FL_DOWN_BOX
;
80 X
= x()+Fl::box_dx(b
);
81 Y
= y()+Fl::box_dy(b
);
82 W
= w()-Fl::box_dw(b
);
83 H
= h()-Fl::box_dh(b
);
84 if (scrollbar
.visible()) {
86 if (scrollbar
.align() & FL_ALIGN_LEFT
) X
+= scrollsize
;
89 if (hscrollbar
.visible()) {
91 if (scrollbar
.align() & FL_ALIGN_TOP
) Y
+= scrollsize
;
97 This method returns the X position of the left edge of the list area
98 after adjusting for the scrollbar and border, if any.
99 \returns The X position of the left edge of the list, in pixels.
100 \see Fl_Browser_::bbox()
102 int Fl_Browser_::leftedge() const {
103 int X
, Y
, W
, H
; bbox(X
, Y
, W
, H
);
107 // The scrollbars may be moved again by draw(), since each one's size
108 // depends on whether the other is visible or not. This skips over
109 // Fl_Group::resize since it moves the scrollbars uselessly.
111 Repositions and/or resizes the browser.
112 \param[in] X,Y,W,H The new position and size for the browser, in pixels.
114 void Fl_Browser_::resize(int X
, int Y
, int W
, int H
) {
115 int scrollsize
= scrollbar_size_
? scrollbar_size_
: Fl::scrollbar_size();
116 Fl_Widget::resize(X
, Y
, W
, H
);
117 // move the scrollbars so they can respond to events:
120 scrollbar
.align()&FL_ALIGN_LEFT
? X
-scrollsize
: X
+W
,
123 X
, scrollbar
.align()&FL_ALIGN_TOP
? Y
-scrollsize
: Y
+H
,
127 // Cause minimal update to redraw the given item:
129 This method should be called when the contents of \p item has changed,
131 \param[in] item The item that needs to be redrawn.
132 \see redraw_lines(), redraw_line()
134 void Fl_Browser_::redraw_line(void* item
) {
135 if (!redraw1
|| redraw1
== item
) {redraw1
= item
; damage(FL_DAMAGE_EXPOSE
);}
136 else if (!redraw2
|| redraw2
== item
) {redraw2
= item
; damage(FL_DAMAGE_EXPOSE
);}
137 else damage(FL_DAMAGE_SCROLL
);
140 // Figure out top() based on position():
141 void Fl_Browser_::update_top() {
142 if (!top_
) top_
= item_first();
143 if (position_
!= real_position_
) {
147 // start from either head or current position, whichever is closer:
148 if (!top_
|| yy
<= (real_position_
/2)) {
153 ly
= real_position_
-offset_
;
160 int hh
= item_quick_height(l
);
161 // step through list until we find line containing this point:
163 void* l1
= item_prev(l
);
164 if (!l1
) {ly
= 0; break;} // hit the top
166 hh
= item_quick_height(l
);
169 while ((ly
+hh
) <= yy
) {
170 void* l1
= item_next(l
);
171 if (!l1
) {yy
= ly
+hh
-1; break;}
174 hh
= item_quick_height(l
);
176 // top item must *really* be visible, use slow height:
179 if ((ly
+hh
) > yy
) break; // it is big enough to see
180 // go up to top of previous item:
181 void* l1
= item_prev(l
);
182 if (!l1
) {ly
= yy
= 0; break;} // hit the top
183 l
= l1
; yy
= position_
= ly
= ly
-item_quick_height(l
);
190 damage(FL_DAMAGE_SCROLL
);
194 // Change position(), top() will update when update_top() is called
195 // (probably by draw() or handle()):
197 Sets the vertical scroll position of the list to pixel position \p pos.
198 The position is how many pixels of the list are scrolled off the top edge
199 of the screen. Example: A position of '3' scrolls the top three pixels of
200 the list off the top edge of the screen.
201 \param[in] pos The vertical position (in pixels) to scroll the browser to.
202 \see position(), hposition()
204 void Fl_Browser_::position(int pos
) {
205 if (pos
< 0) pos
= 0;
206 if (pos
== position_
) return;
208 if (pos
!= real_position_
) redraw_lines();
212 Sets the horizontal scroll position of the list to pixel position \p pos.
213 The position is how many pixels of the list are scrolled off the left edge
214 of the screen. Example: A position of '18' scrolls the left 18 pixels of the list
215 off the left edge of the screen.
216 \param[in] pos The horizontal position (in pixels) to scroll the browser to.
217 \see position(), hposition()
219 void Fl_Browser_::hposition(int pos
) {
220 if (pos
< 0) pos
= 0;
221 if (pos
== hposition_
) return;
223 if (pos
!= real_hposition_
) redraw_lines();
226 // Tell whether item is currently displayed:
228 Returns non-zero if \p item has been scrolled to a position where it is being displayed.
229 Checks to see if the item's vertical position is within the top and bottom
230 edges of the display window. This does NOT take into account the hide()/show()
231 status of the widget or item.
232 \param[in] item The item to check
233 \returns 1 if visible, 0 if not visible.
234 \see display(), displayed()
236 int Fl_Browser_::displayed(void* item
) const {
237 int X
, Y
, W
, H
; bbox(X
, Y
, W
, H
);
239 for (void* l
= top_
; l
&& yy
> 0; l
= item_next(l
)) {
240 if (l
== item
) return 1;
241 yy
-= item_height(l
);
246 // Ensure this item is displayed:
247 // Messy because we have no idea if it is before top or after bottom:
249 Displays the \p item, scrolling the list as necessary.
250 \param[in] item The item to be displayed.
251 \see display(), displayed()
253 void Fl_Browser_::display(void* item
) {
255 // First special case - want to display first item in the list?
257 if (item
== item_first()) {position(0); return;}
259 int X
, Y
, W
, H
, Yp
; bbox(X
, Y
, W
, H
);
264 // 2nd special case - want to display item already displayed at top of browser?
265 if (l
== item
) {position(real_position_
+Y
); return;} // scroll up a bit
267 // 3rd special case - want to display item just above top of browser?
268 void* lp
= item_prev(l
);
269 if (lp
== item
) {position(real_position_
+Y
-item_quick_height(lp
)); return;}
271 #ifdef DISPLAY_SEARCH_BOTH_WAYS_AT_ONCE
272 // search for item. We search both up and down the list at the same time,
273 // this evens up the execution time for the two cases - the old way was
274 // much slower for going up than for going down.
277 h1
= item_quick_height(l
);
279 if (Y
<= H
) { // it is visible or right at bottom
280 Y
= Y
+h1
-H
; // find where bottom edge is
281 if (Y
> 0) position(real_position_
+Y
); // scroll down a bit
283 position(real_position_
+Y
-(H
-h1
)/2); // center it
291 h1
= item_quick_height(lp
);
294 if ((Yp
+ h1
) >= 0) position(real_position_
+Yp
);
295 else position(real_position_
+Yp
-(H
-h1
)/2);
302 // Old version went forwards and then backwards:
303 // search forward for it:
305 for (; l
; l
= item_next(l
)) {
306 h1
= item_quick_height(l
);
308 if (Y
<= H
) { // it is visible or right at bottom
309 Y
= Y
+h1
-H
; // find where bottom edge is
310 if (Y
> 0) position(real_position_
+Y
); // scroll down a bit
312 position(real_position_
+Y
-(H
-h1
)/2); // center it
318 // search backward for it, if found center it:
321 for (; l
; l
= item_prev(l
)) {
322 h1
= item_quick_height(l
);
325 if ((Y
+ h1
) >= 0) position(real_position_
+Y
);
326 else position(real_position_
+Y
-(H
-h1
)/2);
333 // redraw, has side effect of updating top and setting scrollbar:
335 Draws the list within the normal widget bounding box.
337 void Fl_Browser_::draw() {
340 int full_width_
= full_width();
341 int full_height_
= full_height();
342 int X
, Y
, W
, H
; bbox(X
, Y
, W
, H
);
345 if (damage() & FL_DAMAGE_ALL
) { // redraw the box if full redraw
346 Fl_Boxtype b
= box() ? box() : FL_DOWN_BOX
;
347 draw_box(b
, x(), y(), w(), h(), color());
350 // see if scrollbar needs to be switched on/off:
351 if ((has_scrollbar_
& VERTICAL
) && (
352 (has_scrollbar_
& ALWAYS_ON
) || position_
|| full_height_
> H
)) {
353 if (!scrollbar
.visible()) {
354 scrollbar
.set_visible();
359 top_
= item_first(); real_position_
= offset_
= 0;
360 if (scrollbar
.visible()) {
361 scrollbar
.clear_visible();
362 clear_damage((uchar
)(damage()|FL_DAMAGE_SCROLL
));
366 if ((has_scrollbar_
& HORIZONTAL
) && (
367 (has_scrollbar_
& ALWAYS_ON
) || hposition_
|| full_width_
> W
)) {
368 if (!hscrollbar
.visible()) {
369 hscrollbar
.set_visible();
375 if (hscrollbar
.visible()) {
376 hscrollbar
.clear_visible();
377 clear_damage((uchar
)(damage()|FL_DAMAGE_SCROLL
));
381 // Check the vertical scrollbar again, just in case it needs to be drawn
382 // because the horizontal one is drawn. There should be a cleaner way
383 // to do this besides copying the same code...
384 if ((has_scrollbar_
& VERTICAL
) && (
385 (has_scrollbar_
& ALWAYS_ON
) || position_
|| full_height_
> H
)) {
386 if (!scrollbar
.visible()) {
387 scrollbar
.set_visible();
392 top_
= item_first(); real_position_
= offset_
= 0;
393 if (scrollbar
.visible()) {
394 scrollbar
.clear_visible();
395 clear_damage((uchar
)(damage()|FL_DAMAGE_SCROLL
));
401 fl_push_clip(X
, Y
, W
, H
);
402 // for each line, draw it if full redraw or scrolled. Erase background
403 // if not a full redraw or if it is selected:
406 for (; l
&& yy
< H
; l
= item_next(l
)) {
407 int hh
= item_height(l
);
408 if (hh
<= 0) continue;
409 if ((damage()&(FL_DAMAGE_SCROLL
|FL_DAMAGE_ALL
)) || l
== redraw1
|| l
== redraw2
) {
410 if (item_selected(l
)) {
411 fl_color(active_r() ? selection_color() : fl_inactive(selection_color()));
412 fl_rectf(X
, yy
+Y
, W
, hh
);
413 } else if (!(damage()&FL_DAMAGE_ALL
)) {
414 fl_push_clip(X
, yy
+Y
, W
, hh
);
415 draw_box(box() ? box() : FL_DOWN_BOX
, x(), y(), w(), h(), color());
418 item_draw(l
, X
-hposition_
, yy
+Y
, W
+hposition_
, hh
);
419 if (l
== selection_
&& Fl::focus() == this) {
420 draw_box(FL_BORDER_FRAME
, X
, yy
+Y
, W
, hh
, color());
421 draw_focus(FL_NO_BOX
, X
, yy
+Y
, W
+1, hh
+1);
423 int ww
= item_width(l
);
424 if (ww
> max_width
) {max_width
= ww
; max_width_item
= l
;}
428 // erase the area below last line:
429 if (!(damage()&FL_DAMAGE_ALL
) && yy
< H
) {
430 fl_push_clip(X
, yy
+Y
, W
, H
-yy
);
431 draw_box(box() ? box() : FL_DOWN_BOX
, x(), y(), w(), h(), color());
435 redraw1
= redraw2
= 0;
439 // see if changes to full_height caused by calls to slow_height
440 // caused scrollbar state to change, in which case we have to redraw:
441 full_height_
= full_height();
442 full_width_
= full_width();
443 if ((has_scrollbar_
& VERTICAL
) &&
444 ((has_scrollbar_
& ALWAYS_ON
) || position_
|| full_height_
>H
)) {
445 if (!scrollbar
.visible()) { damage(FL_DAMAGE_ALL
); goto J1
; }
447 if (scrollbar
.visible()) { damage(FL_DAMAGE_ALL
); goto J1
; }
449 if ((has_scrollbar_
& HORIZONTAL
) &&
450 ((has_scrollbar_
& ALWAYS_ON
) || hposition_
|| full_width_
>W
)) {
451 if (!hscrollbar
.visible()) { damage(FL_DAMAGE_ALL
); goto J1
; }
453 if (hscrollbar
.visible()) { damage(FL_DAMAGE_ALL
); goto J1
; }
457 // update the scrollbars and redraw them:
458 int scrollsize
= scrollbar_size_
? scrollbar_size_
: Fl::scrollbar_size();
459 int dy
= top_
? item_quick_height(top_
) : 0; if (dy
< 10) dy
= 10;
460 if (scrollbar
.visible()) {
461 scrollbar
.damage_resize(
462 scrollbar
.align()&FL_ALIGN_LEFT
? X
-scrollsize
: X
+W
,
464 scrollbar
.value(position_
, H
, 0, full_height_
);
465 scrollbar
.linesize(dy
);
466 if (drawsquare
) draw_child(scrollbar
);
467 else update_child(scrollbar
);
469 if (hscrollbar
.visible()) {
470 hscrollbar
.damage_resize(
471 X
, scrollbar
.align()&FL_ALIGN_TOP
? Y
-scrollsize
: Y
+H
,
473 hscrollbar
.value(hposition_
, W
, 0, full_width_
);
474 hscrollbar
.linesize(dy
);
475 if (drawsquare
) draw_child(hscrollbar
);
476 else update_child(hscrollbar
);
479 // draw that little square between the scrollbars:
480 if (drawsquare
&& scrollbar
.visible() && hscrollbar
.visible()) {
481 fl_color(parent()->color());
482 fl_rectf(scrollbar
.x(), hscrollbar
.y(), scrollsize
, scrollsize
);
485 real_hposition_
= hposition_
;
488 // Quick way to delete and reset everything:
490 This method should be called when the list data is completely replaced
491 or cleared. It informs the Fl_Browser_ widget that any cached
492 information it has concerning the items is invalid.
493 This method does not clear the list, it just handles the follow up
494 bookkeeping after the list has been cleared.
496 void Fl_Browser_::new_list() {
498 position_
= real_position_
= 0;
499 hposition_
= real_hposition_
= 0;
507 // Tell it that this item is going away, and that this must remove
508 // all pointers to it:
510 This method should be used when \p item is being deleted from the list.
511 It allows the Fl_Browser_ to discard any cached data it has on the item.
512 This method does not actually delete the item, but handles the follow up
513 bookkeeping after the item has just been deleted.
514 \param[in] item The item being deleted.
516 void Fl_Browser_::deleting(void* item
) {
517 if (displayed(item
)) {
520 real_position_
-= offset_
;
522 top_
= item_next(item
);
523 if (!top_
) top_
= item_prev(item
);
526 // we don't know where this item is, recalculate top...
531 if (item
== selection_
) selection_
= 0;
532 if (item
== max_width_item
) {max_width_item
= 0; max_width
= 0;}
536 This method should be used when item \p a is being replaced by item \p b.
537 It allows the Fl_Browser_ to update its cache data as needed,
538 schedules a redraw for the item being changed, and tries to maintain the selection.
539 This method does not actually replace the item, but handles the follow up
540 bookkeeping after the item has just been replaced.
541 \param[in] a Item being replaced
542 \param[in] b Item to replace 'a'
544 void Fl_Browser_::replacing(void* a
, void* b
) {
546 if (a
== selection_
) selection_
= b
;
547 if (a
== top_
) top_
= b
;
548 if (a
== max_width_item
) {max_width_item
= 0; max_width
= 0;}
552 This method should be used when two items \p a and \p b are being swapped.
553 It allows the Fl_Browser_ to update its cache data as needed,
554 schedules a redraw for the two items, and tries to maintain the current selection.
555 This method does not actually swap items, but handles the follow up
556 bookkeeping after items have been swapped.
557 \param[in] a,b Items being swapped.
559 void Fl_Browser_::swapping(void* a
, void* b
) {
562 if (a
== selection_
) selection_
= b
;
563 else if (b
== selection_
) selection_
= a
;
564 if (a
== top_
) top_
= b
;
565 else if (b
== top_
) top_
= a
;
569 This method should be used when an item is in the process of
570 being inserted into the list.
571 It allows the Fl_Browser_ to update its cache data as needed,
572 scheduling a redraw for the affected lines.
573 This method does not actually insert items, but handles the
574 follow up bookkeeping after items have been inserted.
575 \param[in] a The starting item position
576 \param[in] b The new item being inserted
578 void Fl_Browser_::inserting(void* a
, void* b
) {
579 if (displayed(a
)) redraw_lines();
580 if (a
== top_
) top_
= b
;
584 This method returns the item under mouse y position \p ypos.
585 NULL is returned if no item is displayed at that position.
586 \param[in] ypos The y position (eg. Fl::event_y()) to find an item under.
587 \returns The item, or NULL if not found
589 void* Fl_Browser_::find_item(int ypos
) {
591 int X
, Y
, W
, H
; bbox(X
, Y
, W
, H
);
593 for (void *l
= top_
; l
; l
= item_next(l
)) {
594 int hh
= item_height(l
); if (hh
<= 0) continue;
596 if (ypos
<= yy
|| yy
>=(Y
+H
)) return l
;
602 Sets the selection state of \p item to \p val,
603 and returns 1 if the state changed or 0 if it did not.
605 If \p docallbacks is non-zero, select tries to call
606 the callback function for the widget.
608 \param[in] item The item whose selection state is to be changed
609 \param[in] val The new selection state (1=select, 0=de-select)
610 \param[in] docallbacks If 1, invokes widget callback if item changed.\n
611 If 0, doesn't do callback (default).
612 \returns 1 if state was changed, 0 if not.
614 int Fl_Browser_::select(void* item
, int val
, int docallbacks
) {
615 if (type() == FL_MULTI_BROWSER
) {
616 if (selection_
!= item
) {
617 if (selection_
) redraw_line(selection_
);
621 if ((!val
)==(!item_selected(item
))) return 0;
622 item_select(item
, val
);
625 if (val
&& selection_
== item
) return 0;
626 if (!val
&& selection_
!= item
) return 0;
628 item_select(selection_
, 0);
629 redraw_line(selection_
);
633 item_select(item
, 1);
647 Deselects all items in the list and returns 1 if the state changed
650 If the optional \p docallbacks parameter is non-zero, deselect tries
651 to call the callback function for the widget.
653 \param[in] docallbacks If 1, invokes widget callback if item changed.\n
654 If 0, doesn't do callback (default).
656 int Fl_Browser_::deselect(int docallbacks
) {
657 if (type() == FL_MULTI_BROWSER
) {
659 for (void* p
= item_first(); p
; p
= item_next(p
))
660 change
|= select(p
, 0, docallbacks
);
663 if (!selection_
) return 0;
664 item_select(selection_
, 0);
665 redraw_line(selection_
);
672 Selects \p item and returns 1 if the state changed or 0 if it did not.
673 Any other items in the list are deselected.
674 \param[in] item The \p item to select.
675 \param[in] docallbacks If 1, invokes widget callback if item changed.\n
676 If 0, doesn't do callback (default).
678 int Fl_Browser_::select_only(void* item
, int docallbacks
) {
679 if (!item
) return deselect(docallbacks
);
681 Fl_Widget_Tracker
wp(this);
682 if (type() == FL_MULTI_BROWSER
) {
683 for (void* p
= item_first(); p
; p
= item_next(p
)) {
684 if (p
!= item
) change
|= select(p
, 0, docallbacks
);
685 if (wp
.deleted()) return change
;
688 change
|= select(item
, 1, docallbacks
);
689 if (wp
.deleted()) return change
;
695 Handles the \p event within the normal widget bounding box.
696 \param[in] event The event to process.
697 \returns 1 if event was processed, 0 if not.
699 int Fl_Browser_::handle(int event
) {
702 // We use Fl_Widget_Tracker to test if the user has deleted
703 // this widget in a callback. Callbacks can be called by:
708 // Thus we must test wp.deleted() after each of these calls,
709 // unless we return directly after one of these.
710 // If wp.deleted() is true, we return 1 because we used the event.
712 Fl_Widget_Tracker
wp(this);
714 // must do shortcuts first or the scrollbar will get them...
715 if (event
== FL_ENTER
|| event
== FL_LEAVE
) return 1;
716 if (event
== FL_KEYBOARD
&& type() >= FL_HOLD_BROWSER
) {
717 void* l1
= selection_
;
718 void* l
= l1
; if (!l
) l
= top_
; if (!l
) l
= item_first();
720 if (type()==FL_HOLD_BROWSER
) {
721 switch (Fl::event_key()) {
723 while ((l
= item_next(l
)))
724 if (item_height(l
)>0) {select_only(l
, when()); break;}
727 while ((l
= item_prev(l
))) {
728 if (item_height(l
)>0) {
729 select_only(l
, when());
730 break; // no need to test wp (return 1)
736 switch (Fl::event_key()) {
739 select_only(l
, when() & ~FL_WHEN_ENTER_KEY
);
740 if (wp
.deleted()) return 1;
741 if (when() & FL_WHEN_ENTER_KEY
) {
748 select(l
, !item_selected(l
), when() & ~FL_WHEN_ENTER_KEY
);
751 while ((l
= item_next(l
))) {
752 if (Fl::event_state(FL_SHIFT
|FL_CTRL
))
753 select(l
, l1
? item_selected(l1
) : 1, when());
754 if (wp
.deleted()) return 1;
755 if (item_height(l
)>0) goto J1
;
759 while ((l
= item_prev(l
))) {
760 if (Fl::event_state(FL_SHIFT
|FL_CTRL
))
761 select(l
, l1
? item_selected(l1
) : 1, when());
762 if (wp
.deleted()) return 1;
763 if (item_height(l
)>0) goto J1
;
767 if (selection_
) redraw_line(selection_
);
768 selection_
= l
; redraw_line(l
);
776 if (Fl_Group::handle(event
)) return 1;
777 if (wp
.deleted()) return 1;
779 int X
, Y
, W
, H
; bbox(X
, Y
, W
, H
);
783 // change = select_only(find_item(my), when() & FL_WHEN_CHANGED)
784 // we use the construct:
785 // change = select_only(find_item(my), 0);
786 // if (change && (when() & FL_WHEN_CHANGED)) {
791 // The first form calls the callback *before* setting change.
792 // The callback may execute an Fl::wait(), resulting in another
793 // call of Fl_Browser_::handle() for the same widget. The sequence
794 // of events can be an FL_PUSH followed by an FL_RELEASE.
795 // This second call of Fl_Browser_::handle() may result in a -
796 // somewhat unexpected - second concurrent invocation of the callback.
799 static char whichway
;
803 if (!Fl::event_inside(X
, Y
, W
, H
)) return 0;
804 if (Fl::visible_focus()) {
808 my
= py
= Fl::event_y();
810 if (type() == FL_NORMAL_BROWSER
|| !top_
)
812 else if (type() != FL_MULTI_BROWSER
) {
813 change
= select_only(find_item(my
), 0);
814 if (wp
.deleted()) return 1;
815 if (change
&& (when() & FL_WHEN_CHANGED
)) {
818 if (wp
.deleted()) return 1;
821 void* l
= find_item(my
);
823 if (Fl::event_state(FL_CTRL
)) { // toggle selection:
826 whichway
= !item_selected(l
);
827 change
= select(l
, whichway
, 0);
828 if (wp
.deleted()) return 1;
829 if (change
&& (when() & FL_WHEN_CHANGED
)) {
832 if (wp
.deleted()) return 1;
835 } else if (Fl::event_state(FL_SHIFT
)) { // extend selection:
836 if (l
== selection_
) goto TOGGLE
;
837 // state of previous selection determines new value:
838 whichway
= l
? !item_selected(l
) : 1;
839 // see which of the new item or previous selection is earlier,
840 // by searching from the previous forward for this one:
843 else {for (void* m
= selection_
; ; m
= item_next(m
)) {
844 if (m
== l
) {down
= 1; break;}
845 if (!m
) {down
= 0; break;}
848 for (void* m
= selection_
; m
!= l
; m
= item_next(m
)) {
849 select(m
, whichway
, when() & FL_WHEN_CHANGED
);
850 if (wp
.deleted()) return 1;
853 void* e
= selection_
;
854 for (void* m
= item_next(l
); m
; m
= item_next(m
)) {
855 select(m
, whichway
, when() & FL_WHEN_CHANGED
);
856 if (wp
.deleted()) return 1;
860 // do the clicked item last so the select box is around it:
862 if (l
) select(l
, whichway
, when() & FL_WHEN_CHANGED
);
863 if (wp
.deleted()) return 1;
864 } else { // select only this item
865 change
= select_only(l
, 0);
866 if (wp
.deleted()) return 1;
867 if (change
&& (when() & FL_WHEN_CHANGED
)) {
870 if (wp
.deleted()) return 1;
876 // do the scrolling first:
878 if (my
< Y
&& my
< py
) {
879 int p
= real_position_
+my
-Y
;
882 } else if (my
> (Y
+H
) && my
> py
) {
883 int p
= real_position_
+my
-(Y
+H
);
884 int hh
= full_height()-H
; if (p
> hh
) p
= hh
;
888 if (type() == FL_NORMAL_BROWSER
|| !top_
)
890 else if (type() == FL_MULTI_BROWSER
) {
891 void* l
= find_item(my
);
892 void* t
; void* b
; // this will be the range to change
893 if (my
> py
) { // go down
894 t
= selection_
? item_next(selection_
) : 0;
895 b
= l
? item_next(l
) : 0;
900 for (; t
&& t
!= b
; t
= item_next(t
)) {
902 change_t
= select(t
, whichway
, 0);
903 if (wp
.deleted()) return 1;
905 if (change_t
&& (when() & FL_WHEN_CHANGED
)) {
908 if (wp
.deleted()) return 1;
911 if (l
) selection_
= l
;
913 void* l1
= selection_
;
915 (Fl::event_x()<x() || Fl::event_x()>x()+w()) ? selection_
:
918 select_only(l
, when() & FL_WHEN_CHANGED
);
919 if (wp
.deleted()) return 1;
924 if (type() == FL_SELECT_BROWSER
) {
925 void* t
= selection_
;
927 if (wp
.deleted()) return 1;
932 if (when() & FL_WHEN_RELEASE
) do_callback();
934 if (when() & FL_WHEN_NOT_CHANGED
) do_callback();
936 if (wp
.deleted()) return 1;
938 // double click calls the callback: (like Enter Key)
939 if (Fl::event_clicks() && (when() & FL_WHEN_ENTER_KEY
)) {
946 if (type() >= FL_HOLD_BROWSER
&& Fl::visible_focus()) {
956 The constructor makes an empty browser.
957 \param[in] X,Y,W,H position and size.
958 \param[in] L The label string, may be NULL.
960 Fl_Browser_::Fl_Browser_(int X
, int Y
, int W
, int H
, const char* L
)
961 : Fl_Group(X
, Y
, W
, H
, L
),
962 scrollbar(0, 0, 0, 0, 0), // they will be resized by draw()
963 hscrollbar(0, 0, 0, 0, 0)
966 align(FL_ALIGN_BOTTOM
);
967 position_
= real_position_
= 0;
968 hposition_
= real_hposition_
= 0;
971 when(FL_WHEN_RELEASE_ALWAYS
);
973 color(FL_BACKGROUND2_COLOR
, FL_SELECTION_COLOR
);
974 scrollbar
.callback(scrollbar_callback
);
975 //scrollbar.align(FL_ALIGN_LEFT|FL_ALIGN_BOTTOM); // back compatibility?
976 hscrollbar
.callback(hscrollbar_callback
);
977 hscrollbar
.type(FL_HORIZONTAL
);
978 textfont_
= FL_HELVETICA
;
979 textsize_
= FL_NORMAL_SIZE
;
980 textcolor_
= FL_FOREGROUND_COLOR
;
981 has_scrollbar_
= BOTH
;
985 redraw1
= redraw2
= 0;
990 Sort the items in the browser based on \p flags.
991 item_swap(void*, void*) and item_text(void*) must be implemented for this call.
992 \param[in] flags FL_SORT_ASCENDING -- sort in ascending order\n
993 FL_SORT_DESCENDING -- sort in descending order\n
994 Values other than the above will cause undefined behavior\n
995 Other flags may appear in the future.
996 \todo Add a flag to ignore case
998 void Fl_Browser_::sort(int flags
) {
1000 // Simple bubble sort - pure lazyness on my side.
1002 int i
, j
, n
= -1, desc
= ((flags
&FL_SORT_DESCENDING
)==FL_SORT_DESCENDING
);
1003 void *a
=item_first(), *b
, *c
;
1009 for (i
=n
; i
>0; i
--) {
1013 for (j
=0; j
<i
; j
++) {
1014 const char *ta
= item_text(a
);
1015 const char *tb
= item_text(b
);
1018 if (strcmp(ta
, tb
)<0) {
1023 if (strcmp(ta
, tb
)>0) {
1029 b
= c
; a
= item_prev(b
);
1036 // Default versions of some of the virtual functions:
1039 This method may be provided by the subclass to return the height of the
1041 Allow for two additional pixels for the list selection box.
1042 This method differs from item_height in that it is only called for
1043 selection and scrolling operations.
1044 The default implementation calls item_height.
1045 \param[in] item The item whose height to return.
1046 \returns The height, in pixels.
1048 int Fl_Browser_::item_quick_height(void* item
) const {
1049 return item_height(item
);
1053 This method may be provided to return the average height of all items
1054 to be used for scrolling.
1055 The default implementation uses the height of the first item.
1056 \returns The average height of items, in pixels.
1058 int Fl_Browser_::incr_height() const {
1059 return item_quick_height(item_first());
1063 This method may be provided by the subclass to indicate the full height
1064 of the item list, in pixels.
1065 The default implementation computes the full height from the item heights.
1066 Includes the items that are scrolled off screen.
1067 \returns The height of the entire list, in pixels.
1069 int Fl_Browser_::full_height() const {
1071 for (void* p
= item_first(); p
; p
= item_next(p
))
1072 t
+= item_quick_height(p
);
1077 This method may be provided by the subclass to indicate the full width
1078 of the item list, in pixels.
1079 The default implementation computes the full width from the item widths.
1080 \returns The maximum width of all the items, in pixels.
1082 int Fl_Browser_::full_width() const {
1087 This method must be implemented by the subclass if it supports
1088 multiple selections; sets the selection state to \p val for the \p item.
1089 Sets the selection state for \p item, where optional \p val is 1 (select, the default)
1091 \param[in] item The item to be selected
1092 \param[in] val The optional selection state; 1=select, 0=de-select.\n
1093 The default is to select the item (1).
1095 void Fl_Browser_::item_select(void *item
, int val
) {}
1098 This method must be implemented by the subclass if it supports
1099 multiple selections; returns the selection state for \p item.
1100 The method should return 1 if \p item is selected, or 0 otherwise.
1101 \param[in] item The item to test.
1103 int Fl_Browser_::item_selected(void* item
) const { return item
==selection_
? 1 : 0; }
1106 // End of "$Id: Fl_Browser_.cxx 7903 2010-11-28 21:06:39Z matt $".