themes: Workaround for bug where a background color of RGB 0,0,0 in Black color schem...
[ntk.git] / src / Fl_Tabs.cxx
blob14f309b6b8fe152f672677a9c71df8f4bcb46532
1 //
2 // "$Id: Fl_Tabs.cxx 8658 2011-05-12 15:53:59Z manolo $"
3 //
4 // Tab widget 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
29 // This is the "file card tabs" interface to allow you to put lots and lots
30 // of buttons and switches in a panel, as popularized by many toolkits.
32 // Each child widget is a card, and its label() is printed on the card tab.
33 // Clicking the tab makes that card visible.
35 #include <stdio.h>
36 #include <FL/Fl.H>
37 #include <FL/Fl_Tabs.H>
38 #include <FL/fl_draw.H>
39 #include <FL/Fl_Tooltip.H>
41 #define BORDER 2
42 #define EXTRASPACE 10
43 #define SELECTION_BORDER 5
45 // Return the left edges of each tab (plus a fake left edge for a tab
46 // past the right-hand one). These positions are actually of the left
47 // edge of the slope. They are either separated by the correct distance
48 // or by EXTRASPACE or by zero.
49 // These positions are updated in the private arrays tab_pos[] and
50 // tab_width[], resp.. If needed, these arrays are (re)allocated.
51 // Return value is the index of the selected item.
53 int Fl_Tabs::tab_positions() {
54 int nc = children();
55 if (nc != tab_count) {
56 clear_tab_positions();
57 if (nc) {
58 tab_pos = (int*)malloc((nc+1)*sizeof(int));
59 tab_width = (int*)malloc((nc+1)*sizeof(int));
61 tab_count = nc;
63 if (nc == 0) return 0;
64 int selected = 0;
65 Fl_Widget*const* a = array();
66 int i;
67 char prev_draw_shortcut = fl_draw_shortcut;
68 fl_draw_shortcut = 1;
70 tab_pos[0] = Fl::box_dx(box());
71 for (i=0; i<nc; i++) {
72 Fl_Widget* o = *a++;
73 if (o->visible()) selected = i;
75 int wt = 0; int ht = 0;
76 o->measure_label(wt,ht);
78 tab_width[i] = wt + EXTRASPACE;
79 tab_pos[i+1] = tab_pos[i] + tab_width[i] + BORDER;
81 fl_draw_shortcut = prev_draw_shortcut;
83 int r = w();
84 if (tab_pos[i] <= r) return selected;
85 // uh oh, they are too big:
86 // pack them against right edge:
87 tab_pos[i] = r;
88 for (i = nc; i--;) {
89 int l = r-tab_width[i];
90 if (tab_pos[i+1] < l) l = tab_pos[i+1];
91 if (tab_pos[i] <= l) break;
92 tab_pos[i] = l;
93 r -= EXTRASPACE;
95 // pack them against left edge and truncate width if they still don't fit:
96 for (i = 0; i<nc; i++) {
97 if (tab_pos[i] >= i*EXTRASPACE) break;
98 tab_pos[i] = i*EXTRASPACE;
99 int W = w()-1-EXTRASPACE*(children()-i) - tab_pos[i];
100 if (tab_width[i] > W) tab_width[i] = W;
102 // adjust edges according to visiblity:
103 for (i = nc; i > selected; i--) {
104 tab_pos[i] = tab_pos[i-1] + tab_width[i-1];
106 return selected;
109 // Returns space (height) in pixels needed for tabs. Negative to put them on the bottom.
110 // Returns full height, if children() = 0.
111 int Fl_Tabs::tab_height() {
112 if (children() == 0) return h();
113 int H = h();
114 int H2 = y();
115 Fl_Widget*const* a = array();
116 for (int i=children(); i--;) {
117 Fl_Widget* o = *a++;
118 if (o->y() < y()+H) H = o->y()-y();
119 if (o->y()+o->h() > H2) H2 = o->y()+o->h();
121 H2 = y()+h()-H2;
122 if (H2 > H) return (H2 <= 0) ? 0 : -H2;
123 else return (H <= 0) ? 0 : H;
126 // This is used for event handling (clicks) and by fluid to pick tabs.
127 // Returns 0, if children() = 0, or if the event is outside of the tabs area.
128 Fl_Widget *Fl_Tabs::which(int event_x, int event_y) {
129 if (children() == 0) return 0;
130 int H = tab_height();
131 if (H < 0) {
132 if (event_y > y()+h() || event_y < y()+h()+H) return 0;
133 } else {
134 if (event_y > y()+H || event_y < y()) return 0;
136 if (event_x < x()) return 0;
137 Fl_Widget *ret = 0L;
138 int nc = children();
139 tab_positions();
140 for (int i=0; i<nc; i++) {
141 if (event_x < x()+tab_pos[i+1]) {
142 ret = child(i);
143 break;
146 return ret;
149 void Fl_Tabs::redraw_tabs()
151 int H = tab_height();
152 if (H >= 0) {
153 H += Fl::box_dy(box());
154 damage(FL_DAMAGE_SCROLL, x(), y(), w(), H);
155 } else {
156 H = Fl::box_dy(box()) - H;
157 damage(FL_DAMAGE_SCROLL, x(), y() + h() - H, w(), H);
161 int Fl_Tabs::handle(int event) {
163 Fl_Widget *o;
164 int i;
166 switch (event) {
168 case FL_PUSH: {
169 int H = tab_height();
170 if (H >= 0) {
171 if (Fl::event_y() > y()+H) return Fl_Group::handle(event);
172 } else {
173 if (Fl::event_y() < y()+h()+H) return Fl_Group::handle(event);
175 /* FALLTHROUGH */
176 case FL_DRAG:
177 case FL_RELEASE:
178 o = which(Fl::event_x(), Fl::event_y());
179 if (event == FL_RELEASE) {
180 push(0);
181 if (o && Fl::visible_focus() && Fl::focus()!=this) {
182 Fl::focus(this);
183 redraw_tabs();
185 if (o && value(o)) {
186 Fl_Widget_Tracker wp(o);
187 set_changed();
188 do_callback();
189 if (wp.deleted()) return 1;
191 Fl_Tooltip::current(o);
192 } else {
193 push(o);
195 return 1;
196 case FL_MOVE: {
197 int ret = Fl_Group::handle(event);
198 Fl_Widget *o = Fl_Tooltip::current(), *n = o;
199 int H = tab_height();
200 if ( (H>=0) && (Fl::event_y()>y()+H) )
201 return ret;
202 else if ( (H<0) && (Fl::event_y() < y()+h()+H) )
203 return ret;
204 else {
205 n = which(Fl::event_x(), Fl::event_y());
206 if (!n) n = this;
208 if (n!=o)
209 Fl_Tooltip::enter(n);
210 return ret; }
211 case FL_FOCUS:
212 case FL_UNFOCUS:
213 if (!Fl::visible_focus()) return Fl_Group::handle(event);
214 if (Fl::event() == FL_RELEASE ||
215 Fl::event() == FL_SHORTCUT ||
216 Fl::event() == FL_KEYBOARD ||
217 Fl::event() == FL_FOCUS ||
218 Fl::event() == FL_UNFOCUS) {
219 redraw_tabs();
220 if (Fl::event() == FL_FOCUS) return Fl_Group::handle(event);
221 if (Fl::event() == FL_UNFOCUS) return 0;
222 else return 1;
223 } else return Fl_Group::handle(event);
224 case FL_KEYBOARD:
225 switch (Fl::event_key()) {
226 case FL_Left:
227 if (child(0)->visible()) return 0;
228 for (i = 1; i < children(); i ++)
229 if (child(i)->visible()) break;
230 value(child(i - 1));
231 set_changed();
232 do_callback();
233 return 1;
234 case FL_Right:
235 if (child(children() - 1)->visible()) return 0;
236 for (i = 0; i < children(); i ++)
237 if (child(i)->visible()) break;
238 value(child(i + 1));
239 set_changed();
240 do_callback();
241 return 1;
242 case FL_Down:
243 redraw();
244 return Fl_Group::handle(FL_FOCUS);
245 default:
246 break;
248 return Fl_Group::handle(event);
249 case FL_SHORTCUT:
250 for (i = 0; i < children(); ++i) {
251 Fl_Widget *c = child(i);
252 if (c->test_shortcut(c->label())) {
253 char sc = !c->visible();
254 value(c);
255 if (sc) set_changed();
256 do_callback();
257 return 1;
260 return Fl_Group::handle(event);
261 case FL_SHOW:
262 value(); // update visibilities and fall through
263 default:
264 return Fl_Group::handle(event);
269 int Fl_Tabs::push(Fl_Widget *o) {
270 if (push_ == o) return 0;
271 if ( (push_ && !push_->visible()) || (o && !o->visible()) )
272 redraw_tabs();
273 push_ = o;
274 return 1;
277 /**
278 Gets the currently visible widget/tab.
279 The value() is the first visible child (or the last child if none
280 are visible) and this also hides any other children.
281 This allows the tabs to be deleted, moved to other groups, and
282 show()/hide() called without it screwing up.
284 Fl_Widget* Fl_Tabs::value() {
285 Fl_Widget* v = 0;
286 Fl_Widget*const* a = array();
287 for (int i=children(); i--;) {
288 Fl_Widget* o = *a++;
289 if (v) o->hide();
290 else if (o->visible()) v = o;
291 else if (!i) {o->show(); v = o;}
293 return v;
297 Sets the widget to become the current visible widget/tab.
298 Setting the value hides all other children, and makes this one
299 visible, if it is really a child.
301 int Fl_Tabs::value(Fl_Widget *newvalue) {
302 Fl_Widget*const* a = array();
303 int ret = 0;
304 for (int i=children(); i--;) {
305 Fl_Widget* o = *a++;
306 if (o == newvalue) {
307 if (!o->visible()) ret = 1;
308 o->show();
309 } else {
310 o->hide();
313 return ret;
316 enum {LEFT, RIGHT, SELECTED};
318 void Fl_Tabs::draw() {
319 Fl_Widget *v = value();
320 int H = tab_height();
322 if (damage() & FL_DAMAGE_ALL) { // redraw the entire thing:
323 Fl_Color c = v ? v->color() : color();
325 draw_box(box(), x(), y()+(H>=0?H:0), w(), h()-(H>=0?H:-H), c);
327 if (selection_color() != c) {
328 // Draw the top or bottom SELECTION_BORDER lines of the tab pane in the
329 // selection color so that the user knows which tab is selected...
330 int clip_y = (H >= 0) ? y() + H : y() + h() + H - SELECTION_BORDER;
331 fl_push_clip(x(), clip_y, w(), SELECTION_BORDER);
332 draw_box(box(), x(), clip_y, w(), SELECTION_BORDER, selection_color());
333 fl_pop_clip();
335 if (v) draw_child(*v);
336 } else { // redraw the child
337 if (v) update_child(*v);
339 if (damage() & (FL_DAMAGE_SCROLL|FL_DAMAGE_ALL)) {
340 int nc = children();
341 int selected = tab_positions();
342 int i;
343 Fl_Widget*const* a = array();
344 for (i=0; i<selected; i++)
345 draw_tab(x()+tab_pos[i], x()+tab_pos[i+1],
346 tab_width[i], H, a[i], LEFT);
347 for (i=nc-1; i > selected; i--)
348 draw_tab(x()+tab_pos[i], x()+tab_pos[i+1],
349 tab_width[i], H, a[i], RIGHT);
350 if (v) {
351 i = selected;
352 draw_tab(x()+tab_pos[i], x()+tab_pos[i+1],
353 tab_width[i], H, a[i], SELECTED);
358 void Fl_Tabs::draw_tab(int x1, int x2, int W, int H, Fl_Widget* o, int what) {
359 int sel = (what == SELECTED);
360 int dh = Fl::box_dh(box());
361 int dy = Fl::box_dy(box());
362 char prev_draw_shortcut = fl_draw_shortcut;
363 fl_draw_shortcut = 1;
365 Fl_Boxtype bt = (o==push_ &&!sel) ? fl_down(box()) : box();
367 // compute offsets to make selected tab look bigger
368 int yofs = sel ? 0 : BORDER;
370 if ((x2 < x1+W) && what == RIGHT) x1 = x2 - W;
372 if (H >= 0) {
373 if (sel) fl_push_clip(x1, y(), x2 - x1, H + dh - dy);
374 else fl_push_clip(x1, y(), x2 - x1, H);
376 H += dh;
378 Fl_Color c = sel ? selection_color() : o->selection_color();
380 draw_box(bt, x1, y() + yofs, W, H + 10 - yofs, c);
382 // Save the previous label color
383 Fl_Color oc = o->labelcolor();
385 // Draw the label using the current color...
386 o->labelcolor(sel ? labelcolor() : o->labelcolor());
387 o->draw_label(x1, y() + yofs, W, H - yofs, FL_ALIGN_CENTER);
389 // Restore the original label color...
390 o->labelcolor(oc);
392 if (Fl::focus() == this && o->visible())
393 draw_focus(box(), x1, y(), W, H);
395 fl_pop_clip();
396 } else {
397 H = -H;
399 if (sel) fl_push_clip(x1, y() + h() - H - dy, x2 - x1, H + dy);
400 else fl_push_clip(x1, y() + h() - H, x2 - x1, H);
402 H += dh;
404 Fl_Color c = sel ? selection_color() : o->selection_color();
406 draw_box(bt, x1, y() + h() - H - 10, W, H + 10 - yofs, c);
408 // Save the previous label color
409 Fl_Color oc = o->labelcolor();
411 // Draw the label using the current color...
412 o->labelcolor(sel ? labelcolor() : o->labelcolor());
413 o->draw_label(x1, y() + h() - H, W, H - yofs, FL_ALIGN_CENTER);
415 // Restore the original label color...
416 o->labelcolor(oc);
418 if (Fl::focus() == this && o->visible())
419 draw_focus(box(), x1, y() + h() - H, W, H);
421 fl_pop_clip();
423 fl_draw_shortcut = prev_draw_shortcut;
427 Creates a new Fl_Tabs widget using the given position, size,
428 and label string. The default boxtype is FL_THIN_UP_BOX.
430 Use add(Fl_Widget*) to add each child, which are usually
431 Fl_Group widgets. The children should be sized to stay
432 away from the top or bottom edge of the Fl_Tabs widget,
433 which is where the tabs will be drawn.
435 All children of Fl_Tabs should have the same size and exactly fit on top of
436 each other. They should only leave space above or below where that tabs will
437 go, but not on the sides. If the first child of Fl_Tabs is set to
438 "resizable()", the riders will not resize when the tabs are resized.
440 The destructor <I>also deletes all the children</I>. This
441 allows a whole tree to be deleted at once, without having to
442 keep a pointer to all the children in the user code. A kludge
443 has been done so the Fl_Tabs and all of its children
444 can be automatic (local) variables, but you must declare the
445 Fl_Tabs widget <I>first</I> so that it is destroyed last.
447 Fl_Tabs::Fl_Tabs(int X,int Y,int W, int H, const char *l) :
448 Fl_Group(X,Y,W,H,l)
450 box(FL_THIN_UP_BOX);
451 push_ = 0;
452 tab_pos = 0;
453 tab_width = 0;
454 tab_count = 0;
457 Fl_Tabs::~Fl_Tabs() {
458 clear_tab_positions();
462 Returns the position and size available to be used by its children.
464 If there isn't any child yet the \p tabh parameter will be used to
465 calculate the return values. This assumes that the children's labelsize
466 is the same as the Fl_Tabs' labelsize and adds a small border.
468 If there are already children, the values of child(0) are returned, and
469 \p tabh is ignored.
471 \note Children should always use the same positions and sizes.
473 \p tabh can be one of
474 \li 0: calculate label size, tabs on top
475 \li -1: calculate label size, tabs on bottom
476 \li > 0: use given \p tabh value, tabs on top (height = tabh)
477 \li < -1: use given \p tabh value, tabs on bottom (height = -tabh)
479 \param[in] tabh position and optional height of tabs (see above)
480 \param[out] rx,ry,rw,rh (x,y,w,h) of client area for children
482 \since FLTK 1.3.0
484 void Fl_Tabs::client_area(int &rx, int &ry, int &rw, int &rh, int tabh) {
486 if (children()) { // use existing values
488 rx = child(0)->x();
489 ry = child(0)->y();
490 rw = child(0)->w();
491 rh = child(0)->h();
493 } else { // calculate values
495 int y_offset;
496 int label_height = fl_height(labelfont(), labelsize()) + BORDER*2;
498 if (tabh == 0) // use default (at top)
499 y_offset = label_height;
500 else if (tabh == -1) // use default (at bottom)
501 y_offset = -label_height;
502 else
503 y_offset = tabh; // user given value
505 rx = x();
506 rw = w();
508 if (y_offset >= 0) { // labels at top
509 ry = y() + y_offset;
510 rh = h() - y_offset;
511 } else { // labels at bottom
512 ry = y();
513 rh = h() + y_offset;
518 void Fl_Tabs::clear_tab_positions() {
519 if (tab_pos) {
520 free(tab_pos);
521 tab_pos = 0;
523 if (tab_width){
524 free(tab_width);
525 tab_width = 0;
530 // End of "$Id: Fl_Tabs.cxx 8658 2011-05-12 15:53:59Z manolo $".