summaryrefslogtreecommitdiff
path: root/src/Fl_Tabs.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'src/Fl_Tabs.cxx')
-rw-r--r--src/Fl_Tabs.cxx397
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();
}
+