DRC | 2ff39b8 | 2011-07-28 08:38:59 +0000 | [diff] [blame] | 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. |
| 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 | |
| 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. |
| 31 | |
| 32 | // Each child widget is a card, and its label() is printed on the card tab. |
| 33 | // Clicking the tab makes that card visible. |
| 34 | |
| 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> |
| 40 | |
| 41 | #define BORDER 2 |
| 42 | #define EXTRASPACE 10 |
| 43 | #define SELECTION_BORDER 5 |
| 44 | |
| 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. |
| 52 | |
| 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)); |
| 60 | } |
| 61 | tab_count = nc; |
| 62 | } |
| 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; |
| 69 | |
| 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; |
| 74 | |
| 75 | int wt = 0; int ht = 0; |
| 76 | o->measure_label(wt,ht); |
| 77 | |
| 78 | tab_width[i] = wt + EXTRASPACE; |
| 79 | tab_pos[i+1] = tab_pos[i] + tab_width[i] + BORDER; |
| 80 | } |
| 81 | fl_draw_shortcut = prev_draw_shortcut; |
| 82 | |
| 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; |
| 94 | } |
| 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; |
| 101 | } |
| 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]; |
| 105 | } |
| 106 | return selected; |
| 107 | } |
| 108 | |
| 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(); |
| 120 | } |
| 121 | H2 = y()+h()-H2; |
| 122 | if (H2 > H) return (H2 <= 0) ? 0 : -H2; |
| 123 | else return (H <= 0) ? 0 : H; |
| 124 | } |
| 125 | |
| 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; |
| 135 | } |
| 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; |
| 144 | } |
| 145 | } |
| 146 | return ret; |
| 147 | } |
| 148 | |
| 149 | void Fl_Tabs::redraw_tabs() |
| 150 | { |
| 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); |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | int Fl_Tabs::handle(int event) { |
| 162 | |
| 163 | Fl_Widget *o; |
| 164 | int i; |
| 165 | |
| 166 | switch (event) { |
| 167 | |
| 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); |
| 174 | }} |
| 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(); |
| 184 | } |
| 185 | if (o && value(o)) { |
| 186 | Fl_Widget_Tracker wp(o); |
| 187 | set_changed(); |
| 188 | do_callback(); |
| 189 | if (wp.deleted()) return 1; |
| 190 | } |
| 191 | Fl_Tooltip::current(o); |
| 192 | } else { |
| 193 | push(o); |
| 194 | } |
| 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; |
| 207 | } |
| 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; |
| 247 | } |
| 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; |
| 258 | } |
| 259 | } |
| 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); |
| 265 | |
| 266 | } |
| 267 | } |
| 268 | |
| 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; |
| 275 | } |
| 276 | |
| 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. |
| 283 | */ |
| 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;} |
| 292 | } |
| 293 | return v; |
| 294 | } |
| 295 | |
| 296 | /** |
| 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. |
| 300 | */ |
| 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(); |
| 311 | } |
| 312 | } |
| 313 | return ret; |
| 314 | } |
| 315 | |
| 316 | enum {LEFT, RIGHT, SELECTED}; |
| 317 | |
| 318 | void Fl_Tabs::draw() { |
| 319 | Fl_Widget *v = value(); |
| 320 | int H = tab_height(); |
| 321 | |
| 322 | if (damage() & FL_DAMAGE_ALL) { // redraw the entire thing: |
| 323 | Fl_Color c = v ? v->color() : color(); |
| 324 | |
| 325 | draw_box(box(), x(), y()+(H>=0?H:0), w(), h()-(H>=0?H:-H), c); |
| 326 | |
| 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(); |
| 334 | } |
| 335 | if (v) draw_child(*v); |
| 336 | } else { // redraw the child |
| 337 | if (v) update_child(*v); |
| 338 | } |
| 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); |
| 354 | } |
| 355 | } |
| 356 | } |
| 357 | |
| 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; |
| 364 | |
| 365 | Fl_Boxtype bt = (o==push_ &&!sel) ? fl_down(box()) : box(); |
| 366 | |
| 367 | // compute offsets to make selected tab look bigger |
| 368 | int yofs = sel ? 0 : BORDER; |
| 369 | |
| 370 | if ((x2 < x1+W) && what == RIGHT) x1 = x2 - W; |
| 371 | |
| 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); |
| 375 | |
| 376 | H += dh; |
| 377 | |
| 378 | Fl_Color c = sel ? selection_color() : o->selection_color(); |
| 379 | |
| 380 | draw_box(bt, x1, y() + yofs, W, H + 10 - yofs, c); |
| 381 | |
| 382 | // Save the previous label color |
| 383 | Fl_Color oc = o->labelcolor(); |
| 384 | |
| 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); |
| 388 | |
| 389 | // Restore the original label color... |
| 390 | o->labelcolor(oc); |
| 391 | |
| 392 | if (Fl::focus() == this && o->visible()) |
| 393 | draw_focus(box(), x1, y(), W, H); |
| 394 | |
| 395 | fl_pop_clip(); |
| 396 | } else { |
| 397 | H = -H; |
| 398 | |
| 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); |
| 401 | |
| 402 | H += dh; |
| 403 | |
| 404 | Fl_Color c = sel ? selection_color() : o->selection_color(); |
| 405 | |
| 406 | draw_box(bt, x1, y() + h() - H - 10, W, H + 10 - yofs, c); |
| 407 | |
| 408 | // Save the previous label color |
| 409 | Fl_Color oc = o->labelcolor(); |
| 410 | |
| 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); |
| 414 | |
| 415 | // Restore the original label color... |
| 416 | o->labelcolor(oc); |
| 417 | |
| 418 | if (Fl::focus() == this && o->visible()) |
| 419 | draw_focus(box(), x1, y() + h() - H, W, H); |
| 420 | |
| 421 | fl_pop_clip(); |
| 422 | } |
| 423 | fl_draw_shortcut = prev_draw_shortcut; |
| 424 | } |
| 425 | |
| 426 | /** |
| 427 | Creates a new Fl_Tabs widget using the given position, size, |
| 428 | and label string. The default boxtype is FL_THIN_UP_BOX. |
| 429 | |
| 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. |
| 434 | |
| 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. |
| 439 | |
| 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. |
| 446 | */ |
| 447 | Fl_Tabs::Fl_Tabs(int X,int Y,int W, int H, const char *l) : |
| 448 | Fl_Group(X,Y,W,H,l) |
| 449 | { |
| 450 | box(FL_THIN_UP_BOX); |
| 451 | push_ = 0; |
| 452 | tab_pos = 0; |
| 453 | tab_width = 0; |
| 454 | tab_count = 0; |
| 455 | } |
| 456 | |
| 457 | Fl_Tabs::~Fl_Tabs() { |
| 458 | clear_tab_positions(); |
| 459 | } |
| 460 | |
| 461 | /** |
| 462 | Returns the position and size available to be used by its children. |
| 463 | |
| 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. |
| 467 | |
| 468 | If there are already children, the values of child(0) are returned, and |
| 469 | \p tabh is ignored. |
| 470 | |
| 471 | \note Children should always use the same positions and sizes. |
| 472 | |
| 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) |
| 478 | |
| 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 |
| 481 | |
| 482 | \since FLTK 1.3.0 |
| 483 | */ |
| 484 | void Fl_Tabs::client_area(int &rx, int &ry, int &rw, int &rh, int tabh) { |
| 485 | |
| 486 | if (children()) { // use existing values |
| 487 | |
| 488 | rx = child(0)->x(); |
| 489 | ry = child(0)->y(); |
| 490 | rw = child(0)->w(); |
| 491 | rh = child(0)->h(); |
| 492 | |
| 493 | } else { // calculate values |
| 494 | |
| 495 | int y_offset; |
| 496 | int label_height = fl_height(labelfont(), labelsize()) + BORDER*2; |
| 497 | |
| 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 |
| 504 | |
| 505 | rx = x(); |
| 506 | rw = w(); |
| 507 | |
| 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; |
| 514 | } |
| 515 | } |
| 516 | } |
| 517 | |
| 518 | void Fl_Tabs::clear_tab_positions() { |
| 519 | if (tab_pos) { |
| 520 | free(tab_pos); |
| 521 | tab_pos = 0; |
| 522 | } |
| 523 | if (tab_width){ |
| 524 | free(tab_width); |
| 525 | tab_width = 0; |
| 526 | } |
| 527 | } |
| 528 | |
| 529 | // |
| 530 | // End of "$Id: Fl_Tabs.cxx 8658 2011-05-12 15:53:59Z manolo $". |
| 531 | // |