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