1 | // |
---|
2 | // "$Id$" |
---|
3 | // |
---|
4 | // Base Browser widget class for the Fast Light Tool Kit (FLTK). |
---|
5 | // |
---|
6 | // Copyright 1998-2006 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. |
---|
12 | // |
---|
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. |
---|
17 | // |
---|
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. |
---|
22 | // |
---|
23 | // Please report all bugs and problems on the following page: |
---|
24 | // |
---|
25 | // http://www.fltk.org/str.php |
---|
26 | // |
---|
27 | |
---|
28 | #define DISPLAY_SEARCH_BOTH_WAYS_AT_ONCE |
---|
29 | |
---|
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> |
---|
35 | |
---|
36 | |
---|
37 | // This is the base class for browsers. To be useful it must be |
---|
38 | // subclassed and several virtual functions defined. The |
---|
39 | // Forms-compatable browser and the file chooser's browser are |
---|
40 | // subclassed off of this. |
---|
41 | |
---|
42 | // Yes, I know this should be a template... |
---|
43 | |
---|
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. |
---|
49 | |
---|
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. |
---|
55 | |
---|
56 | /* redraw bits: |
---|
57 | 1 = redraw children (the scrollbar) |
---|
58 | 2 = redraw one or two items |
---|
59 | 4 = redraw all items |
---|
60 | */ |
---|
61 | |
---|
62 | static void scrollbar_callback(Fl_Widget* s, void*) { |
---|
63 | ((Fl_Browser_*)(s->parent()))->position(int(((Fl_Scrollbar*)s)->value())); |
---|
64 | } |
---|
65 | |
---|
66 | static void hscrollbar_callback(Fl_Widget* s, void*) { |
---|
67 | ((Fl_Browser_*)(s->parent()))->hposition(int(((Fl_Scrollbar*)s)->value())); |
---|
68 | } |
---|
69 | |
---|
70 | // Scrollbar size should be part of the Fl class, but is left here for |
---|
71 | // binary compatibility in 1.1.x - M. Sweet |
---|
72 | int Fl_Browser_::scrollbar_width_ = 16; |
---|
73 | |
---|
74 | // Get the standard scrollbar size |
---|
75 | int Fl::scrollbar_size() { |
---|
76 | return Fl_Browser_::scrollbar_width(); |
---|
77 | } |
---|
78 | |
---|
79 | // Set the standard scrollbar size |
---|
80 | void Fl::scrollbar_size(int W) { |
---|
81 | Fl_Browser_::scrollbar_width(W); |
---|
82 | } |
---|
83 | |
---|
84 | // return where to draw the actual box: |
---|
85 | void Fl_Browser_::bbox(int& X, int& Y, int& W, int& H) const { |
---|
86 | Fl_Boxtype b = box() ? box() : FL_DOWN_BOX; |
---|
87 | X = x()+Fl::box_dx(b); |
---|
88 | Y = y()+Fl::box_dy(b); |
---|
89 | W = w()-Fl::box_dw(b); |
---|
90 | H = h()-Fl::box_dh(b); |
---|
91 | if (scrollbar.visible()) { |
---|
92 | W -= scrollbar_width_; |
---|
93 | if (scrollbar.align() & FL_ALIGN_LEFT) X += scrollbar_width_; |
---|
94 | } |
---|
95 | if (W < 0) W = 0; |
---|
96 | if (hscrollbar.visible()) { |
---|
97 | H -= scrollbar_width_; |
---|
98 | if (scrollbar.align() & FL_ALIGN_TOP) Y += scrollbar_width_; |
---|
99 | } |
---|
100 | if (H < 0) H = 0; |
---|
101 | } |
---|
102 | |
---|
103 | int Fl_Browser_::leftedge() const { |
---|
104 | int X, Y, W, H; bbox(X, Y, W, H); |
---|
105 | return X; |
---|
106 | } |
---|
107 | |
---|
108 | // The scrollbars may be moved again by draw(), since each one's size |
---|
109 | // depends on whether the other is visible or not. This skips over |
---|
110 | // Fl_Group::resize since it moves the scrollbars uselessly. |
---|
111 | void Fl_Browser_::resize(int X, int Y, int W, int H) { |
---|
112 | Fl_Widget::resize(X, Y, W, H); |
---|
113 | // move the scrollbars so they can respond to events: |
---|
114 | bbox(X,Y,W,H); |
---|
115 | scrollbar.resize( |
---|
116 | scrollbar.align()&FL_ALIGN_LEFT ? X-scrollbar_width_ : X+W, |
---|
117 | Y, scrollbar_width_, H); |
---|
118 | hscrollbar.resize( |
---|
119 | X, scrollbar.align()&FL_ALIGN_TOP ? Y-scrollbar_width_ : Y+H, |
---|
120 | W, scrollbar_width_); |
---|
121 | } |
---|
122 | |
---|
123 | // Cause minimal update to redraw the given item: |
---|
124 | void Fl_Browser_::redraw_line(void* l) { |
---|
125 | if (!redraw1 || redraw1 == l) {redraw1 = l; damage(FL_DAMAGE_EXPOSE);} |
---|
126 | else if (!redraw2 || redraw2 == l) {redraw2 = l; damage(FL_DAMAGE_EXPOSE);} |
---|
127 | else damage(FL_DAMAGE_SCROLL); |
---|
128 | } |
---|
129 | |
---|
130 | // Figure out top() based on position(): |
---|
131 | void Fl_Browser_::update_top() { |
---|
132 | if (!top_) top_ = item_first(); |
---|
133 | if (position_ != real_position_) { |
---|
134 | void* l; |
---|
135 | int ly; |
---|
136 | int yy = position_; |
---|
137 | // start from either head or current position, whichever is closer: |
---|
138 | if (!top_ || yy <= (real_position_/2)) { |
---|
139 | l = item_first(); |
---|
140 | ly = 0; |
---|
141 | } else { |
---|
142 | l = top_; |
---|
143 | ly = real_position_-offset_; |
---|
144 | } |
---|
145 | if (!l) { |
---|
146 | top_ = 0; |
---|
147 | offset_ = 0; |
---|
148 | real_position_ = 0; |
---|
149 | } else { |
---|
150 | int hh = item_quick_height(l); |
---|
151 | // step through list until we find line containing this point: |
---|
152 | while (ly > yy) { |
---|
153 | void* l1 = item_prev(l); |
---|
154 | if (!l1) {ly = 0; break;} // hit the top |
---|
155 | l = l1; |
---|
156 | hh = item_quick_height(l); |
---|
157 | ly -= hh; |
---|
158 | } |
---|
159 | while ((ly+hh) <= yy) { |
---|
160 | void* l1 = item_next(l); |
---|
161 | if (!l1) {yy = ly+hh-1; break;} |
---|
162 | l = l1; |
---|
163 | ly += hh; |
---|
164 | hh = item_quick_height(l); |
---|
165 | } |
---|
166 | // top item must *really* be visible, use slow height: |
---|
167 | for (;;) { |
---|
168 | hh = item_height(l); |
---|
169 | if ((ly+hh) > yy) break; // it is big enough to see |
---|
170 | // go up to top of previous item: |
---|
171 | void* l1 = item_prev(l); |
---|
172 | if (!l1) {ly = yy = 0; break;} // hit the top |
---|
173 | l = l1; yy = position_ = ly = ly-item_quick_height(l); |
---|
174 | } |
---|
175 | // use it: |
---|
176 | top_ = l; |
---|
177 | offset_ = yy-ly; |
---|
178 | real_position_ = yy; |
---|
179 | } |
---|
180 | damage(FL_DAMAGE_SCROLL); |
---|
181 | } |
---|
182 | } |
---|
183 | |
---|
184 | // Change position(), top() will update when update_top() is called |
---|
185 | // (probably by draw() or handle()): |
---|
186 | void Fl_Browser_::position(int yy) { |
---|
187 | if (yy < 0) yy = 0; |
---|
188 | if (yy == position_) return; |
---|
189 | position_ = yy; |
---|
190 | if (yy != real_position_) redraw_lines(); |
---|
191 | } |
---|
192 | |
---|
193 | void Fl_Browser_::hposition(int xx) { |
---|
194 | if (xx < 0) xx = 0; |
---|
195 | if (xx == hposition_) return; |
---|
196 | hposition_ = xx; |
---|
197 | if (xx != real_hposition_) redraw_lines(); |
---|
198 | } |
---|
199 | |
---|
200 | // Tell whether item is currently displayed: |
---|
201 | int Fl_Browser_::displayed(void* p) const { |
---|
202 | int X, Y, W, H; bbox(X, Y, W, H); |
---|
203 | int yy = H+offset_; |
---|
204 | for (void* l = top_; l && yy > 0; l = item_next(l)) { |
---|
205 | if (l == p) return 1; |
---|
206 | yy -= item_height(l); |
---|
207 | } |
---|
208 | return 0; |
---|
209 | } |
---|
210 | |
---|
211 | // Ensure this item is displayed: |
---|
212 | // Messy because we have no idea if it is before top or after bottom: |
---|
213 | void Fl_Browser_::display(void* p) { |
---|
214 | |
---|
215 | // First special case - want to display first item in the list? |
---|
216 | update_top(); |
---|
217 | if (p == item_first()) {position(0); return;} |
---|
218 | |
---|
219 | int X, Y, W, H, Yp; bbox(X, Y, W, H); |
---|
220 | void* l = top_; |
---|
221 | Y = Yp = -offset_; |
---|
222 | int h1; |
---|
223 | |
---|
224 | // 2nd special case - want to display item already displayed at top of browser? |
---|
225 | if (l == p) {position(real_position_+Y); return;} // scroll up a bit |
---|
226 | |
---|
227 | // 3rd special case - want to display item just above top of browser? |
---|
228 | void* lp = item_prev(l); |
---|
229 | if (lp == p) {position(real_position_+Y-item_quick_height(lp)); return;} |
---|
230 | |
---|
231 | #ifdef DISPLAY_SEARCH_BOTH_WAYS_AT_ONCE |
---|
232 | // search for item. We search both up and down the list at the same time, |
---|
233 | // this evens up the execution time for the two cases - the old way was |
---|
234 | // much slower for going up than for going down. |
---|
235 | while (l || lp) { |
---|
236 | if (l) { |
---|
237 | h1 = item_quick_height(l); |
---|
238 | if (l == p) { |
---|
239 | if (Y <= H) { // it is visible or right at bottom |
---|
240 | Y = Y+h1-H; // find where bottom edge is |
---|
241 | if (Y > 0) position(real_position_+Y); // scroll down a bit |
---|
242 | } else { |
---|
243 | position(real_position_+Y-(H-h1)/2); // center it |
---|
244 | } |
---|
245 | return; |
---|
246 | } |
---|
247 | Y += h1; |
---|
248 | l = item_next(l); |
---|
249 | } |
---|
250 | if (lp) { |
---|
251 | h1 = item_quick_height(lp); |
---|
252 | Yp -= h1; |
---|
253 | if (lp == p) { |
---|
254 | if ((Yp + h1) >= 0) position(real_position_+Yp); |
---|
255 | else position(real_position_+Yp-(H-h1)/2); |
---|
256 | return; |
---|
257 | } |
---|
258 | lp = item_prev(lp); |
---|
259 | } |
---|
260 | } |
---|
261 | #else |
---|
262 | // Old version went forwards and then backwards: |
---|
263 | // search forward for it: |
---|
264 | l = top_; |
---|
265 | for (; l; l = item_next(l)) { |
---|
266 | h1 = item_quick_height(l); |
---|
267 | if (l == p) { |
---|
268 | if (Y <= H) { // it is visible or right at bottom |
---|
269 | Y = Y+h1-H; // find where bottom edge is |
---|
270 | if (Y > 0) position(real_position_+Y); // scroll down a bit |
---|
271 | } else { |
---|
272 | position(real_position_+Y-(H-h1)/2); // center it |
---|
273 | } |
---|
274 | return; |
---|
275 | } |
---|
276 | Y += h1; |
---|
277 | } |
---|
278 | // search backward for it, if found center it: |
---|
279 | l = lp; |
---|
280 | Y = -offset_; |
---|
281 | for (; l; l = item_prev(l)) { |
---|
282 | h1 = item_quick_height(l); |
---|
283 | Y -= h1; |
---|
284 | if (l == p) { |
---|
285 | if ((Y + h1) >= 0) position(real_position_+Y); |
---|
286 | else position(real_position_+Y-(H-h1)/2); |
---|
287 | return; |
---|
288 | } |
---|
289 | } |
---|
290 | #endif |
---|
291 | } |
---|
292 | |
---|
293 | // redraw, has side effect of updating top and setting scrollbar: |
---|
294 | |
---|
295 | void Fl_Browser_::draw() { |
---|
296 | int drawsquare = 0; |
---|
297 | update_top(); |
---|
298 | int full_width_ = full_width(); |
---|
299 | int full_height_ = full_height(); |
---|
300 | int X, Y, W, H; bbox(X, Y, W, H); |
---|
301 | int dont_repeat = 0; |
---|
302 | J1: |
---|
303 | if (damage() & FL_DAMAGE_ALL) { // redraw the box if full redraw |
---|
304 | Fl_Boxtype b = box() ? box() : FL_DOWN_BOX; |
---|
305 | draw_box(b, x(), y(), w(), h(), color()); |
---|
306 | drawsquare = 1; |
---|
307 | } |
---|
308 | // see if scrollbar needs to be switched on/off: |
---|
309 | if ((has_scrollbar_ & VERTICAL) && ( |
---|
310 | (has_scrollbar_ & ALWAYS_ON) || position_ || full_height_ > H)) { |
---|
311 | if (!scrollbar.visible()) { |
---|
312 | scrollbar.set_visible(); |
---|
313 | drawsquare = 1; |
---|
314 | bbox(X, Y, W, H); |
---|
315 | } |
---|
316 | } else { |
---|
317 | top_ = item_first(); real_position_ = offset_ = 0; |
---|
318 | if (scrollbar.visible()) { |
---|
319 | scrollbar.clear_visible(); |
---|
320 | clear_damage((uchar)(damage()|FL_DAMAGE_SCROLL)); |
---|
321 | } |
---|
322 | } |
---|
323 | |
---|
324 | if ((has_scrollbar_ & HORIZONTAL) && ( |
---|
325 | (has_scrollbar_ & ALWAYS_ON) || hposition_ || full_width_ > W)) { |
---|
326 | if (!hscrollbar.visible()) { |
---|
327 | hscrollbar.set_visible(); |
---|
328 | drawsquare = 1; |
---|
329 | bbox(X, Y, W, H); |
---|
330 | } |
---|
331 | } else { |
---|
332 | real_hposition_ = 0; |
---|
333 | if (hscrollbar.visible()) { |
---|
334 | hscrollbar.clear_visible(); |
---|
335 | clear_damage((uchar)(damage()|FL_DAMAGE_SCROLL)); |
---|
336 | } |
---|
337 | } |
---|
338 | |
---|
339 | // Check the vertical scrollbar again, just in case it needs to be drawn |
---|
340 | // because the horizontal one is drawn. There should be a cleaner way |
---|
341 | // to do this besides copying the same code... |
---|
342 | if ((has_scrollbar_ & VERTICAL) && ( |
---|
343 | (has_scrollbar_ & ALWAYS_ON) || position_ || full_height_ > H)) { |
---|
344 | if (!scrollbar.visible()) { |
---|
345 | scrollbar.set_visible(); |
---|
346 | drawsquare = 1; |
---|
347 | bbox(X, Y, W, H); |
---|
348 | } |
---|
349 | } else { |
---|
350 | top_ = item_first(); real_position_ = offset_ = 0; |
---|
351 | if (scrollbar.visible()) { |
---|
352 | scrollbar.clear_visible(); |
---|
353 | clear_damage((uchar)(damage()|FL_DAMAGE_SCROLL)); |
---|
354 | } |
---|
355 | } |
---|
356 | |
---|
357 | bbox(X, Y, W, H); |
---|
358 | |
---|
359 | fl_clip(X, Y, W, H); |
---|
360 | // for each line, draw it if full redraw or scrolled. Erase background |
---|
361 | // if not a full redraw or if it is selected: |
---|
362 | void* l = top(); |
---|
363 | int yy = -offset_; |
---|
364 | for (; l && yy < H; l = item_next(l)) { |
---|
365 | int hh = item_height(l); |
---|
366 | if (hh <= 0) continue; |
---|
367 | if ((damage()&(FL_DAMAGE_SCROLL|FL_DAMAGE_ALL)) || l == redraw1 || l == redraw2) { |
---|
368 | if (item_selected(l)) { |
---|
369 | fl_color(active_r() ? selection_color() : fl_inactive(selection_color())); |
---|
370 | fl_rectf(X, yy+Y, W, hh); |
---|
371 | } else if (!(damage()&FL_DAMAGE_ALL)) { |
---|
372 | fl_push_clip(X, yy+Y, W, hh); |
---|
373 | draw_box(box() ? box() : FL_DOWN_BOX, x(), y(), w(), h(), color()); |
---|
374 | fl_pop_clip(); |
---|
375 | } |
---|
376 | item_draw(l, X-hposition_, yy+Y, W+hposition_, hh); |
---|
377 | if (l == selection_ && Fl::focus() == this) { |
---|
378 | draw_box(FL_BORDER_FRAME, X, yy+Y, W, hh, color()); |
---|
379 | draw_focus(FL_NO_BOX, X, yy+Y, W+1, hh+1); |
---|
380 | } |
---|
381 | int ww = item_width(l); |
---|
382 | if (ww > max_width) {max_width = ww; max_width_item = l;} |
---|
383 | } |
---|
384 | yy += hh; |
---|
385 | } |
---|
386 | // erase the area below last line: |
---|
387 | if (!(damage()&FL_DAMAGE_ALL) && yy < H) { |
---|
388 | fl_push_clip(X, yy+Y, W, H-yy); |
---|
389 | draw_box(box() ? box() : FL_DOWN_BOX, x(), y(), w(), h(), color()); |
---|
390 | fl_pop_clip(); |
---|
391 | } |
---|
392 | fl_pop_clip(); |
---|
393 | redraw1 = redraw2 = 0; |
---|
394 | |
---|
395 | if (!dont_repeat) { |
---|
396 | dont_repeat = 1; |
---|
397 | // see if changes to full_height caused by calls to slow_height |
---|
398 | // caused scrollbar state to change, in which case we have to redraw: |
---|
399 | full_height_ = full_height(); |
---|
400 | full_width_ = full_width(); |
---|
401 | if ((has_scrollbar_ & VERTICAL) && |
---|
402 | ((has_scrollbar_ & ALWAYS_ON) || position_ || full_height_>H)) { |
---|
403 | if (!scrollbar.visible()) { damage(FL_DAMAGE_ALL); goto J1; } |
---|
404 | } else { |
---|
405 | if (scrollbar.visible()) { damage(FL_DAMAGE_ALL); goto J1; } |
---|
406 | } |
---|
407 | if ((has_scrollbar_ & HORIZONTAL) && |
---|
408 | ((has_scrollbar_ & ALWAYS_ON) || hposition_ || full_width_>W)) { |
---|
409 | if (!hscrollbar.visible()) { damage(FL_DAMAGE_ALL); goto J1; } |
---|
410 | } else { |
---|
411 | if (hscrollbar.visible()) { damage(FL_DAMAGE_ALL); goto J1; } |
---|
412 | } |
---|
413 | } |
---|
414 | |
---|
415 | // update the scrollbars and redraw them: |
---|
416 | int dy = top_ ? item_quick_height(top_) : 0; if (dy < 10) dy = 10; |
---|
417 | if (scrollbar.visible()) { |
---|
418 | scrollbar.damage_resize( |
---|
419 | scrollbar.align()&FL_ALIGN_LEFT ? X-scrollbar_width_ : X+W, |
---|
420 | Y, scrollbar_width_, H); |
---|
421 | scrollbar.value(position_, H, 0, full_height_); |
---|
422 | scrollbar.linesize(dy); |
---|
423 | if (drawsquare) draw_child(scrollbar); |
---|
424 | else update_child(scrollbar); |
---|
425 | } |
---|
426 | if (hscrollbar.visible()) { |
---|
427 | hscrollbar.damage_resize( |
---|
428 | X, scrollbar.align()&FL_ALIGN_TOP ? Y-scrollbar_width_ : Y+H, |
---|
429 | W, scrollbar_width_); |
---|
430 | hscrollbar.value(hposition_, W, 0, full_width_); |
---|
431 | hscrollbar.linesize(dy); |
---|
432 | if (drawsquare) draw_child(hscrollbar); |
---|
433 | else update_child(hscrollbar); |
---|
434 | } |
---|
435 | |
---|
436 | // draw that little square between the scrollbars: |
---|
437 | if (drawsquare && scrollbar.visible() && hscrollbar.visible()) { |
---|
438 | fl_color(parent()->color()); |
---|
439 | fl_rectf(scrollbar.x(), hscrollbar.y(), scrollbar_width_,scrollbar_width_); |
---|
440 | } |
---|
441 | |
---|
442 | real_hposition_ = hposition_; |
---|
443 | } |
---|
444 | |
---|
445 | // Quick way to delete and reset everything: |
---|
446 | void Fl_Browser_::new_list() { |
---|
447 | top_ = 0; |
---|
448 | position_ = real_position_ = 0; |
---|
449 | hposition_ = real_hposition_ = 0; |
---|
450 | selection_ = 0; |
---|
451 | offset_ = 0; |
---|
452 | max_width = 0; |
---|
453 | max_width_item = 0; |
---|
454 | redraw_lines(); |
---|
455 | } |
---|
456 | |
---|
457 | // Tell it that this item is going away, and that this must remove |
---|
458 | // all pointers to it: |
---|
459 | void Fl_Browser_::deleting(void* l) { |
---|
460 | if (displayed(l)) { |
---|
461 | redraw_lines(); |
---|
462 | if (l == top_) { |
---|
463 | real_position_ -= offset_; |
---|
464 | offset_ = 0; |
---|
465 | top_ = item_next(l); |
---|
466 | if (!top_) top_ = item_prev(l); |
---|
467 | } |
---|
468 | } else { |
---|
469 | // we don't know where this item is, recalculate top... |
---|
470 | real_position_ = 0; |
---|
471 | offset_ = 0; |
---|
472 | top_ = 0; |
---|
473 | } |
---|
474 | if (l == selection_) selection_ = 0; |
---|
475 | if (l == max_width_item) {max_width_item = 0; max_width = 0;} |
---|
476 | } |
---|
477 | |
---|
478 | void Fl_Browser_::replacing(void* a, void* b) { |
---|
479 | redraw_line(a); |
---|
480 | if (a == selection_) selection_ = b; |
---|
481 | if (a == top_) top_ = b; |
---|
482 | if (a == max_width_item) {max_width_item = 0; max_width = 0;} |
---|
483 | } |
---|
484 | |
---|
485 | void Fl_Browser_::swapping(void* a, void* b) { |
---|
486 | redraw_line(a); |
---|
487 | redraw_line(b); |
---|
488 | if (a == selection_) selection_ = b; |
---|
489 | else if (b == selection_) selection_ = a; |
---|
490 | if (a == top_) top_ = b; |
---|
491 | else if (b == top_) top_ = a; |
---|
492 | } |
---|
493 | |
---|
494 | void Fl_Browser_::inserting(void* a, void* b) { |
---|
495 | if (displayed(a)) redraw_lines(); |
---|
496 | if (a == top_) top_ = b; |
---|
497 | } |
---|
498 | |
---|
499 | void* Fl_Browser_::find_item(int my) { |
---|
500 | update_top(); |
---|
501 | int X, Y, W, H; bbox(X, Y, W, H); |
---|
502 | void* l; |
---|
503 | int yy = Y-offset_; |
---|
504 | for (l = top_; l; l = item_next(l)) { |
---|
505 | int hh = item_height(l); if (hh <= 0) continue; |
---|
506 | yy += hh; |
---|
507 | if (my <= yy || yy>=(Y+H)) return l; |
---|
508 | } |
---|
509 | return 0; |
---|
510 | } |
---|
511 | |
---|
512 | int Fl_Browser_::select(void* l, int i, int docallbacks) { |
---|
513 | if (type() == FL_MULTI_BROWSER) { |
---|
514 | if (selection_ != l) { |
---|
515 | if (selection_) redraw_line(selection_); |
---|
516 | selection_ = l; |
---|
517 | redraw_line(l); |
---|
518 | } |
---|
519 | if ((!i)==(!item_selected(l))) return 0; |
---|
520 | item_select(l, i); |
---|
521 | redraw_line(l); |
---|
522 | } else { |
---|
523 | if (i && selection_ == l) return 0; |
---|
524 | if (!i && selection_ != l) return 0; |
---|
525 | if (selection_) { |
---|
526 | item_select(selection_, 0); |
---|
527 | redraw_line(selection_); |
---|
528 | selection_ = 0; |
---|
529 | } |
---|
530 | if (i) { |
---|
531 | item_select(l, 1); |
---|
532 | selection_ = l; |
---|
533 | redraw_line(l); |
---|
534 | display(l); |
---|
535 | } |
---|
536 | } |
---|
537 | if (docallbacks) { |
---|
538 | set_changed(); |
---|
539 | do_callback(); |
---|
540 | } |
---|
541 | return 1; |
---|
542 | } |
---|
543 | |
---|
544 | int Fl_Browser_::deselect(int docallbacks) { |
---|
545 | if (type() == FL_MULTI_BROWSER) { |
---|
546 | int change = 0; |
---|
547 | for (void* p = item_first(); p; p = item_next(p)) |
---|
548 | change |= select(p, 0, docallbacks); |
---|
549 | return change; |
---|
550 | } else { |
---|
551 | if (!selection_) return 0; |
---|
552 | item_select(selection_, 0); |
---|
553 | redraw_line(selection_); |
---|
554 | selection_ = 0; |
---|
555 | return 1; |
---|
556 | } |
---|
557 | } |
---|
558 | |
---|
559 | int Fl_Browser_::select_only(void* l, int docallbacks) { |
---|
560 | if (!l) return deselect(docallbacks); |
---|
561 | int change = 0; |
---|
562 | if (type() == FL_MULTI_BROWSER) { |
---|
563 | for (void* p = item_first(); p; p = item_next(p)) |
---|
564 | if (p != l) change |= select(p, 0, docallbacks); |
---|
565 | } |
---|
566 | change |= select(l, 1, docallbacks); |
---|
567 | display(l); |
---|
568 | return change; |
---|
569 | } |
---|
570 | |
---|
571 | int Fl_Browser_::handle(int event) { |
---|
572 | // must do shortcuts first or the scrollbar will get them... |
---|
573 | if (event == FL_ENTER || event == FL_LEAVE) return 1; |
---|
574 | if (event == FL_KEYBOARD && type() >= FL_HOLD_BROWSER) { |
---|
575 | void* l1 = selection_; |
---|
576 | void* l = l1; if (!l) l = top_; if (!l) l = item_first(); |
---|
577 | if (l) { |
---|
578 | if (type()==FL_HOLD_BROWSER) { |
---|
579 | switch (Fl::event_key()) { |
---|
580 | case FL_Down: |
---|
581 | while ((l = item_next(l))) |
---|
582 | if (item_height(l)>0) {select_only(l, when()); break;} |
---|
583 | return 1; |
---|
584 | case FL_Up: |
---|
585 | while ((l = item_prev(l))) if (item_height(l)>0) { |
---|
586 | select_only(l, when()); break;} |
---|
587 | return 1; |
---|
588 | } |
---|
589 | } else { |
---|
590 | switch (Fl::event_key()) { |
---|
591 | case FL_Enter: |
---|
592 | case FL_KP_Enter: |
---|
593 | select_only(l, when() & ~FL_WHEN_ENTER_KEY); |
---|
594 | if (when() & FL_WHEN_ENTER_KEY) { |
---|
595 | set_changed(); |
---|
596 | do_callback(); |
---|
597 | } |
---|
598 | return 1; |
---|
599 | case ' ': |
---|
600 | selection_ = l; |
---|
601 | select(l, !item_selected(l), when() & ~FL_WHEN_ENTER_KEY); |
---|
602 | return 1; |
---|
603 | case FL_Down: |
---|
604 | while ((l = item_next(l))) { |
---|
605 | if (Fl::event_state(FL_SHIFT|FL_CTRL)) |
---|
606 | select(l, l1 ? item_selected(l1) : 1, when()); |
---|
607 | if (item_height(l)>0) goto J1; |
---|
608 | } |
---|
609 | return 1; |
---|
610 | case FL_Up: |
---|
611 | while ((l = item_prev(l))) { |
---|
612 | if (Fl::event_state(FL_SHIFT|FL_CTRL)) |
---|
613 | select(l, l1 ? item_selected(l1) : 1, when()); |
---|
614 | if (item_height(l)>0) goto J1; |
---|
615 | } |
---|
616 | return 1; |
---|
617 | J1: |
---|
618 | if (selection_) redraw_line(selection_); |
---|
619 | selection_ = l; redraw_line(l); |
---|
620 | display(l); |
---|
621 | return 1; |
---|
622 | } |
---|
623 | } |
---|
624 | } |
---|
625 | } |
---|
626 | |
---|
627 | if (Fl_Group::handle(event)) return 1; |
---|
628 | int X, Y, W, H; bbox(X, Y, W, H); |
---|
629 | int my; |
---|
630 | // NOTE: |
---|
631 | // instead of: |
---|
632 | // change = select_only(find_item(my), when() & FL_WHEN_CHANGED) |
---|
633 | // we use the construct: |
---|
634 | // change = select_only(find_item(my), 0); |
---|
635 | // if (change && (when() & FL_WHEN_CHANGED)) { |
---|
636 | // set_changed(); |
---|
637 | // do_callback(); |
---|
638 | // } |
---|
639 | // See str #834 |
---|
640 | // The first form calls the callback *before* setting change. |
---|
641 | // The callback may execute an Fl::wait(), resulting in another |
---|
642 | // call of Fl_Browser_::handle() for the same widget. The sequence |
---|
643 | // of events can be an FL_PUSH followed by an FL_RELEASE. |
---|
644 | // This second call of Fl_Browser_::handle() may result in a - |
---|
645 | // somewhat unexpected - second concurrent invocation of the callback. |
---|
646 | |
---|
647 | static char change; |
---|
648 | static char whichway; |
---|
649 | static int py; |
---|
650 | switch (event) { |
---|
651 | case FL_PUSH: |
---|
652 | if (!Fl::event_inside(X, Y, W, H)) return 0; |
---|
653 | if (Fl::visible_focus()) { |
---|
654 | Fl::focus(this); |
---|
655 | redraw(); |
---|
656 | } |
---|
657 | my = py = Fl::event_y(); |
---|
658 | change = 0; |
---|
659 | if (type() == FL_NORMAL_BROWSER || !top_) |
---|
660 | ; |
---|
661 | else if (type() != FL_MULTI_BROWSER) { |
---|
662 | change = select_only(find_item(my), 0); |
---|
663 | if (change && (when() & FL_WHEN_CHANGED)) { |
---|
664 | set_changed(); |
---|
665 | do_callback(); |
---|
666 | } |
---|
667 | } else { |
---|
668 | void* l = find_item(my); |
---|
669 | whichway = 1; |
---|
670 | if (Fl::event_state(FL_CTRL)) { // toggle selection: |
---|
671 | TOGGLE: |
---|
672 | if (l) { |
---|
673 | whichway = !item_selected(l); |
---|
674 | change = select(l, whichway, 0); |
---|
675 | if (change && (when() & FL_WHEN_CHANGED)) { |
---|
676 | set_changed(); |
---|
677 | do_callback(); |
---|
678 | } |
---|
679 | } |
---|
680 | } else if (Fl::event_state(FL_SHIFT)) { // extend selection: |
---|
681 | if (l == selection_) goto TOGGLE; |
---|
682 | // state of previous selection determines new value: |
---|
683 | whichway = l ? !item_selected(l) : 1; |
---|
684 | // see which of the new item or previous selection is earlier, |
---|
685 | // by searching from the previous forward for this one: |
---|
686 | int down; |
---|
687 | if (!l) down = 1; |
---|
688 | else {for (void* m = selection_; ; m = item_next(m)) { |
---|
689 | if (m == l) {down = 1; break;} |
---|
690 | if (!m) {down = 0; break;} |
---|
691 | }} |
---|
692 | if (down) { |
---|
693 | for (void* m = selection_; m != l; m = item_next(m)) |
---|
694 | select(m, whichway, when() & FL_WHEN_CHANGED); |
---|
695 | } else { |
---|
696 | void* e = selection_; |
---|
697 | for (void* m = item_next(l); m; m = item_next(m)) { |
---|
698 | select(m, whichway, when() & FL_WHEN_CHANGED); |
---|
699 | if (m == e) break; |
---|
700 | } |
---|
701 | } |
---|
702 | // do the clicked item last so the select box is around it: |
---|
703 | change = 1; |
---|
704 | if (l) select(l, whichway, when() & FL_WHEN_CHANGED); |
---|
705 | } else { // select only this item |
---|
706 | change = select_only(l, 0); |
---|
707 | if (change && (when() & FL_WHEN_CHANGED)) { |
---|
708 | set_changed(); |
---|
709 | do_callback(); |
---|
710 | } |
---|
711 | } |
---|
712 | } |
---|
713 | return 1; |
---|
714 | case FL_DRAG: |
---|
715 | // do the scrolling first: |
---|
716 | my = Fl::event_y(); |
---|
717 | if (my < Y && my < py) { |
---|
718 | int p = real_position_+my-Y; |
---|
719 | if (p<0) p = 0; |
---|
720 | position(p); |
---|
721 | } else if (my > (Y+H) && my > py) { |
---|
722 | int p = real_position_+my-(Y+H); |
---|
723 | int hh = full_height()-H; if (p > hh) p = hh; |
---|
724 | if (p<0) p = 0; |
---|
725 | position(p); |
---|
726 | } |
---|
727 | if (type() == FL_NORMAL_BROWSER || !top_) |
---|
728 | ; |
---|
729 | else if (type() == FL_MULTI_BROWSER) { |
---|
730 | void* l = find_item(my); |
---|
731 | void* t; void* b; // this will be the range to change |
---|
732 | if (my > py) { // go down |
---|
733 | t = selection_ ? item_next(selection_) : 0; |
---|
734 | b = l ? item_next(l) : 0; |
---|
735 | } else { // go up |
---|
736 | t = l; |
---|
737 | b = selection_; |
---|
738 | } |
---|
739 | for (; t && t != b; t = item_next(t)) { |
---|
740 | char change_t; |
---|
741 | change_t = select(t, whichway, 0); |
---|
742 | change |= change_t; |
---|
743 | if (change_t && (when() & FL_WHEN_CHANGED)) { |
---|
744 | set_changed(); |
---|
745 | do_callback(); |
---|
746 | } |
---|
747 | } |
---|
748 | if (l) selection_ = l; |
---|
749 | } else { |
---|
750 | void* l1 = selection_; |
---|
751 | void* l = |
---|
752 | (Fl::event_x()<x() || Fl::event_x()>x()+w()) ? selection_ : |
---|
753 | find_item(my); |
---|
754 | change = (l != l1); |
---|
755 | select_only(l, when() & FL_WHEN_CHANGED); |
---|
756 | } |
---|
757 | py = my; |
---|
758 | return 1; |
---|
759 | case FL_RELEASE: |
---|
760 | if (type() == FL_SELECT_BROWSER) { |
---|
761 | void* t = selection_; deselect(); selection_ = t; |
---|
762 | } |
---|
763 | if (change) { |
---|
764 | set_changed(); |
---|
765 | if (when() & FL_WHEN_RELEASE) do_callback(); |
---|
766 | } else { |
---|
767 | if (when() & FL_WHEN_NOT_CHANGED) do_callback(); |
---|
768 | } |
---|
769 | |
---|
770 | // double click calls the callback: (like Enter Key) |
---|
771 | if (Fl::event_clicks() && (when() & FL_WHEN_ENTER_KEY)) { |
---|
772 | set_changed(); |
---|
773 | do_callback(); |
---|
774 | } |
---|
775 | return 1; |
---|
776 | case FL_FOCUS: |
---|
777 | case FL_UNFOCUS: |
---|
778 | if (type() >= FL_HOLD_BROWSER && Fl::visible_focus()) { |
---|
779 | redraw(); |
---|
780 | return 1; |
---|
781 | } else return 0; |
---|
782 | } |
---|
783 | |
---|
784 | return 0; |
---|
785 | } |
---|
786 | |
---|
787 | Fl_Browser_::Fl_Browser_(int X, int Y, int W, int H, const char* l) |
---|
788 | : Fl_Group(X, Y, W, H, l), |
---|
789 | scrollbar(0, 0, 0, 0, 0), // they will be resized by draw() |
---|
790 | hscrollbar(0, 0, 0, 0, 0) |
---|
791 | { |
---|
792 | box(FL_NO_BOX); |
---|
793 | align(FL_ALIGN_BOTTOM); |
---|
794 | position_ = real_position_ = 0; |
---|
795 | hposition_ = real_hposition_ = 0; |
---|
796 | offset_ = 0; |
---|
797 | top_ = 0; |
---|
798 | when(FL_WHEN_RELEASE_ALWAYS); |
---|
799 | selection_ = 0; |
---|
800 | color(FL_BACKGROUND2_COLOR, FL_SELECTION_COLOR); |
---|
801 | scrollbar.callback(scrollbar_callback); |
---|
802 | //scrollbar.align(FL_ALIGN_LEFT|FL_ALIGN_BOTTOM); // back compatability? |
---|
803 | hscrollbar.callback(hscrollbar_callback); |
---|
804 | hscrollbar.type(FL_HORIZONTAL); |
---|
805 | textfont_ = FL_HELVETICA; |
---|
806 | textsize_ = (uchar)FL_NORMAL_SIZE; |
---|
807 | textcolor_ = FL_FOREGROUND_COLOR; |
---|
808 | has_scrollbar_ = BOTH; |
---|
809 | max_width = 0; |
---|
810 | max_width_item = 0; |
---|
811 | redraw1 = redraw2 = 0; |
---|
812 | end(); |
---|
813 | } |
---|
814 | |
---|
815 | // Default versions of some of the virtual functions: |
---|
816 | |
---|
817 | int Fl_Browser_::item_quick_height(void* l) const { |
---|
818 | return item_height(l); |
---|
819 | } |
---|
820 | |
---|
821 | int Fl_Browser_::incr_height() const { |
---|
822 | return item_quick_height(item_first()); |
---|
823 | } |
---|
824 | |
---|
825 | int Fl_Browser_::full_height() const { |
---|
826 | int t = 0; |
---|
827 | for (void* p = item_first(); p; p = item_next(p)) |
---|
828 | t += item_quick_height(p); |
---|
829 | return t; |
---|
830 | } |
---|
831 | |
---|
832 | int Fl_Browser_::full_width() const { |
---|
833 | return max_width; |
---|
834 | } |
---|
835 | |
---|
836 | void Fl_Browser_::item_select(void*, int) {} |
---|
837 | |
---|
838 | int Fl_Browser_::item_selected(void* l) const {return l==selection_;} |
---|
839 | |
---|
840 | // |
---|
841 | // End of "$Id$". |
---|
842 | // |
---|