diff options
Diffstat (limited to 'src/Fl_Menu.cxx')
| -rw-r--r-- | src/Fl_Menu.cxx | 694 |
1 files changed, 694 insertions, 0 deletions
diff --git a/src/Fl_Menu.cxx b/src/Fl_Menu.cxx new file mode 100644 index 000000000..ddd80c80b --- /dev/null +++ b/src/Fl_Menu.cxx @@ -0,0 +1,694 @@ +// Fl_Menu.C + +// fltk (Fast Light Tool Kit) version 0.99 +// Copyright (C) 1998 Bill Spitzak + +// Warning: this menu code is quite a mess! + +// This file contains code for implementing Fl_Menu_Item, and for +// methods for bringing up popup menu hierarchies without using the +// Fl_Menu_ widget. + +#include <FL/Fl.H> +#include <FL/Fl_Menu_Window.H> +#include <FL/Fl_Menu_.H> +#include <FL/fl_draw.H> + +int Fl_Menu_Item::size() const { + const Fl_Menu_Item* m = this; + int nest = 0; + for (;;) { + if (!m->text) { + if (!nest) return (m-this+1); + nest--; + } else if (m->flags & FL_SUBMENU) { + nest++; + } + m++; + } +} + +const Fl_Menu_Item* Fl_Menu_Item::next(int n) const { + if (n < 0) return 0; // this is so selected==-1 returns NULL + const Fl_Menu_Item* m = this; + int nest = 0; + while (n>0) { + if (!m->text) { + if (!nest) return m; + nest--; + } else if (m->flags&FL_SUBMENU) { + nest++; + } + m++; + if (!nest && m->visible()) n--; + } + return m; +} + +// appearance of current menus are pulled from this parent widget: +static const Fl_Menu_* button; + +//////////////////////////////////////////////////////////////// + +// tiny window for title of menu: +class menutitle : public Fl_Menu_Window { + void draw(); +public: + const Fl_Menu_Item* menu; + menutitle(int X, int Y, int W, int H, const Fl_Menu_Item*); +}; + +// each vertical menu has one of these: +class menuwindow : public Fl_Menu_Window { + void draw(); + void drawentry(const Fl_Menu_Item*, int i, int erase); +public: + menutitle* title; + int handle(int); + int itemheight; // zero == menubar + int numitems; + int selected; + int drawn_selected; // last redraw has this selected + const Fl_Menu_Item* menu; + menuwindow(const Fl_Menu_Item* m, int X, int Y, int W, int H, + const Fl_Menu_Item* picked, const Fl_Menu_Item* title, + int menubar = 0); + ~menuwindow(); + void set_selected(int); + int find_selected(int mx, int my); + int titlex(int); + void autoscroll(int); + void position(int x, int y); +}; + +#define BW 3 // border thickness +#define LEFT 6 // between left edge of item and edge of box +#define RIGHT 8 // between right edge of item and edge of box +#define BOTTOM 4 // between bottom item and bottom of box +#define LEADING 4 // extra vertical leading +#define TOP 5 // between top item and top of box + +extern char fl_draw_shortcut; + +// width of label, including effect of & characters: +int Fl_Menu_Item::measure(int* hp, const Fl_Menu_* m) const { + Fl_Label l; + l.value = text; + l.type = labeltype_; + l.font = labelsize_ ? labelfont_ : uchar(m ? m->textfont() : FL_HELVETICA); + if (l.font < 4) l.font = (Fl_Font)(l.font | Fl_Menu_::default_font()); + l.size = labelsize_ ? labelsize_ : m ? m->textsize() : FL_NORMAL_SIZE; + l.size += Fl_Menu_::default_size(); + l.color = labelcolor_; + fl_draw_shortcut = 1; + int w = 0; int h = 0; l.measure(w, hp ? *hp : h); + fl_draw_shortcut = 0; + if (flags & (FL_MENU_TOGGLE|FL_MENU_RADIO)) w += 14; + return w; +} + +void Fl_Menu_Item::draw(int x, int y, int w, int h, const Fl_Menu_* m, + int selected) const { + Fl_Label l; + l.value = text; + l.type = labeltype_; + l.font = labelsize_ ? labelfont_ : uchar(m ? m->textfont() : FL_HELVETICA); + if (l.font < 4) l.font = (Fl_Font)(l.font | Fl_Menu_::default_font()); + l.size = labelsize_ ? labelsize_ : m ? m->textsize() : FL_NORMAL_SIZE; + l.size += Fl_Menu_::default_size(); + l.color = !active() ? (labelcolor_|8) : labelcolor_; + Fl_Color color = m ? m->color() : FL_GRAY; + if (selected) { + Fl_Color r = m ? m->selection_color() : FL_SELECTION_COLOR; + Fl_Boxtype b = m && m->down_box() ? m->down_box() : FL_FLAT_BOX; + if (contrast(r,color)!=r) { // back compatability boxtypes + if (selected == 2) { // menu title + r = color; + b = m ? m->box() : FL_UP_BOX; + } else { + r = (Fl_Color)(FL_COLOR_CUBE-1); // white + b = FL_THIN_UP_BOX; + } + } else { + l.color = contrast((Fl_Color)labelcolor_, r); + } + if (selected == 2) { + fl_draw_box(b, x, y, w, h, r); + x += LEFT; + w -= LEFT+RIGHT; + } else { + fl_draw_box(b, x-2, y-1, w+7, h+2, r); + } + } + + if (flags & (FL_MENU_TOGGLE|FL_MENU_RADIO)) { + int y1 = y+(h-14)/2; + fl_color(FL_DARK3); + if (flags & FL_MENU_RADIO) { + fl_line(x-1, y1+7, x+5, y1+1, x+11, y1+7); + if (selected) { + fl_color(color); + fl_polygon(x, y1+7, x+5, y1+2, x+10, y1+7, x+5, y1+12); + } + fl_color(FL_LIGHT3); fl_line(x+11, y1+7, x+5, y1+13, x-1, y1+7); + if (value()) { + fl_color(FL_BLACK); + fl_polygon(x+1, y1+7, x+5, y1+3, x+9, y1+7, x+5, y1+11); + } + } else { + fl_yxline(x, y1+11, y1+2, x+9); + if (selected) {fl_color(color); fl_rectf(x+1, y1+3, 9, 9);} + fl_color(FL_LIGHT3); fl_xyline(x+1, y1+12, x+10, y1+3); + if (value()) {fl_color(FL_BLACK); fl_rectf(x+2, y1+4, 7, 7);} + } + x += 14; w -= 14; + } + + fl_draw_shortcut = 1; + l.draw(x, y, w, h, FL_ALIGN_LEFT); + fl_draw_shortcut = 0; +} + +menutitle::menutitle(int X, int Y, int W, int H, const Fl_Menu_Item* L) : + Fl_Menu_Window(X, Y, W, H, 0) { + end(); + set_modal(); + clear_border(); + menu = L; + if (L->labelcolor_) clear_overlay(); + box(FL_NO_BOX); +} + +menuwindow::menuwindow(const Fl_Menu_Item* m, int X, int Y, int Wp, int Hp, + const Fl_Menu_Item* picked, const Fl_Menu_Item* t, + int menubar) + : Fl_Menu_Window(X, Y, Wp, Hp, 0) +{ + end(); + set_modal(); + clear_border(); + menu = m; + drawn_selected = -1; + box(FL_NO_BOX); + selected = -1; + {int i = 0; + if (m) for (const Fl_Menu_Item* m1=m; ; m1 = m1->next(), i++) { + if (picked) { + if (m1 == picked) {selected = i; picked = 0;} + else if (m1 > picked) {selected = i-1; picked = 0; Wp = Hp = 0;} + } + if (!m1->text) break; + } + numitems = i;} + + if (menubar) { + itemheight = 0; + title = 0; + return; + } + + itemheight = 1; + + int hotKeysw = 0; + int Wtitle = 0; + int Htitle = 0; + if (t) Wtitle = t->measure(&Htitle, button); + int W = Wtitle; + if (m) for (; m->text; m = m->next()) { + int h; int w1 = m->measure(&h, button); + if (h+LEADING>itemheight) itemheight = h+LEADING; + if (m->flags&(FL_SUBMENU|FL_SUBMENU_POINTER)) w1 += 14; + if (w1 > W) W = w1; + if (m->shortcut_) { + w1 = int(fl_width(fl_shortcut_label(m->shortcut_))) + 8; + if (w1 > hotKeysw) hotKeysw = w1; + } + if (m->labelcolor_) clear_overlay(); + } + if (selected >= 0 && !Wp) X -= W/2; + W += hotKeysw+LEFT+RIGHT; if (Wp > W) W = Wp; + + if (!Wp) {if (X < 0) X = 0; if (X > Fl::w()-W) X= Fl::w()-W;} + x(X); w(W); + h((numitems ? itemheight*numitems-LEADING : 0)+TOP+BOTTOM+1); + if (selected >= 0) + Y = Y+(Hp-itemheight)/2-selected*itemheight-2; + else + Y = Y+Hp; + if (m) y(Y-1); else {y(Y-3); w(1); h(1);} + + if (t) { + int ht = button && button->h() <= 50 ? button->h()-6 + : Htitle+TOP+BOTTOM-1; + title = new menutitle(X, Y-ht-3, Wtitle+LEFT+RIGHT, ht, t); + } else + title = 0; +} + +menuwindow::~menuwindow() { + delete title; +} + +void menuwindow::position(int X, int Y) { + if (title) {title->position(X, title->y()+Y-y());} + Fl_Menu_Window::position(X, Y); + x(X); y(Y); // don't wait for response from X +} + +// scroll so item i is visible on screen +void menuwindow::autoscroll(int i) { + int Y = y()+h()-(BOTTOM + (numitems-i)*itemheight - LEADING + 1); + if (Y <= 0) Y = -Y+10; + else { + Y = Y+itemheight-Fl::h(); + if (Y <= 0) return; + Y = -Y-10; + } + Fl_Menu_Window::position(x(), y()+Y); + y(y()+Y); // don't wait for response from X +} + +//////////////////////////////////////////////////////////////// + +void menuwindow::drawentry(const Fl_Menu_Item* m, int i, int erase) { + if (!m) return; // this happens if -1 is selected item and redrawn + + int x = LEFT-3; + int W = this->w(); + int w = W-(LEFT+RIGHT-6); + int y = h()-(BOTTOM + (numitems-i)*itemheight - LEADING + 1); + int h = itemheight - LEADING; + + if (erase && i != selected) { + fl_color(button ? button->color() : FL_GRAY); + fl_rectf(x+1, y-1, w+1, h+2); + } + + m->draw(x+3, y, w-6, h, button, i==selected); + + // the shortcuts and arrows assumme fl_color() was left set by draw(): + if (m->submenu()) { + int y1 = y+(h-14)/2; + fl_polygon(x+w-10, y1+2, x+w-10, y1+2+10, x+w, y1+2+5); + } else if (m->shortcut_) { + Fl_Font f = button ? button->textfont() : FL_HELVETICA; + fl_font(f, button ? button->textsize() : FL_NORMAL_SIZE, + Fl_Menu_::default_font(), Fl_Menu_::default_size()); + fl_draw(fl_shortcut_label(m->shortcut_), x, y, w-3, h, FL_ALIGN_RIGHT); + } + + if (m->flags & FL_MENU_DIVIDER) { + fl_color(FL_DARK3); + fl_xyline(BW-1, y+h+1, W-BW); + fl_color(FL_LIGHT3); + fl_xyline(BW, y+h+2, W-BW); + } + +} + +void menutitle::draw() { + menu->draw(0, 0, w(), h(), button, 2); +} + +void menuwindow::draw() { + + if (damage() != 1) { // complete redraw + if (menu) { + fl_draw_box(button&&button->box() ? button->box() : FL_UP_BOX, + 0, 0, w(), h(), + button ? button->color() : FL_GRAY); + const Fl_Menu_Item* m; int i; + for (m=menu, i=0; m->text; i++, m = m->next()) drawentry(m, i, 0); + } + } else { + if (damage() & 1 && selected!=drawn_selected) { // change selection + drawentry(menu->next(drawn_selected), drawn_selected, 1); + drawentry(menu->next(selected), selected, 1); + } + } + drawn_selected = selected; +} + +void menuwindow::set_selected(int i) { + if (i != selected) {selected = i; damage(1);} +} + +//////////////////////////////////////////////////////////////// + +int menuwindow::find_selected(int mx, int my) { + if (!menu || !menu->text) return -1; + mx -= x(); + my -= y(); + if (my <= 0 || my >= h()) return -1; + if (!itemheight) { // menubar + int x = BW; int i = 0; + const Fl_Menu_Item* m = menu; + for (; ; m = m->next(), i++) { + if (!m->text) return -1; + x += m->measure(0, button) + 16; + if (x > mx) break; + } + return i; + } + if (mx <= 0 || mx >= w()) return -1; + my -= h()-(BOTTOM + numitems*itemheight - LEADING + 2); + if (my <= 0) return -1; + int i = my/itemheight; + if (i>=numitems) i = numitems-1; + return i; +} + +// return horizontal position for item i in a menubar: +int menuwindow::titlex(int i) { + const Fl_Menu_Item* m; + int x = BW; + for (m=menu; i--; m = m->next()) x += m->measure(0, button) + 16; + return x; +} + +// match shortcuts & label shortcuts, don't search submenus: +// returns menu item and index +const Fl_Menu_Item* Fl_Menu_Item::find_shortcut(int* ip) const { + const Fl_Menu_Item* m1 = this; + for (int ii = 0; m1 && m1->text; m1 = m1->next(1), ii++) { + if (m1->activevisible() && + (Fl::test_shortcut(m1->shortcut_) + || Fl_Widget::test_shortcut(m1->text))) {if (ip) *ip=ii; return m1;} + } + return 0; +} + +//////////////////////////////////////////////////////////////// +// Fl_Menu_Item::popup(...) + +// Because Fl::grab() is done, all events go to one of the menu windows. +// But the handle method needs to look at all of them to find out +// what item the user is pointing at. And it needs a whole lot +// of other state variables to determine what is going on with +// the currently displayed menus. +// So the main loop (handlemenu()) puts all the state in a structure +// and puts a pointer to it in a static location, so the handle() +// on menus can refer to it and alter it. The handle() method +// changes variables in this state to indicate what item is +// picked, but does not actually alter the display, instead the +// main loop does that. This is because the X mapping and unmapping +// of windows is slow, and we don't want to fall behind the events. + +struct menustate { + const Fl_Menu_Item* current_item; // what mouse is pointing at + int menu_number; // which menu it is in + int item_number; // which item in that menu + menuwindow* p[20]; // pointers to menus + int nummenus; + int menubar; // if true p[0] is a menubar + int state; // 0 at first, 1 after push, 2 when done +}; +static menustate* p; + +static inline void setitem(const Fl_Menu_Item* i, int m, int n) { + p->current_item = i; + p->menu_number = m; + p->item_number = n; +} + +static void setitem(int m, int n) { + menustate &p = *(::p); + p.current_item = (m >= 0 && n >= 0) ? + p.current_item = p.p[m]->menu->next(n) : 0; + p.menu_number = m; + p.item_number = n; +} + +static int forward(int menu) { // go to next item in menu menu if possible + menustate &p = *(::p); + menuwindow &m = *(p.p[menu]); + int item = (menu == p.menu_number) ? p.item_number : m.selected; + while (++item < m.numitems) { + const Fl_Menu_Item* m1 = m.menu->next(item); + if (m1->activevisible()) {setitem(m1, menu, item); return 1;} + } + return 0; +} + +static int backward(int menu) { // previous item in menu menu if possible + menustate &p = *(::p); + menuwindow &m = *(p.p[menu]); + int item = (menu == p.menu_number) ? p.item_number : m.selected; + while (--item >= 0) { + const Fl_Menu_Item* m1 = m.menu->next(item); + if (m1->activevisible()) {setitem(m1, menu, item); return 1;} + } + return 0; +} + +int menuwindow::handle(int e) { + menustate &p = *(::p); + switch (e) { + case FL_KEYBOARD: + switch (Fl::event_key()) { + case FL_Up: + if (p.menu_number < 0) setitem(0, 0); + if (p.menubar && p.menu_number == 0) ; + else if (backward(p.menu_number)); + else if (p.menubar && p.menu_number==1) setitem(0, p.p[0]->selected); + return 1; + case FL_Down: + if (p.menu_number < 0) setitem(0, 0); + else if (p.menu_number || !p.menubar) forward(p.menu_number); + else if (p.menu_number < p.nummenus-1) forward(p.menu_number+1); + return 1; + case FL_Right: + if (p.menubar && (p.menu_number<=0 || p.menu_number==1 && p.nummenus==2)) + forward(0); + else if (p.menu_number < p.nummenus-1) forward(p.menu_number+1); + return 1; + case FL_Left: + if (p.menubar && p.menu_number<=1) backward(0); + else if (p.menu_number>0) + setitem(p.menu_number-1, p.p[p.menu_number-1]->selected); + return 1; + case FL_Enter: + p.state = 2; + return 1; + case FL_Escape: + setitem(0, -1, 0); + p.state = 2; + return 1; + } + break; + case FL_SHORTCUT: { + for (int menu = p.nummenus; menu--;) { + menuwindow &mw = *(p.p[menu]); + int item; const Fl_Menu_Item* m = mw.menu->find_shortcut(&item); + if (m) { + setitem(m, menu, item); + if (!m->submenu()) p.state = 2; + return 1; + } + }} break; + case FL_PUSH: + //case FL_MOVE: + case FL_DRAG: { + int mx = Fl::event_x_root(); + int my = Fl::event_y_root(); + int item=0; int menu; + for (menu = p.nummenus-1; menu >= 0; menu--) { + item = p.p[menu]->find_selected(mx, my); + if (item >= 0) break; + } + setitem(menu, item); + if (e == FL_PUSH) { + // detect second click on a menu title: + if (p.current_item && item == p.p[menu]->selected) p.state = 3; + else p.state = 1; + } + } return 1; + case FL_RELEASE: + if (!p.current_item) { + if (p.state || !Fl::event_is_click()) p.state = 2; + } else { + if (p.state == 3 && Fl::event_is_click()) p.state = 2; + else if (p.current_item->activevisible() && !p.current_item->submenu()) + p.state = 2; + } + return 1; + } + return Fl_Window::handle(e); +} + +const Fl_Menu_Item* Fl_Menu_Item::pulldown( + int X, int Y, int W, int H, + const Fl_Menu_Item* initial_item, + const Fl_Menu_* pbutton, + const Fl_Menu_Item* t, + int menubar) const +{ + Fl_Group::current(0); // fix possible user error... + + button = pbutton; + if (pbutton) { + for (Fl_Window* w = pbutton->window(); w; w = w->window()) { + X += w->x(); + Y += w->y(); + } + } else { + X += Fl::event_x_root()-Fl::event_x(); + Y += Fl::event_y_root()-Fl::event_y(); + } + menuwindow mw(this, X, Y, W, H, initial_item, t, menubar); + Fl::grab(mw); + menustate p; ::p = &p; + p.p[0] = &mw; + p.nummenus = 1; + p.menubar = menubar; + p.state = 0; + + menuwindow* fakemenu = 0; // kludge for buttons in menubar + + // preselected item, pop up submenus if necessary: + if (initial_item && mw.selected >= 0) { + setitem(0, mw.selected); + goto STARTUP; + } + + p.current_item = 0; p.menu_number = -1; p.item_number = -1; + if (menubar) mw.handle(FL_DRAG); // find the initial menu + initial_item = p.current_item; + if (initial_item) goto STARTUP; + + // the main loop, runs until p.state goes to 2: + for (;;) { + + // make sure all the menus are shown: + {for (int k = menubar; k < p.nummenus; k++) + if (!p.p[k]->shown()) { + if (p.p[k]->title) p.p[k]->title->show(); + p.p[k]->show(); + } + } + + // get events: + {const Fl_Menu_Item* oldi = p.current_item; + Fl::wait(); + if (p.state == 2) break; // done. + if (p.current_item == oldi) continue;} + // only do rest if item changes: + + delete fakemenu; fakemenu = 0; // turn off "menubar button" + + if (!p.current_item) { // pointing at nothing + // turn off selection in deepest menu, but don't erase other menus: + p.p[p.nummenus-1]->set_selected(-1); + continue; + } + + delete fakemenu; fakemenu = 0; + initial_item = 0; // stop the startup code + p.p[p.menu_number]->autoscroll(p.item_number); + + STARTUP: + menuwindow& cw = *p.p[p.menu_number]; + const Fl_Menu_Item* m = p.current_item; + if (!m->activevisible()) { // pointing at inactive item + cw.set_selected(-1); + initial_item = 0; // turn off startup code + continue; + } + cw.set_selected(p.item_number); + + if (m==initial_item) initial_item=0; // stop the startup code if item found + if (m->submenu()) { + const Fl_Menu_Item* title = m; + const Fl_Menu_Item* menutable; + if (m->flags&FL_SUBMENU) menutable = m+1; + else menutable = (Fl_Menu_Item*)(m)->user_data_; + // figure out where new menu goes: + int nX, nY; + if (!p.menu_number && p.menubar) { // menu off a menubar: + nX = cw.x() + cw.titlex(p.item_number); + nY = cw.y() + cw.h(); + initial_item = 0; + } else { + nX = cw.x() + cw.w(); + nY = cw.y() + 1 + p.item_number * cw.itemheight; + title = 0; + } + if (initial_item) { // bring up submenu containing initial item: + menuwindow* n = new menuwindow(menutable,X,Y,W,H,initial_item,title); + p.p[p.nummenus++] = n; + // move all earlier menus to line up with this new one: + if (n->selected>=0) { + int dy = n->y()-nY; + int dx = n->x()-nX; + for (int menu = 0; menu <= p.menu_number; menu++) { + menuwindow* t = p.p[menu]; + int nx = t->x()+dx; if (nx < 0) {nx = 0; dx = -t->x();} + int ny = t->y()+dy+1; if (ny < 0) {ny = 0; dy = -t->y()-1;} + t->position(nx, ny); + } + setitem(p.nummenus-1, n->selected); + goto STARTUP; + } + } else if (p.nummenus > p.menu_number+1 && + p.p[p.menu_number+1]->menu == menutable) { + // the menu is already up: + while (p.nummenus > p.menu_number+2) delete p.p[--p.nummenus]; + p.p[p.nummenus-1]->set_selected(-1); + } else { + // delete all the old menus and create new one: + while (p.nummenus > p.menu_number+1) delete p.p[--p.nummenus]; + p.p[p.nummenus++]= new menuwindow(menutable,nX,nY,title?1:0,0,0,title); + } + } else { // !m->submenu(): + while (p.nummenus > p.menu_number+1) delete p.p[--p.nummenus]; + if (!p.menu_number && p.menubar) { + // kludge so "menubar buttons" turn "on" by using menu title: + fakemenu = new menuwindow(0, + cw.x()+cw.titlex(p.item_number), + cw.y()+cw.h(), 0, 0, + 0, m); + fakemenu->title->show(); + } + } + } + const Fl_Menu_Item* m = p.current_item; + delete fakemenu; + while (p.nummenus>1) delete p.p[--p.nummenus]; + mw.hide(); + Fl::release(); + return m; +} + +const Fl_Menu_Item* +Fl_Menu_Item::popup( + int X, int Y, + const char* title, + const Fl_Menu_Item* picked, + const Fl_Menu_* button + ) const +{ + static Fl_Menu_Item dummy; // static so it is all zeros + dummy.text = title; + return pulldown(X, Y, 0, 0, picked, button, title ? &dummy : 0); +} + +const Fl_Menu_Item* Fl_Menu_Item::test_shortcut() const { + const Fl_Menu_Item* m = this; + const Fl_Menu_Item* ret = 0; + if (m) for (; m->text; m = m->next()) { + if (m->activevisible()) { + // return immediately any match of an item in top level menu: + if (Fl::test_shortcut(m->shortcut_)) return m; + // if (Fl_Widget::test_shortcut(m->text)) return m; + // only return matches from lower menu if nothing found in top menu: + if (!ret && m->submenu()) { + const Fl_Menu_Item* s = + (m->flags&FL_SUBMENU) ? m+1:(const Fl_Menu_Item*)m->user_data_; + ret = s->test_shortcut(); + } + } + } + return ret; +} + +// end of Fl_Menu.C |
