ManaPlus
guitable.cpp
Go to the documentation of this file.
1 /*
2  * The ManaPlus Client
3  * Copyright (C) 2008-2009 The Mana World Development Team
4  * Copyright (C) 2009-2010 The Mana Developers
5  * Copyright (C) 2011-2019 The ManaPlus Developers
6  * Copyright (C) 2019-2021 Andrei Karas
7  *
8  * This file is part of The ManaPlus Client.
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program. If not, see <http://www.gnu.org/licenses/>.
22  */
23 
24 #include "gui/widgets/guitable.h"
25 
26 #include "settings.h"
27 
28 #include "gui/gui.h"
29 
30 #include "gui/models/tablemodel.h"
31 
33 
34 #include "render/graphics.h"
35 
36 #include "utils/delete2.h"
37 #include "utils/dtor.h"
38 
39 #include "debug.h"
40 
41 float GuiTable::mAlpha = 1.0;
42 
43 GuiTable::GuiTable(const Widget2 *const widget,
44  TableModel *const initial_model,
45  const Opaque opacity) :
46  Widget(widget),
47  MouseListener(),
48  KeyListener(),
50  mModel(nullptr),
51  mTopWidget(nullptr),
52  mActionListeners2(),
53  mHighlightColor(getThemeColor(ThemeColorId::HIGHLIGHT, 255U)),
54  mSelectedRow(-1),
55  mSelectedColumn(-1),
56  mLinewiseMode(false),
57  mWrappingEnabled(false),
58  mOpaque(opacity),
59  mSelectableGui(true)
60 {
61  mAllowLogic = false;
63 
64  setModel(initial_model);
65  setFocusable(true);
66 
67  addMouseListener(this);
68  addKeyListener(this);
69 }
70 
72 {
73  if (gui != nullptr)
74  gui->removeDragged(this);
75 
78 }
79 
81 {
82  return mModel;
83 }
84 
85 void GuiTable::setModel(TableModel *const new_model)
86 {
87  if (mModel != nullptr)
88  {
90  mModel->removeListener(this);
91  }
92 
93  mModel = new_model;
95 
96  new_model->installListener(this);
98 }
99 
101 {
102  if (mModel == nullptr)
103  return;
104 
105  const int rows_nr = mModel->getRows();
106  const int columns_nr = mModel->getColumns();
107  int width = 0;
108 
109  if (mSelectableGui)
110  {
111  if (mSelectedRow >= rows_nr)
112  mSelectedRow = rows_nr - 1;
113 
114  if (mSelectedColumn >= columns_nr)
115  mSelectedColumn = columns_nr - 1;
116  }
117 
118  for (int i = 0; i < columns_nr; i++)
119  width += getColumnWidth(i);
120 
121  setWidth(width);
122  setHeight(getRowHeight() * rows_nr);
123 }
124 
125 void GuiTable::setSelected(const int row, const int column)
126 {
127  mSelectedColumn = column;
128  mSelectedRow = row;
129 }
130 
132 {
133  return mSelectedRow;
134 }
135 
137 {
138  return mSelectedColumn;
139 }
140 
142 {
143  return mModel->getRowHeight() + 4; // border
144 }
145 
146 int GuiTable::getColumnWidth(const int i) const
147 {
148  return mModel->getColumnWidth(i) + 4; // border
149 }
150 
151 void GuiTable::setSelectedRow(const int selected)
152 {
153  if (!mSelectableGui)
154  {
155  mSelectedRow = -1;
156  }
157  else
158  {
159  const int rows = mModel->getRows();
160  if (selected < 0 && !mWrappingEnabled)
161  {
162  mSelectedRow = -1;
163  }
164  else if (selected >= rows && mWrappingEnabled)
165  {
166  mSelectedRow = 0;
167  }
168  else if ((selected >= rows && !mWrappingEnabled) ||
169  (selected < 0 && mWrappingEnabled))
170  {
171  mSelectedRow = rows - 1;
172  }
173  else
174  {
175  mSelectedRow = selected;
176  }
177  }
178 }
179 
180 void GuiTable::setSelectedColumn(const int selected)
181 {
182  const int columns = mModel->getColumns();
183  if ((selected >= columns && mWrappingEnabled) ||
184  (selected < 0 && !mWrappingEnabled))
185  {
186  mSelectedColumn = 0;
187  }
188  else if ((selected >= columns && !mWrappingEnabled) ||
189  (selected < 0 && mWrappingEnabled))
190  {
191  mSelectedColumn = columns - 1;
192  }
193  else
194  {
195  mSelectedColumn = selected;
196  }
197 }
198 
200 {
202  mActionListeners2.clear();
203 }
204 
206 {
207  const int rows = mModel->getRows();
208  const int columns = mModel->getColumns();
209 
210  for (int row = 0; row < rows; ++row)
211  {
212  for (int column = 0; column < columns; ++column)
213  {
214  Widget *const widget = mModel->getElementAt(row, column);
215  if (widget != nullptr)
216  {
218  this, widget, row, column));
219  }
220  }
221  }
222 
224 }
225 
226 // -- widget ops
227 void GuiTable::draw(Graphics *const graphics)
228 {
229  if (getRowHeight() == 0)
230  return;
231 
232  BLOCK_START("GuiTable::draw")
233  if (settings.guiAlpha != mAlpha)
235 
236  const Rect &rect = mDimension;
237  const int width = rect.width;
238  const int height = rect.height;
239  const int y = rect.y;
240  if (mOpaque == Opaque_true)
241  {
242  mBackgroundColor.a = CAST_U32(mAlpha * 255.0F);
243  graphics->setColor(mBackgroundColor);
244  graphics->fillRectangle(Rect(0, 0, width, height));
245  }
246 
247  // First, determine how many rows we need to draw,
248  // and where we should start.
249  int rHeight = getRowHeight();
250  if (rHeight == 0)
251  rHeight = 1;
252  int first_row = -(y / rHeight);
253 
254  if (first_row < 0)
255  first_row = 0;
256 
257  unsigned int rows_nr = CAST_U32(1 +
258  height / rHeight); // May overestimate by one.
259  unsigned int max_rows_nr;
260  if (mModel->getRows() < first_row)
261  {
262  max_rows_nr = 0U;
263  }
264  else
265  {
266  max_rows_nr = CAST_U32(
267  mModel->getRows() - first_row); // clip if neccessary:
268  }
269  if (max_rows_nr < rows_nr)
270  rows_nr = max_rows_nr;
271 
272  // Now determine the first and last column
273  // Take the easy way out; these are usually bounded and all visible.
274  const unsigned first_column = 0;
275  const unsigned last_column1 = CAST_U32(
276  mModel->getColumns());
277 
278  int y_offset = first_row * rHeight;
279 
280  for (unsigned int r = CAST_U32(first_row);
281  r < CAST_U32(first_row) + rows_nr;
282  ++r)
283  {
284  int x_offset = 0;
285 
286  for (unsigned c = first_column; c + 1 <= last_column1; ++c)
287  {
288  Widget *const widget = mModel->getElementAt(CAST_S32(r),
289  CAST_S32(c));
290  const int cWidth = CAST_S32(getColumnWidth(
291  CAST_S32(c)));
292  if (widget != nullptr)
293  {
294  Rect bounds(x_offset, y_offset, cWidth, rHeight);
295 
296  if (widget == mTopWidget)
297  {
298  bounds.height = widget->getHeight();
299  bounds.width = widget->getWidth();
300  }
301 
302  widget->setDimension(bounds);
303 
304  if (mSelectedRow > -1)
305  {
307  mAlpha * 255.0F);
308  graphics->setColor(mHighlightColor);
309 
310  if (mLinewiseMode && r == CAST_U32(
311  mSelectedRow) && c == 0)
312  {
313  graphics->fillRectangle(Rect(0, y_offset,
314  width, rHeight));
315  }
316  else if (!mLinewiseMode && mSelectedColumn > 0
317  && c == CAST_U32(mSelectedColumn)
318  && r == CAST_U32(mSelectedRow))
319  {
320  graphics->fillRectangle(Rect(
321  x_offset, y_offset, cWidth, rHeight));
322  }
323  }
324  graphics->pushClipArea(bounds);
325  widget->draw(graphics);
326  graphics->popClipArea();
327  }
328 
329  x_offset += cWidth;
330  }
331 
332  y_offset += rHeight;
333  }
334 
335  if (mTopWidget != nullptr)
336  {
337  const Rect &bounds = mTopWidget->getDimension();
338  graphics->pushClipArea(bounds);
339  mTopWidget->draw(graphics);
340  graphics->popClipArea();
341  }
342  BLOCK_END("GuiTable::draw")
343 }
344 
345 void GuiTable::safeDraw(Graphics *const graphics)
346 {
347  if (getRowHeight() == 0)
348  return;
349 
350  BLOCK_START("GuiTable::draw")
351  if (settings.guiAlpha != mAlpha)
353 
354  const Rect &rect = mDimension;
355  const int width = rect.width;
356  const int height = rect.height;
357  const int y = rect.y;
358  if (mOpaque == Opaque_true)
359  {
360  mBackgroundColor.a = CAST_U32(mAlpha * 255.0F);
361  graphics->setColor(mBackgroundColor);
362  graphics->fillRectangle(Rect(0, 0, width, height));
363  }
364 
365  // First, determine how many rows we need to draw,
366  // and where we should start.
367  int rHeight = getRowHeight();
368  if (rHeight == 0)
369  rHeight = 1;
370  int first_row = -(y / rHeight);
371 
372  if (first_row < 0)
373  first_row = 0;
374 
375  unsigned int rows_nr = CAST_U32(
376  1 + height / rHeight); // May overestimate by one.
377  unsigned int max_rows_nr;
378  if (mModel->getRows() < first_row)
379  {
380  max_rows_nr = 0;
381  }
382  else
383  {
384  max_rows_nr = CAST_U32(
385  mModel->getRows() - first_row); // clip if neccessary:
386  }
387  if (max_rows_nr < rows_nr)
388  rows_nr = max_rows_nr;
389 
390  // Now determine the first and last column
391  // Take the easy way out; these are usually bounded and all visible.
392  const unsigned int first_column = 0;
393  const unsigned int last_column1 = CAST_U32(
394  mModel->getColumns());
395 
396  int y_offset = first_row * rHeight;
397 
398  for (unsigned int r = CAST_U32(first_row);
399  r < CAST_U32(first_row + CAST_S32(rows_nr));
400  ++r)
401  {
402  int x_offset = 0;
403 
404  for (unsigned c = first_column; c + 1 <= last_column1; ++c)
405  {
406  Widget *const widget = mModel->getElementAt(CAST_S32(r),
407  CAST_S32(c));
408  const int cWidth = CAST_S32(getColumnWidth(
409  CAST_S32(c)));
410  if (widget != nullptr)
411  {
412  Rect bounds(x_offset, y_offset, cWidth, rHeight);
413 
414  if (widget == mTopWidget)
415  {
416  bounds.height = widget->getHeight();
417  bounds.width = widget->getWidth();
418  }
419 
420  widget->setDimension(bounds);
421 
422  if (mSelectedRow > -1)
423  {
425  mAlpha * 255.0F);
426  graphics->setColor(mHighlightColor);
427 
428  if (mLinewiseMode && r == CAST_U32(
429  mSelectedRow) && c == 0)
430  {
431  graphics->fillRectangle(Rect(0, y_offset,
432  width, rHeight));
433  }
434  else if (!mLinewiseMode && mSelectedColumn > 0
435  && c == CAST_U32(mSelectedColumn)
436  && r == CAST_U32(mSelectedRow))
437  {
438  graphics->fillRectangle(Rect(
439  x_offset, y_offset, cWidth, rHeight));
440  }
441  }
442  graphics->pushClipArea(bounds);
443  widget->safeDraw(graphics);
444  graphics->popClipArea();
445  }
446 
447  x_offset += cWidth;
448  }
449 
450  y_offset += rHeight;
451  }
452 
453  if (mTopWidget != nullptr)
454  {
455  const Rect &bounds = mTopWidget->getDimension();
456  graphics->pushClipArea(bounds);
457  mTopWidget->safeDraw(graphics);
458  graphics->popClipArea();
459  }
460  BLOCK_END("GuiTable::draw")
461 }
462 
463 void GuiTable::moveToTop(Widget *const widget)
464 {
465  Widget::moveToTop(widget);
466  mTopWidget = widget;
467 }
468 
469 void GuiTable::moveToBottom(Widget *const widget)
470 {
471  Widget::moveToBottom(widget);
472  if (widget == mTopWidget)
473  mTopWidget = nullptr;
474 }
475 
477 {
478  return Rect(0, 0, mDimension.width, mDimension.height);
479 }
480 
481 // -- KeyListener notifications
483 {
484  const InputActionT action = event.getActionId();
485 
486  if (action == InputAction::GUI_SELECT)
487  {
489  event.consume();
490  }
491  else if (action == InputAction::GUI_UP)
492  {
494  event.consume();
495  }
496  else if (action == InputAction::GUI_DOWN)
497  {
499  event.consume();
500  }
501  else if (action == InputAction::GUI_LEFT)
502  {
504  event.consume();
505  }
506  else if (action == InputAction::GUI_RIGHT)
507  {
509  event.consume();
510  }
511  else if (action == InputAction::GUI_HOME)
512  {
513  setSelectedRow(0);
515  event.consume();
516  }
517  else if (action == InputAction::GUI_END && (mModel != nullptr))
518  {
519  setSelectedRow(mModel->getRows() - 1);
521  event.consume();
522  }
523 }
524 
525 // -- MouseListener notifications
527 {
528  if (!mSelectableGui)
529  return;
530 
531  if (event.getButton() == MouseButton::LEFT)
532  {
533  const int row = getRowForY(event.getY());
534  const int column = getColumnForX(event.getX());
535 
536  if (row > -1 && column > -1 &&
537  row < mModel->getRows() && column < mModel->getColumns())
538  {
539  mSelectedColumn = column;
540  mSelectedRow = row;
541  event.consume();
542  }
543 
545  }
546 }
547 
549 {
550  if (isFocused())
551  {
552  const int selRow = getSelectedRow();
553  if (selRow > 0 || (selRow == 0 && mWrappingEnabled))
554  setSelectedRow(selRow - 1);
555  event.consume();
556  }
557 }
558 
560 {
561  if (isFocused())
562  {
564  event.consume();
565  }
566 }
567 
569 {
570  if (event.getButton() != MouseButton::LEFT)
571  return;
572 
573  // Make table selection update on drag
574  const int x = std::max(0, event.getX());
575  const int y = std::max(0, event.getY());
576 
579 }
580 
581 void GuiTable::modelUpdated(const bool completed)
582 {
583  if (completed)
584  {
587  }
588  else
589  { // before the update?
590  mTopWidget = nullptr; // No longer valid in general
592  }
593 }
594 
596 {
597  if (mModel == nullptr)
598  return nullptr;
599 
600  const int row = getRowForY(y);
601  const int column = getColumnForX(x);
602 
603  if (mTopWidget != nullptr &&
605  {
606  return mTopWidget;
607  }
608 
609  if (row > -1 && column > -1)
610  {
611  Widget *const w = mModel->getElementAt(row, column);
612  if (w != nullptr && w->isFocusable())
613  return w;
614  }
615  return nullptr;
616 }
617 
618 int GuiTable::getRowForY(const int y) const
619 {
620  int row = -1;
621 
622  const int rowHeight = getRowHeight();
623  if (rowHeight > 0)
624  row = y / rowHeight;
625 
626  if (row < 0 || row >= mModel->getRows())
627  return -1;
628  return row;
629 }
630 
631 int GuiTable::getColumnForX(const int x) const
632 {
633  int column;
634  int delta = 0;
635 
636  const int colnum = mModel->getColumns();
637  for (column = 0; column < colnum; column ++)
638  {
639  delta += getColumnWidth(column);
640  if (x <= delta)
641  break;
642  }
643 
644  if (column >= colnum)
645  return -1;
646  return column;
647 }
648 
649 void GuiTable::setFocusHandler(FocusHandler *const focusHandler)
650 {
651  // add check for focusHandler. may be need remove it?
652 
653  if (focusHandler == nullptr)
654  return;
655 
656  Widget::setFocusHandler(focusHandler);
657 
658  const int rows = mModel->getRows();
659  const int cols = mModel->getColumns();
660  for (int r = 0; r < rows; ++r)
661  {
662  for (int c = 0; c < cols ; ++c)
663  {
664  Widget *const w = mModel->getElementAt(r, c);
665  if (w != nullptr)
666  w->setFocusHandler(focusHandler);
667  }
668  }
669 }
670 
672 {
673  if (mFocusHandler == nullptr)
674  return;
676 }
#define CAST_S32
Definition: cast.h:30
#define CAST_U32
Definition: cast.h:31
unsigned int a
Definition: color.h:251
virtual void popClipArea()
Definition: graphics.cpp:739
virtual void fillRectangle(const Rect &rectangle)=0
virtual void setColor(const Color &color)
Definition: graphics.h:320
virtual void pushClipArea(const Rect &area)
Definition: graphics.cpp:677
bool mWrappingEnabled
Definition: guitable.h:203
void mouseDragged(MouseEvent &event)
Definition: guitable.cpp:568
void mouseWheelMovedUp(MouseEvent &event)
Definition: guitable.cpp:548
void installActionListeners()
Definition: guitable.cpp:205
int mSelectedRow
Definition: guitable.h:199
void mouseWheelMovedDown(MouseEvent &event)
Definition: guitable.cpp:559
void requestFocus()
Definition: guitable.cpp:671
void mousePressed(MouseEvent &event)
Definition: guitable.cpp:526
const TableModel * getModel() const
Definition: guitable.cpp:80
Widget * getWidgetAt(int x, int y)
Definition: guitable.cpp:595
GuiTable(const Widget2 *const widget, TableModel *const initial_model, const Opaque opacity)
Definition: guitable.cpp:43
void setSelected(const int row, const int column)
Definition: guitable.cpp:125
void setFocusHandler(FocusHandler *const focusHandler)
Definition: guitable.cpp:649
static float mAlpha
Definition: guitable.h:184
Widget * mTopWidget
Definition: guitable.h:189
void moveToTop(Widget *const widget)
Definition: guitable.cpp:463
Color mHighlightColor
Definition: guitable.h:197
int getSelectedColumn() const
Definition: guitable.cpp:136
void moveToBottom(Widget *const widget)
Definition: guitable.cpp:469
void modelUpdated(const bool completed)
Definition: guitable.cpp:581
void keyPressed(KeyEvent &event)
Definition: guitable.cpp:482
~GuiTable()
Definition: guitable.cpp:71
int getColumnForX(const int x) const
Definition: guitable.cpp:631
void setModel(TableModel *const m)
Definition: guitable.cpp:85
std::vector< GuiTableActionListener * > mActionListeners2
Definition: guitable.h:192
int getSelectedRow() const
Definition: guitable.cpp:131
int getRowHeight() const
Definition: guitable.cpp:141
int mSelectedColumn
Definition: guitable.h:200
void safeDraw(Graphics *const graphics)
Definition: guitable.cpp:345
friend class GuiTableActionListener
Definition: guitable.h:55
void setSelectedRow(const int selected)
Definition: guitable.cpp:151
bool mLinewiseMode
Definition: guitable.h:202
void draw(Graphics *const graphics)
Definition: guitable.cpp:227
int getRowForY(const int y) const
Definition: guitable.cpp:618
void setSelectedColumn(const int selected)
Definition: guitable.cpp:180
void recomputeDimensions()
Definition: guitable.cpp:100
int getColumnWidth(const int i) const
Definition: guitable.cpp:146
Rect getChildrenArea()
Definition: guitable.cpp:476
bool mSelectableGui
Definition: guitable.h:205
void uninstallActionListeners()
Definition: guitable.cpp:199
TableModel * mModel
Definition: guitable.h:186
Opaque mOpaque
Definition: guitable.h:204
void removeDragged(const Widget *const widget)
Definition: gui.cpp:1162
MouseButtonT getButton() const
Definition: mouseevent.h:116
int getX() const
Definition: mouseevent.h:127
int getY() const
Definition: mouseevent.h:138
Definition: rect.h:74
int y
Definition: rect.h:214
int width
Definition: rect.h:219
bool isPointInRect(const int x_, const int y_) const
Definition: rect.h:197
int height
Definition: rect.h:224
float guiAlpha
Definition: settings.h:131
virtual void removeListener(TableModelListener *const listener)
Definition: tablemodel.cpp:40
virtual int getRows() const =0
virtual int getColumnWidth(const int index) const =0
virtual Widget * getElementAt(const int row, const int column) const =0
virtual void installListener(TableModelListener *const listener)
Definition: tablemodel.cpp:34
virtual int getRowHeight() const =0
virtual int getColumns() const =0
const Color & getThemeColor(const ThemeColorIdT type, const unsigned int alpha) const A_INLINE
Definition: widget2.h:45
Definition: widget.h:99
virtual void setFocusHandler(FocusHandler *const focusHandler)
Definition: widget.cpp:238
void setFocusable(const bool focusable)
Definition: widget.cpp:192
void setWidth(const int width)
Definition: widget.cpp:133
void distributeActionEvent()
Definition: widget.cpp:493
FocusHandler * getFocusHandler()
Definition: widget.h:474
const Rect & getDimension() const
Definition: widget.h:317
virtual void moveToBottom(Widget *widget)
Definition: widget.h:877
Rect mDimension
Definition: widget.h:1101
Color mBackgroundColor
Definition: widget.h:1091
bool mAllowLogic
Definition: widget.h:1160
FocusHandler * mFocusHandler
Definition: widget.h:1116
bool isFocusable() const
Definition: widget.cpp:199
void addMouseListener(MouseListener *const mouseListener)
Definition: widget.cpp:292
void setHeight(const int height)
Definition: widget.cpp:140
void addKeyListener(KeyListener *const keyListener)
Definition: widget.cpp:272
virtual void moveToTop(Widget *widget)
Definition: widget.h:867
virtual void requestFocus()
Definition: widget.cpp:204
void setDimension(const Rect &dimension)
Definition: widget.cpp:169
virtual void safeDraw(Graphics *const graphics)=0
virtual void draw(Graphics *const graphics)=0
int getHeight() const
Definition: widget.h:240
int getWidth() const
Definition: widget.h:221
virtual bool isFocused() const
Definition: widget.cpp:184
#define delete2(var)
Definition: delete2.h:25
void delete_all(Container &c)
Definition: dtor.h:56
Gui * gui
Definition: gui.cpp:111
InputAction ::T InputActionT
Definition: inputaction.h:717
#define nullptr
Definition: localconsts.h:45
const bool Opaque_true
Definition: opaque.h:30
bool Opaque
Definition: opaque.h:30
#define BLOCK_END(name)
Definition: perfomance.h:80
#define BLOCK_START(name)
Definition: perfomance.h:79
Settings settings
Definition: settings.cpp:32