fltk 1.3.0rc3
About: FLTK (Fast Light Tool Kit) is a cross-platform C++ GUI toolkit for UNIX/Linux (X11), Microsoft Windows, and MacOS X. Release candidate.
  SfR Fresh Dox: fltk-1.3.0rc3-source.tar.gz ("inofficial" and yet experimental doxygen-generated source code documentation)  

Fl_Menu.cxx

Go to the documentation of this file.
00001 //
00002 // "$Id: Fl_Menu.cxx 8076 2010-12-20 13:59:09Z matt $"
00003 //
00004 // Menu code for the Fast Light Tool Kit (FLTK).
00005 //
00006 // Copyright 1998-2010 by Bill Spitzak and others.
00007 //
00008 // This library is free software; you can redistribute it and/or
00009 // modify it under the terms of the GNU Library General Public
00010 // License as published by the Free Software Foundation; either
00011 // version 2 of the License, or (at your option) any later version.
00012 //
00013 // This library is distributed in the hope that it will be useful,
00014 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00015 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00016 // Library General Public License for more details.
00017 //
00018 // You should have received a copy of the GNU Library General Public
00019 // License along with this library; if not, write to the Free Software
00020 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
00021 // USA.
00022 //
00023 // Please report all bugs and problems on the following page:
00024 //
00025 //     http://www.fltk.org/str.php
00026 //
00027 
00028 // Warning: this menu code is quite a mess!
00029 
00030 // This file contains code for implementing Fl_Menu_Item, and for
00031 // methods for bringing up popup menu hierarchies without using the
00032 // Fl_Menu_ widget.
00033 
00034 #include <FL/Fl.H>
00035 #include <FL/Fl_Menu_Window.H>
00036 #include <FL/Fl_Menu_.H>
00037 #include <FL/fl_draw.H>
00038 #include <stdio.h>
00039 #include "flstring.h"
00040 
00042 int Fl_Menu_Item::size() const {
00043   const Fl_Menu_Item* m = this;
00044   int nest = 0;
00045   for (;;) {
00046     if (!m->text) {
00047       if (!nest) return (m-this+1);
00048       nest--;
00049     } else if (m->flags & FL_SUBMENU) {
00050       nest++;
00051     }
00052     m++;
00053   }
00054 }
00055 
00061 const Fl_Menu_Item* Fl_Menu_Item::next(int n) const {
00062   if (n < 0) return 0; // this is so selected==-1 returns NULL
00063   const Fl_Menu_Item* m = this;
00064   int nest = 0;
00065   if (!m->visible()) n++;
00066   while (n>0) {
00067     if (!m->text) {
00068       if (!nest) return m;
00069       nest--;
00070     } else if (m->flags&FL_SUBMENU) {
00071       nest++;
00072     }
00073     m++;
00074     if (!nest && m->visible()) n--;
00075   }
00076   return m;
00077 }
00078 
00079 // appearance of current menus are pulled from this parent widget:
00080 static const Fl_Menu_* button=0;
00081 
00083 
00084 // tiny window for title of menu:
00085 class menutitle : public Fl_Menu_Window {
00086   void draw();
00087 public:
00088   const Fl_Menu_Item* menu;
00089   menutitle(int X, int Y, int W, int H, const Fl_Menu_Item*);
00090 };
00091 
00092 // each vertical menu has one of these:
00093 class menuwindow : public Fl_Menu_Window {
00094   void draw();
00095   void drawentry(const Fl_Menu_Item*, int i, int erase);
00096 public:
00097   menutitle* title;
00098   int handle(int);
00099 #if defined (__APPLE__) || defined (USE_X11)
00100   int early_hide_handle(int);
00101 #endif
00102   int itemheight;       // zero == menubar
00103   int numitems;
00104   int selected;
00105   int drawn_selected;   // last redraw has this selected
00106   int shortcutWidth;
00107   const Fl_Menu_Item* menu;
00108   menuwindow(const Fl_Menu_Item* m, int X, int Y, int W, int H,
00109              const Fl_Menu_Item* picked, const Fl_Menu_Item* title,
00110              int menubar = 0, int menubar_title = 0, int right_edge = 0);
00111   ~menuwindow();
00112   void set_selected(int);
00113   int find_selected(int mx, int my);
00114   int titlex(int);
00115   void autoscroll(int);
00116   void position(int x, int y);
00117   int is_inside(int x, int y);
00118 };
00119 
00120 #define LEADING 4 // extra vertical leading
00121 
00122 extern char fl_draw_shortcut;
00123 
00128 int Fl_Menu_Item::measure(int* hp, const Fl_Menu_* m) const {
00129   Fl_Label l;
00130   l.value   = text;
00131   l.image   = 0;
00132   l.deimage = 0;
00133   l.type    = labeltype_;
00134   l.font    = labelsize_ || labelfont_ ? labelfont_ : (m ? m->textfont() : FL_HELVETICA);
00135   l.size    = labelsize_ ? labelsize_ : m ? m->textsize() : FL_NORMAL_SIZE;
00136   l.color   = FL_FOREGROUND_COLOR; // this makes no difference?
00137   fl_draw_shortcut = 1;
00138   int w = 0; int h = 0;
00139   l.measure(w, hp ? *hp : h);
00140   fl_draw_shortcut = 0;
00141   if (flags & (FL_MENU_TOGGLE|FL_MENU_RADIO)) w += 14;
00142   return w;
00143 }
00144 
00146 void Fl_Menu_Item::draw(int x, int y, int w, int h, const Fl_Menu_* m,
00147                         int selected) const {
00148   Fl_Label l;
00149   l.value   = text;
00150   l.image   = 0;
00151   l.deimage = 0;
00152   l.type    = labeltype_;
00153   l.font    = labelsize_ || labelfont_ ? labelfont_ : (m ? m->textfont() : FL_HELVETICA);
00154   l.size    = labelsize_ ? labelsize_ : m ? m->textsize() : FL_NORMAL_SIZE;
00155   l.color   = labelcolor_ ? labelcolor_ : m ? m->textcolor() : int(FL_FOREGROUND_COLOR);
00156   if (!active()) l.color = fl_inactive((Fl_Color)l.color);
00157   Fl_Color color = m ? m->color() : FL_GRAY;
00158   if (selected) {
00159     Fl_Color r = m ? m->selection_color() : FL_SELECTION_COLOR;
00160     Fl_Boxtype b = m && m->down_box() ? m->down_box() : FL_FLAT_BOX;
00161     if (fl_contrast(r,color)!=r) { // back compatibility boxtypes
00162       if (selected == 2) { // menu title
00163         r = color;
00164         b = m ? m->box() : FL_UP_BOX;
00165       } else {
00166         r = (Fl_Color)(FL_COLOR_CUBE-1); // white
00167         l.color = fl_contrast((Fl_Color)labelcolor_, r);
00168       }
00169     } else {
00170       l.color = fl_contrast((Fl_Color)labelcolor_, r);
00171     }
00172     if (selected == 2) { // menu title
00173       fl_draw_box(b, x, y, w, h, r);
00174       x += 3;
00175       w -= 8;
00176     } else {
00177       fl_draw_box(b, x+1, y-(LEADING-2)/2, w-2, h+(LEADING-2), r);
00178     }
00179   }
00180 
00181   if (flags & (FL_MENU_TOGGLE|FL_MENU_RADIO)) {
00182     int d = (h - FL_NORMAL_SIZE + 1) / 2;
00183     int W = h - 2 * d;
00184 
00185     if (flags & FL_MENU_RADIO) {
00186       fl_draw_box(FL_ROUND_DOWN_BOX, x+2, y+d, W, W, FL_BACKGROUND2_COLOR);
00187       if (value()) {
00188         int tW = (W - Fl::box_dw(FL_ROUND_DOWN_BOX)) / 2 + 1;
00189         if ((W - tW) & 1) tW++; // Make sure difference is even to center
00190         int td = Fl::box_dx(FL_ROUND_DOWN_BOX) + 1;
00191         if (Fl::scheme()) {
00192           // Offset the radio circle...
00193           td ++;
00194 
00195           if (!strcmp(Fl::scheme(), "gtk+")) {
00196             fl_color(FL_SELECTION_COLOR);
00197             tW --;
00198             fl_pie(x + td + 1, y + d + td - 1, tW + 3, tW + 3, 0.0, 360.0);
00199             fl_arc(x + td + 1, y + d + td - 1, tW + 3, tW + 3, 0.0, 360.0);
00200             fl_color(fl_color_average(FL_WHITE, FL_SELECTION_COLOR, 0.2f));
00201           } else fl_color(labelcolor_);
00202         } else fl_color(labelcolor_);
00203 
00204         switch (tW) {
00205           // Larger circles draw fine...
00206           default :
00207             fl_pie(x + td + 2, y + d + td, tW, tW, 0.0, 360.0);
00208             break;
00209 
00210           // Small circles don't draw well on many systems...
00211           case 6 :
00212             fl_rectf(x + td + 4, y + d + td, tW - 4, tW);
00213             fl_rectf(x + td + 3, y + d + td + 1, tW - 2, tW - 2);
00214             fl_rectf(x + td + 2, y + d + td + 2, tW, tW - 4);
00215             break;
00216 
00217           case 5 :
00218           case 4 :
00219           case 3 :
00220             fl_rectf(x + td + 3, y + d + td, tW - 2, tW);
00221             fl_rectf(x + td + 2, y + d + td + 1, tW, tW - 2);
00222             break;
00223 
00224           case 2 :
00225           case 1 :
00226             fl_rectf(x + td + 2, y + d + td, tW, tW);
00227             break;
00228         }
00229 
00230         if (Fl::scheme() && !strcmp(Fl::scheme(), "gtk+")) {
00231           fl_color(fl_color_average(FL_WHITE, FL_SELECTION_COLOR, 0.5));
00232           fl_arc(x + td + 2, y + d + td, tW + 1, tW + 1, 60.0, 180.0);
00233         }
00234       }
00235     } else {
00236       fl_draw_box(FL_DOWN_BOX, x+2, y+d, W, W, FL_BACKGROUND2_COLOR);
00237       if (value()) {
00238         if (Fl::scheme() && !strcmp(Fl::scheme(), "gtk+")) {
00239           fl_color(FL_SELECTION_COLOR);
00240         } else {
00241           fl_color(labelcolor_);
00242         }
00243         int tx = x + 5;
00244         int tw = W - 6;
00245         int d1 = tw/3;
00246         int d2 = tw-d1;
00247         int ty = y + d + (W+d2)/2-d1-2;
00248         for (int n = 0; n < 3; n++, ty++) {
00249           fl_line(tx, ty, tx+d1, ty+d1);
00250           fl_line(tx+d1, ty+d1, tx+tw-1, ty+d1-d2+1);
00251         }
00252       }
00253     }
00254     x += W + 3;
00255     w -= W + 3;
00256   }
00257 
00258   if (!fl_draw_shortcut) fl_draw_shortcut = 1;
00259   l.draw(x+3, y, w>6 ? w-6 : 0, h, FL_ALIGN_LEFT);
00260   fl_draw_shortcut = 0;
00261 }
00262 
00263 menutitle::menutitle(int X, int Y, int W, int H, const Fl_Menu_Item* L) :
00264   Fl_Menu_Window(X, Y, W, H, 0) {
00265   end();
00266   set_modal();
00267   clear_border();
00268   set_menu_window();
00269   menu = L;
00270   if (L->labelcolor_ || Fl::scheme() || L->labeltype_ > FL_NO_LABEL) clear_overlay();
00271 }
00272 
00273 menuwindow::menuwindow(const Fl_Menu_Item* m, int X, int Y, int Wp, int Hp,
00274                        const Fl_Menu_Item* picked, const Fl_Menu_Item* t, 
00275                        int menubar, int menubar_title, int right_edge)
00276   : Fl_Menu_Window(X, Y, Wp, Hp, 0)
00277 {
00278   int scr_x, scr_y, scr_w, scr_h;
00279   int tx = X, ty = Y;
00280 
00281   Fl::screen_xywh(scr_x, scr_y, scr_w, scr_h);
00282   if (!right_edge || right_edge > scr_x+scr_w) right_edge = scr_x+scr_w;
00283 
00284   end();
00285   set_modal();
00286   clear_border();
00287   set_menu_window();
00288   menu = m;
00289   if (m) m = m->first(); // find the first item that needs to be rendered
00290   drawn_selected = -1;
00291   if (button) {
00292     box(button->box());
00293     if (box() == FL_NO_BOX || box() == FL_FLAT_BOX) box(FL_UP_BOX);
00294   } else {
00295     box(FL_UP_BOX);
00296   }
00297   color(button && !Fl::scheme() ? button->color() : FL_GRAY);
00298   selected = -1;
00299   {
00300     int j = 0;
00301     if (m) for (const Fl_Menu_Item* m1=m; ; m1 = m1->next(), j++) {
00302       if (picked) {
00303         if (m1 == picked) {selected = j; picked = 0;}
00304         else if (m1 > picked) {selected = j-1; picked = 0; Wp = Hp = 0;}
00305     }
00306     if (!m1->text) break;
00307   }
00308   numitems = j;}
00309 
00310   if (menubar) {
00311     itemheight = 0;
00312     title = 0;
00313     return;
00314   }
00315 
00316   itemheight = 1;
00317 
00318   int hotKeysw = 0;
00319   int hotModsw = 0;
00320   int Wtitle = 0;
00321   int Htitle = 0;
00322   if (t) Wtitle = t->measure(&Htitle, button) + 12;
00323   int W = 0;
00324   if (m) for (; m->text; m = m->next()) {
00325     int hh; 
00326     int w1 = m->measure(&hh, button);
00327     if (hh+LEADING>itemheight) itemheight = hh+LEADING;
00328     if (m->flags&(FL_SUBMENU|FL_SUBMENU_POINTER)) w1 += 14;
00329     if (w1 > W) W = w1;
00330     // calculate the maximum width of all shortcuts
00331     if (m->shortcut_) {
00332       // s is a pointerto the utf8 string for the entire shortcut
00333       // k points only to the key part (minus the modifier keys)
00334       const char *k, *s = fl_shortcut_label(m->shortcut_, &k);
00335       if (fl_utf_nb_char((const unsigned char*)k, strlen(k))<=4) {
00336         // a regular shortcut has a right-justified modifier followed by a left-justified key
00337         w1 = int(fl_width(s, k-s));
00338         if (w1 > hotModsw) hotModsw = w1;
00339         w1 = int(fl_width(k))+4;
00340         if (w1 > hotKeysw) hotKeysw = w1;
00341       } else {
00342         // a shortcut with a long modifier is right-justified to the menu
00343         w1 = int(fl_width(s))+4;
00344         if (w1 > (hotModsw+hotKeysw)) {
00345           hotModsw = w1-hotKeysw;
00346         }
00347       }
00348     }
00349     if (m->labelcolor_ || Fl::scheme() || m->labeltype_ > FL_NO_LABEL) clear_overlay();
00350   }
00351   shortcutWidth = hotKeysw;
00352   if (selected >= 0 && !Wp) X -= W/2;
00353   int BW = Fl::box_dx(box());
00354   W += hotKeysw+hotModsw+2*BW+7;
00355   if (Wp > W) W = Wp;
00356   if (Wtitle > W) W = Wtitle;
00357 
00358   if (X < scr_x) X = scr_x; if (X > scr_x+scr_w-W) X = right_edge-W; //X= scr_x+scr_w-W;
00359   x(X); w(W);
00360   h((numitems ? itemheight*numitems-LEADING : 0)+2*BW+3);
00361   if (selected >= 0) {
00362     Y = Y+(Hp-itemheight)/2-selected*itemheight-BW;
00363   } else {
00364     Y = Y+Hp;
00365     // if the menu hits the bottom of the screen, we try to draw
00366     // it above the menubar instead. We will not adjust any menu
00367     // that has a selected item.
00368     if (Y+h()>scr_y+scr_h && Y-h()>=scr_y) {
00369       if (Hp>1) {
00370         // if we know the height of the Fl_Menu_, use it
00371         Y = Y-Hp-h();
00372       } else if (t) {
00373         // assume that the menubar item height relates to the first
00374         // menuitem as well
00375         Y = Y-itemheight-h()-Fl::box_dh(box());
00376       } else {
00377         // draw the menu to the right
00378         Y = Y-h()+itemheight+Fl::box_dy(box());
00379       }
00380     }
00381   }
00382   if (m) y(Y); else {y(Y-2); w(1); h(1);}
00383 
00384   if (t) {
00385     if (menubar_title) {
00386       int dy = Fl::box_dy(button->box())+1;
00387       int ht = button->h()-dy*2;
00388       title = new menutitle(tx, ty-ht-dy, Wtitle, ht, t);
00389     } else {
00390       int dy = 2;
00391       int ht = Htitle+2*BW+3;
00392       title = new menutitle(X, Y-ht-dy, Wtitle, ht, t);
00393     }
00394   } else {
00395     title = 0;
00396   }
00397 }
00398 
00399 menuwindow::~menuwindow() {
00400   hide();
00401   delete title;
00402 }
00403 
00404 void menuwindow::position(int X, int Y) {
00405   if (title) {title->position(X, title->y()+Y-y());}
00406   Fl_Menu_Window::position(X, Y);
00407   // x(X); y(Y); // don't wait for response from X
00408 }
00409 
00410 // scroll so item i is visible on screen
00411 void menuwindow::autoscroll(int n) {
00412   int scr_x, scr_y, scr_w, scr_h;
00413   int Y = y()+Fl::box_dx(box())+2+n*itemheight;
00414 
00415   Fl::screen_xywh(scr_x, scr_y, scr_w, scr_h);
00416   if (Y <= scr_y) Y = scr_y-Y+10;
00417   else {
00418     Y = Y+itemheight-scr_h-scr_y;
00419     if (Y < 0) return;
00420     Y = -Y-10;
00421   }
00422   Fl_Menu_Window::position(x(), y()+Y);
00423   // y(y()+Y); // don't wait for response from X
00424 }
00425 
00427 
00428 void menuwindow::drawentry(const Fl_Menu_Item* m, int n, int eraseit) {
00429   if (!m) return; // this happens if -1 is selected item and redrawn
00430 
00431   int BW = Fl::box_dx(box());
00432   int xx = BW;
00433   int W = w();
00434   int ww = W-2*BW-1;
00435   int yy = BW+1+n*itemheight;
00436   int hh = itemheight - LEADING;
00437 
00438   if (eraseit && n != selected) {
00439     fl_push_clip(xx+1, yy-(LEADING-2)/2, ww-2, hh+(LEADING-2));
00440     draw_box(box(), 0, 0, w(), h(), button ? button->color() : color());
00441     fl_pop_clip();
00442   }
00443 
00444   m->draw(xx, yy, ww, hh, button, n==selected);
00445 
00446   // the shortcuts and arrows assume fl_color() was left set by draw():
00447   if (m->submenu()) {
00448     int sz = (hh-7)&-2;
00449     int y1 = yy+(hh-sz)/2;
00450     int x1 = xx+ww-sz-3;
00451     fl_polygon(x1+2, y1, x1+2, y1+sz, x1+sz/2+2, y1+sz/2);
00452   } else if (m->shortcut_) {
00453     Fl_Font f = m->labelsize_ || m->labelfont_ ? (Fl_Font)m->labelfont_ :
00454                     button ? button->textfont() : FL_HELVETICA;
00455     fl_font(f, m->labelsize_ ? m->labelsize_ :
00456                    button ? button->textsize() : FL_NORMAL_SIZE);
00457     const char *k, *s = fl_shortcut_label(m->shortcut_, &k);
00458     if (fl_utf_nb_char((const unsigned char*)k, strlen(k))<=4) {
00459       // righ-align the modifiers and left-align the key
00460       char buf[32]; strcpy(buf, s); buf[k-s] = 0;
00461       fl_draw(buf, xx, yy, ww-shortcutWidth, hh, FL_ALIGN_RIGHT);
00462       fl_draw(  k, xx+ww-shortcutWidth, yy, shortcutWidth, hh, FL_ALIGN_LEFT);
00463     } else {
00464       // right-align to the menu
00465       fl_draw(s, xx, yy, ww-4, hh, FL_ALIGN_RIGHT);
00466     }
00467   }
00468 
00469   if (m->flags & FL_MENU_DIVIDER) {
00470     fl_color(FL_DARK3);
00471     fl_xyline(BW-1, yy+hh+(LEADING-2)/2, W-2*BW+2);
00472     fl_color(FL_LIGHT3);
00473     fl_xyline(BW-1, yy+hh+((LEADING-2)/2+1), W-2*BW+2);
00474   }
00475 }
00476 
00477 void menutitle::draw() {
00478   menu->draw(0, 0, w(), h(), button, 2);
00479 }
00480 
00481 void menuwindow::draw() {
00482   if (damage() != FL_DAMAGE_CHILD) {    // complete redraw
00483     fl_draw_box(box(), 0, 0, w(), h(), button ? button->color() : color());
00484     if (menu) {
00485       const Fl_Menu_Item* m; int j;
00486       for (m=menu->first(), j=0; m->text; j++, m = m->next()) drawentry(m, j, 0);
00487     }
00488   } else {
00489     if (damage() & FL_DAMAGE_CHILD && selected!=drawn_selected) { // change selection
00490       drawentry(menu->next(drawn_selected), drawn_selected, 1);
00491       drawentry(menu->next(selected), selected, 1);
00492     }
00493   }         
00494   drawn_selected = selected;
00495 }
00496 
00497 void menuwindow::set_selected(int n) {
00498   if (n != selected) {selected = n; damage(FL_DAMAGE_CHILD);}
00499 }
00500 
00502 
00503 int menuwindow::find_selected(int mx, int my) {
00504   if (!menu || !menu->text) return -1;
00505   mx -= x();
00506   my -= y();
00507   if (my < 0 || my >= h()) return -1;
00508   if (!itemheight) { // menubar
00509     int xx = 3; int n = 0;
00510     const Fl_Menu_Item* m = menu ? menu->first() : 0;
00511     for (; ; m = m->next(), n++) {
00512       if (!m->text) return -1;
00513       xx += m->measure(0, button) + 16;
00514       if (xx > mx) break;
00515     }
00516     return n;
00517   }
00518   if (mx < Fl::box_dx(box()) || mx >= w()) return -1;
00519   int n = (my-Fl::box_dx(box())-1)/itemheight;
00520   if (n < 0 || n>=numitems) return -1;
00521   return n;
00522 }
00523 
00524 // return horizontal position for item n in a menubar:
00525 int menuwindow::titlex(int n) {
00526   const Fl_Menu_Item* m;
00527   int xx = 3;
00528   for (m=menu->first(); n--; m = m->next()) xx += m->measure(0, button) + 16;
00529   return xx;
00530 }
00531 
00532 // return 1, if the given root coordinates are inside the window
00533 int menuwindow::is_inside(int mx, int my) {
00534   if ( mx < x_root() || mx >= x_root() + w() ||
00535        my < y_root() || my >= y_root() + h()) {
00536     return 0;
00537   }
00538   return 1;
00539 }
00540 
00542 // Fl_Menu_Item::popup(...)
00543 
00544 // Because Fl::grab() is done, all events go to one of the menu windows.
00545 // But the handle method needs to look at all of them to find out
00546 // what item the user is pointing at.  And it needs a whole lot
00547 // of other state variables to determine what is going on with
00548 // the currently displayed menus.
00549 // So the main loop (handlemenu()) puts all the state in a structure
00550 // and puts a pointer to it in a static location, so the handle()
00551 // on menus can refer to it and alter it.  The handle() method
00552 // changes variables in this state to indicate what item is
00553 // picked, but does not actually alter the display, instead the
00554 // main loop does that.  This is because the X mapping and unmapping
00555 // of windows is slow, and we don't want to fall behind the events.
00556 
00557 // values for menustate.state:
00558 #define INITIAL_STATE 0 // no mouse up or down since popup() called
00559 #define PUSH_STATE 1    // mouse has been pushed on a normal item
00560 #define DONE_STATE 2    // exit the popup, the current item was picked
00561 #define MENU_PUSH_STATE 3 // mouse has been pushed on a menu title
00562 
00563 struct menustate {
00564   const Fl_Menu_Item* current_item; // what mouse is pointing at
00565   int menu_number; // which menu it is in
00566   int item_number; // which item in that menu, -1 if none
00567   menuwindow* p[20]; // pointers to menus
00568   int nummenus;
00569   int menubar; // if true p[0] is a menubar
00570   int state;
00571   menuwindow* fakemenu; // kludge for buttons in menubar
00572   int is_inside(int mx, int my);
00573 };
00574 static menustate* p=0;
00575 
00576 // return 1 if the coordinates are inside any of the menuwindows
00577 int menustate::is_inside(int mx, int my) {
00578   int i;
00579   for (i=nummenus-1; i>=0; i--) {
00580     if (p[i]->is_inside(mx, my))
00581       return 1;
00582   }
00583   return 0;
00584 }
00585 
00586 static inline void setitem(const Fl_Menu_Item* i, int m, int n) {
00587   p->current_item = i;
00588   p->menu_number = m;
00589   p->item_number = n;
00590 }
00591 
00592 static void setitem(int m, int n) {
00593   menustate &pp = *p;
00594   pp.current_item = (n >= 0) ? pp.p[m]->menu->next(n) : 0;
00595   pp.menu_number = m;
00596   pp.item_number = n;
00597 }
00598 
00599 static int forward(int menu) { // go to next item in menu menu if possible
00600   menustate &pp = *p;
00601   // Fl_Menu_Button can geberate menu=-1. This line fixes it and selectes the first item.
00602   if (menu==-1) 
00603     menu = 0;
00604   menuwindow &m = *(pp.p[menu]);
00605   int item = (menu == pp.menu_number) ? pp.item_number : m.selected;
00606   while (++item < m.numitems) {
00607     const Fl_Menu_Item* m1 = m.menu->next(item);
00608     if (m1->activevisible()) {setitem(m1, menu, item); return 1;}
00609   }
00610   return 0;
00611 }
00612 
00613 static int backward(int menu) { // previous item in menu menu if possible
00614   menustate &pp = *p;
00615   menuwindow &m = *(pp.p[menu]);
00616   int item = (menu == pp.menu_number) ? pp.item_number : m.selected;
00617   if (item < 0) item = m.numitems;
00618   while (--item >= 0) {
00619     const Fl_Menu_Item* m1 = m.menu->next(item);
00620     if (m1->activevisible()) {setitem(m1, menu, item); return 1;}
00621   }
00622   return 0;
00623 }
00624 
00625 int menuwindow::handle(int e) {
00626 #if defined (__APPLE__) || defined (USE_X11)
00627   // This off-route takes care of the "detached menu" bug on OS X.
00628   // Apple event handler requires that we hide all menu windows right
00629   // now, so that Carbon can continue undisturbed with handling window
00630   // manager events, like dragging the application window.
00631   int ret = early_hide_handle(e);
00632   menustate &pp = *p;
00633   if (pp.state == DONE_STATE) {
00634     hide();
00635     if (pp.fakemenu) {
00636       pp.fakemenu->hide();
00637       if (pp.fakemenu->title)
00638         pp.fakemenu->title->hide();
00639     }
00640     int i = pp.nummenus;
00641     while (i>0) {
00642       menuwindow *mw = pp.p[--i];
00643       if (mw) {
00644         mw->hide();
00645         if (mw->title) 
00646           mw->title->hide();
00647       }
00648     }
00649   }
00650   return ret;
00651 }
00652 
00653 int menuwindow::early_hide_handle(int e) {
00654 #endif
00655   menustate &pp = *p;
00656   switch (e) {
00657   case FL_KEYBOARD:
00658     switch (Fl::event_key()) {
00659     case FL_BackSpace:
00660     BACKTAB:
00661       if (!backward(pp.menu_number)) {pp.item_number = -1;backward(pp.menu_number);}
00662       return 1;
00663     case FL_Up:
00664       if (pp.menubar && pp.menu_number == 0) {
00665         // Do nothing...
00666       } else if (backward(pp.menu_number)) {
00667         // Do nothing...
00668       } else if (pp.menubar && pp.menu_number==1) {
00669         setitem(0, pp.p[0]->selected);
00670       }
00671       return 1;
00672     case FL_Tab:
00673       if (Fl::event_shift()) goto BACKTAB;
00674     case FL_Down:
00675       if (pp.menu_number || !pp.menubar) {
00676         if (!forward(pp.menu_number) && Fl::event_key()==FL_Tab) {
00677           pp.item_number = -1;
00678           forward(pp.menu_number);
00679         }
00680       } else if (pp.menu_number < pp.nummenus-1) {
00681         forward(pp.menu_number+1);
00682       }
00683       return 1;
00684     case FL_Right:
00685       if (pp.menubar && (pp.menu_number<=0 || (pp.menu_number==1 && pp.nummenus==2)))
00686         forward(0);
00687       else if (pp.menu_number < pp.nummenus-1) forward(pp.menu_number+1);
00688       return 1;
00689     case FL_Left:
00690       if (pp.menubar && pp.menu_number<=1) backward(0);
00691       else if (pp.menu_number>0)
00692         setitem(pp.menu_number-1, pp.p[pp.menu_number-1]->selected);
00693       return 1;
00694     case FL_Enter:
00695     case FL_KP_Enter:
00696     case ' ':
00697       pp.state = DONE_STATE;
00698       return 1;
00699     case FL_Escape:
00700       setitem(0, -1, 0);
00701       pp.state = DONE_STATE;
00702       return 1;
00703     }
00704     break;
00705   case FL_SHORTCUT: 
00706     {
00707       for (int mymenu = pp.nummenus; mymenu--;) {
00708         menuwindow &mw = *(pp.p[mymenu]);
00709         int item; const Fl_Menu_Item* m = mw.menu->find_shortcut(&item);
00710         if (m) {
00711           setitem(m, mymenu, item);
00712           if (!m->submenu()) pp.state = DONE_STATE;
00713           return 1;
00714         }
00715       }
00716     }
00717     break;
00718   case FL_ENTER:
00719   case FL_MOVE:
00720   case FL_PUSH:
00721   case FL_DRAG:
00722     {
00723 #ifdef __QNX__
00724       // STR 704: workaround QNX X11 bug - in QNX a FL_MOVE event is sent
00725       // right after FL_RELEASE...
00726       if (pp.state == DONE_STATE) return 1;
00727 #endif // __QNX__
00728       int mx = Fl::event_x_root();
00729       int my = Fl::event_y_root();
00730       int item=0; int mymenu = pp.nummenus-1;
00731       // Clicking or dragging outside menu cancels it...
00732       if ((!pp.menubar || mymenu) && !pp.is_inside(mx, my)) {
00733         setitem(0, -1, 0);
00734         if (e==FL_PUSH)
00735           pp.state = DONE_STATE;
00736         return 1;
00737       }
00738       for (mymenu = pp.nummenus-1; ; mymenu--) {
00739         item = pp.p[mymenu]->find_selected(mx, my);
00740         if (item >= 0) 
00741           break;
00742         if (mymenu <= 0) {
00743           // buttons in menubars must be deselected if we move outside of them!
00744           if (pp.menu_number==-1 && e==FL_PUSH) {
00745             pp.state = DONE_STATE;
00746             return 1;
00747           }
00748           if (pp.current_item && pp.menu_number==0 && !pp.current_item->submenu()) {
00749             if (e==FL_PUSH)
00750               pp.state = DONE_STATE;
00751             setitem(0, -1, 0);
00752             return 1;
00753           }
00754           // all others can stay selected
00755           return 0;
00756         }
00757       }
00758       if (my == 0 && item > 0) setitem(mymenu, item - 1);
00759       else setitem(mymenu, item);
00760       if (e == FL_PUSH) {
00761         if (pp.current_item && pp.current_item->submenu() // this is a menu title
00762             && item != pp.p[mymenu]->selected // and it is not already on
00763             && !pp.current_item->callback_) // and it does not have a callback
00764           pp.state = MENU_PUSH_STATE;
00765         else
00766           pp.state = PUSH_STATE;
00767       }
00768     }
00769     return 1;
00770   case FL_RELEASE:
00771     // Mouse must either be held down/dragged some, or this must be
00772     // the second click (not the one that popped up the menu):
00773     if (   !Fl::event_is_click() 
00774         || pp.state == PUSH_STATE 
00775         || (pp.menubar && pp.current_item && !pp.current_item->submenu()) // button
00776         ) {
00777 #if 0 // makes the check/radio items leave the menu up
00778       const Fl_Menu_Item* m = pp.current_item;
00779       if (m && button && (m->flags & (FL_MENU_TOGGLE|FL_MENU_RADIO))) {
00780         ((Fl_Menu_*)button)->picked(m);
00781         pp.p[pp.menu_number]->redraw();
00782       } else
00783 #endif
00784       // do nothing if they try to pick inactive items
00785       if (!pp.current_item || pp.current_item->activevisible())
00786         pp.state = DONE_STATE;
00787     }
00788     return 1;
00789   }
00790   return Fl_Window::handle(e);
00791 }
00792 
00803 const Fl_Menu_Item* Fl_Menu_Item::pulldown(
00804     int X, int Y, int W, int H,
00805     const Fl_Menu_Item* initial_item,
00806     const Fl_Menu_* pbutton,
00807     const Fl_Menu_Item* t,
00808     int menubar) const {
00809   Fl_Group::current(0); // fix possible user error...
00810 
00811   button = pbutton;
00812   if (pbutton) {
00813     for (Fl_Window* w = pbutton->window(); w; w = w->window()) {
00814       X += w->x();
00815       Y += w->y();
00816     }
00817   } else {
00818     X += Fl::event_x_root()-Fl::event_x();
00819     Y += Fl::event_y_root()-Fl::event_y();
00820   }
00821   menuwindow mw(this, X, Y, W, H, initial_item, t, menubar);
00822   Fl::grab(mw);
00823   menustate pp; p = &pp;
00824   pp.p[0] = &mw;
00825   pp.nummenus = 1;
00826   pp.menubar = menubar;
00827   pp.state = INITIAL_STATE;
00828   pp.fakemenu = 0; // kludge for buttons in menubar
00829 
00830   // preselected item, pop up submenus if necessary:
00831   if (initial_item && mw.selected >= 0) {
00832     setitem(0, mw.selected);
00833     goto STARTUP;
00834   }
00835 
00836   pp.current_item = 0; pp.menu_number = 0; pp.item_number = -1;
00837   if (menubar) {
00838     // find the initial menu
00839     if (!mw.handle(FL_DRAG)) {
00840       Fl::grab(0);
00841       return 0;
00842     }
00843   }
00844   initial_item = pp.current_item;
00845   if (initial_item) goto STARTUP;
00846 
00847   // the main loop, runs until p.state goes to DONE_STATE:
00848   for (;;) {
00849 
00850     // make sure all the menus are shown:
00851     {
00852       for (int k = menubar; k < pp.nummenus; k++) {
00853         if (!pp.p[k]->shown()) {
00854           if (pp.p[k]->title) pp.p[k]->title->show();
00855           pp.p[k]->show();
00856         }
00857       }
00858     }
00859 
00860     // get events:
00861     {
00862       const Fl_Menu_Item* oldi = pp.current_item;
00863       Fl::wait();
00864       if (pp.state == DONE_STATE) break; // done.
00865       if (pp.current_item == oldi) continue;
00866     }
00867 
00868     // only do rest if item changes:
00869     if(pp.fakemenu) {delete pp.fakemenu; pp.fakemenu = 0;} // turn off "menubar button"
00870 
00871     if (!pp.current_item) { // pointing at nothing
00872       // turn off selection in deepest menu, but don't erase other menus:
00873       pp.p[pp.nummenus-1]->set_selected(-1);
00874       continue;
00875     }
00876 
00877     if(pp.fakemenu) {delete pp.fakemenu; pp.fakemenu = 0;}
00878     initial_item = 0; // stop the startup code
00879     pp.p[pp.menu_number]->autoscroll(pp.item_number);
00880 
00881   STARTUP:
00882     menuwindow& cw = *pp.p[pp.menu_number];
00883     const Fl_Menu_Item* m = pp.current_item;
00884     if (!m->activevisible()) { // pointing at inactive item
00885       cw.set_selected(-1);
00886       initial_item = 0; // turn off startup code
00887       continue;
00888     }
00889     cw.set_selected(pp.item_number);
00890 
00891     if (m==initial_item) initial_item=0; // stop the startup code if item found
00892     if (m->submenu()) {
00893       const Fl_Menu_Item* title = m;
00894       const Fl_Menu_Item* menutable;
00895       if (m->flags&FL_SUBMENU) menutable = m+1;
00896       else menutable = (Fl_Menu_Item*)(m)->user_data_;
00897       // figure out where new menu goes:
00898       int nX, nY;
00899       if (!pp.menu_number && pp.menubar) {      // menu off a menubar:
00900         nX = cw.x() + cw.titlex(pp.item_number);
00901         nY = cw.y() + cw.h();
00902         initial_item = 0;
00903       } else {
00904         nX = cw.x() + cw.w();
00905         nY = cw.y() + pp.item_number * cw.itemheight;
00906         title = 0;
00907       }
00908       if (initial_item) { // bring up submenu containing initial item:
00909         menuwindow* n = new menuwindow(menutable,X,Y,W,H,initial_item,title,0,0,cw.x());
00910         pp.p[pp.nummenus++] = n;
00911         // move all earlier menus to line up with this new one:
00912         if (n->selected>=0) {
00913           int dy = n->y()-nY;
00914           int dx = n->x()-nX;
00915           for (int menu = 0; menu <= pp.menu_number; menu++) {
00916             menuwindow* tt = pp.p[menu];
00917             int nx = tt->x()+dx; if (nx < 0) {nx = 0; dx = -tt->x();}
00918             int ny = tt->y()+dy; if (ny < 0) {ny = 0; dy = -tt->y();}
00919             tt->position(nx, ny);
00920           }
00921           setitem(pp.nummenus-1, n->selected);
00922           goto STARTUP;
00923         }
00924       } else if (pp.nummenus > pp.menu_number+1 &&
00925                  pp.p[pp.menu_number+1]->menu == menutable) {
00926         // the menu is already up:
00927         while (pp.nummenus > pp.menu_number+2) delete pp.p[--pp.nummenus];
00928         pp.p[pp.nummenus-1]->set_selected(-1);
00929       } else {
00930         // delete all the old menus and create new one:
00931         while (pp.nummenus > pp.menu_number+1) delete pp.p[--pp.nummenus];
00932         pp.p[pp.nummenus++]= new menuwindow(menutable, nX, nY,
00933                                           title?1:0, 0, 0, title, 0, menubar, cw.x());
00934       }
00935     } else { // !m->submenu():
00936       while (pp.nummenus > pp.menu_number+1) delete pp.p[--pp.nummenus];
00937       if (!pp.menu_number && pp.menubar) {
00938         // kludge so "menubar buttons" turn "on" by using menu title:
00939         pp.fakemenu = new menuwindow(0,
00940                                   cw.x()+cw.titlex(pp.item_number),
00941                                   cw.y()+cw.h(), 0, 0,
00942                                   0, m, 0, 1);
00943         pp.fakemenu->title->show();
00944       }
00945     }
00946   }
00947   const Fl_Menu_Item* m = pp.current_item;
00948   Fl::grab(0);
00949   delete pp.fakemenu;
00950   while (pp.nummenus>1) delete pp.p[--pp.nummenus];
00951   mw.hide();
00952   return m;
00953 }
00954 
00977 const Fl_Menu_Item* Fl_Menu_Item::popup(
00978   int X, int Y,
00979   const char* title,
00980   const Fl_Menu_Item* picked,
00981   const Fl_Menu_* button
00982   ) const {
00983   static Fl_Menu_Item dummy; // static so it is all zeros
00984   dummy.text = title;
00985   return pulldown(X, Y, 0, 0, picked, button, title ? &dummy : 0);
00986 }
00987 
01000 const Fl_Menu_Item* Fl_Menu_Item::find_shortcut(int* ip, const bool require_alt) const {
01001   const Fl_Menu_Item* m = first();
01002   if (m) for (int ii = 0; m->text; m = m->next(), ii++) {
01003     if (m->activevisible()) {
01004       if (Fl::test_shortcut(m->shortcut_)
01005          || Fl_Widget::test_shortcut(m->text, require_alt)) {
01006         if (ip) *ip=ii;
01007         return m;
01008       }
01009     }
01010   }
01011   return 0;
01012 }
01013 
01014 // Recursive search of all submenus for anything with this key as a
01015 // shortcut.  Only uses the shortcut field, ignores &x in the labels:
01024 const Fl_Menu_Item* Fl_Menu_Item::test_shortcut() const {
01025   const Fl_Menu_Item* m = first();
01026   const Fl_Menu_Item* ret = 0;
01027   if (m) for (; m->text; m = m->next()) {
01028     if (m->activevisible()) {
01029       // return immediately any match of an item in top level menu:
01030       if (Fl::test_shortcut(m->shortcut_)) return m;
01031       // if (Fl_Widget::test_shortcut(m->text)) return m;
01032       // only return matches from lower menu if nothing found in top menu:
01033       if (!ret && m->submenu()) {
01034         const Fl_Menu_Item* s =
01035           (m->flags&FL_SUBMENU) ? m+1:(const Fl_Menu_Item*)m->user_data_;
01036         ret = s->test_shortcut();
01037       }
01038     }
01039   }
01040   return ret;
01041 }
01042 
01043 //
01044 // End of "$Id: Fl_Menu.cxx 8076 2010-12-20 13:59:09Z matt $".
01045 //