]> git.lyx.org Git - lyx.git/blob - src/frontends/xforms/XFormsMenubar.C
add possibility to specify the origin of a FuncRequest; use it to get rid of the...
[lyx.git] / src / frontends / xforms / XFormsMenubar.C
1 /**
2  * \file XFormsMenubar.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author  Lars Gullik Bjønnes
7  * \author Jean-Marc Lasgouttes
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "XFormsMenubar.h"
15
16 #include "XFormsView.h"
17
18 #include "debug.h"
19 #include "gettext.h"
20 #include "lyxfunc.h"
21 #include "MenuBackend.h"
22
23 #include "support/lstrings.h"
24 #include "support/tostr.h"
25
26 #include <boost/bind.hpp>
27
28 #include "lyx_forms.h"
29
30 using std::distance;
31 using std::endl;
32 using std::for_each;
33 using std::string;
34 using std::vector;
35
36 namespace lyx {
37
38 using support::lowercase;
39 using support::subst;
40
41 namespace frontend {
42
43 typedef vector<int>::size_type size_type;
44
45 namespace {
46
47 // Some constants
48 int const MENU_LABEL_SIZE = FL_NORMAL_SIZE;
49 int const MENU_LABEL_STYLE = FL_NORMAL_STYLE;
50 int const mheight = 30;
51 int const mbheight= 22;
52 // where to place the menubar?
53 int const yloc = (mheight - mbheight)/2; //air + bw;
54 int const mbadd = 20; // menu button add (to width)
55 // Some space between buttons on the menubar
56 int const air = 2;
57 char const * menu_tabstop = "aa";
58 char const * default_tabstop = "aaaaaaaa";
59 // We do not want to mix position values in a menu (like the index of
60 // a submenu) with the action numbers which convey actual information.
61 // Therefore we offset all the action values by an arbitrary large
62 // constant.
63 int const action_offset = 1000;
64
65 // This is used a few times below.
66 inline
67 int string_width(string const & str)
68 {
69         return fl_get_string_widthTAB(MENU_LABEL_STYLE, MENU_LABEL_SIZE,
70                                       str.c_str(),
71                                       static_cast<int>(str.length()));
72 }
73
74 } // namespace anon
75
76
77 extern "C" {
78
79         //Defined later, used in makeMenubar().
80         static
81         void C_XFormsMenubar_MenuCallback(FL_OBJECT * ob, long button)
82         {
83                 XFormsMenubar::MenuCallback(ob, button);
84         }
85
86 }
87
88
89 XFormsMenubar::XFormsMenubar(LyXView * view, MenuBackend const & mb)
90         : owner_(static_cast<XFormsView*>(view)),
91           menubackend_(&mb),
92           menubar_(0)
93 {
94         owner_->metricsUpdated.connect(boost::bind(&WidgetMap::updateMetrics,
95                                                    &widgets_));
96
97         makeMenubar(menubackend_->getMenubar());
98 }
99
100
101 XFormsMenubar::~XFormsMenubar()
102 {}
103
104
105 void XFormsMenubar::makeMenubar(Menu const & menu)
106 {
107         // Draw a frame around the whole.
108         BoxList & boxlist = owner_->getBox(XFormsView::Top).children();
109
110         FL_OBJECT * frame = fl_add_frame(FL_UP_FRAME, 0, 0, 0, 0, "");
111         fl_set_object_resize(frame, FL_RESIZE_ALL);
112         fl_set_object_gravity(frame, NorthWestGravity, NorthEastGravity);
113
114         menubar_ = &widgets_.add(frame, boxlist, 0, mheight);
115
116         // The menubar contains three vertically-aligned Boxes,
117         // the center one of which is to contain the buttons,
118         // aligned horizontally.
119         // The other two provide some visual padding.
120         menubar_->children().push_back(Box(0, yloc));
121         Box & menubar_center = menubar_->children().push_back(Box(0,0));
122         menubar_center.set(Box::Horizontal);
123         menubar_->children().push_back(Box(0, yloc));
124
125         BoxList & menubar_buttons = menubar_center.children();
126
127         // Add the buttons.
128         Menu::const_iterator i = menu.begin();
129         Menu::const_iterator end = menu.end();
130         for (; i != end; ++i) {
131                 FL_OBJECT * obj;
132                 if (i->kind() != MenuItem::Submenu) {
133                         lyxerr << "ERROR: XFormsMenubar::createMenubar:"
134                                 " only submenus can appear in a menubar"
135                                << endl;
136                         continue;
137                 }
138                 string const label = i->label();
139                 string const shortcut = '#' + i->shortcut();
140                 int const width = string_width(label);
141
142                 obj = fl_add_button(FL_MENU_BUTTON, 0, 0, 0, 0, label.c_str());
143
144                 menubar_buttons.push_back(Box(air, 0));
145                 widgets_.add(obj, menubar_buttons, width + mbadd, mbheight);
146
147                 fl_set_object_boxtype(obj, FL_FLAT_BOX);
148                 fl_set_object_color(obj, FL_MCOL, FL_MCOL);
149                 fl_set_object_lsize(obj, MENU_LABEL_SIZE);
150                 fl_set_object_lstyle(obj, MENU_LABEL_STYLE);
151                 fl_set_object_resize(obj, FL_RESIZE_ALL);
152                 fl_set_object_gravity(obj, NorthWestGravity,
153                                       NorthWestGravity);
154                 fl_set_object_shortcut(obj, shortcut.c_str(), 1);
155                 fl_set_object_callback(obj, C_XFormsMenubar_MenuCallback, 1);
156
157                 boost::shared_ptr<ItemInfo>
158                         iteminfo(new ItemInfo(this, new MenuItem(*i), obj));
159                 buttonlist_.push_back(iteminfo);
160                 obj->u_vdata = iteminfo.get();
161         }
162 }
163
164
165 void XFormsMenubar::update()
166 {
167         // nothing yet
168 }
169
170
171 void XFormsMenubar::openByName(string const & name)
172 {
173         for (ButtonList::const_iterator cit = buttonlist_.begin();
174              cit != buttonlist_.end(); ++cit) {
175                 if ((*cit)->item_->submenuname() == name) {
176                         MenuCallback((*cit)->obj_, 1);
177                         return;
178                 }
179         }
180
181         lyxerr << "XFormsMenubar::openByName: menu "
182                << name << " not found" << endl;
183 }
184
185
186 namespace {
187
188 Menu::size_type const max_number_of_items = 25;
189
190 int get_new_submenu(vector<int> & smn, Window win)
191 {
192         static size_type max_number_of_menus = 32;
193         if (smn.size() >= max_number_of_menus)
194                 max_number_of_menus =
195                     fl_setpup_maxpup(static_cast<int>(2*smn.size()));
196         int menu = fl_newpup(win);
197         fl_setpup_softedge(menu, true);
198         fl_setpup_bw(menu, -1);
199         smn.push_back(menu);
200         return menu;
201 }
202
203
204 string const fixlabel(string const & str)
205 {
206         return subst(str, "%", "%%");
207 }
208
209
210
211 } // namespace anon
212
213
214
215 int XFormsMenubar::create_submenu(Window win, XFormsView * view,
216                                   Menu const & menu,
217                                   vector<int> & smn, Funcs & funcs)
218 {
219         int const menuid = get_new_submenu(smn, win);
220         lyxerr[Debug::GUI] << "XFormsMenubar::create_submenu: creating "
221                            << menu.name() << " as menuid=" << menuid << endl;
222
223         // Compute the size of the largest label (because xforms is
224         // not able to support shortcuts correctly...)
225         int max_width = 0;
226         string widest_label;
227         Menu::const_iterator end = menu.end();
228         for (Menu::const_iterator i = menu.begin(); i != end; ++i) {
229                 MenuItem const & item = (*i);
230                 if (item.kind() == MenuItem::Command) {
231                         string const label = item.label() + '\t';
232                         int const width = string_width(label);
233                         if (width > max_width) {
234                                 max_width = width;
235                                 widest_label = label;
236                         }
237                 }
238         }
239         lyxerr[Debug::GUI] << "max_width=" << max_width
240                            << ", widest_label=\"" << widest_label
241                            << '"' << endl;
242
243         size_type count = 0;
244         int curmenuid = menuid;
245         for (Menu::const_iterator i = menu.begin(); i != end; ++i) {
246                 MenuItem const & item = (*i);
247
248                 ++count;
249                 // add a More... submenu if the menu is too long (but
250                 // not just for one extra entry!)
251                 if (count > max_number_of_items && (i + 1) != end) {
252                         int tmpmenuid = get_new_submenu(smn, win);
253                         lyxerr[Debug::GUI] << "Too many items, creating "
254                                            << "new menu " << tmpmenuid << endl;
255                         string label = _("More");
256                         label += "...%m";
257                         fl_addtopup(curmenuid, label.c_str(), tmpmenuid);
258                         count = 0;
259                         curmenuid = tmpmenuid;
260                 }
261
262                 switch (item.kind()) {
263                 case MenuItem::Command:
264                 case MenuItem::Submenu:
265                 {
266                         // Build the menu label from all the info
267                         string label = fixlabel(item.label());
268                         FuncStatus const flag = item.status();
269                         int submenuid = 0;
270
271                         // Is there a key binding?
272                         string const binding = item.binding();
273                         if (!binding.empty()) {
274                                 // Try to be clever and add  just enough
275                                 // tabs to align shortcuts.
276                                 do
277                                         label += '\t';
278                                 while (string_width(label) < max_width + 5);
279                                 label += binding;
280                         }
281
282                         // Is there a separator after the item?
283                         if ((i + 1) != end
284                             && (i + 1)->kind() == MenuItem::Separator)
285                                 label += "%l";
286
287                         // Modify the entry using the function status
288                         if (flag.onoff(true))
289                                 label += "%B";
290                         if (flag.onoff(false))
291                                 label += "%b";
292                         if (!flag.enabled())
293                                 label += "%i";
294
295                         // Add the shortcut
296                         string shortcut = item.shortcut();
297                         if (!shortcut.empty()) {
298                                 shortcut += lowercase(shortcut[0]);
299                                 label += "%h";
300                         }
301
302                         // Finally add the action/submenu
303                         if (item.kind() == MenuItem::Submenu) {
304                                 // create the submenu
305                                 submenuid =
306                                         create_submenu(win, view,
307                                                        *item.submenu(), smn, funcs);
308                                 if (submenuid == -1)
309                                         return -1;
310                                 label += "%x" + tostr(smn.size());
311                                 lyxerr[Debug::GUI]
312                                         << "Menu: " << submenuid
313                                         << " (at index " << smn.size()
314                                         << "), ";
315                         } else {
316                                 // Add the action
317                                 Funcs::iterator fit =
318                                         funcs.insert(funcs.end(), item.func());
319                                 int const action_count =
320                                         distance(funcs.begin(), fit);
321
322                                 label += "%x" + tostr(action_count + action_offset);
323                                 lyxerr[Debug::GUI] << "Action: \""
324                                                    << item.func().action
325                                                    << "(" << item.func().argument
326                                                    << ")\", ";
327                         }
328
329                         // Add everything to the menu
330                         fl_addtopup(curmenuid, label.c_str(),
331                                     shortcut.c_str());
332                         if (item.kind() == MenuItem::Submenu)
333                                 fl_setpup_submenu(curmenuid, smn.size(),
334                                                   submenuid);
335
336                         lyxerr[Debug::GUI] << "label \"" << label
337                                            << "\", binding \"" << binding
338                                            << "\", shortcut \"" << shortcut
339                                            << "\" (added to menu "
340                                            << curmenuid << ')' << endl;
341                         break;
342                 }
343
344                 case MenuItem::Separator:
345                         // already done, and if it was the first one,
346                         // we just ignore it.
347                         --count;
348                         break;
349
350
351                 default:
352                         lyxerr << "XFormsMenubar::create_submenu: "
353                                 "this should not happen" << endl;
354                         break;
355                 }
356         }
357         return menuid;
358 }
359
360
361 void XFormsMenubar::MenuCallback(FL_OBJECT * ob, long button)
362 {
363         ItemInfo * iteminfo = static_cast<ItemInfo *>(ob->u_vdata);
364         XFormsView * view = iteminfo->menubar_->owner_;
365         MenuItem const * item = iteminfo->item_.get();
366
367         if (button == 1) {
368                 // set the pseudo menu-button
369                 fl_set_object_boxtype(ob, FL_DOWN_BOX);
370                 fl_set_button(ob, 0);
371                 fl_redraw_object(ob);
372         }
373
374         // Paranoia check
375         BOOST_ASSERT(item->kind() == MenuItem::Submenu);
376
377         // set tabstop length
378         fl_set_tabstop(menu_tabstop);
379
380         MenuBackend const * menubackend_ = iteminfo->menubar_->menubackend_;
381         Menu tomenu;
382         Menu const frommenu = menubackend_->getMenu(item->submenuname());
383         menubackend_->expand(frommenu, tomenu, view);
384         Funcs funcs;
385         vector<int> submenus;
386         int menu = iteminfo->menubar_->create_submenu(FL_ObjWin(ob), view,
387                                                     tomenu, submenus, funcs);
388         if (menu != -1) {
389                 // place popup
390                 fl_setpup_position(view->getForm()->x + ob->x,
391                                    view->getForm()->y + ob->y + ob->h + 10);
392                 int choice = fl_dopup(menu);
393
394                 if (button == 1) {
395                                 // set the pseudo menu-button back
396                         fl_set_object_boxtype(ob, FL_FLAT_BOX);
397                         fl_redraw_object(ob);
398                 }
399
400                 // If the action value is too low, then it is not a
401                 // valid action, but something else.
402                 if (choice >= action_offset) {
403                         view->getLyXFunc().dispatch(funcs[choice - action_offset]);
404                 } else {
405                         lyxerr[Debug::GUI]
406                                 << "MenuCallback: ignoring bogus action "
407                                 << choice << endl;
408                 }
409         } else {
410                 lyxerr << "Error in MenuCallback" << endl;
411         }
412
413         for_each(submenus.begin(), submenus.end(), fl_freepup);
414         // restore tabstop length
415         fl_set_tabstop(default_tabstop);
416
417 }
418
419
420 XFormsMenubar::ItemInfo::ItemInfo
421         (XFormsMenubar * p, MenuItem const * i, FL_OBJECT * o)
422         : menubar_(p), obj_(o)
423 {
424         item_.reset(i);
425 }
426
427
428 XFormsMenubar::ItemInfo::~ItemInfo()
429 {}
430
431 } // namespace frontend
432 } // namespace lyx