0ad/source/gui/COList.cpp
elexis 8f1b37b216 Major ordered list GUI cleanup. Patch by Imarok, fixes #3905.
Remove m_SelectedDef, m_SelectedColumnOrder and m_HeadingHeight. Use
GetSetting and SetSetting instead. Thus
implement selecting a column / column order sprite from JS correctly.
Since the actual list sorting is done in JS, only the column header
sprites were inaccurate.

Remove "selected_def" since that is redundant with "selected_column".
Merge "selected_column" with "default_column" (and
"selected_column_order" with "default_column_order") since
all properties provided in XML pages are defaults and to remove the
copying on init.

Rename "def" to "column" and "ObjectDef" to "COListColumn".
Replace 30px header height hardcoded in the C++ with an option set by
the style.
Remove the unneeded requirement to specify a "list_name".
Thus rename "list_name" to "list_month" in the replay menu.
Remove unneeded "list_" prefix and substr(5) in column checks.
Use ranged loops.
Fix typo "Avalible".
Add an error message if the GUI style doesn't specify the selected
column order.

This was SVN commit r18652.
2016-08-31 19:29:48 +00:00

449 lines
13 KiB
C++

/* Copyright (C) 2016 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "COList.h"
#include "i18n/L10n.h"
#include "ps/CLogger.h"
#include "soundmanager/ISoundManager.h"
COList::COList() : CList()
{
AddSetting(GUIST_CGUISpriteInstance, "sprite_heading");
AddSetting(GUIST_float, "heading_height");
AddSetting(GUIST_bool, "sortable"); // The actual sorting is done in JS for more versatility
AddSetting(GUIST_CStr, "selected_column");
AddSetting(GUIST_int, "selected_column_order");
AddSetting(GUIST_CGUISpriteInstance, "sprite_asc"); // Show the order of sorting
AddSetting(GUIST_CGUISpriteInstance, "sprite_desc");
AddSetting(GUIST_CGUISpriteInstance, "sprite_not_sorted");
}
void COList::SetupText()
{
if (!GetGUI())
return;
CGUIList* pList;
GUI<CGUIList>::GetSettingPointer(this, "list", pList);
m_ItemsYPositions.resize(pList->m_Items.size() + 1);
// Delete all generated texts. Some could probably be saved,
// but this is easier, and this function will never be called
// continuously, or even often, so it'll probably be okay.
for (SGUIText* const& t : m_GeneratedTexts)
delete t;
m_GeneratedTexts.clear();
CStrW font;
if (GUI<CStrW>::GetSetting(this, "font", font) != PSRETURN_OK || font.empty())
// Use the default if none is specified
// TODO Gee: (2004-08-14) Don't define standard like this. Do it with the default style.
font = L"default";
bool scrollbar;
GUI<bool>::GetSetting(this, "scrollbar", scrollbar);
float width = GetListRect().GetWidth();
// remove scrollbar if applicable
if (scrollbar && GetScrollBar(0).GetStyle())
width -= GetScrollBar(0).GetStyle()->m_Width;
m_TotalAvailableColumnWidth = width;
float buffer_zone = 0.f;
GUI<float>::GetSetting(this, "buffer_zone", buffer_zone);
for (COListColumn column : m_Columns)
{
SGUIText* text = new SGUIText();
CGUIString gui_string;
gui_string.SetValue(column.m_Heading);
*text = GetGUI()->GenerateText(gui_string, font, width, buffer_zone, this);
AddText(text);
}
// Generate texts
float buffered_y = 0.f;
for (size_t i = 0; i < pList->m_Items.size(); ++i)
{
m_ItemsYPositions[i] = buffered_y;
for (size_t c = 0; c < m_Columns.size(); ++c)
{
CGUIList* pList_c;
GUI<CGUIList>::GetSettingPointer(this, "list_" + m_Columns[c].m_Id, pList_c);
SGUIText* text = new SGUIText();
*text = GetGUI()->GenerateText(pList_c->m_Items[i], font, width, buffer_zone, this);
if (c == 0)
buffered_y += text->m_Size.cy;
AddText(text);
}
}
m_ItemsYPositions[pList->m_Items.size()] = buffered_y;
if (scrollbar)
{
CRect rect = GetListRect();
GetScrollBar(0).SetScrollRange(m_ItemsYPositions.back());
GetScrollBar(0).SetScrollSpace(rect.GetHeight());
GetScrollBar(0).SetX(rect.right);
GetScrollBar(0).SetY(rect.top);
GetScrollBar(0).SetZ(GetBufferedZ());
GetScrollBar(0).SetLength(rect.bottom - rect.top);
}
}
CRect COList::GetListRect() const
{
float headingHeight;
GUI<float>::GetSetting(this, "heading_height", headingHeight);
return m_CachedActualSize + CRect(0, headingHeight, 0, 0);
}
void COList::HandleMessage(SGUIMessage& Message)
{
CList::HandleMessage(Message);
switch (Message.type)
{
// If somebody clicks on the column heading
case GUIM_MOUSE_PRESS_LEFT:
{
bool sortable;
GUI<bool>::GetSetting(this, "sortable", sortable);
if (!sortable)
return;
CPos mouse = GetMousePos();
if (!m_CachedActualSize.PointInside(mouse))
return;
CStr selectedColumn;
GUI<CStr>::GetSetting(this, "selected_column", selectedColumn);
int selectedColumnOrder;
GUI<int>::GetSetting(this, "selected_column_order", selectedColumnOrder);
float headingHeight;
GUI<float>::GetSetting(this, "heading_height", headingHeight);
float xpos = 0;
for (COListColumn column : m_Columns)
{
float width = column.m_Width;
// Check if it's a decimal value, and if so, assume relative positioning.
if (column.m_Width < 1 && column.m_Width > 0)
width *= m_TotalAvailableColumnWidth;
CPos leftTopCorner = m_CachedActualSize.TopLeft() + CPos(xpos, 0);
if (mouse.x >= leftTopCorner.x &&
mouse.x < leftTopCorner.x + width &&
mouse.y < leftTopCorner.y + headingHeight)
{
if (column.m_Id != selectedColumn)
{
selectedColumnOrder = 1;
selectedColumn = column.m_Id;
}
else
selectedColumnOrder = -selectedColumnOrder;
GUI<CStr>::SetSetting(this, "selected_column", column.m_Id);
GUI<int>::SetSetting(this, "selected_column_order", selectedColumnOrder);
ScriptEvent("selectioncolumnchange");
CStrW soundPath;
if (g_SoundManager && GUI<CStrW>::GetSetting(this, "sound_selected", soundPath) == PSRETURN_OK && !soundPath.empty())
g_SoundManager->PlayAsUI(soundPath.c_str(), false);
return;
}
xpos += width;
}
return;
}
default:
return;
}
}
bool COList::HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile)
{
#define ELMT(x) int elmt_##x = pFile->GetElementID(#x)
#define ATTR(x) int attr_##x = pFile->GetAttributeID(#x)
ELMT(item);
ELMT(column);
ELMT(translatableAttribute);
ATTR(id);
ATTR(context);
if (child.GetNodeName() == elmt_item)
{
AddItem(child.GetText().FromUTF8(), child.GetText().FromUTF8());
return true;
}
else if (child.GetNodeName() == elmt_column)
{
COListColumn column;
for (XMBAttribute attr : child.GetAttributes())
{
CStr attr_name(pFile->GetAttributeString(attr.Name));
CStr attr_value(attr.Value);
if (attr_name == "color")
{
CColor color;
if (!GUI<CColor>::ParseString(attr_value.FromUTF8(), color))
LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str());
else
column.m_TextColor = color;
}
else if (attr_name == "id")
{
column.m_Id = attr_value;
}
else if (attr_name == "width")
{
float width;
if (!GUI<float>::ParseString(attr_value.FromUTF8(), width))
LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str());
else
{
// Check if it's a relative value, and save as decimal if so.
if (attr_value.find("%") != std::string::npos)
width = width / 100.f;
column.m_Width = width;
}
}
else if (attr_name == "heading")
{
column.m_Heading = attr_value.FromUTF8();
}
}
for (XMBElement grandchild : child.GetChildNodes())
{
if (grandchild.GetNodeName() != elmt_translatableAttribute)
continue;
CStr attributeName(grandchild.GetAttributes().GetNamedItem(attr_id));
// only the heading is translatable for list column
if (attributeName.empty() || attributeName != "heading")
{
LOGERROR("GUI: translatable attribute in olist column that isn't a heading. (object: %s)", this->GetPresentableName().c_str());
continue;
}
CStr value(grandchild.GetText());
if (value.empty())
continue;
CStr context(grandchild.GetAttributes().GetNamedItem(attr_context)); // Read the context if any.
if (!context.empty())
{
CStr translatedValue(g_L10n.TranslateWithContext(context, value));
column.m_Heading = translatedValue.FromUTF8();
}
else
{
CStr translatedValue(g_L10n.Translate(value));
column.m_Heading = translatedValue.FromUTF8();
}
}
m_Columns.push_back(column);
AddSetting(GUIST_CGUIList, "list_" + column.m_Id);
SetupText();
return true;
}
else
{
return false;
}
}
void COList::DrawList(const int& selected, const CStr& _sprite, const CStr& _sprite_selected, const CStr& _textcolor)
{
float bz = GetBufferedZ();
bool scrollbar;
GUI<bool>::GetSetting(this, "scrollbar", scrollbar);
if (scrollbar)
IGUIScrollBarOwner::Draw();
if (!GetGUI())
return;
CRect rect = GetListRect();
CGUISpriteInstance* sprite = NULL;
CGUISpriteInstance* sprite_selectarea = NULL;
int cell_id;
GUI<CGUISpriteInstance>::GetSettingPointer(this, _sprite, sprite);
GUI<CGUISpriteInstance>::GetSettingPointer(this, _sprite_selected, sprite_selectarea);
GUI<int>::GetSetting(this, "cell_id", cell_id);
CGUIList* pList;
GUI<CGUIList>::GetSettingPointer(this, "list", pList);
GetGUI()->DrawSprite(*sprite, cell_id, bz, rect);
float scroll = 0.f;
if (scrollbar)
scroll = GetScrollBar(0).GetPos();
// Draw item selection
if (selected != -1)
{
ENSURE(selected >= 0 && selected+1 < (int)m_ItemsYPositions.size());
// Get rectangle of selection:
CRect rect_sel(rect.left, rect.top + m_ItemsYPositions[selected] - scroll,
rect.right, rect.top + m_ItemsYPositions[selected+1] - scroll);
if (rect_sel.top <= rect.bottom &&
rect_sel.bottom >= rect.top)
{
if (rect_sel.bottom > rect.bottom)
rect_sel.bottom = rect.bottom;
if (rect_sel.top < rect.top)
rect_sel.top = rect.top;
if (scrollbar)
{
// Remove any overlapping area of the scrollbar.
if (rect_sel.right > GetScrollBar(0).GetOuterRect().left &&
rect_sel.right <= GetScrollBar(0).GetOuterRect().right)
rect_sel.right = GetScrollBar(0).GetOuterRect().left;
if (rect_sel.left >= GetScrollBar(0).GetOuterRect().left &&
rect_sel.left < GetScrollBar(0).GetOuterRect().right)
rect_sel.left = GetScrollBar(0).GetOuterRect().right;
}
// Draw item selection
GetGUI()->DrawSprite(*sprite_selectarea, cell_id, bz+0.05f, rect_sel);
}
}
float headingHeight;
GUI<float>::GetSetting(this, "heading_height", headingHeight);
// Draw line above column header
CGUISpriteInstance* sprite_heading = NULL;
GUI<CGUISpriteInstance>::GetSettingPointer(this, "sprite_heading", sprite_heading);
CRect rect_head(m_CachedActualSize.left, m_CachedActualSize.top, m_CachedActualSize.right,
m_CachedActualSize.top + headingHeight);
GetGUI()->DrawSprite(*sprite_heading, cell_id, bz, rect_head);
int selectedColumnOrder;
GUI<int>::GetSetting(this, "selected_column_order", selectedColumnOrder);
CGUISpriteInstance* sprite_order;
CGUISpriteInstance* sprite_not_sorted;
if (selectedColumnOrder == 0)
LOGERROR("selected_column_order must not be 0");
else if (selectedColumnOrder != -1)
GUI<CGUISpriteInstance>::GetSettingPointer(this, "sprite_asc", sprite_order);
else
GUI<CGUISpriteInstance>::GetSettingPointer(this, "sprite_desc", sprite_order);
GUI<CGUISpriteInstance>::GetSettingPointer(this, "sprite_not_sorted", sprite_not_sorted);
CStr selectedColumn;
GUI<CStr>::GetSetting(this, "selected_column", selectedColumn);
// Draw column headers
CColor color;
GUI<CColor>::GetSetting(this, _textcolor, color);
float xpos = 0;
for (size_t col = 0; col < m_Columns.size(); ++col)
{
// Check if it's a decimal value, and if so, assume relative positioning.
float width = m_Columns[col].m_Width;
if (m_Columns[col].m_Width < 1 && m_Columns[col].m_Width > 0)
width *= m_TotalAvailableColumnWidth;
CPos leftTopCorner = m_CachedActualSize.TopLeft() + CPos(xpos, 0);
CGUISpriteInstance* sprite;
// If the list sorted by current column
if (selectedColumn == m_Columns[col].m_Id)
sprite = sprite_order;
else
sprite = sprite_not_sorted;
// Draw sort arrows in colum header
GetGUI()->DrawSprite(*sprite, cell_id, bz + 0.1f, CRect(leftTopCorner + CPos(width - 16, 0), leftTopCorner + CPos(width, 16)));
// Draw column header text
DrawText(col, color, leftTopCorner + CPos(0, 4), bz + 0.1f, rect_head);
xpos += width;
}
// Draw list items for each column
const size_t objectsCount = m_Columns.size();
for (size_t i = 0; i < pList->m_Items.size(); ++i)
{
if (m_ItemsYPositions[i+1] - scroll < 0 ||
m_ItemsYPositions[i] - scroll > rect.GetHeight())
continue;
const float rowHeight = m_ItemsYPositions[i+1] - m_ItemsYPositions[i];
// Clipping area (we'll have to substract the scrollbar)
CRect cliparea = GetListRect();
if (scrollbar)
{
if (cliparea.right > GetScrollBar(0).GetOuterRect().left &&
cliparea.right <= GetScrollBar(0).GetOuterRect().right)
cliparea.right = GetScrollBar(0).GetOuterRect().left;
if (cliparea.left >= GetScrollBar(0).GetOuterRect().left &&
cliparea.left < GetScrollBar(0).GetOuterRect().right)
cliparea.left = GetScrollBar(0).GetOuterRect().right;
}
// Draw all items for that column
xpos = 0;
for (size_t col = 0; col < objectsCount; ++col)
{
// Determine text position and width
const CPos textPos = rect.TopLeft() + CPos(xpos, -scroll + m_ItemsYPositions[i]);
float width = m_Columns[col].m_Width;
// Check if it's a decimal value, and if so, assume relative positioning.
if (m_Columns[col].m_Width < 1 && m_Columns[col].m_Width > 0)
width *= m_TotalAvailableColumnWidth;
// Clip text to the column (to prevent drawing text into the neighboring column)
CRect cliparea2 = cliparea;
cliparea2.right = std::min(cliparea2.right, textPos.x + width);
cliparea2.bottom = std::min(cliparea2.bottom, textPos.y + rowHeight);
// Draw list item
DrawText(objectsCount * (i +/*Heading*/1) + col, m_Columns[col].m_TextColor, textPos, bz + 0.1f, cliparea2);
xpos += width;
}
}
}