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 : }
|