diff options
Diffstat (limited to 'src/Fl_Tabs.cxx')
| -rw-r--r-- | src/Fl_Tabs.cxx | 397 |
1 files changed, 341 insertions, 56 deletions
diff --git a/src/Fl_Tabs.cxx b/src/Fl_Tabs.cxx index 9b626456b..230b9edc4 100644 --- a/src/Fl_Tabs.cxx +++ b/src/Fl_Tabs.cxx @@ -1,7 +1,7 @@ // // Tab widget for the Fast Light Tool Kit (FLTK). // -// Copyright 1998-2021 by Bill Spitzak and others. +// Copyright 1998-2023 by Bill Spitzak and others. // // This library is free software. Distribution and use rights are outlined in // the file "COPYING" which should have been included with this file. If this @@ -24,6 +24,7 @@ #include <FL/Fl_Tabs.H> #include <FL/fl_draw.H> #include <FL/Fl_Tooltip.H> +#include <FL/Fl_Menu_Item.H> #include <stdio.h> #include <stdlib.h> @@ -31,6 +32,41 @@ #define BORDER 2 #define EXTRASPACE 10 #define SELECTION_BORDER 5 +#define EXTRAGAP 2 + + +/** Make sure that we redraw all tabs when new children are added. */ +int Fl_Tabs::on_insert(Fl_Widget* candidate, int index) { + redraw_tabs(); + damage(FL_DAMAGE_EXPOSE); + return Fl_Group::on_insert(candidate, index); +} + +/** Make sure that we redraw all tabs when children are moved. */ +int Fl_Tabs::on_move(int a, int b) { + redraw_tabs(); + damage(FL_DAMAGE_EXPOSE); + return Fl_Group::on_move(a, b); +} + +/** Make sure that we redraw all tabs when new children are removed. */ +void Fl_Tabs::on_remove(int index) { + redraw_tabs(); + damage(FL_DAMAGE_EXPOSE|FL_DAMAGE_ALL); + if (child(index)->visible()) { + if (index+1<children()) + value(child(index+1)); + else if (index>0) + value(child(index-1)); + } + Fl_Group::on_remove(index); +} + +/** Make sure that we redraw all tabs when the widget size changes. */ +void Fl_Tabs::resize(int X, int Y, int W, int H) { + damage(FL_DAMAGE_EXPOSE); + Fl_Group::resize(X, Y, W, H); +} // Return the left edges of each tab (plus a fake left edge for a tab // past the right-hand one). These positions are actually of the left @@ -47,6 +83,7 @@ int Fl_Tabs::tab_positions() { if (nc) { tab_pos = (int*)malloc((nc+1)*sizeof(int)); tab_width = (int*)malloc((nc)*sizeof(int)); + tab_flags = (int*)malloc((nc)*sizeof(int)); } tab_count = nc; } @@ -73,33 +110,45 @@ int Fl_Tabs::tab_positions() { o->labeltype(ot); o->align(oa); + if (o->when() & FL_WHEN_CLOSED) + wt += labelsize()/2 + EXTRAGAP; + tab_width[i] = wt + EXTRASPACE; tab_pos[i+1] = tab_pos[i] + tab_width[i] + BORDER; + tab_flags[i] = 0; } fl_draw_shortcut = prev_draw_shortcut; int r = w(); if (tab_pos[i] <= r) return selected; - // uh oh, they are too big: - // pack them against right edge: - tab_pos[i] = r; - for (i = nc; i--;) { - int l = r-tab_width[i]; - if (tab_pos[i+1] < l) l = tab_pos[i+1]; - if (tab_pos[i] <= l) break; - tab_pos[i] = l; - r -= EXTRASPACE; - } - // pack them against left edge and truncate width if they still don't fit: - for (i = 0; i<nc; i++) { - if (tab_pos[i] >= i*EXTRASPACE) break; - tab_pos[i] = i*EXTRASPACE; - int W = w()-1-EXTRASPACE*(nc-i) - tab_pos[i]; - if (tab_width[i] > W) tab_width[i] = W; - } - // adjust edges according to visiblity: - for (i = nc; i > selected; i--) { - tab_pos[i] = tab_pos[i-1] + tab_width[i-1]; + + if (overflow_type == OVERFLOW_COMPRESS) { + // uh oh, they are too big: + // pack them against right edge: + tab_pos[i] = r; + for (i = nc; i--;) { + int l = r-tab_width[i]; + if (tab_pos[i+1] < l) l = tab_pos[i+1]; + if (tab_pos[i] <= l) break; + tab_pos[i] = l; + tab_flags[i] |= 1; + r -= EXTRASPACE; + } + // pack them against left edge and truncate width if they still don't fit: + for (i = 0; i<nc; i++) { + if (tab_pos[i] >= i*EXTRASPACE) break; + tab_pos[i] = i*EXTRASPACE; + tab_flags[i] |= 1; + int W = w()-1-EXTRASPACE*(nc-i) - tab_pos[i]; + if (tab_width[i] > W) tab_width[i] = W; + } + // adjust edges according to visiblity: + for (i = nc; i > selected; i--) { + tab_pos[i] = tab_pos[i-1] + tab_width[i-1]; + } + if ((selected > 0) && (tab_pos[selected-1]+tab_width[selected-1]>tab_pos[selected])) + tab_flags[selected] |= 1; + tab_flags[selected] &= ~1; } return selected; } @@ -121,12 +170,15 @@ int Fl_Tabs::tab_height() { else return (H <= 0) ? 0 : H; } -/** - Return the widget of the tab the user clicked on at \p event_x / \p event_y. - This is used for event handling (clicks) and by fluid to pick tabs. +/** Return a pointer to the child widget with a tab at the given coordinates. + + The Fl_Tabs::which() method returns a pointer to the child widget of the + Fl_Tabs container that corresponds to the tab at the given event coordinates. + If the event coordinates are outside the area of the tabs or if the Fl_Tabs + container has no children, the method returns NULL. - \returns The child widget of the tab the user clicked on, or<br> - 0 if there are no children or if the event is outside of the tabs area. + \param event_x, event_y event coordinates + \returns pointer to the selected child widget, or NULL */ Fl_Widget *Fl_Tabs::which(int event_x, int event_y) { if (children() == 0) return 0; @@ -141,7 +193,7 @@ Fl_Widget *Fl_Tabs::which(int event_x, int event_y) { const int nc = children(); tab_positions(); for (int i=0; i<nc; i++) { - if (event_x < x()+tab_pos[i+1]) { + if (event_x < x()+tab_pos[i+1]+tab_offset) { ret = child(i); break; } @@ -149,6 +201,92 @@ Fl_Widget *Fl_Tabs::which(int event_x, int event_y) { return ret; } +/** Check whether the coordinates fall within the "close" button area of the tab. + + The Fl_Tabs::hit_close() method checks whether the given event coordinates + fall within the area of the "close" button on the tab of the specified + child widget. This method should be called after the Fl_Tabs::which() method, + which updates a lookup table used to determine the width of each tab. + + \param o check the tab of this widget + \param event_x, event_y event coordinatese + \return 1 if we hit the close button, and 0 otherwie + */ +int Fl_Tabs::hit_close(Fl_Widget *o, int event_x, int event_y) { + (void)event_y; + for (int i=0; i<children(); i++) { + if (child(i)==o) { + // never hit the "close" button on a compressed tab unless it's the active one + if (tab_flags[i] & 1) + return 0; + // did we hit the area of teh "x"? + int tab_x = tab_pos[i] + tab_offset + x(); + return ( (event_x >= tab_x) + && (event_x < tab_x + (labelsize()+EXTRASPACE+EXTRAGAP)/2) ); + } + } + return 0; +} + +void Fl_Tabs::check_overflow_menu() { + int nc = children(); + int H = tab_height(); if (H < 0) H = -H; + if (tab_pos[nc] > w()-H) { + has_overflow_menu = 1; + } else { + has_overflow_menu = 0; + } +} + +void Fl_Tabs::handle_overflow_menu() { + int nc = children(); + int H = tab_height(); if (H < 0) H = -H; + int vc; // number of visible children + + // count visibel children + for (vc = 0; vc < nc; vc++) { + if (tab_pos[vc+1] > w()-H) break; + } + if (vc == nc) return; // children are visible + + // create a menu with invisible children + int i, n = nc - vc; + overflow_menu = new Fl_Menu_Item[n+1]; + memset(overflow_menu, 0, sizeof(Fl_Menu_Item)*(n+1)); + for (i = 0; i < n; i++) { + overflow_menu[i].label(child(vc+i)->label()); + overflow_menu[i].user_data(child(vc+i)); + } + overflow_menu[i].label(NULL); + + // show the menu and handle the selection + const Fl_Menu_Item *m = overflow_menu->popup(x()+w()-H, (tab_height()>0)?(y()+H):(y()+h())); + if (m) + value((Fl_Widget*)m->user_data()); + + // delete the menu until we need it next time + if (overflow_menu) { + delete[] overflow_menu; + overflow_menu = NULL; + } +} + +void Fl_Tabs::draw_overflow_menu_button() { + int H = tab_height(); + int X, Y; + if (H > 0) { + X = x() + w() - H; + Y = y(); + } else { + H = -H; + X = x() + w() - H; + Y = y() + h() - H; + } + fl_draw_box(box(), X, Y, H, H, color()); + Fl_Rect r(X, Y, H, H); + fl_draw_arrow(r, FL_ARROW_CHOICE, FL_ORIENT_NONE, fl_contrast(FL_BLACK, color())); +} + void Fl_Tabs::redraw_tabs() { int H = tab_height(); @@ -162,31 +300,73 @@ void Fl_Tabs::redraw_tabs() } int Fl_Tabs::handle(int event) { - + static int initial_x = 0; + static int initial_tab_offset = 0; + static int forward_motion_to_group = 0; Fl_Widget *o; int i; switch (event) { case FL_PUSH: + initial_x = Fl::event_x(); + initial_tab_offset = tab_offset; + forward_motion_to_group = 0; { int H = tab_height(); if (H >= 0) { - if (Fl::event_y() > y()+H) return Fl_Group::handle(event); + if (Fl::event_y() > y()+H) { + forward_motion_to_group = 1; + return Fl_Group::handle(event); + } } else { - if (Fl::event_y() < y()+h()+H) return Fl_Group::handle(event); + if (Fl::event_y() < y()+h()+H) { + forward_motion_to_group = 1; + return Fl_Group::handle(event); + } + H = - H; + } + if (has_overflow_menu && Fl::event_x() > x()+w()-H) { + handle_overflow_menu(); + return 1; } } /* FALLTHROUGH */ case FL_DRAG: case FL_RELEASE: + if (forward_motion_to_group) { + return Fl_Group::handle(event); + } o = which(Fl::event_x(), Fl::event_y()); + if (overflow_type == OVERFLOW_DRAG) { + if (tab_pos[children()] < w() && tab_offset == 0) { + // fall through + } else if (!Fl::event_is_click()) { + tab_offset = initial_tab_offset + Fl::event_x() - initial_x; + if (tab_offset > 0) { + initial_tab_offset -= tab_offset; + tab_offset = 0; + } else { + int dw = tab_pos[children()] + tab_offset - w(); + if (dw < -20) { + initial_tab_offset -= dw+20; + tab_offset -= dw+20; + } + } + damage(FL_DAMAGE_EXPOSE|FL_DAMAGE_SCROLL); + return 1; + } + } if (event == FL_RELEASE) { push(0); if (o && Fl::visible_focus() && Fl::focus()!=this) { Fl::focus(this); redraw_tabs(); } + if (o && (o->when() & FL_WHEN_CLOSED) && hit_close(o, Fl::event_x(), Fl::event_y())) { + o->do_callback(FL_REASON_CLOSED); + return 1; // o may be deleted at this point + } if (o && // Released on a tab and.. (value(o) || // tab changed value or.. (when()&(FL_WHEN_NOT_CHANGED)) // ..no change but WHEN_NOT_CHANGED set, @@ -194,7 +374,7 @@ int Fl_Tabs::handle(int event) { ) { Fl_Widget_Tracker wp(o); set_changed(); - do_callback(); + do_callback(FL_REASON_SELECTED); if (wp.deleted()) return 1; } Fl_Tooltip::current(o); @@ -240,16 +420,16 @@ int Fl_Tabs::handle(int event) { if (child(i)->visible()) break; value(child(i - 1)); set_changed(); - do_callback(); + do_callback(FL_REASON_SELECTED); return 1; case FL_Right: if (!children()) return 0; if (child(children() - 1)->visible()) return 0; - for (i = 0; i < children(); i ++) + for (i = 0; i < children()-1; i++) if (child(i)->visible()) break; value(child(i + 1)); set_changed(); - do_callback(); + do_callback(FL_REASON_SELECTED); return 1; case FL_Down: redraw(); @@ -264,8 +444,12 @@ int Fl_Tabs::handle(int event) { if (c->test_shortcut(c->label())) { char sc = !c->visible(); value(c); - if (sc) set_changed(); - do_callback(); + if (sc) { + set_changed(); + do_callback(FL_REASON_SELECTED); + } else { + do_callback(FL_REASON_RESELECTED); + } return 1; } } @@ -300,10 +484,15 @@ int Fl_Tabs::push(Fl_Widget *o) { /** Gets the currently visible widget/tab. - The value() is the first visible child (or the last child if none - are visible) and this also hides any other children. - This allows the tabs to be deleted, moved to other groups, and - show()/hide() called without it screwing up. + The Fl_Tabs::value() method returns a pointer to the currently visible child + widget of the Fl_Tabs container. The visible child is the first child that + is currently being displayed, or the last child if none of the children are + being displayed. + + If child widgets have been added, moved, or deleted, this method ensures that + only one tab is visible at a time. + + \return a pointer to the currently visible child */ Fl_Widget* Fl_Tabs::value() { Fl_Widget* v = 0; @@ -317,12 +506,17 @@ Fl_Widget* Fl_Tabs::value() { return v; } -/** - Sets the widget to become the current visible widget/tab. - Setting the value hides all other children, and makes this one - visible, if it is really a child. - \returns 1 if there was a change (new value different from previous),<BR> - 0 if there was no change (new value already set) +/** Sets the widget to become the current visible widget/tab. + + The Fl_Tabs::value() method allows you to set a particular child widget of + the Fl_Tabs container to be the currently visible widget. If the specified + widget is a child of the Fl_Tabs container, it will be made visible and all + other children will be hidden. The method returns 1 if the value was changed, + and 0 if the specified value was already set. + + \param[in] newvalue a poiner to a child widget + \return 1 if a different tab was chosen + \return 0 if there was no change (new value already set) */ int Fl_Tabs::value(Fl_Widget *newvalue) { Fl_Widget*const* a = array(); @@ -344,12 +538,18 @@ enum {LEFT, RIGHT, SELECTED}; void Fl_Tabs::draw() { Fl_Widget *v = value(); int H = tab_height(); + int ty, th; + if (H >= 0) { + ty = y(); th = H; + } else { + ty = y() + h() + H; th = -H; + } + Fl_Color c = v ? v->color() : color(); - if (damage() & FL_DAMAGE_ALL) { // redraw the entire thing: - Fl_Color c = v ? v->color() : color(); - + if (damage() & FL_DAMAGE_ALL) { // redraw the children draw_box(box(), x(), y()+(H>=0?H:0), w(), h()-(H>=0?H:-H), c); - + } + if (damage() & (FL_DAMAGE_SCROLL|FL_DAMAGE_ALL)) { if (selection_color() != c) { // Draw the top or bottom SELECTION_BORDER lines of the tab pane in the // selection color so that the user knows which tab is selected... @@ -358,33 +558,60 @@ void Fl_Tabs::draw() { draw_box(box(), x(), clip_y, w(), SELECTION_BORDER, selection_color()); fl_pop_clip(); } + } + if (damage() & FL_DAMAGE_ALL) { // redraw the children if (v) draw_child(*v); } else { // redraw the child if (v) update_child(*v); } + if (damage() & FL_DAMAGE_EXPOSE) { // redraw the tab bar background + if (parent()) { + Fl_Widget *p = parent(); + fl_push_clip(x(), ty, w(), th); + if (p->as_window()) + fl_draw_box(p->box(), 0, 0, p->w(), p->h(), p->color()); + else + fl_draw_box(p->box(), p->x(), p->y(), p->w(), p->h(), p->color()); + fl_pop_clip(); + } else { + fl_rectf(x(), ty, w(), th, color()); + } + } if (damage() & (FL_DAMAGE_SCROLL|FL_DAMAGE_ALL)) { const int nc = children(); int selected = tab_positions(); int i; + if (H>0) + fl_push_clip(x(), ty, w(), th+BORDER); + else + fl_push_clip(x(), ty-BORDER, w(), th+BORDER); Fl_Widget*const* a = array(); for (i=0; i<selected; i++) draw_tab(x()+tab_pos[i], x()+tab_pos[i+1], - tab_width[i], H, a[i], LEFT); + tab_width[i], H, a[i], tab_flags[i], LEFT); for (i=nc-1; i > selected; i--) draw_tab(x()+tab_pos[i], x()+tab_pos[i+1], - tab_width[i], H, a[i], RIGHT); + tab_width[i], H, a[i], tab_flags[i], RIGHT); if (v) { i = selected; draw_tab(x()+tab_pos[i], x()+tab_pos[i+1], - tab_width[i], H, a[i], SELECTED); + tab_width[i], H, a[i], tab_flags[i], SELECTED); } + if (overflow_type == OVERFLOW_PULLDOWN) + check_overflow_menu(); + if (has_overflow_menu) + draw_overflow_menu_button(); + fl_pop_clip(); } } -void Fl_Tabs::draw_tab(int x1, int x2, int W, int H, Fl_Widget* o, int what) { +void Fl_Tabs::draw_tab(int x1, int x2, int W, int H, Fl_Widget* o, int flags, int what) { + x1 += tab_offset; + x2 += tab_offset; int sel = (what == SELECTED); int dh = Fl::box_dh(box()); int dy = Fl::box_dy(box()); + int wc = 0; // width of "close" button if drawn, or 0 char prev_draw_shortcut = fl_draw_shortcut; fl_draw_shortcut = 1; @@ -414,8 +641,18 @@ void Fl_Tabs::draw_tab(int x1, int x2, int W, int H, Fl_Widget* o, int what) { // Draw the label using the current color... o->labelcolor(sel ? labelcolor() : o->labelcolor()); - o->draw_label(x1, y() + yofs, W, H - yofs, tab_align()); + // Draw the "close" button if requested + if ( (o->when() & FL_WHEN_CLOSED) && !(flags & 1) ) { + int sz = labelsize()/2, sy = (H - sz)/2; + fl_draw_symbol("@3+", x1 + EXTRASPACE/2, y() + yofs/2 + sy, sz, sz, o->labelcolor()); + wc = sz + EXTRAGAP; + } + + // Draw the label text + o->draw_label(x1 + wc, y() + yofs, W - wc, H - yofs, tab_align()); + + // Draw the focus box if (Fl::focus() == this && o->visible()) draw_focus(bt, x1, y(), W, H, bc); @@ -432,8 +669,18 @@ void Fl_Tabs::draw_tab(int x1, int x2, int W, int H, Fl_Widget* o, int what) { // Draw the label using the current color... o->labelcolor(sel ? labelcolor() : o->labelcolor()); - o->draw_label(x1, y() + h() - H, W, H - yofs, tab_align()); + // Draw the "close" button if requested + if ( (o->when() & FL_WHEN_CLOSED) && (x1+W < x2) ) { + int sz = labelsize()/2, sy = (H - sz)/2; + fl_draw_symbol("@3+", x1 + EXTRASPACE/2, y() + h() - H -yofs/2 + sy, sz, sz, o->labelcolor()); + wc = sz + EXTRAGAP; + } + + // Draw the label text + o->draw_label(x1 + wc, y() + h() - H, W - wc, H - yofs, tab_align()); + + // Draw the focus box if (Fl::focus() == this && o->visible()) draw_focus(bt, x1, y() + h() - H, W, H, bc); @@ -472,14 +719,21 @@ Fl_Tabs::Fl_Tabs(int X, int Y, int W, int H, const char *L) : { box(FL_THIN_UP_BOX); push_ = 0; + overflow_type = OVERFLOW_COMPRESS; + tab_offset = 0; tab_pos = 0; tab_width = 0; + tab_flags = NULL; tab_count = 0; tab_align_ = FL_ALIGN_CENTER; + has_overflow_menu = 0; + overflow_menu = NULL; } Fl_Tabs::~Fl_Tabs() { clear_tab_positions(); + if (overflow_menu) + delete[] overflow_menu; } /** @@ -548,4 +802,35 @@ void Fl_Tabs::clear_tab_positions() { free(tab_width); tab_width = 0; } + if (tab_flags){ + free(tab_flags); + tab_flags = NULL; + } +} + +/** Set a method to handle an overflowing tab bar. + + The Fl_Tabs widget allows you to specify how to handle the situation where + there are more tabs than can be displayed at once. The available options are: + + - \c OVERFLOW_COMPRESS: Tabs will be compressed and overlaid on top of each other. + - \c OVERFLOW_CLIP: Only the first tabs that fit will be displayed. + - \c OVERFLOW_PULLDOWN: Tabs that do not fit will be placed in a pull-down menu. + - \c OVERFLOW_DRAG: The tab bar can be dragged horizontally to reveal additional tabs. + + You can set the desired behavior using the overflow() method. + + \param ov overflow type + */ +void Fl_Tabs::handle_overflow(int ov) { + overflow_type = ov; + tab_offset = 0; + has_overflow_menu = 0; + if (overflow_menu) { + delete[] overflow_menu; + overflow_menu = NULL; + } + damage(FL_DAMAGE_EXPOSE|FL_DAMAGE_ALL); + redraw(); } + |
