1 // Copyright (C) 2002-2012 Nikolaus Gebhardt
2 // This file is part of the "Irrlicht Engine".
3 // For conditions of distribution and use, see copyright notice in irrlicht.h
4 
5 // 07.10.2005 - Multicolor-Listbox added by A. Buschhueter (Acki)
6 //                                          A_Buschhueter@gmx.de
7 
8 #include "CGUITable.h"
9 #ifdef _IRR_COMPILE_WITH_GUI_
10 
11 #include "IGUISkin.h"
12 #include "IGUIEnvironment.h"
13 #include "IVideoDriver.h"
14 #include "IGUIFont.h"
15 #include "CGUIScrollBar.h"
16 #include "os.h"
17 
18 #define ARROW_PAD 15
19 
20 namespace irr
21 {
22 namespace gui
23 {
24 
25 //! constructor
CGUITable(IGUIEnvironment * environment,IGUIElement * parent,s32 id,const core::rect<s32> & rectangle,bool clip,bool drawBack,bool moveOverSelect)26 CGUITable::CGUITable(IGUIEnvironment* environment, IGUIElement* parent,
27 						s32 id, const core::rect<s32>& rectangle, bool clip,
28 						bool drawBack, bool moveOverSelect)
29 : IGUITable(environment, parent, id, rectangle), Font(0),
30 	VerticalScrollBar(0), HorizontalScrollBar(0),
31 	Clip(clip), DrawBack(drawBack), MoveOverSelect(moveOverSelect),
32 	Selecting(false), CurrentResizedColumn(-1), ResizeStart(0), ResizableColumns(true),
33 	ItemHeight(0), TotalItemHeight(0), TotalItemWidth(0), Selected(-1),
34 	CellHeightPadding(2), CellWidthPadding(5), ActiveTab(-1),
35 	CurrentOrdering(EGOM_NONE), DrawFlags(EGTDF_ROWS | EGTDF_COLUMNS | EGTDF_ACTIVE_ROW )
36 {
37 	#ifdef _DEBUG
38 	setDebugName("CGUITable");
39 	#endif
40 
41 	VerticalScrollBar = Environment->addScrollBar(false, core::rect<s32>(0, 0, 100, 100), this, -1);
42 	if (VerticalScrollBar)
43 	{
44 		VerticalScrollBar->grab();
45 		VerticalScrollBar->setNotClipped(false);
46 		VerticalScrollBar->setSubElement(true);
47 	}
48 
49 	HorizontalScrollBar = Environment->addScrollBar(true, core::rect<s32>(0, 0, 100, 100), this, -1);
50 	if ( HorizontalScrollBar )
51 	{
52 		HorizontalScrollBar->grab();
53 		HorizontalScrollBar->setNotClipped(false);
54 		HorizontalScrollBar->setSubElement(true);
55 	}
56 
57 	refreshControls();
58 }
59 
60 
61 //! destructor
~CGUITable()62 CGUITable::~CGUITable()
63 {
64 	if (VerticalScrollBar)
65 		VerticalScrollBar->drop();
66 	if ( HorizontalScrollBar )
67 		HorizontalScrollBar->drop();
68 
69 	if (Font)
70 		Font->drop();
71 }
72 
73 
addColumn(const wchar_t * caption,s32 columnIndex)74 void CGUITable::addColumn(const wchar_t* caption, s32 columnIndex)
75 {
76 	Column tabHeader;
77 	tabHeader.Name = caption;
78 	tabHeader.Width = Font->getDimension(caption).Width + (CellWidthPadding * 2) + ARROW_PAD;
79 	tabHeader.OrderingMode = EGCO_NONE;
80 
81 	if ( columnIndex < 0 || columnIndex >= (s32)Columns.size() )
82 	{
83 		Columns.push_back(tabHeader);
84 		for ( u32 i=0; i < Rows.size(); ++i )
85 		{
86 			Cell cell;
87 			Rows[i].Items.push_back(cell);
88 		}
89 	}
90 	else
91 	{
92 		Columns.insert(tabHeader, columnIndex);
93 		for ( u32 i=0; i < Rows.size(); ++i )
94 		{
95 			Cell cell;
96 			Rows[i].Items.insert(cell, columnIndex);
97 		}
98 	}
99 
100 	if (ActiveTab == -1)
101 		ActiveTab = 0;
102 
103 	recalculateWidths();
104 }
105 
106 
107 //! remove a column from the table
removeColumn(u32 columnIndex)108 void CGUITable::removeColumn(u32 columnIndex)
109 {
110 	if ( columnIndex < Columns.size() )
111 	{
112 		Columns.erase(columnIndex);
113 		for ( u32 i=0; i < Rows.size(); ++i )
114 		{
115 			Rows[i].Items.erase(columnIndex);
116 		}
117 	}
118 	if ( (s32)columnIndex <= ActiveTab )
119 		ActiveTab = Columns.size() ? 0 : -1;
120 
121 	recalculateWidths();
122 }
123 
124 
getColumnCount() const125 s32 CGUITable::getColumnCount() const
126 {
127 	return Columns.size();
128 }
129 
130 
getRowCount() const131 s32 CGUITable::getRowCount() const
132 {
133 	return Rows.size();
134 }
135 
136 
setActiveColumn(s32 idx,bool doOrder)137 bool CGUITable::setActiveColumn(s32 idx, bool doOrder )
138 {
139 	if (idx < 0 || idx >= (s32)Columns.size())
140 		return false;
141 
142 	bool changed = (ActiveTab != idx);
143 
144 	ActiveTab = idx;
145 	if ( ActiveTab < 0 )
146 		return false;
147 
148 	if ( doOrder )
149 	{
150 		switch ( Columns[idx].OrderingMode )
151 		{
152 			case EGCO_NONE:
153 				CurrentOrdering = EGOM_NONE;
154 				break;
155 
156 			case EGCO_CUSTOM:
157 				CurrentOrdering = EGOM_NONE;
158 				if (Parent)
159 				{
160 					SEvent event;
161 					event.EventType = EET_GUI_EVENT;
162 					event.GUIEvent.Caller = this;
163 					event.GUIEvent.Element = 0;
164 					event.GUIEvent.EventType = EGET_TABLE_HEADER_CHANGED;
165 					Parent->OnEvent(event);
166 				}
167 
168 				break;
169 
170 			case EGCO_ASCENDING:
171 				CurrentOrdering = EGOM_ASCENDING;
172 				break;
173 
174 			case EGCO_DESCENDING:
175 				CurrentOrdering = EGOM_DESCENDING;
176 				break;
177 
178 			case EGCO_FLIP_ASCENDING_DESCENDING:
179 				CurrentOrdering = EGOM_ASCENDING == CurrentOrdering ? EGOM_DESCENDING : EGOM_ASCENDING;
180 				break;
181 			default:
182 				CurrentOrdering = EGOM_NONE;
183 		}
184 
185 		orderRows(getActiveColumn(), CurrentOrdering);
186 	}
187 
188 	if (changed)
189 	{
190 		SEvent event;
191 		event.EventType = EET_GUI_EVENT;
192 		event.GUIEvent.Caller = this;
193 		event.GUIEvent.Element = 0;
194 		event.GUIEvent.EventType = EGET_TABLE_HEADER_CHANGED;
195 		Parent->OnEvent(event);
196 	}
197 
198 	return true;
199 }
200 
201 
getActiveColumn() const202 s32 CGUITable::getActiveColumn() const
203 {
204 	return ActiveTab;
205 }
206 
207 
getActiveColumnOrdering() const208 EGUI_ORDERING_MODE CGUITable::getActiveColumnOrdering() const
209 {
210 	return CurrentOrdering;
211 }
212 
213 
setColumnWidth(u32 columnIndex,u32 width)214 void CGUITable::setColumnWidth(u32 columnIndex, u32 width)
215 {
216 	if ( columnIndex < Columns.size() )
217 	{
218 		const u32 MIN_WIDTH = Font->getDimension(Columns[columnIndex].Name.c_str() ).Width + (CellWidthPadding * 2);
219 		if ( width < MIN_WIDTH )
220 			width = MIN_WIDTH;
221 
222 		Columns[columnIndex].Width = width;
223 
224 		for ( u32 i=0; i < Rows.size(); ++i )
225 		{
226 			breakText( Rows[i].Items[columnIndex].Text, Rows[i].Items[columnIndex].BrokenText, Columns[columnIndex].Width );
227 		}
228 	}
229 	recalculateWidths();
230 }
231 
232 //! Get the width of a column
getColumnWidth(u32 columnIndex) const233 u32 CGUITable::getColumnWidth(u32 columnIndex) const
234 {
235 	if ( columnIndex >= Columns.size() )
236 		return 0;
237 
238 	return Columns[columnIndex].Width;
239 }
240 
setResizableColumns(bool resizable)241 void CGUITable::setResizableColumns(bool resizable)
242 {
243 	ResizableColumns = resizable;
244 }
245 
246 
hasResizableColumns() const247 bool CGUITable::hasResizableColumns() const
248 {
249 	return ResizableColumns;
250 }
251 
252 
addRow(u32 rowIndex)253 u32 CGUITable::addRow(u32 rowIndex)
254 {
255 	if ( rowIndex > Rows.size() )
256 	{
257 		rowIndex = Rows.size();
258 	}
259 
260 	Row row;
261 
262 	if ( rowIndex == Rows.size() )
263 		Rows.push_back(row);
264 	else
265 		Rows.insert(row, rowIndex);
266 
267 	Rows[rowIndex].Items.reallocate(Columns.size());
268 	for ( u32 i = 0 ; i < Columns.size() ; ++i )
269 	{
270 		Rows[rowIndex].Items.push_back(Cell());
271 	}
272 
273 	recalculateHeights();
274 	return rowIndex;
275 }
276 
277 
removeRow(u32 rowIndex)278 void CGUITable::removeRow(u32 rowIndex)
279 {
280 	if ( rowIndex > Rows.size() )
281 		return;
282 
283 	Rows.erase( rowIndex );
284 
285 	if ( !(Selected < s32(Rows.size())) )
286 		Selected = Rows.size() - 1;
287 
288 	recalculateHeights();
289 }
290 
291 
292 //! adds an list item, returns id of item
setCellText(u32 rowIndex,u32 columnIndex,const core::stringw & text)293 void CGUITable::setCellText(u32 rowIndex, u32 columnIndex, const core::stringw& text)
294 {
295 	if ( rowIndex < Rows.size() && columnIndex < Columns.size() )
296 	{
297 		Rows[rowIndex].Items[columnIndex].Text = text;
298 		breakText( Rows[rowIndex].Items[columnIndex].Text, Rows[rowIndex].Items[columnIndex].BrokenText, Columns[columnIndex].Width );
299 
300 		IGUISkin* skin = Environment->getSkin();
301 		if ( skin )
302 			Rows[rowIndex].Items[columnIndex].Color = skin->getColor(EGDC_BUTTON_TEXT);
303 	}
304 }
305 
setCellText(u32 rowIndex,u32 columnIndex,const core::stringw & text,video::SColor color)306 void CGUITable::setCellText(u32 rowIndex, u32 columnIndex, const core::stringw& text, video::SColor color)
307 {
308 	if ( rowIndex < Rows.size() && columnIndex < Columns.size() )
309 	{
310 		Rows[rowIndex].Items[columnIndex].Text = text;
311 		breakText( Rows[rowIndex].Items[columnIndex].Text, Rows[rowIndex].Items[columnIndex].BrokenText, Columns[columnIndex].Width );
312 		Rows[rowIndex].Items[columnIndex].Color = color;
313 		Rows[rowIndex].Items[columnIndex].IsOverrideColor = true;
314 	}
315 }
316 
317 
setCellColor(u32 rowIndex,u32 columnIndex,video::SColor color)318 void CGUITable::setCellColor(u32 rowIndex, u32 columnIndex, video::SColor color)
319 {
320 	if ( rowIndex < Rows.size() && columnIndex < Columns.size() )
321 	{
322 		Rows[rowIndex].Items[columnIndex].Color = color;
323 		Rows[rowIndex].Items[columnIndex].IsOverrideColor = true;
324 	}
325 }
326 
327 
setCellData(u32 rowIndex,u32 columnIndex,void * data)328 void CGUITable::setCellData(u32 rowIndex, u32 columnIndex, void *data)
329 {
330 	if ( rowIndex < Rows.size() && columnIndex < Columns.size() )
331 	{
332 		Rows[rowIndex].Items[columnIndex].Data = data;
333 	}
334 }
335 
336 
getCellText(u32 rowIndex,u32 columnIndex) const337 const wchar_t* CGUITable::getCellText(u32 rowIndex, u32 columnIndex ) const
338 {
339 	if ( rowIndex < Rows.size() && columnIndex < Columns.size() )
340 	{
341 		return Rows[rowIndex].Items[columnIndex].Text.c_str();
342 	}
343 
344 	return 0;
345 }
346 
347 
getCellData(u32 rowIndex,u32 columnIndex) const348 void* CGUITable::getCellData(u32 rowIndex, u32 columnIndex ) const
349 {
350 	if ( rowIndex < Rows.size() && columnIndex < Columns.size() )
351 	{
352 		return Rows[rowIndex].Items[columnIndex].Data;
353 	}
354 
355 	return 0;
356 }
357 
358 
359 //! clears the list
clear()360 void CGUITable::clear()
361 {
362     Selected = -1;
363 	Rows.clear();
364 	Columns.clear();
365 
366 	if (VerticalScrollBar)
367 		VerticalScrollBar->setPos(0);
368 	if ( HorizontalScrollBar )
369 		HorizontalScrollBar->setPos(0);
370 
371 	recalculateHeights();
372 	recalculateWidths();
373 }
374 
375 
clearRows()376 void CGUITable::clearRows()
377 {
378     Selected = -1;
379 	Rows.clear();
380 
381 	if (VerticalScrollBar)
382 		VerticalScrollBar->setPos(0);
383 
384 	recalculateHeights();
385 }
386 
387 
388 /*!
389 */
getSelected() const390 s32 CGUITable::getSelected() const
391 {
392 	return Selected;
393 }
394 
395 //! set wich row is currently selected
setSelected(s32 index)396 void CGUITable::setSelected( s32 index )
397 {
398 	Selected = -1;
399 	if ( index >= 0 && index < (s32) Rows.size() )
400 		Selected = index;
401 }
402 
403 
recalculateWidths()404 void CGUITable::recalculateWidths()
405 {
406 	TotalItemWidth=0;
407 	for ( u32 i=0; i < Columns.size(); ++i )
408 	{
409 		TotalItemWidth += Columns[i].Width;
410 	}
411 	checkScrollbars();
412 }
413 
414 
recalculateHeights()415 void CGUITable::recalculateHeights()
416 {
417 	TotalItemHeight = 0;
418 	IGUISkin* skin = Environment->getSkin();
419 	if (Font != skin->getFont())
420 	{
421 		if (Font)
422 			Font->drop();
423 
424 		Font = skin->getFont();
425 
426 		ItemHeight = 0;
427 
428 		if(Font)
429 		{
430 			ItemHeight = Font->getDimension(L"A").Height + (CellHeightPadding * 2);
431 			Font->grab();
432 		}
433 	}
434 	TotalItemHeight = ItemHeight * Rows.size();		//  header is not counted, because we only want items
435 	checkScrollbars();
436 }
437 
438 
439 // automatic enabled/disabling and resizing of scrollbars
checkScrollbars()440 void CGUITable::checkScrollbars()
441 {
442 	IGUISkin* skin = Environment->getSkin();
443 	if ( !HorizontalScrollBar || !VerticalScrollBar || !skin)
444 		return;
445 
446 	s32 scrollBarSize = skin->getSize(EGDS_SCROLLBAR_SIZE);
447 	bool wasHorizontalScrollBarVisible = HorizontalScrollBar->isVisible();
448 	bool wasVerticalScrollBarVisible = VerticalScrollBar->isVisible();
449 	HorizontalScrollBar->setVisible(false);
450 	VerticalScrollBar->setVisible(false);
451 
452 	// CAREFUL: near identical calculations for tableRect and clientClip are also done in draw
453 	// area of table used for drawing without scrollbars
454 	core::rect<s32> tableRect(AbsoluteRect);
455 	tableRect.UpperLeftCorner.X += 1;
456 	tableRect.UpperLeftCorner.Y += 1;
457 	s32 headerBottom = tableRect.UpperLeftCorner.Y + ItemHeight;
458 
459 	// area of for the items (without header and without scrollbars)
460 	core::rect<s32> clientClip(tableRect);
461 	clientClip.UpperLeftCorner.Y = headerBottom + 1;
462 
463 	// needs horizontal scroll be visible?
464 	if( TotalItemWidth > clientClip.getWidth() )
465 	{
466 		clientClip.LowerRightCorner.Y -= scrollBarSize;
467 		HorizontalScrollBar->setVisible(true);
468 		HorizontalScrollBar->setMax(core::max_(0,TotalItemWidth - clientClip.getWidth()));
469 	}
470 
471 	// needs vertical scroll be visible?
472 	if( TotalItemHeight > clientClip.getHeight() )
473 	{
474 		clientClip.LowerRightCorner.X -= scrollBarSize;
475 		VerticalScrollBar->setVisible(true);
476 		VerticalScrollBar->setMax(core::max_(0,TotalItemHeight - clientClip.getHeight()));
477 
478 		// check horizontal again because we have now smaller clientClip
479 		if ( !HorizontalScrollBar->isVisible() )
480 		{
481 			if( TotalItemWidth > clientClip.getWidth() )
482 			{
483 				clientClip.LowerRightCorner.Y -= scrollBarSize;
484 				HorizontalScrollBar->setVisible(true);
485 				HorizontalScrollBar->setMax(core::max_(0,TotalItemWidth - clientClip.getWidth()));
486 			}
487 		}
488 	}
489 
490 	// find the correct size for the vertical scrollbar
491 	if ( VerticalScrollBar->isVisible() )
492 	{
493 		if  (!wasVerticalScrollBarVisible )
494 			VerticalScrollBar->setPos(0);
495 
496 		if ( HorizontalScrollBar->isVisible() )
497 		{
498 			VerticalScrollBar->setRelativePosition(
499 				core::rect<s32>(RelativeRect.getWidth() - scrollBarSize, 1,
500 				RelativeRect.getWidth()-1, RelativeRect.getHeight()-(1+scrollBarSize) ) );
501 		}
502 		else
503 		{
504 			VerticalScrollBar->setRelativePosition(
505 				core::rect<s32>(RelativeRect.getWidth() - scrollBarSize, 1,
506 				RelativeRect.getWidth()-1, RelativeRect.getHeight()-1) );
507 		}
508 	}
509 
510 	// find the correct size for the horizontal scrollbar
511 	if ( HorizontalScrollBar->isVisible() )
512 	{
513 		if ( !wasHorizontalScrollBarVisible )
514 			HorizontalScrollBar->setPos(0);
515 
516 		if ( VerticalScrollBar->isVisible() )
517 		{
518 			HorizontalScrollBar->setRelativePosition( core::rect<s32>(1, RelativeRect.getHeight() - scrollBarSize, RelativeRect.getWidth()-(1+scrollBarSize), RelativeRect.getHeight()-1) );
519 		}
520 		else
521 		{
522 			HorizontalScrollBar->setRelativePosition( core::rect<s32>(1, RelativeRect.getHeight() - scrollBarSize, RelativeRect.getWidth()-1, RelativeRect.getHeight()-1) );
523 		}
524 	}
525 }
526 
527 
refreshControls()528 void CGUITable::refreshControls()
529 {
530 	updateAbsolutePosition();
531 
532 	if ( VerticalScrollBar )
533 		VerticalScrollBar->setVisible(false);
534 
535 	if ( HorizontalScrollBar )
536 		HorizontalScrollBar->setVisible(false);
537 
538 	recalculateHeights();
539 	recalculateWidths();
540 }
541 
542 
543 //! called if an event happened.
OnEvent(const SEvent & event)544 bool CGUITable::OnEvent(const SEvent &event)
545 {
546 	if (isEnabled())
547 	{
548 
549 		switch(event.EventType)
550 		{
551 		case EET_GUI_EVENT:
552 			switch(event.GUIEvent.EventType)
553 			{
554 			case gui::EGET_SCROLL_BAR_CHANGED:
555 				if (event.GUIEvent.Caller == VerticalScrollBar)
556 				{
557 					// current position will get read out in draw
558 					return true;
559 				}
560 				if (event.GUIEvent.Caller == HorizontalScrollBar)
561 				{
562 					// current position will get read out in draw
563 					return true;
564 				}
565 				break;
566 			case gui::EGET_ELEMENT_FOCUS_LOST:
567 				{
568 					CurrentResizedColumn = -1;
569 					Selecting = false;
570 				}
571 				break;
572 			default:
573 				break;
574 			}
575 			break;
576 		case EET_MOUSE_INPUT_EVENT:
577 			{
578 				if ( !isEnabled() )
579 					return false;
580 
581 				core::position2d<s32> p(event.MouseInput.X, event.MouseInput.Y);
582 
583 				switch(event.MouseInput.Event)
584 				{
585 				case EMIE_MOUSE_WHEEL:
586 					VerticalScrollBar->setPos(VerticalScrollBar->getPos() + (event.MouseInput.Wheel < 0 ? -1 : 1)*-10);
587 					return true;
588 
589 				case EMIE_LMOUSE_PRESSED_DOWN:
590 
591 					if (Environment->hasFocus(this) &&
592 						VerticalScrollBar->isVisible() &&
593 						VerticalScrollBar->getAbsolutePosition().isPointInside(p) &&
594 						VerticalScrollBar->OnEvent(event))
595 						return true;
596 
597 					if (Environment->hasFocus(this) &&
598 						HorizontalScrollBar->isVisible() &&
599 						HorizontalScrollBar->getAbsolutePosition().isPointInside(p) &&
600 						HorizontalScrollBar->OnEvent(event))
601 						return true;
602 
603 					if ( dragColumnStart( event.MouseInput.X, event.MouseInput.Y ) )
604 					{
605 						Environment->setFocus(this);
606 						return true;
607 					}
608 
609 					if ( selectColumnHeader( event.MouseInput.X, event.MouseInput.Y ) )
610 						return true;
611 
612 					Selecting = true;
613 					Environment->setFocus(this);
614 					return true;
615 
616 				case EMIE_LMOUSE_LEFT_UP:
617 
618 					CurrentResizedColumn = -1;
619 					Selecting = false;
620 					if (!getAbsolutePosition().isPointInside(p))
621 					{
622 						Environment->removeFocus(this);
623 					}
624 
625 					if (Environment->hasFocus(this) &&
626 						VerticalScrollBar->isVisible() &&
627 						VerticalScrollBar->getAbsolutePosition().isPointInside(p) &&
628 						VerticalScrollBar->OnEvent(event))
629 					{
630 						return true;
631 					}
632 
633 					if (Environment->hasFocus(this) &&
634 						HorizontalScrollBar->isVisible() &&
635 						HorizontalScrollBar->getAbsolutePosition().isPointInside(p) &&
636 						HorizontalScrollBar->OnEvent(event))
637 					{
638 						return true;
639 					}
640 
641 					selectNew(event.MouseInput.Y);
642 					return true;
643 
644 				case EMIE_MOUSE_MOVED:
645 					if ( CurrentResizedColumn >= 0 )
646 					{
647 						if ( dragColumnUpdate(event.MouseInput.X) )
648 						{
649 							return true;
650 						}
651 					}
652 					if (Selecting || MoveOverSelect)
653 					{
654 						if (getAbsolutePosition().isPointInside(p))
655 						{
656 							selectNew(event.MouseInput.Y);
657 							return true;
658 						}
659 					}
660 					break;
661 				default:
662 					break;
663 				}
664 			}
665 			break;
666 		default:
667 			break;
668 		}
669 	}
670 
671 	return IGUIElement::OnEvent(event);
672 }
673 
674 
setColumnOrdering(u32 columnIndex,EGUI_COLUMN_ORDERING mode)675 void CGUITable::setColumnOrdering(u32 columnIndex, EGUI_COLUMN_ORDERING mode)
676 {
677 	if ( columnIndex < Columns.size() )
678 		Columns[columnIndex].OrderingMode = mode;
679 }
680 
681 
swapRows(u32 rowIndexA,u32 rowIndexB)682 void CGUITable::swapRows(u32 rowIndexA, u32 rowIndexB)
683 {
684 	if ( rowIndexA >= Rows.size() )
685 		return;
686 
687 	if ( rowIndexB >= Rows.size() )
688 		return;
689 
690 	Row swap = Rows[rowIndexA];
691 	Rows[rowIndexA] = Rows[rowIndexB];
692 	Rows[rowIndexB] = swap;
693 
694 	if ( Selected == s32(rowIndexA) )
695 		Selected = rowIndexB;
696 	else if( Selected == s32(rowIndexB) )
697 		Selected = rowIndexA;
698 
699 }
700 
701 
dragColumnStart(s32 xpos,s32 ypos)702 bool CGUITable::dragColumnStart(s32 xpos, s32 ypos)
703 {
704 	if ( !ResizableColumns )
705 		return false;
706 
707 	if ( ypos > ( AbsoluteRect.UpperLeftCorner.Y + ItemHeight ) )
708 		return false;
709 
710 	const s32 CLICK_AREA = 12;	// to left and right of line which can be dragged
711 	s32 pos = AbsoluteRect.UpperLeftCorner.X+1;
712 
713 	if ( HorizontalScrollBar && HorizontalScrollBar->isVisible() )
714 		pos -= HorizontalScrollBar->getPos();
715 
716 	pos += TotalItemWidth;
717 
718 	// have to search from the right as otherwise lines could no longer be resized when a column width is 0
719 	for ( s32 i = (s32)Columns.size()-1; i >= 0 ; --i )
720 	{
721 		u32 colWidth = Columns[i].Width;
722 
723 		if ( xpos >= (pos - CLICK_AREA) && xpos < ( pos + CLICK_AREA ) )
724 		{
725 			CurrentResizedColumn = i;
726 			ResizeStart = xpos;
727 			return true;
728 		}
729 
730 		pos -= colWidth;
731 	}
732 
733 	return false;
734 }
735 
736 
dragColumnUpdate(s32 xpos)737 bool CGUITable::dragColumnUpdate(s32 xpos)
738 {
739 	if ( !ResizableColumns || CurrentResizedColumn < 0 || CurrentResizedColumn >= s32(Columns.size()) )
740 	{
741 		CurrentResizedColumn = -1;
742 		return false;
743 	}
744 
745 	s32 width = s32(Columns[CurrentResizedColumn].Width) + (xpos-ResizeStart);
746 	if ( width < 0 )
747 		width = 0;
748 	setColumnWidth(CurrentResizedColumn, u32(width));
749 	ResizeStart = xpos;
750 
751 	return false;
752 }
753 
754 
selectColumnHeader(s32 xpos,s32 ypos)755 bool CGUITable::selectColumnHeader(s32 xpos, s32 ypos)
756 {
757 	if ( ypos > ( AbsoluteRect.UpperLeftCorner.Y + ItemHeight ) )
758 		return false;
759 
760 	s32 pos = AbsoluteRect.UpperLeftCorner.X+1;
761 
762 	if ( HorizontalScrollBar && HorizontalScrollBar->isVisible() )
763 		pos -= HorizontalScrollBar->getPos();
764 
765 	for ( u32 i = 0 ; i < Columns.size() ; ++i )
766 	{
767 		u32 colWidth = Columns[i].Width;
768 
769 		if ( xpos >= pos && xpos < ( pos + s32(colWidth) ) )
770 		{
771 			setActiveColumn( i, true );
772 
773 			return true;
774 		}
775 
776 		pos += colWidth;
777 	}
778 
779 	return false;
780 }
781 
782 
orderRows(s32 columnIndex,EGUI_ORDERING_MODE mode)783 void CGUITable::orderRows(s32 columnIndex, EGUI_ORDERING_MODE mode)
784 {
785 	Row swap;
786 
787 	if ( columnIndex == -1 )
788 		columnIndex = getActiveColumn();
789 	if ( columnIndex < 0 )
790 		return;
791 
792 	if ( mode == EGOM_ASCENDING )
793 	{
794 		for ( s32 i = 0 ; i < s32(Rows.size()) - 1 ; ++i )
795 		{
796 			for ( s32 j = 0 ; j < s32(Rows.size()) - i - 1 ; ++j )
797 			{
798 				if ( Rows[j+1].Items[columnIndex].Text < Rows[j].Items[columnIndex].Text )
799 				{
800 					swap = Rows[j];
801 					Rows[j] = Rows[j+1];
802 					Rows[j+1] = swap;
803 
804 					if ( Selected == j )
805 						Selected = j+1;
806 					else if( Selected == j+1 )
807 						Selected = j;
808 				}
809 			}
810 		}
811 	}
812 	else if ( mode == EGOM_DESCENDING )
813 	{
814 		for ( s32 i = 0 ; i < s32(Rows.size()) - 1 ; ++i )
815 		{
816 			for ( s32 j = 0 ; j < s32(Rows.size()) - i - 1 ; ++j )
817 			{
818 				if ( Rows[j].Items[columnIndex].Text < Rows[j+1].Items[columnIndex].Text)
819 				{
820 					swap = Rows[j];
821 					Rows[j] = Rows[j+1];
822 					Rows[j+1] = swap;
823 
824 					if ( Selected == j )
825 						Selected = j+1;
826 					else if( Selected == j+1 )
827 						Selected = j;
828 				}
829 			}
830 		}
831 	}
832 }
833 
834 
selectNew(s32 ypos,bool onlyHover)835 void CGUITable::selectNew(s32 ypos, bool onlyHover)
836 {
837 	IGUISkin* skin = Environment->getSkin();
838 	if (!skin)
839 		return;
840 
841 	s32 oldSelected = Selected;
842 
843 	if ( ypos < ( AbsoluteRect.UpperLeftCorner.Y + ItemHeight ) )
844 		return;
845 
846 	// find new selected item.
847 	if (ItemHeight!=0)
848 		Selected = ((ypos - AbsoluteRect.UpperLeftCorner.Y - ItemHeight - 1) + VerticalScrollBar->getPos()) / ItemHeight;
849 
850 	if (Selected >= (s32)Rows.size())
851 		Selected = Rows.size() - 1;
852 	else if (Selected<0)
853 		Selected = 0;
854 
855 	// post the news
856 	if (Parent && !onlyHover)
857 	{
858 		SEvent event;
859 		event.EventType = EET_GUI_EVENT;
860 		event.GUIEvent.Caller = this;
861 		event.GUIEvent.Element = 0;
862 		event.GUIEvent.EventType = (Selected != oldSelected) ? EGET_TABLE_CHANGED : EGET_TABLE_SELECTED_AGAIN;
863 		Parent->OnEvent(event);
864 	}
865 }
866 
867 
868 //! draws the element and its children
draw()869 void CGUITable::draw()
870 {
871 	if (!IsVisible)
872 		return;
873 
874 	irr::video::IVideoDriver* driver = Environment->getVideoDriver();
875 
876 	IGUISkin* skin = Environment->getSkin();
877 	if (!skin)
878 		return;
879 
880 	IGUIFont* font = skin->getFont();
881 	if (!font)
882 		return;
883 
884 	// CAREFUL: near identical calculations for tableRect and clientClip are also done in checkScrollbars and selectColumnHeader
885 	// Area of table used for drawing without scrollbars
886 	core::rect<s32> tableRect(AbsoluteRect);
887 	tableRect.UpperLeftCorner.X += 1;
888 	tableRect.UpperLeftCorner.Y += 1;
889 	if ( VerticalScrollBar && VerticalScrollBar->isVisible() )
890 		tableRect.LowerRightCorner.X -= skin->getSize(EGDS_SCROLLBAR_SIZE);
891 	if ( HorizontalScrollBar && HorizontalScrollBar->isVisible() )
892 		tableRect.LowerRightCorner.Y -= skin->getSize(EGDS_SCROLLBAR_SIZE);
893 
894 	s32 headerBottom = tableRect.UpperLeftCorner.Y + ItemHeight;
895 
896 	// area of for the items (without header and without scrollbars)
897 	core::rect<s32> clientClip(tableRect);
898 	clientClip.UpperLeftCorner.Y = headerBottom + 1;
899 	clientClip.clipAgainst(AbsoluteClippingRect);
900 
901 	// draw background for whole element
902 	skin->draw3DSunkenPane(this, skin->getColor(EGDC_3D_HIGH_LIGHT), true, DrawBack, AbsoluteRect, &AbsoluteClippingRect);
903 
904 	// scrolledTableClient is the area where the table items would be if it could be drawn completely
905 	core::rect<s32> scrolledTableClient(tableRect);
906 	scrolledTableClient.UpperLeftCorner.Y = headerBottom + 1;
907 	scrolledTableClient.LowerRightCorner.Y = scrolledTableClient.UpperLeftCorner.Y + TotalItemHeight;
908 	scrolledTableClient.LowerRightCorner.X = scrolledTableClient.UpperLeftCorner.X + TotalItemWidth;
909 	if ( VerticalScrollBar && VerticalScrollBar->isVisible() )
910 	{
911 		scrolledTableClient.UpperLeftCorner.Y -= VerticalScrollBar->getPos();
912 		scrolledTableClient.LowerRightCorner.Y -= VerticalScrollBar->getPos();
913 	}
914 	if ( HorizontalScrollBar && HorizontalScrollBar->isVisible() )
915 	{
916 		scrolledTableClient.UpperLeftCorner.X -= HorizontalScrollBar->getPos();
917 		scrolledTableClient.LowerRightCorner.X -= HorizontalScrollBar->getPos();
918 	}
919 
920 	// rowRect is around the scrolled row
921 	core::rect<s32> rowRect(scrolledTableClient);
922 	rowRect.LowerRightCorner.Y = rowRect.UpperLeftCorner.Y + ItemHeight;
923 
924 	u32 pos;
925 	for ( u32 i = 0 ; i < Rows.size() ; ++i )
926 	{
927 		if (rowRect.LowerRightCorner.Y >= AbsoluteRect.UpperLeftCorner.Y &&
928 			rowRect.UpperLeftCorner.Y <= AbsoluteRect.LowerRightCorner.Y)
929 		{
930 			// draw row seperator
931 			if ( DrawFlags & EGTDF_ROWS )
932 			{
933 				core::rect<s32> lineRect(rowRect);
934 				lineRect.UpperLeftCorner.Y = lineRect.LowerRightCorner.Y - 1;
935 				driver->draw2DRectangle(skin->getColor(EGDC_3D_SHADOW), lineRect, &clientClip);
936 			}
937 
938 			core::rect<s32> textRect(rowRect);
939 			pos = rowRect.UpperLeftCorner.X;
940 
941 			// draw selected row background highlighted
942 			if ((s32)i == Selected && DrawFlags & EGTDF_ACTIVE_ROW )
943 				driver->draw2DRectangle(skin->getColor(EGDC_HIGH_LIGHT), rowRect, &clientClip);
944 
945 			for ( u32 j = 0 ; j < Columns.size() ; ++j )
946 			{
947 				textRect.UpperLeftCorner.X = pos + CellWidthPadding;
948 				textRect.LowerRightCorner.X = pos + Columns[j].Width - CellWidthPadding;
949 
950 				// draw item text
951 				if ((s32)i == Selected)
952 				{
953 					font->draw(Rows[i].Items[j].BrokenText.c_str(), textRect, skin->getColor(isEnabled() ? EGDC_HIGH_LIGHT_TEXT : EGDC_GRAY_TEXT), false, true, &clientClip);
954 				}
955 				else
956 				{
957 					if ( !Rows[i].Items[j].IsOverrideColor )	// skin-colors can change
958 						Rows[i].Items[j].Color = skin->getColor(EGDC_BUTTON_TEXT);
959 					font->draw(Rows[i].Items[j].BrokenText.c_str(), textRect, isEnabled() ? Rows[i].Items[j].Color : skin->getColor(EGDC_GRAY_TEXT), false, true, &clientClip);
960 				}
961 
962 				pos += Columns[j].Width;
963 			}
964 		}
965 
966 		rowRect.UpperLeftCorner.Y += ItemHeight;
967 		rowRect.LowerRightCorner.Y += ItemHeight;
968 	}
969 
970 	core::rect<s32> columnSeparator(clientClip);
971 	pos = scrolledTableClient.UpperLeftCorner.X;
972 
973 	core::rect<s32> tableClip(tableRect);
974 	tableClip.clipAgainst(AbsoluteClippingRect);
975 
976 	for (u32 i = 0 ; i < Columns.size() ; ++i )
977 	{
978 		const wchar_t* text = Columns[i].Name.c_str();
979 		u32 colWidth = Columns[i].Width;
980 
981 		//core::dimension2d<s32 > dim = font->getDimension(text);
982 
983 		core::rect<s32> columnrect(pos, tableRect.UpperLeftCorner.Y, pos + colWidth, headerBottom);
984 
985 		// draw column background
986 		skin->draw3DButtonPaneStandard(this, columnrect, &tableClip);
987 
988 		// draw column seperator
989 		if ( DrawFlags & EGTDF_COLUMNS )
990 		{
991 			columnSeparator.UpperLeftCorner.X = pos;
992 			columnSeparator.LowerRightCorner.X = pos + 1;
993 			driver->draw2DRectangle(skin->getColor(EGDC_3D_SHADOW), columnSeparator, &tableClip);
994 		}
995 
996 		// draw header column text
997 		columnrect.UpperLeftCorner.X += CellWidthPadding;
998 		font->draw(text, columnrect, skin->getColor( isEnabled() ? EGDC_BUTTON_TEXT : EGDC_GRAY_TEXT), false, true, &tableClip);
999 
1000 		// draw icon for active column tab
1001 		if ( (s32)i == ActiveTab )
1002 		{
1003 			if ( CurrentOrdering == EGOM_ASCENDING )
1004 			{
1005 				columnrect.UpperLeftCorner.X = columnrect.LowerRightCorner.X - CellWidthPadding - ARROW_PAD / 2 + 2;
1006 				columnrect.UpperLeftCorner.Y += 7;
1007 				skin->drawIcon(this,EGDI_CURSOR_UP,columnrect.UpperLeftCorner,0,0,false,&tableClip);
1008 			}
1009 			else
1010 			{
1011 				columnrect.UpperLeftCorner.X = columnrect.LowerRightCorner.X - CellWidthPadding - ARROW_PAD / 2 + 2;
1012 				columnrect.UpperLeftCorner.Y += 7;
1013 				skin->drawIcon(this,EGDI_CURSOR_DOWN,columnrect.UpperLeftCorner,0,0,false,&tableClip);
1014 			}
1015 		}
1016 
1017 		pos += colWidth;
1018 	}
1019 
1020 	// fill up header background up to the right side
1021 	core::rect<s32> columnrect(pos, tableRect.UpperLeftCorner.Y, tableRect.LowerRightCorner.X , headerBottom);
1022 	skin->draw3DButtonPaneStandard(this, columnrect, &tableClip);
1023 
1024 	IGUIElement::draw();
1025 }
1026 
1027 
breakText(const core::stringw & text,core::stringw & brokenText,u32 cellWidth)1028 void CGUITable::breakText(const core::stringw& text, core::stringw& brokenText, u32 cellWidth)
1029 {
1030 	IGUISkin* skin = Environment->getSkin();
1031 
1032 	if (!skin)
1033 		return;
1034 
1035 	if (!Font)
1036 		return;
1037 
1038 	IGUIFont* font = skin->getFont();
1039 	if (!font)
1040 		return;
1041 
1042 	core::stringw line, lineDots;
1043 	wchar_t c[2];
1044 	c[1] = L'\0';
1045 
1046 	const u32 maxLength = cellWidth - (CellWidthPadding * 2);
1047 	const u32 maxLengthDots = cellWidth - (CellWidthPadding * 2) - font->getDimension(L"...").Width;
1048 	const u32 size = text.size();
1049 	u32 pos = 0;
1050 
1051 	u32 i;
1052 
1053 	for (i=0; i<size; ++i)
1054 	{
1055 		c[0] = text[i];
1056 
1057 		if (c[0] == L'\n')
1058 			break;
1059 
1060 		pos += font->getDimension(c).Width;
1061 		if ( pos > maxLength )
1062 			break;
1063 
1064 		if ( font->getDimension( (line + c).c_str() ).Width > maxLengthDots )
1065 			lineDots = line;
1066 
1067 		line += c[0];
1068 	}
1069 
1070 	if ( i < size )
1071 		brokenText = lineDots + L"...";
1072 	else
1073 		brokenText = line;
1074 }
1075 
1076 
1077 //! Set some flags influencing the layout of the table
setDrawFlags(s32 flags)1078 void CGUITable::setDrawFlags(s32 flags)
1079 {
1080 	DrawFlags = flags;
1081 }
1082 
1083 
1084 //! Get the flags which influence the layout of the table
getDrawFlags() const1085 s32 CGUITable::getDrawFlags() const
1086 {
1087 	return DrawFlags;
1088 }
1089 
1090 
1091 //! Writes attributes of the element.
serializeAttributes(io::IAttributes * out,io::SAttributeReadWriteOptions * options) const1092 void CGUITable::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const
1093 {
1094 	IGUITable::serializeAttributes(out, options);
1095 
1096 	out->addInt("ColumnCount", Columns.size());
1097 	u32 i;
1098 	for (i=0;i<Columns.size(); ++i)
1099 	{
1100 		core::stringc label;
1101 
1102 		label = "Column"; label += i; label += "name";
1103 		out->addString(label.c_str(), Columns[i].Name.c_str() );
1104 		label = "Column"; label += i; label += "width";
1105 		out->addInt(label.c_str(), Columns[i].Width );
1106 		label = "Column"; label += i; label += "OrderingMode";
1107 		out->addEnum(label.c_str(), Columns[i].OrderingMode, GUIColumnOrderingNames);
1108 	}
1109 
1110 	out->addInt("RowCount", Rows.size());
1111 	for (i=0;i<Rows.size(); ++i)
1112 	{
1113 		core::stringc label;
1114 
1115 		// Height currently not used and could be recalculated anyway
1116 		//label = "Row"; label += i; label += "height";
1117 		//out->addInt(label.c_str(), Rows[i].Height );
1118 
1119 		//label = "Row"; label += i; label += "ItemCount";
1120 		//out->addInt(label.c_str(), Rows[i].Items.size());
1121 		u32 c;
1122 		for ( c=0; c < Rows[i].Items.size(); ++c )
1123 		{
1124 			label = "Row"; label += i; label += "cell"; label += c; label += "text";
1125 			out->addString(label.c_str(), Rows[i].Items[c].Text.c_str() );
1126 			// core::stringw BrokenText;	// can be recalculated
1127 			label = "Row"; label += i; label += "cell"; label += c; label += "color";
1128 			out->addColor(label.c_str(), Rows[i].Items[c].Color );
1129 			label = "Row"; label += i; label += "cell"; label += c; label += "IsOverrideColor";
1130 			out->addColor(label.c_str(), Rows[i].Items[c].IsOverrideColor );
1131 			// void *data;	// can't be serialized
1132 		}
1133 	}
1134 
1135 	// s32 ItemHeight;	// can be calculated
1136 	// TotalItemHeight	// calculated
1137 	// TotalItemWidth	// calculated
1138 	// gui::IGUIFont* Font; // font is just the current font from environment
1139 	// gui::IGUIScrollBar* VerticalScrollBar;		// not serialized
1140 	// gui::IGUIScrollBar* HorizontalScrollBar;		// not serialized
1141 
1142 	out->addBool ("Clip", Clip);
1143 	out->addBool ("DrawBack", DrawBack);
1144 	out->addBool ("MoveOverSelect", MoveOverSelect);
1145 
1146 	// s32  CurrentResizedColumn;	// runtime info - depends on user action
1147 	out->addBool ("ResizableColumns", ResizableColumns);
1148 
1149 	// s32 Selected;	// runtime info - depends on user action
1150 	out->addInt("CellWidthPadding", CellWidthPadding );
1151 	out->addInt("CellHeightPadding", CellHeightPadding );
1152 	// s32 ActiveTab;	// runtime info - depends on user action
1153 	// bool Selecting;	// runtime info - depends on user action
1154 	out->addEnum("CurrentOrdering", CurrentOrdering, GUIOrderingModeNames);
1155 	out->addInt("DrawFlags", DrawFlags);
1156 }
1157 
1158 
1159 //! Reads attributes of the element
deserializeAttributes(io::IAttributes * in,io::SAttributeReadWriteOptions * options)1160 void CGUITable::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options)
1161 {
1162 	IGUITable::deserializeAttributes(in, options);
1163 
1164 	Columns.clear();
1165 	u32 columnCount = in->getAttributeAsInt("ColumnCount");
1166 	u32 i;
1167 	for (i=0;i<columnCount; ++i)
1168 	{
1169 		core::stringc label;
1170 		Column column;
1171 
1172 		label = "Column"; label += i; label += "name";
1173 		column.Name = core::stringw(in->getAttributeAsString(label.c_str()).c_str());
1174 		label = "Column"; label += i; label += "width";
1175 		column.Width = in->getAttributeAsInt(label.c_str());
1176 		label = "Column"; label += i; label += "OrderingMode";
1177 
1178 		column.OrderingMode = EGCO_NONE;
1179 		s32 co = in->getAttributeAsEnumeration(label.c_str(), GUIColumnOrderingNames);
1180 		if (co > 0)
1181 			column.OrderingMode = EGUI_COLUMN_ORDERING(co);
1182 
1183 		Columns.push_back(column);
1184 	}
1185 
1186 	Rows.clear();
1187 	u32 rowCount = in->getAttributeAsInt("RowCount");
1188 	for (i=0; i<rowCount; ++i)
1189 	{
1190 		core::stringc label;
1191 
1192 		Row row;
1193 
1194 		// Height currently not used and could be recalculated anyway
1195 		//label = "Row"; label += i; label += "height";
1196 		//row.Height = in->getAttributeAsInt(label.c_str() );
1197 
1198 		Rows.push_back(row);
1199 
1200 		//label = "Row"; label += i; label += "ItemCount";
1201 		//u32 itemCount = in->getAttributeAsInt(label.c_str());
1202 		u32 c;
1203 		for ( c=0; c < columnCount; ++c )
1204 		{
1205 			Cell cell;
1206 
1207 			label = "Row"; label += i; label += "cell"; label += c; label += "text";
1208 			cell.Text = core::stringw(in->getAttributeAsString(label.c_str()).c_str());
1209 			breakText( cell.Text, cell.BrokenText, Columns[c].Width );
1210 			label = "Row"; label += i; label += "cell"; label += c; label += "color";
1211 			cell.Color = in->getAttributeAsColor(label.c_str());
1212 			label = "Row"; label += i; label += "cell"; label += c; label += "IsOverrideColor";
1213 			cell.IsOverrideColor = in->getAttributeAsBool(label.c_str());
1214 
1215 			cell.Data = NULL;
1216 
1217 			Rows[Rows.size()-1].Items.push_back(cell);
1218 		}
1219 	}
1220 
1221 	ItemHeight = 0;		// calculated
1222 	TotalItemHeight = 0;	// calculated
1223 	TotalItemWidth = 0;	// calculated
1224 
1225 	// force font recalculation
1226 	if ( Font )
1227 	{
1228 		Font->drop();
1229 		Font = 0;
1230 	}
1231 
1232 	Clip = in->getAttributeAsBool("Clip");
1233 	DrawBack = in->getAttributeAsBool("DrawBack");
1234 	MoveOverSelect = in->getAttributeAsBool("MoveOverSelect");
1235 
1236 	CurrentResizedColumn = -1;
1237 	ResizeStart = 0;
1238 	ResizableColumns = in->getAttributeAsBool("ResizableColumns");
1239 
1240 	Selected = -1;
1241 	CellWidthPadding = in->getAttributeAsInt("CellWidthPadding");
1242 	CellHeightPadding = in->getAttributeAsInt("CellHeightPadding");
1243 	ActiveTab = -1;
1244 	Selecting = false;
1245 
1246 	CurrentOrdering = (EGUI_ORDERING_MODE) in->getAttributeAsEnumeration("CurrentOrdering", GUIOrderingModeNames);
1247 	DrawFlags = in->getAttributeAsInt("DrawFlags");
1248 
1249 	refreshControls();
1250 }
1251 
1252 } // end namespace gui
1253 } // end namespace irr
1254 
1255 #endif
1256 
1257