1 /***********************************************************************
2     created:    2/4/2005
3     author:     Tomas Lindquist Olsen (based on code by Paul D Turner)
4 
5     purpose:    Implementation of MenuItem widget base class
6 *************************************************************************/
7 /***************************************************************************
8  *   Copyright (C) 2004 - 2006 Paul D Turner & The CEGUI Development Team
9  *
10  *   Permission is hereby granted, free of charge, to any person obtaining
11  *   a copy of this software and associated documentation files (the
12  *   "Software"), to deal in the Software without restriction, including
13  *   without limitation the rights to use, copy, modify, merge, publish,
14  *   distribute, sublicense, and/or sell copies of the Software, and to
15  *   permit persons to whom the Software is furnished to do so, subject to
16  *   the following conditions:
17  *
18  *   The above copyright notice and this permission notice shall be
19  *   included in all copies or substantial portions of the Software.
20  *
21  *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22  *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23  *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24  *   IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
25  *   OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
26  *   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27  *   OTHER DEALINGS IN THE SOFTWARE.
28  ***************************************************************************/
29 #include "CEGUI/widgets/MenuItem.h"
30 #include "CEGUI/widgets/Menubar.h"
31 #include "CEGUI/widgets/PopupMenu.h"
32 
33 #include "CEGUI/Logger.h"
34 #include "CEGUI/WindowManager.h"
35 
36 // Start of CEGUI namespace section
37 namespace CEGUI
38 {
39 
40 /*************************************************************************
41     Constants
42 *************************************************************************/
43 // event strings
44 const String MenuItem::WidgetTypeName("CEGUI/MenuItem");
45 const String MenuItem::EventNamespace("MenuItem");
46 const String MenuItem::EventClicked("Clicked");
47 
48 /*************************************************************************
49     Constructor for MenuItem base class.
50 *************************************************************************/
MenuItem(const String & type,const String & name)51 MenuItem::MenuItem(const String& type, const String& name)
52     : ItemEntry(type, name),
53       d_pushed(false),
54       d_hovering(false),
55       d_opened(false),
56       d_popupClosing(false),
57       d_popupOpening(false),
58       d_autoPopupTimeout(0.0f),
59       d_autoPopupTimeElapsed(0.0f),
60       d_popup(0)
61 {
62     // menuitems dont want multi-click events
63     setWantsMultiClickEvents(false);
64     // add the new properties
65     addMenuItemProperties();
66     d_popupOffset.d_x = cegui_absdim(0);
67     d_popupOffset.d_y = cegui_absdim(0);
68 }
69 
70 
71 /*************************************************************************
72     Destructor for MenuItem base class.
73 *************************************************************************/
~MenuItem(void)74 MenuItem::~MenuItem(void)
75 {
76 }
77 
78 
79 /*************************************************************************
80     Update the internal state of the Widget
81 *************************************************************************/
updateInternalState(const Vector2f & mouse_pos)82 void MenuItem::updateInternalState(const Vector2f& mouse_pos)
83 {
84     bool oldstate = d_hovering;
85 
86     // assume not hovering
87     d_hovering = false;
88 
89     // if input is captured, but not by 'this', then we never hover highlight
90     const Window* capture_wnd = getCaptureWindow();
91 
92     if (capture_wnd == 0)
93         d_hovering = (getGUIContext().getWindowContainingMouse() == this && isHit(mouse_pos));
94     else
95         d_hovering = (capture_wnd == this && isHit(mouse_pos));
96 
97     // if state has changed, trigger a re-draw
98     // and possible make the parent menu open another popup
99     if (oldstate != d_hovering)
100     {
101         // are we attached to a menu ?
102         MenuBase* menu = dynamic_cast<MenuBase*>(d_ownerList);
103         if (menu)
104         {
105             if (d_hovering)
106             {
107                 // does this menubar only allow one popup open? and is there a popup open?
108                 const MenuItem* curpopup = menu->getPopupMenuItem();
109 
110                 if (!menu->isMultiplePopupsAllowed())
111                 {
112                     if (curpopup != this && curpopup != 0)
113                     {
114                         if (!hasAutoPopup())
115                         {
116                             // open this popup instead
117                             openPopupMenu();
118                         }
119                         else
120                         {
121                             // start close timer on current popup
122                             menu->setPopupMenuItemClosing();
123                             startPopupOpening();
124                         }
125                     }
126                     else
127                     {
128                         startPopupOpening();
129                     }
130                 }
131             }
132         }
133 
134         invalidate();
135     }
136 }
137 
138 
139 /*************************************************************************
140     Set the popup menu for this item.
141 *************************************************************************/
setPopupMenu(PopupMenu * popup)142 void MenuItem::setPopupMenu(PopupMenu* popup)
143 {
144     setPopupMenu_impl(popup);
145 }
146 
147 
148 /*************************************************************************
149     Set the popup menu for this item.
150 *************************************************************************/
setPopupMenu_impl(PopupMenu * popup,bool add_as_child)151 void MenuItem::setPopupMenu_impl(PopupMenu* popup, bool add_as_child)
152 {
153     // is it the one we have already ?
154     if (popup == d_popup)
155     {
156         // then do nothing;
157         return;
158     }
159 
160     // keep the old one around
161     PopupMenu* old_popup = d_popup;
162     // update the internal state pointer
163     d_popup = popup;
164     d_opened = false;
165 
166     // is there already a popup ?
167     if (old_popup)
168     {
169         removeChild(old_popup);
170 
171         // should we destroy it as well?
172         if (old_popup->isDestroyedByParent())
173         {
174             // then do so
175             WindowManager::getSingletonPtr()->destroyWindow(old_popup);
176         }
177     }
178 
179     // we are setting a new popup and not just clearing. and we are told to add the child
180     if (popup != 0 && add_as_child)
181     {
182         addChild(popup);
183     }
184 
185     invalidate();
186 }
187 
188 /*************************************************************************
189     Open the PopupMenu attached to this item.
190 *************************************************************************/
openPopupMenu(bool notify)191 void MenuItem::openPopupMenu(bool notify)
192 {
193     // no popup? or already open...
194     if (d_popup == 0 || d_opened)
195         return;
196 
197     d_popupOpening = false;
198     d_popupClosing = false;
199 
200     // should we notify ?
201     // if so, and we are attached to a menu bar or popup menu, we let it handle the "activation"
202     Window* p = d_ownerList;
203 
204     if (notify && p)
205     {
206         if (dynamic_cast<Menubar*>(p))
207         {
208             // align the popup to the bottom-left of the menuitem
209             UVector2 pos(cegui_absdim(0), cegui_absdim(d_pixelSize.d_height));
210             d_popup->setPosition(pos + d_popupOffset);
211 
212             static_cast<Menubar*>(p)->changePopupMenuItem(this);
213             return; // the rest is handled when the menu bar eventually calls us itself
214         }
215         // or maybe a popup menu?
216         else if (dynamic_cast<PopupMenu*>(p))
217         {
218             // align the popup to the top-right of the menuitem
219             UVector2 pos(cegui_absdim(d_pixelSize.d_width), cegui_absdim(0));
220             d_popup->setPosition(pos + d_popupOffset);
221 
222             static_cast<PopupMenu*>(p)->changePopupMenuItem(this);
223             return; // the rest is handled when the popup menu eventually calls us itself
224         }
225     }
226 
227     // by now we must handle it ourselves
228     // match up with Menubar::changePopupMenu
229     d_popup->openPopupMenu(false);
230 
231     d_opened = true;
232     invalidate();
233 }
234 
235 
236 /*************************************************************************
237     Close the PopupMenu attached to this item.
238 *************************************************************************/
closePopupMenu(bool notify)239 void MenuItem::closePopupMenu(bool notify)
240 {
241     // no popup? or not open...
242     if (d_popup == 0 || !d_opened)
243         return;
244 
245     d_popupOpening = false;
246     d_popupClosing = false;
247 
248     // should we notify the parent menu base?
249     // if we are attached to a menu base, we let it handle the "deactivation"
250     MenuBase* menu = dynamic_cast<MenuBase*>(d_ownerList);
251     if (notify && menu)
252     {
253         // only if the menu base does not allow multiple popups
254         if (!menu->isMultiplePopupsAllowed())
255         {
256             menu->changePopupMenuItem(0);
257             return; // the rest is handled when the menu base eventually call us again itself
258         }
259     }
260     // otherwise we do ourselves
261     else
262     {
263         // match up with Menubar::changePopupMenu
264         //d_popup->hide();
265         d_popup->closePopupMenu(false);
266     }
267 
268     d_opened = false;
269     invalidate();
270 }
271 
272 
273 /*************************************************************************
274     Toggles the PopupMenu.
275 *************************************************************************/
togglePopupMenu(void)276 bool MenuItem::togglePopupMenu(void)
277 {
278     if (d_opened)
279     {
280         closePopupMenu();
281         return false;
282     }
283 
284     openPopupMenu();
285     return true;
286 }
287 
startPopupClosing(void)288 void MenuItem::startPopupClosing(void)
289 {
290     d_popupOpening = false;
291 
292     if (d_opened)
293     {
294         d_autoPopupTimeElapsed = 0.0f;
295         d_popupClosing = true;
296         invalidate();
297     }
298     else
299     {
300         d_popupClosing = false;
301     }
302 }
303 
startPopupOpening(void)304 void MenuItem::startPopupOpening(void)
305 {
306     d_popupClosing = false;
307 
308     if (d_opened)
309     {
310         d_popupOpening = false;
311     }
312     else
313     {
314         d_autoPopupTimeElapsed = 0.0f;
315         d_popupOpening = true;
316     }
317 }
318 
319 /*************************************************************************
320     Recursive function that closes all popups down the hierarchy starting
321     with this one.
322 *************************************************************************/
closeAllMenuItemPopups()323 void MenuItem::closeAllMenuItemPopups()
324 {
325     // are we attached to a PopupMenu?
326     if (!d_ownerList)
327         return;
328 
329     if (dynamic_cast<Menubar*>(d_ownerList))
330     {
331         closePopupMenu();
332         return;
333     }
334 
335     PopupMenu* pop = dynamic_cast<PopupMenu*>(d_ownerList);
336     if (pop)
337     {
338         // is this parent popup attached to a menu item?
339         Window* popParent = pop->getParent();
340         MenuItem* mi = dynamic_cast<MenuItem*>(popParent);
341 
342         if (mi)
343         {
344             // recurse
345             mi->closeAllMenuItemPopups();
346         }
347         // otherwise we just hide the parent popup
348         else
349         {
350             pop->closePopupMenu(false);
351         }
352     }
353 }
354 
355 
356 /*************************************************************************
357     handler invoked internally when the menuitem is clicked.
358 *************************************************************************/
onClicked(WindowEventArgs & e)359 void MenuItem::onClicked(WindowEventArgs& e)
360 {
361     // close the popup if we did'nt spawn a child
362     if (!d_opened && !d_popupWasClosed)
363     {
364         closeAllMenuItemPopups();
365     }
366 
367     d_popupWasClosed = false;
368     fireEvent(EventClicked, e, EventNamespace);
369 }
370 
371 
372 /*************************************************************************
373     Handler for when the mouse moves
374 *************************************************************************/
onMouseMove(MouseEventArgs & e)375 void MenuItem::onMouseMove(MouseEventArgs& e)
376 {
377     // this is needed to discover whether mouse is in the widget area or not.
378     // The same thing used to be done each frame in the rendering method,
379     // but in this version the rendering method may not be called every frame
380     // so we must discover the internal widget state here - which is actually
381     // more efficient anyway.
382 
383     // base class processing
384     ItemEntry::onMouseMove(e);
385 
386     updateInternalState(e.position);
387     ++e.handled;
388 }
389 
390 
391 /*************************************************************************
392     Handler for mouse button pressed events
393 *************************************************************************/
onMouseButtonDown(MouseEventArgs & e)394 void MenuItem::onMouseButtonDown(MouseEventArgs& e)
395 {
396     // default processing
397     ItemEntry::onMouseButtonDown(e);
398 
399     if (e.button == LeftButton)
400     {
401         d_popupWasClosed = false;
402 
403         if (captureInput())
404         {
405             d_pushed = true;
406             updateInternalState(e.position);
407             d_popupWasClosed = !togglePopupMenu();
408             invalidate();
409         }
410 
411         // event was handled by us.
412         ++e.handled;
413     }
414 
415 }
416 
417 
418 /*************************************************************************
419     Handler for mouse button release events
420 *************************************************************************/
onMouseButtonUp(MouseEventArgs & e)421 void MenuItem::onMouseButtonUp(MouseEventArgs& e)
422 {
423     // default processing
424     ItemEntry::onMouseButtonUp(e);
425 
426     if (e.button == LeftButton)
427     {
428         releaseInput();
429 
430         // was the button released over this window?
431         // (use mouse position, as e.position in args has been unprojected)
432         if (!d_popupWasClosed &&
433                 getGUIContext().getRootWindow()->getTargetChildAtPosition(
434                     getGUIContext().getMouseCursor().getPosition()) == this)
435         {
436             WindowEventArgs we(this);
437             onClicked(we);
438         }
439 
440         // event was handled by us.
441         ++e.handled;
442     }
443 
444 }
445 
446 /*************************************************************************
447     Handler for when mouse capture is lost
448 *************************************************************************/
onCaptureLost(WindowEventArgs & e)449 void MenuItem::onCaptureLost(WindowEventArgs& e)
450 {
451     // Default processing
452     ItemEntry::onCaptureLost(e);
453 
454     d_pushed = false;
455     updateInternalState(getUnprojectedPosition(
456         getGUIContext().getMouseCursor().getPosition()));
457     invalidate();
458 
459     // event was handled by us.
460     ++e.handled;
461 }
462 
463 
464 /*************************************************************************
465     Handler for when mouse leaves the widget
466 *************************************************************************/
onMouseLeaves(MouseEventArgs & e)467 void MenuItem::onMouseLeaves(MouseEventArgs& e)
468 {
469     // deafult processing
470     ItemEntry::onMouseLeaves(e);
471 
472     d_hovering = false;
473     invalidate();
474 
475     ++e.handled;
476 }
477 
478 
479 /*************************************************************************
480     Handler called when text is changed.
481 *************************************************************************/
onTextChanged(WindowEventArgs & e)482 void MenuItem::onTextChanged(WindowEventArgs& e)
483 {
484     ItemEntry::onTextChanged(e);
485 
486     // if we are attached to a ItemListBase, we make it update as necessary
487     Window* parent = getParent();
488     ItemListBase* ilb = dynamic_cast<ItemListBase*>(parent);
489 
490     if (ilb)
491     {
492         ilb->handleUpdatedItemData();
493     }
494 
495     ++e.handled;
496 }
497 
498 /*************************************************************************
499 Perform actual update processing for this Window.
500 *************************************************************************/
updateSelf(float elapsed)501 void MenuItem::updateSelf(float elapsed)
502 {
503     ItemEntry::updateSelf(elapsed);
504 
505     //handle delayed popup closing/opening when hovering with the mouse
506     if (d_autoPopupTimeout != 0.0f && (d_popupOpening || d_popupClosing))
507     {
508         // stop timer if the hovering state isn't set appropriately anymore
509         if (d_hovering)
510         {
511             d_popupClosing = false;
512         }
513         else
514         {
515             d_popupOpening = false;
516         }
517 
518         //check if the timer elapsed and take action appropriately
519         d_autoPopupTimeElapsed += elapsed;
520 
521         if (d_autoPopupTimeElapsed > d_autoPopupTimeout)
522         {
523             if (d_popupOpening)
524             {
525                 d_popupOpening = false;
526                 openPopupMenu(true);
527             }
528             else if (d_popupClosing)
529             {
530                 d_popupClosing = false;
531                 closePopupMenu(true);
532             }
533         }
534     }
535 }
536 
537 /*************************************************************************
538     Internal version of adding a child window.
539 *************************************************************************/
addChild_impl(Element * element)540 void MenuItem::addChild_impl(Element* element)
541 {
542     Window* wnd = dynamic_cast<Window*>(element);
543 
544     if (!wnd)
545         CEGUI_THROW(InvalidRequestException(
546             "MenuItem can only have Elements of type Window added as children "
547             "(Window path: " + getNamePath() + ")."));
548 
549     ItemEntry::addChild_impl(wnd);
550 
551     PopupMenu* pop = dynamic_cast<PopupMenu*>(wnd);
552     // if this is a PopupMenu we add it like one
553     if (pop)
554     {
555         setPopupMenu_impl(pop, false);
556     }
557 }
558 
559 /*************************************************************************
560 Add MenuItem specific properties
561 *************************************************************************/
addMenuItemProperties(void)562 void MenuItem::addMenuItemProperties(void)
563 {
564     const String& propertyOrigin = WidgetTypeName;
565 
566     CEGUI_DEFINE_PROPERTY(MenuItem, UVector2,
567         "PopupOffset","Property to specify an offset for the popup menu position. Value is a UVector2 property value.",
568         &MenuItem::setPopupOffset, &MenuItem::getPopupOffset, UVector2::zero()
569     );
570 
571     CEGUI_DEFINE_PROPERTY(MenuItem, float,
572         "AutoPopupTimeout","Property to specify the time, which has to elapse before the popup window is opened/closed if the hovering state changes. Value is a float property value.",
573         &MenuItem::setAutoPopupTimeout, &MenuItem::getAutoPopupTimeout, 0.0f
574     );
575 }
576 
577 } // End of  CEGUI namespace section
578