diff options
| -rw-r--r-- | FL/Fl_Menu_Item.H | 26 | ||||
| -rw-r--r-- | fluid/nodes/Menu_Node.cxx | 5 | ||||
| -rw-r--r-- | fluid/nodes/Widget_Node.cxx | 3 | ||||
| -rw-r--r-- | fluid/nodes/Widget_Node.h | 6 | ||||
| -rw-r--r-- | fluid/panels/widget_panel.cxx | 47 | ||||
| -rw-r--r-- | fluid/panels/widget_panel.fl | 59 | ||||
| -rw-r--r-- | src/Fl_MacOS_Sys_Menu_Bar.mm | 16 | ||||
| -rw-r--r-- | src/Fl_Menu.cxx | 18 | ||||
| -rw-r--r-- | test/menubar.cxx | 3 |
9 files changed, 153 insertions, 30 deletions
diff --git a/FL/Fl_Menu_Item.H b/FL/Fl_Menu_Item.H index 6d086ea8b..4dab9c624 100644 --- a/FL/Fl_Menu_Item.H +++ b/FL/Fl_Menu_Item.H @@ -36,8 +36,10 @@ enum { // values for flags: FL_SUBMENU = 0x40, ///< Item is a submenu to other items FL_MENU_DIVIDER = 0x80, ///< Creates divider line below this item. Also ends a group of radio buttons FL_MENU_HORIZONTAL = 0x100, ///< reserved, do not use - FL_MENU_CHATTY = 0x200 ///< Menu Item receives additional callbacks - ///< Note: \b ALL other bits in \p flags are reserved: do not use them for your own purposes! + FL_MENU_CHATTY = 0x200, ///< Menu Item receives additional callbacks + FL_MENU_HEADLINE = 0x400, ///< Mark menu item as a headline + ///< + ///< Note: All other bits in `flags` are reserved: do not use them for your own purposes! }; class Fl_Menu_; @@ -460,9 +462,29 @@ struct FL_EXPORT Fl_Menu_Item { the menu item to appear grayed-out. */ void deactivate() {flags |= FL_MENU_INACTIVE;} + /** Returns non 0 if FL_INACTIVE and FL_INVISIBLE are cleared, 0 otherwise. */ int activevisible() const {return !(flags & (FL_MENU_INACTIVE|FL_MENU_INVISIBLE));} + /** Returns non-0 if the menu item can be selected. + Return 0 if the menu is hidden, inactive, or a headline. + */ + int selectable() const {return !(flags & (FL_MENU_INACTIVE|FL_MENU_INVISIBLE|FL_MENU_HEADLINE));} + + /** + Set menu item as a headline. + A menu item can be designated as a headline. Headline items are not selectable, + but they are also not grayed out. To visually distinguish them, add + indentation and apply bold or italic styling. + */ + void headline(bool yes) { + if (yes) flags |= FL_MENU_HEADLINE; else flags &= ~FL_MENU_HEADLINE; + } + /** + Return non 0 if this menu item is used as a headline. + */ + int headline() const { return (flags & FL_MENU_HEADLINE); } + // compatibility for FLUID so it can set the image of a menu item... /** Compatibility API for FLUID, same as image->label(this). diff --git a/fluid/nodes/Menu_Node.cxx b/fluid/nodes/Menu_Node.cxx index f5555644c..342fcf014 100644 --- a/fluid/nodes/Menu_Node.cxx +++ b/fluid/nodes/Menu_Node.cxx @@ -128,7 +128,7 @@ void Input_Choice_Node::build_menu() { } m->shortcut(((Fl_Button*)(i->o))->shortcut()); m->callback(nullptr,(void*)i); - m->flags = i->flags(); + m->flags = i->flags() & ~FL_MENU_HEADLINE; m->labelfont(i->o->labelfont()); m->labelsize(i->o->labelsize()); m->labelcolor(i->o->labelcolor()); @@ -467,6 +467,7 @@ int Menu_Item_Node::flags() { else i |= FL_SUBMENU_POINTER; } if (hotspot()) i |= FL_MENU_DIVIDER; + if (menu_headline()) i |= FL_MENU_HEADLINE; return i; } @@ -697,7 +698,7 @@ void Menu_Base_Node::build_menu() { } m->shortcut(((Fl_Button*)(i->o))->shortcut()); m->callback(nullptr,(void*)i); - m->flags = i->flags() | i->o->type(); + m->flags = (i->flags() | i->o->type()) & ~FL_MENU_HEADLINE; m->labelfont(i->o->labelfont()); m->labelsize(i->o->labelsize()); m->labelcolor(i->o->labelcolor()); diff --git a/fluid/nodes/Widget_Node.cxx b/fluid/nodes/Widget_Node.cxx index 2082941ce..3d076b29b 100644 --- a/fluid/nodes/Widget_Node.cxx +++ b/fluid/nodes/Widget_Node.cxx @@ -2007,6 +2007,7 @@ void Widget_Node::write_properties(fld::io::Project_Writer &f) { if (!o->active()) f.write_string("deactivate"); if (resizable()) f.write_string("resizable"); if (hotspot()) f.write_string(is_a(Type::Menu_Item) ? "divider" : "hotspot"); + if (menu_headline()) f.write_string("headline"); for (int n=0; n < NUM_EXTRA_CODE; n++) if (extra_code(n)) { f.write_indent(level+1); f.write_string("code%d",n); @@ -2179,6 +2180,8 @@ void Widget_Node::read_property(fld::io::Project_Reader &f, const char *c) { resizable(1); } else if (!strcmp(c,"hotspot") || !strcmp(c, "divider")) { hotspot(1); + } else if (!strcmp(c,"headline")) { + menu_headline(true); } else if (!strcmp(c,"class")) { subclass(f.read_word()); } else if (!strcmp(c,"shortcut")) { diff --git a/fluid/nodes/Widget_Node.h b/fluid/nodes/Widget_Node.h index 261005590..fd0b7ddbe 100644 --- a/fluid/nodes/Widget_Node.h +++ b/fluid/nodes/Widget_Node.h @@ -51,6 +51,8 @@ class Widget_Node : public Node const char *inactive_name_; uchar hotspot_; + bool menu_headline_ { false }; + protected: /// This variable is set for visible windows in batch mode. @@ -97,11 +99,15 @@ public: void image_name(const char *); const char *inactive_name() const {return inactive_name_;} void inactive_name(const char *); + // Note: hotspot is misused by menu items to indicate a divider uchar hotspot() const {return hotspot_;} void hotspot(uchar v) {hotspot_ = v;} uchar resizable() const; void resizable(uchar v); + bool menu_headline() const { return menu_headline_; } + void menu_headline(bool v) { menu_headline_ = v; } + virtual int textstuff(int what, Fl_Font &, int &, Fl_Color &); virtual Fl_Menu_Item *subtypes(); diff --git a/fluid/panels/widget_panel.cxx b/fluid/panels/widget_panel.cxx index 86bff8e62..049667d69 100644 --- a/fluid/panels/widget_panel.cxx +++ b/fluid/panels/widget_panel.cxx @@ -620,7 +620,7 @@ static void cb_Browse1(Fl_Button* o, void* v) { Fl_Group *wp_gui_alignment=(Fl_Group *)0; Fl_Menu_Item menu_[] = { - {" Image Alignment ", 0, 0, (void*)((fl_intptr_t)0xFFFFFFFF), 1, (uchar)FL_NORMAL_LABEL, 2, 10, 0}, + {" Image Alignment ", 0, 0, (void*)((fl_intptr_t)-1), 1024, (uchar)FL_NORMAL_LABEL, 1, 10, 0}, {"image over text", 0, 0, (void*)((fl_intptr_t)FL_ALIGN_IMAGE_OVER_TEXT), 0, (uchar)FL_NORMAL_LABEL, 0, 9, 0}, {"text over image", 0, 0, (void*)((fl_intptr_t)FL_ALIGN_TEXT_OVER_IMAGE), 0, (uchar)FL_NORMAL_LABEL, 0, 9, 0}, {"text next to image", 0, 0, (void*)((fl_intptr_t)FL_ALIGN_TEXT_NEXT_TO_IMAGE), 0, (uchar)FL_NORMAL_LABEL, 0, 9, 0}, @@ -630,7 +630,7 @@ Fl_Menu_Item menu_[] = { }; Fl_Menu_Item menu_1[] = { - {" Inside && Outside ", 0, 0, (void*)((fl_intptr_t)0xFFFFFFFF), 1, (uchar)FL_NORMAL_LABEL, 2, 10, 0}, + {" Inside && Outside ", 0, 0, (void*)((fl_intptr_t)-1), 1024, (uchar)FL_NORMAL_LABEL, 1, 10, 0}, {"top left", 0, 0, (void*)((fl_intptr_t)FL_ALIGN_TOP_LEFT), 0, (uchar)FL_NORMAL_LABEL, 0, 9, 0}, {"top", 0, 0, (void*)((fl_intptr_t)FL_ALIGN_TOP), 0, (uchar)FL_NORMAL_LABEL, 0, 9, 0}, {"top right", 0, 0, (void*)((fl_intptr_t)FL_ALIGN_TOP_RIGHT), 0, (uchar)FL_NORMAL_LABEL, 0, 9, 0}, @@ -640,7 +640,7 @@ Fl_Menu_Item menu_1[] = { {"bottom left", 0, 0, (void*)((fl_intptr_t)FL_ALIGN_BOTTOM_LEFT), 0, (uchar)FL_NORMAL_LABEL, 0, 9, 0}, {"bottom", 0, 0, (void*)((fl_intptr_t)FL_ALIGN_BOTTOM), 0, (uchar)FL_NORMAL_LABEL, 0, 9, 0}, {"bottom right", 0, 0, (void*)((fl_intptr_t)FL_ALIGN_BOTTOM_RIGHT), 0, (uchar)FL_NORMAL_LABEL, 0, 9, 0}, - {" Outside Alignment ", 0, 0, (void*)((fl_intptr_t)0xFFFFFFFF), 1, (uchar)FL_NORMAL_LABEL, 2, 10, 0}, + {" Outside Alignment ", 0, 0, (void*)((fl_intptr_t)-1), 1024, (uchar)FL_NORMAL_LABEL, 1, 10, 0}, {"left top", 0, 0, (void*)((fl_intptr_t)FL_ALIGN_LEFT_TOP), 0, (uchar)FL_NORMAL_LABEL, 0, 9, 0}, {"right top", 0, 0, (void*)((fl_intptr_t)FL_ALIGN_RIGHT_TOP), 0, (uchar)FL_NORMAL_LABEL, 0, 9, 0}, {"left bottom", 0, 0, (void*)((fl_intptr_t)FL_ALIGN_LEFT_BOTTOM), 0, (uchar)FL_NORMAL_LABEL, 0, 9, 0}, @@ -1454,9 +1454,12 @@ static void cb_Active(Fl_Light_Button* o, void* v) { static void cb_Resizable(Fl_Light_Button* o, void* v) { if (v == LOAD) { - if (current_widget->is_a(Type::Menu_Item)) {o->deactivate(); return;} + if (current_widget->is_a(Type::Menu_Item)) { + o->hide(); + return; + } if (numselected > 1) {o->deactivate(); return;} - o->activate(); + o->show(); o->value(current_widget->resizable()); } else { Fluid.proj.undo.checkpoint(); @@ -1465,6 +1468,31 @@ static void cb_Resizable(Fl_Light_Button* o, void* v) { } } +static void cb_Headline(Fl_Light_Button* o, void* v) { + if (v == LOAD) { + if (!current_widget->is_a(Type::Menu_Item)) { + o->hide(); + return; + } + o->show(); + o->value(current_widget->menu_headline()); + } else { + int mod = 0; + int n = o->value(); + for (Widget_Node *q: Fluid.proj.tree.all_selected_widgets()) { + if (q->is_a(Type::Menu_Item)) { + if (!mod) { + mod = 1; + Fluid.proj.undo.checkpoint(); + } + q->menu_headline(n); + q->redraw(); + } + } + if (mod) Fluid.proj.set_modflag(1); + } +} + static void cb_Hotspot(Fl_Light_Button* o, void* v) { if (v == LOAD) { if (numselected > 1) {o->deactivate(); return;} @@ -2735,6 +2763,14 @@ Fl_Double_Window* make_widget_panel() { o->callback((Fl_Callback*)cb_Resizable); o->when(FL_WHEN_CHANGED); } // Fl_Light_Button* o + { Fl_Light_Button* o = new Fl_Light_Button(225, 260, 75, 20, "Headline"); + o->tooltip("Make a menu item the headline of a menu\nunselectable, but not grayed out"); + o->selection_color((Fl_Color)1); + o->labelsize(11); + o->callback((Fl_Callback*)cb_Headline); + o->when(FL_WHEN_CHANGED); + o->hide(); + } // Fl_Light_Button* o { Fl_Light_Button* o = new Fl_Light_Button(305, 260, 70, 20, "Hotspot"); o->tooltip("Center the window under this widget."); o->selection_color((Fl_Color)1); @@ -2744,7 +2780,6 @@ Fl_Double_Window* make_widget_panel() { } // Fl_Light_Button* o { Fl_Box* o = new Fl_Box(395, 260, 0, 20); o->labelsize(11); - Fl_Group::current()->resizable(o); } // Fl_Box* o wp_gui_attributes->end(); } // Fl_Group* wp_gui_attributes diff --git a/fluid/panels/widget_panel.fl b/fluid/panels/widget_panel.fl index b120ec28f..2c2e75a08 100644 --- a/fluid/panels/widget_panel.fl +++ b/fluid/panels/widget_panel.fl @@ -2,6 +2,7 @@ version 1.0500 header_name {.h} code_name {.cxx} +include_guard {} snap { ver 1 current_suite FLTK @@ -25,7 +26,7 @@ comment {// } {in_source in_header } -decl {\#include <stdlib.h> // free()} {selected private global +decl {\#include <stdlib.h> // free()} {private global } decl {\#include "panels/widget_panel/Grid_Child_Tab.h"} {public global @@ -630,13 +631,13 @@ Use Ctrl-J for newlines.} xywh {95 40 190 20} labelfont 1 labelsize 11 when 15 t tooltip {Bottom-align the label.} xywh {353 115 20 20} type Toggle selection_color 8 labelsize 11 labelcolor 8 hide } Fl_Choice {} { - callback align_text_image_cb + callback align_text_image_cb open xywh {172 115 116 20} down_box BORDER_BOX labelsize 11 textsize 11 } { MenuItem {} { label { Image Alignment } - user_data {(fl_intptr_t)0xFFFFFFFF} - xywh {145 145 100 20} labelfont 2 labelsize 10 deactivate + user_data {(fl_intptr_t)-1} selected + xywh {145 145 100 20} labelfont 1 labelsize 10 headline } MenuItem {} { label {image over text} @@ -665,13 +666,13 @@ Use Ctrl-J for newlines.} xywh {95 40 190 20} labelfont 1 labelsize 11 when 15 t } } Fl_Choice {} { - callback align_position_cb + callback align_position_cb open xywh {293 115 86 20} down_box BORDER_BOX labelsize 11 textsize 11 } { MenuItem {} { label { Inside && Outside } - user_data {(fl_intptr_t)0xFFFFFFFF} - xywh {135 135 100 20} labelfont 2 labelsize 10 deactivate + user_data {(fl_intptr_t)-1} + xywh {135 135 100 20} labelfont 1 labelsize 10 headline } MenuItem {} { label {top left} @@ -720,8 +721,8 @@ Use Ctrl-J for newlines.} xywh {95 40 190 20} labelfont 1 labelsize 11 when 15 t } MenuItem {} { label { Outside Alignment } - user_data {(fl_intptr_t)0xFFFFFFFF} - xywh {125 125 100 20} labelfont 2 labelsize 10 deactivate + user_data {(fl_intptr_t)-1} + xywh {125 125 100 20} labelfont 1 labelsize 10 headline } MenuItem {} { label {left top} @@ -756,7 +757,7 @@ Use Ctrl-J for newlines.} xywh {95 40 190 20} labelfont 1 labelsize 11 when 15 t } Fl_Group {} { label {Position:} - callback position_group_cb + callback position_group_cb open xywh {95 150 314 20} labelfont 1 labelsize 11 align 4 } { Fl_Input widget_x_input { @@ -1574,7 +1575,7 @@ Use 'Backspace' key to clear.} xywh {95 210 310 20} box DOWN_BOX color 7 selecti } Fl_Group wp_gui_attributes { label {Attributes:} - callback propagate_load + callback propagate_load open xywh {95 260 305 20} labelfont 1 labelsize 11 align 4 } { Fl_Light_Button {} { @@ -1632,9 +1633,12 @@ Use 'Backspace' key to clear.} xywh {95 210 310 20} box DOWN_BOX color 7 selecti Fl_Light_Button {} { label Resizable callback {if (v == LOAD) { - if (current_widget->is_a(Type::Menu_Item)) {o->deactivate(); return;} + if (current_widget->is_a(Type::Menu_Item)) { + o->hide(); + return; + } if (numselected > 1) {o->deactivate(); return;} - o->activate(); + o->show(); o->value(current_widget->resizable()); } else { Fluid.proj.undo.checkpoint(); @@ -1644,6 +1648,33 @@ Use 'Backspace' key to clear.} xywh {95 210 310 20} box DOWN_BOX color 7 selecti tooltip {Make the widget resizable.} xywh {225 260 75 20} selection_color 1 labelsize 11 when 1 } Fl_Light_Button {} { + label Headline + callback {if (v == LOAD) { + if (!current_widget->is_a(Type::Menu_Item)) { + o->hide(); + return; + } + o->show(); + o->value(current_widget->menu_headline()); +} else { + int mod = 0; + int n = o->value(); + for (Widget_Node *q: Fluid.proj.tree.all_selected_widgets()) { + if (q->is_a(Type::Menu_Item)) { + if (!mod) { + mod = 1; + Fluid.proj.undo.checkpoint(); + } + q->menu_headline(n); + q->redraw(); + } + } + if (mod) Fluid.proj.set_modflag(1); +}} + tooltip {Make a menu item the headline of a menu +unselectable, but not grayed out} xywh {225 260 75 20} selection_color 1 labelsize 11 when 1 hide + } + Fl_Light_Button {} { label Hotspot callback {if (v == LOAD) { if (numselected > 1) {o->deactivate(); return;} @@ -1672,7 +1703,7 @@ Use 'Backspace' key to clear.} xywh {95 210 310 20} box DOWN_BOX color 7 selecti tooltip {Center the window under this widget.} xywh {305 260 70 20} selection_color 1 labelsize 11 when 1 } Fl_Box {} { - xywh {395 260 0 20} labelsize 11 resizable + xywh {395 260 0 20} labelsize 11 } } Fl_Input wp_gui_tooltip { diff --git a/src/Fl_MacOS_Sys_Menu_Bar.mm b/src/Fl_MacOS_Sys_Menu_Bar.mm index 87c5089cd..f41db31b4 100644 --- a/src/Fl_MacOS_Sys_Menu_Bar.mm +++ b/src/Fl_MacOS_Sys_Menu_Bar.mm @@ -229,6 +229,14 @@ const char *Fl_Mac_App_Menu::quit = "Quit %@"; FLMenuItem *item = [[FLMenuItem alloc] initWithTitle:title action:selector keyEquivalent:@""]; + if (mitem->labelfont() & FL_BOLD) { + NSFont *boldFont = [NSFont boldSystemFontOfSize:[NSFont systemFontSize]]; + NSAttributedString *attributed = + [ [ [NSAttributedString alloc] initWithString:title + attributes:@{ NSFontAttributeName: boldFont }] + autorelease]; + item.attributedTitle = attributed; + } // >= 0 if mitem is in the menu items of fl_sys_menu_bar, -1 if not NSInteger index = (fl_sys_menu_bar ? fl_sys_menu_bar->find_index(mitem) : -1); [item setTag:index]; @@ -369,6 +377,14 @@ static void createSubMenu( NSMenu *mh, pFl_Menu_Item &mm, const Fl_Menu_Item *m cnt = (int)[mh numberOfItems]; cnt--; menuItem = [mh itemAtIndex:cnt]; + if (mitem->labelfont() & FL_BOLD) { + NSFont *boldFont = [NSFont boldSystemFontOfSize:[NSFont systemFontSize]]; + NSAttributedString *attributed = + [ [ [NSAttributedString alloc] initWithString:title + attributes:@{ NSFontAttributeName: boldFont }] + autorelease]; + menuItem.attributedTitle = attributed; + } [menuItem setSubmenu:submenu]; [submenu release]; } else submenu = mh; diff --git a/src/Fl_Menu.cxx b/src/Fl_Menu.cxx index f938f50d3..3ace3e495 100644 --- a/src/Fl_Menu.cxx +++ b/src/Fl_Menu.cxx @@ -727,7 +727,10 @@ static int forward(int menu) { // go to next item in menu menu if possible do { while (++item < m.numitems) { const Fl_Menu_Item* m1 = m.menu->next(item); - if (m1->activevisible()) {setitem(m1, menu, item); return 1;} + if (m1->selectable()) { + setitem(m1, menu, item); + return 1; + } } if (wrapped) break; item = -1; @@ -748,7 +751,10 @@ static int backward(int menu) { // previous item in menu menu if possible do { while (--item >= 0) { const Fl_Menu_Item* m1 = m.menu->next(item); - if (m1->activevisible()) {setitem(m1, menu, item); return 1;} + if (m1->selectable()) { + setitem(m1, menu, item); + return 1; + } } if (wrapped) break; item = m.numitems; @@ -868,7 +874,7 @@ int menuwindow::handle_part1(int e) { goto RIGHT; } // Ignore keypresses over inactive items, mark KEYBOARD event as used. - if (pp.current_item && !pp.current_item->activevisible()) + if (pp.current_item && !pp.current_item->selectable()) return 1; // Mark the menu 'done' which will trigger the callback pp.state = DONE_STATE; @@ -961,7 +967,7 @@ int menuwindow::handle_part1(int e) { } 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->activevisible() && + 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; } @@ -1038,7 +1044,7 @@ const Fl_Menu_Item* Fl_Menu_Item::pulldown( } initial_item = pp.current_item; if (initial_item) { - if (menubar && !initial_item->activevisible()) { // pointing at inactive item + if (menubar && !initial_item->selectable()) { // pointing at inactive item Fl::grab(0); return NULL; } @@ -1085,7 +1091,7 @@ const Fl_Menu_Item* Fl_Menu_Item::pulldown( STARTUP: menuwindow& cw = *pp.p[pp.menu_number]; const Fl_Menu_Item* m = pp.current_item; - if (!m->activevisible()) { // pointing at inactive item + if (!m->selectable()) { // pointing at inactive item cw.set_selected(-1); initial_item = 0; // turn off startup code continue; diff --git a/test/menubar.cxx b/test/menubar.cxx index 355921eba..8295af8e1 100644 --- a/test/menubar.cxx +++ b/test/menubar.cxx @@ -133,6 +133,7 @@ Fl_Menu_Item menutable[] = { {"Size", 0, 0}, {0}, {"&Checkbox",FL_F+3,0,0,FL_SUBMENU}, + {" Greek: ", 0, 0, nullptr, FL_MENU_HEADLINE, 0, FL_BOLD }, {"&Alpha", FL_F+2, 0, (void *)1, FL_MENU_TOGGLE}, {"&Beta", 0, 0, (void *)2, FL_MENU_TOGGLE}, {"&Gamma", 0, 0, (void *)3, FL_MENU_TOGGLE}, @@ -140,8 +141,10 @@ Fl_Menu_Item menutable[] = { {"&Epsilon",0, 0, (void *)5, FL_MENU_TOGGLE}, {"&Pi", 0, 0, (void *)6, FL_MENU_TOGGLE}, {"&Mu", 0, 0, (void *)7, FL_MENU_TOGGLE|FL_MENU_DIVIDER}, + {" Colors: ", 0, 0, nullptr, FL_MENU_HEADLINE, 0, FL_BOLD }, {"Red", 0, 0, (void *)1, FL_MENU_TOGGLE, 0, 0, 0, 1}, {"Black", 0, 0, (void *)1, FL_MENU_TOGGLE|FL_MENU_DIVIDER}, + {" Digits: ", 0, 0, nullptr, FL_MENU_HEADLINE, 0, FL_BOLD }, {"00", 0, 0, (void *)1, FL_MENU_TOGGLE}, {"000", 0, 0, (void *)1, FL_MENU_TOGGLE}, {0}, |
