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