summaryrefslogtreecommitdiff
path: root/src/Fl_Menu.cxx
diff options
context:
space:
mode:
authorMatthias Melcher <github@matthiasm.com>2025-11-21 00:11:30 +0100
committerMatthias Melcher <github@matthiasm.com>2025-11-21 00:11:30 +0100
commita01fcae832c997a10fc05dd7146401585c661ce4 (patch)
tree904336e0d4cb1bfb5cc433260ef5d18d5103f9d4 /src/Fl_Menu.cxx
parent8dcc99279e54d0d044813e12deb0c55633a6c277 (diff)
Reformat src/Fl_Menu.cxx.
Reorder declarations and implementations. Rename to current FLTK standards, reformat.
Diffstat (limited to 'src/Fl_Menu.cxx')
-rw-r--r--src/Fl_Menu.cxx1762
1 files changed, 953 insertions, 809 deletions
diff --git a/src/Fl_Menu.cxx b/src/Fl_Menu.cxx
index 16fe5a7b3..0e559a3d4 100644
--- a/src/Fl_Menu.cxx
+++ b/src/Fl_Menu.cxx
@@ -1,7 +1,7 @@
//
// Menu code for the Fast Light Tool Kit (FLTK).
//
-// Copyright 1998-2024 by Bill Spitzak and others.
+// Copyright 1998-2025 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
@@ -14,12 +14,12 @@
// https://www.fltk.org/bugs.php
//
-// Warning: this menu code is quite a mess!
-
// This file contains code for implementing Fl_Menu_Item, and for
// methods for bringing up popup menu hierarchies without using the
// Fl_Menu_ widget.
+// The menu code is in the process of refactoring.
+
#include <FL/Fl.H>
#include "Fl_Screen_Driver.H"
#include "Fl_Window_Driver.H"
@@ -29,387 +29,386 @@
#include <stdio.h>
#include "flstring.h"
-/** Size of the menu starting from this menu item.
+// This file will declare:
+class Menu_Window_Basetype;
+class Menu_Title_Window;
+class Menu_Window;
+struct Menu_State;
- This method counts all menu items starting with \p this menu item,
- including all menu items in the same (sub)menu level, all nested
- submenus, \b and the terminating empty (0) menu item.
+static bool is_special_labeltype(uchar t);
- It does \b not count menu items referred to by FL_SUBMENU_POINTER
- menu items (except the single menu item with FL_SUBMENU_POINTER).
+// Global variables:
- All menu items counted are consecutive in memory (one array).
+// Global variable that tells the label draw call if `&x` Alt-key shortcuts must be rendered.
+extern char fl_draw_shortcut;
- Example:
+// Local variables:
- \code
- schemechoice = new Fl_Choice(X+125,Y,140,25,"FLTK Scheme");
- schemechoice->add("none");
- schemechoice->add("plastic");
- schemechoice->add("gtk+");
- schemechoice->add("gleam");
- printf("schemechoice->menu()->size() = %d\n", schemechoice->menu()->size());
- \endcode
+// appearance of current menus are pulled from this parent widget:
+static const Fl_Menu_* button=0;
- Output:
+//
+// ==== Declarations ===========================================================
+//
+//
+// ---- Menu_State -------------------------------------------------------------
+//
- schemechoice->menu()->%size() = 5
-*/
-int Fl_Menu_Item::size() const {
- const Fl_Menu_Item* m = this;
- int nest = 0;
- for (;;) {
- if (!m->text) {
- if (!nest) return (int) (m-this+1);
- nest--;
- } else if (m->flags & FL_SUBMENU) {
- nest++;
- }
- m++;
- }
-}
+// Fl_Menu_Item::popup(...)
-// Advance a pointer to next visible or invisible item of a menu array,
-// skipping the contents of submenus.
-static const Fl_Menu_Item* next_visible_or_not(const Fl_Menu_Item* m) {
- int nest = 0;
- do {
- if (!m->text) {
- if (!nest) return m;
- nest--;
- } else if (m->flags&FL_SUBMENU) {
- nest++;
- }
- m++;
- }
- while (nest);
- return m;
-}
+// Because Fl::grab() is done, all events go to one of the menu windows.
+// But the handle method needs to look at all of them to find out
+// what item the user is pointing at. And it needs a whole lot
+// of other state variables to determine what is going on with
+// the currently displayed menus.
+// So the main loop (handlemenu()) puts all the state in a structure
+// and puts a pointer to it in a static location, so the handle()
+// on menus can refer to it and alter it. The handle() method
+// changes variables in this state to indicate what item is
+// picked, but does not actually alter the display, instead the
+// main loop does that. This is because the X mapping and unmapping
+// of windows is slow, and we don't want to fall behind the events.
-/**
- Advance a pointer by n items through a menu array, skipping
- the contents of submenus and invisible items. There are two calls so
- that you can advance through const and non-const data.
-*/
-const Fl_Menu_Item* Fl_Menu_Item::next(int n) const {
- if (n < 0) return 0; // this is so selected==-1 returns NULL
- const Fl_Menu_Item* m = this;
- if (!m->visible()) n++;
- while (n) {
- m = next_visible_or_not(m);
- if (m->visible() || !m->text) n--;
- }
- return m;
-}
+// values for menustate.state:
+static constexpr int INITIAL_STATE = 0; // no mouse up or down since popup() called
+static constexpr int PUSH_STATE = 1; // mouse has been pushed on a normal item
+static constexpr int DONE_STATE = 2; // exit the popup, the current item was picked
+static constexpr int MENU_PUSH_STATE = 3; // mouse has been pushed on a menu title
-// appearance of current menus are pulled from this parent widget:
-static const Fl_Menu_* button=0;
+/*
+ This class handles the current cascade of menu windows for a pulldown call.
+ */
+struct Menu_State
+{
+ // what mouse is pointing at
+ const Fl_Menu_Item* current_item { nullptr };
+
+ // which menu it is in
+ int menu_number { 0 };
+
+ // which item in that menu, -1 if none
+ int item_number { -1 };
+
+ // pointers to menus
+ Menu_Window* menu_window[20] { nullptr };
+
+ // number of open menuwindows
+ int num_menus { 0 };
+
+ // if true p[0] is a menubar
+ int menubar { 0 };
-////////////////////////////////////////////////////////////////
-class menuwindow;
+ // INITIAL_STATE, etc. See above
+ int state { INITIAL_STATE };
-// utility class covering both menuwindow and menutitle
-class window_with_items : public Fl_Menu_Window {
+ // kludge for buttons in menubar
+ Menu_Window* fake_menu { nullptr };
+
+ int is_inside(int mx, int my);
+
+ void set_item(const Fl_Menu_Item* i, int m, int n);
+
+ void set_item(int m, int n);
+
+ // previous item in menu menu if possible
+ int backward(int menu);
+
+ // go to next item in menu menu if possible
+ int forward(int menu);
+
+ int handle_shortcut();
+};
+// Global state of menu windows and popup windows.
+static Menu_State* menu_state = nullptr;
+
+
+//
+// ---- Menu_Window_Basetype ---------------------------------------------------
+//
+
+/*
+ Base type for Menu_Title_Window and Menu_Window, derived from Fl_Menu_Window.
+*/
+class Menu_Window_Basetype : public Fl_Menu_Window
+{
protected:
- /*
- Create a window that can hold menu items. The implementation is in the
- derived class menuwindow and menutitle.
+
+ /* Create a window that can hold menu items.
+ The implementation is in the derived class Menu_Title_Window and Menu_Window.
\param X, Y, W, H position and size of the window
- \param m saved in member variable `menu`, used in `class menuwindow`
+ \param m saved in member variable `menu` for derived classes
*/
- window_with_items(int X, int Y, int W, int H, const Fl_Menu_Item *m) :
- Fl_Menu_Window(X, Y, W, H, 0) {
- menu = m;
- set_menu_window();
- Fl_Window_Driver::driver(this)->set_popup_window();
- end();
- set_modal();
- clear_border();
- }
+ Menu_Window_Basetype(int X, int Y, int W, int H, const Fl_Menu_Item *m)
+ : Fl_Menu_Window(X, Y, W, H, 0),
+ menu(m)
+ {
+ set_menu_window();
+ Fl_Window_Driver::driver(this)->set_popup_window();
+ end();
+ set_modal();
+ clear_border();
+ }
+
public:
+
// Store a pointer to the first item in the menu array.
- const Fl_Menu_Item* menu;
+ const Fl_Menu_Item* menu { nullptr };
+
// Use this to check this is a Fl_Menu_Window or a derived window class.
- virtual menuwindow* as_menuwindow() { return NULL; }
+ virtual Menu_Window* as_menuwindow() { return nullptr; }
};
-// tiny window for title of menu:
-class menutitle : public window_with_items {
- // Implement the class specifc drawing code
- void draw() FL_OVERRIDE;
+//
+// ---- Menu_Title_Window ------------------------------------------------------
+//
+
+/*
+ Menu window showing the title above a popup menu.
+ */
+class Menu_Title_Window : public Menu_Window_Basetype
+{
+ /* Draw the contents of the menu title window. */
+ void draw() override {
+ menu->draw(0, 0, w(), h(), button, 2);
+ }
+
public:
- /*
- Create a window that hold the title of another menu.
+
+ /* Create a window that hold the title of another menu.
\param X, Y, W, H position and size of the window
- \param m saved in member variable `menu`, used in `class menuwindow`
- \param menubar if set, this title is part of a horizontal menu bar,
- if clear, this is a floating menu title
+ \param[in] L pointer to menu item that holds the label text
+ \param[in] inbar true if this is part of an Fl_Menu_Bar
*/
- menutitle(int X, int Y, int W, int H, const Fl_Menu_Item*, bool menubar = false);
+ Menu_Title_Window(int X, int Y, int W, int H, const Fl_Menu_Item* L, bool inbar = false)
+ : Menu_Window_Basetype(X, Y, W, H, L),
+ in_menubar(inbar) { }
+
// If set, title is part of a menubar.
- bool in_menubar;
+ bool in_menubar { false };
};
-/*
- A window that renders the menu items from an array and handles all events.
+//
+// ---- Menu_Window ------------------------------------------------------------
+//
+
+/* A window that renders the menu items from an array and handles all events.
The event handler runs in its own loop inside `Fl_Menu_Item::pulldown`.
- The `struct menustate` holds up to 20 menu windows simultaneously.
+ All Menu Windows are managed in the struct `menu_state`.
*/
-class menuwindow : public window_with_items {
+class Menu_Window : public Menu_Window_Basetype
+{
// For tricky direct access
friend class Fl_Window_Driver;
+
// Fl_Menu_Item::pulldown does some direct manipulations
friend struct Fl_Menu_Item;
+
// Draw this window, either entirely, or just the selected and deselect items.
void draw() override;
+
// Draw a single menu item in this window
- void drawentry(const Fl_Menu_Item*, int i, int erase);
+ void draw_entry(const Fl_Menu_Item*, int i, int erase);
+
// Main event handler
int handle_part1(int);
+
// Kludge to avoid abandoned window on macOS
int handle_part2(int e, int ret);
+
// All open menu windows are positioned relative to this window
static Fl_Window *parent_;
+
// Helper to store the height of the screen that contains the menu windows
static int display_height_;
+
public:
- // Optional title for menubar windows and floating menus
- menutitle* title;
- // Override to handle all incoming events
- int handle(int) override;
- // Override to fixup the current selection
- void hide() override;
- // Height of the tallest menu item in the array.
- int itemheight; // zero == menubar
- // Number of visible menu items.
- int numitems;
- // Index of selected item, or -1 if none is selected.
- int selected;
- // Remember the last item we drew selected, so we can redraw it unselected
- // when the selection changes. -1 if none.
- int drawn_selected;
- // Width of the shortcut column, using the longest shortcut text width.
- int shortcutWidth;
+
// Create our menu window
- menuwindow(const Fl_Menu_Item* m, int X, int Y, int W, int H,
- const Fl_Menu_Item* picked, const Fl_Menu_Item* title,
- int menubar = 0, int menubar_title = 0, int right_edge = 0);
+ Menu_Window(const Fl_Menu_Item* m, int X, int Y, int W, int H,
+ const Fl_Menu_Item* picked, const Fl_Menu_Item* title,
+ int menubar = 0, int menubar_title = 0, int right_edge = 0);
+
// Destructor
- ~menuwindow();
+ ~Menu_Window();
+
+ // Override to fixup the current selection
+ void hide() override;
+
+ // Override to handle all incoming events
+ int handle(int) override;
+
// Change the index of the selected item, -1 for none. Trigger chatty callbacks
// and marks the window area of the newly selected item for redraw.
void set_selected(int);
+
// Find the index to the item under the given mouse coordinates.
int find_selected(int mx, int my);
+
// Calculate the horizontal position of an item by index for horizontal
// menus inside a menubar.
int titlex(int);
+
// Scroll so item i is visible on screen. This may move the entire window..
void autoscroll(int i);
+
// Also reposition the title (relative to the parent_ window?)
void position(int x, int y);
+
// return 1, if the given root coordinates are inside the window
int is_inside(int x, int y);
- // Fake runtime type information
- menuwindow* as_menuwindow() FL_OVERRIDE { return this; }
- // (if set, this is part of a menubar?)
- int menubartitle;
- // In a cascading window, this points to the menu window that opened this menu.
- menuwindow *origin;
- // Used by the window driver
- int offset_y;
-};
-Fl_Window *menuwindow::parent_ = nullptr;
-int menuwindow::display_height_ = 0;
+ // Fake runtime type information
+ Menu_Window* as_menuwindow() override { return this; }
-/**
- \cond DriverDev
- \addtogroup DriverDeveloper
- \{
- */
+ // Optional title for menubar windows and floating menus
+ Menu_Title_Window* title { nullptr };
-/** The Fl_Window from which currently displayed popups originate.
- Optionally, gives also the height of the display containing this window.
- \parmm[out]display_height return the height of the display here.
- \return pointe to the owning window
- */
-Fl_Window *Fl_Window_Driver::menu_parent(int *display_height) {
- if (display_height) *display_height = menuwindow::display_height_;
- return menuwindow::parent_;
-}
+ // Height of the tallest menu item in the array, zero == menubar.
+ int item_height { 0 };
-/* Cast to menuwindow if win is of claa menuwindow and the driver is initialized. */
-static menuwindow *to_menuwindow(Fl_Window *win) {
- if (!Fl_Window_Driver::driver(win)->popup_window() || !win->menu_window()) return nullptr;
- return ((window_with_items*)win)->as_menuwindow();
-}
+ // Number of visible menu items.
+ int num_items { 0 };
-/** Accessor to the "origin" member variable of class menuwindow.
- Variable origin is not NULL when 2 menuwindow's occur, one being a submenu of the other;
- it links the menuwindow at right to the one at left. */
-Fl_Window *Fl_Window_Driver::menu_leftorigin(Fl_Window *win) {
- menuwindow *mwin = to_menuwindow(win);
- return (mwin ? mwin->origin : NULL);
-}
+ // Index of selected item, or -1 if none is selected.
+ int selected { -1 };
-/** Accessor to the "title" member variable of class menuwindow */
-Fl_Window *Fl_Window_Driver::menu_title(Fl_Window *win) {
- menuwindow *mwin = to_menuwindow(win);
- return (mwin ? mwin->title : NULL);
-}
+ // Remember the last item we drew selected, so we can redraw it unselected
+ // when the selection changes. -1 if none.
+ int drawn_selected { -1 };
-/** Accessor to the "itemheight" member variable of class menuwindow */
-int Fl_Window_Driver::menu_itemheight(Fl_Window *win) {
- menuwindow *mwin = to_menuwindow(win);
- return (mwin ? mwin->itemheight : 0);
-}
+ // Width of the shortcut column, using the longest shortcut text width.
+ int shortcut_width { 0 };
-/** Accessor to the "menubartitle" member variable of class menuwindow */
-int Fl_Window_Driver::menu_bartitle(Fl_Window *win) {
- menuwindow *mwin = to_menuwindow(win);
- return (mwin ? mwin->menubartitle : 0);
-}
+ // (if set, this is part of a menubar?)
+ int menubar_title { 0 };
-/** Accessor to the "selected" member variable of class menuwindow */
-int Fl_Window_Driver::menu_selected(Fl_Window *win) {
- menuwindow *mwin = to_menuwindow(win);
- return (mwin ? mwin->selected : -1);
-}
+ // In a cascading window, this points to the menu window that opened this menu.
+ Menu_Window *origin { nullptr };
-/** Accessor to the address of the offset_y member variable of class menuwindow */
-int *Fl_Window_Driver::menu_offset_y(Fl_Window *win) {
- menuwindow *mwin = to_menuwindow(win);
- return (mwin ? &(mwin->offset_y) : NULL);
-}
+ // Used by the window driver
+ int offset_y { 0 };
+};
-/** Returns whether win is a non-menubar menutitle */
-bool Fl_Window_Driver::is_floating_title(Fl_Window *win) {
- if (!Fl_Window_Driver::driver(win)->popup_window() || !win->menu_window()) return false;
- Fl_Window *mwin = ((window_with_items*)win)->as_menuwindow();
- return !mwin && !((menutitle*)win)->in_menubar;
-}
+//
+// ==== Implementations ========================================================
+//
+//
+// ---- Menu_State -------------------------------------------------------------
+//
-/** Makes sure that the tall menu's selected item is visible in display */
-void Fl_Window_Driver::scroll_to_selected_item(Fl_Window *win) {
- menuwindow *mwin = to_menuwindow(win);
- if (mwin && mwin->selected > 0) {
- mwin->autoscroll(mwin->selected);
+/* Find out if any menu window is under the mouse.
+ \return 1 if the coordinates are inside any of the menuwindows
+ */
+int Menu_State::is_inside(int mx, int my) {
+ int i;
+ for (i=num_menus-1; i>=0; i--) {
+ if (menu_window[i]->is_inside(mx, my)) {
+ return 1;
+ }
}
+ return 0;
}
-/**
- \}
- \endcond
+/* Remember this item in the state machine.
+ \param[in] i current menu item
+ \param[in] m index into menu window array
+ \param[in] n index into visible item in that menu window
*/
+void Menu_State::set_item(const Fl_Menu_Item* i, int m, int n) {
+ current_item = i;
+ menu_number = m;
+ item_number = n;
+}
-// Global variable that tells the label draw call if `&x` Alt-key shortcuts must be rendered.
-extern char fl_draw_shortcut;
-
-/**
- Measures width of label, including effect of & characters.
- Optionally, can get height if hp is not NULL.
- \param[out] hp return the height of the label
- \param[in] m for this menu item
- \return width of the label without shortcut text, but including checkbox
- for radio and check items.
-*/
-int Fl_Menu_Item::measure(int* hp, const Fl_Menu_* m) const {
- Fl_Label l;
- l.value = text;
- l.image = 0;
- l.deimage = 0;
- l.type = labeltype_;
- l.font = labelsize_ || labelfont_ ? labelfont_ : (m ? m->textfont() : FL_HELVETICA);
- l.size = labelsize_ ? labelsize_ : m ? m->textsize() : FL_NORMAL_SIZE;
- l.color = FL_FOREGROUND_COLOR; // this makes no difference?
- l.h_margin_ = l.v_margin_ = l.spacing = 0;
- fl_draw_shortcut = 1;
- int w = 0; int h = 0;
- l.measure(w, hp ? *hp : h);
- fl_draw_shortcut = 0;
- if (flags & (FL_MENU_TOGGLE|FL_MENU_RADIO)) w += FL_NORMAL_SIZE + 4;
- return w;
+/* Find and store a menu item in the state machine.
+ \param[in] m index into menu window array
+ \param[in] n index into visible item in that menu window
+ */
+void Menu_State::set_item(int m, int n) {
+ current_item = (n >= 0) ? menu_window[m]->menu->next(n) : 0;
+ menu_number = m;
+ item_number = n;
}
-/**
- Draws the menu item in the bounding box selected or unselected.
- This does not draw the shortcut: see menuwindow::drawentry().
- \param[in] x, y, w, h bounding box for the menu item
- \param[in] m draw the background, label, and checkbox of this item
- \param[in] selected 0 = draw unselected, 1 = draw selected, 2 = draw menu title
+/* Go down to the next selectable menu item.
+ If the event button is FL_Down, increment once, else go to the bottom of the menu.
+ \param[in] menu index into menu window list
+ \return 1 if an item was found, 0 if the menu wrapped
*/
-void Fl_Menu_Item::draw(int x, int y, int w, int h, const Fl_Menu_* m,
- int selected) const {
- Fl_Label l;
- l.value = text;
- l.image = 0;
- l.deimage = 0;
- l.type = labeltype_;
- l.font = labelsize_ || labelfont_ ? labelfont_ : (m ? m->textfont() : FL_HELVETICA);
- l.size = labelsize_ ? labelsize_ : m ? m->textsize() : FL_NORMAL_SIZE;
- l.color = labelcolor_ ? labelcolor_ : m ? m->textcolor() : int(FL_FOREGROUND_COLOR);
- l.h_margin_ = l.v_margin_ = l.spacing = 0;
- if (!active()) l.color = fl_inactive((Fl_Color)l.color);
- if (selected) {
- Fl_Color r = m ? m->selection_color() : FL_SELECTION_COLOR;
- Fl_Boxtype b = m && m->down_box() ? m->down_box() : FL_FLAT_BOX;
- l.color = fl_contrast((Fl_Color)labelcolor_, r);
- if (selected == 2) { // menu title
- fl_draw_box(b, x, y, w, h, r);
- x += 3;
- w -= 8;
- } else {
- fl_draw_box(b, x+1, y-(Fl::menu_linespacing()-2)/2, w-2, h+(Fl::menu_linespacing()-2), r);
+int Menu_State::forward(int menu) { // go to next item in menu menu if possible
+ // `menu` is -1 if no item is currently selected, so use the first menu
+ if (menu < 0)
+ menu = 0;
+ Menu_Window &m = *(menu_window[menu]);
+ int item = (menu == menu_number) ? item_number : m.selected;
+ bool wrapped = false;
+ do {
+ while (++item < m.num_items) {
+ const Fl_Menu_Item* m1 = m.menu->next(item);
+ if (m1->selectable()) {
+ set_item(m1, menu, item);
+ return 1;
+ }
}
+ if (wrapped) break;
+ item = -1;
+ wrapped = true;
}
+ while (Fl::event_key() != FL_Down);
+ return 0;
+}
- if (flags & (FL_MENU_TOGGLE|FL_MENU_RADIO)) {
- int d = (h - FL_NORMAL_SIZE + 1) / 2;
- int W = h - 2 * d;
-
- Fl_Color check_color = labelcolor_;
- if (Fl::is_scheme("gtk+"))
- check_color = FL_SELECTION_COLOR;
- check_color = fl_contrast(check_color, FL_BACKGROUND2_COLOR);
-
- if (flags & FL_MENU_RADIO) {
-
- fl_draw_box(FL_ROUND_DOWN_BOX, x+2, y+d, W, W, FL_BACKGROUND2_COLOR);
- if (value()) {
- int tW = (W - Fl::box_dw(FL_ROUND_DOWN_BOX)) / 2 + 1;
- if ((W - tW) & 1) tW++; // Make sure difference is even to center
- int td = (W - tW) / 2;
- fl_draw_radio(x + td + 1, y + d + td - 1, tW + 2, check_color);
- } // FL_MENU_RADIO && value()
-
- } else { // FL_MENU_TOGGLE && ! FL_MENU_RADIO
-
- fl_draw_box(FL_DOWN_BOX, x+2, y+d, W, W, FL_BACKGROUND2_COLOR);
- if (value()) {
- fl_draw_check(Fl_Rect(x+3, y+d+1, W-2, W-2), check_color);
+/* Go up to the previous selectable menu item.
+ If the event button is FL_Up, decrement once, else go to the top of the menu.
+ \param[in] menu index into menu window list
+ \return 1 if an item was found, 0 if the menu wrapped
+ */
+int Menu_State::backward(int menu) { // previous item in menu menu if possible
+ // `menu` is -1 if no item is currently selected, so use the first menu
+ if (menu < 0)
+ menu = 0;
+ Menu_Window &m = *(menu_window[menu]);
+ int item = (menu == menu_number) ? item_number : m.selected;
+ bool wrapped = false;
+ do {
+ while (--item >= 0) {
+ const Fl_Menu_Item* m1 = m.menu->next(item);
+ if (m1->selectable()) {
+ set_item(m1, menu, item);
+ return 1;
}
}
- x += W + 3;
- w -= W + 3;
+ if (wrapped) break;
+ item = m.num_items;
+ wrapped = true;
}
-
- if (!fl_draw_shortcut) fl_draw_shortcut = 1;
- l.draw(x+3, y, w>6 ? w-6 : 0, h, FL_ALIGN_LEFT);
- fl_draw_shortcut = 0;
+ while (Fl::event_key() != FL_Up);
+ return 0;
}
-/*
- Construct a menu title window holding a single line of text.
- \param[in] X, Y, W, H position and size
- \param[in] L pointer to menu item that hold the label text
- \param[in] inbar true if this is part of an Fl_Menu_Bar
- */
-menutitle::menutitle(int X, int Y, int W, int H, const Fl_Menu_Item* L, bool inbar) :
- window_with_items(X, Y, W, H, L) {
- in_menubar = inbar;
+int Menu_State::handle_shortcut() {
+ for (int mymenu = num_menus; mymenu--;) {
+ Menu_Window &mw = *(menu_window[mymenu]);
+ int item;
+ const Fl_Menu_Item* m = mw.menu->find_shortcut(&item);
+ if (m) {
+ set_item(m, mymenu, item);
+ if (!m->submenu())
+ state = DONE_STATE;
+ return 1;
+ }
+ }
}
+//
+// ---- Menu_Window ------------------------------------------------------------
+//
+
+// Static members:
+Fl_Window *Menu_Window::parent_ = nullptr;
+int Menu_Window::display_height_ = 0;
+
+
/*
Construct a menu window that can render a list of menu items.
\param[in] m pointer to the first menu item in the array
@@ -421,22 +420,20 @@ menutitle::menutitle(int X, int Y, int W, int H, const Fl_Menu_Item* L, bool inb
\param[in] menubar_title set if top level of Fl_Menu_Bar(?)
\param[in] right_edge maximum right edge of menu on current screen(?), not used
*/
-menuwindow::menuwindow(const Fl_Menu_Item* m, int X, int Y, int Wp, int Hp,
- const Fl_Menu_Item* picked, const Fl_Menu_Item* t,
- int menubar, int menubar_title, int right_edge)
- : window_with_items(X, Y, Wp, Hp, m)
+Menu_Window::Menu_Window(const Fl_Menu_Item* m, int X, int Y, int Wp, int Hp,
+ const Fl_Menu_Item* picked, const Fl_Menu_Item* t,
+ int menubar, int menubar_title, int right_edge)
+: Menu_Window_Basetype(X, Y, Wp, Hp, m)
{
int scr_x, scr_y, scr_w, scr_h;
int tx = X, ty = Y;
- menubartitle = menubar_title;
- origin = NULL;
- offset_y = 0;
+ menubar_title = menubar_title;
int n = (Wp > 0 ? Fl::screen_num(X, Y) : -1);
Fl_Window_Driver::driver(this)->menu_window_area(scr_x, scr_y, scr_w, scr_h, n);
- if (!right_edge || right_edge > scr_x+scr_w) right_edge = scr_x+scr_w;
+ if (!right_edge || right_edge > scr_x+scr_w)
+ right_edge = scr_x+scr_w;
if (m) m = m->first(); // find the first item that needs to be rendered
- drawn_selected = -1;
if (button) {
Fl_Boxtype b = button->menu_box();
if (b==FL_NO_BOX)
@@ -448,25 +445,30 @@ menuwindow::menuwindow(const Fl_Menu_Item* m, int X, int Y, int Wp, int Hp,
box(FL_UP_BOX);
}
color(button && !Fl::scheme() ? button->color() : FL_GRAY);
- selected = -1;
{
int j = 0;
if (m) for (const Fl_Menu_Item* m1=m; ; m1 = m1->next(), j++) {
if (picked) {
- if (m1 == picked) {selected = j; picked = 0;}
- else if (m1 > picked) {selected = j-1; picked = 0; Wp = Hp = 0;}
+ if (m1 == picked) {
+ selected = j;
+ picked = 0;
+ } else if (m1 > picked) {
+ selected = j-1;
+ picked = 0;
+ Wp = Hp = 0;
+ }
+ }
+ if (!m1->text) break;
}
- if (!m1->text) break;
- }
- numitems = j;}
+ num_items = j;}
if (menubar) {
- itemheight = 0;
+ item_height = 0;
title = 0;
return;
}
- itemheight = 1;
+ item_height = 1;
int hotKeysw = 0;
int hotModsw = 0;
@@ -477,7 +479,7 @@ menuwindow::menuwindow(const Fl_Menu_Item* m, int X, int Y, int Wp, int Hp,
if (m) for (; m->text; m = m->next()) {
int hh;
int w1 = m->measure(&hh, button);
- if (hh+Fl::menu_linespacing()>itemheight) itemheight = hh+Fl::menu_linespacing();
+ if (hh+Fl::menu_linespacing()>item_height) item_height = hh+Fl::menu_linespacing();
if (m->flags&(FL_SUBMENU|FL_SUBMENU_POINTER))
w1 += FL_NORMAL_SIZE;
if (w1 > W) W = w1;
@@ -501,7 +503,7 @@ menuwindow::menuwindow(const Fl_Menu_Item* m, int X, int Y, int Wp, int Hp,
}
}
}
- shortcutWidth = hotKeysw;
+ shortcut_width = hotKeysw;
if (selected >= 0 && !Wp) X -= W/2;
int BW = Fl::box_dx(box());
W += hotKeysw+hotModsw+2*BW+7;
@@ -514,9 +516,9 @@ menuwindow::menuwindow(const Fl_Menu_Item* m, int X, int Y, int Wp, int Hp,
//if (X > scr_x+scr_w-W) X = right_edge-W;
if (X > scr_x+scr_w-W) X = scr_x+scr_w-W;
x(X); w(W);
- h((numitems ? itemheight*numitems-4 : 0)+2*BW+3);
+ h((num_items ? item_height*num_items-4 : 0)+2*BW+3);
if (selected >= 0) {
- Y = Y+(Hp-itemheight)/2-selected*itemheight-BW;
+ Y = Y+(Hp-item_height)/2-selected*item_height-BW;
} else {
Y = Y+Hp;
// if the menu hits the bottom of the screen, we try to draw
@@ -529,10 +531,10 @@ menuwindow::menuwindow(const Fl_Menu_Item* m, int X, int Y, int Wp, int Hp,
} else if (t) {
// assume that the menubar item height relates to the first
// menuitem as well
- Y = Y-itemheight-h()-Fl::box_dh(box());
+ Y = Y-item_height-h()-Fl::box_dh(box());
} else {
// draw the menu to the right
- Y = Y-h()+itemheight+Fl::box_dy(box());
+ Y = Y-h()+item_height+Fl::box_dy(box());
}
if (t) {
if (menubar_title) {
@@ -549,11 +551,11 @@ menuwindow::menuwindow(const Fl_Menu_Item* m, int X, int Y, int Wp, int Hp,
if (menubar_title) {
int dy = Fl::box_dy(button->box())+1;
int ht = button->h()-dy*2;
- title = new menutitle(tx, ty-ht-dy, Wtitle, ht, t, true);
+ title = new Menu_Title_Window(tx, ty-ht-dy, Wtitle, ht, t, true);
} else {
int dy = 2;
int ht = Htitle+2*BW+3;
- title = new menutitle(X, Y-ht-dy, Wtitle, ht, t);
+ title = new Menu_Title_Window(X, Y-ht-dy, Wtitle, ht, t);
}
} else {
title = 0;
@@ -561,40 +563,302 @@ menuwindow::menuwindow(const Fl_Menu_Item* m, int X, int Y, int Wp, int Hp,
}
/* Destroy this window. */
-menuwindow::~menuwindow() {
+Menu_Window::~Menu_Window() {
hide();
delete title;
}
/* Fixup the selection and hide this window */
-void menuwindow::hide() {
+void Menu_Window::hide() {
set_selected(-1);
- window_with_items::hide();
+ Menu_Window_Basetype::hide();
}
-/* Set the position of this menu and its title window. */
-void menuwindow::position(int X, int Y) {
- if (title) {title->position(X, title->y()+Y-y());}
- Fl_Menu_Window::position(X, Y);
- // x(X); y(Y); // don't wait for response from X
+/* Handle events sent to the window.
+ \param[in] e event number
+ \return 1 if the event was used
+ */
+int Menu_Window::handle(int e) {
+ /* In FLTK 1.3.4, the equivalent of handle_part2() is called for the Mac OS and X11 platforms
+ and "svn blame" shows it is here to fix STR #449.
+ But this STR is Mac OS-specific.
+ So, it is unclear why handle_part2() is called also for X11.
+
+ Furthermore, calling handle_part2() for X11 renders the
+ fix for STR #2619 below necessary. If handle_part2() is not called under X11,
+ then STR #2619 does not occur. need_menu_handle_part1_extra() activates this fix.
+
+ FLTK 1.3.4 behavior:
+ Fl::screen_driver()->need_menu_handle_part2() returns true on Mac + X11
+ Fl::screen_driver()->need_menu_handle_part1_extra() returns true on X11
+
+ Alternative behavior that seems equally correct:
+ Fl::screen_driver()->need_menu_handle_part2() returns true on Mac
+ need_menu_handle_part1_extra() does not exist
+
+ Other alternative:
+ Neither need_menu_handle_part2() nor need_menu_handle_part1_extra() exist
+ --> the menuwindow code is entirely cross-platform and simpler.
+ It makes a small difference with Mac OS when resizing a window with a menu on:
+ the menu disappears after the end of the resize rather than at its beginning.
+ Apple applications do close popups at the beginning of resizes.
+ */
+ static int use_part2 = Fl::screen_driver()->need_menu_handle_part2();
+ int ret = handle_part1(e);
+ if (use_part2) ret = handle_part2(e, ret);
+ return ret;
+}
+
+/* Window event handling implementation.
+ \param[in] e event number
+ \return 1 if the event was used
+ */
+int Menu_Window::handle_part1(int e) {
+ Menu_State &pp = *menu_state;
+ switch (e) {
+ case FL_KEYBOARD:
+ switch (Fl::event_key()) {
+ case FL_BackSpace:
+ BACKTAB:
+ menu_state->backward(pp.menu_number);
+ return 1;
+ case FL_Up:
+ if (pp.menubar && pp.menu_number == 0) {
+ // Do nothing...
+ } else if (menu_state->backward(pp.menu_number)) {
+ // Do nothing...
+ } else if (pp.menubar && pp.menu_number==1) {
+ menu_state->set_item(0, pp.menu_window[0]->selected);
+ }
+ return 1;
+ case FL_Tab:
+ if (Fl::event_shift()) goto BACKTAB;
+ if (pp.menubar && pp.menu_number == 0) goto RIGHT;
+ case FL_Down:
+ if (pp.menu_number || !pp.menubar) {
+ menu_state->forward(pp.menu_number);
+ } else if (pp.menu_number < pp.num_menus-1) {
+ menu_state->forward(pp.menu_number+1);
+ }
+ return 1;
+ case FL_Right:
+ RIGHT:
+ if (pp.menubar && (pp.menu_number<=0 || (pp.menu_number == pp.num_menus-1))) {
+ menu_state->forward(0);
+ } else if (pp.menu_number < pp.num_menus-1) {
+ menu_state->forward(pp.menu_number+1);
+ }
+ return 1;
+ case FL_Left:
+ if (pp.menubar && pp.menu_number<=1) {
+ menu_state->backward(0);
+ } else if (pp.menu_number>0) {
+ menu_state->set_item(pp.menu_number-1, pp.menu_window[pp.menu_number-1]->selected);
+ }
+ return 1;
+ case FL_Enter:
+ case FL_KP_Enter:
+ case ' ':
+ // if the current item is a submenu with no callback,
+ // simulate FL_Right to enter the submenu
+ if ( pp.current_item
+ && (!pp.menubar || pp.menu_number > 0)
+ && pp.current_item->activevisible()
+ && pp.current_item->submenu()
+ && !pp.current_item->callback_)
+ {
+ goto RIGHT;
+ }
+ // Ignore keypresses over inactive items, mark KEYBOARD event as used.
+ if (pp.current_item && !pp.current_item->selectable())
+ return 1;
+ // Mark the menu 'done' which will trigger the callback
+ pp.state = DONE_STATE;
+ return 1;
+ case FL_Escape:
+ menu_state->set_item(0, -1, 0);
+ pp.state = DONE_STATE;
+ return 1;
+ }
+ break;
+ case FL_SHORTCUT:
+ if (menu_state->handle_shortcut()) return 1;
+ break;
+ case FL_MOVE: {
+ static int use_part1_extra = Fl::screen_driver()->need_menu_handle_part1_extra();
+ if (use_part1_extra && pp.state == DONE_STATE) {
+ return 1; // Fix for STR #2619
+ }
+ }
+ /* FALLTHROUGH */
+ case FL_ENTER:
+ case FL_PUSH:
+ case FL_DRAG:
+ {
+ int mx = Fl::event_x_root();
+ int my = Fl::event_y_root();
+ int item=0; int mymenu = pp.num_menus-1;
+ // Clicking or dragging outside menu cancels it...
+ if ((!pp.menubar || mymenu) && !pp.is_inside(mx, my)) {
+ menu_state->set_item(0, -1, 0);
+ if (e==FL_PUSH)
+ pp.state = DONE_STATE;
+ return 1;
+ }
+ for (mymenu = pp.num_menus-1; ; mymenu--) {
+ item = pp.menu_window[mymenu]->find_selected(mx, my);
+ if (item >= 0)
+ break;
+ if (mymenu <= 0) {
+ // buttons in menubars must be deselected if we move outside of them!
+ if (pp.menu_number==-1 && e==FL_PUSH) {
+ pp.state = DONE_STATE;
+ return 1;
+ }
+ if (pp.current_item && pp.menu_number==0 && !pp.current_item->submenu()) {
+ if (e==FL_PUSH) {
+ pp.state = DONE_STATE;
+ menu_state->set_item(0, -1, 0);
+ }
+ return 1;
+ }
+ // all others can stay selected
+ return 0;
+ }
+ }
+ menu_state->set_item(mymenu, item);
+ if (e == FL_PUSH) {
+ if (pp.current_item && pp.current_item->submenu() // this is a menu title
+ && item != pp.menu_window[mymenu]->selected // and it is not already on
+ && !pp.current_item->callback_) // and it does not have a callback
+ pp.state = MENU_PUSH_STATE;
+ else
+ pp.state = PUSH_STATE;
+ }
+ }
+ return 1;
+ case FL_RELEASE:
+ // Mouse must either be held down/dragged some, or this must be
+ // the second click (not the one that popped up the menu):
+ if ( !Fl::event_is_click()
+ || pp.state == PUSH_STATE
+ || (pp.menubar && pp.current_item && !pp.current_item->submenu()) // button
+ ) {
+#if 0 // makes the check/radio items leave the menu up
+ const Fl_Menu_Item* m = pp.current_item;
+ if (m && button && (m->flags & (FL_MENU_TOGGLE|FL_MENU_RADIO))) {
+ ((Fl_Menu_*)button)->picked(m);
+ pp.p[pp.menu_number]->redraw();
+ } else
+#endif
+ // do nothing if they try to pick an inactive item, or a submenu with no callback
+ if (!pp.current_item || (pp.current_item->selectable() &&
+ (!pp.current_item->submenu() || pp.current_item->callback_ || (pp.menubar && pp.menu_number <= 0))))
+ pp.state = DONE_STATE;
+ }
+ return 1;
+ }
+ return Fl_Window::handle(e);
+}
+
+int Menu_Window::handle_part2(int e, int ret) {
+ // This off-route takes care of the "detached menu" bug on OS X (STR #449).
+ // Apple event handler requires that we hide all menu windows right
+ // now, so that Carbon can continue undisturbed with handling window
+ // manager events, like dragging the application window.
+ Menu_State &pp = *menu_state;
+ if (pp.state == DONE_STATE) {
+ hide();
+ if (pp.fake_menu) {
+ pp.fake_menu->hide();
+ if (pp.fake_menu->title)
+ pp.fake_menu->title->hide();
+ }
+ int i = pp.num_menus;
+ while (i>0) {
+ Menu_Window *mw = pp.menu_window[--i];
+ if (mw) {
+ mw->hide();
+ if (mw->title)
+ mw->title->hide();
+ }
+ }
+ }
+ return ret;
+}
+
+/* Set a new selected item.
+ \param[in] n index into visible item list
+ */
+void Menu_Window::set_selected(int n) {
+ if (n != selected) {
+ if ((selected!=-1) && (menu)) {
+ const Fl_Menu_Item *mi = menu->next(selected);
+ if ((mi) && (mi->callback_) && (mi->flags & FL_MENU_CHATTY))
+ mi->do_callback(this, FL_REASON_LOST_FOCUS);
+ }
+ selected = n;
+ if ((selected!=-1) && (menu)) {
+ const Fl_Menu_Item *mi = menu->next(selected);
+ if ((mi) && (mi->callback_) && (mi->flags & FL_MENU_CHATTY))
+ mi->do_callback(this, FL_REASON_GOT_FOCUS);
+ }
+ damage(FL_DAMAGE_CHILD);
+ }
+}
+
+/* Find the item at the give pixel position.
+ \param[in] mx, my position in pixels
+ \return index of item that is under the pixel, or -1 for none
+ */
+int Menu_Window::find_selected(int mx, int my) {
+ if (!menu || !menu->text) return -1;
+ mx -= x();
+ my -= y();
+ if (my < 0 || my >= h()) return -1;
+ if (!item_height) { // menubar
+ int xx = 3; int n = 0;
+ const Fl_Menu_Item* m = menu->first();
+ for (; ; m = m->next(), n++) {
+ if (!m->text) return -1;
+ xx += m->measure(0, button) + 16;
+ if (xx > mx) break;
+ }
+ return n;
+ }
+ if (mx < Fl::box_dx(box()) || mx >= w()) return -1;
+ int n = (my-Fl::box_dx(box())-1)/item_height;
+ if (n < 0 || n>=num_items) return -1;
+ return n;
+}
+
+/* Return horizontal position for item n in a menubar.
+ \return position in window in pixels.
+ */
+int Menu_Window::titlex(int n) {
+ const Fl_Menu_Item* m;
+ int xx = 3;
+ for (m=menu->first(); n--; m = m->next())
+ xx += m->measure(0, button) + 16;
+ return xx;
}
/* Scroll so item i is visible on screen.
May scroll or move the window.
\param[in] n index into visible menu items
*/
-void menuwindow::autoscroll(int n) {
+void Menu_Window::autoscroll(int n) {
int scr_y, scr_h;
- int Y = y()+Fl::box_dx(box())+2+n*itemheight;
+ int Y = y()+Fl::box_dx(box())+2+n*item_height;
int xx, ww;
Fl_Window_Driver::driver(this)->menu_window_area(xx, scr_y, ww, scr_h, this->screen_num());
- if (n==0 && Y <= scr_y + itemheight) {
+ if (n==0 && Y <= scr_y + item_height) {
Y = scr_y - Y + 10;
- } else if (Y <= scr_y + itemheight) {
- Y = scr_y - Y + 10 + itemheight;
+ } else if (Y <= scr_y + item_height) {
+ Y = scr_y - Y + 10 + item_height;
} else {
- Y = Y+itemheight-scr_h-scr_y;
+ Y = Y+item_height-scr_h-scr_y;
if (Y < 0) return;
Y = -Y-10;
}
@@ -602,22 +866,44 @@ void menuwindow::autoscroll(int n) {
// y(y()+Y); // don't wait for response from X
}
-////////////////////////////////////////////////////////////////
+/* Set the position of this menu and its title window. */
+void Menu_Window::position(int X, int Y) {
+ if (title) {
+ title->position(X, title->y()+Y-y());
+ }
+ Fl_Menu_Window::position(X, Y);
+ // x(X); y(Y); // don't wait for response from X
+}
+
+/* Check if mouse is positions over the window.
+ \return 1, if the given root coordinates are inside the window
+ */
+int Menu_Window::is_inside(int mx, int my) {
+ if ( mx < x_root() || mx >= x_root() + w() ||
+ my < y_root() || my >= y_root() + h()) {
+ return 0;
+ }
+ if (item_height == 0 && find_selected(mx, my) == -1) {
+ // in the menubar but out from any menu header
+ return 0;
+ }
+ return 1;
+}
/* Draw one menu item.
\param[in] m pointer to the item
\param[in] n index into the visible item list
\param[in] eraseit if set, redraw the unselected background
*/
-void menuwindow::drawentry(const Fl_Menu_Item* m, int n, int eraseit) {
+void Menu_Window::draw_entry(const Fl_Menu_Item* m, int n, int eraseit) {
if (!m) return; // this happens if -1 is selected item and redrawn
int BW = Fl::box_dx(box());
int xx = BW;
int W = w();
int ww = W-2*BW-1;
- int yy = BW+1+n*itemheight+Fl::menu_linespacing()/2-2;
- int hh = itemheight - Fl::menu_linespacing();
+ int yy = BW+1+n*item_height+Fl::menu_linespacing()/2-2;
+ int hh = item_height - Fl::menu_linespacing();
if (eraseit && n != selected) {
fl_push_clip(xx+1, yy-(Fl::menu_linespacing()-2)/2, ww-2, hh+(Fl::menu_linespacing()-2));
@@ -641,16 +927,16 @@ void menuwindow::drawentry(const Fl_Menu_Item* m, int n, int eraseit) {
} else if (m->shortcut_) {
Fl_Font f = m->labelsize_ || m->labelfont_ ? (Fl_Font)m->labelfont_ :
- button ? button->textfont() : FL_HELVETICA;
+ button ? button->textfont() : FL_HELVETICA;
fl_font(f, m->labelsize_ ? m->labelsize_ :
- button ? button->textsize() : FL_NORMAL_SIZE);
+ button ? button->textsize() : FL_NORMAL_SIZE);
const char *k, *s = fl_shortcut_label(m->shortcut_, &k);
if (fl_utf_nb_char((const unsigned char*)k, (int) strlen(k))<=4) {
// right-align the modifiers and left-align the key
char *buf = (char*)malloc(k-s+1);
memcpy(buf, s, k-s); buf[k-s] = 0;
- fl_draw(buf, xx, yy, ww-shortcutWidth, hh, FL_ALIGN_RIGHT);
- fl_draw( k, xx+ww-shortcutWidth, yy, shortcutWidth, hh, FL_ALIGN_LEFT);
+ fl_draw(buf, xx, yy, ww-shortcut_width, hh, FL_ALIGN_RIGHT);
+ fl_draw( k, xx+ww-shortcut_width, yy, shortcut_width, hh, FL_ALIGN_LEFT);
free(buf);
} else {
// right-align to the menu
@@ -666,18 +952,13 @@ void menuwindow::drawentry(const Fl_Menu_Item* m, int n, int eraseit) {
}
}
-/* Draw the contents of the menutitle window. */
-void menutitle::draw() {
- menu->draw(0, 0, w(), h(), button, 2);
-}
-
/* Draw the menuwindow. If the damage flags are FL_DAMAGE_CHILD, only redraw
the old selected and the newly selected items.
*/
-void menuwindow::draw() {
+void Menu_Window::draw() {
if (damage() != FL_DAMAGE_CHILD) { // complete redraw
- if ( box() != FL_FLAT_BOX && ( Fl::is_scheme( "gtk+" ) ||
- Fl::is_scheme( "plastic") || Fl::is_scheme( "gleam" ) )) {
+ if ( (box() != FL_FLAT_BOX)
+ && (Fl::is_scheme( "gtk+" ) || Fl::is_scheme( "plastic") || Fl::is_scheme( "gleam" ) )) {
// Draw a FL_FLAT_BOX to avoid on macOS the white corners of the menus
fl_draw_box( FL_FLAT_BOX, 0, 0, w(), h(),
button ? button->color() : color());
@@ -685,438 +966,199 @@ void menuwindow::draw() {
fl_draw_box(box(), 0, 0, w(), h(), button ? button->color() : color());
if (menu) {
const Fl_Menu_Item* m; int j;
- for (m=menu->first(), j=0; m->text; j++, m = m->next()) drawentry(m, j, 0);
+ for (m=menu->first(), j=0; m->text; j++, m = m->next())
+ draw_entry(m, j, 0);
}
} else {
if (damage() & FL_DAMAGE_CHILD && selected!=drawn_selected) { // change selection
- drawentry(menu->next(drawn_selected), drawn_selected, 1);
- drawentry(menu->next(selected), selected, 1);
+ draw_entry(menu->next(drawn_selected), drawn_selected, 1);
+ draw_entry(menu->next(selected), selected, 1);
}
}
drawn_selected = selected;
}
-/* Set a new selected item.
- \param[in] n index into visible item list
- */
-void menuwindow::set_selected(int n) {
- if (n != selected) {
- if ((selected!=-1) && (menu)) {
- const Fl_Menu_Item *mi = menu->next(selected);
- if ((mi) && (mi->callback_) && (mi->flags & FL_MENU_CHATTY))
- mi->do_callback(this, FL_REASON_LOST_FOCUS);
- }
- selected = n;
- if ((selected!=-1) && (menu)) {
- const Fl_Menu_Item *mi = menu->next(selected);
- if ((mi) && (mi->callback_) && (mi->flags & FL_MENU_CHATTY))
- mi->do_callback(this, FL_REASON_GOT_FOCUS);
- }
- damage(FL_DAMAGE_CHILD);
- }
-}
-
-////////////////////////////////////////////////////////////////
+//
+// ---- Fl_Menu_Item -----------------------------------------------------------
+//
-/* Find the item at the give pixel position.
- \param[in] mx, my position in pixels
- \return index of item that is under the pixel, or -1 for none
- */
-int menuwindow::find_selected(int mx, int my) {
- if (!menu || !menu->text) return -1;
- mx -= x();
- my -= y();
- if (my < 0 || my >= h()) return -1;
- if (!itemheight) { // menubar
- int xx = 3; int n = 0;
- const Fl_Menu_Item* m = menu->first();
- for (; ; m = m->next(), n++) {
- if (!m->text) return -1;
- xx += m->measure(0, button) + 16;
- if (xx > mx) break;
- }
- return n;
- }
- if (mx < Fl::box_dx(box()) || mx >= w()) return -1;
- int n = (my-Fl::box_dx(box())-1)/itemheight;
- if (n < 0 || n>=numitems) return -1;
- return n;
-}
+/** Size of the menu starting from this menu item.
-/* Return horizontal position for item n in a menubar.
- \return position in window in pixels.
- */
-int menuwindow::titlex(int n) {
- const Fl_Menu_Item* m;
- int xx = 3;
- for (m=menu->first(); n--; m = m->next()) xx += m->measure(0, button) + 16;
- return xx;
-}
+ This method counts all menu items starting with \p this menu item,
+ including all menu items in the same (sub)menu level, all nested
+ submenus, \b and the terminating empty (0) menu item.
-/* Check if mouse is positions over the window.
- \return 1, if the given root coordinates are inside the window
- */
-int menuwindow::is_inside(int mx, int my) {
- if ( mx < x_root() || mx >= x_root() + w() ||
- my < y_root() || my >= y_root() + h()) {
- return 0;
- }
- if (itemheight == 0 && find_selected(mx, my) == -1) {
- // in the menubar but out from any menu header
- return 0;
- }
- return 1;
-}
+ It does \b not count menu items referred to by FL_SUBMENU_POINTER
+ menu items (except the single menu item with FL_SUBMENU_POINTER).
-////////////////////////////////////////////////////////////////
-// Fl_Menu_Item::popup(...)
+ All menu items counted are consecutive in memory (one array).
-// Because Fl::grab() is done, all events go to one of the menu windows.
-// But the handle method needs to look at all of them to find out
-// what item the user is pointing at. And it needs a whole lot
-// of other state variables to determine what is going on with
-// the currently displayed menus.
-// So the main loop (handlemenu()) puts all the state in a structure
-// and puts a pointer to it in a static location, so the handle()
-// on menus can refer to it and alter it. The handle() method
-// changes variables in this state to indicate what item is
-// picked, but does not actually alter the display, instead the
-// main loop does that. This is because the X mapping and unmapping
-// of windows is slow, and we don't want to fall behind the events.
+ Example:
-// values for menustate.state:
-#define INITIAL_STATE 0 // no mouse up or down since popup() called
-#define PUSH_STATE 1 // mouse has been pushed on a normal item
-#define DONE_STATE 2 // exit the popup, the current item was picked
-#define MENU_PUSH_STATE 3 // mouse has been pushed on a menu title
+ \code
+ schemechoice = new Fl_Choice(X+125,Y,140,25,"FLTK Scheme");
+ schemechoice->add("none");
+ schemechoice->add("plastic");
+ schemechoice->add("gtk+");
+ schemechoice->add("gleam");
+ printf("schemechoice->menu()->size() = %d\n", schemechoice->menu()->size());
+ \endcode
-/*
- This class is a singleton that handles the current cascade of menu windows.
- */
-struct menustate {
- const Fl_Menu_Item* current_item; // what mouse is pointing at
- int menu_number; // which menu it is in
- int item_number; // which item in that menu, -1 if none
- menuwindow* p[20]; // pointers to menus
- int nummenus; // number of open menuwindows
- int menubar; // if true p[0] is a menubar
- int state; // INITIAL_STATE, etc. See above
- menuwindow* fakemenu; // kludge for buttons in menubar
- int is_inside(int mx, int my);
-};
-// Global state of menu windows and popup windows.
-static menustate* p=0;
+ Output:
-/* Find out if any menu window is under the mouse.
- \return 1 if the coordinates are inside any of the menuwindows
+ schemechoice->menu()->%size() = 5
*/
-int menustate::is_inside(int mx, int my) {
- int i;
- for (i=nummenus-1; i>=0; i--) {
- if (p[i]->is_inside(mx, my))
- return 1;
+int Fl_Menu_Item::size() const {
+ const Fl_Menu_Item* m = this;
+ int nest = 0;
+ for (;;) {
+ if (!m->text) {
+ if (!nest) return (int) (m-this+1);
+ nest--;
+ } else if (m->flags & FL_SUBMENU) {
+ nest++;
+ }
+ m++;
}
- return 0;
}
-/* Remember this item in the state machine.
- \param[in] i current menu item
- \param[in] m index into menu window array
- \param[in] n index into visible item in that menu window
- */
-static inline void setitem(const Fl_Menu_Item* i, int m, int n) {
- p->current_item = i;
- p->menu_number = m;
- p->item_number = n;
-}
+/* Advance a pointer to next visible or invisible item of a menu array.
-/* Find and store a mneu item in the state machine.
- \param[in] m index into menu window array
- \param[in] n index into visible item in that menu window
- */
-static void setitem(int m, int n) {
- menustate &pp = *p;
- pp.current_item = (n >= 0) ? pp.p[m]->menu->next(n) : 0;
- pp.menu_number = m;
- pp.item_number = n;
-}
+ This function skips over the contents of submenus.
-/* Go down to the next selectable menu item.
- If the event button is FL_Down, increment once, else go to the bottom of the menu.
- \param[in] menu index into menu window list
- \return 1 if an item was found, 0 if the menu wrapped
+ \param[in] m start from this menu item inside an array
+ \return a pointer to the next menu item. If the label() of the returned
+ item is nullptr, the function reached the end of the array and
+ no next item was found.
*/
-static int forward(int menu) { // go to next item in menu menu if possible
- // `menu` is -1 if no item is currently selected, so use the first menu
- if (menu < 0)
- menu = 0;
- menustate &pp = *p;
- menuwindow &m = *(pp.p[menu]);
- int item = (menu == pp.menu_number) ? pp.item_number : m.selected;
- bool wrapped = false;
+static const Fl_Menu_Item* next_visible_or_not(const Fl_Menu_Item* m) {
+ int nest = 0;
do {
- while (++item < m.numitems) {
- const Fl_Menu_Item* m1 = m.menu->next(item);
- if (m1->selectable()) {
- setitem(m1, menu, item);
- return 1;
- }
+ if (!m->text) {
+ if (!nest) return m;
+ nest--;
+ } else if (m->flags&FL_SUBMENU) {
+ nest++;
}
- if (wrapped) break;
- item = -1;
- wrapped = true;
+ m++;
}
- while (Fl::event_key() != FL_Down);
- return 0;
+ while (nest);
+ return m;
}
-/* Go up to the previous selectable menu item.
- If the event button is FL_Up, decrement once, else go to the top of the menu.
- \param[in] menu index into menu window list
- \return 1 if an item was found, 0 if the menu wrapped
+/** Advance a pointer by n items through a menu array.
+
+ This function skips the contents of submenus, and also invisible items.
+ There are two calls so that you can advance through const and non-const data.
+
+ \param[in] n advance by n items
+ \return a pointer to the next menu item. If the label() of the returned
+ item is nullptr, the function reached the end of the array and
+ no next item was found.
*/
-static int backward(int menu) { // previous item in menu menu if possible
- // `menu` is -1 if no item is currently selected, so use the first menu
- if (menu < 0)
- menu = 0;
- menustate &pp = *p;
- menuwindow &m = *(pp.p[menu]);
- int item = (menu == pp.menu_number) ? pp.item_number : m.selected;
- bool wrapped = false;
- do {
- while (--item >= 0) {
- const Fl_Menu_Item* m1 = m.menu->next(item);
- if (m1->selectable()) {
- setitem(m1, menu, item);
- return 1;
- }
- }
- if (wrapped) break;
- item = m.numitems;
- wrapped = true;
+const Fl_Menu_Item* Fl_Menu_Item::next(int n) const {
+ if (n < 0) return 0; // this is so selected==-1 returns NULL
+ const Fl_Menu_Item* m = this;
+ if (!m->visible()) n++;
+ while (n) {
+ m = next_visible_or_not(m);
+ if (m->visible() || !m->text) n--;
}
- while (Fl::event_key() != FL_Up);
- return 0;
+ return m;
}
-/* Handle events sent to the window.
- \param[in] e event number
- \return 1 if the event was used
+/**
+ Measures width of label, including effect of & characters.
+ Optionally, can get height if hp is not NULL.
+ \param[out] hp return the height of the label
+ \param[in] m for this menu item
+ \return width of the label without shortcut text, but including checkbox
+ for radio and check items.
*/
-int menuwindow::handle(int e) {
- /* In FLTK 1.3.4, the equivalent of handle_part2() is called for the Mac OS and X11 platforms
- and "svn blame" shows it is here to fix STR #449.
- But this STR is Mac OS-specific.
- So, it is unclear why handle_part2() is called also for X11.
+int Fl_Menu_Item::measure(int* hp, const Fl_Menu_* m) const {
+ Fl_Label l;
+ l.value = text;
+ l.image = 0;
+ l.deimage = 0;
+ l.type = labeltype_;
+ l.font = labelsize_ || labelfont_ ? labelfont_ : (m ? m->textfont() : FL_HELVETICA);
+ l.size = labelsize_ ? labelsize_ : m ? m->textsize() : FL_NORMAL_SIZE;
+ l.color = FL_FOREGROUND_COLOR; // this makes no difference?
+ l.h_margin_ = l.v_margin_ = l.spacing = 0;
+ fl_draw_shortcut = 1;
+ int w = 0; int h = 0;
+ l.measure(w, hp ? *hp : h);
+ fl_draw_shortcut = 0;
+ if (flags & (FL_MENU_TOGGLE|FL_MENU_RADIO))
+ w += FL_NORMAL_SIZE + 4;
+ return w;
+}
- Furthermore, calling handle_part2() for X11 renders the
- fix for STR #2619 below necessary. If handle_part2() is not called under X11,
- then STR #2619 does not occur. need_menu_handle_part1_extra() activates this fix.
+/**
+ Draws the menu item in the bounding box selected or unselected.
+ This does not draw the shortcut: see menuwindow::drawentry().
+ \param[in] x, y, w, h bounding box for the menu item
+ \param[in] m draw the background, label, and checkbox of this item
+ \param[in] selected 0 = draw unselected, 1 = draw selected, 2 = draw menu title
+ */
+void Fl_Menu_Item::draw(int x, int y, int w, int h, const Fl_Menu_* m,
+ int selected) const {
+ Fl_Label l;
+ l.value = text;
+ l.image = 0;
+ l.deimage = 0;
+ l.type = labeltype_;
+ l.font = labelsize_ || labelfont_ ? labelfont_ : (m ? m->textfont() : FL_HELVETICA);
+ l.size = labelsize_ ? labelsize_ : m ? m->textsize() : FL_NORMAL_SIZE;
+ l.color = labelcolor_ ? labelcolor_ : m ? m->textcolor() : int(FL_FOREGROUND_COLOR);
+ l.h_margin_ = l.v_margin_ = l.spacing = 0;
+ if (!active()) l.color = fl_inactive((Fl_Color)l.color);
+ if (selected) {
+ Fl_Color r = m ? m->selection_color() : FL_SELECTION_COLOR;
+ Fl_Boxtype b = m && m->down_box() ? m->down_box() : FL_FLAT_BOX;
+ l.color = fl_contrast((Fl_Color)labelcolor_, r);
+ if (selected == 2) { // menu title
+ fl_draw_box(b, x, y, w, h, r);
+ x += 3;
+ w -= 8;
+ } else {
+ fl_draw_box(b, x+1, y-(Fl::menu_linespacing()-2)/2, w-2, h+(Fl::menu_linespacing()-2), r);
+ }
+ }
- FLTK 1.3.4 behavior:
- Fl::screen_driver()->need_menu_handle_part2() returns true on Mac + X11
- Fl::screen_driver()->need_menu_handle_part1_extra() returns true on X11
+ if (flags & (FL_MENU_TOGGLE|FL_MENU_RADIO)) {
+ int d = (h - FL_NORMAL_SIZE + 1) / 2;
+ int W = h - 2 * d;
- Alternative behavior that seems equally correct:
- Fl::screen_driver()->need_menu_handle_part2() returns true on Mac
- need_menu_handle_part1_extra() does not exist
+ Fl_Color check_color = labelcolor_;
+ if (Fl::is_scheme("gtk+"))
+ check_color = FL_SELECTION_COLOR;
+ check_color = fl_contrast(check_color, FL_BACKGROUND2_COLOR);
- Other alternative:
- Neither need_menu_handle_part2() nor need_menu_handle_part1_extra() exist
- --> the menuwindow code is entirely cross-platform and simpler.
- It makes a small difference with Mac OS when resizing a window with a menu on:
- the menu disappears after the end of the resize rather than at its beginning.
- Apple applications do close popups at the beginning of resizes.
- */
- static int use_part2 = Fl::screen_driver()->need_menu_handle_part2();
- int ret = handle_part1(e);
- if (use_part2) ret = handle_part2(e, ret);
- return ret;
-}
+ if (flags & FL_MENU_RADIO) {
-int menuwindow::handle_part2(int e, int ret) {
- // This off-route takes care of the "detached menu" bug on OS X (STR #449).
- // Apple event handler requires that we hide all menu windows right
- // now, so that Carbon can continue undisturbed with handling window
- // manager events, like dragging the application window.
- menustate &pp = *p;
- if (pp.state == DONE_STATE) {
- hide();
- if (pp.fakemenu) {
- pp.fakemenu->hide();
- if (pp.fakemenu->title)
- pp.fakemenu->title->hide();
- }
- int i = pp.nummenus;
- while (i>0) {
- menuwindow *mw = pp.p[--i];
- if (mw) {
- mw->hide();
- if (mw->title)
- mw->title->hide();
- }
- }
- }
- return ret;
-}
+ fl_draw_box(FL_ROUND_DOWN_BOX, x+2, y+d, W, W, FL_BACKGROUND2_COLOR);
+ if (value()) {
+ int tW = (W - Fl::box_dw(FL_ROUND_DOWN_BOX)) / 2 + 1;
+ if ((W - tW) & 1) tW++; // Make sure difference is even to center
+ int td = (W - tW) / 2;
+ fl_draw_radio(x + td + 1, y + d + td - 1, tW + 2, check_color);
+ } // FL_MENU_RADIO && value()
-/* Window event handling implementation.
- \param[in] e event number
- \return 1 if the event was used
- */
-int menuwindow::handle_part1(int e) {
- menustate &pp = *p;
- switch (e) {
- case FL_KEYBOARD:
- switch (Fl::event_key()) {
- case FL_BackSpace:
- BACKTAB:
- backward(pp.menu_number);
- return 1;
- case FL_Up:
- if (pp.menubar && pp.menu_number == 0) {
- // Do nothing...
- } else if (backward(pp.menu_number)) {
- // Do nothing...
- } else if (pp.menubar && pp.menu_number==1) {
- setitem(0, pp.p[0]->selected);
- }
- return 1;
- case FL_Tab:
- if (Fl::event_shift()) goto BACKTAB;
- if (pp.menubar && pp.menu_number == 0) goto RIGHT;
- case FL_Down:
- if (pp.menu_number || !pp.menubar) {
- forward(pp.menu_number);
- } else if (pp.menu_number < pp.nummenus-1) {
- forward(pp.menu_number+1);
- }
- return 1;
- case FL_Right:
- RIGHT:
- if (pp.menubar && (pp.menu_number<=0 || (pp.menu_number == pp.nummenus-1)))
- forward(0);
- else if (pp.menu_number < pp.nummenus-1) forward(pp.menu_number+1);
- return 1;
- case FL_Left:
- if (pp.menubar && pp.menu_number<=1) backward(0);
- else if (pp.menu_number>0)
- setitem(pp.menu_number-1, pp.p[pp.menu_number-1]->selected);
- return 1;
- case FL_Enter:
- case FL_KP_Enter:
- case ' ':
- // if the current item is a submenu with no callback,
- // simulate FL_Right to enter the submenu
- if ( pp.current_item
- && (!pp.menubar || pp.menu_number > 0)
- && pp.current_item->activevisible()
- && pp.current_item->submenu()
- && !pp.current_item->callback_)
- {
- goto RIGHT;
- }
- // Ignore keypresses over inactive items, mark KEYBOARD event as used.
- if (pp.current_item && !pp.current_item->selectable())
- return 1;
- // Mark the menu 'done' which will trigger the callback
- pp.state = DONE_STATE;
- return 1;
- case FL_Escape:
- setitem(0, -1, 0);
- pp.state = DONE_STATE;
- return 1;
- }
- break;
- case FL_SHORTCUT:
- {
- for (int mymenu = pp.nummenus; mymenu--;) {
- menuwindow &mw = *(pp.p[mymenu]);
- int item; const Fl_Menu_Item* m = mw.menu->find_shortcut(&item);
- if (m) {
- setitem(m, mymenu, item);
- if (!m->submenu())
- pp.state = DONE_STATE;
- return 1;
- }
- }
- }
- break;
- case FL_MOVE: {
- static int use_part1_extra = Fl::screen_driver()->need_menu_handle_part1_extra();
- if (use_part1_extra && pp.state == DONE_STATE) {
- return 1; // Fix for STR #2619
- }
- }
- /* FALLTHROUGH */
- case FL_ENTER:
- case FL_PUSH:
- case FL_DRAG:
- {
- int mx = Fl::event_x_root();
- int my = Fl::event_y_root();
- int item=0; int mymenu = pp.nummenus-1;
- // Clicking or dragging outside menu cancels it...
- if ((!pp.menubar || mymenu) && !pp.is_inside(mx, my)) {
- setitem(0, -1, 0);
- if (e==FL_PUSH)
- pp.state = DONE_STATE;
- return 1;
- }
- for (mymenu = pp.nummenus-1; ; mymenu--) {
- item = pp.p[mymenu]->find_selected(mx, my);
- if (item >= 0)
- break;
- if (mymenu <= 0) {
- // buttons in menubars must be deselected if we move outside of them!
- if (pp.menu_number==-1 && e==FL_PUSH) {
- pp.state = DONE_STATE;
- return 1;
- }
- if (pp.current_item && pp.menu_number==0 && !pp.current_item->submenu()) {
- if (e==FL_PUSH) {
- pp.state = DONE_STATE;
- setitem(0, -1, 0);
- }
- return 1;
- }
- // all others can stay selected
- return 0;
- }
- }
- setitem(mymenu, item);
- if (e == FL_PUSH) {
- if (pp.current_item && pp.current_item->submenu() // this is a menu title
- && item != pp.p[mymenu]->selected // and it is not already on
- && !pp.current_item->callback_) // and it does not have a callback
- pp.state = MENU_PUSH_STATE;
- else
- pp.state = PUSH_STATE;
+ } else { // FL_MENU_TOGGLE && ! FL_MENU_RADIO
+
+ fl_draw_box(FL_DOWN_BOX, x+2, y+d, W, W, FL_BACKGROUND2_COLOR);
+ if (value()) {
+ fl_draw_check(Fl_Rect(x+3, y+d+1, W-2, W-2), check_color);
}
}
- return 1;
- case FL_RELEASE:
- // Mouse must either be held down/dragged some, or this must be
- // the second click (not the one that popped up the menu):
- if ( !Fl::event_is_click()
- || pp.state == PUSH_STATE
- || (pp.menubar && pp.current_item && !pp.current_item->submenu()) // button
- ) {
-#if 0 // makes the check/radio items leave the menu up
- const Fl_Menu_Item* m = pp.current_item;
- if (m && button && (m->flags & (FL_MENU_TOGGLE|FL_MENU_RADIO))) {
- ((Fl_Menu_*)button)->picked(m);
- pp.p[pp.menu_number]->redraw();
- } else
-#endif
- // do nothing if they try to pick an inactive item, or a submenu with no callback
- if (!pp.current_item || (pp.current_item->selectable() &&
- (!pp.current_item->submenu() || pp.current_item->callback_ || (pp.menubar && pp.menu_number <= 0))))
- pp.state = DONE_STATE;
- }
- return 1;
+ x += W + 3;
+ w -= W + 3;
}
- return Fl_Window::handle(e);
+
+ if (!fl_draw_shortcut) fl_draw_shortcut = 1;
+ l.draw(x+3, y, w>6 ? w-6 : 0, h, FL_ALIGN_LEFT);
+ fl_draw_shortcut = 0;
}
/**
@@ -1137,7 +1179,8 @@ const Fl_Menu_Item* Fl_Menu_Item::pulldown(
const Fl_Menu_Item* initial_item,
const Fl_Menu_* pbutton,
const Fl_Menu_Item* title,
- int menubar) const {
+ int menubar) const
+{
Fl_Group::current(0); // fix possible user error...
// track the Fl_Menu_ widget to make sure we notice if it gets
@@ -1146,7 +1189,7 @@ const Fl_Menu_Item* Fl_Menu_Item::pulldown(
button = pbutton;
if (pbutton && pbutton->window()) {
- menuwindow::parent_ = pbutton->top_window();
+ Menu_Window::parent_ = pbutton->top_window();
for (Fl_Window* w = pbutton->window(); w; w = w->window()) {
X += w->x();
Y += w->y();
@@ -1154,26 +1197,26 @@ const Fl_Menu_Item* Fl_Menu_Item::pulldown(
} else {
X += Fl::event_x_root()-Fl::event_x();
Y += Fl::event_y_root()-Fl::event_y();
- menuwindow::parent_ = Fl::first_window();
+ Menu_Window::parent_ = Fl::first_window();
}
int XX, YY, WW;
- Fl::screen_xywh(XX, YY, WW, menuwindow::display_height_, menuwindow::parent_->screen_num());
- menuwindow mw(this, X, Y, W, H, initial_item, title, menubar);
+ Fl::screen_xywh(XX, YY, WW, Menu_Window::display_height_, Menu_Window::parent_->screen_num());
+ Menu_Window mw(this, X, Y, W, H, initial_item, title, menubar);
Fl::grab(mw);
// If we grab the mouse pointer, we should also make sure that it is visible.
- if (menuwindow::parent_)
- menuwindow::parent_->cursor(FL_CURSOR_DEFAULT);
- menustate pp; p = &pp;
- pp.p[0] = &mw;
- pp.nummenus = 1;
+ if (Menu_Window::parent_)
+ Menu_Window::parent_->cursor(FL_CURSOR_DEFAULT);
+ Menu_State pp; menu_state = &pp;
+ pp.menu_window[0] = &mw;
+ pp.num_menus = 1;
pp.menubar = menubar;
pp.state = INITIAL_STATE;
- pp.fakemenu = 0; // kludge for buttons in menubar
+ pp.fake_menu = 0; // kludge for buttons in menubar
// preselected item, pop up submenus if necessary:
if (initial_item && mw.selected >= 0) {
- setitem(0, mw.selected);
+ menu_state->set_item(0, mw.selected);
goto STARTUP;
}
@@ -1200,10 +1243,10 @@ const Fl_Menu_Item* Fl_Menu_Item::pulldown(
// make sure all the menus are shown:
{
- for (int k = menubar; k < pp.nummenus; k++) {
- if (!pp.p[k]->shown()) {
- if (pp.p[k]->title) pp.p[k]->title->show();
- pp.p[k]->show();
+ for (int k = menubar; k < pp.num_menus; k++) {
+ if (!pp.menu_window[k]->shown()) {
+ if (pp.menu_window[k]->title) pp.menu_window[k]->title->show();
+ pp.menu_window[k]->show();
}
}
}
@@ -1219,20 +1262,26 @@ const Fl_Menu_Item* Fl_Menu_Item::pulldown(
}
// only do rest if item changes:
- if(pp.fakemenu) {delete pp.fakemenu; pp.fakemenu = 0;} // turn off "menubar button"
+ if (pp.fake_menu) {
+ delete pp.fake_menu;
+ pp.fake_menu = nullptr;
+ } // turn off "menubar button"
if (!pp.current_item) { // pointing at nothing
// turn off selection in deepest menu, but don't erase other menus:
- pp.p[pp.nummenus-1]->set_selected(-1);
+ pp.menu_window[pp.num_menus-1]->set_selected(-1);
continue;
}
- if(pp.fakemenu) {delete pp.fakemenu; pp.fakemenu = 0;}
+ if (pp.fake_menu) {
+ delete pp.fake_menu;
+ pp.fake_menu = nullptr;
+ }
initial_item = 0; // stop the startup code
- pp.p[pp.menu_number]->autoscroll(pp.item_number);
+ pp.menu_window[pp.menu_number]->autoscroll(pp.item_number);
STARTUP:
- menuwindow& cw = *pp.p[pp.menu_number];
+ Menu_Window& cw = *pp.menu_window[pp.menu_number];
const Fl_Menu_Item* m = pp.current_item;
if (!m->selectable()) { // pointing at inactive item
cw.set_selected(-1);
@@ -1245,8 +1294,10 @@ const Fl_Menu_Item* Fl_Menu_Item::pulldown(
if (m->submenu()) {
const Fl_Menu_Item* title = m;
const Fl_Menu_Item* menutable;
- if (m->flags&FL_SUBMENU) menutable = m+1;
- else menutable = (Fl_Menu_Item*)(m)->user_data_;
+ if (m->flags&FL_SUBMENU)
+ menutable = m+1;
+ else
+ menutable = (Fl_Menu_Item*)(m)->user_data_;
// figure out where new menu goes:
int nX, nY;
if (!pp.menu_number && pp.menubar) { // menu off a menubar:
@@ -1255,13 +1306,14 @@ const Fl_Menu_Item* Fl_Menu_Item::pulldown(
initial_item = 0;
} else {
nX = cw.x() + cw.w();
- nY = cw.y() + pp.item_number * cw.itemheight;
+ nY = cw.y() + pp.item_number * cw.item_height;
title = 0;
}
if (initial_item) { // bring up submenu containing initial item:
- menuwindow* n = new menuwindow(menutable,X,Y,W,H,initial_item,title,0,0,cw.x());
- pp.p[pp.nummenus++] = n;
- if (pp.nummenus >= 2) pp.p[pp.nummenus-1]->origin = pp.p[pp.nummenus-2];
+ Menu_Window* n = new Menu_Window(menutable,X,Y,W,H,initial_item,title,0,0,cw.x());
+ pp.menu_window[pp.num_menus++] = n;
+ if (pp.num_menus >= 2)
+ pp.menu_window[pp.num_menus-1]->origin = pp.menu_window[pp.num_menus-2];
// move all earlier menus to line up with this new one:
if (n->selected>=0) {
int dy = n->y()-nY;
@@ -1269,47 +1321,49 @@ const Fl_Menu_Item* Fl_Menu_Item::pulldown(
int waX, waY, waW, waH;
Fl_Window_Driver::driver(n)->menu_window_area(waX, waY, waW, waH, Fl::screen_num(X, Y));
for (int menu = 0; menu <= pp.menu_number; menu++) {
- menuwindow* tt = pp.p[menu];
+ Menu_Window* tt = pp.menu_window[menu];
int nx = tt->x()+dx; if (nx < waX) {nx = waX; dx = -tt->x() + waX;}
int ny = tt->y()+dy; if (ny < waY) {ny = waY; dy = -tt->y() + waY;}
tt->position(nx, ny);
}
- setitem(pp.nummenus-1, n->selected);
+ menu_state->set_item(pp.num_menus-1, n->selected);
goto STARTUP;
}
- } else if (pp.nummenus > pp.menu_number+1 &&
- pp.p[pp.menu_number+1]->menu == menutable) {
+ } else if (pp.num_menus > pp.menu_number+1 &&
+ pp.menu_window[pp.menu_number+1]->menu == menutable) {
// the menu is already up:
- while (pp.nummenus > pp.menu_number+2) delete pp.p[--pp.nummenus];
- pp.p[pp.nummenus-1]->set_selected(-1);
+ while (pp.num_menus > pp.menu_number+2) delete pp.menu_window[--pp.num_menus];
+ pp.menu_window[pp.num_menus-1]->set_selected(-1);
} else {
// delete all the old menus and create new one:
- while (pp.nummenus > pp.menu_number+1) delete pp.p[--pp.nummenus];
- pp.p[pp.nummenus++]= new menuwindow(menutable, nX, nY,
+ while (pp.num_menus > pp.menu_number+1) delete pp.menu_window[--pp.num_menus];
+ pp.menu_window[pp.num_menus++] = new Menu_Window(menutable, nX, nY,
title?1:0, 0, 0, title, 0, menubar,
(title ? 0 : cw.x()) );
- if (pp.nummenus >= 2 && pp.p[pp.nummenus-2]->itemheight) {
- pp.p[pp.nummenus-1]->origin = pp.p[pp.nummenus-2];
+ if (pp.num_menus >= 2 && pp.menu_window[pp.num_menus-2]->item_height) {
+ pp.menu_window[pp.num_menus-1]->origin = pp.menu_window[pp.num_menus-2];
}
}
} else { // !m->submenu():
- while (pp.nummenus > pp.menu_number+1) delete pp.p[--pp.nummenus];
+ while (pp.num_menus > pp.menu_number+1)
+ delete pp.menu_window[--pp.num_menus];
if (!pp.menu_number && pp.menubar) {
// kludge so "menubar buttons" turn "on" by using menu title:
- pp.fakemenu = new menuwindow(0,
+ pp.fake_menu = new Menu_Window(0,
cw.x()+cw.titlex(pp.item_number),
cw.y()+cw.h(), 0, 0,
0, m, 0, 1);
- pp.fakemenu->title->show();
+ pp.fake_menu->title->show();
}
}
}
const Fl_Menu_Item* m = (pbutton && wp.deleted()) ? NULL : pp.current_item;
- delete pp.fakemenu;
- while (pp.nummenus>1) delete pp.p[--pp.nummenus];
+ delete pp.fake_menu;
+ while (pp.num_menus>1)
+ delete pp.menu_window[--pp.num_menus];
mw.hide();
Fl::grab(0);
- menuwindow::parent_ = NULL;
+ Menu_Window::parent_ = NULL;
return m;
}
@@ -1351,34 +1405,30 @@ const Fl_Menu_Item* Fl_Menu_Item::popup(
return pulldown(X, Y, 0, 0, picked, menu_button, title ? &dummy : 0);
}
-static bool is_special_labeltype(uchar t) {
- return t == _FL_MULTI_LABEL || t == _FL_ICON_LABEL || t == _FL_IMAGE_LABEL;
-}
-
/**
- Search only the top level menu for a shortcut.
- Either &x in the label or the shortcut fields are used.
+ Search only the top level menu for a shortcut.
+ Either &x in the label or the shortcut fields are used.
- This tests the current event, which must be an FL_KEYBOARD or
- FL_SHORTCUT, against a shortcut value.
+ This tests the current event, which must be an FL_KEYBOARD or
+ FL_SHORTCUT, against a shortcut value.
- \param ip returns the index of the item, if \p ip is not NULL.
- \param require_alt if true: match only if Alt key is pressed.
+ \param ip returns the index of the item, if \p ip is not NULL.
+ \param require_alt if true: match only if Alt key is pressed.
- \return found Fl_Menu_Item or NULL
-*/
+ \return found Fl_Menu_Item or NULL
+ */
const Fl_Menu_Item* Fl_Menu_Item::find_shortcut(int* ip, const bool require_alt) const {
const Fl_Menu_Item* m = this;
if (m) for (int ii = 0; m->text; m = next_visible_or_not(m), ii++) {
if (m->active()) {
if (Fl::test_shortcut(m->shortcut_)
- || (!is_special_labeltype(m->labeltype_) && Fl_Widget::test_shortcut(m->text, require_alt))
- || (m->labeltype_ == _FL_MULTI_LABEL
- && !is_special_labeltype(((Fl_Multi_Label*)m->text)->typea)
- && Fl_Widget::test_shortcut(((Fl_Multi_Label*)m->text)->labela, require_alt))
- || (m->labeltype_ == _FL_MULTI_LABEL
- && !is_special_labeltype(((Fl_Multi_Label*)m->text)->typeb)
- && Fl_Widget::test_shortcut(((Fl_Multi_Label*)m->text)->labelb, require_alt))) {
+ || (!is_special_labeltype(m->labeltype_) && Fl_Widget::test_shortcut(m->text, require_alt))
+ || (m->labeltype_ == _FL_MULTI_LABEL
+ && !is_special_labeltype(((Fl_Multi_Label*)m->text)->typea)
+ && Fl_Widget::test_shortcut(((Fl_Multi_Label*)m->text)->labela, require_alt))
+ || (m->labeltype_ == _FL_MULTI_LABEL
+ && !is_special_labeltype(((Fl_Multi_Label*)m->text)->typeb)
+ && Fl_Widget::test_shortcut(((Fl_Multi_Label*)m->text)->labelb, require_alt))) {
if (ip) *ip=ii;
return m;
}
@@ -1390,13 +1440,13 @@ const Fl_Menu_Item* Fl_Menu_Item::find_shortcut(int* ip, const bool require_alt)
// Recursive search of all submenus for anything with this key as a
// shortcut. Only uses the shortcut field, ignores &x in the labels:
/**
- This is designed to be called by a widgets handle() method in
- response to a FL_SHORTCUT event. If the current event matches
- one of the items shortcut, that item is returned. If the keystroke
- does not match any shortcuts then NULL is returned. This only
- matches the shortcut() fields, not the letters in the title
- preceeded by '
-*/
+ This is designed to be called by a widgets handle() method in
+ response to a FL_SHORTCUT event. If the current event matches
+ one of the items shortcut, that item is returned. If the keystroke
+ does not match any shortcuts then NULL is returned. This only
+ matches the shortcut() fields, not the letters in the title
+ preceeded by '
+ */
const Fl_Menu_Item* Fl_Menu_Item::test_shortcut() const {
const Fl_Menu_Item* m = this;
const Fl_Menu_Item* ret = 0;
@@ -1408,10 +1458,104 @@ const Fl_Menu_Item* Fl_Menu_Item::test_shortcut() const {
// only return matches from lower menu if nothing found in top menu:
if (!ret && m->submenu()) {
const Fl_Menu_Item* s =
- (m->flags&FL_SUBMENU) ? m+1:(const Fl_Menu_Item*)m->user_data_;
+ (m->flags&FL_SUBMENU) ? m+1:(const Fl_Menu_Item*)m->user_data_;
ret = s->test_shortcut();
}
}
}
return ret;
}
+
+//
+// ---- Fl_Window_Driver -------------------------------------------------------
+//
+
+/**
+ \cond DriverDev
+ \addtogroup DriverDeveloper
+ \{
+ */
+
+/** The Fl_Window from which currently displayed popups originate.
+ Optionally, gives also the height of the display containing this window.
+ \parmm[out]display_height return the height of the display here.
+ \return pointe to the owning window
+*/
+Fl_Window *Fl_Window_Driver::menu_parent(int *display_height) {
+ if (display_height) *display_height = Menu_Window::display_height_;
+ return Menu_Window::parent_;
+}
+
+/* Cast to menuwindow if win is of claa menuwindow and the driver is initialized. */
+static Menu_Window *to_menuwindow(Fl_Window *win) {
+ if (!Fl_Window_Driver::driver(win)->popup_window() || !win->menu_window()) return nullptr;
+ return ((Menu_Window_Basetype*)win)->as_menuwindow();
+}
+
+/** Accessor to the "origin" member variable of class menuwindow.
+ Variable origin is not NULL when 2 menuwindow's occur, one being a submenu of the other;
+ it links the menuwindow at right to the one at left.
+*/
+Fl_Window *Fl_Window_Driver::menu_leftorigin(Fl_Window *win) {
+ Menu_Window *mwin = to_menuwindow(win);
+ return (mwin ? mwin->origin : NULL);
+}
+
+/** Accessor to the "title" member variable of class menuwindow */
+Fl_Window *Fl_Window_Driver::menu_title(Fl_Window *win) {
+ Menu_Window *mwin = to_menuwindow(win);
+ return (mwin ? mwin->title : NULL);
+}
+
+/** Accessor to the "itemheight" member variable of class menuwindow */
+int Fl_Window_Driver::menu_itemheight(Fl_Window *win) {
+ Menu_Window *mwin = to_menuwindow(win);
+ return (mwin ? mwin->item_height : 0);
+}
+
+/** Accessor to the "menubartitle" member variable of class menuwindow */
+int Fl_Window_Driver::menu_bartitle(Fl_Window *win) {
+ Menu_Window *mwin = to_menuwindow(win);
+ return (mwin ? mwin->menubar_title : 0);
+}
+
+/** Accessor to the "selected" member variable of class menuwindow */
+int Fl_Window_Driver::menu_selected(Fl_Window *win) {
+ Menu_Window *mwin = to_menuwindow(win);
+ return (mwin ? mwin->selected : -1);
+}
+
+/** Accessor to the address of the offset_y member variable of class menuwindow */
+int *Fl_Window_Driver::menu_offset_y(Fl_Window *win) {
+ Menu_Window *mwin = to_menuwindow(win);
+ return (mwin ? &(mwin->offset_y) : NULL);
+}
+
+/** Returns whether win is a non-menubar menutitle */
+bool Fl_Window_Driver::is_floating_title(Fl_Window *win) {
+ if (!Fl_Window_Driver::driver(win)->popup_window() || !win->menu_window()) return false;
+ Fl_Window *mwin = ((Menu_Window_Basetype*)win)->as_menuwindow();
+ return !mwin && !((Menu_Title_Window*)win)->in_menubar;
+}
+
+/** Makes sure that the tall menu's selected item is visible in display */
+void Fl_Window_Driver::scroll_to_selected_item(Fl_Window *win) {
+ Menu_Window *mwin = to_menuwindow(win);
+ if (mwin && mwin->selected > 0) {
+ mwin->autoscroll(mwin->selected);
+ }
+}
+
+/**
+ \}
+ \endcond
+ */
+
+//
+// ---- helper functions -------------------------------------------------------
+//
+
+static bool is_special_labeltype(uchar t) {
+ return t == _FL_MULTI_LABEL || t == _FL_ICON_LABEL || t == _FL_IMAGE_LABEL;
+}
+