themes: Workaround for bug where a background color of RGB 0,0,0 in Black color schem...
[ntk.git] / src / Fl_Menu.cxx
blobd5b96f74b12c9f474bf8373bca910317050e36cc
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.
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.
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.
23 // Please report all bugs and problems on the following page:
25 // http://www.fltk.org/str.php
28 // Warning: this menu code is quite a mess!
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.
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"
42 /** Size of the menu starting from this menu item */
43 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++;
53 m++;
57 // Advance a pointer to next visible or invisible item of a menu array,
58 // skipping the contents of submenus.
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++;
68 m++;
70 while (nest);
71 return m;
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.
79 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--;
87 return m;
90 // appearance of current menus are pulled from this parent widget:
91 static const Fl_Menu_* button=0;
93 ////////////////////////////////////////////////////////////////
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*);
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);
131 #define LEADING 4 // extra vertical leading
133 extern char fl_draw_shortcut;
135 /**
136 Measures width of label, including effect of & characters.
137 Optionally, can get height if hp is not NULL.
139 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;
156 /** Draws the menu item in bounding box x,y,w,h, optionally selects the item. */
157 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);
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);
181 } else {
182 l.color = fl_contrast((Fl_Color)labelcolor_, r);
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);
193 if (flags & (FL_MENU_TOGGLE|FL_MENU_RADIO)) {
194 int d = (h - FL_NORMAL_SIZE + 1) / 2;
195 int W = h - 2 * d;
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 ++;
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_);
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;
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;
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;
236 case 2 :
237 case 1 :
238 fl_rectf(x + td + 2, y + d + td, tW, tW);
239 break;
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);
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_);
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);
266 x += W + 3;
267 w -= W + 3;
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;
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;
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)
289 int scr_x, scr_y, scr_w, scr_h;
290 int tx = X, ty = Y;
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;
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);
308 color(button && !Fl::scheme() ? button->color() : FL_GRAY);
309 selected = -1;
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;}
317 if (!m1->text) break;
319 numitems = j;}
321 if (menubar) {
322 itemheight = 0;
323 title = 0;
324 return;
327 itemheight = 1;
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;
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;
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());
392 if (m) y(Y); else {y(Y-2); w(1); h(1);}
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);
404 } else {
405 title = 0;
409 menuwindow::~menuwindow() {
410 hide();
411 delete title;
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
420 // scroll so item i is visible on screen
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;
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;
432 Fl_Menu_Window::position(x(), y()+Y);
433 // y(y()+Y); // don't wait for response from X
436 ////////////////////////////////////////////////////////////////
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
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;
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();
456 m->draw(xx, yy, ww, hh, button, n==selected);
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);
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);
489 void menutitle::draw() {
490 menu->draw(0, 0, w(), h(), button, 2);
493 void menuwindow::draw() {
494 if (damage() != FL_DAMAGE_CHILD) { // complete redraw
495 fl_rectf( 0,0,w(),h(), FL_BACKGROUND_COLOR );
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);
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);
508 drawn_selected = selected;
511 void menuwindow::set_selected(int n) {
512 if (n != selected) {selected = n; damage(FL_DAMAGE_CHILD);}
515 ////////////////////////////////////////////////////////////////
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;
530 return n;
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;
538 // return horizontal position for item n in a menubar:
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;
546 // return 1, if the given root coordinates are inside the window
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;
552 if (itemheight == 0 && find_selected(mx, my) == -1) {
553 // in the menubar but out from any menu header
554 return 0;
556 return 1;
559 ////////////////////////////////////////////////////////////////
560 // Fl_Menu_Item::popup(...)
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.
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
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);
592 static menustate* p=0;
594 // return 1 if the coordinates are inside any of the menuwindows
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;
601 return 0;
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;
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;
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;}
628 return 0;
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;}
640 return 0;
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();
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();
668 return ret;
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);
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);
698 } else if (pp.menu_number < pp.nummenus-1) {
699 forward(pp.menu_number+1);
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;
722 break;
723 case FL_SHORTCUT:
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;
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
741 /* FALLTHROUGH */
742 #endif
743 case FL_ENTER:
744 case FL_PUSH:
745 case FL_DRAG:
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;
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;
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;
773 // all others can stay selected
774 return 0;
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;
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
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;
807 return 1;
809 return Fl_Window::handle(e);
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.
822 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...
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();
836 } else {
837 X += Fl::event_x_root()-Fl::event_x();
838 Y += Fl::event_y_root()-Fl::event_y();
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
849 // preselected item, pop up submenus if necessary:
850 if (initial_item && mw.selected >= 0) {
851 setitem(0, mw.selected);
852 goto STARTUP;
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;
863 initial_item = pp.current_item;
864 if (initial_item) goto STARTUP;
866 // the main loop, runs until p.state goes to DONE_STATE:
867 for (;;) {
869 // make sure all the menus are shown:
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();
879 // get events:
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;
887 // only do rest if item changes:
888 if(pp.fakemenu) {delete pp.fakemenu; pp.fakemenu = 0;} // turn off "menubar button"
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;
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);
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;
908 cw.set_selected(pp.item_number);
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;
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);
940 setitem(pp.nummenus-1, n->selected);
941 goto STARTUP;
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());
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();
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;
975 This method is called by widgets that want to display menus.
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>
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).
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.
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.
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.
996 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);
1008 Search only the top level menu for a shortcut.
1009 Either &x in the label or the shortcut fields are used.
1011 This tests the current event, which must be an FL_KEYBOARD or
1012 FL_SHORTCUT, against a shortcut value.
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.
1017 \return found Fl_Menu_Item or NULL
1019 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;
1030 return 0;
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:
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 '
1043 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();
1059 return ret;
1063 // End of "$Id: Fl_Menu.cxx 8775 2011-06-03 14:07:52Z manolo $".