Don't crash while in the lobby when receiving an error IQ stanza without an error...
[0ad.git] / source / gui / COList.cpp
blob6f31fe402f92d7a4c92032eb04f72e6979b5ba74
1 /* Copyright (C) 2017 Wildfire Games.
2 * This file is part of 0 A.D.
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
18 #include "precompiled.h"
20 #include "COList.h"
22 #include "i18n/L10n.h"
23 #include "ps/CLogger.h"
24 #include "soundmanager/ISoundManager.h"
26 COList::COList() : CList()
28 AddSetting(GUIST_CGUISpriteInstance, "sprite_heading");
29 AddSetting(GUIST_float, "heading_height");
30 AddSetting(GUIST_bool, "sortable"); // The actual sorting is done in JS for more versatility
31 AddSetting(GUIST_CStr, "selected_column");
32 AddSetting(GUIST_int, "selected_column_order");
33 AddSetting(GUIST_CGUISpriteInstance, "sprite_asc"); // Show the order of sorting
34 AddSetting(GUIST_CGUISpriteInstance, "sprite_desc");
35 AddSetting(GUIST_CGUISpriteInstance, "sprite_not_sorted");
38 void COList::SetupText()
40 if (!GetGUI())
41 return;
43 CGUIList* pList;
44 GUI<CGUIList>::GetSettingPointer(this, "list", pList);
46 m_ItemsYPositions.resize(pList->m_Items.size() + 1);
48 // Delete all generated texts. Some could probably be saved,
49 // but this is easier, and this function will never be called
50 // continuously, or even often, so it'll probably be okay.
51 for (SGUIText* const& t : m_GeneratedTexts)
52 delete t;
53 m_GeneratedTexts.clear();
55 CStrW font;
56 if (GUI<CStrW>::GetSetting(this, "font", font) != PSRETURN_OK || font.empty())
57 // Use the default if none is specified
58 // TODO Gee: (2004-08-14) Don't define standard like this. Do it with the default style.
59 font = L"default";
61 bool scrollbar;
62 GUI<bool>::GetSetting(this, "scrollbar", scrollbar);
64 float width = GetListRect().GetWidth();
65 // remove scrollbar if applicable
66 if (scrollbar && GetScrollBar(0).GetStyle())
67 width -= GetScrollBar(0).GetStyle()->m_Width;
69 m_TotalAvailableColumnWidth = width;
71 float buffer_zone = 0.f;
72 GUI<float>::GetSetting(this, "buffer_zone", buffer_zone);
74 for (COListColumn column : m_Columns)
76 SGUIText* text = new SGUIText();
77 CGUIString gui_string;
78 gui_string.SetValue(column.m_Heading);
79 *text = GetGUI()->GenerateText(gui_string, font, width, buffer_zone, this);
80 AddText(text);
83 // Generate texts
84 float buffered_y = 0.f;
86 for (size_t i = 0; i < pList->m_Items.size(); ++i)
88 m_ItemsYPositions[i] = buffered_y;
89 float shift = 0.0f;
90 for (size_t c = 0; c < m_Columns.size(); ++c)
92 CGUIList* pList_c;
93 GUI<CGUIList>::GetSettingPointer(this, "list_" + m_Columns[c].m_Id, pList_c);
94 SGUIText* text = new SGUIText();
95 if (!pList_c->m_Items[i].GetOriginalString().empty())
96 *text = GetGUI()->GenerateText(pList_c->m_Items[i], font, width, buffer_zone, this);
97 else
99 // Minimum height of a space character of the current font size
100 CGUIString align_string;
101 align_string.SetValue(L" ");
102 *text = GetGUI()->GenerateText(align_string, font, width, buffer_zone, this);
104 shift = std::max(shift, text->m_Size.cy);
105 AddText(text);
107 buffered_y += shift;
110 m_ItemsYPositions[pList->m_Items.size()] = buffered_y;
112 if (scrollbar)
114 CRect rect = GetListRect();
115 GetScrollBar(0).SetScrollRange(m_ItemsYPositions.back());
116 GetScrollBar(0).SetScrollSpace(rect.GetHeight());
118 GetScrollBar(0).SetX(rect.right);
119 GetScrollBar(0).SetY(rect.top);
120 GetScrollBar(0).SetZ(GetBufferedZ());
121 GetScrollBar(0).SetLength(rect.bottom - rect.top);
125 CRect COList::GetListRect() const
127 float headingHeight;
128 GUI<float>::GetSetting(this, "heading_height", headingHeight);
129 return m_CachedActualSize + CRect(0, headingHeight, 0, 0);
132 void COList::HandleMessage(SGUIMessage& Message)
134 CList::HandleMessage(Message);
136 switch (Message.type)
138 // If somebody clicks on the column heading
139 case GUIM_MOUSE_PRESS_LEFT:
141 bool sortable;
142 GUI<bool>::GetSetting(this, "sortable", sortable);
143 if (!sortable)
144 return;
146 CPos mouse = GetMousePos();
147 if (!m_CachedActualSize.PointInside(mouse))
148 return;
150 CStr selectedColumn;
151 GUI<CStr>::GetSetting(this, "selected_column", selectedColumn);
152 int selectedColumnOrder;
153 GUI<int>::GetSetting(this, "selected_column_order", selectedColumnOrder);
154 float headingHeight;
155 GUI<float>::GetSetting(this, "heading_height", headingHeight);
157 float xpos = 0;
158 for (COListColumn column : m_Columns)
160 bool hidden = false;
161 GUI<bool>::GetSetting(this, "hidden_" + column.m_Id, hidden);
162 if (hidden)
163 continue;
165 float width = column.m_Width;
166 // Check if it's a decimal value, and if so, assume relative positioning.
167 if (column.m_Width < 1 && column.m_Width > 0)
168 width *= m_TotalAvailableColumnWidth;
169 CPos leftTopCorner = m_CachedActualSize.TopLeft() + CPos(xpos, 0);
170 if (mouse.x >= leftTopCorner.x &&
171 mouse.x < leftTopCorner.x + width &&
172 mouse.y < leftTopCorner.y + headingHeight)
174 if (column.m_Id != selectedColumn)
176 selectedColumnOrder = 1;
177 selectedColumn = column.m_Id;
179 else
180 selectedColumnOrder = -selectedColumnOrder;
181 GUI<CStr>::SetSetting(this, "selected_column", column.m_Id);
182 GUI<int>::SetSetting(this, "selected_column_order", selectedColumnOrder);
183 ScriptEvent("selectioncolumnchange");
185 CStrW soundPath;
186 if (g_SoundManager && GUI<CStrW>::GetSetting(this, "sound_selected", soundPath) == PSRETURN_OK && !soundPath.empty())
187 g_SoundManager->PlayAsUI(soundPath.c_str(), false);
189 return;
191 xpos += width;
193 return;
195 default:
196 return;
200 bool COList::HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile)
202 #define ELMT(x) int elmt_##x = pFile->GetElementID(#x)
203 #define ATTR(x) int attr_##x = pFile->GetAttributeID(#x)
204 ELMT(item);
205 ELMT(column);
206 ELMT(translatableAttribute);
207 ATTR(id);
208 ATTR(context);
210 if (child.GetNodeName() == elmt_item)
212 AddItem(child.GetText().FromUTF8(), child.GetText().FromUTF8());
213 return true;
215 else if (child.GetNodeName() == elmt_column)
217 COListColumn column;
218 bool hidden = false;
220 for (XMBAttribute attr : child.GetAttributes())
222 CStr attr_name(pFile->GetAttributeString(attr.Name));
223 CStr attr_value(attr.Value);
225 if (attr_name == "color")
227 CColor color;
228 if (!GUI<CColor>::ParseString(attr_value.FromUTF8(), color))
229 LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str());
230 else
231 column.m_TextColor = color;
233 else if (attr_name == "id")
235 column.m_Id = attr_value;
237 else if (attr_name == "hidden")
239 if (!GUI<bool>::ParseString(attr_value.FromUTF8(), hidden))
240 LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str());
242 else if (attr_name == "width")
244 float width;
245 if (!GUI<float>::ParseString(attr_value.FromUTF8(), width))
246 LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str());
247 else
249 // Check if it's a relative value, and save as decimal if so.
250 if (attr_value.find("%") != std::string::npos)
251 width = width / 100.f;
252 column.m_Width = width;
255 else if (attr_name == "heading")
257 column.m_Heading = attr_value.FromUTF8();
261 for (XMBElement grandchild : child.GetChildNodes())
263 if (grandchild.GetNodeName() != elmt_translatableAttribute)
264 continue;
266 CStr attributeName(grandchild.GetAttributes().GetNamedItem(attr_id));
267 // only the heading is translatable for list column
268 if (attributeName.empty() || attributeName != "heading")
270 LOGERROR("GUI: translatable attribute in olist column that isn't a heading. (object: %s)", this->GetPresentableName().c_str());
271 continue;
274 CStr value(grandchild.GetText());
275 if (value.empty())
276 continue;
278 CStr context(grandchild.GetAttributes().GetNamedItem(attr_context)); // Read the context if any.
279 if (!context.empty())
281 CStr translatedValue(g_L10n.TranslateWithContext(context, value));
282 column.m_Heading = translatedValue.FromUTF8();
284 else
286 CStr translatedValue(g_L10n.Translate(value));
287 column.m_Heading = translatedValue.FromUTF8();
291 m_Columns.push_back(column);
293 AddSetting(GUIST_CGUIList, "list_" + column.m_Id);
294 AddSetting(GUIST_bool, "hidden_" + column.m_Id);
295 GUI<bool>::SetSetting(this, "hidden_" + column.m_Id, hidden);
297 SetupText();
299 return true;
301 else
303 return false;
307 void COList::DrawList(const int& selected, const CStr& _sprite, const CStr& _sprite_selected, const CStr& _textcolor)
309 float bz = GetBufferedZ();
311 bool scrollbar;
312 GUI<bool>::GetSetting(this, "scrollbar", scrollbar);
314 if (scrollbar)
315 IGUIScrollBarOwner::Draw();
317 if (!GetGUI())
318 return;
320 CRect rect = GetListRect();
322 CGUISpriteInstance* sprite = NULL;
323 CGUISpriteInstance* sprite_selectarea = NULL;
324 int cell_id;
325 GUI<CGUISpriteInstance>::GetSettingPointer(this, _sprite, sprite);
326 GUI<CGUISpriteInstance>::GetSettingPointer(this, _sprite_selected, sprite_selectarea);
327 GUI<int>::GetSetting(this, "cell_id", cell_id);
329 CGUIList* pList;
330 GUI<CGUIList>::GetSettingPointer(this, "list", pList);
332 GetGUI()->DrawSprite(*sprite, cell_id, bz, rect);
334 float scroll = 0.f;
335 if (scrollbar)
336 scroll = GetScrollBar(0).GetPos();
338 // Draw item selection
339 if (selected != -1)
341 ENSURE(selected >= 0 && selected+1 < (int)m_ItemsYPositions.size());
343 // Get rectangle of selection:
344 CRect rect_sel(rect.left, rect.top + m_ItemsYPositions[selected] - scroll,
345 rect.right, rect.top + m_ItemsYPositions[selected+1] - scroll);
347 if (rect_sel.top <= rect.bottom &&
348 rect_sel.bottom >= rect.top)
350 if (rect_sel.bottom > rect.bottom)
351 rect_sel.bottom = rect.bottom;
352 if (rect_sel.top < rect.top)
353 rect_sel.top = rect.top;
355 if (scrollbar)
357 // Remove any overlapping area of the scrollbar.
358 if (rect_sel.right > GetScrollBar(0).GetOuterRect().left &&
359 rect_sel.right <= GetScrollBar(0).GetOuterRect().right)
360 rect_sel.right = GetScrollBar(0).GetOuterRect().left;
362 if (rect_sel.left >= GetScrollBar(0).GetOuterRect().left &&
363 rect_sel.left < GetScrollBar(0).GetOuterRect().right)
364 rect_sel.left = GetScrollBar(0).GetOuterRect().right;
367 // Draw item selection
368 GetGUI()->DrawSprite(*sprite_selectarea, cell_id, bz+0.05f, rect_sel);
372 float headingHeight;
373 GUI<float>::GetSetting(this, "heading_height", headingHeight);
375 // Draw line above column header
376 CGUISpriteInstance* sprite_heading = NULL;
377 GUI<CGUISpriteInstance>::GetSettingPointer(this, "sprite_heading", sprite_heading);
378 CRect rect_head(m_CachedActualSize.left, m_CachedActualSize.top, m_CachedActualSize.right,
379 m_CachedActualSize.top + headingHeight);
380 GetGUI()->DrawSprite(*sprite_heading, cell_id, bz, rect_head);
382 // Draw column headers
383 bool sortable;
384 GUI<bool>::GetSetting(this, "sortable", sortable);
386 CStr selectedColumn;
387 GUI<CStr>::GetSetting(this, "selected_column", selectedColumn);
389 int selectedColumnOrder;
390 GUI<int>::GetSetting(this, "selected_column_order", selectedColumnOrder);
392 CColor color;
393 GUI<CColor>::GetSetting(this, _textcolor, color);
395 float xpos = 0;
396 for (size_t col = 0; col < m_Columns.size(); ++col)
398 bool hidden = false;
399 GUI<bool>::GetSetting(this, "hidden_" + m_Columns[col].m_Id, hidden);
400 if (hidden)
401 continue;
403 // Check if it's a decimal value, and if so, assume relative positioning.
404 float width = m_Columns[col].m_Width;
405 if (m_Columns[col].m_Width < 1 && m_Columns[col].m_Width > 0)
406 width *= m_TotalAvailableColumnWidth;
408 CPos leftTopCorner = m_CachedActualSize.TopLeft() + CPos(xpos, 0);
410 // Draw sort arrows in colum header
411 if (sortable)
413 CGUISpriteInstance* sprite;
414 if (selectedColumn == m_Columns[col].m_Id)
416 if (selectedColumnOrder == 0)
417 LOGERROR("selected_column_order must not be 0");
419 if (selectedColumnOrder != -1)
420 GUI<CGUISpriteInstance>::GetSettingPointer(this, "sprite_asc", sprite);
421 else
422 GUI<CGUISpriteInstance>::GetSettingPointer(this, "sprite_desc", sprite);
424 else
425 GUI<CGUISpriteInstance>::GetSettingPointer(this, "sprite_not_sorted", sprite);
427 GetGUI()->DrawSprite(*sprite, cell_id, bz + 0.1f, CRect(leftTopCorner + CPos(width - 16, 0), leftTopCorner + CPos(width, 16)));
430 // Draw column header text
431 DrawText(col, color, leftTopCorner + CPos(0, 4), bz + 0.1f, rect_head);
432 xpos += width;
435 // Draw list items for each column
436 const size_t objectsCount = m_Columns.size();
437 for (size_t i = 0; i < pList->m_Items.size(); ++i)
439 if (m_ItemsYPositions[i+1] - scroll < 0 ||
440 m_ItemsYPositions[i] - scroll > rect.GetHeight())
441 continue;
443 const float rowHeight = m_ItemsYPositions[i+1] - m_ItemsYPositions[i];
445 // Clipping area (we'll have to substract the scrollbar)
446 CRect cliparea = GetListRect();
448 if (scrollbar)
450 if (cliparea.right > GetScrollBar(0).GetOuterRect().left &&
451 cliparea.right <= GetScrollBar(0).GetOuterRect().right)
452 cliparea.right = GetScrollBar(0).GetOuterRect().left;
454 if (cliparea.left >= GetScrollBar(0).GetOuterRect().left &&
455 cliparea.left < GetScrollBar(0).GetOuterRect().right)
456 cliparea.left = GetScrollBar(0).GetOuterRect().right;
459 // Draw all items for that column
460 xpos = 0;
461 for (size_t col = 0; col < objectsCount; ++col)
463 bool hidden = false;
464 GUI<bool>::GetSetting(this, "hidden_" + m_Columns[col].m_Id, hidden);
465 if (hidden)
466 continue;
468 // Determine text position and width
469 const CPos textPos = rect.TopLeft() + CPos(xpos, -scroll + m_ItemsYPositions[i]);
471 float width = m_Columns[col].m_Width;
472 // Check if it's a decimal value, and if so, assume relative positioning.
473 if (m_Columns[col].m_Width < 1 && m_Columns[col].m_Width > 0)
474 width *= m_TotalAvailableColumnWidth;
476 // Clip text to the column (to prevent drawing text into the neighboring column)
477 CRect cliparea2 = cliparea;
478 cliparea2.right = std::min(cliparea2.right, textPos.x + width);
479 cliparea2.bottom = std::min(cliparea2.bottom, textPos.y + rowHeight);
481 // Draw list item
482 DrawText(objectsCount * (i +/*Heading*/1) + col, m_Columns[col].m_TextColor, textPos, bz + 0.1f, cliparea2);
483 xpos += width;