From 8826dca1066361b474139bcc5aeed2e3a5246ed0 Mon Sep 17 00:00:00 2001 From: Matthias Melcher Date: Thu, 5 Jan 2023 13:51:30 +0100 Subject: [PATCH] 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 --- CHANGES.txt | 1 + FL/Enumerations.H | 53 +++-- FL/Fl.H | 2 + FL/Fl_Browser_.H | 6 + FL/Fl_Button.H | 9 +- FL/Fl_Color_Chooser.H | 3 + FL/Fl_Group.H | 5 + FL/Fl_Input_.H | 2 +- FL/Fl_Menu_Item.H | 6 +- FL/Fl_Tabs.H | 60 +++++- FL/Fl_Text_Editor.H | 2 +- FL/Fl_Tree.H | 14 +- FL/Fl_Widget.H | 20 +- FL/Fl_Window.H | 3 + FL/names.h | 21 ++ fluid/CodeEditor.cxx | 2 +- fluid/Fl_Widget_Type.cxx | 59 ++++-- fluid/Shortcut_Button.cxx | 34 ++-- 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 | 41 ++-- test/browser.cxx | 6 +- test/tabs.fl | 75 +++++-- 41 files changed, 754 insertions(+), 220 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 560424ce5..282cb233f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -51,6 +51,7 @@ Changes in FLTK 1.4.0 Released: ??? ?? 2022 - New Fl_ICO_Image class to read Windows .ico icon files. - New classes Fl_SVG_File_Surface and Fl_EPS_File_Surface to save any FLTK graphics to SVG or EPS files, respectively. + - Fl_Tabs now supports close buttons for individual tabs. - Windows platform: added support for using a manifest to set the application's level of DPI awareness (issue #309). - class Fl_Native_File_Chooser on the X11/Wayland platform relies on external diff --git a/FL/Enumerations.H b/FL/Enumerations.H index 9d40152d6..928f09612 100644 --- a/FL/Enumerations.H +++ b/FL/Enumerations.H @@ -1,7 +1,7 @@ // // Enumerations for the Fast Light Tool Kit (FLTK). // -// Copyright 1998-2022 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 @@ -415,22 +415,51 @@ enum Fl_Event { // events /*@{*/ /** These constants determine when a callback is performed. - \see Fl_Widget::when(); - \todo doxygen comments for values are incomplete and maybe wrong or unclear + Fl_When is a bit field. Some values are merely shorcuts for common bit + combinations. New flags may be added in the future, so it's important to + mask the required bit when reading via \p when(). + + \note Some widgets may not fully suppoert \p FL_WHEN_... flags. + + \see Fl_Widget::when(), Fl::callback_reason(), Fl_Callback_Reason, Fl_Widget::do_callback() */ enum Fl_When { // Fl_Widget::when(): - FL_WHEN_NEVER = 0, ///< Never call the callback - FL_WHEN_CHANGED = 1, ///< Do the callback only when the widget value changes - FL_WHEN_NOT_CHANGED = 2, ///< Do the callback whenever the user interacts with the widget - FL_WHEN_RELEASE = 4, ///< Do the callback when the button or key is released and the value changes - FL_WHEN_RELEASE_ALWAYS = 6, ///< Do the callback when the button or key is released, even if the value doesn't change - FL_WHEN_ENTER_KEY = 8, ///< Do the callback when the user presses the ENTER key and the value changes - FL_WHEN_ENTER_KEY_ALWAYS =10, ///< Do the callback when the user presses the ENTER key, even if the value doesn't change - FL_WHEN_ENTER_KEY_CHANGED =11 ///< = (FL_WHEN_ENTER_KEY | FL_WHEN_CHANGED | FL_WHEN_NOT_CHANGED) + FL_WHEN_NEVER = 0, ///< Never call the callback + FL_WHEN_CHANGED = 1, ///< Do the callback only when the widget value changes + FL_WHEN_NOT_CHANGED = 2, ///< Do the callback whenever the user interacts with the widget + FL_WHEN_RELEASE = 4, ///< Do the callback when the button or key is released and the value changes + FL_WHEN_RELEASE_ALWAYS = 6, ///< Do the callback when the button or key is released, even if the value doesn't change + FL_WHEN_ENTER_KEY = 8, ///< Do the callback when the user presses the ENTER key and the value changes + FL_WHEN_ENTER_KEY_ALWAYS = 10, ///< Do the callback when the user presses the ENTER key, even if the value doesn't change + FL_WHEN_ENTER_KEY_CHANGED = 11, ///< Do callbacks whether the value changed or not, and when the ENTER key is pressed + FL_WHEN_CLOSED = 16 ///< Do the callback when a child of Fl_Tabs is closed }; - /*@}*/ // group: When Conditions + +/** \name Callback Reasons */ +/*@{*/ +/** These constants describe why a callback is performed. + + \see Fl::callback_reason(), Fl_Widget::when(), Fl_When + */ +enum Fl_Callback_Reason { + FL_REASON_UNKNOWN=0, ///< unknown or unset reason + FL_REASON_SELECTED, ///< an item was selected + FL_REASON_DESELECTED, ///< an item was de-selected + FL_REASON_RESELECTED, ///< an item was re-selected (double-clicked). + FL_REASON_OPENED, ///< an item was opened + FL_REASON_CLOSED, ///< an item was closed + FL_REASON_DRAGGED, ///< an item was dragged into a new place + FL_REASON_CANCELLED, ///< a dialog was cancelled + FL_REASON_CHANGED, ///< the value of the widget was modified + FL_REASON_GOT_FOCUS, ///< a widget received focus + FL_REASON_LOST_FOCUS, ///< a widget lost focus + FL_REASON_RELEASED, ///< the mouse button was released +}; +/*@}*/ // group: Callback Reasons + + /** \name Mouse and Keyboard Events This and the following constants define the non-ASCII keys on the diff --git a/FL/Fl.H b/FL/Fl.H index 8f1e0bf44..6f51181a4 100644 --- a/FL/Fl.H +++ b/FL/Fl.H @@ -202,6 +202,7 @@ public: // should be private! static void *e_clipboard_data; static const char *e_clipboard_type; static Fl_Event_Dispatch e_dispatch; + static Fl_Callback_Reason callback_reason_; static Fl_Widget* belowmouse_; static Fl_Widget* pushed_; static Fl_Widget* focus_; @@ -847,6 +848,7 @@ public: static void remove_system_handler(Fl_System_Handler h); static void event_dispatch(Fl_Event_Dispatch d); static Fl_Event_Dispatch event_dispatch(); + static Fl_Callback_Reason callback_reason(); /** @} */ /** \defgroup fl_clipboard Selection & Clipboard functions diff --git a/FL/Fl_Browser_.H b/FL/Fl_Browser_.H index 78d242206..03550b67a 100644 --- a/FL/Fl_Browser_.H +++ b/FL/Fl_Browser_.H @@ -53,6 +53,12 @@ accessing image data or doing stat() on a file or doing some other slow operation. + Callbacks are called when the value changes with \p FL_REASON_CHANGED. + If \p FL_WHEN_RELEASE is set, callbacks are called when the mouse button is + released with \p FL_REASON_CHANGED or \p FL_REASON_RESELECTED if the selection + did not change. If \p FL_WHEN_ENTER_KEY is set, callbacks are also called when + key presses or double clicks change the selection. + Keyboard navigation of browser items ------------------------------------ diff --git a/FL/Fl_Button.H b/FL/Fl_Button.H index 38d804070..14e515d1b 100644 --- a/FL/Fl_Button.H +++ b/FL/Fl_Button.H @@ -65,10 +65,15 @@ class Fl_Widget_Tracker; being \c FL_WHEN_RELEASE: \li \c 0: The callback is not done, instead changed() is turned on. \li \c FL_WHEN_RELEASE: The callback is done after the user successfully - clicks the button, or when a shortcut is typed. + clicks the button, or when a shortcut is typed. The reason is + \p FL_REASON_RELEASED. \li \c FL_WHEN_CHANGED: The callback is done each time the value() changes (when the user pushes and releases the button, and as the mouse is - dragged around in and out of the button). + dragged around in and out of the button). The reason is set to + \p FL_REASON_CHANGED + \li \c FL_WHEN_NOT_CHANGED: The callback is done when the mouse button is + released, but the value did not changed. The reason is set to + \p FL_REASON_SELECTED */ class FL_EXPORT Fl_Button : public Fl_Widget { diff --git a/FL/Fl_Color_Chooser.H b/FL/Fl_Color_Chooser.H index 1aa569c11..29baecec7 100644 --- a/FL/Fl_Color_Chooser.H +++ b/FL/Fl_Color_Chooser.H @@ -104,6 +104,9 @@ public: available colors, leaving you no space to exactly represent the color the user picks! You can however use fl_rectf() to fill a region with a simulated color using dithering. + + Callback reasons can be \c FL_REASON_DRAGGED, \c FL_REASON_CHANGED, or + \c FL_REASON_RESELECTED. */ /** @} */ class FL_EXPORT Fl_Color_Chooser : public Fl_Group { diff --git a/FL/Fl_Group.H b/FL/Fl_Group.H index 793f2c854..1e21b20ba 100644 --- a/FL/Fl_Group.H +++ b/FL/Fl_Group.H @@ -47,6 +47,11 @@ class Fl_Rect; \endcode ..and this will trigger proper scheduling of the widget's removal from its parent group. + + If used as a child of \p Fl_Tabs, setting \p when(FL_WHEN_CLOSED) will + enable the Close button in the corresponding tab. If the user clicks the + Close button, the callback of this group will be called with the callback + reason \p FL_REASON_CLOSED. */ class FL_EXPORT Fl_Group : public Fl_Widget { diff --git a/FL/Fl_Input_.H b/FL/Fl_Input_.H index 0ec9b09ab..7d3f90cc3 100644 --- a/FL/Fl_Input_.H +++ b/FL/Fl_Input_.H @@ -200,7 +200,7 @@ protected: int handletext(int e, int, int, int, int); /* Check the when() field and do a callback if indicated. */ - void maybe_do_callback(); + void maybe_do_callback(Fl_Callback_Reason reason = FL_REASON_UNKNOWN); /** \internal Horizontal offset of text to left edge of widget. */ int xscroll() const {return xscroll_;} diff --git a/FL/Fl_Menu_Item.H b/FL/Fl_Menu_Item.H index 7ae1faeef..0f1083a9a 100644 --- a/FL/Fl_Menu_Item.H +++ b/FL/Fl_Menu_Item.H @@ -390,14 +390,14 @@ struct FL_EXPORT Fl_Menu_Item { The callback is called with the stored user_data() as its second argument. You must first check that callback() is non-zero before calling this. */ - void do_callback(Fl_Widget* o) const {callback_(o, user_data_);} + void do_callback(Fl_Widget* o) const {Fl::callback_reason_=FL_REASON_SELECTED; callback_(o, user_data_);} /** Calls the Fl_Menu_Item item's callback, and provides the Fl_Widget argument. This call overrides the callback's second argument with the given value \p arg. You must first check that callback() is non-zero before calling this. */ - void do_callback(Fl_Widget* o,void* arg) const {callback_(o, arg);} + void do_callback(Fl_Widget* o,void* arg) const {Fl::callback_reason_=FL_REASON_SELECTED; callback_(o, arg);} /** Calls the Fl_Menu_Item item's callback, and provides the Fl_Widget argument. @@ -406,7 +406,7 @@ struct FL_EXPORT Fl_Menu_Item { the callback. You must first check that callback() is non-zero before calling this. */ - void do_callback(Fl_Widget* o,long arg) const {callback_(o, (void*)(fl_intptr_t)arg);} + void do_callback(Fl_Widget* o,long arg) const {Fl::callback_reason_=FL_REASON_SELECTED; callback_(o, (void*)(fl_intptr_t)arg);} /** Back compatibility only. \deprecated diff --git a/FL/Fl_Tabs.H b/FL/Fl_Tabs.H index 27f5bc0da..334be5773 100644 --- a/FL/Fl_Tabs.H +++ b/FL/Fl_Tabs.H @@ -1,7 +1,7 @@ // // Tab header file for the Fast Light Tool Kit (FLTK). // -// Copyright 1998-2017 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 @@ -22,10 +22,15 @@ #include "Fl_Group.H" +struct Fl_Menu_Item; + /** - The Fl_Tabs widget is the "file card tabs" - interface that allows you to put lots and lots of buttons and - switches in a panel, as popularized by many toolkits. + The Fl_Tabs widget is a container widget that displays a set of tabs, with + each tab representing a different child widget. The user can select a tab by + clicking on it, and the corresponding child widget will be displayed. + The Fl_Tabs widget is useful for organizing a large number of controls or + other widgets into a compact space, allowing the user to switch between + different sets of controls as needed. \image html tabs.png \image latex tabs.png "Fl_Tabs" width=8cm @@ -46,8 +51,8 @@ children (there should be some space between the children and the edge of the Fl_Tabs), and the tabs may be placed "inverted" on the bottom - this is determined by which - gap is larger. It is easiest to lay this out in fluid, using the - fluid browser to select each child group and resize them until + gap is larger. It is easiest to lay this out in FLUID, using the + FLUID browser to select each child group and resize them until the tabs look the way you want them to. The background area behind and to the right of the tabs is @@ -157,6 +162,19 @@ \image html tabs_uniform.png "Fl_Tabs with uniform colors" \image latex tabs_uniform.png "Fl_Tabs with uniform colors" width=8cm + \b Close \b Button \b on \b Tabs + + The Fl_Tabs widget allows you to specify that a child widget should display + a close button in its tab. If the \ref FL_WHEN_CLOSED flag is set for the + child widget, an "X" symbol will be displayed to the left of the label text + in the tab. When the close button is clicked, the child widget's callback + function will be called with the \ref FL_REASON_CLOSED argument. It is then + the responsibility of the child widget to remove itself from the + Fl_Tabs container. + + Tabs that are in a compressed state will not display a close button until + they are fully expanded. + \b Resizing \b Caveats When Fl_Tabs is resized vertically, the default behavior scales the @@ -197,6 +215,7 @@ -# \ref FL_WHEN_NOT_CHANGED can happen if someone clicks on an already selected tab, or if a keyboard navigation attempt results in no change to the tabs, such as using the arrow keys while at the left or right end of the tabs. + -# \ref Fl::callback_reason() returns FL_REASON_SELECTED or FL_REASON_RESELECTED */ class FL_EXPORT Fl_Tabs : public Fl_Group { @@ -204,16 +223,31 @@ class FL_EXPORT Fl_Tabs : public Fl_Group { protected: + int overflow_type; + int tab_offset; int *tab_pos; // array of x-offsets of tabs per child + 1 int *tab_width; // array of widths of tabs per child + 1 + int *tab_flags; // array of tab flag of tabs per child + 1 int tab_count; // array size Fl_Align tab_align_; // tab label alignment + int has_overflow_menu; + Fl_Menu_Item* overflow_menu; + + void check_overflow_menu(); + void handle_overflow_menu(); + void draw_overflow_menu_button(); + + int on_insert(Fl_Widget*, int) FL_OVERRIDE; + int on_move(int, int) FL_OVERRIDE; + void on_remove(int) FL_OVERRIDE; + void resize(int, int, int, int) FL_OVERRIDE; virtual void redraw_tabs(); virtual int tab_positions(); // allocate and calculate tab positions virtual void clear_tab_positions(); - virtual void draw_tab(int x1, int x2, int W, int H, Fl_Widget* o, int sel=0); + virtual void draw_tab(int x1, int x2, int W, int H, Fl_Widget* o, int flags, int sel); virtual int tab_height(); + virtual int hit_close(Fl_Widget *o, int event_x, int event_y); void draw() FL_OVERRIDE; @@ -225,6 +259,7 @@ public: int handle(int) FL_OVERRIDE; Fl_Widget *value(); int value(Fl_Widget *); + /** Returns the tab group for the tab the user has currently down-clicked on and remains over until FL_RELEASE. Otherwise, returns NULL. @@ -256,12 +291,23 @@ public: recommended alignment to show the icon left of the text. */ void tab_align(Fl_Align a) {tab_align_ = a;} + /** Gets the tab label alignment. \see tab_align(Fl_Align) */ Fl_Align tab_align() const {return tab_align_;} + + enum { + OVERFLOW_COMPRESS = 0, + OVERFLOW_CLIP, + OVERFLOW_PULLDOWN, + OVERFLOW_DRAG, + }; + + void handle_overflow(int ov); + }; #endif diff --git a/FL/Fl_Text_Editor.H b/FL/Fl_Text_Editor.H index b9a0d42b0..5bf49781e 100644 --- a/FL/Fl_Text_Editor.H +++ b/FL/Fl_Text_Editor.H @@ -113,7 +113,7 @@ class FL_EXPORT Fl_Text_Editor : public Fl_Text_Display { protected: int handle_key(); - void maybe_do_callback(); + void maybe_do_callback(Fl_Callback_Reason reason = FL_REASON_CHANGED); #ifndef FL_DOXYGEN int insert_mode_; diff --git a/FL/Fl_Tree.H b/FL/Fl_Tree.H index 9a56e1841..6c2b7bf3c 100644 --- a/FL/Fl_Tree.H +++ b/FL/Fl_Tree.H @@ -273,14 +273,14 @@ /// The reason the callback was invoked. /// enum Fl_Tree_Reason { - FL_TREE_REASON_NONE=0, ///< unknown reason - FL_TREE_REASON_SELECTED, ///< an item was selected - FL_TREE_REASON_DESELECTED, ///< an item was de-selected - FL_TREE_REASON_RESELECTED, ///< an item was re-selected (double-clicked). + FL_TREE_REASON_NONE = FL_REASON_UNKNOWN, ///< unknown reason + FL_TREE_REASON_SELECTED = FL_REASON_SELECTED, ///< an item was selected + FL_TREE_REASON_DESELECTED = FL_REASON_DESELECTED, ///< an item was de-selected + FL_TREE_REASON_RESELECTED = FL_REASON_RESELECTED, ///< an item was re-selected (double-clicked). ///< See ::Fl_Tree_Item_Reselect_Mode to enable this. - FL_TREE_REASON_OPENED, ///< an item was opened - FL_TREE_REASON_CLOSED, ///< an item was closed - FL_TREE_REASON_DRAGGED ///< an item was dragged into a new place + FL_TREE_REASON_OPENED = FL_REASON_OPENED, ///< an item was opened + FL_TREE_REASON_CLOSED = FL_REASON_CLOSED, ///< an item was closed + FL_TREE_REASON_DRAGGED = FL_REASON_DRAGGED ///< an item was dragged into a new place }; class FL_EXPORT Fl_Tree : public Fl_Group { diff --git a/FL/Fl_Widget.H b/FL/Fl_Widget.H index 5b16555b9..772717856 100644 --- a/FL/Fl_Widget.H +++ b/FL/Fl_Widget.H @@ -1,7 +1,7 @@ // // Widget header file for the Fast Light Tool Kit (FLTK). // -// Copyright 1998-2020 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 @@ -707,7 +707,7 @@ public: FL_WHEN_RELEASE. \return set of flags - \see when(uchar) + \see when(uchar), Fl_When, do_callback(), Fl::callback_reason() */ Fl_When when() const {return (Fl_When)when_;} @@ -733,6 +733,11 @@ public: a newline for a Fl_Multiline_Input) - this changes the behavior. \li FL_WHEN_ENTER_KEY|FL_WHEN_NOT_CHANGED: The Enter key will do the callback even if the text has not changed. Useful for command fields. + \li \ref FL_WHEN_CLOSED : If the user requests that the widget is closed, + the callback is called with \ref FL_REASON_CLOSED. The \ref Fl_Tabs + widget checks this flag on its children to determine whether to display + a close button on the tab of that widget. + Fl_Widget::when() is a set of bitflags used by subclasses of Fl_Widget to decide when to do the callback. @@ -741,6 +746,7 @@ public: class so that you can scan a panel and do_callback() on all the ones that don't do their own callbacks in response to an "OK" button. \param[in] i set of flags + \see Fl_When, do_callback(), Fl::callback_reason() */ void when(uchar i) {when_ = i;} @@ -962,7 +968,7 @@ public: \see callback() \see do_callback(Fl_Widget *widget, void *data) */ - void do_callback() {do_callback(this, user_data_);} + void do_callback(Fl_Callback_Reason reason=FL_REASON_UNKNOWN) {do_callback(this, user_data_, reason);} /** Calls the widget callback function with arbitrary arguments. \param[in] widget call the callback with \p widget as the first argument @@ -970,13 +976,11 @@ public: \see callback() \see do_callback(Fl_Widget *widget, void *data) */ - void do_callback(Fl_Widget *widget, long arg) { - do_callback(widget, (void*)(fl_intptr_t)arg); + void do_callback(Fl_Widget *widget, long arg, Fl_Callback_Reason reason=FL_REASON_UNKNOWN) { + do_callback(widget, (void*)(fl_intptr_t)arg, reason); } - // Causes a widget to invoke its callback function with arbitrary arguments. - // Documentation and implementation in Fl_Widget.cxx - void do_callback(Fl_Widget *widget, void *arg = 0); + void do_callback(Fl_Widget *widget, void *arg = 0, Fl_Callback_Reason reason=FL_REASON_UNKNOWN); /* Internal use only. */ int test_shortcut(); diff --git a/FL/Fl_Window.H b/FL/Fl_Window.H index ddb3283eb..f012d59a2 100644 --- a/FL/Fl_Window.H +++ b/FL/Fl_Window.H @@ -48,6 +48,9 @@ class Fl_Double_Window; The window's callback is done if the user tries to close a window using the window manager and Fl::modal() is zero or equal to the window. Fl_Window has a default callback that calls Fl_Window::hide(). + Callback reasons can be \p FL_REASON_CANCELLED if the Escape key was pressed, + or \p FL_REASON_CLOSED when the close button is clicked. \p FL_WHEN_... + flags are ignored. */ class FL_EXPORT Fl_Window : public Fl_Group { friend int Fl::arg(int argc, char **argv, int &i); diff --git a/FL/names.h b/FL/names.h index 9aaef5490..745ec2133 100644 --- a/FL/names.h +++ b/FL/names.h @@ -110,6 +110,27 @@ const char * const fl_fontnames[] = "FL_ZAPF_DINGBATS", }; +/** + This is an array of callback reason names you can use to convert font numbers into names. + + The array gets defined inline wherever your '\#include ' appears. + */ +const char * const fl_callback_reason_names[] = +{ + "FL_REASON_UNKNOWN", + "FL_REASON_SELECTED", + "FL_REASON_DESELECTED", + "FL_REASON_RESELECTED", + "FL_REASON_OPENED", + "FL_REASON_CLOSED", + "FL_REASON_DRAGGED", + "FL_REASON_CANCELLED", + "FL_REASON_CHANGED", + "FL_REASON_GOT_FOCUS", + "FL_REASON_LOST_FOCUS", + "FL_REASON_RELEASED", +}; + /** @} */ #endif /* FL_NAMES_H */ diff --git a/fluid/CodeEditor.cxx b/fluid/CodeEditor.cxx index d4b4dee91..40fe667fc 100644 --- a/fluid/CodeEditor.cxx +++ b/fluid/CodeEditor.cxx @@ -197,7 +197,7 @@ int CodeEditor::auto_indent(int, CodeEditor* 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); free(text); diff --git a/fluid/Fl_Widget_Type.cxx b/fluid/Fl_Widget_Type.cxx index cab346b5d..08396e85c 100644 --- a/fluid/Fl_Widget_Type.cxx +++ b/fluid/Fl_Widget_Type.cxx @@ -1110,12 +1110,20 @@ void down_box_cb(Fl_Choice* i, void *v) { //////////////////////////////////////////////////////////////// Fl_Menu_Item whenmenu[] = { + // set individual bits {"FL_WHEN_CHANGED",0,0,(void*)FL_WHEN_CHANGED, FL_MENU_TOGGLE}, {"FL_WHEN_NOT_CHANGED",0,0,(void*)FL_WHEN_NOT_CHANGED, FL_MENU_TOGGLE}, {"FL_WHEN_RELEASE",0,0,(void*)FL_WHEN_RELEASE, FL_MENU_TOGGLE}, {"FL_WHEN_ENTER_KEY",0,0,(void*)FL_WHEN_ENTER_KEY, FL_MENU_TOGGLE}, + {"FL_WHEN_CLOSED",0,0,(void*)FL_WHEN_CLOSED, FL_MENU_TOGGLE|FL_MENU_DIVIDER}, + // set bit combinations + {"FL_WHEN_NEVER",0,0,(void*)FL_WHEN_NEVER}, + {"FL_WHEN_RELEASE_ALWAYS",0,0,(void*)FL_WHEN_RELEASE_ALWAYS}, + {"FL_WHEN_ENTER_KEY_ALWAYS",0,0,(void*)FL_WHEN_ENTER_KEY_ALWAYS}, + {"FL_WHEN_ENTER_KEY_CHANGED",0,0,(void*)FL_WHEN_ENTER_KEY_CHANGED}, {0}}; + static Fl_Menu_Item whensymbolmenu[] = { /* 0 */ {"FL_WHEN_NEVER",0,0,(void*)FL_WHEN_NEVER}, /* 1 */ {"FL_WHEN_CHANGED",0,0,(void*)FL_WHEN_CHANGED}, @@ -1136,23 +1144,48 @@ static Fl_Menu_Item whensymbolmenu[] = { {0} }; +// Return a text string representing the Fl_When value n +const char* when_symbol_name(int n) { + static char sym[128]; + if (n == FL_WHEN_CLOSED) { + strcpy(sym, "FL_WHEN_CLOSED"); + } else { + strcpy(sym, whensymbolmenu[n&15].label()); + if (n & FL_WHEN_CLOSED) + strcat(sym, " | FL_WHEN_CLOSED"); + } + return sym; +} + +// Set the check marks in the "when()" menu according to the Fl_When value n +void set_whenmenu(int n) { + if (n&FL_WHEN_CHANGED) whenmenu[0].set(); else whenmenu[0].clear(); + if (n&FL_WHEN_NOT_CHANGED) whenmenu[1].set(); else whenmenu[1].clear(); + if (n&FL_WHEN_RELEASE) whenmenu[2].set(); else whenmenu[2].clear(); + if (n&FL_WHEN_ENTER_KEY) whenmenu[3].set(); else whenmenu[3].clear(); + if (n&FL_WHEN_CLOSED) whenmenu[4].set(); else whenmenu[4].clear(); +} + void when_cb(Fl_Menu_Button* i, void *v) { if (v == LOAD) { if (current_widget->is_menu_item()) {i->deactivate(); return;} else i->activate(); - int n = current_widget->o->when() & 15; - if (n&1) whenmenu[0].set(); else whenmenu[0].clear(); - if (n&2) whenmenu[1].set(); else whenmenu[1].clear(); - if (n&4) whenmenu[2].set(); else whenmenu[2].clear(); - if (n&8) whenmenu[3].set(); else whenmenu[3].clear(); - w_when_box->label(whensymbolmenu[n].label()); + int n = current_widget->o->when(); + set_whenmenu(n); + w_when_box->copy_label(when_symbol_name(n)); } else { int mod = 0; int n = 0; - if (whenmenu[0].value()) n |= 1; - if (whenmenu[1].value()) n |= 2; - if (whenmenu[2].value()) n |= 4; - if (whenmenu[3].value()) n |= 8; - w_when_box->label(whensymbolmenu[n].label()); + if (i->mvalue() && ((i->mvalue()->flags & FL_MENU_TOGGLE) == 0) ) { + n = (int)i->mvalue()->argument(); + set_whenmenu(n); + } else { + if (whenmenu[0].value()) n |= FL_WHEN_CHANGED; + if (whenmenu[1].value()) n |= FL_WHEN_NOT_CHANGED; + if (whenmenu[2].value()) n |= FL_WHEN_RELEASE; + if (whenmenu[3].value()) n |= FL_WHEN_ENTER_KEY; + if (whenmenu[4].value()) n |= FL_WHEN_CLOSED; + } + w_when_box->copy_label(when_symbol_name(n)); for (Fl_Type *o = Fl_Type::first; o; o = o->next) { if (o->selected && o->is_widget()) { Fl_Widget_Type* q = (Fl_Widget_Type*)o; @@ -3074,8 +3107,7 @@ void Fl_Widget_Type::write_widget_code() { if (ww==FL_WHEN_NOT_CHANGED) ww = FL_WHEN_NEVER; if (ww != tplate->when() || subclass()) - write_c("%s%s->when(%s);\n", indent(), var, - item_name(whensymbolmenu, ww)); + write_c("%s%s->when(%s);\n", indent(), var, when_symbol_name(ww)); if (!o->visible() && o->parent()) write_c("%s%s->hide();\n", indent(), var); if (!o->active()) @@ -3552,6 +3584,7 @@ void Fl_Widget_Type::copy_properties() { w->labelsize(o->labelsize()); w->labelcolor(o->labelcolor()); w->align(o->align()); + w->when(o->when()); // copy all attributes specific to widgets derived from Fl_Button if (is_button()) { diff --git a/fluid/Shortcut_Button.cxx b/fluid/Shortcut_Button.cxx index 134029082..e0ce2c9b2 100644 --- a/fluid/Shortcut_Button.cxx +++ b/fluid/Shortcut_Button.cxx @@ -1,7 +1,7 @@ // // Widget type code 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 @@ -86,9 +86,10 @@ int Shortcut_Button::handle(int e) { } /** \class Widget_Bin_Button - A button for the widget bin that allows the user to drag widgets into a window. - Dragging and dropping a new widget makes it easy for the user to position - a widget inside a window or group. + The Widget_Bin_Button button is a button that can be used in the widget bin to + allow the user to drag and drop widgets into a window or group. This feature + makes it easy for the user to position a widget at a specific location within + the window or group. */ /** @@ -124,8 +125,9 @@ int Widget_Bin_Button::handle(int inEvent) } /** \class Widget_Bin_Window_Button - This button is used by the widget bin to create new windows by drag'n'drop. - The new window will be created wherever the user drops it on the desktop. + The Widget_Bin_Window_Button button is used in the widget bin to create new + windows by dragging and dropping. When the button is dragged and dropped onto + the desktop, a new window will be created at the drop location. */ /** @@ -179,9 +181,10 @@ int Widget_Bin_Window_Button::handle(int inEvent) } /** \class Fluid_Coord_Input - An Input field for widget coordinates and sizes. - This widget adds basic math capability to the text input field and a number - of variables that can be used in the formula. + The Fluid_Coord_Input widget is an input field for entering widget coordinates + and sizes. It includes basic math capabilities and allows the use of variables + in formulas. This widget is useful for specifying precise positions and + dimensions for widgets in a graphical user interface. */ /** @@ -298,10 +301,15 @@ int Fluid_Coord_Input::eval(uchar *&s, int prio) const { /** Evaluate a formula into an integer. - The interpreter understands unary plus and minus, basic integer math - (+, -, *, /), brackets, and can handle a user defined list of variables - by name. There is no error checking. We assume that the formula is - entered correctly. + + The Fluid_Coord_Input widget includes a formula interpreter that allows you + to evaluate a string containing a mathematical formula and obtain the result + as an integer. The interpreter supports unary plus and minus, basic integer + math operations (such as addition, subtraction, multiplication, and division), + and brackets. It also allows you to define a list of variables by name and use + them in the formula. The interpreter does not perform error checking, so it is + assumed that the formula is entered correctly. + \param s formula as a C string \return the calculated value */ 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. - \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. + 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. + + \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. + 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. + A \p reason must be set for widgets if different actions can trigger + the same 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 + \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. - \see default_callback() - \see callback() - \see class Fl_Widget_Tracker -*/ + \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 -void Fl_Widget::do_callback(Fl_Widget *widget, void *arg) { + \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); diff --git a/test/browser.cxx b/test/browser.cxx index 05c83acb2..b4b8cdbb0 100644 --- a/test/browser.cxx +++ b/test/browser.cxx @@ -168,7 +168,11 @@ int main(int argc, char **argv) { browser->callback(b_cb); if (!browser->load(fname)) { fl_message("Can't load '%s'\n%s\n", fname, strerror(errno)); - exit(1); + browser->add("This is a test of how the browser draws lines."); + browser->add("This is a second line."); + browser->add("This is a third."); + browser->add("@bBold text"); + browser->add("@iItalic text"); } browser->position(0); diff --git a/test/tabs.fl b/test/tabs.fl index da5ecbbb5..814ffd82d 100644 --- a/test/tabs.fl +++ b/test/tabs.fl @@ -5,19 +5,21 @@ code_name {.cxx} Function {} {open } { Fl_Window foo_window { - label {Comparison of Fl_Tab (left) vs. Fl_Wizard (right)} open selected - xywh {516 38 660 400} type Double hide resizable + label {Comparison of Fl_Tab (left) vs. Fl_Wizard (right)} open + xywh {330 402 660 400} type Double resizable visible } { Fl_Box {} { label {class Fl_Tabs} xywh {95 0 130 35} labeltype ENGRAVED_LABEL labelfont 1 } - Fl_Tabs {} {open + Fl_Tabs tabs_group {open tooltip {the various index cards test different aspects of the Fl_Tabs widget} xywh {10 35 315 260} selection_color 4 labelcolor 7 resizable } { Fl_Group {} { label {Label&1} - tooltip {this tab tests correct keyboard navigation between text input fields} xywh {10 60 315 235} selection_color 1 resizable + callback {if (Fl::callback_reason()==FL_REASON_CLOSED) + o->parent()->remove(o);} selected + tooltip {this tab tests correct keyboard navigation between text input fields} xywh {10 60 315 235} selection_color 1 when 16 hide resizable } { Fl_Input {} { label {input:} @@ -35,7 +37,9 @@ Function {} {open } Fl_Group {} { label {tab&2} - tooltip {tab2 tests among other things the cooperation of modal windows and tabs} xywh {10 60 315 235} selection_color 2 hide + callback {if (Fl::callback_reason()==FL_REASON_CLOSED) + o->parent()->remove(o);} selected + tooltip {tab2 tests among other things the cooperation of modal windows and tabs} xywh {10 60 315 235} selection_color 2 when 16 hide } { Fl_Button {} { label button1 @@ -61,7 +65,9 @@ Function {} {open } Fl_Group {} { label {tab&3} - tooltip {tab3 checks for correct keyboard navigation} xywh {10 60 315 235} selection_color 3 hide + callback {if (Fl::callback_reason()==FL_REASON_CLOSED) + o->parent()->remove(o);} selected + tooltip {tab3 checks for correct keyboard navigation} xywh {10 60 315 235} selection_color 3 when 16 hide } { Fl_Button {} { label button2 @@ -78,7 +84,9 @@ Function {} {open } Fl_Group {} { label {&tab4} - tooltip {this tab shows the issue of indicating a selected tab if the tab layouts are very similar} xywh {10 60 315 235} selection_color 5 labeltype ENGRAVED_LABEL labelfont 2 hide + callback {if (Fl::callback_reason()==FL_REASON_CLOSED) + o->parent()->remove(o);} selected + tooltip {this tab shows the issue of indicating a selected tab if the tab layouts are very similar} xywh {10 60 315 235} selection_color 5 labeltype ENGRAVED_LABEL labelfont 2 when 16 hide } { Fl_Button {} { label button2 @@ -94,8 +102,8 @@ Function {} {open } } Fl_Group {} { - label {@fileprint &print} - tooltip {tab5 verifies if visibility requests are handled correctly} xywh {10 60 315 235} hide + label {@fileprint &print} selected + tooltip {tab5 verifies if visibility requests are handled correctly} xywh {10 60 315 235} } { Fl_Button {} { label button2 @@ -265,12 +273,53 @@ wWizard->value(wWizard->child(last));} tooltip {go to last page [End]} xywh {510 305 30 25} shortcut 0xff57 } } - Fl_Input {} { - label {inputA:} - xywh {60 310 130 25} + Fl_Button {} { + label {add tab} + callback {static int n = 6; +int X = tabs_group->x(), Y = tabs_group->y()+25; +int W = tabs_group->w(), H = tabs_group->h()-25; +Fl_Group::current(NULL); +char buf[64]; +snprintf(buf, 62, "tab%d", n); +Fl_Group* new_tab = new Fl_Group(X, Y, W, H); +new_tab->copy_label(buf); +new_tab->when(FL_WHEN_CLOSED); +new_tab->callback((Fl_Callback*)cb_tab); +snprintf(buf, 62, "Empty tab %d.", n); +Fl_Box* box = new Fl_Box(X+5, Y+5, W-10, H-10); +box->copy_label(buf); +box->align(Fl_Align(FL_ALIGN_CENTER|FL_ALIGN_INSIDE)); +tabs_group->add(new_tab); +n++;} + tooltip {Add more tabs to check overflow handling.} xywh {60 309 70 25} + } + Fl_Choice {} { + label {overflow:} open + tooltip {Change how Fl_Tabs handles more tabs than fit into the tabs space.} xywh {212 309 95 25} down_box BORDER_BOX + } { + MenuItem {} { + label compress + callback {tabs_group->handle_overflow(Fl_Tabs::OVERFLOW_COMPRESS);} + xywh {0 0 31 20} + } + MenuItem {} { + label clip + callback {tabs_group->handle_overflow(Fl_Tabs::OVERFLOW_CLIP);} + xywh {0 0 31 20} + } + MenuItem {} { + label pulldown + callback {tabs_group->handle_overflow(Fl_Tabs::OVERFLOW_PULLDOWN);} + xywh {0 0 31 20} + } + MenuItem {} { + label drag + callback {tabs_group->handle_overflow(Fl_Tabs::OVERFLOW_DRAG);} + xywh {0 0 31 20} + } } Fl_Input {} { - label {inputB:} + label {input:} xywh {60 345 250 25} } Fl_Button {} {