LCOV - code coverage report
Current view: top level - source/gui/ObjectTypes - COList.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 3 229 1.3 %
Date: 2023-01-19 00:18:29 Functions: 2 9 22.2 %

          Line data    Source code
       1             : /* Copyright (C) 2023 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       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.
       8             :  *
       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.
      13             :  *
      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/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "COList.h"
      21             : 
      22             : #include "gui/CGUI.h"
      23             : #include "gui/IGUIScrollBar.h"
      24             : #include "gui/SettingTypes/CGUIColor.h"
      25             : #include "gui/SettingTypes/CGUIList.h"
      26             : #include "i18n/L10n.h"
      27             : #include "ps/CLogger.h"
      28             : 
      29             : const float SORT_SPRITE_DIM = 16.0f;
      30           1 : const CVector2D COLUMN_SHIFT = CVector2D(0, 4);
      31             : 
      32           1 : const CStr COList::EventNameSelectionColumnChange = "SelectionColumnChange";
      33             : 
      34           0 : COList::COList(CGUI& pGUI)
      35             :     : CList(pGUI),
      36             :       m_SpriteHeading(this, "sprite_heading"),
      37             :       m_Sortable(this, "sortable"), // The actual sorting is done in JS for more versatility
      38             :       m_SelectedColumn(this, "selected_column"),
      39             :       m_SelectedColumnOrder(this, "selected_column_order"),
      40             :       m_SpriteAsc(this, "sprite_asc"), // Show the order of sorting
      41             :       m_SpriteDesc(this, "sprite_desc"),
      42           0 :       m_SpriteNotSorted(this, "sprite_not_sorted")
      43             : {
      44           0 : }
      45             : 
      46           0 : void COList::SetupText()
      47             : {
      48           0 :     m_ItemsYPositions.resize(m_List->m_Items.size() + 1);
      49             : 
      50             :     // Delete all generated texts. Some could probably be saved,
      51             :     //  but this is easier, and this function will never be called
      52             :     //  continuously, or even often, so it'll probably be okay.
      53           0 :     m_GeneratedTexts.clear();
      54             : 
      55           0 :     m_TotalAvailableColumnWidth = GetListRect().GetWidth();
      56             :     // remove scrollbar if applicable
      57           0 :     if (m_ScrollBar && GetScrollBar(0).GetStyle())
      58           0 :         m_TotalAvailableColumnWidth -= GetScrollBar(0).GetStyle()->m_Width;
      59             : 
      60           0 :     m_HeadingHeight = SORT_SPRITE_DIM; // At least the size of the sorting sprite
      61             : 
      62           0 :     for (const COListColumn& column : m_Columns)
      63             :     {
      64           0 :         float width = column.m_Width;
      65           0 :         if (column.m_Width > 0 && column.m_Width < 1)
      66           0 :             width *= m_TotalAvailableColumnWidth;
      67             : 
      68           0 :         CGUIString gui_string;
      69           0 :         gui_string.SetValue(column.m_Heading);
      70             : 
      71           0 :         const CGUIText& text = AddText(gui_string, m_Font, width, m_BufferZone);
      72           0 :         m_HeadingHeight = std::max(m_HeadingHeight, text.GetSize().Height + COLUMN_SHIFT.Y);
      73             :     }
      74             : 
      75             :     // Generate texts
      76           0 :     float buffered_y = 0.f;
      77             : 
      78           0 :     for (size_t i = 0; i < m_List->m_Items.size(); ++i)
      79             :     {
      80           0 :         m_ItemsYPositions[i] = buffered_y;
      81           0 :         float shift = 0.0f;
      82           0 :         for (const COListColumn& column : m_Columns)
      83             :         {
      84           0 :             float width = column.m_Width;
      85           0 :             if (column.m_Width > 0 && column.m_Width < 1)
      86           0 :                 width *= m_TotalAvailableColumnWidth;
      87             : 
      88             :             CGUIText* text;
      89           0 :             if (!column.m_List->m_Items[i].GetOriginalString().empty())
      90           0 :                 text = &AddText(column.m_List->m_Items[i], m_Font, width, m_BufferZone);
      91             :             else
      92             :             {
      93             :                 // Minimum height of a space character of the current font size
      94           0 :                 CGUIString align_string;
      95           0 :                 align_string.SetValue(L" ");
      96           0 :                 text = &AddText(align_string, m_Font, width, m_BufferZone);
      97             :             }
      98           0 :             shift = std::max(shift, text->GetSize().Height);
      99             :         }
     100           0 :         buffered_y += shift;
     101             :     }
     102             : 
     103           0 :     m_ItemsYPositions[m_List->m_Items.size()] = buffered_y;
     104             : 
     105           0 :     if (m_ScrollBar)
     106             :     {
     107           0 :         CRect rect = GetListRect();
     108           0 :         GetScrollBar(0).SetScrollRange(m_ItemsYPositions.back());
     109           0 :         GetScrollBar(0).SetScrollSpace(rect.GetHeight());
     110             : 
     111           0 :         GetScrollBar(0).SetX(rect.right);
     112           0 :         GetScrollBar(0).SetY(rect.top);
     113           0 :         GetScrollBar(0).SetZ(GetBufferedZ());
     114           0 :         GetScrollBar(0).SetLength(rect.bottom - rect.top);
     115             :     }
     116           0 : }
     117             : 
     118           0 : CRect COList::GetListRect() const
     119             : {
     120           0 :     return m_CachedActualSize + CRect(0, m_HeadingHeight, 0, 0);
     121             : }
     122             : 
     123           0 : void COList::HandleMessage(SGUIMessage& Message)
     124             : {
     125           0 :     CList::HandleMessage(Message);
     126             : 
     127           0 :     switch (Message.type)
     128             :     {
     129             : 
     130           0 :     case GUIM_SETTINGS_UPDATED:
     131             :     {
     132           0 :         if (Message.value.find("heading_") == 0)
     133           0 :             SetupText();
     134           0 :         break;
     135             :     }
     136             : 
     137             :     // If somebody clicks on the column heading
     138           0 :     case GUIM_MOUSE_PRESS_LEFT:
     139             :     {
     140           0 :         if (!m_Sortable)
     141           0 :             return;
     142             : 
     143           0 :         const CVector2D& mouse = m_pGUI.GetMousePos();
     144           0 :         if (!m_CachedActualSize.PointInside(mouse))
     145           0 :             return;
     146             : 
     147           0 :         float xpos = 0;
     148           0 :         for (const COListColumn& column : m_Columns)
     149             :         {
     150           0 :             if (column.m_Hidden)
     151           0 :                 continue;
     152             : 
     153           0 :             float width = column.m_Width;
     154             :             // Check if it's a decimal value, and if so, assume relative positioning.
     155           0 :             if (column.m_Width < 1 && column.m_Width > 0)
     156           0 :                 width *= m_TotalAvailableColumnWidth;
     157           0 :             CVector2D leftTopCorner = m_CachedActualSize.TopLeft() + CVector2D(xpos, 0);
     158           0 :             if (mouse.X >= leftTopCorner.X &&
     159           0 :                 mouse.X < leftTopCorner.X + width &&
     160           0 :                 mouse.Y < leftTopCorner.Y + m_HeadingHeight)
     161             :             {
     162           0 :                 if (column.m_Id != static_cast<CStr>(m_SelectedColumn))
     163             :                 {
     164           0 :                     m_SelectedColumnOrder.Set(column.m_SortOrder, true);
     165           0 :                     CStr selected_column = column.m_Id;
     166           0 :                     m_SelectedColumn.Set(selected_column, true);
     167             :                 }
     168             :                 else
     169           0 :                     m_SelectedColumnOrder.Set(-m_SelectedColumnOrder, true);
     170             : 
     171           0 :                 ScriptEvent(EventNameSelectionColumnChange);
     172           0 :                 PlaySound(m_SoundSelected);
     173           0 :                 return;
     174             :             }
     175           0 :             xpos += width;
     176             :         }
     177           0 :         return;
     178             :     }
     179           0 :     default:
     180           0 :         return;
     181             :     }
     182             : }
     183             : 
     184           0 : bool COList::HandleAdditionalChildren(const XMBData& xmb, const XMBElement& child)
     185             : {
     186             :     #define ELMT(x) int elmt_##x = xmb.GetElementID(#x)
     187             :     #define ATTR(x) int attr_##x = xmb.GetAttributeID(#x)
     188           0 :     ELMT(item);
     189           0 :     ELMT(column);
     190           0 :     ELMT(translatableAttribute);
     191           0 :     ATTR(id);
     192           0 :     ATTR(context);
     193             : 
     194           0 :     if (child.GetNodeName() == elmt_item)
     195             :     {
     196           0 :         CGUIString vlist;
     197           0 :         vlist.SetValue(child.GetText().FromUTF8());
     198           0 :         AddItem(vlist, vlist);
     199           0 :         return true;
     200             :     }
     201           0 :     else if (child.GetNodeName() == elmt_column)
     202             :     {
     203           0 :         CStr id;
     204           0 :         XERO_ITER_ATTR(child, attr)
     205             :         {
     206           0 :             if (attr.Name == attr_id)
     207           0 :                 id = attr.Value;
     208             :         }
     209             : 
     210           0 :         COListColumn column(this, id);
     211             : 
     212           0 :         for (XMBAttribute attr : child.GetAttributes())
     213             :         {
     214           0 :             std::string_view attr_name(xmb.GetAttributeStringView(attr.Name));
     215           0 :             CStr attr_value(attr.Value);
     216             : 
     217           0 :             if (attr_name == "color")
     218             :             {
     219           0 :                 if (!CGUI::ParseString<CGUIColor>(&m_pGUI, attr_value.FromUTF8(), column.m_TextColor))
     220           0 :                     LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.data(), attr_value.c_str());
     221             :             }
     222           0 :             else if (attr_name == "hidden")
     223             :             {
     224           0 :                 bool hidden = false;
     225           0 :                 if (!CGUI::ParseString<bool>(&m_pGUI, attr_value.FromUTF8(), hidden))
     226           0 :                     LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.data(), attr_value.c_str());
     227             :                 else
     228           0 :                     column.m_Hidden.Set(hidden, false);
     229             :             }
     230           0 :             else if (attr_name == "width")
     231             :             {
     232             :                 float width;
     233           0 :                 if (!CGUI::ParseString<float>(&m_pGUI, attr_value.FromUTF8(), width))
     234           0 :                     LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.data(), attr_value.c_str());
     235             :                 else
     236             :                 {
     237             :                     // Check if it's a relative value, and save as decimal if so.
     238           0 :                     if (attr_value.find("%") != std::string::npos)
     239           0 :                         width = width / 100.f;
     240           0 :                     column.m_Width = width;
     241             :                 }
     242             :             }
     243           0 :             else if (attr_name == "heading")
     244             :             {
     245           0 :                 column.m_Heading.Set(attr_value.FromUTF8(), false);
     246             :             }
     247           0 :             else if (attr_name == "sort_order")
     248             :             {
     249           0 :                 column.m_SortOrder.Set(attr_value == "desc" ? -1 : 1, false);
     250             :             }
     251             :         }
     252             : 
     253           0 :         for (XMBElement grandchild : child.GetChildNodes())
     254             :         {
     255           0 :             if (grandchild.GetNodeName() != elmt_translatableAttribute)
     256           0 :                 continue;
     257             : 
     258           0 :             CStr attributeName(grandchild.GetAttributes().GetNamedItem(attr_id));
     259             :             // only the heading is translatable for list column
     260           0 :             if (attributeName.empty() || attributeName != "heading")
     261             :             {
     262           0 :                 LOGERROR("GUI: translatable attribute in olist column that isn't a heading. (object: %s)", this->GetPresentableName().c_str());
     263           0 :                 continue;
     264             :             }
     265             : 
     266           0 :             CStr value(grandchild.GetText());
     267           0 :             if (value.empty())
     268           0 :                 continue;
     269             : 
     270           0 :             CStr context(grandchild.GetAttributes().GetNamedItem(attr_context)); // Read the context if any.
     271           0 :             if (!context.empty())
     272             :             {
     273           0 :                 CStr translatedValue(g_L10n.TranslateWithContext(context, value));
     274           0 :                 column.m_Heading.Set(translatedValue.FromUTF8(), false);
     275             :             }
     276             :             else
     277             :             {
     278           0 :                 CStr translatedValue(g_L10n.Translate(value));
     279           0 :                 column.m_Heading.Set(translatedValue.FromUTF8(), false);
     280             :             }
     281             :         }
     282             : 
     283           0 :         m_Columns.emplace_back(std::move(column));
     284           0 :         return true;
     285             :     }
     286             : 
     287           0 :     return false;
     288             : }
     289             : 
     290           0 : void COList::AdditionalChildrenHandled()
     291             : {
     292           0 :     SetupText();
     293           0 : }
     294             : 
     295           0 : void COList::DrawList(CCanvas2D& canvas, const int& selected, const CGUISpriteInstance& sprite, const CGUISpriteInstance& spriteOverlay,
     296             :                       const CGUISpriteInstance& spriteSelectArea, const CGUISpriteInstance& spriteSelectAreaOverlay, const CGUIColor& textColor)
     297             : {
     298           0 :     CRect rect = GetListRect();
     299             : 
     300           0 :     m_pGUI.DrawSprite(sprite, canvas, rect);
     301             : 
     302           0 :     float scroll = 0.f;
     303           0 :     if (m_ScrollBar)
     304           0 :         scroll = GetScrollBar(0).GetPos();
     305             : 
     306           0 :     bool drawSelected = false;
     307           0 :     CRect rectSel;
     308             :     // Draw item selection
     309           0 :     if (selected != -1)
     310             :     {
     311           0 :         ENSURE(selected >= 0 && selected+1 < (int)m_ItemsYPositions.size());
     312             : 
     313             :         // Get rectangle of selection:
     314           0 :         rectSel = CRect(
     315           0 :             rect.left, rect.top + m_ItemsYPositions[selected] - scroll,
     316           0 :             rect.right, rect.top + m_ItemsYPositions[selected+1] - scroll);
     317             : 
     318           0 :         if (rectSel.top <= rect.bottom &&
     319           0 :             rectSel.bottom >= rect.top)
     320             :         {
     321           0 :             if (rectSel.bottom > rect.bottom)
     322           0 :                 rectSel.bottom = rect.bottom;
     323           0 :             if (rectSel.top < rect.top)
     324           0 :                 rectSel.top = rect.top;
     325             : 
     326           0 :             if (m_ScrollBar)
     327             :             {
     328             :                 // Remove any overlapping area of the scrollbar.
     329           0 :                 if (rectSel.right > GetScrollBar(0).GetOuterRect().left &&
     330           0 :                     rectSel.right <= GetScrollBar(0).GetOuterRect().right)
     331           0 :                     rectSel.right = GetScrollBar(0).GetOuterRect().left;
     332             : 
     333           0 :                 if (rectSel.left >= GetScrollBar(0).GetOuterRect().left &&
     334           0 :                     rectSel.left < GetScrollBar(0).GetOuterRect().right)
     335           0 :                     rectSel.left = GetScrollBar(0).GetOuterRect().right;
     336             :             }
     337             : 
     338             :             // Draw item selection
     339           0 :             m_pGUI.DrawSprite(spriteSelectArea, canvas, rectSel);
     340           0 :             drawSelected = true;
     341             :         }
     342             :     }
     343             : 
     344             :     // Draw line above column header
     345             :     CRect rect_head(m_CachedActualSize.left, m_CachedActualSize.top, m_CachedActualSize.right,
     346           0 :                                     m_CachedActualSize.top + m_HeadingHeight);
     347           0 :     m_pGUI.DrawSprite(m_SpriteHeading, canvas, rect_head);
     348             : 
     349             :     // Draw column headers
     350           0 :     float xpos = 0;
     351           0 :     size_t col = 0;
     352           0 :     for (const COListColumn& column : m_Columns)
     353             :     {
     354           0 :         if (column.m_Hidden)
     355             :         {
     356           0 :             ++col;
     357           0 :             continue;
     358             :         }
     359             : 
     360             :         // Check if it's a decimal value, and if so, assume relative positioning.
     361           0 :         float width = column.m_Width;
     362           0 :         if (column.m_Width < 1 && column.m_Width > 0)
     363           0 :             width *= m_TotalAvailableColumnWidth;
     364             : 
     365           0 :         CVector2D leftTopCorner = m_CachedActualSize.TopLeft() + CVector2D(xpos, 0);
     366             : 
     367             :         // Draw sort arrows in colum header
     368           0 :         if (m_Sortable)
     369             :         {
     370             :             const CGUISpriteInstance* pSprite;
     371           0 :             if (*m_SelectedColumn == column.m_Id)
     372             :             {
     373           0 :                 if (m_SelectedColumnOrder == 0)
     374           0 :                     LOGERROR("selected_column_order must not be 0");
     375             : 
     376           0 :                 if (m_SelectedColumnOrder != -1)
     377           0 :                     pSprite = &*m_SpriteAsc;
     378             :                 else
     379           0 :                     pSprite = &*m_SpriteDesc;
     380             :             }
     381             :             else
     382           0 :                 pSprite = &*m_SpriteNotSorted;
     383             : 
     384           0 :             m_pGUI.DrawSprite(*pSprite, canvas, CRect(leftTopCorner + CVector2D(width - SORT_SPRITE_DIM, 0), leftTopCorner + CVector2D(width, SORT_SPRITE_DIM)));
     385             :         }
     386             : 
     387             :         // Draw column header text
     388           0 :         DrawText(canvas, col, textColor, leftTopCorner + COLUMN_SHIFT, rect_head);
     389           0 :         xpos += width;
     390           0 :         ++col;
     391             :     }
     392             : 
     393             :     // Draw list items for each column
     394           0 :     const size_t objectsCount = m_Columns.size();
     395           0 :     for (size_t i = 0; i < m_List->m_Items.size(); ++i)
     396             :     {
     397           0 :         if (m_ItemsYPositions[i+1] - scroll < 0 ||
     398           0 :             m_ItemsYPositions[i] - scroll > rect.GetHeight())
     399           0 :             continue;
     400             : 
     401           0 :         const float rowHeight = m_ItemsYPositions[i+1] - m_ItemsYPositions[i];
     402             : 
     403             :         // Clipping area (we'll have to substract the scrollbar)
     404           0 :         CRect cliparea = GetListRect();
     405             : 
     406           0 :         if (m_ScrollBar)
     407             :         {
     408           0 :             if (cliparea.right > GetScrollBar(0).GetOuterRect().left &&
     409           0 :                 cliparea.right <= GetScrollBar(0).GetOuterRect().right)
     410           0 :                 cliparea.right = GetScrollBar(0).GetOuterRect().left;
     411             : 
     412           0 :             if (cliparea.left >= GetScrollBar(0).GetOuterRect().left &&
     413           0 :                 cliparea.left < GetScrollBar(0).GetOuterRect().right)
     414           0 :                 cliparea.left = GetScrollBar(0).GetOuterRect().right;
     415             :         }
     416             : 
     417             :         // Draw all items for that column
     418           0 :         xpos = 0;
     419           0 :         for (size_t colIdx = 0; colIdx < m_Columns.size(); ++colIdx)
     420             :         {
     421           0 :             const COListColumn& column = m_Columns[colIdx];
     422           0 :             if (column.m_Hidden)
     423           0 :                 continue;
     424             : 
     425             :             // Determine text position and width
     426           0 :             const CVector2D textPos = rect.TopLeft() + CVector2D(xpos, -scroll + m_ItemsYPositions[i]);
     427             : 
     428           0 :             float width = column.m_Width;
     429             :             // Check if it's a decimal value, and if so, assume relative positioning.
     430           0 :             if (column.m_Width < 1 && column.m_Width > 0)
     431           0 :                 width *= m_TotalAvailableColumnWidth;
     432             : 
     433             :             // Clip text to the column (to prevent drawing text into the neighboring column)
     434           0 :             CRect cliparea2 = cliparea;
     435           0 :             cliparea2.right = std::min(cliparea2.right, textPos.X + width);
     436           0 :             cliparea2.bottom = std::min(cliparea2.bottom, textPos.Y + rowHeight);
     437             : 
     438             :             // Draw list item
     439           0 :             DrawText(canvas, objectsCount * (i +/*Heading*/1) + colIdx, column.m_TextColor, textPos, cliparea2);
     440           0 :             xpos += width;
     441             :         }
     442             :     }
     443             : 
     444             :     // Draw scrollbars on top of the content
     445           0 :     if (m_ScrollBar)
     446           0 :         IGUIScrollBarOwner::Draw(canvas);
     447             : 
     448             :     // Draw the overlays last
     449           0 :     m_pGUI.DrawSprite(spriteOverlay, canvas, rect);
     450           0 :     if (drawSelected)
     451           0 :         m_pGUI.DrawSprite(spriteSelectAreaOverlay, canvas, rectSel);
     452           3 : }

Generated by: LCOV version 1.13