1 /***********************************************************************
2 created: 31/3/2005
3 author: Tomas Lindquist Olsen (based on original Listbox code by Paul D Turner)
4
5 purpose: Implementation of ItemListBase 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/Exceptions.h"
30 #include "CEGUI/WindowManager.h"
31 #include "CEGUI/CoordConverter.h"
32 #include "CEGUI/widgets/ItemListBase.h"
33 #include "CEGUI/widgets/ItemEntry.h"
34
35 #include <algorithm>
36
37 // Start of CEGUI namespace section
38 namespace CEGUI
39 {
40 const String ItemListBase::EventNamespace("ItemListBase");
41
42 /*************************************************************************
43 ItemListBaseWindowRenderer
44 *************************************************************************/
ItemListBaseWindowRenderer(const String & name)45 ItemListBaseWindowRenderer::ItemListBaseWindowRenderer(const String& name) :
46 WindowRenderer(name, ItemListBase::EventNamespace)
47 {
48 }
49
50
51 /************************************************************************/
52
53
54 /*************************************************************************
55 used for < comparisons between ItemEntry pointers
56 *************************************************************************/
ItemEntry_less(const ItemEntry * a,const ItemEntry * b)57 static bool ItemEntry_less(const ItemEntry* a, const ItemEntry* b)
58 {
59 return a->getText() < b->getText();
60 }
61
62
63 /*************************************************************************
64 used for > comparisons between ItemEntry pointers
65 *************************************************************************/
ItemEntry_greater(const ItemEntry * a,const ItemEntry * b)66 static bool ItemEntry_greater(const ItemEntry* a, const ItemEntry* b)
67 {
68 return (a->getText() > b->getText());
69 }
70
71
72 /************************************************************************/
73
74 /*************************************************************************
75 Constants
76 *************************************************************************/
77 // event names
78 const String ItemListBase::EventListContentsChanged( "ListContentsChanged" );
79 const String ItemListBase::EventSortEnabledChanged("SortEnabledChanged");
80 const String ItemListBase::EventSortModeChanged("SortModeChanged");
81
82 /*************************************************************************
83 Constructor for ItemListBase base class.
84 *************************************************************************/
ItemListBase(const String & type,const String & name)85 ItemListBase::ItemListBase(const String& type, const String& name)
86 : Window(type, name),
87 d_autoResize(false),
88 d_sortEnabled(false),
89 d_sortMode(Ascending),
90 d_sortCallback(0),
91 d_resort(false)
92 {
93 // by default we dont have a content pane, but to make sure things still work
94 // we "emulate" it by setting it to this
95 d_pane = this;
96
97 // add properties for ItemListBase class
98 addItemListBaseProperties();
99 }
100
101
102 /*************************************************************************
103 Destructor for ItemListBase base class.
104 *************************************************************************/
~ItemListBase(void)105 ItemListBase::~ItemListBase(void)
106 {
107 //resetList_impl();
108 }
109
110
111 /*************************************************************************
112 Initialise components
113 *************************************************************************/
initialiseComponents(void)114 void ItemListBase::initialiseComponents(void)
115 {
116 // this pane may be ourselves, and in fact is by default...
117 d_pane->subscribeEvent(Window::EventChildRemoved,
118 Event::Subscriber(&ItemListBase::handle_PaneChildRemoved, this));
119 }
120
121
122 /*************************************************************************
123 Return the item at index position 'index'.
124 *************************************************************************/
getItemFromIndex(size_t index) const125 ItemEntry* ItemListBase::getItemFromIndex(size_t index) const
126 {
127 if (index < d_listItems.size())
128 {
129 return d_listItems[index];
130 }
131 else
132 {
133 CEGUI_THROW(InvalidRequestException(
134 "the specified index is out of range for this ItemListBase."));
135 }
136 }
137
138
139 /*************************************************************************
140 Return the index of ItemEntry \a item
141 *************************************************************************/
getItemIndex(const ItemEntry * item) const142 size_t ItemListBase::getItemIndex(const ItemEntry* item) const
143 {
144 ItemEntryList::const_iterator pos = std::find(d_listItems.begin(), d_listItems.end(), item);
145
146 if (pos != d_listItems.end())
147 {
148 return std::distance(d_listItems.begin(), pos);
149 }
150 else
151 {
152 CEGUI_THROW(InvalidRequestException(
153 "the specified ItemEntry is not attached to this ItemListBase."));
154 }
155 }
156
157
158 /*************************************************************************
159 Search the list for an item with the specified text
160 *************************************************************************/
findItemWithText(const String & text,const ItemEntry * start_item)161 ItemEntry* ItemListBase::findItemWithText(const String& text, const ItemEntry* start_item)
162 {
163 // if start_item is NULL begin search at begining, else start at item after start_item
164 size_t index = (!start_item) ? 0 : (getItemIndex(start_item) + 1);
165
166 while (index < d_listItems.size())
167 {
168 // return pointer to this item if it's text matches
169 if (d_listItems[index]->getText() == text)
170 {
171 return d_listItems[index];
172 }
173 // no matching text, advance to next item
174 else
175 {
176 index++;
177 }
178 }
179
180 // no items matched.
181 return 0;
182 }
183
184
185 /*************************************************************************
186 Return whether the specified ItemEntry is in the List
187 *************************************************************************/
isItemInList(const ItemEntry * item) const188 bool ItemListBase::isItemInList(const ItemEntry* item) const
189 {
190 //return std::find(d_listItems.begin(), d_listItems.end(), item) != d_listItems.end();
191 return (item->d_ownerList == this);
192 }
193
194
195 /*************************************************************************
196 Remove all items from the list.
197 *************************************************************************/
resetList(void)198 void ItemListBase::resetList(void)
199 {
200 if (resetList_impl())
201 {
202 handleUpdatedItemData();
203 }
204 }
205
206
207 /*************************************************************************
208 Add the given ItemEntry to the list.
209 *************************************************************************/
addItem(ItemEntry * item)210 void ItemListBase::addItem(ItemEntry* item)
211 {
212 // make sure the item is valid and that we dont already have it in our list
213 if (item && item->d_ownerList != this)
214 {
215 // if sorting is enabled, re-sort the list
216 if (d_sortEnabled)
217 {
218 d_listItems.insert(
219 std::upper_bound(d_listItems.begin(), d_listItems.end(), item, getRealSortCallback()),
220 item);
221 }
222 // just stick it on the end.
223 else
224 {
225 d_listItems.push_back(item);
226 }
227 // make sure it gets added properly
228 item->d_ownerList = this;
229 addChild(item);
230 handleUpdatedItemData();
231 }
232 }
233
234
235 /*************************************************************************
236 Insert an item into the list box after a specified item already in
237 the list.
238 *************************************************************************/
insertItem(ItemEntry * item,const ItemEntry * position)239 void ItemListBase::insertItem(ItemEntry* item, const ItemEntry* position)
240 {
241 if (d_sortEnabled)
242 {
243 addItem(item);
244 }
245 else if (item && item->d_ownerList != this)
246 {
247 // if position is NULL begin insert at begining, else insert after item 'position'
248 ItemEntryList::iterator ins_pos;
249
250 if (!position)
251 {
252 ins_pos = d_listItems.begin();
253 }
254 else
255 {
256 ins_pos = std::find(d_listItems.begin(), d_listItems.end(), position);
257
258 // throw if item 'position' is not in the list
259 if (ins_pos == d_listItems.end())
260 {
261 CEGUI_THROW(InvalidRequestException(
262 "the specified ItemEntry for parameter 'position' is not attached to this ItemListBase."));
263 }
264
265 }
266
267 d_listItems.insert(ins_pos, item);
268 item->d_ownerList = this;
269 addChild(item);
270
271 handleUpdatedItemData();
272 }
273 }
274
275
276 /*************************************************************************
277 Removes the given item from the list box.
278 *************************************************************************/
removeItem(ItemEntry * item)279 void ItemListBase::removeItem(ItemEntry* item)
280 {
281 if (item && item->d_ownerList == this)
282 {
283 d_pane->removeChild(item);
284 if (item->isDestroyedByParent())
285 {
286 WindowManager::getSingleton().destroyWindow(item);
287 }
288 }
289 }
290
291
292 /*************************************************************************
293 Set wheter or not this ItemListBase widget should automatically
294 resize to fit its content.
295 *************************************************************************/
setAutoResizeEnabled(bool setting)296 void ItemListBase::setAutoResizeEnabled(bool setting)
297 {
298 bool old = d_autoResize;
299 d_autoResize = setting;
300
301 // if not already enabled, trigger a resize - only if not currently initialising
302 if ( d_autoResize && !old && !d_initialising)
303 {
304 sizeToContent();
305 }
306 }
307
308
309 /*************************************************************************
310 Causes the list box to update it's internal state after changes have
311 been made to one or more attached ItemEntry objects.
312 *************************************************************************/
handleUpdatedItemData(bool resort)313 void ItemListBase::handleUpdatedItemData(bool resort)
314 {
315 if (!d_destructionStarted)
316 {
317 d_resort |= resort;
318 WindowEventArgs args(this);
319 onListContentsChanged(args);
320 }
321 }
322
323
324 /*************************************************************************
325 Handler called internally when the list contents are changed
326 *************************************************************************/
onListContentsChanged(WindowEventArgs & e)327 void ItemListBase::onListContentsChanged(WindowEventArgs& e)
328 {
329 // if we are not currently initialising we might have things todo
330 if (!d_initialising)
331 {
332 invalidate();
333
334 // if auto resize is enabled - do it
335 if (d_autoResize)
336 sizeToContent();
337
338 // resort list if requested and enabled
339 if (d_resort && d_sortEnabled)
340 sortList(false);
341 d_resort = false;
342
343 // redo the item layout and fire our event
344 layoutItemWidgets();
345 fireEvent(EventListContentsChanged, e, EventNamespace);
346 }
347 }
348
349 //----------------------------------------------------------------------------//
onParentSized(ElementEventArgs & e)350 void ItemListBase::onParentSized(ElementEventArgs& e)
351 {
352 Window::onParentSized(e);
353
354 if (d_autoResize)
355 sizeToContent();
356 }
357
358 //----------------------------------------------------------------------------//
359
360 /************************************************************************
361 Handler for when a child is removed
362 *************************************************************************/
363 /*void ItemListBase::onChildRemoved(WindowEventArgs& e)
364 {
365 // if destruction has already begun, we don't need to do anything.
366 // everything has to go anyway
367 // make sure it is removed from the itemlist if we have an ItemEntry
368 if (!d_destructionStarted && e.window->testClassName("ItemEntry"))
369 {
370 ItemEntryList::iterator pos = std::find(d_listItems.begin(), d_listItems.end(), e.window);
371
372 // if item is in the list
373 if (pos != d_listItems.end())
374 {
375 // remove item
376 (*pos)->d_ownerList = 0;
377 d_listItems.erase(pos);
378 // trigger list update
379 handleUpdatedItemData();
380 }
381 }
382
383 // base class handling
384 Window::onChildRemoved(e);
385 }*/
386
387 /************************************************************************
388 Handler for when the window initiates destruction
389 *************************************************************************/
390 /*void ItemListBase::onDestructionStarted(WindowEventArgs& e)
391 {
392 // base class handling
393 Window::onDestructionStarted(e);
394
395 // remove everything from the list properly
396 resetList_impl();
397 }*/
398
399
400 /*************************************************************************
401 Remove all items from the list.
402 *************************************************************************/
resetList_impl(void)403 bool ItemListBase::resetList_impl(void)
404 {
405 // just return false if the list is already empty
406 if (d_listItems.empty())
407 {
408 return false;
409 }
410 // we have items to be removed and possible deleted
411 else
412 {
413 // delete any items we are supposed to
414 while (!d_listItems.empty())
415 {
416 ItemEntry* item = d_listItems[0];
417 d_pane->removeChild(item);
418 if (item->isDestroyedByParent())
419 {
420 WindowManager::getSingleton().destroyWindow(item);
421 }
422 }
423
424 // list is cleared by the removeChild calls
425 return true;
426 }
427 }
428
429
430 /*************************************************************************
431 Add ItemListBase specific properties
432 *************************************************************************/
addItemListBaseProperties(void)433 void ItemListBase::addItemListBaseProperties(void)
434 {
435 const String propertyOrigin("ItemListBase");
436
437 CEGUI_DEFINE_PROPERTY(ItemListBase, bool,
438 "AutoResizeEnabled", "Property to get/set the state of the auto resizing enabled setting for the ItemListBase. Value is either \"true\" or \"false\".",
439 &ItemListBase::setAutoResizeEnabled, &ItemListBase::isAutoResizeEnabled, false
440 );
441 CEGUI_DEFINE_PROPERTY(ItemListBase, bool,
442 "SortEnabled", "Property to get/set the state of the sorting enabled setting for the ItemListBase. Value is either \"true\" or \"false\".",
443 &ItemListBase::setSortEnabled, &ItemListBase::isSortEnabled, false
444 );
445 CEGUI_DEFINE_PROPERTY(ItemListBase, ItemListBase::SortMode,
446 "SortMode", "Property to get/set the sorting mode for the ItemListBase. Value is either \"Ascending\", \"Descending\" or \"UserSort\".",
447 &ItemListBase::setSortMode, &ItemListBase::getSortMode, ItemListBase::Ascending
448 );
449 }
450
451
452 /*************************************************************************
453 Internal version of adding a child window.
454 *************************************************************************/
addChild_impl(Element * element)455 void ItemListBase::addChild_impl(Element* element)
456 {
457 ItemEntry* item = dynamic_cast<ItemEntry*>(element);
458
459 // if this is an ItemEntry we add it like one, but only if it is not already in the list!
460 if (item)
461 {
462 // add to the pane if we have one
463 if (d_pane != this)
464 {
465 d_pane->addChild(item);
466 }
467 // add item directly to us
468 else
469 {
470 Window::addChild_impl(item);
471 }
472
473 if (item->d_ownerList != this)
474 {
475 // perform normal addItem
476 // if sorting is enabled, re-sort the list
477 if (d_sortEnabled)
478 {
479 d_listItems.insert(
480 std::upper_bound(d_listItems.begin(), d_listItems.end(), item, getRealSortCallback()),
481 item);
482 }
483 // just stick it on the end.
484 else
485 {
486 d_listItems.push_back(item);
487 }
488 item->d_ownerList = this;
489 handleUpdatedItemData();
490 }
491 }
492 // otherwise it's base class processing
493 else
494 {
495 Window::addChild_impl(element);
496 }
497 }
498
499
500 /************************************************************************
501 Initialisation done
502 *************************************************************************/
endInitialisation(void)503 void ItemListBase::endInitialisation(void)
504 {
505 Window::endInitialisation();
506 handleUpdatedItemData(true);
507 }
508
509
510 /************************************************************************
511 Perform child window layout
512 ************************************************************************/
performChildWindowLayout(bool nonclient_sized_hint,bool client_sized_hint)513 void ItemListBase::performChildWindowLayout(bool nonclient_sized_hint,
514 bool client_sized_hint)
515 {
516 Window::performChildWindowLayout(nonclient_sized_hint,
517 client_sized_hint);
518 // if we are not currently initialising
519 if (!d_initialising)
520 {
521 // Redo the item layout.
522 // We don't just call handleUpdateItemData, as that could trigger a resize,
523 // which is not what is being requested.
524 // It would also cause infinite recursion... so lets just avoid that :)
525 layoutItemWidgets();
526 }
527 }
528
529 /************************************************************************
530 Resize to fit content
531 ************************************************************************/
sizeToContent_impl(void)532 void ItemListBase::sizeToContent_impl(void)
533 {
534 Rectf renderArea(getItemRenderArea());
535 Rectf wndArea(CoordConverter::asAbsolute(getArea(), getParentPixelSize()));
536
537 // get size of content
538 Sizef sz(getContentSize());
539
540 // calculate the full size with the frame accounted for and resize the window to this
541 sz.d_width += wndArea.getWidth() - renderArea.getWidth();
542 sz.d_height += wndArea.getHeight() - renderArea.getHeight();
543 setSize(USize(cegui_absdim(sz.d_width), cegui_absdim(sz.d_height)));
544 }
545
546 /************************************************************************
547 Get item render area
548 ************************************************************************/
getItemRenderArea(void) const549 Rectf ItemListBase::getItemRenderArea(void) const
550 {
551 if (d_windowRenderer != 0)
552 {
553 ItemListBaseWindowRenderer* wr = (ItemListBaseWindowRenderer*)d_windowRenderer;
554 return wr->getItemRenderArea();
555 }
556 else
557 {
558 //return getItemRenderArea_impl();
559 CEGUI_THROW(InvalidRequestException(
560 "This function must be implemented by the window renderer module"));
561 }
562 }
563
564 /************************************************************************
565 Handler to manage items being removed from the content pane
566 ************************************************************************/
handle_PaneChildRemoved(const EventArgs & e)567 bool ItemListBase::handle_PaneChildRemoved(const EventArgs& e)
568 {
569 Window* wnd = static_cast<const WindowEventArgs&>(e).window;
570
571 // make sure it is removed from the itemlist if we have an ItemEntry
572 ItemEntry* item = dynamic_cast<ItemEntry*>(wnd);
573 if (item)
574 {
575 ItemEntryList::iterator pos = std::find(d_listItems.begin(), d_listItems.end(), item);
576
577 // if item is in the list
578 if (pos != d_listItems.end())
579 {
580 // make sure the item is no longer related to us
581 (*pos)->d_ownerList = 0;
582 // remove item
583 d_listItems.erase(pos);
584 // trigger list update
585 handleUpdatedItemData();
586 }
587 }
588
589 return false;
590 }
591
592 /************************************************************************
593 Set sorting enabled state
594 ************************************************************************/
setSortEnabled(bool setting)595 void ItemListBase::setSortEnabled(bool setting)
596 {
597 if (d_sortEnabled != setting)
598 {
599 d_sortEnabled = setting;
600
601 if (d_sortEnabled && !d_initialising)
602 {
603 sortList();
604 }
605
606 WindowEventArgs e(this);
607 onSortEnabledChanged(e);
608 }
609 }
610
611 /************************************************************************
612 Set the user sorting callback
613 ************************************************************************/
setSortCallback(SortCallback cb)614 void ItemListBase::setSortCallback(SortCallback cb)
615 {
616 if (d_sortCallback != cb)
617 {
618 d_sortCallback = cb;
619 if (d_sortEnabled && !d_initialising)
620 {
621 sortList();
622 }
623 handleUpdatedItemData(true);
624 }
625 }
626
627 /************************************************************************
628 Handle sort enabled changed
629 ************************************************************************/
onSortEnabledChanged(WindowEventArgs & e)630 void ItemListBase::onSortEnabledChanged(WindowEventArgs& e)
631 {
632 fireEvent(EventSortEnabledChanged, e);
633 }
634
635 /************************************************************************
636 Handle sort mode changed
637 ************************************************************************/
onSortModeChanged(WindowEventArgs & e)638 void ItemListBase::onSortModeChanged(WindowEventArgs& e)
639 {
640 fireEvent(EventSortModeChanged, e);
641 }
642
643 /************************************************************************
644 Sort list
645 ************************************************************************/
sortList(bool relayout)646 void ItemListBase::sortList(bool relayout)
647 {
648 std::sort(d_listItems.begin(), d_listItems.end(), getRealSortCallback());
649 if (relayout)
650 {
651 layoutItemWidgets();
652 }
653 }
654
validateWindowRenderer(const WindowRenderer * renderer) const655 bool ItemListBase::validateWindowRenderer(const WindowRenderer* renderer) const
656 {
657 return dynamic_cast<const ItemListBaseWindowRenderer*>(renderer) != 0;
658 }
659
660 /************************************************************************
661 Get the real function pointer to use for the sorting operation
662 ************************************************************************/
getRealSortCallback() const663 ItemListBase::SortCallback ItemListBase::getRealSortCallback() const
664 {
665 switch (d_sortMode)
666 {
667 case Ascending:
668 return &ItemEntry_less;
669
670 case Descending:
671 return &ItemEntry_greater;
672
673 case UserSort:
674 return (d_sortCallback!=0) ? d_sortCallback : &ItemEntry_less;
675
676 // we default to ascending sorting
677 default:
678 return &ItemEntry_less;
679 }
680 }
681
682 /************************************************************************
683 Set sort mode
684 ************************************************************************/
setSortMode(SortMode mode)685 void ItemListBase::setSortMode(SortMode mode)
686 {
687 if (d_sortMode != mode)
688 {
689 d_sortMode = mode;
690 if (d_sortEnabled && !d_initialising)
691 sortList();
692
693 WindowEventArgs e(this);
694 onSortModeChanged(e);
695 }
696 }
697
698 } // End of CEGUI namespace section
699