1 //
2 // "$Id: Fl_Menu.cxx 8775 2011-06-03 14:07:52Z manolo $"
3 //
4 // Menu code for the Fast Light Tool Kit (FLTK).
5 //
6 // Copyright 1998-2010 by Bill Spitzak and others.
7 //
8 // This library is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU Library General Public
10 // License as published by the Free Software Foundation; either
11 // version 2 of the License, or (at your option) any later version.
12 //
13 // This library is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 // Library General Public License for more details.
17 //
18 // You should have received a copy of the GNU Library General Public
19 // License along with this library; if not, write to the Free Software
20 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
21 // USA.
22 //
23 // Please report all bugs and problems on the following page:
24 //
25 //     http://www.fltk.org/str.php
26 //
27 
28 // Warning: this menu code is quite a mess!
29 
30 // This file contains code for implementing Fl_Menu_Item, and for
31 // methods for bringing up popup menu hierarchies without using the
32 // Fl_Menu_ widget.
33 
34 #include <FL/Fl.H>
35 #include <FL/Fl_Menu_Window.H>
36 #include <FL/Fl_Menu_.H>
37 #include <FL/fl_draw.H>
38 #include <FL/x.H>
39 #include <stdio.h>
40 #include "flstring.h"
41 
42 /** Size of the menu starting from this menu item */
size() const43 int Fl_Menu_Item::size() const {
44   const Fl_Menu_Item* m = this;
45   int nest = 0;
46   for (;;) {
47     if (!m->text) {
48       if (!nest) return (m-this+1);
49       nest--;
50     } else if (m->flags & FL_SUBMENU) {
51       nest++;
52     }
53     m++;
54   }
55 }
56 
57 // Advance a pointer to next visible or invisible item of a menu array,
58 // skipping the contents of submenus.
next_visible_or_not(const Fl_Menu_Item * m)59 static const Fl_Menu_Item* next_visible_or_not(const Fl_Menu_Item* m) {
60   int nest = 0;
61   do {
62     if (!m->text) {
63       if (!nest) return m;
64       nest--;
65     } else if (m->flags&FL_SUBMENU) {
66       nest++;
67     }
68     m++;
69   }
70   while (nest);
71   return m;
72 }
73 
74 /**
75   Advance a pointer by n items through a menu array, skipping
76   the contents of submenus and invisible items.  There are two calls so
77   that you can advance through const and non-const data.
78 */
next(int n) const79 const Fl_Menu_Item* Fl_Menu_Item::next(int n) const {
80   if (n < 0) return 0; // this is so selected==-1 returns NULL
81   const Fl_Menu_Item* m = this;
82   if (!m->visible()) n++;
83   while (n) {
84     m = next_visible_or_not(m);
85     if (m->visible()) n--;
86   }
87   return m;
88 }
89 
90 // appearance of current menus are pulled from this parent widget:
91 static const Fl_Menu_* button=0;
92 
93 ////////////////////////////////////////////////////////////////
94 
95 // tiny window for title of menu:
96 class menutitle : public Fl_Menu_Window {
97   void draw();
98 public:
99   const Fl_Menu_Item* menu;
100   menutitle(int X, int Y, int W, int H, const Fl_Menu_Item*);
101 };
102 
103 // each vertical menu has one of these:
104 class menuwindow : public Fl_Menu_Window {
105   void draw();
106   void drawentry(const Fl_Menu_Item*, int i, int erase);
107 public:
108   menutitle* title;
109   int handle(int);
110 #if defined (__APPLE__) || defined (USE_X11)
111   int early_hide_handle(int);
112 #endif
113   int itemheight;	// zero == menubar
114   int numitems;
115   int selected;
116   int drawn_selected;	// last redraw has this selected
117   int shortcutWidth;
118   const Fl_Menu_Item* menu;
119   menuwindow(const Fl_Menu_Item* m, int X, int Y, int W, int H,
120 	     const Fl_Menu_Item* picked, const Fl_Menu_Item* title,
121 	     int menubar = 0, int menubar_title = 0, int right_edge = 0);
122   ~menuwindow();
123   void set_selected(int);
124   int find_selected(int mx, int my);
125   int titlex(int);
126   void autoscroll(int);
127   void position(int x, int y);
128   int is_inside(int x, int y);
129 };
130 
131 #define LEADING 4 // extra vertical leading
132 
133 extern char fl_draw_shortcut;
134 
135 /**
136   Measures width of label, including effect of & characters.
137   Optionally, can get height if hp is not NULL.
138 */
measure(int * hp,const Fl_Menu_ * m) const139 int Fl_Menu_Item::measure(int* hp, const Fl_Menu_* m) const {
140   Fl_Label l;
141   l.value   = text;
142   l.image   = 0;
143   l.deimage = 0;
144   l.type    = labeltype_;
145   l.font    = labelsize_ || labelfont_ ? labelfont_ : (m ? m->textfont() : FL_HELVETICA);
146   l.size    = labelsize_ ? labelsize_ : m ? m->textsize() : FL_NORMAL_SIZE;
147   l.color   = FL_FOREGROUND_COLOR; // this makes no difference?
148   fl_draw_shortcut = 1;
149   int w = 0; int h = 0;
150   l.measure(w, hp ? *hp : h);
151   fl_draw_shortcut = 0;
152   if (flags & (FL_MENU_TOGGLE|FL_MENU_RADIO)) w += 14;
153   return w;
154 }
155 
156 /** Draws the menu item in bounding box x,y,w,h, optionally selects the item. */
draw(int x,int y,int w,int h,const Fl_Menu_ * m,int selected) const157 void Fl_Menu_Item::draw(int x, int y, int w, int h, const Fl_Menu_* m,
158 			int selected) const {
159   Fl_Label l;
160   l.value   = text;
161   l.image   = 0;
162   l.deimage = 0;
163   l.type    = labeltype_;
164   l.font    = labelsize_ || labelfont_ ? labelfont_ : (m ? m->textfont() : FL_HELVETICA);
165   l.size    = labelsize_ ? labelsize_ : m ? m->textsize() : FL_NORMAL_SIZE;
166   l.color   = labelcolor_ ? labelcolor_ : m ? m->textcolor() : int(FL_FOREGROUND_COLOR);
167 
168   if (!active()) l.color = fl_inactive((Fl_Color)l.color);
169   Fl_Color color = m ? m->color() : FL_GRAY;
170   if (selected) {
171     Fl_Color r = m ? m->selection_color() : FL_SELECTION_COLOR;
172     Fl_Boxtype b = m && m->down_box() ? m->down_box() : FL_FLAT_BOX;
173     if (fl_contrast(r,color)!=r) { // back compatibility boxtypes
174       if (selected == 2) { // menu title
175 	r = color;
176 	b = m ? m->box() : FL_UP_BOX;
177       } else {
178 	r = (Fl_Color)(FL_COLOR_CUBE-1); // white
179 	l.color = fl_contrast((Fl_Color)labelcolor_, r);
180       }
181     } else {
182       l.color = fl_contrast((Fl_Color)labelcolor_, r);
183     }
184     if (selected == 2) { // menu title
185       fl_draw_box(b, x, y, w, h, r);
186       x += 3;
187       w -= 8;
188     } else {
189       fl_draw_box(b, x+1, y-(LEADING-2)/2, w-2, h+(LEADING-2), r);
190     }
191   }
192 
193   if (flags & (FL_MENU_TOGGLE|FL_MENU_RADIO)) {
194     int d = (h - FL_NORMAL_SIZE + 1) / 2;
195     int W = h - 2 * d;
196 
197     if (flags & FL_MENU_RADIO) {
198       fl_draw_box(FL_ROUND_DOWN_BOX, x+2, y+d, W, W, FL_BACKGROUND2_COLOR);
199       if (value()) {
200 	int tW = (W - Fl::box_dw(FL_ROUND_DOWN_BOX)) / 2 + 1;
201 	if ((W - tW) & 1) tW++;	// Make sure difference is even to center
202 	int td = Fl::box_dx(FL_ROUND_DOWN_BOX) + 1;
203         if (Fl::scheme()) {
204 	  // Offset the radio circle...
205 	  td ++;
206 
207 	  if (!strcmp(Fl::scheme(), "gtk+")) {
208 	    fl_color(FL_SELECTION_COLOR);
209 	    tW --;
210 	    fl_pie(x + td + 1, y + d + td - 1, tW + 3, tW + 3, 0.0, 360.0);
211 	    fl_arc(x + td + 1, y + d + td - 1, tW + 3, tW + 3, 0.0, 360.0);
212 	    fl_color(fl_color_average(FL_WHITE, FL_SELECTION_COLOR, 0.2f));
213 	  } else fl_color(labelcolor_);
214 	} else fl_color(labelcolor_);
215 
216 	switch (tW) {
217 	  // Larger circles draw fine...
218 	  default :
219             fl_pie(x + td + 2, y + d + td, tW, tW, 0.0, 360.0);
220 	    break;
221 
222           // Small circles don't draw well on many systems...
223 	  case 6 :
224 	    fl_rectf(x + td + 4, y + d + td, tW - 4, tW);
225 	    fl_rectf(x + td + 3, y + d + td + 1, tW - 2, tW - 2);
226 	    fl_rectf(x + td + 2, y + d + td + 2, tW, tW - 4);
227 	    break;
228 
229 	  case 5 :
230 	  case 4 :
231 	  case 3 :
232 	    fl_rectf(x + td + 3, y + d + td, tW - 2, tW);
233 	    fl_rectf(x + td + 2, y + d + td + 1, tW, tW - 2);
234 	    break;
235 
236 	  case 2 :
237 	  case 1 :
238 	    fl_rectf(x + td + 2, y + d + td, tW, tW);
239 	    break;
240 	}
241 
242 	if (Fl::scheme() && !strcmp(Fl::scheme(), "gtk+")) {
243 	  fl_color(fl_color_average(FL_WHITE, FL_SELECTION_COLOR, 0.5));
244 	  fl_arc(x + td + 2, y + d + td, tW + 1, tW + 1, 60.0, 180.0);
245 	}
246       }
247     } else {
248       fl_draw_box(FL_DOWN_BOX, x+2, y+d, W, W, FL_BACKGROUND2_COLOR);
249       if (value()) {
250 	if (Fl::scheme() && !strcmp(Fl::scheme(), "gtk+")) {
251 	  fl_color(FL_SELECTION_COLOR);
252 	} else {
253 	  fl_color(labelcolor_);
254 	}
255 	int tx = x + 5;
256 	int tw = W - 6;
257 	int d1 = tw/3;
258 	int d2 = tw-d1;
259 	int ty = y + d + (W+d2)/2-d1-2;
260 	for (int n = 0; n < 3; n++, ty++) {
261 	  fl_line(tx, ty, tx+d1, ty+d1);
262 	  fl_line(tx+d1, ty+d1, tx+tw-1, ty+d1-d2+1);
263 	}
264       }
265     }
266     x += W + 3;
267     w -= W + 3;
268   }
269 
270   if (!fl_draw_shortcut) fl_draw_shortcut = 1;
271   l.draw(x+3, y, w>6 ? w-6 : 0, h, FL_ALIGN_LEFT);
272   fl_draw_shortcut = 0;
273 }
274 
menutitle(int X,int Y,int W,int H,const Fl_Menu_Item * L)275 menutitle::menutitle(int X, int Y, int W, int H, const Fl_Menu_Item* L) :
276   Fl_Menu_Window(X, Y, W, H, 0) {
277   end();
278   set_modal();
279   clear_border();
280   set_menu_window();
281   menu = L;
282 }
283 
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)284 menuwindow::menuwindow(const Fl_Menu_Item* m, int X, int Y, int Wp, int Hp,
285 		       const Fl_Menu_Item* picked, const Fl_Menu_Item* t,
286 		       int menubar, int menubar_title, int right_edge)
287   : Fl_Menu_Window(X, Y, Wp, Hp, 0)
288 {
289   int scr_x, scr_y, scr_w, scr_h;
290   int tx = X, ty = Y;
291 
292   Fl::screen_xywh(scr_x, scr_y, scr_w, scr_h);
293   if (!right_edge || right_edge > scr_x+scr_w) right_edge = scr_x+scr_w;
294 
295   end();
296   set_modal();
297   clear_border();
298   set_menu_window();
299   menu = m;
300   if (m) m = m->first(); // find the first item that needs to be rendered
301   drawn_selected = -1;
302   if (button) {
303     box(button->box());
304     if (box() == FL_NO_BOX || box() == FL_FLAT_BOX) box(FL_UP_BOX);
305   } else {
306     box(FL_UP_BOX);
307   }
308   color(button && !Fl::scheme() ? button->color() : FL_GRAY);
309   selected = -1;
310   {
311     int j = 0;
312     if (m) for (const Fl_Menu_Item* m1=m; ; m1 = m1->next(), j++) {
313       if (picked) {
314         if (m1 == picked) {selected = j; picked = 0;}
315         else if (m1 > picked) {selected = j-1; picked = 0; Wp = Hp = 0;}
316     }
317     if (!m1->text) break;
318   }
319   numitems = j;}
320 
321   if (menubar) {
322     itemheight = 0;
323     title = 0;
324     return;
325   }
326 
327   itemheight = 1;
328 
329   int hotKeysw = 0;
330   int hotModsw = 0;
331   int Wtitle = 0;
332   int Htitle = 0;
333   if (t) Wtitle = t->measure(&Htitle, button) + 12;
334   int W = 0;
335   if (m) for (; m->text; m = m->next()) {
336     int hh;
337     int w1 = m->measure(&hh, button);
338     if (hh+LEADING>itemheight) itemheight = hh+LEADING;
339     if (m->flags&(FL_SUBMENU|FL_SUBMENU_POINTER)) w1 += 14;
340     if (w1 > W) W = w1;
341     // calculate the maximum width of all shortcuts
342     if (m->shortcut_) {
343       // s is a pointerto the utf8 string for the entire shortcut
344       // k points only to the key part (minus the modifier keys)
345       const char *k, *s = fl_shortcut_label(m->shortcut_, &k);
346       if (fl_utf_nb_char((const unsigned char*)k, strlen(k))<=4) {
347         // a regular shortcut has a right-justified modifier followed by a left-justified key
348         w1 = int(fl_width(s, k-s));
349         if (w1 > hotModsw) hotModsw = w1;
350         w1 = int(fl_width(k))+4;
351         if (w1 > hotKeysw) hotKeysw = w1;
352       } else {
353         // a shortcut with a long modifier is right-justified to the menu
354         w1 = int(fl_width(s))+4;
355         if (w1 > (hotModsw+hotKeysw)) {
356           hotModsw = w1-hotKeysw;
357         }
358       }
359     }
360   }
361   shortcutWidth = hotKeysw;
362   if (selected >= 0 && !Wp) X -= W/2;
363   int BW = Fl::box_dx(box());
364   W += hotKeysw+hotModsw+2*BW+7;
365   if (Wp > W) W = Wp;
366   if (Wtitle > W) W = Wtitle;
367 
368   if (X < scr_x) X = scr_x; if (X > scr_x+scr_w-W) X = right_edge-W; //X= scr_x+scr_w-W;
369   x(X); w(W);
370   h((numitems ? itemheight*numitems-LEADING : 0)+2*BW+3);
371   if (selected >= 0) {
372     Y = Y+(Hp-itemheight)/2-selected*itemheight-BW;
373   } else {
374     Y = Y+Hp;
375     // if the menu hits the bottom of the screen, we try to draw
376     // it above the menubar instead. We will not adjust any menu
377     // that has a selected item.
378     if (Y+h()>scr_y+scr_h && Y-h()>=scr_y) {
379       if (Hp>1) {
380         // if we know the height of the Fl_Menu_, use it
381         Y = Y-Hp-h();
382       } else if (t) {
383         // assume that the menubar item height relates to the first
384         // menuitem as well
385         Y = Y-itemheight-h()-Fl::box_dh(box());
386       } else {
387         // draw the menu to the right
388         Y = Y-h()+itemheight+Fl::box_dy(box());
389       }
390     }
391   }
392   if (m) y(Y); else {y(Y-2); w(1); h(1);}
393 
394   if (t) {
395     if (menubar_title) {
396       int dy = Fl::box_dy(button->box())+1;
397       int ht = button->h()-dy*2;
398       title = new menutitle(tx, ty-ht-dy, Wtitle, ht, t);
399     } else {
400       int dy = 2;
401       int ht = Htitle+2*BW+3;
402       title = new menutitle(X, Y-ht-dy, Wtitle, ht, t);
403     }
404   } else {
405     title = 0;
406   }
407 }
408 
~menuwindow()409 menuwindow::~menuwindow() {
410   hide();
411   delete title;
412 }
413 
position(int X,int Y)414 void menuwindow::position(int X, int Y) {
415   if (title) {title->position(X, title->y()+Y-y());}
416   Fl_Menu_Window::position(X, Y);
417   // x(X); y(Y); // don't wait for response from X
418 }
419 
420 // scroll so item i is visible on screen
autoscroll(int n)421 void menuwindow::autoscroll(int n) {
422   int scr_x, scr_y, scr_w, scr_h;
423   int Y = y()+Fl::box_dx(box())+2+n*itemheight;
424 
425   Fl::screen_xywh(scr_x, scr_y, scr_w, scr_h);
426   if (Y <= scr_y) Y = scr_y-Y+10;
427   else {
428     Y = Y+itemheight-scr_h-scr_y;
429     if (Y < 0) return;
430     Y = -Y-10;
431   }
432   Fl_Menu_Window::position(x(), y()+Y);
433   // y(y()+Y); // don't wait for response from X
434 }
435 
436 ////////////////////////////////////////////////////////////////
437 
drawentry(const Fl_Menu_Item * m,int n,int eraseit)438 void menuwindow::drawentry(const Fl_Menu_Item* m, int n, int eraseit) {
439   if (!m) return; // this happens if -1 is selected item and redrawn
440 
441   int BW = Fl::box_dx(box());
442   int xx = BW;
443   int W = w();
444   int ww = W-2*BW-1;
445   int yy = BW+1+n*itemheight;
446   int hh = itemheight - LEADING;
447 
448 
449   if ( n != selected) {
450     fl_push_clip(xx, yy-(LEADING)/2, ww, hh+(LEADING));
451     fl_rectf( 0,0,w(),h(), FL_BACKGROUND_COLOR );
452     draw_box(box(), 0, 0, w(), h(), button ? button->color() : color());
453     fl_pop_clip();
454   }
455 
456   m->draw(xx, yy, ww, hh, button, n==selected);
457 
458   // the shortcuts and arrows assume fl_color() was left set by draw():
459   if (m->submenu()) {
460     int sz = (hh-7)&-2;
461     int y1 = yy+(hh-sz)/2;
462     int x1 = xx+ww-sz-3;
463     fl_polygon(x1+2, y1, x1+2, y1+sz, x1+sz/2+2, y1+sz/2);
464   } else if (m->shortcut_) {
465     Fl_Font f = m->labelsize_ || m->labelfont_ ? (Fl_Font)m->labelfont_ :
466                     button ? button->textfont() : FL_HELVETICA;
467     fl_font(f, m->labelsize_ ? m->labelsize_ :
468                    button ? button->textsize() : FL_NORMAL_SIZE);
469     const char *k, *s = fl_shortcut_label(m->shortcut_, &k);
470     if (fl_utf_nb_char((const unsigned char*)k, strlen(k))<=4) {
471       // righ-align the modifiers and left-align the key
472       char buf[32]; strcpy(buf, s); buf[k-s] = 0;
473       fl_draw(buf, xx, yy, ww-shortcutWidth, hh, FL_ALIGN_RIGHT);
474       fl_draw(  k, xx+ww-shortcutWidth, yy, shortcutWidth, hh, FL_ALIGN_LEFT);
475     } else {
476       // right-align to the menu
477       fl_draw(s, xx, yy, ww-4, hh, FL_ALIGN_RIGHT);
478     }
479   }
480 
481   if (m->flags & FL_MENU_DIVIDER) {
482     fl_color(FL_DARK3);
483     fl_xyline(BW-1, yy+hh+(LEADING-2)/2, W-2*BW+2);
484     fl_color(FL_LIGHT3);
485     fl_xyline(BW-1, yy+hh+((LEADING-2)/2+1), W-2*BW+2);
486   }
487 }
488 
draw()489 void menutitle::draw() {
490   menu->draw(0, 0, w(), h(), button, 2);
491 }
492 
draw()493 void menuwindow::draw() {
494   if (damage() != FL_DAMAGE_CHILD) {	// complete redraw
495     fl_rectf( 0,0,w(),h(), FL_BACKGROUND_COLOR );
496 
497     fl_draw_box(box(), 0, 0, w(), h(), button ? button->color() : color());
498     if (menu) {
499       const Fl_Menu_Item* m; int j;
500       for (m=menu->first(), j=0; m->text; j++, m = m->next()) drawentry(m, j, 0);
501     }
502   } else {
503     if (damage() & FL_DAMAGE_CHILD && selected!=drawn_selected) { // change selection
504       drawentry(menu->next(drawn_selected), drawn_selected, 1);
505       drawentry(menu->next(selected), selected, 1);
506     }
507   }
508   drawn_selected = selected;
509 }
510 
set_selected(int n)511 void menuwindow::set_selected(int n) {
512   if (n != selected) {selected = n; damage(FL_DAMAGE_CHILD);}
513 }
514 
515 ////////////////////////////////////////////////////////////////
516 
find_selected(int mx,int my)517 int menuwindow::find_selected(int mx, int my) {
518   if (!menu || !menu->text) return -1;
519   mx -= x();
520   my -= y();
521   if (my < 0 || my >= h()) return -1;
522   if (!itemheight) { // menubar
523     int xx = 3; int n = 0;
524     const Fl_Menu_Item* m = menu ? menu->first() : 0;
525     for (; ; m = m->next(), n++) {
526       if (!m->text) return -1;
527       xx += m->measure(0, button) + 16;
528       if (xx > mx) break;
529     }
530     return n;
531   }
532   if (mx < Fl::box_dx(box()) || mx >= w()) return -1;
533   int n = (my-Fl::box_dx(box())-1)/itemheight;
534   if (n < 0 || n>=numitems) return -1;
535   return n;
536 }
537 
538 // return horizontal position for item n in a menubar:
titlex(int n)539 int menuwindow::titlex(int n) {
540   const Fl_Menu_Item* m;
541   int xx = 3;
542   for (m=menu->first(); n--; m = m->next()) xx += m->measure(0, button) + 16;
543   return xx;
544 }
545 
546 // return 1, if the given root coordinates are inside the window
is_inside(int mx,int my)547 int menuwindow::is_inside(int mx, int my) {
548   if ( mx < x_root() || mx >= x_root() + w() ||
549        my < y_root() || my >= y_root() + h()) {
550     return 0;
551   }
552   if (itemheight == 0 && find_selected(mx, my) == -1) {
553     // in the menubar but out from any menu header
554     return 0;
555     }
556   return 1;
557 }
558 
559 ////////////////////////////////////////////////////////////////
560 // Fl_Menu_Item::popup(...)
561 
562 // Because Fl::grab() is done, all events go to one of the menu windows.
563 // But the handle method needs to look at all of them to find out
564 // what item the user is pointing at.  And it needs a whole lot
565 // of other state variables to determine what is going on with
566 // the currently displayed menus.
567 // So the main loop (handlemenu()) puts all the state in a structure
568 // and puts a pointer to it in a static location, so the handle()
569 // on menus can refer to it and alter it.  The handle() method
570 // changes variables in this state to indicate what item is
571 // picked, but does not actually alter the display, instead the
572 // main loop does that.  This is because the X mapping and unmapping
573 // of windows is slow, and we don't want to fall behind the events.
574 
575 // values for menustate.state:
576 #define INITIAL_STATE 0	// no mouse up or down since popup() called
577 #define PUSH_STATE 1	// mouse has been pushed on a normal item
578 #define DONE_STATE 2	// exit the popup, the current item was picked
579 #define MENU_PUSH_STATE 3 // mouse has been pushed on a menu title
580 
581 struct menustate {
582   const Fl_Menu_Item* current_item; // what mouse is pointing at
583   int menu_number; // which menu it is in
584   int item_number; // which item in that menu, -1 if none
585   menuwindow* p[20]; // pointers to menus
586   int nummenus;
587   int menubar; // if true p[0] is a menubar
588   int state;
589   menuwindow* fakemenu; // kludge for buttons in menubar
590   int is_inside(int mx, int my);
591 };
592 static menustate* p=0;
593 
594 // return 1 if the coordinates are inside any of the menuwindows
is_inside(int mx,int my)595 int menustate::is_inside(int mx, int my) {
596   int i;
597   for (i=nummenus-1; i>=0; i--) {
598     if (p[i]->is_inside(mx, my))
599       return 1;
600   }
601   return 0;
602 }
603 
setitem(const Fl_Menu_Item * i,int m,int n)604 static inline void setitem(const Fl_Menu_Item* i, int m, int n) {
605   p->current_item = i;
606   p->menu_number = m;
607   p->item_number = n;
608 }
609 
setitem(int m,int n)610 static void setitem(int m, int n) {
611   menustate &pp = *p;
612   pp.current_item = (n >= 0) ? pp.p[m]->menu->next(n) : 0;
613   pp.menu_number = m;
614   pp.item_number = n;
615 }
616 
forward(int menu)617 static int forward(int menu) { // go to next item in menu menu if possible
618   menustate &pp = *p;
619   // Fl_Menu_Button can generate menu=-1. This line fixes it and selectes the first item.
620   if (menu==-1)
621     menu = 0;
622   menuwindow &m = *(pp.p[menu]);
623   int item = (menu == pp.menu_number) ? pp.item_number : m.selected;
624   while (++item < m.numitems) {
625     const Fl_Menu_Item* m1 = m.menu->next(item);
626     if (m1->activevisible()) {setitem(m1, menu, item); return 1;}
627   }
628   return 0;
629 }
630 
backward(int menu)631 static int backward(int menu) { // previous item in menu menu if possible
632   menustate &pp = *p;
633   menuwindow &m = *(pp.p[menu]);
634   int item = (menu == pp.menu_number) ? pp.item_number : m.selected;
635   if (item < 0) item = m.numitems;
636   while (--item >= 0) {
637     const Fl_Menu_Item* m1 = m.menu->next(item);
638     if (m1->activevisible()) {setitem(m1, menu, item); return 1;}
639   }
640   return 0;
641 }
642 
handle(int e)643 int menuwindow::handle(int e) {
644 #if defined (__APPLE__) || defined (USE_X11)
645   // This off-route takes care of the "detached menu" bug on OS X.
646   // Apple event handler requires that we hide all menu windows right
647   // now, so that Carbon can continue undisturbed with handling window
648   // manager events, like dragging the application window.
649   int ret = early_hide_handle(e);
650   menustate &pp = *p;
651   if (pp.state == DONE_STATE) {
652     hide();
653     if (pp.fakemenu) {
654       pp.fakemenu->hide();
655       if (pp.fakemenu->title)
656         pp.fakemenu->title->hide();
657     }
658     int i = pp.nummenus;
659     while (i>0) {
660       menuwindow *mw = pp.p[--i];
661       if (mw) {
662         mw->hide();
663         if (mw->title)
664           mw->title->hide();
665       }
666     }
667   }
668   return ret;
669 }
670 
early_hide_handle(int e)671 int menuwindow::early_hide_handle(int e) {
672 #endif
673   menustate &pp = *p;
674   switch (e) {
675   case FL_KEYBOARD:
676     switch (Fl::event_key()) {
677     case FL_BackSpace:
678     BACKTAB:
679       if (!backward(pp.menu_number)) {pp.item_number = -1;backward(pp.menu_number);}
680       return 1;
681     case FL_Up:
682       if (pp.menubar && pp.menu_number == 0) {
683         // Do nothing...
684       } else if (backward(pp.menu_number)) {
685         // Do nothing...
686       } else if (pp.menubar && pp.menu_number==1) {
687         setitem(0, pp.p[0]->selected);
688       }
689       return 1;
690     case FL_Tab:
691       if (Fl::event_shift()) goto BACKTAB;
692     case FL_Down:
693       if (pp.menu_number || !pp.menubar) {
694         if (!forward(pp.menu_number) && Fl::event_key()==FL_Tab) {
695           pp.item_number = -1;
696           forward(pp.menu_number);
697         }
698       } else if (pp.menu_number < pp.nummenus-1) {
699         forward(pp.menu_number+1);
700       }
701       return 1;
702     case FL_Right:
703       if (pp.menubar && (pp.menu_number<=0 || (pp.menu_number==1 && pp.nummenus==2)))
704 	forward(0);
705       else if (pp.menu_number < pp.nummenus-1) forward(pp.menu_number+1);
706       return 1;
707     case FL_Left:
708       if (pp.menubar && pp.menu_number<=1) backward(0);
709       else if (pp.menu_number>0)
710 	setitem(pp.menu_number-1, pp.p[pp.menu_number-1]->selected);
711       return 1;
712     case FL_Enter:
713     case FL_KP_Enter:
714     case ' ':
715       pp.state = DONE_STATE;
716       return 1;
717     case FL_Escape:
718       setitem(0, -1, 0);
719       pp.state = DONE_STATE;
720       return 1;
721     }
722     break;
723   case FL_SHORTCUT:
724     {
725       for (int mymenu = pp.nummenus; mymenu--;) {
726 	menuwindow &mw = *(pp.p[mymenu]);
727 	int item; const Fl_Menu_Item* m = mw.menu->find_shortcut(&item);
728 	if (m) {
729 	  setitem(m, mymenu, item);
730 	  if (!m->submenu()) pp.state = DONE_STATE;
731 	  return 1;
732 	}
733       }
734     }
735     break;
736     case FL_MOVE:
737 #if ! (defined(WIN32) || defined(__APPLE__))
738       if (pp.state == DONE_STATE) {
739 	return 1; // Fix for STR #2619
740       }
741       /* FALLTHROUGH */
742 #endif
743   case FL_ENTER:
744   case FL_PUSH:
745   case FL_DRAG:
746     {
747       int mx = Fl::event_x_root();
748       int my = Fl::event_y_root();
749       int item=0; int mymenu = pp.nummenus-1;
750       // Clicking or dragging outside menu cancels it...
751       if ((!pp.menubar || mymenu) && !pp.is_inside(mx, my)) {
752 	setitem(0, -1, 0);
753 	if (e==FL_PUSH)
754 	  pp.state = DONE_STATE;
755 	return 1;
756       }
757       for (mymenu = pp.nummenus-1; ; mymenu--) {
758 	item = pp.p[mymenu]->find_selected(mx, my);
759 	if (item >= 0)
760 	  break;
761 	if (mymenu <= 0) {
762 	  // buttons in menubars must be deselected if we move outside of them!
763 	  if (pp.menu_number==-1 && e==FL_PUSH) {
764 	    pp.state = DONE_STATE;
765 	    return 1;
766 	  }
767 	  if (pp.current_item && pp.menu_number==0 && !pp.current_item->submenu()) {
768 	    if (e==FL_PUSH)
769 	      pp.state = DONE_STATE;
770 	    setitem(0, -1, 0);
771 	    return 1;
772 	  }
773 	  // all others can stay selected
774 	  return 0;
775 	}
776       }
777       if (my == 0 && item > 0) setitem(mymenu, item - 1);
778       else setitem(mymenu, item);
779       if (e == FL_PUSH) {
780 	if (pp.current_item && pp.current_item->submenu() // this is a menu title
781 	    && item != pp.p[mymenu]->selected // and it is not already on
782 	    && !pp.current_item->callback_) // and it does not have a callback
783 	  pp.state = MENU_PUSH_STATE;
784 	else
785 	  pp.state = PUSH_STATE;
786       }
787     }
788     return 1;
789   case FL_RELEASE:
790     // Mouse must either be held down/dragged some, or this must be
791     // the second click (not the one that popped up the menu):
792     if (   !Fl::event_is_click()
793         || pp.state == PUSH_STATE
794         || (pp.menubar && pp.current_item && !pp.current_item->submenu()) // button
795 	) {
796 #if 0 // makes the check/radio items leave the menu up
797       const Fl_Menu_Item* m = pp.current_item;
798       if (m && button && (m->flags & (FL_MENU_TOGGLE|FL_MENU_RADIO))) {
799 	((Fl_Menu_*)button)->picked(m);
800 	pp.p[pp.menu_number]->redraw();
801       } else
802 #endif
803       // do nothing if they try to pick inactive items
804       if (!pp.current_item || pp.current_item->activevisible())
805 	pp.state = DONE_STATE;
806     }
807     return 1;
808   }
809   return Fl_Window::handle(e);
810 }
811 
812 /**
813   Pulldown() is similar to popup(), but a rectangle is
814   provided to position the menu.  The menu is made at least W
815   wide, and the picked item is centered over the rectangle
816   (like Fl_Choice uses).  If picked is zero or not
817   found, the menu is aligned just below the rectangle (like a pulldown
818   menu).
819   <P>The title and menubar arguments are used
820   internally by the Fl_Menu_Bar widget.
821 */
pulldown(int X,int Y,int W,int H,const Fl_Menu_Item * initial_item,const Fl_Menu_ * pbutton,const Fl_Menu_Item * t,int menubar) const822 const Fl_Menu_Item* Fl_Menu_Item::pulldown(
823     int X, int Y, int W, int H,
824     const Fl_Menu_Item* initial_item,
825     const Fl_Menu_* pbutton,
826     const Fl_Menu_Item* t,
827     int menubar) const {
828   Fl_Group::current(0); // fix possible user error...
829 
830   button = pbutton;
831   if (pbutton && pbutton->window() && ! fl_embed_called) {
832     for (Fl_Window* w = pbutton->window(); w; w = w->window()) {
833       X += w->x();
834       Y += w->y();
835     }
836   } else {
837     X += Fl::event_x_root()-Fl::event_x();
838     Y += Fl::event_y_root()-Fl::event_y();
839   }
840   menuwindow mw(this, X, Y, W, H, initial_item, t, menubar);
841   Fl::grab(mw);
842   menustate pp; p = &pp;
843   pp.p[0] = &mw;
844   pp.nummenus = 1;
845   pp.menubar = menubar;
846   pp.state = INITIAL_STATE;
847   pp.fakemenu = 0; // kludge for buttons in menubar
848 
849   // preselected item, pop up submenus if necessary:
850   if (initial_item && mw.selected >= 0) {
851     setitem(0, mw.selected);
852     goto STARTUP;
853   }
854 
855   pp.current_item = 0; pp.menu_number = 0; pp.item_number = -1;
856   if (menubar) {
857     // find the initial menu
858     if (!mw.handle(FL_DRAG)) {
859       Fl::grab(0);
860       return 0;
861     }
862   }
863   initial_item = pp.current_item;
864   if (initial_item) goto STARTUP;
865 
866   // the main loop, runs until p.state goes to DONE_STATE:
867   for (;;) {
868 
869     // make sure all the menus are shown:
870     {
871       for (int k = menubar; k < pp.nummenus; k++) {
872         if (!pp.p[k]->shown()) {
873 	  if (pp.p[k]->title) pp.p[k]->title->show();
874 	  pp.p[k]->show();
875         }
876       }
877     }
878 
879     // get events:
880     {
881       const Fl_Menu_Item* oldi = pp.current_item;
882       Fl::wait();
883       if (pp.state == DONE_STATE) break; // done.
884       if (pp.current_item == oldi) continue;
885     }
886 
887     // only do rest if item changes:
888     if(pp.fakemenu) {delete pp.fakemenu; pp.fakemenu = 0;} // turn off "menubar button"
889 
890     if (!pp.current_item) { // pointing at nothing
891       // turn off selection in deepest menu, but don't erase other menus:
892       pp.p[pp.nummenus-1]->set_selected(-1);
893       continue;
894     }
895 
896     if(pp.fakemenu) {delete pp.fakemenu; pp.fakemenu = 0;}
897     initial_item = 0; // stop the startup code
898     pp.p[pp.menu_number]->autoscroll(pp.item_number);
899 
900   STARTUP:
901     menuwindow& cw = *pp.p[pp.menu_number];
902     const Fl_Menu_Item* m = pp.current_item;
903     if (!m->activevisible()) { // pointing at inactive item
904       cw.set_selected(-1);
905       initial_item = 0; // turn off startup code
906       continue;
907     }
908     cw.set_selected(pp.item_number);
909 
910     if (m==initial_item) initial_item=0; // stop the startup code if item found
911     if (m->submenu()) {
912       const Fl_Menu_Item* title = m;
913       const Fl_Menu_Item* menutable;
914       if (m->flags&FL_SUBMENU) menutable = m+1;
915       else menutable = (Fl_Menu_Item*)(m)->user_data_;
916       // figure out where new menu goes:
917       int nX, nY;
918       if (!pp.menu_number && pp.menubar) {	// menu off a menubar:
919 	nX = cw.x() + cw.titlex(pp.item_number);
920 	nY = cw.y() + cw.h();
921 	initial_item = 0;
922       } else {
923 	nX = cw.x() + cw.w();
924 	nY = cw.y() + pp.item_number * cw.itemheight;
925 	title = 0;
926       }
927       if (initial_item) { // bring up submenu containing initial item:
928 	menuwindow* n = new menuwindow(menutable,X,Y,W,H,initial_item,title,0,0,cw.x());
929 	pp.p[pp.nummenus++] = n;
930 	// move all earlier menus to line up with this new one:
931 	if (n->selected>=0) {
932 	  int dy = n->y()-nY;
933 	  int dx = n->x()-nX;
934 	  for (int menu = 0; menu <= pp.menu_number; menu++) {
935 	    menuwindow* tt = pp.p[menu];
936 	    int nx = tt->x()+dx; if (nx < 0) {nx = 0; dx = -tt->x();}
937 	    int ny = tt->y()+dy; if (ny < 0) {ny = 0; dy = -tt->y();}
938 	    tt->position(nx, ny);
939 	  }
940 	  setitem(pp.nummenus-1, n->selected);
941 	  goto STARTUP;
942 	}
943       } else if (pp.nummenus > pp.menu_number+1 &&
944 		 pp.p[pp.menu_number+1]->menu == menutable) {
945 	// the menu is already up:
946 	while (pp.nummenus > pp.menu_number+2) delete pp.p[--pp.nummenus];
947 	pp.p[pp.nummenus-1]->set_selected(-1);
948       } else {
949 	// delete all the old menus and create new one:
950 	while (pp.nummenus > pp.menu_number+1) delete pp.p[--pp.nummenus];
951 	pp.p[pp.nummenus++]= new menuwindow(menutable, nX, nY,
952 					  title?1:0, 0, 0, title, 0, menubar, cw.x());
953       }
954     } else { // !m->submenu():
955       while (pp.nummenus > pp.menu_number+1) delete pp.p[--pp.nummenus];
956       if (!pp.menu_number && pp.menubar) {
957 	// kludge so "menubar buttons" turn "on" by using menu title:
958 	pp.fakemenu = new menuwindow(0,
959 				  cw.x()+cw.titlex(pp.item_number),
960 				  cw.y()+cw.h(), 0, 0,
961 				  0, m, 0, 1);
962 	pp.fakemenu->title->show();
963       }
964     }
965   }
966   const Fl_Menu_Item* m = pp.current_item;
967   delete pp.fakemenu;
968   while (pp.nummenus>1) delete pp.p[--pp.nummenus];
969   mw.hide();
970   Fl::grab(0);
971   return m;
972 }
973 
974 /**
975   This method is called by widgets that want to display menus.
976 
977   The menu stays up until the user picks an item or dismisses it.
978   The selected item (or NULL if none) is returned. <I>This does not
979   do the callbacks or change the state of check or radio items.</I>
980 
981   X,Y is the position of the mouse cursor, relative to the
982   window that got the most recent event (usually you can pass
983   Fl::event_x() and Fl::event_y() unchanged here).
984 
985   \p title is a character string title for the menu.  If
986   non-zero a small box appears above the menu with the title in it.
987 
988   The menu is positioned so the cursor is centered over the item
989   picked.  This will work even if \p picked is in a submenu.
990   If \p picked is zero or not in the menu item table the menu is
991   positioned with the cursor in the top-left corner.
992 
993   \p button is a pointer to an Fl_Menu_ from which the color and
994   boxtypes for the menu are pulled.  If NULL then defaults are used.
995 */
popup(int X,int Y,const char * title,const Fl_Menu_Item * picked,const Fl_Menu_ * button) const996 const Fl_Menu_Item* Fl_Menu_Item::popup(
997   int X, int Y,
998   const char* title,
999   const Fl_Menu_Item* picked,
1000   const Fl_Menu_* button
1001   ) const {
1002   static Fl_Menu_Item dummy; // static so it is all zeros
1003   dummy.text = title;
1004   return pulldown(X, Y, 0, 0, picked, button, title ? &dummy : 0);
1005 }
1006 
1007 /**
1008   Search only the top level menu for a shortcut.
1009   Either &x in the label or the shortcut fields are used.
1010 
1011   This tests the current event, which must be an FL_KEYBOARD or
1012   FL_SHORTCUT, against a shortcut value.
1013 
1014   \param ip returns the index of the item, if \p ip is not NULL.
1015   \param require_alt if true: match only if Alt key is pressed.
1016 
1017   \return found Fl_Menu_Item or NULL
1018 */
find_shortcut(int * ip,const bool require_alt) const1019 const Fl_Menu_Item* Fl_Menu_Item::find_shortcut(int* ip, const bool require_alt) const {
1020   const Fl_Menu_Item* m = this;
1021   if (m) for (int ii = 0; m->text; m = next_visible_or_not(m), ii++) {
1022     if (m->active()) {
1023       if (Fl::test_shortcut(m->shortcut_)
1024 	 || Fl_Widget::test_shortcut(m->text, require_alt)) {
1025 	if (ip) *ip=ii;
1026 	return m;
1027       }
1028     }
1029   }
1030   return 0;
1031 }
1032 
1033 // Recursive search of all submenus for anything with this key as a
1034 // shortcut.  Only uses the shortcut field, ignores &x in the labels:
1035 /**
1036   This is designed to be called by a widgets handle() method in
1037   response to a FL_SHORTCUT event.  If the current event matches
1038   one of the items shortcut, that item is returned.  If the keystroke
1039   does not match any shortcuts then NULL is returned.  This only
1040   matches the shortcut() fields, not the letters in the title
1041   preceeded by '
1042 */
test_shortcut() const1043 const Fl_Menu_Item* Fl_Menu_Item::test_shortcut() const {
1044   const Fl_Menu_Item* m = this;
1045   const Fl_Menu_Item* ret = 0;
1046   if (m) for (; m->text; m = next_visible_or_not(m)) {
1047     if (m->active()) {
1048       // return immediately any match of an item in top level menu:
1049       if (Fl::test_shortcut(m->shortcut_)) return m;
1050       // if (Fl_Widget::test_shortcut(m->text)) return m;
1051       // only return matches from lower menu if nothing found in top menu:
1052       if (!ret && m->submenu()) {
1053 	const Fl_Menu_Item* s =
1054 	  (m->flags&FL_SUBMENU) ? m+1:(const Fl_Menu_Item*)m->user_data_;
1055 	ret = s->test_shortcut();
1056       }
1057     }
1058   }
1059   return ret;
1060 }
1061 
1062 //
1063 // End of "$Id: Fl_Menu.cxx 8775 2011-06-03 14:07:52Z manolo $".
1064 //
1065