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>
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.
26 #include "Application.hh"
30 #include "PixmapCache.hh"
31 #include "Resource.hh"
34 #include <X11/keysym.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();
47 styles
= new MenuStyle
*[screen_count
];
48 for (unsigned int i
= 0; i
< screen_count
; ++i
)
51 // we need to remap screen N to 0 if there is only one
52 const size_t index
= (screen_count
== 1) ? 0 : screen
;
54 styles
[index
] = new MenuStyle(app
, screen
);
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
) {
70 textureResource(_app
.display(), _screen
, resource
,
71 "menu.title", "Menu.Title", "black");
73 textureResource(_app
.display(), _screen
, resource
,
74 "menu.frame", "Menu.Frame", "white");
76 textureResource(_app
.display(), _screen
, resource
,
77 "menu.active", "Menu.Active", "black");
81 Color::namedColor(_app
.display(), _screen
,
82 resource
.read("menu.title.foregroundColor",
83 "Menu.Title.ForegroundColor",
86 Color::namedColor(_app
.display(), _screen
,
87 resource
.read("menu.title.textColor",
88 "Menu.Title.TextColor",
91 Color::namedColor(_app
.display(), _screen
,
92 resource
.read("menu.frame.foregroundColor",
93 "Menu.Frame.ForegroundColor",
96 Color::namedColor(_app
.display(), _screen
,
97 resource
.read("menu.frame.textColor",
98 "Menu.Frame.TextColor",
101 Color::namedColor(_app
.display(), _screen
,
102 resource
.read("menu.frame.disabledColor",
103 "Menu.Frame.DisabledColor",
106 Color::namedColor(_app
.display(), _screen
,
107 resource
.read("menu.active.foregroundColor",
108 "Menu.Active.ForegroundColor",
111 Color::namedColor(_app
.display(), _screen
,
112 resource
.read("menu.active.textColor",
113 "Menu.Active.TextColor",
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
));
127 alignResource(resource
, "menu.title.alignment",
128 "Menu.Title.Alignment");
131 alignResource(resource
, "menu.frame.alignment",
132 "Menu.Frame.Alignment");
136 str
= resource
.read("menu.title.marginWidth", "Menu.Title.MarginWidth", "1");
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");
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
);
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());
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
);
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 {
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));
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()));
230 // internal helper classes
232 class MenuDelay
: public TimeoutHandler
{
236 inline MenuDelay(void)
237 : showmenu(0), hidemenu(0)
239 inline void timeout(Timer
*) {
250 inline IdentMatch(unsigned int id
)
253 inline bool operator()(const MenuItem
&item
) const
254 { return item
.id() == _id
; }
260 inline IndexMatch(unsigned int index
)
263 inline bool operator()(const MenuItem
&item
) const
264 { return item
.index() == _index
; }
267 class InteractMatch
{
269 inline bool operator()(const MenuItem
&item
) const
270 { return item
.isEnabled() && !item
.isSeparator(); }
275 static bt::MenuDelay menudelay
;
278 bt::Menu::Menu(Application
&app
, unsigned int screen
)
288 _timer(&_app
, &menudelay
),
297 _title_pressed(false),
303 _id_bits
.insert(_id_bits
.begin(), 32, false);
305 const ScreenInfo
& screeninfo
= _app
.display().screenInfo(_screen
);
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
;
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) {
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) {
348 XClearArea(_app
.XDisplay(), _window
,
349 0, 0, _rect
.width(), _rect
.height(), True
);
356 unsigned int bt::Menu::insertItem(const MenuItem
&item
,
357 unsigned int id
, unsigned int index
) {
358 ItemList::iterator it
;
360 // append the new item
361 index
= _items
.size();
364 index
= std::min(static_cast<size_t>(index
), _items
.size());
366 std::advance
<ItemList::iterator
, signed>(it
, index
);
369 it
= _items
.insert(it
, item
);
370 if (!item
.separator
) {
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
)
387 void bt::Menu::positionRect(Rect
& r
, int &row
, int &col
) {
388 r
.setY(r
.y() + r
.height());
391 if (r
.y() >= _irect
.bottom()) {
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
);
414 positionRect(r
, row
, col
);
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())
426 bt::MenuItem
& item
= *it
;
427 if (!item
.isSeparator()) {
428 if (item
.lbl
!= newlabel
) {
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())
449 bt::MenuItem
& item
= *it
;
450 // found the item, change the status and redraw if visible
451 item
.enabled
= enabled
;
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())
473 bt::MenuItem
& item
= *it
;
474 // found the item, change the status and redraw if visible
475 item
.checked
= checked
;
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
) {
493 if (it
->sub
->_auto_delete
)
498 _id_bits
[it
->ident
] = false;
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
));
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())
531 void bt::Menu::showTitle(void) {
537 void bt::Menu::hideTitle(void) {
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
) {
555 bt::Rect
u(x
, y
, _rect
.width(), _rect
.height());
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));
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());
573 u
.setX(u
.x() - _frect
.width() / 2);
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());
597 void bt::Menu::move(int x
, int y
) {
598 XMoveWindow(_app
.XDisplay(), _window
, x
, y
);
603 void bt::Menu::show(void) {
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;
619 XMapRaised(_app
.XDisplay(), _window
);
620 XSync(_app
.XDisplay(), False
);
623 _pressed
= _parent_menu
? _parent_menu
->_pressed
: false;
624 _title_pressed
= false;
628 void bt::Menu::hide(void) {
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;
649 const ItemList::iterator
&end
= _items
.end();
650 ItemList::iterator it
;
651 for (it
= _items
.begin(); it
!= end
; ++it
) {
658 _app
.closeMenu(this);
659 XUnmapWindow(_app
.XDisplay(), _window
);
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
) {
681 it
->sub
->reconfigure();
688 void bt::Menu::updateSize(void) {
689 MenuStyle
* style
= MenuStyle::get(_app
, _screen
);
692 _trect
= style
->titleRect(_title
);
693 _frect
.setPos(0, _trect
.height() - style
->titleTexture().borderWidth());
695 _trect
.setSize(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();
711 const Rect
&rect
= style
->itemRect(*it
);
712 _itemw
= std::max(_itemw
, rect
.width());
713 it
->height
= rect
.height();
719 if (col_h
> (screeninfo
.height() * 3u / 4u)) {
723 max_col_h
= std::max(max_col_h
, col_h
);
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)
733 max_col_h
= std::max(std::max(max_col_h
, col_h
), style
->frameMargin());
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());
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());
752 void bt::Menu::updatePixmaps(void) {
753 MenuStyle
* style
= MenuStyle::get(_app
, _screen
);
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
);
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
)) {
778 if (_trect
.contains(event
->x
, event
->y
)) {
779 _title_pressed
= true;
781 } else if (!_irect
.contains(event
->x
, event
->y
)) {
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
)) {
796 activateItem(r
, *it
);
797 // ensure the submenu is visible
802 positionRect(r
, row
, col
);
807 void bt::Menu::buttonReleaseEvent(const XButtonEvent
* const event
) {
808 if (!_pressed
&& _motion
< 10)
813 if (!_rect
.contains(event
->x_root
, event
->y_root
)) {
818 if (_title_pressed
) {
819 if (_trect
.contains(event
->x
, event
->y
))
820 titleClicked(event
->button
);
821 _title_pressed
= false;
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
837 MenuItem
&item
= *it
++;
838 r
.setHeight(item
.height
);
840 if (item
.enabled
&& r
.contains(event
->x
, event
->y
)) {
841 if (item
.separator
) {
845 // clicked an item w/ a submenu, make sure the submenu is shown,
846 // and keep the menu open
849 activateItem(r
, item
);
850 // ensure the submenu is visible
854 // clicked an enabled item
855 itemClicked(item
.ident
, event
->button
);
860 positionRect(r
, row
, col
);
868 void bt::Menu::motionNotifyEvent(const XMotionEvent
* const event
) {
871 if (_trect
.contains(event
->x
, event
->y
)) {
872 // mouse moved over title, fake a leave event
877 if (!_irect
.contains(event
->x
, event
->y
)) {
878 // mouse outside the menu
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
)
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;
940 r
.setRect(_irect
.x(), _irect
.y(), _itemw
, 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
))
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
);
988 style
->drawItem(_window
, r
, *it
, _apixmap
);
990 positionRect(r
, row
, col
);
995 void bt::Menu::activateSubmenu(void) {
996 if (!_active_submenu
)
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);
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())
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
)
1046 anchor
= _items
.begin();
1048 ItemList::const_iterator it
= std::find_if(anchor
, end
, InteractMatch());
1050 activateIndex(it
->indx
);
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
)
1066 if (anchor
== end
) anchor
= _items
.rbegin();
1068 ItemList::const_reverse_iterator it
=
1069 std::find_if(anchor
, end
, InteractMatch());
1071 activateIndex(it
->indx
);
1076 const ItemList::const_iterator
&end
= _items
.end();
1077 ItemList::const_iterator it
= _items
.begin();
1078 it
= std::find_if(it
, end
, InteractMatch());
1080 activateIndex(it
->indx
);
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());
1089 activateIndex(it
->indx
);
1100 if (_active_index
== ~0u)
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
));
1109 if (!it
->separator
) {
1111 the item could be removed in the itemClicked call, so don't use
1112 the iterator after calling itemClicked
1117 itemClicked(it
->ident
, 1);
1127 void bt::Menu::titleClicked(unsigned int button
) {
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();
1151 void bt::Menu::tearOff(void) {
1152 assert(!"Sorry, tear-off menus aren't implemented yet");
1156 bt::Rect
bt::Menu::geometry(void) const {
1158 const_cast<Menu
*>(this)->updateSize();
1163 unsigned int bt::Menu::verifyId(unsigned int id
) {
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;
1176 fprintf(stderr
, "Error: bt::Menu::verifyId: id %u already used\n", id
);
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());
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())
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();
1222 if (_parent_menu
&& _parent_menu
->isVisible() &&
1223 _parent_menu
->_rect
.x() > _rect
.x())
1225 if (px
+ item
.sub
->_rect
.width() > screeninfo
.width() || left
)
1226 px
-= item
.sub
->_rect
.width() + rect
.width();
1228 if (left
) // damn, lots of menus - move back to the right
1229 px
= _rect
.x() + rect
.x() + rect
.width();
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();
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
);
1259 if (menudelay
.showmenu
== item
.sub
)
1260 menudelay
.showmenu
= 0;
1261 if (item
.sub
->isVisible()) {
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
)
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;