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