From 8826dca1066361b474139bcc5aeed2e3a5246ed0 Mon Sep 17 00:00:00 2001 From: Matthias Melcher Date: Thu, 5 Jan 2023 13:51:30 +0100 Subject: Add close buttons for individual tabs in Fl_Tabs (#628) Add close buttons for Fl_Tabs Introducing callback reasons FLUID shows all FL_WHEN_... options Adding Fl_Tabs overflow types Improved test/tabs to show new features --- src/Fl.cxx | 15 +- src/Fl_Browser_.cxx | 18 +-- src/Fl_Button.cxx | 19 ++- src/Fl_Color_Chooser.cxx | 14 +- src/Fl_File_Input.cxx | 2 +- src/Fl_Help_View.cxx | 2 +- src/Fl_Input.cxx | 2 +- src/Fl_Input_.cxx | 10 +- src/Fl_Input_Choice.cxx | 8 +- src/Fl_Positioner.cxx | 8 +- src/Fl_Repeat_Button.cxx | 4 +- src/Fl_Return_Button.cxx | 2 +- src/Fl_Scrollbar.cxx | 2 +- src/Fl_Spinner.cxx | 6 +- src/Fl_Tabs.cxx | 397 ++++++++++++++++++++++++++++++++++++++++------- src/Fl_Text_Editor.cxx | 25 +-- src/Fl_Tile.cxx | 8 +- src/Fl_Tree.cxx | 2 +- src/Fl_Valuator.cxx | 4 +- src/Fl_Value_Input.cxx | 2 +- src/Fl_Widget.cxx | 49 +++--- 21 files changed, 457 insertions(+), 142 deletions(-) (limited to 'src') diff --git a/src/Fl.cxx b/src/Fl.cxx index ee16805d9..e7ba9f04d 100644 --- a/src/Fl.cxx +++ b/src/Fl.cxx @@ -69,6 +69,7 @@ const char *Fl::e_clipboard_type = ""; void *Fl::e_clipboard_data = NULL; Fl_Event_Dispatch Fl::e_dispatch = 0; +Fl_Callback_Reason Fl::callback_reason_ = FL_REASON_UNKNOWN; unsigned char Fl::options_[] = { 0, 0 }; unsigned char Fl::options_read_ = 0; @@ -1173,6 +1174,14 @@ static int send_event(int event, Fl_Widget* to, Fl_Window* window) { return ret; } +/** + \brief Give the reason for calling a callback. + \return the reason for the current callback + \see Fl_Widget::when(), Fl_Widget::do_callback(), Fl_Widget::callback() + */ +Fl_Callback_Reason Fl::callback_reason() { + return callback_reason_; +} /** \brief Set a new event dispatch function. @@ -1258,6 +1267,8 @@ int Fl::handle(int e, Fl_Window* window) another dispatch function. In that case, the user dispatch function must decide when to call Fl::handle_(int, Fl_Window*) + Callbacks can set \p FL_REASON_CLOSED and \p FL_REASON_CANCELLED. + \param e the event type (Fl::event_number() is not yet set) \param window the window that caused this event \return 0 if the event was not handled @@ -1275,7 +1286,7 @@ int Fl::handle_(int e, Fl_Window* window) case FL_CLOSE: if ( grab() || (modal() && window != modal()) ) return 0; - wi->do_callback(); + wi->do_callback(FL_REASON_CLOSED); return 1; case FL_SHOW: @@ -1425,7 +1436,7 @@ int Fl::handle_(int e, Fl_Window* window) // make Escape key close windows: if (event_key()==FL_Escape) { wi = modal(); if (!wi) wi = window; - wi->do_callback(); + wi->do_callback(FL_REASON_CANCELLED); return 1; } diff --git a/src/Fl_Browser_.cxx b/src/Fl_Browser_.cxx index 11c0b40ba..29d3fada9 100644 --- a/src/Fl_Browser_.cxx +++ b/src/Fl_Browser_.cxx @@ -630,7 +630,7 @@ int Fl_Browser_::select(void* item, int val, int docallbacks) { } if (docallbacks) { set_changed(); - do_callback(); + do_callback(FL_REASON_CHANGED); } return 1; } @@ -733,7 +733,7 @@ int Fl_Browser_::handle(int event) { if (wp.deleted()) return 1; if (when() & FL_WHEN_ENTER_KEY) { set_changed(); - do_callback(); + do_callback(FL_REASON_CHANGED); } return 1; case ' ': @@ -807,7 +807,7 @@ J1: if (wp.deleted()) return 1; if (change && (when() & FL_WHEN_CHANGED)) { set_changed(); - do_callback(); + do_callback(FL_REASON_CHANGED); if (wp.deleted()) return 1; } } else { @@ -821,7 +821,7 @@ J1: if (wp.deleted()) return 1; if (change && (when() & FL_WHEN_CHANGED)) { set_changed(); - do_callback(); + do_callback(FL_REASON_CHANGED); if (wp.deleted()) return 1; } } @@ -859,7 +859,7 @@ J1: if (wp.deleted()) return 1; if (change && (when() & FL_WHEN_CHANGED)) { set_changed(); - do_callback(); + do_callback(FL_REASON_CHANGED); if (wp.deleted()) return 1; } } @@ -897,7 +897,7 @@ J1: change |= change_t; if (change_t && (when() & FL_WHEN_CHANGED)) { set_changed(); - do_callback(); + do_callback(FL_REASON_CHANGED); if (wp.deleted()) return 1; } } @@ -922,16 +922,16 @@ J1: } if (change) { set_changed(); - if (when() & FL_WHEN_RELEASE) do_callback(); + if (when() & FL_WHEN_RELEASE) do_callback(FL_REASON_CHANGED); } else { - if (when() & FL_WHEN_NOT_CHANGED) do_callback(); + if (when() & FL_WHEN_NOT_CHANGED) do_callback(FL_REASON_RESELECTED); } if (wp.deleted()) return 1; // double click calls the callback: (like Enter Key) if (Fl::event_clicks() && (when() & FL_WHEN_ENTER_KEY)) { set_changed(); - do_callback(); + do_callback(FL_REASON_CHANGED); } return 1; case FL_FOCUS: diff --git a/src/Fl_Button.cxx b/src/Fl_Button.cxx index 1651d08f5..dec92d483 100644 --- a/src/Fl_Button.cxx +++ b/src/Fl_Button.cxx @@ -100,12 +100,12 @@ int Fl_Button::handle(int event) { value_ = newval; set_changed(); redraw(); - if (when() & FL_WHEN_CHANGED) do_callback(); + if (when() & FL_WHEN_CHANGED) do_callback(FL_REASON_CHANGED); } return 1; case FL_RELEASE: if (value_ == oldval) { - if (when() & FL_WHEN_NOT_CHANGED) do_callback(); + if (when() & FL_WHEN_NOT_CHANGED) do_callback(FL_REASON_SELECTED); return 1; } set_changed(); @@ -116,11 +116,11 @@ int Fl_Button::handle(int event) { set_changed(); if (when() & FL_WHEN_CHANGED) { Fl_Widget_Tracker wp(this); - do_callback(); + do_callback(FL_REASON_CHANGED); if (wp.deleted()) return 1; } } - if (when() & FL_WHEN_RELEASE) do_callback(); + if (when() & FL_WHEN_RELEASE) do_callback(FL_REASON_RELEASED); return 1; case FL_SHORTCUT: if (!(shortcut() ? @@ -150,16 +150,16 @@ int Fl_Button::handle(int event) { if (type() == FL_RADIO_BUTTON) { if (!value_) { setonly(); - if (when() & FL_WHEN_CHANGED) do_callback(); + if (when() & FL_WHEN_CHANGED) do_callback(FL_REASON_CHANGED); } } else if (type() == FL_TOGGLE_BUTTON) { value(!value()); - if (when() & FL_WHEN_CHANGED) do_callback(); + if (when() & FL_WHEN_CHANGED) do_callback(FL_REASON_CHANGED); } else { simulate_key_action(); } if (wp.deleted()) return 1; - if (when() & FL_WHEN_RELEASE) do_callback(); + if (when() & FL_WHEN_RELEASE) do_callback(FL_REASON_RELEASED); return 1; } /* FALLTHROUGH */ @@ -206,6 +206,11 @@ void Fl_Button::key_release_timeout(void *d) Derived classes may handle this differently. + A button may reequest callbacks with \p whne() \p FL_WHEN_CHANGED, + \p FL_WHEN_NOT_CHANGED, and \p FL_WHEN_RELEASE, triggering the callback + reasons \p FL_REASON_CHANGED, \p FL_REASON_SELECTED, + and \p FL_REASON_DESELECTED. + \param[in] X, Y, W, H position and size of the widget \param[in] L widget label, default is no label */ diff --git a/src/Fl_Color_Chooser.cxx b/src/Fl_Color_Chooser.cxx index 665c72dc6..a8b7ac5c5 100644 --- a/src/Fl_Color_Chooser.cxx +++ b/src/Fl_Color_Chooser.cxx @@ -220,7 +220,7 @@ int Flcc_HueBox::handle(int e) { if (fabs(H-ih) < 3*6.0/w()) H = ih; if (fabs(S-is) < 3*1.0/h()) S = is; if (Fl::event_state(FL_CTRL)) H = ih; - if (c->hsv(H, S, c->value())) c->do_callback(); + if (c->hsv(H, S, c->value())) c->do_callback(FL_REASON_DRAGGED); } return 1; case FL_FOCUS : /* FALLTHROUGH */ case FL_UNFOCUS : @@ -292,7 +292,7 @@ int Flcc_HueBox::handle_key(int key) { Xf = (double)X/(double)w1; Yf = (double)Y/(double)h1; tohs(Xf, Yf, H, S); - if (c->hsv(H, S, c->value())) c->do_callback(); + if (c->hsv(H, S, c->value())) c->do_callback(FL_REASON_CHANGED); return 1; } @@ -347,7 +347,7 @@ int Flcc_ValueBox::handle(int e) { double Yf; Yf = 1-(Fl::event_y()-y()-Fl::box_dy(box()))/double(h()-Fl::box_dh(box())); if (fabs(Yf-iv)<(3*1.0/h())) Yf = iv; - if (c->hsv(c->hue(),c->saturation(),Yf)) c->do_callback(); + if (c->hsv(c->hue(),c->saturation(),Yf)) c->do_callback(FL_REASON_DRAGGED); } return 1; case FL_FOCUS : /* FALLTHROUGH */ case FL_UNFOCUS : @@ -418,7 +418,7 @@ int Flcc_ValueBox::handle_key(int key) { double Yf; Yf = 1-((double)Y/(double)h1); - if (c->hsv(c->hue(),c->saturation(),Yf)) c->do_callback(); + if (c->hsv(c->hue(),c->saturation(),Yf)) c->do_callback(FL_REASON_CHANGED); return 1; } @@ -432,7 +432,7 @@ void Fl_Color_Chooser::rgb_cb(Fl_Widget* o, void*) { double G = c->gvalue.value(); double B = c->bvalue.value(); if (c->mode() == M_HSV) { - if (c->hsv(R,G,B)) c->do_callback(); + if (c->hsv(R,G,B)) c->do_callback(FL_REASON_CHANGED); return; } if (c->mode() != M_RGB) { @@ -440,7 +440,7 @@ void Fl_Color_Chooser::rgb_cb(Fl_Widget* o, void*) { G = G/255; B = B/255; } - if (c->rgb(R,G,B)) c->do_callback(); + if (c->rgb(R,G,B)) c->do_callback(FL_REASON_CHANGED); } void Fl_Color_Chooser::mode_cb(Fl_Widget* o, void*) { @@ -455,7 +455,7 @@ void Fl_Color_Chooser::mode_cb(Fl_Widget* o, void*) { void Fl_Color_Chooser::mode(int newMode) { choice.value(newMode); - choice.do_callback(); + choice.do_callback(FL_REASON_RESELECTED); } // Small local helper function: diff --git a/src/Fl_File_Input.cxx b/src/Fl_File_Input.cxx index 75f40c98c..5aced4111 100644 --- a/src/Fl_File_Input.cxx +++ b/src/Fl_File_Input.cxx @@ -269,7 +269,7 @@ Fl_File_Input::handle_button(int event) // I - Event // Then do the callbacks, if necessary... set_changed(); - if (when() & (FL_WHEN_CHANGED|FL_WHEN_RELEASE) ) do_callback(); + if (when() & (FL_WHEN_CHANGED|FL_WHEN_RELEASE) ) do_callback(FL_REASON_CHANGED); } return 1; diff --git a/src/Fl_Help_View.cxx b/src/Fl_Help_View.cxx index 67ce82a36..92bf3f048 100644 --- a/src/Fl_Help_View.cxx +++ b/src/Fl_Help_View.cxx @@ -3553,7 +3553,7 @@ Fl_Help_View::topline(int top) // I - Top line number scrollbar_.value(topline_, h() - scrollsize, 0, size_); - do_callback(); + do_callback(FL_REASON_DRAGGED); redraw(); } diff --git a/src/Fl_Input.cxx b/src/Fl_Input.cxx index bc94a8897..5edbc37cc 100644 --- a/src/Fl_Input.cxx +++ b/src/Fl_Input.cxx @@ -577,7 +577,7 @@ int Fl_Input::handle(int event) { // For output widgets, do the callback so the app knows the user // did something with the mouse... - if (readonly()) do_callback(); + if (readonly()) do_callback(FL_REASON_RELEASED); return 1; diff --git a/src/Fl_Input_.cxx b/src/Fl_Input_.cxx index a8ad63ce0..88093bffe 100644 --- a/src/Fl_Input_.cxx +++ b/src/Fl_Input_.cxx @@ -942,7 +942,7 @@ int Fl_Input_::replace(int b, int e, const char* text, int ilen) { mark_ = position_ = undo_->undoat; set_changed(); - if (when()&FL_WHEN_CHANGED) do_callback(); + if (when()&FL_WHEN_CHANGED) do_callback(FL_REASON_CHANGED); return 1; } @@ -989,7 +989,7 @@ int Fl_Input_::undo() { while (b1 > 0 && index(b1)!='\n') b1--; minimal_update(b1); set_changed(); - if (when()&FL_WHEN_CHANGED) do_callback(); + if (when()&FL_WHEN_CHANGED) do_callback(FL_REASON_CHANGED); return 1; } @@ -1013,9 +1013,9 @@ int Fl_Input_::copy_cuts() { /** \internal Checks the when() field and does a callback if indicated. */ -void Fl_Input_::maybe_do_callback() { +void Fl_Input_::maybe_do_callback(Fl_Callback_Reason reason) { if (changed() || (when()&FL_WHEN_NOT_CHANGED)) { - do_callback(); + do_callback(reason); } } @@ -1055,7 +1055,7 @@ int Fl_Input_::handletext(int event, int X, int Y, int W, int H) { case FL_HIDE: fl_reset_spot(); if (!readonly() && (when() & FL_WHEN_RELEASE)) - maybe_do_callback(); + maybe_do_callback(FL_REASON_LOST_FOCUS); return 1; case FL_PUSH: diff --git a/src/Fl_Input_Choice.cxx b/src/Fl_Input_Choice.cxx index ae38cd1c6..059a1cc2d 100644 --- a/src/Fl_Input_Choice.cxx +++ b/src/Fl_Input_Choice.cxx @@ -205,7 +205,7 @@ void Fl_Input_Choice::menu_cb(Fl_Widget*, void *data) { { o->Fl_Widget::clear_changed(); if (o->when() & FL_WHEN_NOT_CHANGED) - o->do_callback(); + o->do_callback(FL_REASON_RESELECTED); } else { @@ -213,7 +213,7 @@ void Fl_Input_Choice::menu_cb(Fl_Widget*, void *data) { o->inp_->set_changed(); o->Fl_Widget::set_changed(); if (o->when() & (FL_WHEN_CHANGED|FL_WHEN_RELEASE)) - o->do_callback(); + o->do_callback(FL_REASON_CHANGED); } if (wp.deleted()) return; @@ -233,11 +233,11 @@ void Fl_Input_Choice::inp_cb(Fl_Widget*, void *data) { if (o->inp_->changed()) { o->Fl_Widget::set_changed(); if (o->when() & (FL_WHEN_CHANGED|FL_WHEN_RELEASE)) - o->do_callback(); + o->do_callback(FL_REASON_CHANGED); } else { o->Fl_Widget::clear_changed(); if (o->when() & FL_WHEN_NOT_CHANGED) - o->do_callback(); + o->do_callback(FL_REASON_RESELECTED); } if (wp.deleted()) return; diff --git a/src/Fl_Positioner.cxx b/src/Fl_Positioner.cxx index af86685c1..4bdcad797 100644 --- a/src/Fl_Positioner.cxx +++ b/src/Fl_Positioner.cxx @@ -99,8 +99,12 @@ int Fl_Positioner::handle(int event, int X, int Y, int W, int H) { if (!(when() & FL_WHEN_CHANGED || (when() & FL_WHEN_RELEASE && event == FL_RELEASE))) return 1; if (changed() || when()&FL_WHEN_NOT_CHANGED) { - if (event == FL_RELEASE) clear_changed(); - do_callback(); + Fl_Callback_Reason reason = changed() ? FL_REASON_CHANGED : FL_REASON_SELECTED; + if (event == FL_RELEASE) { + clear_changed(); + reason = FL_REASON_RELEASED; + } + do_callback(reason); } return 1; default: diff --git a/src/Fl_Repeat_Button.cxx b/src/Fl_Repeat_Button.cxx index 03dc2a997..81b8fc5dd 100644 --- a/src/Fl_Repeat_Button.cxx +++ b/src/Fl_Repeat_Button.cxx @@ -23,7 +23,7 @@ void Fl_Repeat_Button::repeat_callback(void *v) { Fl_Button *b = (Fl_Button*)v; Fl::add_timeout(REPEAT,repeat_callback,b); - b->do_callback(); + b->do_callback(FL_REASON_RESELECTED); } int Fl_Repeat_Button::handle(int event) { @@ -43,7 +43,7 @@ int Fl_Repeat_Button::handle(int event) { if (value(newval)) { if (newval) { Fl::add_timeout(INITIALREPEAT,repeat_callback,this); - do_callback(); + do_callback(FL_REASON_SELECTED); } else { Fl::remove_timeout(repeat_callback,this); } diff --git a/src/Fl_Return_Button.cxx b/src/Fl_Return_Button.cxx index 1c0f5f264..33f87e70a 100644 --- a/src/Fl_Return_Button.cxx +++ b/src/Fl_Return_Button.cxx @@ -52,7 +52,7 @@ int Fl_Return_Button::handle(int event) { if (event == FL_SHORTCUT && (Fl::event_key() == FL_Enter || Fl::event_key() == FL_KP_Enter)) { simulate_key_action(); - do_callback(); + do_callback(FL_REASON_SELECTED); return 1; } else return Fl_Button::handle(event); diff --git a/src/Fl_Scrollbar.cxx b/src/Fl_Scrollbar.cxx index 688e760b2..78dfde992 100644 --- a/src/Fl_Scrollbar.cxx +++ b/src/Fl_Scrollbar.cxx @@ -188,7 +188,7 @@ int Fl_Scrollbar::handle(int event) { Fl_Slider::value(v); value_damage(); set_changed(); - do_callback(); + do_callback(FL_REASON_DRAGGED); } return 1;} } diff --git a/src/Fl_Spinner.cxx b/src/Fl_Spinner.cxx index a298d189e..7bc3bd5c4 100644 --- a/src/Fl_Spinner.cxx +++ b/src/Fl_Spinner.cxx @@ -70,7 +70,7 @@ void Fl_Spinner::sb_cb(Fl_Widget *w, Fl_Spinner *sb) { } sb->set_changed(); - sb->do_callback(); + sb->do_callback(FL_REASON_CHANGED); } void Fl_Spinner::update() { @@ -154,10 +154,10 @@ int Fl_Spinner::handle(int event) { case FL_KEYDOWN: case FL_SHORTCUT: if (Fl::event_key() == FL_Up) { - up_button_.do_callback(); + up_button_.do_callback(FL_REASON_DRAGGED); return 1; } else if (Fl::event_key() == FL_Down) { - down_button_.do_callback(); + down_button_.do_callback(FL_REASON_DRAGGED); return 1; } return 0; 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 #include #include +#include #include #include @@ -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+10) + 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= 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= 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
- 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= 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),
- 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], 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(); } + diff --git a/src/Fl_Text_Editor.cxx b/src/Fl_Text_Editor.cxx index 139350048..81dc2f441 100644 --- a/src/Fl_Text_Editor.cxx +++ b/src/Fl_Text_Editor.cxx @@ -239,7 +239,7 @@ int Fl_Text_Editor::kf_default(int c, Fl_Text_Editor* e) { else e->overstrike(s); e->show_insert_position(); e->set_changed(); - if (e->when()&FL_WHEN_CHANGED) e->do_callback(); + if (e->when()&FL_WHEN_CHANGED) e->do_callback(FL_REASON_CHANGED); return 1; } @@ -268,7 +268,7 @@ int Fl_Text_Editor::kf_backspace(int, Fl_Text_Editor* e) { kill_selection(e); e->show_insert_position(); e->set_changed(); - if (e->when()&FL_WHEN_CHANGED) e->do_callback(); + if (e->when()&FL_WHEN_CHANGED) e->do_callback(FL_REASON_CHANGED); return 1; } @@ -280,7 +280,7 @@ int Fl_Text_Editor::kf_enter(int, Fl_Text_Editor* e) { e->insert("\n"); e->show_insert_position(); e->set_changed(); - if (e->when()&FL_WHEN_CHANGED) e->do_callback(); + if (e->when()&FL_WHEN_CHANGED) e->do_callback(FL_REASON_CHANGED); return 1; } @@ -544,7 +544,7 @@ int Fl_Text_Editor::kf_delete(int, Fl_Text_Editor* e) { kill_selection(e); e->show_insert_position(); e->set_changed(); - if (e->when()&FL_WHEN_CHANGED) e->do_callback(); + if (e->when()&FL_WHEN_CHANGED) e->do_callback(FL_REASON_CHANGED); return 1; } @@ -567,7 +567,7 @@ int Fl_Text_Editor::kf_cut(int c, Fl_Text_Editor* e) { kf_copy(c, e); kill_selection(e); e->set_changed(); - if (e->when()&FL_WHEN_CHANGED) e->do_callback(); + if (e->when()&FL_WHEN_CHANGED) e->do_callback(FL_REASON_CHANGED); return 1; } @@ -580,7 +580,7 @@ int Fl_Text_Editor::kf_paste(int, Fl_Text_Editor* e) { Fl::paste(*e, 1); e->show_insert_position(); e->set_changed(); - if (e->when()&FL_WHEN_CHANGED) e->do_callback(); + if (e->when()&FL_WHEN_CHANGED) e->do_callback(FL_REASON_CHANGED); return 1; } @@ -636,7 +636,7 @@ int Fl_Text_Editor::handle_key() { } show_insert_position(); set_changed(); - if (when()&FL_WHEN_CHANGED) do_callback(); + if (when()&FL_WHEN_CHANGED) do_callback(FL_REASON_CHANGED); return 1; } @@ -651,10 +651,11 @@ int Fl_Text_Editor::handle_key() { } /** does or does not a callback according to changed() and when() settings */ -void Fl_Text_Editor::maybe_do_callback() { +void Fl_Text_Editor::maybe_do_callback(Fl_Callback_Reason reason) { // printf("Fl_Text_Editor::maybe_do_callback()\n"); // printf("changed()=%d, when()=%x\n", changed(), when()); - if (changed() || (when()&FL_WHEN_NOT_CHANGED)) do_callback(); + if (changed() || (when()&FL_WHEN_NOT_CHANGED)) + do_callback(reason); } int Fl_Text_Editor::handle(int event) { @@ -679,7 +680,7 @@ int Fl_Text_Editor::handle(int event) { if (buffer()->selected()) redraw(); // Redraw selections... // FALLTHROUGH case FL_HIDE: - if (when() & FL_WHEN_RELEASE) maybe_do_callback(); + if (when() & FL_WHEN_RELEASE) maybe_do_callback(FL_REASON_LOST_FOCUS); return 1; case FL_KEYBOARD: @@ -697,7 +698,7 @@ int Fl_Text_Editor::handle(int event) { else overstrike(Fl::event_text()); show_insert_position(); set_changed(); - if (when()&FL_WHEN_CHANGED) do_callback(); + if (when()&FL_WHEN_CHANGED) do_callback(FL_REASON_CHANGED); return 1; case FL_ENTER: @@ -719,7 +720,7 @@ int Fl_Text_Editor::handle(int event) { Fl::paste(*this, 0); Fl::focus(this); set_changed(); - if (when()&FL_WHEN_CHANGED) do_callback(); + if (when()&FL_WHEN_CHANGED) do_callback(FL_REASON_CHANGED); return 1; } break; diff --git a/src/Fl_Tile.cxx b/src/Fl_Tile.cxx index 9b42d5f69..0d81cb259 100644 --- a/src/Fl_Tile.cxx +++ b/src/Fl_Tile.cxx @@ -269,8 +269,12 @@ int Fl_Tile::handle(int event) { } else newy = sy; position(sx,sy,newx,newy); - if (event == FL_DRAG) set_changed(); - do_callback(); + if (event == FL_DRAG) { + set_changed(); + do_callback(FL_REASON_DRAGGED); + } else { + do_callback(FL_REASON_CHANGED); + } return 1;} } diff --git a/src/Fl_Tree.cxx b/src/Fl_Tree.cxx index 1dd7cc449..a545a321c 100644 --- a/src/Fl_Tree.cxx +++ b/src/Fl_Tree.cxx @@ -2571,7 +2571,7 @@ int Fl_Tree::is_hscroll_visible() const { void Fl_Tree::do_callback_for_item(Fl_Tree_Item* item, Fl_Tree_Reason reason) { callback_reason(reason); callback_item(item); - do_callback((Fl_Widget*)this, user_data()); + do_callback((Fl_Widget*)this, user_data(), (Fl_Callback_Reason)reason); } /// Sets the item that was changed for this callback. diff --git a/src/Fl_Valuator.cxx b/src/Fl_Valuator.cxx index e7bcd2c8b..38383551e 100644 --- a/src/Fl_Valuator.cxx +++ b/src/Fl_Valuator.cxx @@ -101,7 +101,7 @@ void Fl_Valuator::handle_drag(double v) { value_ = v; value_damage(); set_changed(); - if (when() & FL_WHEN_CHANGED) do_callback(); + if (when() & FL_WHEN_CHANGED) do_callback(FL_REASON_CHANGED); } } /** Called after an FL_WHEN_RELEASE event is received and before the callback. */ @@ -113,7 +113,7 @@ void Fl_Valuator::handle_release() { clear_changed(); // now do the callback only if slider in new position or always is on: if (value_ != previous_value_ || when() & FL_WHEN_NOT_CHANGED) { - do_callback(); + do_callback(FL_REASON_RELEASED); } } } diff --git a/src/Fl_Value_Input.cxx b/src/Fl_Value_Input.cxx index 8060b7c00..4139c1ac4 100644 --- a/src/Fl_Value_Input.cxx +++ b/src/Fl_Value_Input.cxx @@ -33,7 +33,7 @@ void Fl_Value_Input::input_cb(Fl_Widget*, void* v) { if (nv != t.value() || t.when() & FL_WHEN_NOT_CHANGED) { t.set_value(nv); t.set_changed(); - if (t.when()) t.do_callback(); + if (t.when()) t.do_callback(FL_REASON_CHANGED); } } diff --git a/src/Fl_Widget.cxx b/src/Fl_Widget.cxx index 1d1b71dc4..f71b27927 100644 --- a/src/Fl_Widget.cxx +++ b/src/Fl_Widget.cxx @@ -365,28 +365,33 @@ void Fl_Widget::bind_deimage(Fl_Image* img) { /** Calls the widget callback function with arbitrary arguments. - All overloads of do_callback() call this method. - It does nothing if the widget's callback() is NULL. - It clears the widget's \e changed flag \b after the callback was - called unless the callback is the default callback. Hence it is not - necessary to call clear_changed() after calling do_callback() - in your own widget's handle() method. - - \note It is legal to delete the widget in the callback (i.e. in user code), - but you must not access the widget in the handle() method after - calling do_callback() if the widget was deleted in the callback. - We recommend to use Fl_Widget_Tracker to check whether the widget - was deleted in the callback. - - \param[in] widget call the callback with \p widget as the first argument - \param[in] arg use \p arg as the user data (second) argument - - \see default_callback() - \see callback() - \see class Fl_Widget_Tracker -*/ - -void Fl_Widget::do_callback(Fl_Widget *widget, void *arg) { + All overloads of do_callback() call this method. + It does nothing if the widget's callback() is NULL. + It clears the widget's \e changed flag \b after the callback was + called unless the callback is the default callback. Hence it is not + necessary to call clear_changed() after calling do_callback() + in your own widget's handle() method. + + A \p reason must be set for widgets if different actions can trigger + the same callback. + + \note It is legal to delete the widget in the callback (i.e. in user code), + but you must not access the widget in the handle() method after + calling do_callback() if the widget was deleted in the callback. + We recommend to use Fl_Widget_Tracker to check whether the widget + was deleted in the callback. + + \param[in] widget call the callback with \p widget as the first argument + \param[in] arg use \p arg as the user data (second) argument + \param[in] reason for calling this callback + + \see default_callback() + \see callback() + \see class Fl_Widget_Tracker + \see Fl::callback_reason() + */ +void Fl_Widget::do_callback(Fl_Widget *widget, void *arg, Fl_Callback_Reason reason) { + Fl::callback_reason_ = reason; if (!callback_) return; Fl_Widget_Tracker wp(this); callback_(widget, arg); -- cgit v1.2.3