allow the user to override the rootCommand from their ~/.blackboxrc
[blackbox.git] / lib / Menu.cc
blob279000e8eb9242749b8824ab049602978e2b5213
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2 // Menu.cc for Blackbox - an X11 Window manager
3 // Copyright (c) 2001 - 2005 Sean 'Shaleh' Perry <shaleh@debian.org>
4 // Copyright (c) 1997 - 2000, 2002 - 2005
5 // Bradley T Hughes <bhughes at trolltech.com>
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining a
8 // copy of this software and associated documentation files (the "Software"),
9 // to deal in the Software without restriction, including without limitation
10 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
11 // and/or sell copies of the Software, and to permit persons to whom the
12 // Software is furnished to do so, subject to the following conditions:
14 // The above copyright notice and this permission notice shall be included in
15 // all copies or substantial portions of the Software.
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 // DEALINGS IN THE SOFTWARE.
25 #include "Menu.hh"
26 #include "Application.hh"
27 #include "Bitmap.hh"
28 #include "Display.hh"
29 #include "Pen.hh"
30 #include "PixmapCache.hh"
31 #include "Resource.hh"
33 #include <X11/Xlib.h>
34 #include <X11/keysym.h>
36 #include <stdio.h>
37 #include <assert.h>
40 bt::MenuStyle **bt::MenuStyle::styles = 0;
43 bt::MenuStyle *bt::MenuStyle::get(Application &app,
44 unsigned int screen) {
45 const size_t screen_count = app.display().screenCount();
46 if (!styles) {
47 styles = new MenuStyle*[screen_count];
48 for (unsigned int i = 0; i < screen_count; ++i)
49 styles[i] = 0;
51 // we need to remap screen N to 0 if there is only one
52 const size_t index = (screen_count == 1) ? 0 : screen;
53 if (!styles[index])
54 styles[index] = new MenuStyle(app, screen);
55 return styles[index];
59 bt::MenuStyle::MenuStyle(Application &app, unsigned int screen)
60 : _app(app), _screen(screen) {
61 title.alignment = AlignLeft;
62 frame.alignment = AlignLeft;
63 title_margin = frame_margin = item_indent = 1u;
67 void bt::MenuStyle::load(const Resource &resource) {
68 // menu textures
69 title.texture =
70 textureResource(_app.display(), _screen, resource,
71 "menu.title", "Menu.Title", "black");
72 frame.texture =
73 textureResource(_app.display(), _screen, resource,
74 "menu.frame", "Menu.Frame", "white");
75 active.texture =
76 textureResource(_app.display(), _screen, resource,
77 "menu.active", "Menu.Active", "black");
79 // non-texture colors
80 title.foreground =
81 Color::namedColor(_app.display(), _screen,
82 resource.read("menu.title.foregroundColor",
83 "Menu.Title.ForegroundColor",
84 "white"));
85 title.text =
86 Color::namedColor(_app.display(), _screen,
87 resource.read("menu.title.textColor",
88 "Menu.Title.TextColor",
89 "white"));
90 frame.foreground =
91 Color::namedColor(_app.display(), _screen,
92 resource.read("menu.frame.foregroundColor",
93 "Menu.Frame.ForegroundColor",
94 "black"));
95 frame.text =
96 Color::namedColor(_app.display(), _screen,
97 resource.read("menu.frame.textColor",
98 "Menu.Frame.TextColor",
99 "black"));
100 frame.disabled =
101 Color::namedColor(_app.display(), _screen,
102 resource.read("menu.frame.disabledColor",
103 "Menu.Frame.DisabledColor",
104 "black"));
105 active.foreground =
106 Color::namedColor(_app.display(), _screen,
107 resource.read("menu.active.foregroundColor",
108 "Menu.Active.ForegroundColor",
109 "white"));
110 active.text =
111 Color::namedColor(_app.display(), _screen,
112 resource.read("menu.active.textColor",
113 "Menu.Active.TextColor",
114 "white"));
116 // fonts
117 title.font.setFontName(resource.read("menu.title.font", "Menu.Title.Font"));
118 frame.font.setFontName(resource.read("menu.frame.font", "Menu.Frame.Font"));
120 const bt::Bitmap &arrow = bt::Bitmap::rightArrow(_screen);
121 const bt::Bitmap &check = bt::Bitmap::checkMark(_screen);
122 item_indent = std::max(check.width(), check.height());
123 item_indent = std::max(item_indent, std::max(arrow.width(), arrow.height()));
124 item_indent = std::max(item_indent, textHeight(_screen, frame.font));
126 title.alignment =
127 alignResource(resource, "menu.title.alignment",
128 "Menu.Title.Alignment");
130 frame.alignment =
131 alignResource(resource, "menu.frame.alignment",
132 "Menu.Frame.Alignment");
134 std::string str;
136 str = resource.read("menu.title.marginWidth", "Menu.Title.MarginWidth", "1");
137 title_margin =
138 static_cast<unsigned int>(std::max(strtol(str.c_str(), 0, 0), 0l));
140 str = resource.read("menu.frame.marginWidth", "Menu.Frame.MarginWidth", "1");
141 frame_margin =
142 static_cast<unsigned int>(std::max(strtol(str.c_str(), 0, 0), 0l));
146 unsigned int bt::MenuStyle::separatorHeight(void) const {
147 return ((frame.texture.borderWidth() > 0 ? frame.texture.borderWidth() : 1)
148 + (2 * frame_margin));
152 unsigned int bt::MenuStyle::titleMargin(void) const {
153 return title.texture.borderWidth() + title_margin;
157 unsigned int bt::MenuStyle::frameMargin(void) const {
158 return frame.texture.borderWidth() + frame_margin;
162 unsigned int bt::MenuStyle::itemMargin(void) const {
163 return active.texture.borderWidth() + (frame_margin == 0 ? 0 : 1);
167 bt::Rect bt::MenuStyle::titleRect(const ustring &text) const {
168 const Rect &rect = textRect(_screen, title.font, text);
169 return Rect(0, 0,
170 rect.width() + (titleMargin() * 2),
171 rect.height() + (titleMargin() * 2));
175 bt::Rect bt::MenuStyle::itemRect(const MenuItem &item) const {
176 const Rect &rect = textRect(_screen, frame.font, item.label());
177 return Rect(0, 0,
178 rect.width() + ((item_indent + itemMargin()) * 2),
179 std::max(rect.height(), item_indent) + (itemMargin() * 2));
183 void bt::MenuStyle::drawTitle(Window window, const Rect &rect,
184 const ustring &text) const {
185 Pen pen(_screen, title.text);
186 Rect r;
187 r.setCoords(rect.left() + titleMargin(), rect.top(),
188 rect.right() - titleMargin(), rect.bottom());
189 drawText(title.font, pen, window, r, title.alignment, text);
193 void bt::MenuStyle::drawItem(Window window, const Rect &rect,
194 const MenuItem &item, Pixmap pixmap) const {
195 Rect r2;
196 r2.setCoords(rect.left() + item_indent, rect.top(),
197 rect.right() - item_indent, rect.bottom());
199 if (item.isSeparator()) {
200 Pen pen(_screen, frame.foreground);
201 XFillRectangle(pen.XDisplay(), window, pen.gc(),
202 r2.x(), r2.y() + frame_margin, r2.width(),
203 (frame.texture.borderWidth() > 0 ?
204 frame.texture.borderWidth() : 1));
205 return;
208 Pen fpen(_screen, (item.isEnabled() ? (item.isActive() ? active.foreground :
209 frame.foreground) : frame.disabled));
210 Pen tpen(_screen, (item.isEnabled() ? (item.isActive() ? active.text :
211 frame.text) : frame.disabled));
212 if (item.isActive() && item.isEnabled())
213 drawTexture(_screen, active.texture, window, rect, rect, pixmap);
214 drawText(frame.font, tpen, window, r2, frame.alignment, item.label());
216 if (item.isChecked()) {
217 drawBitmap(bt::Bitmap::checkMark(_screen), fpen, window,
218 bt::Rect(rect.x(), rect.y(), rect.height(), rect.height()));
221 if (item.submenu()) {
222 drawBitmap(bt::Bitmap::rightArrow(_screen), fpen, window,
223 bt::Rect(rect.x() + rect.width() - rect.height(),
224 rect.y(), rect.height(), rect.height()));
229 namespace bt {
230 // internal helper classes
232 class MenuDelay : public TimeoutHandler {
233 public:
234 Menu *showmenu;
235 Menu *hidemenu;
236 inline MenuDelay(void)
237 : showmenu(0), hidemenu(0)
239 inline void timeout(Timer *) {
240 if (hidemenu)
241 hidemenu->hide();
242 if (showmenu)
243 showmenu->show();
247 class IdentMatch {
248 public:
249 unsigned int _id;
250 inline IdentMatch(unsigned int id)
251 : _id(id)
253 inline bool operator()(const MenuItem &item) const
254 { return item.id() == _id; }
257 class IndexMatch {
258 public:
259 unsigned int _index;
260 inline IndexMatch(unsigned int index)
261 : _index(index)
263 inline bool operator()(const MenuItem &item) const
264 { return item.index() == _index; }
267 class InteractMatch {
268 public:
269 inline bool operator()(const MenuItem &item) const
270 { return item.isEnabled() && !item.isSeparator(); }
273 } // namespace bt
275 static bt::MenuDelay menudelay;
278 bt::Menu::Menu(Application &app, unsigned int screen)
279 : _app(app),
280 _screen(screen),
281 _tpixmap(0),
282 _fpixmap(0),
283 _apixmap(0),
284 _rect(0, 0, 1, 1),
285 _trect(0, 0, 0, 0),
286 _frect(0, 0, 1, 1),
287 _irect(0, 0, 1, 1),
288 _timer(&_app, &menudelay),
289 _parent_menu(0),
290 _current_submenu(0),
291 _active_submenu(0),
292 _motion(0),
293 _itemw(1),
294 _active_index(~0u),
295 _auto_delete(true),
296 _pressed(false),
297 _title_pressed(false),
298 _size_dirty(true),
299 _show_title(false),
300 _visible(false),
301 _tornoff(false)
303 _id_bits.insert(_id_bits.begin(), 32, false);
305 const ScreenInfo& screeninfo = _app.display().screenInfo(_screen);
307 // create the window
308 XSetWindowAttributes attrib;
309 unsigned long mask = CWBackPixmap | CWColormap |
310 CWOverrideRedirect | CWEventMask;
311 attrib.background_pixmap = None;
312 attrib.colormap = screeninfo.colormap();
313 attrib.override_redirect = False;
314 attrib.event_mask = ButtonPressMask | ButtonReleaseMask |
315 ButtonMotionMask | PointerMotionMask |
316 KeyPressMask | LeaveWindowMask | ExposureMask;
317 attrib.override_redirect = True;
319 _window =
320 XCreateWindow(_app.XDisplay(), screeninfo.rootWindow(),
321 _rect.x(), _rect.y(), _rect.width(), _rect.height(), 0,
322 screeninfo.depth(), InputOutput,
323 screeninfo.visual(), mask, &attrib);
324 _app.insertEventHandler(_window, this);
326 _timer.setTimeout(200);
330 bt::Menu::~Menu(void) {
331 hide();
332 clear();
334 bt::PixmapCache::release(_tpixmap);
335 bt::PixmapCache::release(_fpixmap);
336 bt::PixmapCache::release(_apixmap);
337 _tpixmap = _fpixmap = _apixmap = 0ul;
339 _app.removeEventHandler(_window);
340 XDestroyWindow(_app.XDisplay(), _window);
344 void bt::Menu::invalidateSize(void) {
345 if (isVisible()) {
346 updateSize();
347 updatePixmaps();
348 XClearArea(_app.XDisplay(), _window,
349 0, 0, _rect.width(), _rect.height(), True);
350 } else {
351 _size_dirty = true;
356 unsigned int bt::Menu::insertItem(const MenuItem &item,
357 unsigned int id, unsigned int index) {
358 ItemList::iterator it;
359 if (index == ~0u) {
360 // append the new item
361 index = _items.size();
362 it = _items.end();
363 } else {
364 index = std::min(static_cast<size_t>(index), _items.size());
365 it = _items.begin();
366 std::advance<ItemList::iterator, signed>(it, index);
369 it = _items.insert(it, item);
370 if (!item.separator) {
371 id = verifyId(id);
372 it->ident = id;
374 it->indx = index;
376 // when inserting into the middle, update the index of all items
377 // after the insertion point
378 for (++it, ++index; it != _items.end(); ++it, ++index)
379 it->indx = index;
381 invalidateSize();
383 return id;
387 void bt::Menu::positionRect(Rect& r, int &row, int &col) {
388 r.setY(r.y() + r.height());
389 ++row;
391 if (r.y() >= _irect.bottom()) {
392 // next column
393 ++col;
394 row = 0;
395 r.setPos(_irect.x() + (_itemw * col), _irect.y());
400 // method assumes id exists and the items list has at least one item in it
401 bt::Menu::ItemList::iterator bt::Menu::findItem(unsigned int id, Rect& r) {
402 int row = 0, col = 0;
403 ItemList::iterator it, end;
404 for (it = _items.begin(), end = _items.end(); it != end; ++it) {
405 r.setHeight(it->height);
407 if (it->id() == id)
408 break;
411 if (it == end)
412 return end;
414 positionRect(r, row, col);
415 return it;
419 void bt::Menu::changeItem(unsigned int id, const ustring &newlabel,
420 unsigned int newid) {
421 Rect r(_irect.x(), _irect.y(), _itemw, 0);
422 ItemList::iterator it = findItem(id, r);
423 if (it == _items.end())
424 return;
426 bt::MenuItem& item = *it;
427 if (!item.isSeparator()) {
428 if (item.lbl != newlabel) {
429 // new label
430 item.lbl = newlabel;
431 invalidateSize();
434 if (newid != ~0u) {
435 // change the id if necessary
436 _id_bits[item.ident] = false;
437 item.ident = verifyId(newid);
443 void bt::Menu::setItemEnabled(unsigned int id, bool enabled) {
444 Rect r(_irect.x(), _irect.y(), _itemw, 0);
445 ItemList::iterator it = findItem(id, r);
446 if (it == _items.end())
447 return;
449 bt::MenuItem& item = *it;
450 // found the item, change the status and redraw if visible
451 item.enabled = enabled;
452 if (isVisible()) {
453 XClearArea(_app.XDisplay(), _window,
454 r.x(), r.y(), r.width(), r.height(), True);
459 bool bt::Menu::isItemEnabled(unsigned int id) const {
460 const ItemList::const_iterator &end = _items.end();
461 const ItemList::const_iterator it =
462 std::find_if(_items.begin(), end, IdentMatch(id));
463 return (it != end && it->enabled);
467 void bt::Menu::setItemChecked(unsigned int id, bool checked) {
468 Rect r(_irect.x(), _irect.y(), _itemw, 0);
469 ItemList::iterator it = findItem(id, r);
470 if (it == _items.end())
471 return;
473 bt::MenuItem& item = *it;
474 // found the item, change the status and redraw if visible
475 item.checked = checked;
476 if (isVisible()) {
477 XClearArea(_app.XDisplay(), _window,
478 r.x(), r.y(), r.width(), r.height(), True);
483 bool bt::Menu::isItemChecked(unsigned int id) const {
484 const ItemList::const_iterator &end = _items.end();
485 const ItemList::const_iterator it =
486 std::find_if(_items.begin(), end, IdentMatch(id));
487 return (it != end && it->checked);
491 void bt::Menu::removeItemByIterator(ItemList::iterator& it) {
492 if (it->sub) {
493 if (it->sub->_auto_delete)
494 delete it->sub;
497 if (!it->separator)
498 _id_bits[it->ident] = false;
499 _items.erase(it);
501 invalidateSize();
505 void bt::Menu::removeItem(unsigned int id) {
506 const ItemList::iterator &end = _items.end();
507 ItemList::iterator it =
508 std::find_if(_items.begin(), end, IdentMatch(id));
509 if (it == end)
510 return; // item not found
511 removeItemByIterator(it);
515 void bt::Menu::removeIndex(unsigned int index) {
516 ItemList::iterator it = _items.begin();
517 std::advance<ItemList::iterator, signed>(it, index);
518 if (it == _items.end())
519 return; // item not found
520 removeItemByIterator(it);
524 void bt::Menu::clear(void) {
525 while (!_items.empty())
526 removeIndex(0);
527 invalidateSize();
531 void bt::Menu::showTitle(void) {
532 _show_title = true;
533 invalidateSize();
537 void bt::Menu::hideTitle(void) {
538 _show_title = false;
539 invalidateSize();
543 void bt::Menu::popup(int x, int y, bool centered)
544 { popup(x, y, _app.display().screenInfo(_screen).rect(), centered); }
547 void bt::Menu::popup(int x, int y, const Rect &constraint, bool centered) {
548 _motion = 0;
550 refresh();
552 if (_size_dirty)
553 updateSize();
555 bt::Rect u(x, y, _rect.width(), _rect.height());
557 if (_show_title) {
558 if (centered) {
559 u.setPos(u.x() - _trect.width() / 2, u.y() - _trect.height() / 2);
561 if (u.bottom() > constraint.bottom())
562 u.setY(u.y() - _rect.height() + (_trect.height() / 2));
563 } else {
564 u.setY(u.y() - _trect.height());
566 if (u.right() > constraint.right())
567 u.setX(u.x() - _rect.width());
568 if (u.bottom() > constraint.bottom())
569 u.setY(u.y() - _rect.height());
571 } else {
572 if (centered) {
573 u.setX(u.x() - _frect.width() / 2);
574 } else {
575 if (u.right() > constraint.right())
576 u.setX(u.x() - _rect.width());
577 if (u.bottom() > constraint.bottom())
578 u.setY(u.y() - _rect.height());
582 if (u.right() > constraint.right())
583 u.setX(constraint.right() - _rect.width() + 1);
584 if (u.x() < constraint.x())
585 u.setX(constraint.x());
587 if (u.bottom() > constraint.bottom())
588 u.setY(constraint.bottom() - _rect.height() + 1);
589 if (u.y() < constraint.y())
590 u.setY(constraint.y());
592 move(u.x(), u.y());
593 show();
597 void bt::Menu::move(int x, int y) {
598 XMoveWindow(_app.XDisplay(), _window, x, y);
599 _rect.setPos(x, y);
603 void bt::Menu::show(void) {
604 if (isVisible())
605 return;
607 if (_parent_menu && _parent_menu->isVisible())
608 _parent_menu->_current_submenu = this;
610 // explicitly shown, no need to do it later
611 if (menudelay.showmenu == this)
612 menudelay.showmenu = 0;
613 // don't hide this menu later if it is shown now
614 if (menudelay.hidemenu == this)
615 menudelay.hidemenu = 0;
617 updatePixmaps();
619 XMapRaised(_app.XDisplay(), _window);
620 XSync(_app.XDisplay(), False);
621 _app.openMenu(this);
622 _visible = true;
623 _pressed = _parent_menu ? _parent_menu->_pressed : false;
624 _title_pressed = false;
628 void bt::Menu::hide(void) {
629 if (!isVisible())
630 return;
632 if (_current_submenu && _current_submenu->isVisible())
633 _current_submenu->hide();
635 if (_parent_menu && _parent_menu->isVisible())
636 _parent_menu->_current_submenu = 0;
638 // don't show this menu later if it is hidden now
639 if (menudelay.showmenu == this)
640 menudelay.showmenu = 0;
641 // explicitly hidden, no need to do it later
642 if (menudelay.hidemenu == this)
643 menudelay.hidemenu = 0;
645 _active_index = ~0u;
646 _active_submenu = 0;
647 _parent_menu = 0;
649 const ItemList::iterator &end = _items.end();
650 ItemList::iterator it;
651 for (it = _items.begin(); it != end; ++it) {
652 if (it->active) {
653 it->active = false;
654 break;
658 _app.closeMenu(this);
659 XUnmapWindow(_app.XDisplay(), _window);
660 _visible = false;
661 _pressed = false;
663 // release the pixmaps for this menu... menus are not visible 100% of the
664 // time, so they should not use pixmap memory 100% of the time
665 bt::PixmapCache::release(_tpixmap);
666 bt::PixmapCache::release(_fpixmap);
667 bt::PixmapCache::release(_apixmap);
668 _tpixmap = _fpixmap = _apixmap = 0ul;
672 void bt::Menu::refresh(void)
676 void bt::Menu::reconfigure(void) {
677 const ItemList::iterator &end = _items.end();
678 ItemList::iterator it;
679 for (it = _items.begin(); it != end; ++it) {
680 if (it->sub)
681 it->sub->reconfigure();
684 invalidateSize();
688 void bt::Menu::updateSize(void) {
689 MenuStyle* style = MenuStyle::get(_app, _screen);
691 if (_show_title) {
692 _trect = style->titleRect(_title);
693 _frect.setPos(0, _trect.height() - style->titleTexture().borderWidth());
694 } else {
695 _trect.setSize(0, 0);
696 _frect.setPos(0, 0);
699 const ScreenInfo& screeninfo = _app.display().screenInfo(_screen);
700 unsigned int col_h = 0u, max_col_h = 0u;
701 unsigned int row = 0u, cols = 1u;
702 _itemw = std::max(20u, _trect.width());
703 const ItemList::iterator &end = _items.end();
704 ItemList::iterator it;
705 for (it= _items.begin(); it != end; ++it) {
706 if (it->isSeparator()) {
707 _itemw = std::max(_itemw, 20u);
708 it->height = style->separatorHeight();
709 col_h += it->height;
710 } else {
711 const Rect &rect = style->itemRect(*it);
712 _itemw = std::max(_itemw, rect.width());
713 it->height = rect.height();
714 col_h += it->height;
717 ++row;
719 if (col_h > (screeninfo.height() * 3u / 4u)) {
720 ++cols;
721 row = 0;
723 max_col_h = std::max(max_col_h, col_h);
724 col_h = 0;
728 // if we just changed to a new column, but have no items, then
729 // remove the empty column
730 if (cols > 1u && col_h == 0u && row == 0u)
731 --cols;
733 max_col_h = std::max(std::max(max_col_h, col_h), style->frameMargin());
735 // update rects
736 _irect.setRect(style->frameMargin(), _frect.top() + style->frameMargin(),
737 std::max(_trect.width(), cols * _itemw), max_col_h);
738 _frect.setSize(_irect.width() + (style->frameMargin() * 2u),
739 _irect.height() + (style->frameMargin() * 2u));
740 _rect.setSize(_frect.width(), _frect.height());
741 if (_show_title) {
742 _trect.setWidth(std::max(_trect.width(), _frect.width()));
743 _rect.setHeight(_rect.height() + _trect.height() -
744 style->titleTexture().borderWidth());
747 XResizeWindow(_app.XDisplay(), _window, _rect.width(), _rect.height());
748 _size_dirty = false;
752 void bt::Menu::updatePixmaps(void) {
753 MenuStyle* style = MenuStyle::get(_app, _screen);
755 // update pixmaps
756 if (_show_title) {
757 _tpixmap = PixmapCache::find(_screen, style->titleTexture(),
758 _trect.width(), _trect.height(), _tpixmap);
760 _fpixmap = PixmapCache::find(_screen, style->frameTexture(),
761 _frect.width(), _frect.height(), _fpixmap);
763 _apixmap =
764 PixmapCache::find(_screen, style->activeTexture(), _itemw,
765 textHeight(_screen, style->frameFont()) +
766 (style->itemMargin() * 2), _apixmap);
770 void bt::Menu::buttonPressEvent(const XButtonEvent * const event) {
771 if (!_rect.contains(event->x_root, event->y_root)) {
772 hideAll();
773 return;
776 _pressed = true;
778 if (_trect.contains(event->x, event->y)) {
779 _title_pressed = true;
780 return;
781 } else if (!_irect.contains(event->x, event->y)) {
782 return;
785 Rect r(_irect.x(), _irect.y(), _itemw, 0);
786 int row = 0, col = 0;
787 unsigned int index = 0;
788 const ItemList::iterator &end = _items.end();
789 ItemList::iterator it;
790 for (it = _items.begin(); it != end; ++it, ++index) {
791 r.setHeight(it->height);
793 if (!it->separator) {
794 if (it->enabled && r.contains(event->x, event->y)) {
795 if (!it->active)
796 activateItem(r, *it);
797 // ensure the submenu is visible
798 showActiveSubmenu();
802 positionRect(r, row, col);
807 void bt::Menu::buttonReleaseEvent(const XButtonEvent * const event) {
808 if (!_pressed && _motion < 10)
809 return;
811 _pressed = false;
813 if (!_rect.contains(event->x_root, event->y_root)) {
814 hideAll();
815 return;
818 if (_title_pressed) {
819 if (_trect.contains(event->x, event->y))
820 titleClicked(event->button);
821 _title_pressed = false;
822 return;
825 bool do_hide = true;
826 Rect r(_irect.x(), _irect.y(), _itemw, 0);
827 int row = 0, col = 0;
828 unsigned int index = 0;
829 const ItemList::iterator &end = _items.end();
830 ItemList::iterator it;
831 for (it = _items.begin(); it != end; ++index) {
833 increment the iterator here, since the item could be removed
834 below (which invalidates the iterator, and we will get a crash
835 when we loop)
837 MenuItem &item = *it++;
838 r.setHeight(item.height);
840 if (item.enabled && r.contains(event->x, event->y)) {
841 if (item.separator) {
842 do_hide = false;
843 } else {
844 if (item.sub) {
845 // clicked an item w/ a submenu, make sure the submenu is shown,
846 // and keep the menu open
847 do_hide = false;
848 if (!item.active)
849 activateItem(r, item);
850 // ensure the submenu is visible
851 showActiveSubmenu();
854 // clicked an enabled item
855 itemClicked(item.ident, event->button);
857 break;
860 positionRect(r, row, col);
863 if (do_hide)
864 hideAll();
868 void bt::Menu::motionNotifyEvent(const XMotionEvent * const event) {
869 ++_motion;
871 if (_trect.contains(event->x, event->y)) {
872 // mouse moved over title, fake a leave event
873 leaveNotifyEvent(0);
874 return;
877 if (!_irect.contains(event->x, event->y)) {
878 // mouse outside the menu
879 return;
882 Rect r(_irect.x(), _irect.y(), _itemw, 0);
883 int row = 0, col = 0;
884 unsigned int index = 0;
885 const unsigned int old_active = _active_index;
886 const ItemList::iterator &end = _items.end();
887 ItemList::iterator it;
888 for (it = _items.begin(); it != end; ++it, ++index) {
889 r.setHeight(it->height);
891 if (!it->separator) {
892 if (r.contains(event->x, event->y)) {
893 if (!it->active && it->enabled)
894 activateItem(r, *it);
895 } else if (it->active) {
896 deactivateItem(r, *it, false);
900 positionRect(r, row, col);
903 if (_active_index != old_active)
904 _timer.start();
908 void bt::Menu::leaveNotifyEvent(const XCrossingEvent * const /*unused*/) {
909 Rect r(_irect.x(), _irect.y(), _itemw, 0);
910 int row = 0, col = 0;
911 const ItemList::iterator &end = _items.end();
912 ItemList::iterator it;
913 for (it = _items.begin(); it != end; ++it) {
914 r.setHeight(it->height);
916 // deactivate the item unless its menu is visible
917 if (!it->separator) {
918 if (it->active && !(_current_submenu && _current_submenu == it->sub))
919 deactivateItem(r, *it);
922 positionRect(r, row, col);
925 if (_timer.isTiming() && menudelay.hidemenu) {
927 if the user has moved the mouse outside of the menu before the
928 timer has fired (which hides the old menu and shows the new), we
929 stop the timer and mark the "old" menu as active.
931 this results in a really smart menu that allows you to move the
932 mouse into submenus easily (even if you happen to move the mouse
933 over other items in the menu)
935 _current_submenu = menudelay.hidemenu;
936 menudelay.showmenu = 0;
937 menudelay.hidemenu = 0;
938 _timer.stop();
940 r.setRect(_irect.x(), _irect.y(), _itemw, 0);
941 row = col = 0;
942 for (it = _items.begin(); it != end; ++it) {
943 r.setHeight(it->height);
945 if (!it->separator) {
946 if (it->active && (!_current_submenu || it->sub != _current_submenu ||
947 !_current_submenu->isVisible()))
948 deactivateItem(r, *it);
949 else if (it->sub == _current_submenu)
950 activateItem(r, *it);
953 positionRect(r, row, col);
959 void bt::Menu::exposeEvent(const XExposeEvent * const event) {
960 MenuStyle* style = MenuStyle::get(_app, _screen);
961 Rect r(event->x, event->y, event->width, event->height);
963 if (_show_title && r.intersects(_trect)) {
964 drawTexture(_screen, style->titleTexture(), _window,
965 _trect, r & _trect, _tpixmap);
966 style->drawTitle(_window, _trect, _title);
969 if (r.intersects(_frect)) {
970 drawTexture(_screen, style->frameTexture(), _window,
971 _frect, r & _frect, _fpixmap);
974 if (!r.intersects(_irect))
975 return;
977 Rect u = r & _irect;
978 // only draw items that intersect with the needed update rect
979 r.setRect(_irect.x(), _irect.y(), _itemw, 0);
980 int row = 0, col = 0;
981 const ItemList::const_iterator &end = _items.end();
982 ItemList::const_iterator it;
983 for (it = _items.begin(); it != end; ++it) {
984 // note: we are reusing r from above, which is no longer needed now
985 r.setHeight(it->height);
987 if (r.intersects(u))
988 style->drawItem(_window, r, *it, _apixmap);
990 positionRect(r, row, col);
995 void bt::Menu::activateSubmenu(void) {
996 if (!_active_submenu)
997 return;
999 showActiveSubmenu();
1000 assert(_current_submenu != 0);
1002 // activate the first item in the menu when shown with the keyboard
1003 const ItemList::const_iterator &end = _current_submenu->_items.end();
1004 ItemList::const_iterator it = _current_submenu->_items.begin();
1005 it = std::find_if(it, end, InteractMatch());
1006 if (it != end && _current_submenu->count() > 0)
1007 _current_submenu->activateIndex(it->indx);
1011 void bt::Menu::keyPressEvent(const XKeyEvent * const event) {
1012 KeySym sym = XKeycodeToKeysym(_app.XDisplay(), event->keycode, 0);
1013 switch (sym) {
1014 case XK_Escape: {
1015 hide();
1016 return;
1019 case XK_Left: {
1020 // hide() clears _parent_menu, but we want to remember it incase
1021 // the user tries to reopen us
1022 Menu * const p = _parent_menu;
1023 if (p && p->isVisible())
1024 hide();
1025 _parent_menu = p;
1026 return;
1028 } // switch
1030 if (_items.empty())
1031 return;
1033 switch (sym) {
1034 case XK_Down: {
1035 const ItemList::const_iterator &end = _items.end();
1036 ItemList::const_iterator anchor = _items.begin();
1037 if (_active_index != ~0u) {
1038 std::advance<ItemList::const_iterator, signed>(anchor, _active_index);
1040 // go one paste the current active index
1041 if (anchor != end && !anchor->separator)
1042 ++anchor;
1045 if (anchor == end)
1046 anchor = _items.begin();
1048 ItemList::const_iterator it = std::find_if(anchor, end, InteractMatch());
1049 if (it != end)
1050 activateIndex(it->indx);
1051 break;
1054 case XK_Up: {
1055 ItemList::const_reverse_iterator anchor = _items.rbegin();
1056 const ItemList::const_reverse_iterator &end = _items.rend();
1057 if (_active_index != ~0u) {
1058 std::advance<ItemList::const_reverse_iterator, signed>
1059 (anchor, _items.size() - _active_index - 1);
1061 // go one item past the current active index
1062 if (anchor != end && !anchor->separator)
1063 ++anchor;
1066 if (anchor == end) anchor = _items.rbegin();
1068 ItemList::const_reverse_iterator it =
1069 std::find_if(anchor, end, InteractMatch());
1070 if (it != end)
1071 activateIndex(it->indx);
1072 break;
1075 case XK_Home: {
1076 const ItemList::const_iterator &end = _items.end();
1077 ItemList::const_iterator it = _items.begin();
1078 it = std::find_if(it, end, InteractMatch());
1079 if (it != end)
1080 activateIndex(it->indx);
1081 break;
1084 case XK_End: {
1085 const ItemList::const_reverse_iterator &end = _items.rend();
1086 ItemList::const_reverse_iterator it = _items.rbegin();
1087 it = std::find_if(it, end, InteractMatch());
1088 if (it != end)
1089 activateIndex(it->indx);
1090 break;
1093 case XK_Right: {
1094 activateSubmenu();
1095 break;
1098 case XK_Return:
1099 case XK_space: {
1100 if (_active_index == ~0u)
1101 break;
1103 const ItemList::const_iterator &end = _items.end();
1104 ItemList::const_iterator it = _items.begin();
1105 it = std::find_if(it, end, IndexMatch(_active_index));
1106 if (it == end)
1107 break;
1109 if (!it->separator) {
1111 the item could be removed in the itemClicked call, so don't use
1112 the iterator after calling itemClicked
1114 if (it->sub) {
1115 activateSubmenu();
1116 } else {
1117 itemClicked(it->ident, 1);
1118 hideAll();
1121 break;
1123 } // switch
1127 void bt::Menu::titleClicked(unsigned int button) {
1128 switch (button) {
1129 case 1:
1130 // tearOff();
1131 break;
1132 case 3:
1133 hideAll();
1134 break;
1139 void bt::Menu::itemClicked(unsigned int /*id*/, unsigned int /*button*/)
1143 void bt::Menu::hideAll(void) {
1144 if (_parent_menu && _parent_menu->isVisible())
1145 _parent_menu->hideAll();
1146 else
1147 hide();
1151 void bt::Menu::tearOff(void) {
1152 assert(!"Sorry, tear-off menus aren't implemented yet");
1156 bt::Rect bt::Menu::geometry(void) const {
1157 if (_size_dirty)
1158 const_cast<Menu *>(this)->updateSize();
1159 return _rect;
1163 unsigned int bt::Menu::verifyId(unsigned int id) {
1164 if (id != ~0u) {
1165 // request a specific id
1167 // expand if necessary
1168 while (id >= _id_bits.size())
1169 _id_bits.insert(_id_bits.end(), _id_bits.size(), false);
1171 if (!_id_bits[id]) {
1172 _id_bits[id] = true;
1173 return id;
1176 fprintf(stderr, "Error: bt::Menu::verifyId: id %u already used\n", id);
1177 abort();
1180 std::vector<bool>::iterator it =
1181 std::find(_id_bits.begin(), _id_bits.end(), false);
1182 if (it == _id_bits.end()) {
1183 // no free bits... expand
1184 _id_bits.insert(_id_bits.end(), _id_bits.size(), false);
1185 it = std::find(_id_bits.begin(), _id_bits.end(), false);
1186 assert(it != _id_bits.end());
1189 *it = true;
1190 return static_cast<int>(it - _id_bits.begin());
1194 void bt::Menu::activateItem(const Rect &rect, MenuItem &item) {
1195 // mark new active item
1196 _active_index = item.indx;
1197 _active_submenu = item.sub;
1198 if (_active_submenu)
1199 _active_submenu->_parent_menu = this;
1201 item.active = item.enabled;
1202 XClearArea(_app.XDisplay(), _window,
1203 rect.x(), rect.y(), rect.width(), rect.height(), True);
1205 menudelay.showmenu = item.sub;
1206 if (menudelay.hidemenu == item.sub)
1207 menudelay.hidemenu = 0;
1208 if (!item.sub || item.sub->isVisible())
1209 return;
1211 item.sub->refresh();
1213 if (item.sub->_size_dirty)
1214 item.sub->updateSize();
1216 MenuStyle *style = MenuStyle::get(_app, _screen);
1217 const ScreenInfo& screeninfo = _app.display().screenInfo(_screen);
1218 int px = _rect.x() + rect.x() + rect.width();
1219 int py = _rect.y() + rect.y() - style->frameMargin();
1220 bool left = false;
1222 if (_parent_menu && _parent_menu->isVisible() &&
1223 _parent_menu->_rect.x() > _rect.x())
1224 left = true;
1225 if (px + item.sub->_rect.width() > screeninfo.width() || left)
1226 px -= item.sub->_rect.width() + rect.width();
1227 if (px < 0) {
1228 if (left) // damn, lots of menus - move back to the right
1229 px = _rect.x() + rect.x() + rect.width();
1230 else
1231 px = 0;
1234 if (item.sub->_show_title)
1235 py -= item.sub->_trect.height() -
1236 style->titleTexture().borderWidth();
1237 if (py + item.sub->_rect.height() > screeninfo.height())
1238 py -= item.sub->_irect.height() - rect.height();
1239 if (py < 0)
1240 py = 0;
1242 item.sub->move(px, py);
1246 void bt::Menu::deactivateItem(const Rect &rect, MenuItem &item,
1247 bool hide_submenu) {
1248 // clear old active item
1249 if (_active_index == item.indx) {
1250 _active_index = ~0u;
1251 _active_submenu = 0;
1254 item.active = false;
1255 XClearArea(_app.XDisplay(), _window,
1256 rect.x(), rect.y(), rect.width(), rect.height(), True);
1258 if (item.sub) {
1259 if (menudelay.showmenu == item.sub)
1260 menudelay.showmenu = 0;
1261 if (item.sub->isVisible()) {
1262 if (hide_submenu)
1263 item.sub->hide();
1264 else
1265 menudelay.hidemenu = item.sub;
1271 void bt::Menu::activateIndex(unsigned int index) {
1272 assert(index < _items.size());
1274 Rect r(_irect.x(), _irect.y(), _itemw, 0);
1275 int row = 0, col = 0;
1276 const ItemList::iterator &end = _items.end();
1277 ItemList::iterator it;
1278 for (it = _items.begin(); it != end; ++it) {
1279 r.setHeight(it->height);
1281 if (!it->separator) {
1282 if (it->indx == index) {
1283 if (!it->active && it->enabled)
1284 activateItem(r, *it);
1285 } else if (it->active) {
1286 deactivateItem(r, *it);
1290 positionRect(r, row, col);
1295 void bt::Menu::showActiveSubmenu(void) {
1296 if (!_active_submenu)
1297 return;
1299 if (menudelay.hidemenu)
1300 menudelay.hidemenu->hide();
1301 menudelay.hidemenu = 0;
1303 // active submenu, keep the menu item marked active and ensure
1304 // that the menu is visible
1305 if (!_active_submenu->isVisible())
1306 _active_submenu->show();
1307 menudelay.showmenu = 0;
1308 _timer.stop();