LCOV - code coverage report
Current view: top level - src - guiTable.cpp (source / functions) Hit Total Coverage
Test: report Lines: 330 738 44.7 %
Date: 2015-07-11 18:23:49 Functions: 27 39 69.2 %

          Line data    Source code
       1             : /*
       2             : Minetest
       3             : Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
       4             : 
       5             : This program is free software; you can redistribute it and/or modify
       6             : it under the terms of the GNU Lesser General Public License as published by
       7             : the Free Software Foundation; either version 2.1 of the License, or
       8             : (at your option) any later version.
       9             : 
      10             : This program is distributed in the hope that it will be useful,
      11             : but WITHOUT ANY WARRANTY; without even the implied warranty of
      12             : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13             : GNU Lesser General Public License for more details.
      14             : 
      15             : You should have received a copy of the GNU Lesser General Public License along
      16             : with this program; if not, write to the Free Software Foundation, Inc.,
      17             : 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
      18             : */
      19             : 
      20             : 
      21             : #include "guiTable.h"
      22             : #include <queue>
      23             : #include <sstream>
      24             : #include <utility>
      25             : #include <string.h>
      26             : #include <IGUISkin.h>
      27             : #include <IGUIFont.h>
      28             : #include <IGUIScrollBar.h>
      29             : #include "debug.h"
      30             : #include "log.h"
      31             : #include "client/tile.h"
      32             : #include "gettime.h"
      33             : #include "util/string.h"
      34             : #include "util/numeric.h"
      35             : #include "util/string.h" // for parseColorString()
      36             : #include "settings.h" // for settings
      37             : #include "porting.h" // for dpi
      38             : #include "guiscalingfilter.h"
      39             : 
      40             : /*
      41             :         GUITable
      42             : */
      43             : 
      44           2 : GUITable::GUITable(gui::IGUIEnvironment *env,
      45             :                 gui::IGUIElement* parent, s32 id,
      46             :                 core::rect<s32> rectangle,
      47             :                 ISimpleTextureSource *tsrc
      48             : ):
      49             :         gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle),
      50             :         m_tsrc(tsrc),
      51             :         m_is_textlist(false),
      52             :         m_has_tree_column(false),
      53             :         m_selected(-1),
      54             :         m_sel_column(0),
      55             :         m_sel_doubleclick(false),
      56             :         m_keynav_time(0),
      57             :         m_keynav_buffer(L""),
      58             :         m_border(true),
      59             :         m_color(255, 255, 255, 255),
      60             :         m_background(255, 0, 0, 0),
      61             :         m_highlight(255, 70, 100, 50),
      62             :         m_highlight_text(255, 255, 255, 255),
      63             :         m_rowheight(1),
      64             :         m_font(NULL),
      65           2 :         m_scrollbar(NULL)
      66             : {
      67             :         assert(tsrc != NULL);
      68             : 
      69           2 :         gui::IGUISkin* skin = Environment->getSkin();
      70             : 
      71           2 :         m_font = skin->getFont();
      72           2 :         if (m_font) {
      73           2 :                 m_font->grab();
      74           2 :                 m_rowheight = m_font->getDimension(L"A").Height + 4;
      75           2 :                 m_rowheight = MYMAX(m_rowheight, 1);
      76             :         }
      77             : 
      78           2 :         const s32 s = skin->getSize(gui::EGDS_SCROLLBAR_SIZE);
      79           8 :         m_scrollbar = Environment->addScrollBar(false,
      80           2 :                         core::rect<s32>(RelativeRect.getWidth() - s,
      81             :                                         0,
      82             :                                         RelativeRect.getWidth(),
      83             :                                         RelativeRect.getHeight()),
      84           4 :                         this, -1);
      85           2 :         m_scrollbar->setSubElement(true);
      86           2 :         m_scrollbar->setTabStop(false);
      87           2 :         m_scrollbar->setAlignment(gui::EGUIA_LOWERRIGHT, gui::EGUIA_LOWERRIGHT,
      88           2 :                         gui::EGUIA_UPPERLEFT, gui::EGUIA_LOWERRIGHT);
      89           2 :         m_scrollbar->setVisible(false);
      90           2 :         m_scrollbar->setPos(0);
      91             : 
      92           2 :         setTabStop(true);
      93           2 :         setTabOrder(-1);
      94           2 :         updateAbsolutePosition();
      95             : 
      96           2 :         core::rect<s32> relative_rect = m_scrollbar->getRelativePosition();
      97           4 :         s32 width = (relative_rect.getWidth()/(2.0/3.0)) * porting::getDisplayDensity() *
      98           4 :                         g_settings->getFloat("gui_scaling");
      99           6 :         m_scrollbar->setRelativePosition(core::rect<s32>(
     100           2 :                         relative_rect.LowerRightCorner.X-width,relative_rect.UpperLeftCorner.Y,
     101             :                         relative_rect.LowerRightCorner.X,relative_rect.LowerRightCorner.Y
     102           2 :                         ));
     103           2 : }
     104             : 
     105           6 : GUITable::~GUITable()
     106             : {
     107           6 :         for (size_t i = 0; i < m_rows.size(); ++i)
     108           4 :                 delete[] m_rows[i].cells;
     109             : 
     110           2 :         if (m_font)
     111           2 :                 m_font->drop();
     112             :         
     113           2 :         m_scrollbar->remove();
     114           4 : }
     115             : 
     116           0 : GUITable::Option GUITable::splitOption(const std::string &str)
     117             : {
     118           0 :         size_t equal_pos = str.find('=');
     119           0 :         if (equal_pos == std::string::npos)
     120           0 :                 return GUITable::Option(str, "");
     121             :         else
     122           0 :                 return GUITable::Option(str.substr(0, equal_pos),
     123           0 :                                 str.substr(equal_pos + 1));
     124             : }
     125             : 
     126           0 : void GUITable::setTextList(const std::vector<std::string> &content,
     127             :                 bool transparent)
     128             : {
     129           0 :         clear();
     130             : 
     131           0 :         if (transparent) {
     132           0 :                 m_background.setAlpha(0);
     133           0 :                 m_border = false;
     134             :         }
     135             : 
     136           0 :         m_is_textlist = true;
     137             : 
     138           0 :         s32 empty_string_index = allocString("");
     139             : 
     140           0 :         m_rows.resize(content.size());
     141           0 :         for (s32 i = 0; i < (s32) content.size(); ++i) {
     142           0 :                 Row *row = &m_rows[i];
     143           0 :                 row->cells = new Cell[1];
     144           0 :                 row->cellcount = 1;
     145           0 :                 row->indent = 0;
     146           0 :                 row->visible_index = i;
     147           0 :                 m_visible_rows.push_back(i);
     148             : 
     149           0 :                 Cell *cell = row->cells;
     150           0 :                 cell->xmin = 0;
     151           0 :                 cell->xmax = 0x7fff;  // something large enough
     152           0 :                 cell->xpos = 6;
     153           0 :                 cell->content_type = COLUMN_TYPE_TEXT;
     154           0 :                 cell->content_index = empty_string_index;
     155           0 :                 cell->tooltip_index = empty_string_index;
     156           0 :                 cell->color.set(255, 255, 255, 255);
     157           0 :                 cell->color_defined = false;
     158           0 :                 cell->reported_column = 1;
     159             : 
     160             :                 // parse row content (color)
     161           0 :                 const std::string &s = content[i];
     162           0 :                 if (s[0] == '#' && s[1] == '#') {
     163             :                         // double # to escape
     164           0 :                         cell->content_index = allocString(s.substr(2));
     165             :                 }
     166           0 :                 else if (s[0] == '#' && s.size() >= 7 &&
     167           0 :                                 parseColorString(
     168           0 :                                         s.substr(0,7), cell->color, false)) {
     169             :                         // single # for color
     170           0 :                         cell->color_defined = true;
     171           0 :                         cell->content_index = allocString(s.substr(7));
     172             :                 }
     173             :                 else {
     174             :                         // no #, just text
     175           0 :                         cell->content_index = allocString(s);
     176             :                 }
     177             : 
     178             :         }
     179             : 
     180           0 :         allocationComplete();
     181             : 
     182             :         // Clamp scroll bar position
     183           0 :         updateScrollBar();
     184           0 : }
     185             : 
     186           2 : void GUITable::setTable(const TableOptions &options,
     187             :                 const TableColumns &columns,
     188             :                 std::vector<std::string> &content)
     189             : {
     190           2 :         clear();
     191             : 
     192             :         // Naming conventions:
     193             :         // i is always a row index, 0-based
     194             :         // j is always a column index, 0-based
     195             :         // k is another index, for example an option index
     196             : 
     197             :         // Handle a stupid error case... (issue #1187)
     198           2 :         if (columns.empty()) {
     199           0 :                 TableColumn text_column;
     200           0 :                 text_column.type = "text";
     201           0 :                 TableColumns new_columns;
     202           0 :                 new_columns.push_back(text_column);
     203           0 :                 setTable(options, new_columns, content);
     204           0 :                 return;
     205             :         }
     206             : 
     207             :         // Handle table options
     208           2 :         video::SColor default_color(255, 255, 255, 255);
     209           2 :         s32 opendepth = 0;
     210           2 :         for (size_t k = 0; k < options.size(); ++k) {
     211           0 :                 const std::string &name = options[k].name;
     212           0 :                 const std::string &value = options[k].value;
     213           0 :                 if (name == "color")
     214           0 :                         parseColorString(value, m_color, false);
     215           0 :                 else if (name == "background")
     216           0 :                         parseColorString(value, m_background, false);
     217           0 :                 else if (name == "border")
     218           0 :                         m_border = is_yes(value);
     219           0 :                 else if (name == "highlight")
     220           0 :                         parseColorString(value, m_highlight, false);
     221           0 :                 else if (name == "highlight_text")
     222           0 :                         parseColorString(value, m_highlight_text, false);
     223           0 :                 else if (name == "opendepth")
     224           0 :                         opendepth = stoi(value);
     225             :                 else
     226           0 :                         errorstream<<"Invalid table option: \""<<name<<"\""
     227           0 :                                 <<" (value=\""<<value<<"\")"<<std::endl;
     228             :         }
     229             : 
     230             :         // Get number of columns and rows
     231             :         // note: error case columns.size() == 0 was handled above
     232           2 :         s32 colcount = columns.size();
     233             :         assert(colcount >= 1);
     234             :         // rowcount = ceil(cellcount / colcount) but use integer arithmetic
     235           2 :         s32 rowcount = (content.size() + colcount - 1) / colcount;
     236             :         assert(rowcount >= 0);
     237             :         // Append empty strings to content if there is an incomplete row
     238           2 :         s32 cellcount = rowcount * colcount;
     239           2 :         while (content.size() < (u32) cellcount)
     240           0 :                 content.push_back("");
     241             : 
     242             :         // Create temporary rows (for processing columns)
     243           4 :         struct TempRow {
     244             :                 // Current horizontal position (may different between rows due
     245             :                 // to indent/tree columns, or text/image columns with width<0)
     246             :                 s32 x;
     247             :                 // Tree indentation level
     248             :                 s32 indent;
     249             :                 // Next cell: Index into m_strings or m_images
     250             :                 s32 content_index;
     251             :                 // Next cell: Width in pixels
     252             :                 s32 content_width;
     253             :                 // Vector of completed cells in this row
     254             :                 std::vector<Cell> cells;
     255             :                 // Stores colors and how long they last (maximum column index)
     256             :                 std::vector<std::pair<video::SColor, s32> > colors;
     257             : 
     258           4 :                 TempRow(): x(0), indent(0), content_index(0), content_width(0) {}
     259             :         };
     260           2 :         TempRow *rows = new TempRow[rowcount];
     261             : 
     262             :         // Get em width. Pedantically speaking, the width of "M" is not
     263             :         // necessarily the same as the em width, but whatever, close enough.
     264           2 :         s32 em = 6;
     265           2 :         if (m_font)
     266           2 :                 em = m_font->getDimension(L"M").Width;
     267             : 
     268           2 :         s32 default_tooltip_index = allocString("");
     269             : 
     270           4 :         std::map<s32, s32> active_image_indices;
     271             : 
     272             :         // Process content in column-major order
     273           4 :         for (s32 j = 0; j < colcount; ++j) {
     274             :                 // Check column type
     275           2 :                 ColumnType columntype = COLUMN_TYPE_TEXT;
     276           2 :                 if (columns[j].type == "text")
     277           2 :                         columntype = COLUMN_TYPE_TEXT;
     278           0 :                 else if (columns[j].type == "image")
     279           0 :                         columntype = COLUMN_TYPE_IMAGE;
     280           0 :                 else if (columns[j].type == "color")
     281           0 :                         columntype = COLUMN_TYPE_COLOR;
     282           0 :                 else if (columns[j].type == "indent")
     283           0 :                         columntype = COLUMN_TYPE_INDENT;
     284           0 :                 else if (columns[j].type == "tree")
     285           0 :                         columntype = COLUMN_TYPE_TREE;
     286             :                 else
     287           0 :                         errorstream<<"Invalid table column type: \""
     288           0 :                                 <<columns[j].type<<"\""<<std::endl;
     289             : 
     290             :                 // Process column options
     291           2 :                 s32 padding = myround(0.5 * em);
     292           2 :                 s32 tooltip_index = default_tooltip_index;
     293           2 :                 s32 align = 0;
     294           2 :                 s32 width = 0;
     295           2 :                 s32 span = colcount;
     296             : 
     297           2 :                 if (columntype == COLUMN_TYPE_INDENT) {
     298           0 :                         padding = 0; // default indent padding
     299             :                 }
     300           2 :                 if (columntype == COLUMN_TYPE_INDENT ||
     301             :                                 columntype == COLUMN_TYPE_TREE) {
     302           0 :                         width = myround(em * 1.5); // default indent width
     303             :                 }
     304             : 
     305           2 :                 for (size_t k = 0; k < columns[j].options.size(); ++k) {
     306           0 :                         const std::string &name = columns[j].options[k].name;
     307           0 :                         const std::string &value = columns[j].options[k].value;
     308           0 :                         if (name == "padding")
     309           0 :                                 padding = myround(stof(value) * em);
     310           0 :                         else if (name == "tooltip")
     311           0 :                                 tooltip_index = allocString(value);
     312           0 :                         else if (name == "align" && value == "left")
     313           0 :                                 align = 0;
     314           0 :                         else if (name == "align" && value == "center")
     315           0 :                                 align = 1;
     316           0 :                         else if (name == "align" && value == "right")
     317           0 :                                 align = 2;
     318           0 :                         else if (name == "align" && value == "inline")
     319           0 :                                 align = 3;
     320           0 :                         else if (name == "width")
     321           0 :                                 width = myround(stof(value) * em);
     322           0 :                         else if (name == "span" && columntype == COLUMN_TYPE_COLOR)
     323           0 :                                 span = stoi(value);
     324           0 :                         else if (columntype == COLUMN_TYPE_IMAGE &&
     325           0 :                                         !name.empty() &&
     326           0 :                                         string_allowed(name, "0123456789")) {
     327           0 :                                 s32 content_index = allocImage(value);
     328           0 :                                 active_image_indices.insert(std::make_pair(
     329             :                                                         stoi(name),
     330           0 :                                                         content_index));
     331             :                         }
     332             :                         else {
     333           0 :                                 errorstream<<"Invalid table column option: \""<<name<<"\""
     334           0 :                                         <<" (value=\""<<value<<"\")"<<std::endl;
     335             :                         }
     336             :                 }
     337             : 
     338             :                 // If current column type can use information from "color" columns,
     339             :                 // find out which of those is currently active
     340           2 :                 if (columntype == COLUMN_TYPE_TEXT) {
     341           6 :                         for (s32 i = 0; i < rowcount; ++i) {
     342           4 :                                 TempRow *row = &rows[i];
     343           4 :                                 while (!row->colors.empty() && row->colors.back().second < j)
     344           0 :                                         row->colors.pop_back();
     345             :                         }
     346             :                 }
     347             : 
     348             :                 // Make template for new cells
     349           2 :                 Cell newcell;
     350           2 :                 memset(&newcell, 0, sizeof newcell);
     351           2 :                 newcell.content_type = columntype;
     352           2 :                 newcell.tooltip_index = tooltip_index;
     353           2 :                 newcell.reported_column = j+1;
     354             : 
     355           2 :                 if (columntype == COLUMN_TYPE_TEXT) {
     356             :                         // Find right edge of column
     357           2 :                         s32 xmax = 0;
     358           6 :                         for (s32 i = 0; i < rowcount; ++i) {
     359           4 :                                 TempRow *row = &rows[i];
     360           4 :                                 row->content_index = allocString(content[i * colcount + j]);
     361           4 :                                 const core::stringw &text = m_strings[row->content_index];
     362           4 :                                 row->content_width = m_font ?
     363           4 :                                         m_font->getDimension(text.c_str()).Width : 0;
     364           4 :                                 row->content_width = MYMAX(row->content_width, width);
     365           4 :                                 s32 row_xmax = row->x + padding + row->content_width;
     366           4 :                                 xmax = MYMAX(xmax, row_xmax);
     367             :                         }
     368             :                         // Add a new cell (of text type) to each row
     369           6 :                         for (s32 i = 0; i < rowcount; ++i) {
     370           4 :                                 newcell.xmin = rows[i].x + padding;
     371           4 :                                 alignContent(&newcell, xmax, rows[i].content_width, align);
     372           4 :                                 newcell.content_index = rows[i].content_index;
     373           4 :                                 newcell.color_defined = !rows[i].colors.empty();
     374           4 :                                 if (newcell.color_defined)
     375           0 :                                         newcell.color = rows[i].colors.back().first;
     376           4 :                                 rows[i].cells.push_back(newcell);
     377           4 :                                 rows[i].x = newcell.xmax;
     378             :                         }
     379             :                 }
     380           0 :                 else if (columntype == COLUMN_TYPE_IMAGE) {
     381             :                         // Find right edge of column
     382           0 :                         s32 xmax = 0;
     383           0 :                         for (s32 i = 0; i < rowcount; ++i) {
     384           0 :                                 TempRow *row = &rows[i];
     385           0 :                                 row->content_index = -1;
     386             : 
     387             :                                 // Find content_index. Image indices are defined in
     388             :                                 // column options so check active_image_indices.
     389           0 :                                 s32 image_index = stoi(content[i * colcount + j]);
     390             :                                 std::map<s32, s32>::iterator image_iter =
     391           0 :                                         active_image_indices.find(image_index);
     392           0 :                                 if (image_iter != active_image_indices.end())
     393           0 :                                         row->content_index = image_iter->second;
     394             : 
     395             :                                 // Get texture object (might be NULL)
     396           0 :                                 video::ITexture *image = NULL;
     397           0 :                                 if (row->content_index >= 0)
     398           0 :                                         image = m_images[row->content_index];
     399             : 
     400             :                                 // Get content width and update xmax
     401           0 :                                 row->content_width = image ? image->getOriginalSize().Width : 0;
     402           0 :                                 row->content_width = MYMAX(row->content_width, width);
     403           0 :                                 s32 row_xmax = row->x + padding + row->content_width;
     404           0 :                                 xmax = MYMAX(xmax, row_xmax);
     405             :                         }
     406             :                         // Add a new cell (of image type) to each row
     407           0 :                         for (s32 i = 0; i < rowcount; ++i) {
     408           0 :                                 newcell.xmin = rows[i].x + padding;
     409           0 :                                 alignContent(&newcell, xmax, rows[i].content_width, align);
     410           0 :                                 newcell.content_index = rows[i].content_index;
     411           0 :                                 rows[i].cells.push_back(newcell);
     412           0 :                                 rows[i].x = newcell.xmax;
     413             :                         }
     414           0 :                         active_image_indices.clear();
     415             :                 }
     416           0 :                 else if (columntype == COLUMN_TYPE_COLOR) {
     417           0 :                         for (s32 i = 0; i < rowcount; ++i) {
     418           0 :                                 video::SColor cellcolor(255, 255, 255, 255);
     419           0 :                                 if (parseColorString(content[i * colcount + j], cellcolor, true))
     420           0 :                                         rows[i].colors.push_back(std::make_pair(cellcolor, j+span));
     421             :                         }
     422             :                 }
     423           0 :                 else if (columntype == COLUMN_TYPE_INDENT ||
     424             :                                 columntype == COLUMN_TYPE_TREE) {
     425             :                         // For column type "tree", reserve additional space for +/-
     426             :                         // Also enable special processing for treeview-type tables
     427           0 :                         s32 content_width = 0;
     428           0 :                         if (columntype == COLUMN_TYPE_TREE) {
     429           0 :                                 content_width = m_font ? m_font->getDimension(L"+").Width : 0;
     430           0 :                                 m_has_tree_column = true;
     431             :                         }
     432             :                         // Add a new cell (of indent or tree type) to each row
     433           0 :                         for (s32 i = 0; i < rowcount; ++i) {
     434           0 :                                 TempRow *row = &rows[i];
     435             : 
     436           0 :                                 s32 indentlevel = stoi(content[i * colcount + j]);
     437           0 :                                 indentlevel = MYMAX(indentlevel, 0);
     438           0 :                                 if (columntype == COLUMN_TYPE_TREE)
     439           0 :                                         row->indent = indentlevel;
     440             : 
     441           0 :                                 newcell.xmin = row->x + padding;
     442           0 :                                 newcell.xpos = newcell.xmin + indentlevel * width;
     443           0 :                                 newcell.xmax = newcell.xpos + content_width;
     444           0 :                                 newcell.content_index = 0;
     445           0 :                                 newcell.color_defined = !rows[i].colors.empty();
     446           0 :                                 if (newcell.color_defined)
     447           0 :                                         newcell.color = rows[i].colors.back().first;
     448           0 :                                 row->cells.push_back(newcell);
     449           0 :                                 row->x = newcell.xmax;
     450             :                         }
     451             :                 }
     452             :         }
     453             : 
     454             :         // Copy temporary rows to not so temporary rows
     455           2 :         if (rowcount >= 1) {
     456           2 :                 m_rows.resize(rowcount);
     457           6 :                 for (s32 i = 0; i < rowcount; ++i) {
     458           4 :                         Row *row = &m_rows[i];
     459           4 :                         row->cellcount = rows[i].cells.size();
     460           4 :                         row->cells = new Cell[row->cellcount];
     461           4 :                         memcpy((void*) row->cells, (void*) &rows[i].cells[0],
     462           8 :                                         row->cellcount * sizeof(Cell));
     463           4 :                         row->indent = rows[i].indent;
     464           4 :                         row->visible_index = i;
     465           4 :                         m_visible_rows.push_back(i);
     466             :                 }
     467             :         }
     468             : 
     469           2 :         if (m_has_tree_column) {
     470             :                 // Treeview: convert tree to indent cells on leaf rows
     471           0 :                 for (s32 i = 0; i < rowcount; ++i) {
     472           0 :                         if (i == rowcount-1 || m_rows[i].indent >= m_rows[i+1].indent)
     473           0 :                                 for (s32 j = 0; j < m_rows[i].cellcount; ++j)
     474           0 :                                         if (m_rows[i].cells[j].content_type == COLUMN_TYPE_TREE)
     475           0 :                                                 m_rows[i].cells[j].content_type = COLUMN_TYPE_INDENT;
     476             :                 }
     477             : 
     478             :                 // Treeview: close rows according to opendepth option
     479           0 :                 std::set<s32> opened_trees;
     480           0 :                 for (s32 i = 0; i < rowcount; ++i)
     481           0 :                         if (m_rows[i].indent < opendepth)
     482           0 :                                 opened_trees.insert(i);
     483           0 :                 setOpenedTrees(opened_trees);
     484             :         }
     485             : 
     486             :         // Delete temporary information used only during setTable()
     487           2 :         delete[] rows;
     488           2 :         allocationComplete();
     489             : 
     490             :         // Clamp scroll bar position
     491           2 :         updateScrollBar();
     492             : }
     493             : 
     494           2 : void GUITable::clear()
     495             : {
     496             :         // Clean up cells and rows
     497           2 :         for (size_t i = 0; i < m_rows.size(); ++i)
     498           0 :                 delete[] m_rows[i].cells;
     499           2 :         m_rows.clear();
     500           2 :         m_visible_rows.clear();
     501             : 
     502             :         // Get colors from skin
     503           2 :         gui::IGUISkin *skin = Environment->getSkin();
     504           2 :         m_color          = skin->getColor(gui::EGDC_BUTTON_TEXT);
     505           2 :         m_background     = skin->getColor(gui::EGDC_3D_HIGH_LIGHT);
     506           2 :         m_highlight      = skin->getColor(gui::EGDC_HIGH_LIGHT);
     507           2 :         m_highlight_text = skin->getColor(gui::EGDC_HIGH_LIGHT_TEXT);
     508             : 
     509             :         // Reset members
     510           2 :         m_is_textlist = false;
     511           2 :         m_has_tree_column = false;
     512           2 :         m_selected = -1;
     513           2 :         m_sel_column = 0;
     514           2 :         m_sel_doubleclick = false;
     515           2 :         m_keynav_time = 0;
     516           2 :         m_keynav_buffer = L"";
     517           2 :         m_border = true;
     518           2 :         m_strings.clear();
     519           2 :         m_images.clear();
     520           2 :         m_alloc_strings.clear();
     521           2 :         m_alloc_images.clear();
     522           2 : }
     523             : 
     524           3 : std::string GUITable::checkEvent()
     525             : {
     526           3 :         s32 sel = getSelected();
     527             :         assert(sel >= 0);
     528             : 
     529           3 :         if (sel == 0) {
     530           0 :                 return "INV";
     531             :         }
     532             : 
     533           6 :         std::ostringstream os(std::ios::binary);
     534           3 :         if (m_sel_doubleclick) {
     535           1 :                 os<<"DCL:";
     536           1 :                 m_sel_doubleclick = false;
     537             :         }
     538             :         else {
     539           2 :                 os<<"CHG:";
     540             :         }
     541           3 :         os<<sel;
     542           3 :         if (!m_is_textlist) {
     543           3 :                 os<<":"<<m_sel_column;
     544             :         }
     545           3 :         return os.str();
     546             : }
     547             : 
     548           4 : s32 GUITable::getSelected() const
     549             : {
     550           4 :         if (m_selected < 0)
     551           0 :                 return 0;
     552             : 
     553             :         assert(m_selected >= 0 && m_selected < (s32) m_visible_rows.size());
     554           4 :         return m_visible_rows[m_selected] + 1;
     555             : }
     556             : 
     557           2 : void GUITable::setSelected(s32 index)
     558             : {
     559           2 :         m_selected = -1;
     560           2 :         m_sel_column = 0;
     561           2 :         m_sel_doubleclick = false;
     562             : 
     563           2 :         --index;
     564             : 
     565           2 :         s32 rowcount = m_rows.size();
     566             : 
     567           2 :         if (index >= rowcount)
     568           0 :                 index = rowcount - 1;
     569           2 :         while (index >= 0 && m_rows[index].visible_index < 0)
     570           0 :                 --index;
     571           2 :         if (index >= 0) {
     572           2 :                 m_selected = m_rows[index].visible_index;
     573             :                 assert(m_selected >= 0 && m_selected < (s32) m_visible_rows.size());
     574             :         }
     575             : 
     576           2 :         autoScroll();
     577           2 : }
     578             : 
     579           1 : GUITable::DynamicData GUITable::getDynamicData() const
     580             : {
     581           1 :         DynamicData dyndata;
     582           1 :         dyndata.selected = getSelected();
     583           1 :         dyndata.scrollpos = m_scrollbar->getPos();
     584           1 :         dyndata.keynav_time = m_keynav_time;
     585           1 :         dyndata.keynav_buffer = m_keynav_buffer;
     586           1 :         if (m_has_tree_column)
     587           0 :                 getOpenedTrees(dyndata.opened_trees);
     588           1 :         return dyndata;
     589             : }
     590             : 
     591           1 : void GUITable::setDynamicData(const DynamicData &dyndata)
     592             : {
     593           1 :         if (m_has_tree_column)
     594           0 :                 setOpenedTrees(dyndata.opened_trees);
     595             : 
     596           1 :         m_keynav_time = dyndata.keynav_time;
     597           1 :         m_keynav_buffer = dyndata.keynav_buffer;
     598             : 
     599           1 :         m_scrollbar->setPos(dyndata.scrollpos);
     600             : 
     601           1 :         setSelected(dyndata.selected);
     602           1 :         m_sel_column = 0;
     603           1 :         m_sel_doubleclick = false;
     604           1 : }
     605             : 
     606           0 : const c8* GUITable::getTypeName() const
     607             : {
     608           0 :         return "GUITable";
     609             : }
     610             : 
     611           2 : void GUITable::updateAbsolutePosition()
     612             : {
     613           2 :         IGUIElement::updateAbsolutePosition();
     614           2 :         updateScrollBar();
     615           2 : }
     616             : 
     617          91 : void GUITable::draw()
     618             : {
     619          91 :         if (!IsVisible)
     620           0 :                 return;
     621             : 
     622          91 :         gui::IGUISkin *skin = Environment->getSkin();
     623             : 
     624             :         // draw background
     625             : 
     626          91 :         bool draw_background = m_background.getAlpha() > 0;
     627          91 :         if (m_border)
     628          91 :                 skin->draw3DSunkenPane(this, m_background,
     629             :                                 true, draw_background,
     630         182 :                                 AbsoluteRect, &AbsoluteClippingRect);
     631           0 :         else if (draw_background)
     632           0 :                 skin->draw2DRectangle(this, m_background,
     633           0 :                                 AbsoluteRect, &AbsoluteClippingRect);
     634             : 
     635             :         // get clipping rect
     636             : 
     637          91 :         core::rect<s32> client_clip(AbsoluteRect);
     638          91 :         client_clip.UpperLeftCorner.Y += 1;
     639          91 :         client_clip.UpperLeftCorner.X += 1;
     640          91 :         client_clip.LowerRightCorner.Y -= 1;
     641          91 :         client_clip.LowerRightCorner.X -= 1;
     642          91 :         if (m_scrollbar->isVisible()) {
     643             :                 client_clip.LowerRightCorner.X =
     644           0 :                                 m_scrollbar->getAbsolutePosition().UpperLeftCorner.X;
     645             :         }
     646          91 :         client_clip.clipAgainst(AbsoluteClippingRect);
     647             : 
     648             :         // draw visible rows
     649             : 
     650          91 :         s32 scrollpos = m_scrollbar->getPos();
     651          91 :         s32 row_min = scrollpos / m_rowheight;
     652          91 :         s32 row_max = (scrollpos + AbsoluteRect.getHeight() - 1)
     653          91 :                         / m_rowheight + 1;
     654          91 :         row_max = MYMIN(row_max, (s32) m_visible_rows.size());
     655             : 
     656          91 :         core::rect<s32> row_rect(AbsoluteRect);
     657          91 :         if (m_scrollbar->isVisible())
     658             :                 row_rect.LowerRightCorner.X -=
     659           0 :                         skin->getSize(gui::EGDS_SCROLLBAR_SIZE);
     660          91 :         row_rect.UpperLeftCorner.Y += row_min * m_rowheight - scrollpos;
     661          91 :         row_rect.LowerRightCorner.Y = row_rect.UpperLeftCorner.Y + m_rowheight;
     662             : 
     663         273 :         for (s32 i = row_min; i < row_max; ++i) {
     664         182 :                 Row *row = &m_rows[m_visible_rows[i]];
     665         182 :                 bool is_sel = i == m_selected;
     666         182 :                 video::SColor color = m_color;
     667             : 
     668         182 :                 if (is_sel) {
     669           7 :                         skin->draw2DRectangle(this, m_highlight, row_rect, &client_clip);
     670           7 :                         color = m_highlight_text;
     671             :                 }
     672             : 
     673         364 :                 for (s32 j = 0; j < row->cellcount; ++j)
     674         182 :                         drawCell(&row->cells[j], color, row_rect, client_clip);
     675             : 
     676         182 :                 row_rect.UpperLeftCorner.Y += m_rowheight;
     677         182 :                 row_rect.LowerRightCorner.Y += m_rowheight;
     678             :         }
     679             : 
     680             :         // Draw children
     681          91 :         IGUIElement::draw();
     682             : }
     683             : 
     684         182 : void GUITable::drawCell(const Cell *cell, video::SColor color,
     685             :                 const core::rect<s32> &row_rect,
     686             :                 const core::rect<s32> &client_clip)
     687             : {
     688         182 :         if ((cell->content_type == COLUMN_TYPE_TEXT)
     689           0 :                         || (cell->content_type == COLUMN_TYPE_TREE)) {
     690             : 
     691         182 :                 core::rect<s32> text_rect = row_rect;
     692         182 :                 text_rect.UpperLeftCorner.X = row_rect.UpperLeftCorner.X
     693         182 :                                 + cell->xpos;
     694         182 :                 text_rect.LowerRightCorner.X = row_rect.UpperLeftCorner.X
     695         182 :                                 + cell->xmax;
     696             : 
     697         182 :                 if (cell->color_defined)
     698           0 :                         color = cell->color;
     699             : 
     700         182 :                 if (m_font) {
     701         182 :                         if (cell->content_type == COLUMN_TYPE_TEXT)
     702         364 :                                 m_font->draw(m_strings[cell->content_index],
     703             :                                                 text_rect, color,
     704         364 :                                                 false, true, &client_clip);
     705             :                         else // tree
     706           0 :                                 m_font->draw(cell->content_index ? L"+" : L"-",
     707             :                                                 text_rect, color,
     708           0 :                                                 false, true, &client_clip);
     709         182 :                 }
     710             :         }
     711           0 :         else if (cell->content_type == COLUMN_TYPE_IMAGE) {
     712             : 
     713           0 :                 if (cell->content_index < 0)
     714           0 :                         return;
     715             : 
     716           0 :                 video::IVideoDriver *driver = Environment->getVideoDriver();
     717           0 :                 video::ITexture *image = m_images[cell->content_index];
     718             : 
     719           0 :                 if (image) {
     720             :                         core::position2d<s32> dest_pos =
     721           0 :                                         row_rect.UpperLeftCorner;
     722           0 :                         dest_pos.X += cell->xpos;
     723             :                         core::rect<s32> source_rect(
     724             :                                         core::position2d<s32>(0, 0),
     725           0 :                                         image->getOriginalSize());
     726           0 :                         s32 imgh = source_rect.LowerRightCorner.Y;
     727           0 :                         s32 rowh = row_rect.getHeight();
     728           0 :                         if (imgh < rowh)
     729           0 :                                 dest_pos.Y += (rowh - imgh) / 2;
     730             :                         else
     731           0 :                                 source_rect.LowerRightCorner.Y = rowh;
     732             : 
     733           0 :                         video::SColor color(255, 255, 255, 255);
     734             : 
     735             :                         driver->draw2DImage(image, dest_pos, source_rect,
     736           0 :                                         &client_clip, color, true);
     737             :                 }
     738             :         }
     739             : }
     740             : 
     741          11 : bool GUITable::OnEvent(const SEvent &event)
     742             : {
     743          11 :         if (!isEnabled())
     744           0 :                 return IGUIElement::OnEvent(event);
     745             : 
     746          11 :         if (event.EventType == EET_KEY_INPUT_EVENT) {
     747           0 :                 if (event.KeyInput.PressedDown && (
     748           0 :                                 event.KeyInput.Key == KEY_DOWN ||
     749           0 :                                 event.KeyInput.Key == KEY_UP   ||
     750           0 :                                 event.KeyInput.Key == KEY_HOME ||
     751           0 :                                 event.KeyInput.Key == KEY_END  ||
     752           0 :                                 event.KeyInput.Key == KEY_NEXT ||
     753           0 :                                 event.KeyInput.Key == KEY_PRIOR)) {
     754           0 :                         s32 offset = 0;
     755           0 :                         switch (event.KeyInput.Key) {
     756             :                                 case KEY_DOWN:
     757           0 :                                         offset = 1;
     758           0 :                                         break;
     759             :                                 case KEY_UP:
     760           0 :                                         offset = -1;
     761           0 :                                         break;
     762             :                                 case KEY_HOME:
     763           0 :                                         offset = - (s32) m_visible_rows.size();
     764           0 :                                         break;
     765             :                                 case KEY_END:
     766           0 :                                         offset = m_visible_rows.size();
     767           0 :                                         break;
     768             :                                 case KEY_NEXT:
     769           0 :                                         offset = AbsoluteRect.getHeight() / m_rowheight;
     770           0 :                                         break;
     771             :                                 case KEY_PRIOR:
     772           0 :                                         offset = - (s32) (AbsoluteRect.getHeight() / m_rowheight);
     773           0 :                                         break;
     774             :                                 default:
     775           0 :                                         break;
     776             :                         }
     777           0 :                         s32 old_selected = m_selected;
     778           0 :                         s32 rowcount = m_visible_rows.size();
     779           0 :                         if (rowcount != 0) {
     780           0 :                                 m_selected = rangelim(m_selected + offset, 0, rowcount-1);
     781           0 :                                 autoScroll();
     782             :                         }
     783             : 
     784           0 :                         if (m_selected != old_selected)
     785           0 :                                 sendTableEvent(0, false);
     786             : 
     787           0 :                         return true;
     788             :                 }
     789           0 :                 else if (event.KeyInput.PressedDown && (
     790           0 :                                 event.KeyInput.Key == KEY_LEFT ||
     791           0 :                                 event.KeyInput.Key == KEY_RIGHT)) {
     792             :                         // Open/close subtree via keyboard
     793           0 :                         if (m_selected >= 0) {
     794           0 :                                 int dir = event.KeyInput.Key == KEY_LEFT ? -1 : 1;
     795           0 :                                 toggleVisibleTree(m_selected, dir, true);
     796             :                         }
     797           0 :                         return true;
     798             :                 }
     799           0 :                 else if (!event.KeyInput.PressedDown && (
     800           0 :                                 event.KeyInput.Key == KEY_RETURN ||
     801           0 :                                 event.KeyInput.Key == KEY_SPACE)) {
     802           0 :                         sendTableEvent(0, true);
     803           0 :                         return true;
     804             :                 }
     805           0 :                 else if (event.KeyInput.Key == KEY_ESCAPE ||
     806           0 :                                 event.KeyInput.Key == KEY_SPACE) {
     807             :                         // pass to parent
     808             :                 }
     809           0 :                 else if (event.KeyInput.PressedDown && event.KeyInput.Char) {
     810             :                         // change selection based on text as it is typed
     811           0 :                         s32 now = getTimeMs();
     812           0 :                         if (now - m_keynav_time >= 500)
     813           0 :                                 m_keynav_buffer = L"";
     814           0 :                         m_keynav_time = now;
     815             : 
     816             :                         // add to key buffer if not a key repeat
     817           0 :                         if (!(m_keynav_buffer.size() == 1 &&
     818           0 :                                         m_keynav_buffer[0] == event.KeyInput.Char)) {
     819           0 :                                 m_keynav_buffer.append(event.KeyInput.Char);
     820             :                         }
     821             : 
     822             :                         // find the selected item, starting at the current selection
     823             :                         // don't change selection if the key buffer matches the current item
     824           0 :                         s32 old_selected = m_selected;
     825           0 :                         s32 start = MYMAX(m_selected, 0);
     826           0 :                         s32 rowcount = m_visible_rows.size();
     827           0 :                         for (s32 k = 1; k < rowcount; ++k) {
     828           0 :                                 s32 current = start + k;
     829           0 :                                 if (current >= rowcount)
     830           0 :                                         current -= rowcount;
     831           0 :                                 if (doesRowStartWith(getRow(current), m_keynav_buffer)) {
     832           0 :                                         m_selected = current;
     833           0 :                                         break;
     834             :                                 }
     835             :                         }
     836           0 :                         autoScroll();
     837           0 :                         if (m_selected != old_selected)
     838           0 :                                 sendTableEvent(0, false);
     839             : 
     840           0 :                         return true;
     841             :                 }
     842             :         }
     843          11 :         if (event.EventType == EET_MOUSE_INPUT_EVENT) {
     844           4 :                 core::position2d<s32> p(event.MouseInput.X, event.MouseInput.Y);
     845             : 
     846           4 :                 if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) {
     847           0 :                         m_scrollbar->setPos(m_scrollbar->getPos() +
     848           0 :                                         (event.MouseInput.Wheel < 0 ? -3 : 3) *
     849           0 :                                         - (s32) m_rowheight / 2);
     850           0 :                         return true;
     851             :                 }
     852             : 
     853             :                 // Find hovered row and cell
     854           4 :                 bool really_hovering = false;
     855           4 :                 s32 row_i = getRowAt(p.Y, really_hovering);
     856           4 :                 const Cell *cell = NULL;
     857           4 :                 if (really_hovering) {
     858           4 :                         s32 cell_j = getCellAt(p.X, row_i);
     859           4 :                         if (cell_j >= 0)
     860           4 :                                 cell = &(getRow(row_i)->cells[cell_j]);
     861             :                 }
     862             : 
     863             :                 // Update tooltip
     864           4 :                 setToolTipText(cell ? m_strings[cell->tooltip_index].c_str() : L"");
     865             : 
     866             :                 // Fix for #1567/#1806:
     867             :                 // IGUIScrollBar passes double click events to its parent,
     868             :                 // which we don't want. Detect this case and discard the event
     869          12 :                 if (event.MouseInput.Event != EMIE_MOUSE_MOVED &&
     870           4 :                                 m_scrollbar->isVisible() &&
     871           0 :                                 m_scrollbar->isPointInside(p))
     872           0 :                         return true;
     873             : 
     874          10 :                 if (event.MouseInput.isLeftPressed() &&
     875           3 :                                 (isPointInside(p) ||
     876           0 :                                  event.MouseInput.Event == EMIE_MOUSE_MOVED)) {
     877           3 :                         s32 sel_column = 0;
     878           3 :                         bool sel_doubleclick = (event.MouseInput.Event
     879           3 :                                         == EMIE_LMOUSE_DOUBLE_CLICK);
     880           3 :                         bool plusminus_clicked = false;
     881             : 
     882             :                         // For certain events (left click), report column
     883             :                         // Also open/close subtrees when the +/- is clicked
     884           6 :                         if (cell && (
     885           4 :                                         event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN ||
     886           1 :                                         event.MouseInput.Event == EMIE_LMOUSE_DOUBLE_CLICK ||
     887           0 :                                         event.MouseInput.Event == EMIE_LMOUSE_TRIPLE_CLICK)) {
     888           3 :                                 sel_column = cell->reported_column;
     889           3 :                                 if (cell->content_type == COLUMN_TYPE_TREE)
     890           0 :                                         plusminus_clicked = true;
     891             :                         }
     892             : 
     893           3 :                         if (plusminus_clicked) {
     894           0 :                                 if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
     895           0 :                                         toggleVisibleTree(row_i, 0, false);
     896             :                                 }
     897             :                         }
     898             :                         else {
     899             :                                 // Normal selection
     900           3 :                                 s32 old_selected = m_selected;
     901           3 :                                 m_selected = row_i;
     902           3 :                                 autoScroll();
     903             : 
     904           3 :                                 if (m_selected != old_selected ||
     905           0 :                                                 sel_column >= 1 ||
     906             :                                                 sel_doubleclick) {
     907           3 :                                         sendTableEvent(sel_column, sel_doubleclick);
     908             :                                 }
     909             :                         }
     910             :                 }
     911           4 :                 return true;
     912             :         }
     913          14 :         if (event.EventType == EET_GUI_EVENT &&
     914           7 :                         event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED &&
     915           0 :                         event.GUIEvent.Caller == m_scrollbar) {
     916             :                 // Don't pass events from our scrollbar to the parent
     917           0 :                 return true;
     918             :         }
     919             : 
     920           7 :         return IGUIElement::OnEvent(event);
     921             : }
     922             : 
     923             : /******************************************************************************/
     924             : /* GUITable helper functions                                                  */
     925             : /******************************************************************************/
     926             : 
     927           6 : s32 GUITable::allocString(const std::string &text)
     928             : {
     929           6 :         std::map<std::string, s32>::iterator it = m_alloc_strings.find(text);
     930           6 :         if (it == m_alloc_strings.end()) {
     931           6 :                 s32 id = m_strings.size();
     932          12 :                 std::wstring wtext = narrow_to_wide(text);
     933           6 :                 m_strings.push_back(core::stringw(wtext.c_str()));
     934           6 :                 m_alloc_strings.insert(std::make_pair(text, id));
     935           6 :                 return id;
     936             :         }
     937             :         else {
     938           0 :                 return it->second;
     939             :         }
     940             : }
     941             : 
     942           0 : s32 GUITable::allocImage(const std::string &imagename)
     943             : {
     944           0 :         std::map<std::string, s32>::iterator it = m_alloc_images.find(imagename);
     945           0 :         if (it == m_alloc_images.end()) {
     946           0 :                 s32 id = m_images.size();
     947           0 :                 m_images.push_back(m_tsrc->getTexture(imagename));
     948           0 :                 m_alloc_images.insert(std::make_pair(imagename, id));
     949           0 :                 return id;
     950             :         }
     951             :         else {
     952           0 :                 return it->second;
     953             :         }
     954             : }
     955             : 
     956           2 : void GUITable::allocationComplete()
     957             : {
     958             :         // Called when done with creating rows and cells from table data,
     959             :         // i.e. when allocString and allocImage won't be called anymore
     960           2 :         m_alloc_strings.clear();
     961           2 :         m_alloc_images.clear();
     962           2 : }
     963             : 
     964           8 : const GUITable::Row* GUITable::getRow(s32 i) const
     965             : {
     966           8 :         if (i >= 0 && i < (s32) m_visible_rows.size())
     967           8 :                 return &m_rows[m_visible_rows[i]];
     968             :         else
     969           0 :                 return NULL;
     970             : }
     971             : 
     972           0 : bool GUITable::doesRowStartWith(const Row *row, const core::stringw &str) const
     973             : {
     974           0 :         if (row == NULL)
     975           0 :                 return false;
     976             : 
     977           0 :         for (s32 j = 0; j < row->cellcount; ++j) {
     978           0 :                 Cell *cell = &row->cells[j];
     979           0 :                 if (cell->content_type == COLUMN_TYPE_TEXT) {
     980           0 :                         const core::stringw &cellstr = m_strings[cell->content_index];
     981           0 :                         if (cellstr.size() >= str.size() &&
     982           0 :                                         str.equals_ignore_case(cellstr.subString(0, str.size())))
     983           0 :                                 return true;
     984             :                 }
     985             :         }
     986           0 :         return false;
     987             : }
     988             : 
     989           4 : s32 GUITable::getRowAt(s32 y, bool &really_hovering) const
     990             : {
     991           4 :         really_hovering = false;
     992             : 
     993           4 :         s32 rowcount = m_visible_rows.size();
     994           4 :         if (rowcount == 0)
     995           0 :                 return -1;
     996             : 
     997             :         // Use arithmetic to find row
     998           4 :         s32 rel_y = y - AbsoluteRect.UpperLeftCorner.Y - 1;
     999           4 :         s32 i = (rel_y + m_scrollbar->getPos()) / m_rowheight;
    1000             : 
    1001           4 :         if (i >= 0 && i < rowcount) {
    1002           4 :                 really_hovering = true;
    1003           4 :                 return i;
    1004             :         }
    1005           0 :         else if (i < 0)
    1006           0 :                 return 0;
    1007             :         else
    1008           0 :                 return rowcount - 1;
    1009             : 
    1010             : }
    1011             : 
    1012           4 : s32 GUITable::getCellAt(s32 x, s32 row_i) const
    1013             : {
    1014           4 :         const Row *row = getRow(row_i);
    1015           4 :         if (row == NULL)
    1016           0 :                 return -1;
    1017             : 
    1018             :         // Use binary search to find cell in row
    1019           4 :         s32 rel_x = x - AbsoluteRect.UpperLeftCorner.X - 1;
    1020           4 :         s32 jmin = 0;
    1021           4 :         s32 jmax = row->cellcount - 1;
    1022           4 :         while (jmin < jmax) {
    1023           0 :                 s32 pivot = jmin + (jmax - jmin) / 2;
    1024             :                 assert(pivot >= 0 && pivot < row->cellcount);
    1025           0 :                 const Cell *cell = &row->cells[pivot];
    1026             : 
    1027           0 :                 if (rel_x >= cell->xmin && rel_x <= cell->xmax)
    1028           0 :                         return pivot;
    1029           0 :                 else if (rel_x < cell->xmin)
    1030           0 :                         jmax = pivot - 1;
    1031             :                 else
    1032           0 :                         jmin = pivot + 1;
    1033             :         }
    1034             : 
    1035           8 :         if (jmin >= 0 && jmin < row->cellcount &&
    1036           8 :                         rel_x >= row->cells[jmin].xmin &&
    1037           4 :                         rel_x <= row->cells[jmin].xmax)
    1038           4 :                 return jmin;
    1039             :         else
    1040           0 :                 return -1;
    1041             : }
    1042             : 
    1043           5 : void GUITable::autoScroll()
    1044             : {
    1045           5 :         if (m_selected >= 0) {
    1046           5 :                 s32 pos = m_scrollbar->getPos();
    1047           5 :                 s32 maxpos = m_selected * m_rowheight;
    1048           5 :                 s32 minpos = maxpos - (AbsoluteRect.getHeight() - m_rowheight);
    1049           5 :                 if (pos > maxpos)
    1050           0 :                         m_scrollbar->setPos(maxpos);
    1051           5 :                 else if (pos < minpos)
    1052           0 :                         m_scrollbar->setPos(minpos);
    1053             :         }
    1054           5 : }
    1055             : 
    1056           4 : void GUITable::updateScrollBar()
    1057             : {
    1058           4 :         s32 totalheight = m_rowheight * m_visible_rows.size();
    1059           4 :         s32 scrollmax = MYMAX(0, totalheight - AbsoluteRect.getHeight());
    1060           4 :         m_scrollbar->setVisible(scrollmax > 0);
    1061           4 :         m_scrollbar->setMax(scrollmax);
    1062           4 :         m_scrollbar->setSmallStep(m_rowheight);
    1063           4 :         m_scrollbar->setLargeStep(2 * m_rowheight);
    1064           4 : }
    1065             : 
    1066           3 : void GUITable::sendTableEvent(s32 column, bool doubleclick)
    1067             : {
    1068           3 :         m_sel_column = column;
    1069           3 :         m_sel_doubleclick = doubleclick;
    1070           3 :         if (Parent) {
    1071             :                 SEvent e;
    1072           3 :                 memset(&e, 0, sizeof e);
    1073           3 :                 e.EventType = EET_GUI_EVENT;
    1074           3 :                 e.GUIEvent.Caller = this;
    1075           3 :                 e.GUIEvent.Element = 0;
    1076           3 :                 e.GUIEvent.EventType = gui::EGET_TABLE_CHANGED;
    1077           3 :                 Parent->OnEvent(e);
    1078             :         }
    1079           3 : }
    1080             : 
    1081           0 : void GUITable::getOpenedTrees(std::set<s32> &opened_trees) const
    1082             : {
    1083           0 :         opened_trees.clear();
    1084           0 :         s32 rowcount = m_rows.size();
    1085           0 :         for (s32 i = 0; i < rowcount - 1; ++i) {
    1086           0 :                 if (m_rows[i].indent < m_rows[i+1].indent &&
    1087           0 :                                 m_rows[i+1].visible_index != -2)
    1088           0 :                         opened_trees.insert(i);
    1089             :         }
    1090           0 : }
    1091             : 
    1092           0 : void GUITable::setOpenedTrees(const std::set<s32> &opened_trees)
    1093             : {
    1094           0 :         s32 old_selected = getSelected();
    1095             : 
    1096           0 :         std::vector<s32> parents;
    1097           0 :         std::vector<s32> closed_parents;
    1098             : 
    1099           0 :         m_visible_rows.clear();
    1100             : 
    1101           0 :         for (size_t i = 0; i < m_rows.size(); ++i) {
    1102           0 :                 Row *row = &m_rows[i];
    1103             : 
    1104             :                 // Update list of ancestors
    1105           0 :                 while (!parents.empty() && m_rows[parents.back()].indent >= row->indent)
    1106           0 :                         parents.pop_back();
    1107           0 :                 while (!closed_parents.empty() &&
    1108           0 :                                 m_rows[closed_parents.back()].indent >= row->indent)
    1109           0 :                         closed_parents.pop_back();
    1110             : 
    1111             :                 assert(closed_parents.size() <= parents.size());
    1112             : 
    1113           0 :                 if (closed_parents.empty()) {
    1114             :                         // Visible row
    1115           0 :                         row->visible_index = m_visible_rows.size();
    1116           0 :                         m_visible_rows.push_back(i);
    1117             :                 }
    1118           0 :                 else if (parents.back() == closed_parents.back()) {
    1119             :                         // Invisible row, direct parent is closed
    1120           0 :                         row->visible_index = -2;
    1121             :                 }
    1122             :                 else {
    1123             :                         // Invisible row, direct parent is open, some ancestor is closed
    1124           0 :                         row->visible_index = -1;
    1125             :                 }
    1126             : 
    1127             :                 // If not a leaf, add to parents list
    1128           0 :                 if (i < m_rows.size()-1 && row->indent < m_rows[i+1].indent) {
    1129           0 :                         parents.push_back(i);
    1130             : 
    1131           0 :                         s32 content_index = 0; // "-", open
    1132           0 :                         if (opened_trees.count(i) == 0) {
    1133           0 :                                 closed_parents.push_back(i);
    1134           0 :                                 content_index = 1; // "+", closed
    1135             :                         }
    1136             : 
    1137             :                         // Update all cells of type "tree"
    1138           0 :                         for (s32 j = 0; j < row->cellcount; ++j)
    1139           0 :                                 if (row->cells[j].content_type == COLUMN_TYPE_TREE)
    1140           0 :                                         row->cells[j].content_index = content_index;
    1141             :                 }
    1142             :         }
    1143             : 
    1144           0 :         updateScrollBar();
    1145             : 
    1146           0 :         setSelected(old_selected);
    1147           0 : }
    1148             : 
    1149           0 : void GUITable::openTree(s32 to_open)
    1150             : {
    1151           0 :         std::set<s32> opened_trees;
    1152           0 :         getOpenedTrees(opened_trees);
    1153           0 :         opened_trees.insert(to_open);
    1154           0 :         setOpenedTrees(opened_trees);
    1155           0 : }
    1156             : 
    1157           0 : void GUITable::closeTree(s32 to_close)
    1158             : {
    1159           0 :         std::set<s32> opened_trees;
    1160           0 :         getOpenedTrees(opened_trees);
    1161           0 :         opened_trees.erase(to_close);
    1162           0 :         setOpenedTrees(opened_trees);
    1163           0 : }
    1164             : 
    1165             : // The following function takes a visible row index (hidden rows skipped)
    1166             : // dir: -1 = left (close), 0 = auto (toggle), 1 = right (open)
    1167           0 : void GUITable::toggleVisibleTree(s32 row_i, int dir, bool move_selection)
    1168             : {
    1169             :         // Check if the chosen tree is currently open
    1170           0 :         const Row *row = getRow(row_i);
    1171           0 :         if (row == NULL)
    1172           0 :                 return;
    1173             : 
    1174           0 :         bool was_open = false;
    1175           0 :         for (s32 j = 0; j < row->cellcount; ++j) {
    1176           0 :                 if (row->cells[j].content_type == COLUMN_TYPE_TREE) {
    1177           0 :                         was_open = row->cells[j].content_index == 0;
    1178           0 :                         break;
    1179             :                 }
    1180             :         }
    1181             : 
    1182             :         // Check if the chosen tree should be opened
    1183           0 :         bool do_open = !was_open;
    1184           0 :         if (dir < 0)
    1185           0 :                 do_open = false;
    1186           0 :         else if (dir > 0)
    1187           0 :                 do_open = true;
    1188             : 
    1189             :         // Close or open the tree; the heavy lifting is done by setOpenedTrees
    1190           0 :         if (was_open && !do_open)
    1191           0 :                 closeTree(m_visible_rows[row_i]);
    1192           0 :         else if (!was_open && do_open)
    1193           0 :                 openTree(m_visible_rows[row_i]);
    1194             : 
    1195             :         // Change selected row if requested by caller,
    1196             :         // this is useful for keyboard navigation
    1197           0 :         if (move_selection) {
    1198           0 :                 s32 sel = row_i;
    1199           0 :                 if (was_open && do_open) {
    1200             :                         // Move selection to first child
    1201           0 :                         const Row *maybe_child = getRow(sel + 1);
    1202           0 :                         if (maybe_child && maybe_child->indent > row->indent)
    1203           0 :                                 sel++;
    1204             :                 }
    1205           0 :                 else if (!was_open && !do_open) {
    1206             :                         // Move selection to parent
    1207             :                         assert(getRow(sel) != NULL);
    1208           0 :                         while (sel > 0 && getRow(sel - 1)->indent >= row->indent)
    1209           0 :                                 sel--;
    1210           0 :                         sel--;
    1211           0 :                         if (sel < 0)  // was root already selected?
    1212           0 :                                 sel = row_i;
    1213             :                 }
    1214           0 :                 if (sel != m_selected) {
    1215           0 :                         m_selected = sel;
    1216           0 :                         autoScroll();
    1217           0 :                         sendTableEvent(0, false);
    1218             :                 }
    1219             :         }
    1220             : }
    1221             : 
    1222           4 : void GUITable::alignContent(Cell *cell, s32 xmax, s32 content_width, s32 align)
    1223             : {
    1224             :         // requires that cell.xmin, cell.xmax are properly set
    1225             :         // align = 0: left aligned, 1: centered, 2: right aligned, 3: inline
    1226           4 :         if (align == 0) {
    1227           4 :                 cell->xpos = cell->xmin;
    1228           4 :                 cell->xmax = xmax;
    1229             :         }
    1230           0 :         else if (align == 1) {
    1231           0 :                 cell->xpos = (cell->xmin + xmax - content_width) / 2;
    1232           0 :                 cell->xmax = xmax;
    1233             :         }
    1234           0 :         else if (align == 2) {
    1235           0 :                 cell->xpos = xmax - content_width;
    1236           0 :                 cell->xmax = xmax;
    1237             :         }
    1238             :         else {
    1239             :                 // inline alignment: the cells of the column don't have an aligned
    1240             :                 // right border, the right border of each cell depends on the content
    1241           0 :                 cell->xpos = cell->xmin;
    1242           0 :                 cell->xmax = cell->xmin + content_width;
    1243             :         }
    1244           7 : }

Generated by: LCOV version 1.11