themes: Workaround for bug where a background color of RGB 0,0,0 in Black color schem...
[ntk.git] / src / Fl_Browser_.cxx
blob1f738b1d1e7533310eb0226217e3d44c539a555f
1 //
2 // "$Id: Fl_Browser_.cxx 7903 2010-11-28 21:06:39Z matt $"
3 //
4 // Base Browser widget class for the Fast Light Tool Kit (FLTK).
5 //
6 // Copyright 1998-2010 by Bill Spitzak and others.
7 //
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
21 // USA.
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
30 #include <stdio.h>
31 #include <FL/Fl.H>
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
48 // for.
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
54 // over the network.
56 /* redraw bits:
57 1 = redraw children (the scrollbar)
58 2 = redraw one or two items
59 4 = redraw all 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:
71 /**
72 Returns the bounding box for the interior of the list's display window, inside
73 the scrollbars.
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()) {
85 W -= scrollsize;
86 if (scrollbar.align() & FL_ALIGN_LEFT) X += scrollsize;
88 if (W < 0) W = 0;
89 if (hscrollbar.visible()) {
90 H -= scrollsize;
91 if (scrollbar.align() & FL_ALIGN_TOP) Y += scrollsize;
93 if (H < 0) H = 0;
96 /**
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);
104 return X;
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:
118 bbox(X,Y,W,H);
119 scrollbar.resize(
120 scrollbar.align()&FL_ALIGN_LEFT ? X-scrollsize : X+W,
121 Y, scrollsize, H);
122 hscrollbar.resize(
123 X, scrollbar.align()&FL_ALIGN_TOP ? Y-scrollsize : Y+H,
124 W, scrollsize);
127 // Cause minimal update to redraw the given item:
129 This method should be called when the contents of \p item has changed,
130 but not its height.
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_) {
144 void* l;
145 int ly;
146 int yy = position_;
147 // start from either head or current position, whichever is closer:
148 if (!top_ || yy <= (real_position_/2)) {
149 l = item_first();
150 ly = 0;
151 } else {
152 l = top_;
153 ly = real_position_-offset_;
155 if (!l) {
156 top_ = 0;
157 offset_ = 0;
158 real_position_ = 0;
159 } else {
160 int hh = item_quick_height(l);
161 // step through list until we find line containing this point:
162 while (ly > yy) {
163 void* l1 = item_prev(l);
164 if (!l1) {ly = 0; break;} // hit the top
165 l = l1;
166 hh = item_quick_height(l);
167 ly -= hh;
169 while ((ly+hh) <= yy) {
170 void* l1 = item_next(l);
171 if (!l1) {yy = ly+hh-1; break;}
172 l = l1;
173 ly += hh;
174 hh = item_quick_height(l);
176 // top item must *really* be visible, use slow height:
177 for (;;) {
178 hh = item_height(l);
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);
185 // use it:
186 top_ = l;
187 offset_ = yy-ly;
188 real_position_ = yy;
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;
207 position_ = pos;
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;
222 hposition_ = pos;
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);
238 int yy = H+offset_;
239 for (void* l = top_; l && yy > 0; l = item_next(l)) {
240 if (l == item) return 1;
241 yy -= item_height(l);
243 return 0;
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?
256 update_top();
257 if (item == item_first()) {position(0); return;}
259 int X, Y, W, H, Yp; bbox(X, Y, W, H);
260 void* l = top_;
261 Y = Yp = -offset_;
262 int h1;
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.
275 while (l || lp) {
276 if (l) {
277 h1 = item_quick_height(l);
278 if (l == item) {
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
282 } else {
283 position(real_position_+Y-(H-h1)/2); // center it
285 return;
287 Y += h1;
288 l = item_next(l);
290 if (lp) {
291 h1 = item_quick_height(lp);
292 Yp -= h1;
293 if (lp == item) {
294 if ((Yp + h1) >= 0) position(real_position_+Yp);
295 else position(real_position_+Yp-(H-h1)/2);
296 return;
298 lp = item_prev(lp);
301 #else
302 // Old version went forwards and then backwards:
303 // search forward for it:
304 l = top_;
305 for (; l; l = item_next(l)) {
306 h1 = item_quick_height(l);
307 if (l == item) {
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
311 } else {
312 position(real_position_+Y-(H-h1)/2); // center it
314 return;
316 Y += h1;
318 // search backward for it, if found center it:
319 l = lp;
320 Y = -offset_;
321 for (; l; l = item_prev(l)) {
322 h1 = item_quick_height(l);
323 Y -= h1;
324 if (l == item) {
325 if ((Y + h1) >= 0) position(real_position_+Y);
326 else position(real_position_+Y-(H-h1)/2);
327 return;
330 #endif
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() {
338 int drawsquare = 0;
339 update_top();
340 int full_width_ = full_width();
341 int full_height_ = full_height();
342 int X, Y, W, H; bbox(X, Y, W, H);
343 int dont_repeat = 0;
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());
348 drawsquare = 1;
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();
355 drawsquare = 1;
356 bbox(X, Y, W, H);
358 } else {
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();
370 drawsquare = 1;
371 bbox(X, Y, W, H);
373 } else {
374 real_hposition_ = 0;
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();
388 drawsquare = 1;
389 bbox(X, Y, W, H);
391 } else {
392 top_ = item_first(); real_position_ = offset_ = 0;
393 if (scrollbar.visible()) {
394 scrollbar.clear_visible();
395 clear_damage((uchar)(damage()|FL_DAMAGE_SCROLL));
399 bbox(X, Y, W, H);
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:
404 void* l = top();
405 int yy = -offset_;
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());
416 fl_pop_clip();
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;}
426 yy += hh;
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());
432 fl_pop_clip();
434 fl_pop_clip();
435 redraw1 = redraw2 = 0;
437 if (!dont_repeat) {
438 dont_repeat = 1;
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; }
446 } else {
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; }
452 } else {
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,
463 Y, scrollsize, H);
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,
472 W, scrollsize);
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() {
497 top_ = 0;
498 position_ = real_position_ = 0;
499 hposition_ = real_hposition_ = 0;
500 selection_ = 0;
501 offset_ = 0;
502 max_width = 0;
503 max_width_item = 0;
504 redraw_lines();
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)) {
518 redraw_lines();
519 if (item == top_) {
520 real_position_ -= offset_;
521 offset_ = 0;
522 top_ = item_next(item);
523 if (!top_) top_ = item_prev(item);
525 } else {
526 // we don't know where this item is, recalculate top...
527 real_position_ = 0;
528 offset_ = 0;
529 top_ = 0;
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) {
545 redraw_line(a);
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) {
560 redraw_line(a);
561 redraw_line(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) {
590 update_top();
591 int X, Y, W, H; bbox(X, Y, W, H);
592 int yy = Y-offset_;
593 for (void *l = top_; l; l = item_next(l)) {
594 int hh = item_height(l); if (hh <= 0) continue;
595 yy += hh;
596 if (ypos <= yy || yy>=(Y+H)) return l;
598 return 0;
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_);
618 selection_ = item;
619 redraw_line(item);
621 if ((!val)==(!item_selected(item))) return 0;
622 item_select(item, val);
623 redraw_line(item);
624 } else {
625 if (val && selection_ == item) return 0;
626 if (!val && selection_ != item) return 0;
627 if (selection_) {
628 item_select(selection_, 0);
629 redraw_line(selection_);
630 selection_ = 0;
632 if (val) {
633 item_select(item, 1);
634 selection_ = item;
635 redraw_line(item);
636 display(item);
639 if (docallbacks) {
640 set_changed();
641 do_callback();
643 return 1;
647 Deselects all items in the list and returns 1 if the state changed
648 or 0 if it did not.
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) {
658 int change = 0;
659 for (void* p = item_first(); p; p = item_next(p))
660 change |= select(p, 0, docallbacks);
661 return change;
662 } else {
663 if (!selection_) return 0;
664 item_select(selection_, 0);
665 redraw_line(selection_);
666 selection_ = 0;
667 return 1;
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);
680 int change = 0;
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;
690 display(item);
691 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) {
701 // NOTE:
702 // We use Fl_Widget_Tracker to test if the user has deleted
703 // this widget in a callback. Callbacks can be called by:
704 // - do_callback()
705 // - select()
706 // - select_only()
707 // - deselect()
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();
719 if (l) {
720 if (type()==FL_HOLD_BROWSER) {
721 switch (Fl::event_key()) {
722 case FL_Down:
723 while ((l = item_next(l)))
724 if (item_height(l)>0) {select_only(l, when()); break;}
725 return 1;
726 case FL_Up:
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)
733 return 1;
735 } else {
736 switch (Fl::event_key()) {
737 case FL_Enter:
738 case FL_KP_Enter:
739 select_only(l, when() & ~FL_WHEN_ENTER_KEY);
740 if (wp.deleted()) return 1;
741 if (when() & FL_WHEN_ENTER_KEY) {
742 set_changed();
743 do_callback();
745 return 1;
746 case ' ':
747 selection_ = l;
748 select(l, !item_selected(l), when() & ~FL_WHEN_ENTER_KEY);
749 return 1;
750 case FL_Down:
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;
757 return 1;
758 case FL_Up:
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;
765 return 1;
767 if (selection_) redraw_line(selection_);
768 selection_ = l; redraw_line(l);
769 display(l);
770 return 1;
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);
780 int my;
781 // NOTE:
782 // instead of:
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)) {
787 // set_changed();
788 // do_callback();
789 // }
790 // See str #834
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.
798 static char change;
799 static char whichway;
800 static int py;
801 switch (event) {
802 case FL_PUSH:
803 if (!Fl::event_inside(X, Y, W, H)) return 0;
804 if (Fl::visible_focus()) {
805 Fl::focus(this);
806 redraw();
808 my = py = Fl::event_y();
809 change = 0;
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)) {
816 set_changed();
817 do_callback();
818 if (wp.deleted()) return 1;
820 } else {
821 void* l = find_item(my);
822 whichway = 1;
823 if (Fl::event_state(FL_CTRL)) { // toggle selection:
824 TOGGLE:
825 if (l) {
826 whichway = !item_selected(l);
827 change = select(l, whichway, 0);
828 if (wp.deleted()) return 1;
829 if (change && (when() & FL_WHEN_CHANGED)) {
830 set_changed();
831 do_callback();
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:
841 int down;
842 if (!l) down = 1;
843 else {for (void* m = selection_; ; m = item_next(m)) {
844 if (m == l) {down = 1; break;}
845 if (!m) {down = 0; break;}
847 if (down) {
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;
852 } else {
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;
857 if (m == e) break;
860 // do the clicked item last so the select box is around it:
861 change = 1;
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)) {
868 set_changed();
869 do_callback();
870 if (wp.deleted()) return 1;
874 return 1;
875 case FL_DRAG:
876 // do the scrolling first:
877 my = Fl::event_y();
878 if (my < Y && my < py) {
879 int p = real_position_+my-Y;
880 if (p<0) p = 0;
881 position(p);
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;
885 if (p<0) p = 0;
886 position(p);
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;
896 } else { // go up
897 t = l;
898 b = selection_;
900 for (; t && t != b; t = item_next(t)) {
901 char change_t;
902 change_t = select(t, whichway, 0);
903 if (wp.deleted()) return 1;
904 change |= change_t;
905 if (change_t && (when() & FL_WHEN_CHANGED)) {
906 set_changed();
907 do_callback();
908 if (wp.deleted()) return 1;
911 if (l) selection_ = l;
912 } else {
913 void* l1 = selection_;
914 void* l =
915 (Fl::event_x()<x() || Fl::event_x()>x()+w()) ? selection_ :
916 find_item(my);
917 change = (l != l1);
918 select_only(l, when() & FL_WHEN_CHANGED);
919 if (wp.deleted()) return 1;
921 py = my;
922 return 1;
923 case FL_RELEASE:
924 if (type() == FL_SELECT_BROWSER) {
925 void* t = selection_;
926 deselect();
927 if (wp.deleted()) return 1;
928 selection_ = t;
930 if (change) {
931 set_changed();
932 if (when() & FL_WHEN_RELEASE) do_callback();
933 } else {
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)) {
940 set_changed();
941 do_callback();
943 return 1;
944 case FL_FOCUS:
945 case FL_UNFOCUS:
946 if (type() >= FL_HOLD_BROWSER && Fl::visible_focus()) {
947 redraw();
948 return 1;
949 } else return 0;
952 return 0;
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)
965 box(FL_NO_BOX);
966 align(FL_ALIGN_BOTTOM);
967 position_ = real_position_ = 0;
968 hposition_ = real_hposition_ = 0;
969 offset_ = 0;
970 top_ = 0;
971 when(FL_WHEN_RELEASE_ALWAYS);
972 selection_ = 0;
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;
982 max_width = 0;
983 max_width_item = 0;
984 scrollbar_size_ = 0;
985 redraw1 = redraw2 = 0;
986 end();
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;
1004 if (!a) return;
1005 while (a) {
1006 a = item_next(a);
1007 n++;
1009 for (i=n; i>0; i--) {
1010 char swapped = 0;
1011 a = item_first();
1012 b = item_next(a);
1013 for (j=0; j<i; j++) {
1014 const char *ta = item_text(a);
1015 const char *tb = item_text(b);
1016 c = item_next(b);
1017 if (desc) {
1018 if (strcmp(ta, tb)<0) {
1019 item_swap(a, b);
1020 swapped = 1;
1022 } else {
1023 if (strcmp(ta, tb)>0) {
1024 item_swap(a, b);
1025 swapped = 1;
1028 if (!c) break;
1029 b = c; a = item_prev(b);
1031 if (!swapped)
1032 break;
1036 // Default versions of some of the virtual functions:
1039 This method may be provided by the subclass to return the height of the
1040 \p item, in pixels.
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 {
1070 int t = 0;
1071 for (void* p = item_first(); p; p = item_next(p))
1072 t += item_quick_height(p);
1073 return t;
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 {
1083 return max_width;
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)
1090 or 0 (de-select).
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 $".