MythTV  master
mythuibuttonlist.cpp
Go to the documentation of this file.
1 #include "mythuibuttonlist.h"
2 
3 #include <cmath>
4 
5 // QT headers
6 #include <QCoreApplication>
7 #include <QDomDocument>
8 #include <QKeyEvent>
9 #include <utility>
10 
11 // libmyth headers
12 #include "mythlogging.h"
13 
14 // mythui headers
15 #include "mythmainwindow.h"
16 #include "mythuiscrollbar.h"
17 #include "mythuistatetype.h"
18 #include "lcddevice.h"
19 #include "mythuibutton.h"
20 #include "mythuitext.h"
21 #include "mythuitextedit.h"
22 #include "mythuigroup.h"
23 #include "mythuiimage.h"
24 #include "mythgesture.h"
25 
26 #define LOC QString("MythUIButtonList(%1): ").arg(objectName())
27 
29  : MythUIType(parent, name)
30 {
31  // Parent members
32  connect(this, SIGNAL(Enabling()), this, SLOT(ToggleEnabled()));
33  connect(this, SIGNAL(Disabling()), this, SLOT(ToggleEnabled()));
34 
35  Const();
36 }
37 
39  const QRect &area, bool showArrow,
40  bool showScrollBar)
41  : MythUIType(parent, name),
42  m_showArrow(showArrow), m_showScrollBar(showScrollBar)
43 {
44  // Parent members
45  m_Area = area;
46  m_Initiator = true;
47  m_EnableInitiator = true;
48 
49  connect(this, SIGNAL(Enabling()), this, SLOT(ToggleEnabled()));
50  connect(this, SIGNAL(Disabling()), this, SLOT(ToggleEnabled()));
51 
52  Const();
53 }
54 
56 {
57 
58  SetCanTakeFocus(true);
59 
60  connect(this, SIGNAL(TakingFocus()), this, SLOT(Select()));
61  connect(this, SIGNAL(LosingFocus()), this, SLOT(Deselect()));
62 }
63 
65 {
66  m_ButtonToItem.clear();
67  m_clearing = true;
68 
69  while (!m_itemList.isEmpty())
70  delete m_itemList.takeFirst();
71 }
72 
74 {
76 
77  if (item)
78  emit itemSelected(item);
79 
80  SetActive(true);
81 }
82 
84 {
85  SetActive(false);
86 }
87 
89 {
90  if (m_initialized)
91  Update();
92 }
93 
95 {
96  m_drawFromBottom = draw;
97 }
98 
99 void MythUIButtonList::SetActive(bool active)
100 {
101  if (m_active == active)
102  return;
103 
104  m_active = active;
105 
106  if (m_initialized)
107  Update();
108 }
109 
114 {
115  m_ButtonToItem.clear();
116 
117  if (m_itemList.isEmpty())
118  return;
119 
120  m_clearing = true;
121 
122  while (!m_itemList.isEmpty())
123  delete m_itemList.takeFirst();
124 
125  m_clearing = false;
126 
127  m_selPosition = 0;
128  m_topPosition = 0;
129  m_itemCount = 0;
130 
131  StopLoad();
132  Update();
134 
135  emit DependChanged(true);
136 }
137 
139 {
140  m_needsUpdate = true;
141  SetRedraw();
142 }
143 
144 /*
145  * The "width" of a button determines it relative position when using
146  * Dynamic-Layout.
147  *
148  * If a button has a negative offset, that offset needs accounted for
149  * to position the button in proper releation to the surrounding buttons.
150  */
152 {
153  int width = area.width();
154 
155  if (area.x() < 0)
156  {
157  /*
158  * Assume if an overlap is allowed on the left, the same overlap
159  * is on the right
160  */
161  width += (area.x() * 2 - 1); // x is negative
162 
163  while (width < 0)
164  width -= area.x(); // Oops
165  }
166  else if (m_layout == LayoutHorizontal)
167  width -= area.x(); // Get rid of any "space" betwen the buttons
168 
169  return width;
170 }
171 
172 /*
173  * The "height" of a button determines it relative position when using
174  * Dynamic-Layout.
175  *
176  * If a button has a negative offset, that offset needs accounted for
177  * to position the button in proper releation to the surrounding buttons.
178  */
180 {
181  int height = area.height();
182 
183  if (area.y() < 0)
184  {
185  /*
186  * Assume if an overlap is allowed on the top, the same overlap
187  * is on the bottom
188  */
189  height += (area.y() * 2 - 1);
190 
191  while (height < 0)
192  height -= area.y(); // Oops
193  }
194  else if (m_layout == LayoutVertical)
195  height -= area.y(); // Get rid of any "space" betwen the buttons
196 
197  return height;
198 }
199 
200 /*
201  * For Dynamic-Layout, buttons are allocated as needed. If the list is
202  * being redrawn, re-use any previously allocated buttons.
203  */
204 MythUIGroup *MythUIButtonList::PrepareButton(int buttonIdx, int itemIdx,
205  int &selectedIdx,
206  int &button_shift)
207 {
208  MythUIStateType *realButton;
209  MythUIGroup *buttonstate;
210  MythUIButtonListItem *buttonItem = m_itemList[itemIdx];
211 
212  buttonIdx += button_shift;
213 
214  if (buttonIdx < 0 || buttonIdx + 1 > m_maxVisible)
215  {
216  QString name = QString("buttonlist button %1").arg(m_maxVisible);
217  MythUIStateType *button = new MythUIStateType(this, name);
218  button->CopyFrom(m_buttontemplate);
219  button->ConnectDependants(true);
220 
221  if (buttonIdx < 0)
222  {
223  /*
224  * If a new button is needed in the front of the list, previously
225  * existing buttons need shifted to the right.
226  */
227  m_ButtonList.prepend(button);
228  buttonIdx = 0;
229  ++button_shift;
230 
231  if (selectedIdx >= 0)
232  ++selectedIdx;
233  }
234  else
235  m_ButtonList.append(button);
236 
237  ++m_maxVisible;
238  }
239 
240  realButton = m_ButtonList[buttonIdx];
241  m_ButtonToItem[buttonIdx] = buttonItem;
242  buttonItem->SetToRealButton(realButton, itemIdx == m_selPosition);
243  buttonstate = dynamic_cast<MythUIGroup *>(realButton->GetCurrentState());
244 
245  if (itemIdx == m_selPosition)
246  selectedIdx = buttonIdx;
247 
248  return buttonstate;
249 }
250 
251 /*
252  * Dynamically layout the buttons on a row.
253  */
254 bool MythUIButtonList::DistributeRow(int &first_button, int &last_button,
255  int &first_item, int &last_item,
256  int &selected_column, int &skip_cols,
257  bool grow_left, bool grow_right,
258  int **col_widths, int &row_height,
259  int total_height, int split_height,
260  int &col_cnt, bool &wrapped)
261 {
262  MythUIGroup *buttonstate;
263  int initial_first_button, initial_last_button;
264  int initial_first_item, initial_last_item;
265  int col_idx, left_cnt = 0;
266  int width, height;
267  int max_width, max_height;
268  int left_width, right_width;
269  int begin, end;
270  bool underflow = false; // keep from being uninitialized
271  bool added;
272  bool hsplit, vsplit;
273  int selectedIdx;
274  int button_shift;
275 
276  selectedIdx = -1;
277  button_shift = 0;
278  col_cnt = 1;
279  skip_cols = 0;
280 
281  if (last_item + 1 > m_itemCount || last_item < 0 || first_item < 0)
282  return false;
283 
284  /*
285  * Allocate a button on the row. With a vertical layout, there is
286  * only one button per row, and this would be it.
287  */
288  if (grow_right)
289  buttonstate = PrepareButton(last_button, last_item,
290  selectedIdx, button_shift);
291  else
292  buttonstate = PrepareButton(first_button, first_item,
293  selectedIdx, button_shift);
294 
295  if (buttonstate == nullptr)
296  {
297  LOG(VB_GENERAL, LOG_ERR, QString("Failed to query buttonlist state: %1")
298  .arg(last_button));
299  return false;
300  }
301 
302  // Note size of initial button.
303  max_width = m_contentsRect.width();
304  max_height = m_contentsRect.height();
305  row_height = minButtonHeight(buttonstate->GetArea());
306  width = minButtonWidth(buttonstate->GetArea());
307 
308  /*
309  * If the selected button should be centered, don't allow new buttons
310  * to take up more than half the allowed area.
311  */
312  vsplit = (m_scrollStyle == ScrollCenter);
313  hsplit = vsplit && grow_left && grow_right;
314 
315  if (hsplit)
316  {
317  max_width /= 2;
318  left_width = right_width = (width / 2);
319  }
320  else
321  {
322  if (grow_right)
323  {
324  left_width = 0;
325  right_width = width;
326  }
327  else
328  {
329  left_width = width;
330  right_width = 0;
331  }
332  }
333 
334  if (vsplit)
335  max_height /= 2;
336 
337  /*
338  * If total_height == 0, then this is the first row, so allow any height.
339  * Otherwise, If adding a button to a column would exceed the
340  * parent height, abort
341  */
342  if (total_height > 0 &&
343  ((vsplit ? split_height : total_height) +
344  m_itemVertSpacing + row_height > max_height))
345  {
346  LOG(VB_GUI, LOG_DEBUG,
347  QString("%1 Height exceeded %2 + (%3) + %4 = %5 which is > %6")
348  .arg(vsplit ? "Centering" : "Total")
349  .arg(split_height).arg(m_itemVertSpacing).arg(row_height)
350  .arg(split_height + m_itemVertSpacing + row_height)
351  .arg(max_height));
352  first_button += button_shift;
353  last_button += button_shift;
354  return false;
355  }
356 
357  LOG(VB_GUI, LOG_DEBUG, QString("Added button item %1 width %2 height %3")
358  .arg(grow_right ? last_item : first_item)
359  .arg(width).arg(row_height));
360 
361  initial_first_button = first_button;
362  initial_last_button = last_button;
363  initial_first_item = first_item;
364  initial_last_item = last_item;
365 
366  /*
367  * if col_widths is not nullptr, then grow_left & grow_right
368  * are mutually exclusive. So, col_idx can be anchored from
369  * the left or right.
370  */
371  if (grow_right)
372  col_idx = 0;
373  else
374  col_idx = m_columns - 1;
375 
376  // Add butons until no more fit.
377  added = (m_layout != LayoutVertical);
378 
379  while (added)
380  {
381  added = false;
382 
383  // If a grid, maintain same number of columns on each row.
384  if (grow_right && col_cnt < m_columns)
385  {
386  if (wrapped)
387  end = first_item;
388  else
389  {
390  // Are we allowed to wrap when we run out of items?
391  if (m_wrapStyle == WrapItems &&
392  (hsplit || m_scrollStyle != ScrollFree) &&
393  last_item + 1 == m_itemCount)
394  {
395  last_item = -1;
396  wrapped = true;
397  end = first_item;
398  }
399  else
400  end = m_itemCount;
401  }
402 
403  if (last_item + 1 < end)
404  {
405  // Allocate next button to the right.
406  buttonstate = PrepareButton(last_button + 1, last_item + 1,
407  selectedIdx, button_shift);
408 
409  if (buttonstate == nullptr)
410  continue;
411 
412  width = minButtonWidth(buttonstate->GetArea());
413 
414  // For grids, use the widest button in a column
415  if (*col_widths && width < (*col_widths)[col_idx])
416  width = (*col_widths)[col_idx];
417 
418  // Does the button fit?
419  if ((hsplit ? right_width : left_width + right_width) +
420  m_itemHorizSpacing + width > max_width)
421  {
422  int total = hsplit ? right_width : left_width + right_width;
423  LOG(VB_GUI, LOG_DEBUG,
424  QString("button on right would exceed width: "
425  "%1+(%2)+%3 == %4 which is > %5")
426  .arg(total).arg(m_itemHorizSpacing).arg(width)
427  .arg(total + m_itemHorizSpacing + width)
428  .arg(max_width));
429  }
430  else
431  {
432  added = true;
433  ++col_cnt;
434  ++last_button;
435  ++last_item;
436  ++col_idx;
437  right_width += m_itemHorizSpacing + width;
438  height = minButtonHeight(buttonstate->GetArea());
439 
440  if (row_height < height)
441  row_height = height;
442 
443  LOG(VB_GUI, LOG_DEBUG,
444  QString("Added button item %1 "
445  "R.width %2 height %3 total width %4+%5"
446  " (max %6)")
447  .arg(last_item).arg(width).arg(height)
448  .arg(left_width).arg(right_width).arg(max_width));
449  }
450  }
451  else
452  underflow = true;
453  }
454 
455  // If a grid, maintain same number of columns on each row.
456  if (grow_left && col_cnt < m_columns)
457  {
458  if (wrapped)
459  end = last_item + 1;
460  else
461  {
462  // Are we allowed to wrap when we run out of items?
463  if (m_wrapStyle == WrapItems &&
464  (hsplit || m_scrollStyle != ScrollFree) &&
465  first_item == 0)
466  {
467  first_item = m_itemCount;
468  wrapped = true;
469  end = last_item + 1;
470  }
471  else
472  end = 0;
473  }
474 
475  if (first_item > end)
476  {
477  buttonstate = PrepareButton(first_button - 1, first_item - 1,
478  selectedIdx, button_shift);
479 
480  if (buttonstate == nullptr)
481  continue;
482 
483  width = minButtonWidth(buttonstate->GetArea());
484 
485  // For grids, use the widest button in a column
486  if (*col_widths && width < (*col_widths)[col_idx])
487  width = (*col_widths)[col_idx];
488 
489  // Does the button fit?
490  if ((hsplit ? left_width : left_width + right_width) +
491  m_itemHorizSpacing + width > max_width)
492  {
493  int total = hsplit ? left_width : left_width + right_width;
494  LOG(VB_GUI, LOG_DEBUG,
495  QString("button on left would exceed width: "
496  "%1+(%2)+%3 == %4 which is > %5")
497  .arg(total).arg(m_itemHorizSpacing).arg(width)
498  .arg(total + m_itemHorizSpacing + width)
499  .arg(max_width));
500  }
501  else
502  {
503  added = true;
504  --first_button;
505  --first_item;
506  --col_idx;
507  ++col_cnt;
508  ++left_cnt;
509  left_width += m_itemHorizSpacing + width;
510  height = minButtonHeight(buttonstate->GetArea());
511 
512  if (row_height < height)
513  row_height = height;
514 
515  LOG(VB_GUI, LOG_DEBUG,
516  QString("Added button item %1 "
517  "L.width %2 height %3 total width %4+%5"
518  " (max %6)")
519  .arg(first_item).arg(width).arg(height)
520  .arg(left_width).arg(right_width).arg(max_width));
521  }
522  }
523  else
524  {
525  underflow = true;
526  if (m_layout == LayoutGrid)
527  skip_cols = m_columns - col_cnt;
528  }
529  }
530  }
531 
532  /*
533  * If total_height == 0, then this is the first row, so allow any height.
534  * Otherwise, If adding a button to a column would exceed the
535  * parent height, abort
536  */
537  if (total_height > 0 &&
538  ((vsplit ? split_height : total_height) +
539  m_itemVertSpacing + row_height > max_height))
540  {
541  LOG(VB_GUI, LOG_DEBUG,
542  QString("%1 Height exceeded %2 + (%3) + %4 = %5 which is > %6")
543  .arg(vsplit ? "Centering" : "Total")
544  .arg(split_height).arg(m_itemVertSpacing).arg(row_height)
545  .arg(split_height + m_itemVertSpacing + row_height)
546  .arg(max_height));
547  first_button = initial_first_button + button_shift;
548  last_button = initial_last_button + button_shift;
549  first_item = initial_first_item;
550  last_item = initial_last_item;
551  return false;
552  }
553 
554  if (*col_widths == nullptr)
555  {
556  /*
557  * Allocate array to hold columns widths, now that we know
558  * how many columns there are.
559  */
560  *col_widths = new int[col_cnt];
561 
562  for (col_idx = 0; col_idx < col_cnt; ++col_idx)
563  (*col_widths)[col_idx] = 0;
564 
565  }
566 
567  // Adjust for insertions on the front.
568  first_button += button_shift;
569  last_button += button_shift;
570 
571  // It fits, so so note max column widths
572  MythUIStateType *realButton;
573  int buttonIdx;
574 
575  if (grow_left)
576  {
577  begin = first_button;
578  end = first_button + col_cnt;
579  }
580  else
581  {
582  end = last_button + 1;
583  begin = end - col_cnt;
584  }
585 
586  for (buttonIdx = begin, col_idx = 0;
587  buttonIdx < end; ++buttonIdx, ++col_idx)
588  {
589  realButton = m_ButtonList[buttonIdx];
590  buttonstate = dynamic_cast<MythUIGroup *>
591  (realButton->GetCurrentState());
592  if (!buttonstate)
593  break;
594  width = minButtonWidth(buttonstate->GetArea());
595 
596  if ((*col_widths)[col_idx] < width)
597  (*col_widths)[col_idx] = width;
598 
599  // Make note of which column has the selected button
600  if (selectedIdx == buttonIdx)
601  selected_column = col_idx;
602  }
603 
604  /*
605  An underflow indicates we ran out of items, not that the
606  buttons did not fit on the row.
607  */
608  if (total_height && underflow && col_cnt < m_columns)
609  col_cnt = m_columns;
610 
611  return true;
612 }
613 
614 /*
615  * Dynamically layout columns
616  */
617 bool MythUIButtonList::DistributeCols(int &first_button, int &last_button,
618  int &first_item, int &last_item,
619  int &selected_column, int &selected_row,
620  int &skip_cols, int **col_widths,
621  QList<int> & row_heights,
622  int &top_height, int &bottom_height,
623  bool &wrapped)
624 {
625  int col_cnt;
626  int height;
627  int row_cnt = 1;
628  int end;
629  bool added;
630 
631  do
632  {
633  added = false;
634 
635  if (wrapped)
636  end = first_item;
637  else
638  {
639  // Are we allowed to wrap when we run out of items?
640  if (m_wrapStyle == WrapItems &&
643  last_item + 1 == m_itemCount)
644  {
645  last_item = -1;
646  wrapped = true;
647  end = first_item;
648  }
649  else
650  end = m_itemCount;
651  }
652 
653  if (last_item + 1 < end)
654  {
655  // Does another row fit?
656  if (DistributeRow(first_button, ++last_button,
657  first_item, ++last_item, selected_column,
658  skip_cols, false, true, col_widths, height,
659  top_height + bottom_height, bottom_height,
660  col_cnt, wrapped))
661  {
662  if (col_cnt < m_columns)
663  return false; // Need to try again with fewer cols
664 
665  if (selected_row == -1 && selected_column != -1)
666  selected_row = row_heights.size();
667 
668  ++row_cnt;
669  row_heights.push_back(height);
670  bottom_height += (height + m_itemVertSpacing);
671  added = true;
672  }
673  else
674  {
675  --last_button;
676  --last_item;
677  }
678  }
679 
680  if (wrapped)
681  end = last_item + 1;
682  else
683  {
684  // Are we allowed to wrap when we run out of items?
685  if (m_wrapStyle == WrapItems &&
688  first_item == 0)
689  {
690  first_item = m_itemCount;
691  wrapped = true;
692  end = last_item + 1;
693  }
694  else
695  end = 0;
696  }
697 
698  if (first_item > end)
699  {
700  // Can we insert another row?
701  if (DistributeRow(--first_button, last_button,
702  --first_item, last_item, selected_column,
703  skip_cols, true, false, col_widths, height,
704  top_height + bottom_height, top_height,
705  col_cnt, wrapped))
706  {
707  if (col_cnt < m_columns)
708  return false; // Need to try again with fewer cols
709 
710  if (selected_row == -1 && selected_column != -1)
711  selected_row = row_heights.size();
712  else if (selected_row != -1)
713  ++selected_row;
714 
715  ++row_cnt;
716  row_heights.push_front(height);
717  top_height += (height + m_itemVertSpacing);
718  added = true;
719  }
720  else
721  {
722  ++first_button;
723  ++first_item;
724  }
725  }
726  }
727  while (added);
728 
729  return true;
730 }
731 
732 /*
733  * Dynamically layout as many buttons as will fit in the area.
734  */
736 {
737  int first_button, last_button, start_button, start_item;
738  int first_item, last_item, skip_cols = 0;
739  int *col_widths;
740  int col_cnt;
741  int selected_column, selected_row;
742  bool wrapped = false;
743  bool grow_left = true;
744 
745  QList<int> row_heights;
746  QList<int>::iterator Iheight;
747  int height, top_height, bottom_height;
748 
749  start_item = m_selPosition;
750  selected_column = selected_row = -1;
751  top_height = bottom_height = 0;
752  col_widths = nullptr;
753  first_button = last_button = start_button = 0;
754 
755  LOG(VB_GUI, LOG_DEBUG, QString("DistributeButtons: "
756  "selected item %1 total items %2")
757  .arg(start_item).arg(m_itemCount));
758 
759  // if there are no items to show make sure all the buttons are made invisible
760  if (m_itemCount == 0)
761  {
762  for (int i = 0; i < m_ButtonList.count(); ++i)
763  {
764  if (m_ButtonList[i])
765  m_ButtonList[i]->SetVisible(false);
766  }
767 
768  return false;
769  }
770 
771  /*
772  * Try fewer and fewer columns until each row can fit the same
773  * number of columns.
774  */
775  for (m_columns = m_itemCount; m_columns > 0;)
776  {
777  first_item = last_item = start_item;
778 
779  /*
780  * Drawing starts at start_button, and radiates from there.
781  * Attempt to pick a start_button which will minimize the need for new
782  * button allocations.
783  */
784  switch (m_scrollStyle)
785  {
786  case ScrollCenter:
787  case ScrollGroupCenter:
788  start_button = qMax((m_maxVisible / 2) - 1, 0);
789  break;
790  case ScrollFree:
791 
792  if (m_layout == LayoutGrid)
793  {
794  start_button = 0;
795  first_item = last_item = 0;
796  grow_left = false;
797  }
798  else if (!m_ButtonList.empty())
799  {
800  if (m_itemCount - m_selPosition - 1 <
801  (m_ButtonList.size() / 2))
802  start_button = m_ButtonList.size() -
803  (m_itemCount - m_selPosition) + 1;
804  else if (m_selPosition >
805  (m_ButtonList.size() / 2))
806  start_button = (m_ButtonList.size() / 2);
807  else
808  start_button = m_selPosition;
809  }
810  else
811  start_button = 0;
812 
813  break;
814  }
815 
816  first_button = last_button = start_button;
817  row_heights.clear();
818 
819  // Process row with selected button, and set starting val for m_columns.
820  if (!DistributeRow(first_button, last_button,
821  first_item, last_item, selected_column,
822  skip_cols, grow_left, true, &col_widths,
823  height, 0, 0, col_cnt, wrapped))
824  {
825  delete[] col_widths;
826  return false;
827  }
828 
829  m_columns = col_cnt;
830 
832  {
833  /*
834  * Now that we know how many columns there are, we can start
835  * the grid layout for real.
836  */
837  start_item = (m_selPosition / m_columns) * m_columns;
838  first_item = last_item = start_item;
839 
840  /*
841  * Attempt to pick a start_button which will minimize the need
842  * for new button allocations.
843  */
844  start_button = qMax(m_ButtonList.size() / 2, 0);
845  start_button = (start_button / qMax(m_columns, 1)) * m_columns;
846 
847  if (start_button < m_itemCount / 2 &&
848  m_itemCount - m_selPosition - 1 < m_ButtonList.size() / 2)
849  start_button += m_columns;
850 
851  first_button = last_button = start_button;
852 
853  // Now do initial row layout again with our new knowledge
854  selected_column = selected_row = -1;
855 
856  if (!DistributeRow(first_button, last_button,
857  first_item, last_item, selected_column,
858  skip_cols, grow_left, true, &col_widths,
859  height, 0, 0, col_cnt, wrapped))
860  {
861  delete[] col_widths;
862  return false;
863  }
864  }
865 
866  if (selected_column != -1)
867  selected_row = 0;
868 
869  row_heights.push_back(height);
870 
872  top_height = bottom_height = (height / 2);
873  else
874  bottom_height = height;
875 
876  if (m_layout == LayoutHorizontal)
877  break;
878 
879  // As as many columns as will fit.
880  if (DistributeCols(first_button, last_button,
881  first_item, last_item,
882  selected_column, selected_row,
883  skip_cols, &col_widths, row_heights,
884  top_height, bottom_height, wrapped))
885  break; // Buttons fit on each row, so done
886 
887  delete[] col_widths;
888  col_widths = nullptr;
889 
890  --m_columns;
891  start_item = m_selPosition;
892  }
893 
894  m_rows = row_heights.size();
895 
896  LOG(VB_GUI, LOG_DEBUG,
897  QString("%1 rows, %2 columns fit inside parent area %3x%4")
898  .arg(m_rows).arg(m_columns).arg(m_contentsRect.width())
899  .arg(m_contentsRect.height()));
900 
901  if (col_widths == nullptr)
902  return false;
903 
904  int total, row, col;
905  int left_spacing, right_spacing, top_spacing, bottom_spacing;
906  int x, y, x_init, x_adj, y_adj;
907  MythRect min_rect;
908  QString status_msg;
909 
910  /*
911  * Calculate heights of buttons on each side of selected button
912  */
913  top_height = bottom_height = m_topRows = m_bottomRows = 0;
914 
915  status_msg = "Row heights: ";
916 
917  for (row = 0; row < m_rows; ++row)
918  {
919  if (row != 0)
920  status_msg += ", ";
921 
922  if (row == selected_row)
923  {
924  status_msg += '[';
925  top_height += (row_heights[row] / 2);
926  bottom_height += ((row_heights[row] / 2) + (row_heights[row] % 2));
927  }
928  else
929  {
930  if (bottom_height)
931  {
932  bottom_height += m_itemVertSpacing + row_heights[row];
933  ++m_bottomRows;
934  }
935  else
936  {
937  top_height += row_heights[row] + m_itemVertSpacing;
938  ++m_topRows;
939  }
940  }
941 
942  status_msg += QString("%1").arg(row_heights[row]);
943 
944  if (row == selected_row)
945  status_msg += ']';
946  }
947 
948  /*
949  * How much extra space should there be between buttons?
950  */
952  {
953  // None
954  top_spacing = bottom_spacing = 0;
955  }
956  else
957  {
958  if (m_rows < 2)
959  {
960  // Equal space on both sides of single row
961  top_spacing = bottom_spacing =
962  (m_contentsRect.height() - top_height) / 2;
963  }
964  else
965  {
967  {
968  // Selected button needs to end up in the middle of area
969  top_spacing = m_topRows ? (m_contentsRect.height() / 2 -
970  top_height) / m_topRows : 0;
971  bottom_spacing = m_bottomRows ? (m_contentsRect.height() / 2 -
972  bottom_height) / m_bottomRows : 0;
973 
974  if (m_arrange == ArrangeSpread)
975  {
976  // Use same spacing on both sides of selected button
977  if (!m_topRows || top_spacing > bottom_spacing)
978  top_spacing = bottom_spacing;
979  else
980  bottom_spacing = top_spacing;
981  }
982  }
983  else
984  {
985  // Buttons will be evenly spread out to fill entire area
986  top_spacing = bottom_spacing = (m_contentsRect.height() -
987  (top_height + bottom_height)) /
989  }
990  }
991 
992  // Add in intra-button space size
993  top_height += (top_spacing * m_topRows);
994  bottom_height += (bottom_spacing * m_bottomRows);
995  }
996 
997  /*
998  * Calculate top margin
999  */
1000  y = m_contentsRect.y();
1001 
1002  if ((m_alignment & Qt::AlignVCenter) && m_arrange != ArrangeFill)
1003  {
1004  if (m_scrollStyle == ScrollCenter)
1005  {
1006  // Adjust to compensate for top height less than bottom height
1007  y += qMax(bottom_height - top_height, 0);
1008  total = qMax(top_height, bottom_height) * 2;
1009  }
1010  else
1011  total = top_height + bottom_height;
1012 
1013  // Adjust top margin so selected button ends up in the middle
1014  y += (qMax(m_contentsRect.height() - total, 2) / 2);
1015  }
1016  else if ((m_alignment & Qt::AlignBottom) && m_arrange == ArrangeStack)
1017  {
1018  // Adjust top margin so buttons are bottom justified
1019  y += qMax(m_contentsRect.height() -
1020  (top_height + bottom_height), 0);
1021  }
1022  min_rect.setY(y);
1023 
1024  status_msg += QString(" spacing top %1 bottom %2 fixed %3 offset %4")
1025  .arg(top_spacing).arg(bottom_spacing)
1026  .arg(m_itemVertSpacing).arg(y);
1027 
1028  LOG(VB_GUI, LOG_DEBUG, status_msg);
1029 
1030  /*
1031  * Calculate width of buttons on each side of selected button
1032  */
1033  int left_width, right_width;
1034 
1035  left_width = right_width = m_leftColumns = m_rightColumns = 0;
1036 
1037  status_msg = "Col widths: ";
1038 
1039  for (col = 0; col < m_columns; ++col)
1040  {
1041  if (col != 0)
1042  status_msg += ", ";
1043 
1044  if (col == selected_column)
1045  {
1046  status_msg += '[';
1047  left_width += (col_widths[col] / 2);
1048  right_width += ((col_widths[col] / 2) + (col_widths[col] % 2));
1049  }
1050  else
1051  {
1052  if (right_width)
1053  {
1054  right_width += m_itemHorizSpacing + col_widths[col];
1055  ++m_rightColumns;
1056  }
1057  else
1058  {
1059  left_width += col_widths[col] + m_itemHorizSpacing;
1060  ++m_leftColumns;
1061  }
1062  }
1063 
1064  status_msg += QString("%1").arg(col_widths[col]);
1065 
1066  if (col == selected_column)
1067  status_msg += ']';
1068  }
1069 
1070  /*
1071  * How much extra space should there be between buttons?
1072  */
1074  {
1075  // None
1076  left_spacing = right_spacing = 0;
1077  }
1078  else
1079  {
1080  if (m_columns < 2)
1081  {
1082  // Equal space on both sides of single column
1083  left_spacing = right_spacing =
1084  (m_contentsRect.width() - left_width) / 2;
1085  }
1086  else
1087  {
1088  if (m_scrollStyle == ScrollCenter)
1089  {
1090  // Selected button needs to end up in the middle
1091  left_spacing = m_leftColumns ? (m_contentsRect.width() / 2 -
1092  left_width) / m_leftColumns : 0;
1093  right_spacing = m_rightColumns ? (m_contentsRect.width() / 2 -
1094  right_width) / m_rightColumns : 0;
1095 
1096  if (m_arrange == ArrangeSpread)
1097  {
1098  // Use same spacing on both sides of selected button
1099  if (!m_leftColumns || left_spacing > right_spacing)
1100  left_spacing = right_spacing;
1101  else
1102  right_spacing = left_spacing;
1103  }
1104  }
1105  else
1106  {
1107  // Buttons will be evenly spread out to fill entire area
1108  left_spacing = right_spacing = (m_contentsRect.width() -
1109  (left_width + right_width)) /
1111  }
1112  }
1113 
1114  // Add in intra-button space size
1115  left_width += (left_spacing * m_leftColumns);
1116  right_width += (right_spacing * m_rightColumns);
1117  }
1118 
1119  /*
1120  * Calculate left margin
1121  */
1122  x_init = m_contentsRect.x();
1123 
1124  if ((m_alignment & Qt::AlignHCenter) && m_arrange != ArrangeFill)
1125  {
1126  if (m_scrollStyle == ScrollCenter)
1127  {
1128  // Compensate for left being smaller than right
1129  x_init += qMax(right_width - left_width, 0);
1130  total = qMax(left_width, right_width) * 2;
1131  }
1132  else
1133  total = left_width + right_width;
1134 
1135  // Adjust left margin so selected button ends up in the middle
1136  x_init += (qMax(m_contentsRect.width() - total, 2) / 2);
1137  }
1138  else if ((m_alignment & Qt::AlignRight) && m_arrange == ArrangeStack)
1139  {
1140  // Adjust left margin, so buttons are right justified
1141  x_init += qMax(m_contentsRect.width() -
1142  (left_width + right_width), 0);
1143  }
1144  min_rect.setX(x_init);
1145 
1146  status_msg += QString(" spacing left %1 right %2 fixed %3 offset %4")
1147  .arg(left_spacing).arg(right_spacing)
1148  .arg(m_itemHorizSpacing).arg(x_init);
1149  LOG(VB_GUI, LOG_DEBUG, status_msg);
1150 
1151  top_spacing += m_itemVertSpacing;
1152  bottom_spacing += m_itemVertSpacing;
1153  left_spacing += m_itemHorizSpacing;
1154  right_spacing += m_itemHorizSpacing;
1155 
1156  MythUIStateType *realButton = nullptr;
1157  MythUIGroup *buttonstate;
1158 
1159  // Calculate position of each button
1160  int vertical_spacing, horizontal_spacing;
1161  int buttonIdx = first_button - skip_cols;
1162 
1163  vertical_spacing = top_spacing;
1164 
1165  for (row = 0; row < m_rows; ++row)
1166  {
1167  x = x_init;
1168  horizontal_spacing = left_spacing;
1169 
1170  for (col = 0; col < m_columns && buttonIdx <= last_button; ++col)
1171  {
1172  if (buttonIdx >= first_button)
1173  {
1174  realButton = m_ButtonList[buttonIdx];
1175  buttonstate = dynamic_cast<MythUIGroup *>
1176  (realButton->GetCurrentState());
1177  if (!buttonstate)
1178  break; // Not continue
1179 
1180  MythRect area = buttonstate->GetArea();
1181 
1182  // Center button within width of column
1183  if (m_alignment & Qt::AlignHCenter)
1184  x_adj = (col_widths[col] - minButtonWidth(area)) / 2;
1185  else if (m_alignment & Qt::AlignRight)
1186  x_adj = (col_widths[col] - minButtonWidth(area));
1187  else
1188  x_adj = 0;
1189  if (m_layout == LayoutHorizontal)
1190  x_adj -= area.x(); // Negate button's own offset
1191 
1192  // Center button within height of row.
1193  if (m_alignment & Qt::AlignVCenter)
1194  y_adj = (row_heights[row] - minButtonHeight(area)) / 2;
1195  else if (m_alignment & Qt::AlignBottom)
1196  y_adj = (row_heights[row] - minButtonHeight(area));
1197  else
1198  y_adj = 0;
1199  if (m_layout == LayoutVertical)
1200  y_adj -= area.y(); // Negate button's own offset
1201 
1202  // Set position of button
1203  realButton->SetPosition(x + x_adj, y + y_adj);
1204  realButton->SetVisible(true);
1205 
1206  if (col == selected_column)
1207  {
1208  horizontal_spacing = right_spacing;
1209  if (row == selected_row)
1210  realButton->MoveToTop();
1211  }
1212  }
1213  x += col_widths[col] + horizontal_spacing;
1214  ++buttonIdx;
1215  }
1216 
1217  if (row == selected_row)
1218  vertical_spacing = bottom_spacing;
1219 
1220  y += row_heights[row] + vertical_spacing;
1221  }
1222  min_rect.setWidth(x - min_rect.x());
1223  min_rect.setHeight(y - min_rect.y());
1224 
1226 
1227  // Hide buttons before first active button
1228  for (buttonIdx = 0; buttonIdx < first_button; ++buttonIdx)
1229  m_ButtonList[buttonIdx]->SetVisible(false);
1230 
1231  // Hide buttons after last active buttons.
1232  for (buttonIdx = m_maxVisible - 1; buttonIdx > last_button; --buttonIdx)
1233  m_ButtonList[buttonIdx]->SetVisible(false);
1234 
1235  // Set m_topPosition so arrows are displayed correctly.
1237  m_topPosition = static_cast<int>(m_itemsVisible) < m_itemCount;
1238  else
1239  m_topPosition = first_item;
1240 
1242  if (m_MinSize.isValid())
1243  {
1244  // Record the minimal area needed for the button list
1245  SetMinArea(min_rect);
1246  }
1247 
1248  delete[] col_widths;
1249  return true;
1250 }
1251 
1253 {
1254  if (m_ButtonList.empty())
1255  return;
1256 
1257  int button = 0;
1258 
1259  switch (m_scrollStyle)
1260  {
1261  case ScrollCenter:
1262  case ScrollGroupCenter:
1263  m_topPosition = qMax(m_selPosition -
1264  (int)((float)m_itemsVisible / 2), 0);
1265  break;
1266  case ScrollFree:
1267  {
1268  int adjust = 0;
1269 
1270  if (m_topPosition == -1 || m_keepSelAtBottom)
1271  {
1272  if (m_topPosition == -1)
1273  m_topPosition = 0;
1274 
1275  if (m_layout == LayoutHorizontal)
1276  adjust = 1 - m_itemsVisible;
1277  else
1278  adjust = m_columns - m_itemsVisible;
1279 
1280  m_keepSelAtBottom = false;
1281  }
1282 
1283  if (m_selPosition < m_topPosition ||
1285  {
1286  if (m_layout == LayoutHorizontal)
1287  m_topPosition = m_selPosition + adjust;
1288  else
1289  m_topPosition = (m_selPosition + adjust) /
1290  m_columns * m_columns;
1291  }
1292 
1293  // Adjusted if last item is deleted
1294  if (((m_itemList.count() - m_topPosition)
1295  < static_cast<int>(m_itemsVisible)) &&
1296  (m_selPosition - (static_cast<int>(m_itemsVisible) - 1)
1297  < m_topPosition) &&
1298  m_columns == 1)
1300  (static_cast<int>(m_itemsVisible) - 1);
1301 
1302  m_topPosition = qMax(m_topPosition, 0);
1303  break;
1304  }
1305  }
1306 
1307  QList<MythUIButtonListItem *>::iterator it = m_itemList.begin() +
1308  m_topPosition;
1309 
1311  {
1312  if (m_selPosition <= (int)(m_itemsVisible / 2))
1313  {
1314  button = (m_itemsVisible / 2) - m_selPosition;
1315 
1316  if (m_wrapStyle == WrapItems && button > 0 &&
1317  m_itemCount >= (int)m_itemsVisible)
1318  {
1319  it = m_itemList.end() - button;
1320  button = 0;
1321  }
1322  }
1323  else if ((m_itemCount - m_selPosition) < (int)(m_itemsVisible / 2))
1324  {
1325  it = m_itemList.begin() + m_selPosition - (m_itemsVisible / 2);
1326  }
1327  }
1328  else if (m_drawFromBottom && m_itemCount < (int)m_itemsVisible)
1329  button = m_itemsVisible - m_itemCount;
1330 
1331  for (int i = 0; i < button; ++i)
1332  m_ButtonList[i]->SetVisible(false);
1333 
1334  bool seenSelected = false;
1335 
1336  MythUIStateType *realButton = nullptr;
1337  MythUIButtonListItem *buttonItem = nullptr;
1338 
1339  if (it < m_itemList.begin())
1340  it = m_itemList.begin();
1341 
1342  int curItem = it < m_itemList.end() ? GetItemPos(*it) : 0;
1343 
1344  while (it < m_itemList.end() && button < (int)m_itemsVisible)
1345  {
1346  realButton = m_ButtonList[button];
1347  buttonItem = *it;
1348 
1349  if (!realButton || !buttonItem)
1350  break;
1351 
1352  bool selected = false;
1353 
1354  if (!seenSelected && (curItem == m_selPosition))
1355  {
1356  seenSelected = true;
1357  selected = true;
1358  }
1359 
1360  m_ButtonToItem[button] = buttonItem;
1361  buttonItem->SetToRealButton(realButton, selected);
1362  realButton->SetVisible(true);
1363 
1364  if (m_wrapStyle == WrapItems && it == (m_itemList.end() - 1) &&
1365  m_itemCount >= (int)m_itemsVisible)
1366  {
1367  it = m_itemList.begin();
1368  curItem = 0;
1369  }
1370  else
1371  {
1372  ++it;
1373  ++curItem;
1374  }
1375 
1376  ++button;
1377  }
1378 
1379  for (; button < (int)m_itemsVisible; ++button)
1380  m_ButtonList[button]->SetVisible(false);
1381 }
1382 
1384 {
1385  if (m_selPosition < 0)
1386  m_selPosition = (m_wrapStyle > WrapNone) ? m_itemList.size() - 1 : 0;
1387  else if (m_selPosition >= m_itemList.size())
1388  m_selPosition = (m_wrapStyle > WrapNone) ? 0 : m_itemList.size() - 1;
1389 }
1390 
1392 {
1393  if (!m_initialized)
1394  Init();
1395 
1396  if (!m_initialized)
1397  return;
1398 
1399  if (m_clearing)
1400  return;
1401 
1402  m_needsUpdate = false;
1403 
1404  // mark the visible buttons as invisible
1405  QMap<int, MythUIButtonListItem*>::const_iterator i = m_ButtonToItem.constBegin();
1406  while (i != m_ButtonToItem.constEnd())
1407  {
1408  if (i.value())
1409  i.value()->setVisible(false);
1410  ++i;
1411  }
1412 
1413  // set topitem, top position
1414  SanitizePosition();
1415  m_ButtonToItem.clear();
1416 
1417  if (m_arrange == ArrangeFixed)
1419  else
1421 
1422  updateLCD();
1423 
1424  m_needsUpdate = false;
1425 
1426  if (!m_downArrow || !m_upArrow)
1427  return;
1428 
1429  if (m_itemCount == 0)
1430  {
1433  }
1434  else
1435  {
1436  if (m_topPosition != 0)
1438  else
1440 
1443  else
1445 
1446  m_upArrow->MoveToTop();
1448  }
1449 }
1450 
1452 {
1453  if (item)
1454  emit itemVisible(item);
1455 }
1456 
1458 {
1459  bool wasEmpty = m_itemList.isEmpty();
1460 
1461  if (listPosition >= 0 && listPosition <= m_itemList.count())
1462  {
1463  m_itemList.insert(listPosition, item);
1464 
1465  if (listPosition <= m_selPosition)
1466  ++m_selPosition;
1467 
1468  if (listPosition <= m_topPosition)
1469  ++m_topPosition;
1470  }
1471  else
1472  m_itemList.append(item);
1473 
1474  ++m_itemCount;
1475 
1476  if (wasEmpty)
1477  {
1479  emit itemSelected(item);
1480  emit DependChanged(false);
1481  }
1482 
1483  Update();
1484 }
1485 
1487 {
1488  if (m_clearing)
1489  return;
1490 
1491  int curIndex = m_itemList.indexOf(item);
1492 
1493  if (curIndex == -1)
1494  return;
1495 
1496  QMap<int, MythUIButtonListItem*>::iterator it = m_ButtonToItem.begin();
1497  while (it != m_ButtonToItem.end())
1498  {
1499  if (it.value() == item)
1500  {
1501  m_ButtonToItem.erase(it);
1502  break;
1503  }
1504  ++it;
1505  }
1506 
1507  if (curIndex < m_topPosition &&
1508  m_topPosition > 0)
1509  {
1510  // The removed item is before the visible part, move
1511  // everything up 1. The visible part shouldn't appear to
1512  // change.
1513  --m_topPosition;
1514  --m_selPosition;
1515  }
1516  else if (curIndex < m_selPosition ||
1517  (m_selPosition == m_itemCount - 1 &&
1518  m_selPosition > 0))
1519  {
1520  // The removed item is visible and before the selected item or
1521  // the selected item is the last item but not the only item,
1522  // move the selected item up 1.
1523  --m_selPosition;
1524  }
1525 
1526  m_itemList.removeAt(curIndex);
1527  --m_itemCount;
1528 
1529  Update();
1530 
1531  if (m_selPosition < m_itemCount)
1533  else
1534  emit itemSelected(nullptr);
1535 
1536  if (IsEmpty())
1537  emit DependChanged(true);
1538 }
1539 
1540 void MythUIButtonList::SetValueByData(const QVariant& data)
1541 {
1542  if (!m_initialized)
1543  Init();
1544 
1545  for (int i = 0; i < m_itemList.size(); ++i)
1546  {
1547  MythUIButtonListItem *item = m_itemList.at(i);
1548 
1549  if (item->GetData() == data)
1550  {
1551  SetItemCurrent(item);
1552  return;
1553  }
1554  }
1555 }
1556 
1558 {
1559  int newIndex = m_itemList.indexOf(item);
1560  SetItemCurrent(newIndex);
1561 }
1562 
1563 void MythUIButtonList::SetItemCurrent(int current, int topPosition)
1564 {
1565  if (!m_initialized)
1566  Init();
1567 
1568  if (current == -1 || current >= m_itemList.size())
1569  return;
1570 
1571  if (!m_itemList.at(current)->isEnabled())
1572  return;
1573 
1574  if (current == m_selPosition &&
1575  (topPosition == -1 || topPosition == m_topPosition))
1576  return;
1577 
1578  m_topPosition = topPosition;
1579 
1580  if (topPosition > 0 && m_layout == LayoutGrid)
1581  m_topPosition -= (topPosition % m_columns);
1582 
1584 
1585  Update();
1586 
1587  emit itemSelected(GetItemCurrent());
1588 }
1589 
1591 {
1592  if (m_itemList.isEmpty() || m_selPosition >= m_itemList.size() ||
1593  m_selPosition < 0)
1594  return nullptr;
1595 
1596  return m_itemList.at(m_selPosition);
1597 }
1598 
1600 {
1602 
1603  if (item)
1604  return item->GetText().toInt();
1605 
1606  return 0;
1607 }
1608 
1610 {
1612 
1613  if (item)
1614  return item->GetText();
1615 
1616  return QString();
1617 }
1618 
1620 {
1622 
1623  if (item)
1624  return item->GetData();
1625 
1626  return QVariant();
1627 }
1628 
1630 {
1631  if (m_contentsRect.isValid())
1632  return m_contentsRect;
1633  return m_Area;
1634 }
1635 
1637 {
1638  if (!m_itemList.empty())
1639  return m_itemList[0];
1640 
1641  return nullptr;
1642 }
1643 
1645 const
1646 {
1647  QListIterator<MythUIButtonListItem *> it(m_itemList);
1648 
1649  if (!it.findNext(item))
1650  return nullptr;
1651 
1652  return it.previous();
1653 }
1654 
1656 {
1657  return m_itemCount;
1658 }
1659 
1661 {
1662  if (m_needsUpdate)
1663  {
1666  }
1667 
1668  return m_itemsVisible;
1669 }
1670 
1672 {
1673  return m_itemCount <= 0;
1674 }
1675 
1677 {
1678  if (pos < 0 || pos >= m_itemList.size())
1679  return nullptr;
1680 
1681  return m_itemList.at(pos);
1682 }
1683 
1685 {
1686  if (!m_initialized)
1687  Init();
1688 
1689  for (int i = 0; i < m_itemList.size(); ++i)
1690  {
1691  MythUIButtonListItem *item = m_itemList.at(i);
1692 
1693  if (item->GetData() == data)
1694  return item;
1695  }
1696 
1697  return nullptr;
1698 }
1699 
1701 {
1702  if (!item)
1703  return -1;
1704 
1705  return m_itemList.indexOf(item);
1706 }
1707 
1708 void MythUIButtonList::InitButton(int itemIdx, MythUIStateType* & realButton,
1709  MythUIButtonListItem* & buttonItem)
1710 {
1711  buttonItem = m_itemList[itemIdx];
1712 
1713  if (m_maxVisible == 0)
1714  {
1715  QString name("buttonlist button 0");
1716  MythUIStateType *button = new MythUIStateType(this, name);
1717  button->CopyFrom(m_buttontemplate);
1718  button->ConnectDependants(true);
1719  m_ButtonList.append(button);
1720  ++m_maxVisible;
1721  }
1722 
1723  realButton = m_ButtonList[0];
1724  m_ButtonToItem[0] = buttonItem;
1725 }
1726 
1727 /*
1728  * PageUp and PageDown are helpers when Dynamic layout is being used.
1729  *
1730  * When buttons are layed out dynamically, the number of buttons on the next
1731  * page, may not equal the number of buttons on the current page. Dynamic
1732  * layout is always center-weighted, so attempt to figure out which button
1733  * is near the middle on the next page.
1734  */
1735 
1737 {
1738  int pos = m_selPosition;
1739  int total = 0;
1740  MythUIGroup *buttonstate;
1741  MythUIStateType *realButton;
1742  MythUIButtonListItem *buttonItem;
1743 
1744  /*
1745  * /On the new page/
1746  * If the number of buttons before the selected button does not equal
1747  * the number of buttons after the selected button, this logic can
1748  * undershoot the new selected button. That is better than overshooting
1749  * though.
1750  *
1751  * To fix this would require laying out the new page and then figuring
1752  * out which button should be selected, but this is already complex enough.
1753  */
1754 
1755  if (m_layout == LayoutHorizontal)
1756  {
1757  pos -= (m_leftColumns + 1);
1758 
1759  int max_width = m_contentsRect.width() / 2;
1760 
1761  for (; pos >= 0; --pos)
1762  {
1763  InitButton(pos, realButton, buttonItem);
1764  buttonItem->SetToRealButton(realButton, true);
1765  buttonstate = dynamic_cast<MythUIGroup *>
1766  (realButton->GetCurrentState());
1767 
1768  if (buttonstate == nullptr)
1769  {
1770  LOG(VB_GENERAL, LOG_ERR,
1771  "PageUp: Failed to query buttonlist state");
1772  return pos;
1773  }
1774 
1775  if (total + m_itemHorizSpacing +
1776  buttonstate->GetArea().width() / 2 >= max_width)
1777  return pos + 1;
1778 
1779  buttonItem->SetToRealButton(realButton, false);
1780  buttonstate = dynamic_cast<MythUIGroup *>
1781  (realButton->GetCurrentState());
1782  if (buttonstate)
1783  total += m_itemHorizSpacing + buttonstate->GetArea().width();
1784  }
1785 
1786  return 0;
1787  }
1788 
1789  // Grid or Vertical
1790  int dec;
1791 
1792  if (m_layout == LayoutGrid)
1793  {
1794  /*
1795  * Adjusting using bottomRow:TopRow only works if new page
1796  * has the same ratio as the previous page, but that is common
1797  * with the grid layout, so go for it. If themers start doing
1798  * grids where this is not true, then this will need to be modified.
1799  */
1800  pos -= (m_columns * (m_topRows + 2 +
1801  qMax(m_bottomRows - m_topRows, 0)));
1802  dec = m_columns;
1803  }
1804  else
1805  {
1806  pos -= (m_topRows + 1);
1807  dec = 1;
1808  }
1809 
1810  int max_height = m_contentsRect.height() / 2;
1811 
1812  for (; pos >= 0; pos -= dec)
1813  {
1814  InitButton(pos, realButton, buttonItem);
1815  buttonItem->SetToRealButton(realButton, true);
1816  buttonstate = dynamic_cast<MythUIGroup *>
1817  (realButton->GetCurrentState());
1818 
1819  if (buttonstate == nullptr)
1820  {
1821  LOG(VB_GENERAL, LOG_ERR,
1822  "PageUp: Failed to query buttonlist state");
1823  return pos;
1824  }
1825 
1826  if (total + m_itemHorizSpacing +
1827  buttonstate->GetArea().height() / 2 >= max_height)
1828  return pos + dec;
1829 
1830  buttonItem->SetToRealButton(realButton, false);
1831  buttonstate = dynamic_cast<MythUIGroup *>
1832  (realButton->GetCurrentState());
1833  if (buttonstate)
1834  total += m_itemHorizSpacing + buttonstate->GetArea().height();
1835  }
1836 
1837  return 0;
1838 }
1839 
1841 {
1842  int pos = m_selPosition;
1843  int num_items = m_itemList.size();
1844  int total = 0;
1845  MythUIGroup *buttonstate;
1846  MythUIStateType *realButton;
1847  MythUIButtonListItem *buttonItem;
1848 
1849  /*
1850  * /On the new page/
1851  * If the number of buttons before the selected button does not equal
1852  * the number of buttons after the selected button, this logic can
1853  * undershoot the new selected button. That is better than overshooting
1854  * though.
1855  *
1856  * To fix this would require laying out the new page and then figuring
1857  * out which button should be selected, but this is already complex enough.
1858  */
1859 
1860  if (m_layout == LayoutHorizontal)
1861  {
1862  pos += (m_rightColumns + 1);
1863 
1864  int max_width = m_contentsRect.width() / 2;
1865 
1866  for (; pos < num_items; ++pos)
1867  {
1868  InitButton(pos, realButton, buttonItem);
1869  buttonItem->SetToRealButton(realButton, true);
1870  buttonstate = dynamic_cast<MythUIGroup *>
1871  (realButton->GetCurrentState());
1872 
1873  if (buttonstate == nullptr)
1874  {
1875  LOG(VB_GENERAL, LOG_ERR,
1876  "PageDown: Failed to query buttonlist state");
1877  return pos;
1878  }
1879 
1880  if (total + m_itemHorizSpacing +
1881  buttonstate->GetArea().width() / 2 >= max_width)
1882  return pos - 1;
1883 
1884  buttonItem->SetToRealButton(realButton, false);
1885  buttonstate = dynamic_cast<MythUIGroup *>
1886  (realButton->GetCurrentState());
1887  if (buttonstate)
1888  total += m_itemHorizSpacing + buttonstate->GetArea().width();
1889  }
1890 
1891  return num_items - 1;
1892  }
1893 
1894  // Grid or Vertical
1895  int inc;
1896 
1897  if (m_layout == LayoutGrid)
1898  {
1899  /*
1900  * Adjusting using bottomRow:TopRow only works if new page
1901  * has the same ratio as the previous page, but that is common
1902  * with the grid layout, so go for it. If themers start doing
1903  * grids where this is not true, then this will need to be modified.
1904  */
1905  pos += (m_columns * (m_bottomRows + 2 +
1906  qMax(m_topRows - m_bottomRows, 0)));
1907  inc = m_columns;
1908  }
1909  else
1910  {
1911  pos += (m_bottomRows + 1);
1912  inc = 1;
1913  }
1914 
1915  int max_height = m_contentsRect.height() / 2;
1916 
1917  for (; pos < num_items; pos += inc)
1918  {
1919  InitButton(pos, realButton, buttonItem);
1920  buttonItem->SetToRealButton(realButton, true);
1921  buttonstate = dynamic_cast<MythUIGroup *>
1922  (realButton->GetCurrentState());
1923 
1924  if (!buttonstate)
1925  {
1926  LOG(VB_GENERAL, LOG_ERR,
1927  "PageDown: Failed to query buttonlist state");
1928  return pos;
1929  }
1930 
1931  if (total + m_itemHorizSpacing +
1932  buttonstate->GetArea().height() / 2 >= max_height)
1933  return pos - inc;
1934 
1935  buttonItem->SetToRealButton(realButton, false);
1936  buttonstate = dynamic_cast<MythUIGroup *>
1937  (realButton->GetCurrentState());
1938  if (buttonstate)
1939  total += m_itemHorizSpacing + buttonstate->GetArea().height();
1940  }
1941 
1942  return num_items - 1;
1943 }
1944 
1946 {
1947  int pos = m_selPosition;
1948 
1949  if (pos == -1 || m_itemList.isEmpty() || !m_initialized)
1950  return false;
1951 
1952  switch (unit)
1953  {
1954  case MoveItem:
1955  if (m_selPosition > 0)
1956  --m_selPosition;
1957  else if (m_wrapStyle > WrapNone)
1958  m_selPosition = m_itemList.size() - 1;
1959  else if (m_wrapStyle == WrapCaptive)
1960  return true;
1961 
1962  FindEnabledUp(unit);
1963 
1964  break;
1965 
1966  case MoveColumn:
1967  if (pos % m_columns > 0)
1968  --m_selPosition;
1969  else if (m_wrapStyle == WrapFlowing)
1970  if (m_selPosition == 0)
1971  --m_selPosition = m_itemList.size() - 1;
1972  else
1973  --m_selPosition;
1974  else if (m_wrapStyle > WrapNone)
1975  m_selPosition = pos + (m_columns - 1);
1976  else if (m_wrapStyle == WrapCaptive)
1977  return true;
1978 
1979  FindEnabledUp(unit);
1980 
1981  break;
1982 
1983  case MoveRow:
1984  if (m_scrollStyle != ScrollFree)
1985  {
1987  if (m_selPosition < 0)
1988  m_selPosition += m_itemList.size();
1989  else
1990  m_selPosition %= m_itemList.size();
1991  }
1992  else if ((pos - m_columns) >= 0)
1994  else if (m_wrapStyle > WrapNone)
1995  {
1996  m_selPosition = ((m_itemList.size() - 1) / m_columns) *
1997  m_columns + pos;
1998 
1999  if ((m_selPosition / m_columns)
2000  < ((m_itemList.size() - 1) / m_columns))
2001  m_selPosition = m_itemList.size() - 1;
2002 
2003  if (m_layout == LayoutVertical)
2004  m_topPosition = qMax(0, m_selPosition - (int)m_itemsVisible + 1);
2005  }
2006  else if (m_wrapStyle == WrapCaptive)
2007  return true;
2008 
2009  FindEnabledUp(unit);
2010 
2011  break;
2012 
2013  case MovePage:
2014  if (m_arrange == ArrangeFixed)
2015  m_selPosition = qMax(0, m_selPosition - (int)m_itemsVisible);
2016  else
2017  m_selPosition = PageUp();
2018 
2019  FindEnabledUp(unit);
2020 
2021  break;
2022 
2023  case MoveMid:
2024  m_selPosition = m_itemList.size() / 2;
2025  FindEnabledUp(unit);
2026  break;
2027 
2028  case MoveMax:
2029  m_selPosition = 0;
2030  FindEnabledUp(unit);
2031  break;
2032 
2033  case MoveByAmount:
2034  for (uint i = 0; i < amount; ++i)
2035  {
2036  if (m_selPosition > 0)
2037  --m_selPosition;
2038  else if (m_wrapStyle > WrapNone)
2039  m_selPosition = m_itemList.size() - 1;
2040  }
2041 
2042  FindEnabledUp(unit);
2043 
2044  break;
2045  }
2046 
2047  SanitizePosition();
2048 
2049  if (pos != m_selPosition)
2050  {
2051  Update();
2052  emit itemSelected(GetItemCurrent());
2053  }
2054  else
2055  return false;
2056 
2057  return true;
2058 }
2059 
2060 
2065 {
2066  if (m_selPosition < 0 || m_selPosition >= m_itemList.size() ||
2067  m_itemList.at(m_selPosition)->isEnabled())
2068  return;
2069 
2070  int step = (unit == MoveRow) ? m_columns : 1;
2071  if (unit == MoveRow && m_wrapStyle == WrapFlowing)
2072  unit = MoveItem;
2073  if (unit == MoveColumn)
2074  {
2075  while (m_selPosition < m_itemList.size() &&
2076  (m_selPosition + 1) % m_columns > 0 &&
2077  !m_itemList.at(m_selPosition)->isEnabled())
2078  ++m_selPosition;
2079 
2080  if (m_itemList.at(m_selPosition)->isEnabled())
2081  return;
2082 
2083  if (m_wrapStyle > WrapNone)
2084  {
2086  while ((m_selPosition + 1) % m_columns > 0 &&
2087  !m_itemList.at(m_selPosition)->isEnabled())
2088  ++m_selPosition;
2089  }
2090  }
2091  else
2092  {
2093  while (!m_itemList.at(m_selPosition)->isEnabled() &&
2094  (m_selPosition < m_itemList.size() - step))
2095  m_selPosition += step;
2096 
2097  if (!m_itemList.at(m_selPosition)->isEnabled() &&
2099  {
2100  m_selPosition = (m_selPosition + step) % m_itemList.size();
2101 
2102  while (!m_itemList.at(m_selPosition)->isEnabled() &&
2103  (m_selPosition < m_itemList.size() - step))
2104  m_selPosition += step;
2105  }
2106  }
2107 }
2108 
2110 {
2111  if (m_selPosition < 0 || m_selPosition >= m_itemList.size() ||
2112  m_itemList.at(m_selPosition)->isEnabled())
2113  return;
2114 
2115  int step = (unit == MoveRow) ? m_columns : 1;
2116  if (unit == MoveRow && m_wrapStyle == WrapFlowing)
2117  unit = MoveItem;
2118  if (unit == MoveColumn)
2119  {
2120  while (m_selPosition > 0 && (m_selPosition - 1) % m_columns > 0 &&
2121  !m_itemList.at(m_selPosition)->isEnabled())
2122  --m_selPosition;
2123 
2124  if (m_itemList.at(m_selPosition)->isEnabled())
2125  return;
2126 
2127  if (m_wrapStyle > WrapNone)
2128  {
2130  while ((m_selPosition - 1) % m_columns > 0 &&
2131  !m_itemList.at(m_selPosition)->isEnabled())
2132  --m_selPosition;
2133  }
2134  }
2135  else
2136  {
2137  while (!m_itemList.at(m_selPosition)->isEnabled() &&
2138  (m_selPosition - step >= 0))
2139  m_selPosition -= step;
2140 
2141  if (!m_itemList.at(m_selPosition)->isEnabled() &&
2143  {
2144  m_selPosition = m_itemList.size() - 1;
2145 
2146  while (m_selPosition > 0 &&
2147  !m_itemList.at(m_selPosition)->isEnabled() &&
2148  (m_selPosition - step >= 0))
2149  m_selPosition -= step;
2150  }
2151  }
2152 }
2153 
2154 
2156 {
2157  int pos = m_selPosition;
2158 
2159  if (pos == -1 || m_itemList.isEmpty() || !m_initialized)
2160  return false;
2161 
2162  switch (unit)
2163  {
2164  case MoveItem:
2165  if (m_selPosition < m_itemList.size() - 1)
2166  ++m_selPosition;
2167  else if (m_wrapStyle > WrapNone)
2168  m_selPosition = 0;
2169  else if (m_wrapStyle == WrapCaptive)
2170  return true;
2171 
2172  FindEnabledDown(unit);
2173 
2174  break;
2175 
2176  case MoveColumn:
2177  if ((pos + 1) % m_columns > 0)
2178  ++m_selPosition;
2179  else if (m_wrapStyle == WrapFlowing)
2180  if (m_selPosition < m_itemList.size() - 1)
2181  ++m_selPosition;
2182  else
2183  m_selPosition = 0;
2184  else if (m_wrapStyle > WrapNone)
2185  m_selPosition = pos - (m_columns - 1);
2186  else if (m_wrapStyle == WrapCaptive)
2187  return true;
2188 
2189  FindEnabledDown(unit);
2190 
2191  break;
2192 
2193  case MoveRow:
2194  if (m_itemList.empty() || m_columns < 1)
2195  return true;
2196  if (m_scrollStyle != ScrollFree)
2197  {
2199  m_selPosition %= m_itemList.size();
2200  }
2201  else if (((m_itemList.size() - 1) / qMax(m_columns, 0))
2202  > (pos / m_columns))
2203  {
2205  if (m_selPosition >= m_itemList.size())
2206  m_selPosition = m_itemList.size() - 1;
2207  }
2208  else if (m_wrapStyle > WrapNone)
2209  m_selPosition = (pos % m_columns);
2210  else if (m_wrapStyle == WrapCaptive)
2211  return true;
2212 
2213  FindEnabledDown(unit);
2214 
2215  break;
2216 
2217  case MovePage:
2218  if (m_arrange == ArrangeFixed)
2219  m_selPosition = qMin(m_itemCount - 1,
2220  m_selPosition + (int)m_itemsVisible);
2221  else
2222  m_selPosition = PageDown();
2223 
2224  FindEnabledDown(unit);
2225 
2226  break;
2227 
2228  case MoveMax:
2229  m_selPosition = m_itemCount - 1;
2230  FindEnabledDown(unit);
2231  break;
2232 
2233  case MoveByAmount:
2234  for (uint i = 0; i < amount; ++i)
2235  {
2236  if (m_selPosition < m_itemList.size() - 1)
2237  ++m_selPosition;
2238  else if (m_wrapStyle > WrapNone)
2239  m_selPosition = 0;
2240  }
2241  FindEnabledDown(unit);
2242  break;
2243 
2244  case MoveMid:
2245  break;
2246  }
2247 
2248  SanitizePosition();
2249 
2250  if (pos != m_selPosition)
2251  {
2252  m_keepSelAtBottom = true;
2253  Update();
2254  emit itemSelected(GetItemCurrent());
2255  }
2256  else
2257  return false;
2258 
2259  return true;
2260 }
2261 
2262 bool MythUIButtonList::MoveToNamedPosition(const QString &position_name)
2263 {
2264  if (!m_initialized)
2265  Init();
2266 
2267  if (m_selPosition < 0 || m_itemList.isEmpty() || !m_initialized)
2268  return false;
2269 
2270  bool found_it = false;
2271  int selectedPosition = 0;
2272  QList<MythUIButtonListItem *>::iterator it = m_itemList.begin();
2273 
2274  while (it != m_itemList.end())
2275  {
2276  if ((*it)->GetText() == position_name)
2277  {
2278  found_it = true;
2279  break;
2280  }
2281 
2282  ++it;
2283  ++selectedPosition;
2284  }
2285 
2286  if (!found_it || m_selPosition == selectedPosition)
2287  return false;
2288 
2289  SetItemCurrent(selectedPosition);
2290  return true;
2291 }
2292 
2294 {
2295  if (GetItemCurrent() != item)
2296  return false;
2297 
2298  if (item == m_itemList.first() && up)
2299  return false;
2300 
2301  if (item == m_itemList.last() && !up)
2302  return false;
2303 
2304  int oldpos = m_selPosition;
2305  int insertat = 0;
2306  bool dolast = false;
2307 
2308  if (up)
2309  {
2310  insertat = m_selPosition - 1;
2311 
2312  if (item == m_itemList.last())
2313  dolast = true;
2314  else
2315  ++m_selPosition;
2316 
2317  if (item == m_itemList.at(m_topPosition))
2318  ++m_topPosition;
2319  }
2320  else
2321  insertat = m_selPosition + 1;
2322 
2323  m_itemList.removeAt(oldpos);
2324  m_itemList.insert(insertat, item);
2325 
2326  if (up)
2327  {
2328  MoveUp();
2329 
2330  if (!dolast)
2331  MoveUp();
2332  }
2333  else
2334  MoveDown();
2335 
2336  return true;
2337 }
2338 
2340 {
2341  QMutableListIterator<MythUIButtonListItem *> it(m_itemList);
2342 
2343  while (it.hasNext())
2344  it.next()->setChecked(state);
2345 }
2346 
2348 {
2349  if (m_initialized)
2350  return;
2351 
2352  m_upArrow = dynamic_cast<MythUIStateType *>(GetChild("upscrollarrow"));
2353  m_downArrow = dynamic_cast<MythUIStateType *>(GetChild("downscrollarrow"));
2354  m_scrollBar = dynamic_cast<MythUIScrollBar *>(GetChild("scrollbar"));
2355 
2356  if (m_upArrow)
2357  m_upArrow->SetVisible(true);
2358 
2359  if (m_downArrow)
2360  m_downArrow->SetVisible(true);
2361 
2362  if (m_scrollBar)
2364 
2366 
2367  m_buttontemplate = dynamic_cast<MythUIStateType *>(GetChild("buttonitem"));
2368 
2369  if (!m_buttontemplate)
2370  {
2371  LOG(VB_GENERAL, LOG_ERR, QString("(%1) Statetype buttonitem is "
2372  "required in mythuibuttonlist: %2")
2373  .arg(GetXMLLocation()).arg(objectName()));
2374  return;
2375  }
2376 
2377  m_buttontemplate->SetVisible(false);
2378 
2379  MythRect buttonItemArea;
2380 
2381  MythUIGroup *buttonActiveState = dynamic_cast<MythUIGroup *>
2382  (m_buttontemplate->GetState("active"));
2383 
2384  if (buttonActiveState)
2385  buttonItemArea = buttonActiveState->GetArea();
2386  else
2387  buttonItemArea = m_buttontemplate->GetArea();
2388 
2389  buttonItemArea.CalculateArea(m_contentsRect);
2390 
2391  m_itemHeight = buttonItemArea.height();
2392  m_itemWidth = buttonItemArea.width();
2393 
2394  /*
2395  * If fixed spacing is defined, then use the "active" state size
2396  * to predictively determine the position of each button.
2397  */
2398  if (m_arrange == ArrangeFixed)
2399  {
2400 
2402 
2403  int col = 1;
2404  int row = 1;
2405 
2406  for (int i = 0; i < (int)m_itemsVisible; ++i)
2407  {
2408  QString name = QString("buttonlist button %1").arg(i);
2409  MythUIStateType *button = new MythUIStateType(this, name);
2410  button->CopyFrom(m_buttontemplate);
2411  button->ConnectDependants(true);
2412 
2413  if (col > m_columns)
2414  {
2415  col = 1;
2416  ++row;
2417  }
2418 
2419  button->SetPosition(GetButtonPosition(col, row));
2420  ++col;
2421 
2422  m_ButtonList.push_back(button);
2423  }
2424  }
2425 
2426  // The following is pretty much a hack for the benefit of MythGallery
2427  // it scales images based on the button size and we need to give it the
2428  // largest button state so that the images are not too small
2429  // This can be removed once the disk based image caching is added to
2430  // mythui, since the mythgallery thumbnail generator can be ditched.
2431  MythUIGroup *buttonSelectedState = dynamic_cast<MythUIGroup *>
2432  (m_buttontemplate->GetState("selected"));
2433 
2434  if (buttonSelectedState)
2435  {
2436  MythRect itemArea = buttonSelectedState->GetArea();
2437  itemArea.CalculateArea(m_contentsRect);
2438 
2439  if (m_itemHeight < itemArea.height())
2440  m_itemHeight = itemArea.height();
2441 
2442  if (m_itemWidth < itemArea.width())
2443  m_itemWidth = itemArea.width();
2444  }
2445 
2446  // End Hack
2447 
2448  m_initialized = true;
2449 }
2450 
2452 {
2453  if (!m_initialized)
2454  Init();
2455 
2456  return m_itemWidth;
2457 }
2458 
2460 {
2461  if (!m_initialized)
2462  Init();
2463 
2464  return m_itemHeight;
2465 }
2466 
2470 bool MythUIButtonList::keyPressEvent(QKeyEvent *event)
2471 {
2472  QStringList actions;
2473  bool handled = false;
2474  handled = GetMythMainWindow()->TranslateKeyPress("Global", event, actions);
2475 
2476  // Handle action remappings
2477  for (int i = 0; i < actions.size(); ++i)
2478  {
2479  if (!m_actionRemap.contains(actions[i]))
2480  continue;
2481 
2482  QString key = m_actionRemap[actions[i]];
2483  if (key.isEmpty())
2484  return true;
2485 
2486  QKeySequence a(key);
2487  if (a.isEmpty())
2488  continue;
2489 
2490  int keyCode = a[0];
2491  Qt::KeyboardModifiers modifiers = Qt::NoModifier;
2492  QStringList parts = key.split('+');
2493  for (int j = 0; j < parts.count(); ++j)
2494  {
2495  if (parts[j].toUpper() == "CTRL")
2496  modifiers |= Qt::ControlModifier;
2497  if (parts[j].toUpper() == "SHIFT")
2498  modifiers |= Qt::ShiftModifier;
2499  if (parts[j].toUpper() == "ALT")
2500  modifiers |= Qt::AltModifier;
2501  if (parts[j].toUpper() == "META")
2502  modifiers |= Qt::MetaModifier;
2503  }
2504 
2505  QCoreApplication::postEvent(
2507  new QKeyEvent(QEvent::KeyPress, keyCode, modifiers, key));
2508  QCoreApplication::postEvent(
2510  new QKeyEvent(QEvent::KeyRelease, keyCode, modifiers, key));
2511 
2512  return true;
2513  }
2514 
2515  // handle actions for this container
2516  for (int i = 0; i < actions.size() && !handled; ++i)
2517  {
2518  QString action = actions[i];
2519  handled = true;
2520 
2521  if (action == "UP")
2522  {
2523  if ((m_layout == LayoutVertical) || (m_layout == LayoutGrid))
2524  handled = MoveUp(MoveRow);
2525  else
2526  handled = false;
2527  }
2528  else if (action == "DOWN")
2529  {
2530  if ((m_layout == LayoutVertical) || (m_layout == LayoutGrid))
2531  handled = MoveDown(MoveRow);
2532  else
2533  handled = false;
2534  }
2535  else if (action == "RIGHT")
2536  {
2537  if (m_layout == LayoutHorizontal)
2538  handled = MoveDown(MoveItem);
2539  else if (m_layout == LayoutGrid)
2540  {
2541  if (m_scrollStyle == ScrollFree)
2542  handled = MoveDown(MoveColumn);
2543  else
2544  handled = MoveDown(MoveItem);
2545  }
2546  else
2547  handled = false;
2548  }
2549  else if (action == "LEFT")
2550  {
2551  if (m_layout == LayoutHorizontal)
2552  handled = MoveUp(MoveItem);
2553  else if (m_layout == LayoutGrid)
2554  {
2555  if (m_scrollStyle == ScrollFree)
2556  handled = MoveUp(MoveColumn);
2557  else
2558  handled = MoveUp(MoveItem);
2559  }
2560  else
2561  handled = false;
2562  }
2563  else if (action == "PAGEUP")
2564  {
2565  MoveUp(MovePage);
2566  }
2567  else if (action == "PAGEDOWN")
2568  {
2569  MoveDown(MovePage);
2570  }
2571  else if (action == "PAGETOP")
2572  {
2573  MoveUp(MoveMax);
2574  }
2575  else if (action == "PAGEMIDDLE")
2576  {
2577  MoveUp(MoveMid);
2578  }
2579  else if (action == "PAGEBOTTOM")
2580  {
2581  MoveDown(MoveMax);
2582  }
2583  else if (action == "SELECT")
2584  {
2586 
2587  if (item && item->isEnabled())
2588  emit itemClicked(item);
2589  }
2590  else if (action == "SEARCH")
2591  {
2592  ShowSearchDialog();
2593  }
2594  else
2595  handled = false;
2596  }
2597 
2598  return handled;
2599 }
2600 
2605 {
2606  bool handled = false;
2607 
2608  switch (event->gesture())
2609  {
2611  {
2612  // We want the relative position of the click
2613  QPoint position = event->GetPosition() -
2614  m_Parent->GetArea().topLeft();
2615 
2616  MythUIType *type = GetChildAt(position, false, false);
2617 
2618  if (!type)
2619  return false;
2620 
2621  MythUIStateType *object = dynamic_cast<MythUIStateType *>(type);
2622 
2623  if (object)
2624  {
2625  handled = true;
2626  QString name = object->objectName();
2627 
2628  if (name == "upscrollarrow")
2629  {
2630  MoveUp(MovePage);
2631  }
2632  else if (name == "downscrollarrow")
2633  {
2634  MoveDown(MovePage);
2635  }
2636  else if (name.startsWith("buttonlist button"))
2637  {
2638  int pos = name.section(' ', 2, 2).toInt();
2639  MythUIButtonListItem *item = m_ButtonToItem.value(pos);
2640 
2641  if (item)
2642  {
2643  if (item == GetItemCurrent())
2644  emit itemClicked(item);
2645  else
2646  SetItemCurrent(item);
2647  }
2648  }
2649  else
2650  handled = false;
2651  }
2652  }
2653  break;
2654 
2655  case MythGestureEvent::Up:
2656  if ((m_layout == LayoutVertical) || (m_layout == LayoutGrid))
2657  handled = MoveUp(MovePage);
2658  break;
2659 
2662  if ((m_layout == LayoutVertical) || (m_layout == LayoutGrid))
2663  handled = MoveUp(MoveRow);
2664  break;
2665 
2667  if ((m_layout == LayoutVertical) || (m_layout == LayoutGrid))
2668  handled = MoveDown(MovePage);
2669  break;
2670 
2673  if ((m_layout == LayoutVertical) || (m_layout == LayoutGrid))
2674  handled = MoveDown(MoveRow);
2675  break;
2676 
2678  if (m_layout == LayoutHorizontal)
2679  handled = MoveDown(MoveItem);
2680  else if (m_layout == LayoutGrid)
2681  {
2682  if (m_scrollStyle == ScrollFree)
2683  handled = MoveDown(MoveColumn);
2684  else
2685  handled = MoveDown(MoveItem);
2686  }
2687  break;
2688 
2690  if (m_layout == LayoutHorizontal)
2691  handled = MoveUp(MoveItem);
2692  else if (m_layout == LayoutGrid)
2693  {
2694  if (m_scrollStyle == ScrollFree)
2695  handled = MoveUp(MoveColumn);
2696  else
2697  handled = MoveUp(MoveItem);
2698  }
2699  break;
2700 
2701  default:
2702  break;
2703  }
2704 
2705  return handled;
2706 }
2707 
2708 class NextButtonListPageEvent : public QEvent
2709 {
2710  public:
2711  NextButtonListPageEvent(int start, int pageSize) :
2712  QEvent(kEventType), m_start(start), m_pageSize(pageSize) {}
2713  const int m_start;
2714  const int m_pageSize;
2715  static Type kEventType;
2716 };
2717 
2719  (QEvent::Type) QEvent::registerEventType();
2720 
2722 {
2723  if (event->type() == NextButtonListPageEvent::kEventType)
2724  {
2726  static_cast<NextButtonListPageEvent*>(event);
2727  int cur = npe->m_start;
2728  for (; cur < npe->m_start + npe->m_pageSize && cur < GetCount(); ++cur)
2729  {
2730  const int loginterval = (cur < 1000 ? 100 : 500);
2731  if (cur > 200 && cur % loginterval == 0)
2732  LOG(VB_GUI, LOG_INFO,
2733  QString("Build background buttonlist item %1").arg(cur));
2734  emit itemLoaded(GetItemAt(cur));
2735  }
2736  m_nextItemLoaded = cur;
2737  if (cur < GetCount())
2738  LoadInBackground(cur, npe->m_pageSize);
2739  }
2740 }
2741 
2742 void MythUIButtonList::LoadInBackground(int start, int pageSize)
2743 {
2744  m_nextItemLoaded = start;
2745  QCoreApplication::
2746  postEvent(this, new NextButtonListPageEvent(start, pageSize));
2747 }
2748 
2750 {
2751  QCoreApplication::
2752  removePostedEvents(this, NextButtonListPageEvent::kEventType);
2753  return m_nextItemLoaded;
2754 }
2755 
2756 QPoint MythUIButtonList::GetButtonPosition(int column, int row) const
2757 {
2758  int x = m_contentsRect.x() +
2759  ((column - 1) * (m_itemWidth + m_itemHorizSpacing));
2760  int y = m_contentsRect.y() +
2761  ((row - 1) * (m_itemHeight + m_itemVertSpacing));
2762 
2763  return {x, y};
2764 }
2765 
2767 {
2768  m_itemsVisible = 0;
2769  m_rows = 0;
2770  m_columns = 0;
2771 
2772  if ((m_layout == LayoutHorizontal) || (m_layout == LayoutGrid))
2773  {
2774  int x = 0;
2775 
2776  while (x <= m_contentsRect.width() - m_itemWidth)
2777  {
2779  ++m_columns;
2780  }
2781  }
2782 
2783  if ((m_layout == LayoutVertical) || (m_layout == LayoutGrid))
2784  {
2785  int y = 0;
2786 
2787  while (y <= m_contentsRect.height() - m_itemHeight)
2788  {
2790  ++m_rows;
2791  }
2792  }
2793 
2794  if (m_rows <= 0)
2795  m_rows = 1;
2796 
2797  if (m_columns <= 0)
2798  m_columns = 1;
2799 
2801 }
2802 
2804 {
2805  if (rect == m_contentsRect)
2806  return;
2807 
2808  m_contentsRect = rect;
2809 
2810  if (m_Area.isValid())
2812  else if (m_Parent)
2814  else
2815  m_contentsRect.CalculateArea(GetMythMainWindow()->GetUIScreenRect());
2816 }
2817 
2822  const QString &filename, QDomElement &element, bool showWarnings)
2823 {
2824  if (element.tagName() == "buttonarea")
2825  SetButtonArea(parseRect(element));
2826  else if (element.tagName() == "layout")
2827  {
2828  QString layout = getFirstText(element).toLower();
2829 
2830  if (layout == "grid")
2831  m_layout = LayoutGrid;
2832  else if (layout == "horizontal")
2834  else
2836  }
2837  else if (element.tagName() == "arrange")
2838  {
2839  QString arrange = getFirstText(element).toLower();
2840 
2841  if (arrange == "fill")
2843  else if (arrange == "spread")
2845  else if (arrange == "stack")
2847  else
2849 
2850  }
2851  else if (element.tagName() == "align")
2852  {
2853  QString align = getFirstText(element).toLower();
2854  m_alignment = parseAlignment(align);
2855  }
2856  else if (element.tagName() == "scrollstyle")
2857  {
2858  QString layout = getFirstText(element).toLower();
2859 
2860  if (layout == "center")
2862  else if (layout == "groupcenter")
2864  else if (layout == "free")
2866  }
2867  else if (element.tagName() == "wrapstyle")
2868  {
2869  QString wrapstyle = getFirstText(element).toLower();
2870 
2871  if (wrapstyle == "captive")
2873  else if (wrapstyle == "none")
2875  else if (wrapstyle == "selection")
2877  else if (wrapstyle == "flowing")
2879  else if (wrapstyle == "items")
2881  }
2882  else if (element.tagName() == "showarrow")
2883  m_showArrow = parseBool(element);
2884  else if (element.tagName() == "showscrollbar")
2885  m_showScrollBar = parseBool(element);
2886  else if (element.tagName() == "spacing")
2887  {
2888  m_itemHorizSpacing = NormX(getFirstText(element).toInt());
2889  m_itemVertSpacing = NormY(getFirstText(element).toInt());
2890  }
2891  else if (element.tagName() == "drawfrombottom")
2892  {
2893  m_drawFromBottom = parseBool(element);
2894 
2895  if (m_drawFromBottom)
2896  m_alignment |= Qt::AlignBottom;
2897  }
2898  else if (element.tagName() == "searchposition")
2899  {
2900  m_searchPosition = parsePoint(element);
2901  }
2902  else if (element.tagName() == "triggerevent")
2903  {
2904  QString trigger = getFirstText(element);
2905  if (!trigger.isEmpty())
2906  {
2907  QString action = element.attribute("action", "");
2908  if (action.isEmpty())
2909  {
2910  m_actionRemap[trigger] = "";
2911  }
2912  else
2913  {
2914  QString context = element.attribute("context", "");
2915  QString keylist = GetMythMainWindow()->GetKey(context, action);
2916  QStringList keys = keylist.split(',', QString::SkipEmptyParts);
2917  if (!keys.empty())
2918  m_actionRemap[trigger] = keys[0];
2919  }
2920  }
2921  }
2922  else
2923  {
2924  return MythUIType::ParseElement(filename, element, showWarnings);
2925  }
2926 
2927  return true;
2928 }
2929 
2933 void MythUIButtonList::DrawSelf(MythPainter * /*p*/, int /*xoffset*/, int /*yoffset*/,
2934  int /*alphaMod*/, QRect /*clipRect*/)
2935 {
2936  if (m_needsUpdate)
2937  {
2940  }
2941 }
2942 
2947 {
2948  MythUIButtonList *lb = new MythUIButtonList(parent, objectName());
2949  lb->CopyFrom(this);
2950 }
2951 
2956 {
2957  MythUIButtonList *lb = dynamic_cast<MythUIButtonList *>(base);
2958 
2959  if (!lb)
2960  return;
2961 
2962  m_layout = lb->m_layout;
2963  m_arrange = lb->m_arrange;
2964  m_alignment = lb->m_alignment;
2965 
2967 
2968  m_itemHeight = lb->m_itemHeight;
2969  m_itemWidth = lb->m_itemWidth;
2973  m_maxVisible = lb->m_maxVisible;
2974 
2975  m_active = lb->m_active;
2976  m_showArrow = lb->m_showArrow;
2978 
2980 
2982  m_wrapStyle = lb->m_wrapStyle;
2983 
2984  m_clearing = false;
2986 
2989 
2990  MythUIType::CopyFrom(base);
2991 
2992  m_upArrow = dynamic_cast<MythUIStateType *>(GetChild("upscrollarrow"));
2993  m_downArrow = dynamic_cast<MythUIStateType *>(GetChild("downscrollarrow"));
2994  m_scrollBar = dynamic_cast<MythUIScrollBar *>(GetChild("scrollbar"));
2995 
2996  for (int i = 0; i < (int)m_itemsVisible; ++i)
2997  {
2998  QString name = QString("buttonlist button %1").arg(i);
2999  DeleteChild(name);
3000  }
3001 
3002  m_ButtonList.clear();
3003 
3005 
3006  m_initialized = false;
3007 }
3008 
3013 {
3015 }
3016 
3017 void MythUIButtonList::SetLCDTitles(const QString &title, const QString &columnList)
3018 {
3019  m_lcdTitle = title;
3020  m_lcdColumns = columnList.split('|');
3021 }
3022 
3024 {
3025  if (!m_HasFocus)
3026  return;
3027 
3028  LCD *lcddev = LCD::Get();
3029 
3030  if (lcddev == nullptr)
3031  return;
3032 
3033  // Build a list of the menu items
3034  QList<LCDMenuItem> menuItems;
3035  bool selected;
3036 
3037  int start = std::max(0, (int)(m_selPosition - lcddev->getLCDHeight()));
3038  int end = std::min(m_itemCount, (int)(start + (lcddev->getLCDHeight() * 2)));
3039 
3040  for (int r = start; r < end; ++r)
3041  {
3042  selected = r == GetCurrentPos();
3043 
3044  MythUIButtonListItem *item = GetItemAt(r);
3045  CHECKED_STATE state = NOTCHECKABLE;
3046 
3047  if (item->checkable())
3048  state = (item->state() == MythUIButtonListItem::NotChecked) ? UNCHECKED : CHECKED;
3049 
3050  QString text;
3051 
3052  for (int x = 0; x < m_lcdColumns.count(); ++x)
3053  {
3054  if (!m_lcdColumns[x].isEmpty() && item->m_strings.contains(m_lcdColumns[x]))
3055  {
3056  // named text column
3057  TextProperties props = item->m_strings[m_lcdColumns[x]];
3058 
3059  if (text.isEmpty())
3060  text = props.text;
3061  else
3062  text += " ~ " + props.text;
3063  }
3064  else
3065  {
3066  // default text column
3067  if (text.isEmpty())
3068  text = item->GetText();
3069  else
3070  text += " ~ " + item->GetText();
3071  }
3072  }
3073 
3074  if (!text.isEmpty())
3075  menuItems.append(LCDMenuItem(selected, state, text));
3076  else
3077  menuItems.append(LCDMenuItem(selected, state, item->GetText()));
3078  }
3079 
3080  if (!menuItems.isEmpty())
3081  lcddev->switchToMenu(menuItems, m_lcdTitle);
3082 }
3083 
3085 {
3086  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
3087 
3088  SearchButtonListDialog *dlg = new SearchButtonListDialog(popupStack, "MythSearchListDialog", this, "");
3089 
3090  if (dlg->Create())
3091  {
3092  if (m_searchPosition.x() != -2 || m_searchPosition.y() != -2)
3093  {
3094  int x = m_searchPosition.x();
3095  int y = m_searchPosition.y();
3096  QRect screenArea = GetMythMainWindow()->GetUIScreenRect();
3097  QRect dialogArea = dlg->GetArea();
3098 
3099  if (x == -1)
3100  x = (screenArea.width() - dialogArea.width()) / 2;
3101 
3102  if (y == -1)
3103  y = (screenArea.height() - dialogArea.height()) / 2;
3104 
3105  dlg->SetPosition(x, y);
3106  }
3107 
3108  popupStack->AddScreen(dlg);
3109  }
3110  else
3111  delete dlg;
3112 }
3113 
3114 bool MythUIButtonList::Find(const QString &searchStr, bool startsWith)
3115 {
3116  m_searchStr = searchStr;
3117  m_searchStartsWith = startsWith;
3118  return DoFind(false, true);
3119 }
3120 
3122 {
3123  return DoFind(true, true);
3124 }
3125 
3127 {
3128  return DoFind(true, false);
3129 }
3130 
3131 bool MythUIButtonList::DoFind(bool doMove, bool searchForward)
3132 {
3133  if (m_searchStr.isEmpty())
3134  return true;
3135 
3136  if (GetCount() == 0)
3137  return false;
3138 
3139  int startPos = GetCurrentPos();
3140  int currPos = startPos;
3141  bool found = false;
3142 
3143  if (doMove)
3144  {
3145  if (searchForward)
3146  {
3147  ++currPos;
3148 
3149  if (currPos >= GetCount())
3150  currPos = 0;
3151  }
3152  else
3153  {
3154  --currPos;
3155 
3156  if (currPos < 0)
3157  currPos = GetCount() - 1;
3158  }
3159  }
3160 
3161  while (true)
3162  {
3164 
3165  if (found)
3166  {
3167  SetItemCurrent(currPos);
3168  return true;
3169  }
3170 
3171  if (searchForward)
3172  {
3173  ++currPos;
3174 
3175  if (currPos >= GetCount())
3176  currPos = 0;
3177  }
3178  else
3179  {
3180  --currPos;
3181 
3182  if (currPos < 0)
3183  currPos = GetCount() - 1;
3184  }
3185 
3186  if (startPos == currPos)
3187  break;
3188  }
3189 
3190  return false;
3191 }
3192 
3194 
3196  const QString &text, const QString &image,
3197  bool checkable, CheckState state,
3198  bool showArrow, int listPosition)
3199  : m_parent(lbtype), m_text(text), m_imageFilename(image),
3200  m_checkable(checkable), m_state(state), m_showArrow(showArrow)
3201 {
3202  if (!lbtype)
3203  LOG(VB_GENERAL, LOG_ERR, "Cannot add a button to a non-existent list!");
3204 
3205  if (state >= NotChecked)
3206  m_checkable = true;
3207 
3208  if (m_parent)
3209  m_parent->InsertItem(this, listPosition);
3210 }
3211 
3213  const QString &text,
3214  QVariant data, int listPosition)
3215 {
3216  if (!lbtype)
3217  LOG(VB_GENERAL, LOG_ERR, "Cannot add a button to a non-existent list!");
3218 
3219  m_parent = lbtype;
3220  m_text = text;
3221  m_data = std::move(data);
3222 
3223  m_image = nullptr;
3224 
3225  m_checkable = false;
3226  m_state = CantCheck;
3227  m_showArrow = false;
3228  m_isVisible = false;
3229  m_enabled = true;
3230 
3231  if (m_parent)
3232  m_parent->InsertItem(this, listPosition);
3233 }
3234 
3236 {
3237  if (m_parent)
3238  m_parent->RemoveItem(this);
3239 
3240  if (m_image)
3241  m_image->DecrRef();
3242 
3243  QMap<QString, MythImage*>::iterator it;
3244  for (it = m_images.begin(); it != m_images.end(); ++it)
3245  {
3246  if (*it)
3247  (*it)->DecrRef();
3248  }
3249  m_images.clear();
3250 }
3251 
3252 void MythUIButtonListItem::SetText(const QString &text, const QString &name,
3253  const QString &state)
3254 {
3255  if (!name.isEmpty())
3256  {
3257  TextProperties textprop;
3258  textprop.text = text;
3259  textprop.state = state;
3260  m_strings.insert(name, textprop);
3261  }
3262  else
3263  m_text = text;
3264 
3265  if (m_parent && m_isVisible)
3266  m_parent->Update();
3267 }
3268 
3270  const QString &state)
3271 {
3272  InfoMap::const_iterator map_it = infoMap.begin();
3273 
3274  while (map_it != infoMap.end())
3275  {
3276  TextProperties textprop;
3277  textprop.text = (*map_it);
3278  textprop.state = state;
3279  m_strings[map_it.key()] = textprop;
3280  ++map_it;
3281  }
3282 
3283  if (m_parent && m_isVisible)
3284  m_parent->Update();
3285 }
3286 
3287 void MythUIButtonListItem::SetTextFromMap(const QMap<QString, TextProperties> &stringMap)
3288 {
3289  m_strings.clear();
3290  m_strings = stringMap;
3291 }
3292 
3293 QString MythUIButtonListItem::GetText(const QString &name) const
3294 {
3295  if (name.isEmpty())
3296  return m_text;
3297  if (m_strings.contains(name))
3298  return m_strings[name].text;
3299  return QString();
3300 }
3301 
3302 bool MythUIButtonListItem::FindText(const QString &searchStr, const QString &fieldList,
3303  bool startsWith) const
3304 {
3305  if (fieldList.isEmpty())
3306  {
3307  if (startsWith)
3308  return m_text.startsWith(searchStr, Qt::CaseInsensitive);
3309  return m_text.contains(searchStr, Qt::CaseInsensitive);
3310  }
3311  if (fieldList == "**ALL**")
3312  {
3313  if (startsWith)
3314  {
3315  if (m_text.startsWith(searchStr, Qt::CaseInsensitive))
3316  return true;
3317  }
3318  else
3319  {
3320  if (m_text.contains(searchStr, Qt::CaseInsensitive))
3321  return true;
3322  }
3323 
3324  QMap<QString, TextProperties>::const_iterator i = m_strings.constBegin();
3325 
3326  while (i != m_strings.constEnd())
3327  {
3328  if (startsWith)
3329  {
3330  if (i.value().text.startsWith(searchStr, Qt::CaseInsensitive))
3331  return true;
3332  }
3333  else
3334  {
3335  if (i.value().text.contains(searchStr, Qt::CaseInsensitive))
3336  return true;
3337  }
3338 
3339  ++i;
3340  }
3341  }
3342  else
3343  {
3344  QStringList fields = fieldList.split(',', QString::SkipEmptyParts);
3345 
3346  for (int x = 0; x < fields.count(); ++x)
3347  {
3348  if (m_strings.contains(fields.at(x).trimmed()))
3349  {
3350  if (startsWith)
3351  {
3352  if (m_strings[fields.at(x)].text.startsWith(searchStr, Qt::CaseInsensitive))
3353  return true;
3354  }
3355  else
3356  {
3357  if (m_strings[fields.at(x)].text.contains(searchStr, Qt::CaseInsensitive))
3358  return true;
3359  }
3360  }
3361  }
3362  }
3363 
3364  return false;
3365 }
3366 
3367 void MythUIButtonListItem::SetFontState(const QString &state,
3368  const QString &name)
3369 {
3370  if (!name.isEmpty())
3371  {
3372  if (m_strings.contains(name))
3374  }
3375  else
3376  m_fontState = state;
3377 
3378  if (m_parent && m_isVisible)
3379  m_parent->Update();
3380 }
3381 
3382 void MythUIButtonListItem::SetImage(MythImage *image, const QString &name)
3383 {
3384  if (image)
3385  image->IncrRef();
3386 
3387  if (!name.isEmpty())
3388  {
3389  QMap<QString, MythImage*>::iterator it = m_images.find(name);
3390  if (it != m_images.end())
3391  {
3392  (*it)->DecrRef();
3393  if (image)
3394  *it = image;
3395  else
3396  m_images.erase(it);
3397  }
3398  else if (image)
3399  {
3400  m_images[name] = image;
3401  }
3402  }
3403  else
3404  {
3405  if (m_image)
3406  m_image->DecrRef();
3407  m_image = image;
3408  }
3409 
3410  if (m_parent && m_isVisible)
3411  m_parent->Update();
3412 }
3413 
3415 {
3416  m_imageFilenames.clear();
3417  m_imageFilenames = imageMap;
3418 }
3419 
3421 {
3422  if (!name.isEmpty())
3423  {
3424  QMap<QString, MythImage*>::iterator it = m_images.find(name);
3425  if (it != m_images.end())
3426  {
3427  (*it)->IncrRef();
3428  return (*it);
3429  }
3430  }
3431  else if (m_image)
3432  {
3433  m_image->IncrRef();
3434  return m_image;
3435  }
3436 
3437  return nullptr;
3438 }
3439 
3441  const QString &filename, const QString &name, bool force_reload)
3442 {
3443  bool do_update = force_reload;
3444 
3445  if (!name.isEmpty())
3446  {
3447  InfoMap::iterator it = m_imageFilenames.find(name);
3448 
3449  if (it == m_imageFilenames.end())
3450  {
3451  m_imageFilenames.insert(name, filename);
3452  do_update = true;
3453  }
3454  else if (*it != filename)
3455  {
3456  *it = filename;
3457  do_update = true;
3458  }
3459  }
3460  else if (m_imageFilename != filename)
3461  {
3462  m_imageFilename = filename;
3463  do_update = true;
3464  }
3465 
3466  if (m_parent && do_update && m_isVisible)
3467  m_parent->Update();
3468 }
3469 
3470 QString MythUIButtonListItem::GetImageFilename(const QString &name) const
3471 {
3472  if (name.isEmpty())
3473  return m_imageFilename;
3474 
3475  InfoMap::const_iterator it = m_imageFilenames.find(name);
3476 
3477  if (it != m_imageFilenames.end())
3478  return *it;
3479 
3480  return QString();
3481 }
3482 
3483 void MythUIButtonListItem::DisplayState(const QString &state,
3484  const QString &name)
3485 {
3486  if (name.isEmpty())
3487  return;
3488 
3489  bool do_update = false;
3490  InfoMap::iterator it = m_states.find(name);
3491 
3492  if (it == m_states.end())
3493  {
3494  m_states.insert(name, state);
3495  do_update = true;
3496  }
3497  else if (*it != state)
3498  {
3499  *it = state;
3500  do_update = true;
3501  }
3502 
3503  if (m_parent && do_update && m_isVisible)
3504  m_parent->Update();
3505 }
3506 
3508 {
3509  m_states.clear();
3510  m_states = stateMap;
3511 }
3512 
3514 {
3515  return m_checkable;
3516 }
3517 
3519 {
3520  return m_state;
3521 }
3522 
3524 {
3525  return m_parent;
3526 }
3527 
3529 {
3530  if (!m_checkable || m_state == state)
3531  return;
3532 
3533  m_state = state;
3534 
3535  if (m_parent && m_isVisible)
3536  m_parent->Update();
3537 }
3538 
3540 {
3541  m_checkable = flag;
3542 }
3543 
3545 {
3546  m_showArrow = flag;
3547 }
3548 
3550 {
3551  return m_enabled;
3552 }
3553 
3555 {
3556  m_enabled = flag;
3557 }
3558 
3560 {
3561  m_data = std::move(data);
3562 }
3563 
3565 {
3566  return m_data;
3567 }
3568 
3570 {
3571  if (m_parent)
3572  return m_parent->MoveItemUpDown(this, flag);
3573  return false;
3574 }
3575 
3577 {
3578  if (!m_parent)
3579  return;
3580 
3581  m_parent->ItemVisible(this);
3582  m_isVisible = true;
3583 
3584  QString state;
3585 
3586  if (!m_parent->IsEnabled())
3587  state = "disabled";
3588  else if (!m_enabled)
3589  {
3590  state = m_parent->m_active ? "disabledactive" : "disabledinactive";
3591  }
3592  else if (selected)
3593  {
3594  button->MoveToTop();
3595  state = m_parent->m_active ? "selectedactive" : "selectedinactive";
3596  }
3597  else
3598  state = m_parent->m_active ? "active" : "inactive";
3599 
3600  // Begin compatibility code
3601  // Attempt to fallback if the theme is missing certain states
3602  if (state == "disabled" && !button->GetState(state))
3603  {
3604  LOG(VB_GUI, LOG_WARNING, "Theme Error: Missing buttonlist state: disabled");
3605  state = "inactive";
3606  }
3607 
3608  if (state == "inactive" && !button->GetState(state))
3609  {
3610  LOG(VB_GUI, LOG_WARNING, "Theme Error: Missing buttonlist state: inactive");
3611  state = "active";
3612  }
3613  // End compatibility code
3614 
3615  MythUIGroup *buttonstate = dynamic_cast<MythUIGroup *>
3616  (button->GetState(state));
3617  if (!buttonstate)
3618  {
3619  LOG(VB_GENERAL, LOG_CRIT, QString("Theme Error: Missing buttonlist state: %1")
3620  .arg(state));
3621  return;
3622  }
3623 
3624  buttonstate->Reset();
3625 
3626  MythUIText *buttontext = dynamic_cast<MythUIText *>
3627  (buttonstate->GetChild("buttontext"));
3628 
3629  if (buttontext)
3630  {
3631  buttontext->SetText(m_text);
3632  buttontext->SetFontState(m_fontState);
3633  }
3634 
3635  MythUIImage *buttonimage = dynamic_cast<MythUIImage *>
3636  (buttonstate->GetChild("buttonimage"));
3637 
3638  if (buttonimage)
3639  {
3640  if (!m_imageFilename.isEmpty())
3641  {
3642  buttonimage->SetFilename(m_imageFilename);
3643  buttonimage->Load();
3644  }
3645  else if (m_image)
3646  buttonimage->SetImage(m_image);
3647  }
3648 
3649  MythUIImage *buttonarrow = dynamic_cast<MythUIImage *>
3650  (buttonstate->GetChild("buttonarrow"));
3651 
3652  if (buttonarrow)
3653  buttonarrow->SetVisible(m_showArrow);
3654 
3655  MythUIStateType *buttoncheck = dynamic_cast<MythUIStateType *>
3656  (buttonstate->GetChild("buttoncheck"));
3657 
3658  if (buttoncheck)
3659  {
3660  buttoncheck->SetVisible(m_checkable);
3661 
3662  if (m_checkable)
3663  {
3664  if (m_state == NotChecked)
3665  buttoncheck->DisplayState(MythUIStateType::Off);
3666  else if (m_state == HalfChecked)
3667  buttoncheck->DisplayState(MythUIStateType::Half);
3668  else
3669  buttoncheck->DisplayState(MythUIStateType::Full);
3670  }
3671  }
3672 
3673  MythUIText *text;
3674  QMap<QString, TextProperties>::iterator string_it = m_strings.begin();
3675 
3676  while (string_it != m_strings.end())
3677  {
3678  text = dynamic_cast<MythUIText *>
3679  (buttonstate->GetChild(string_it.key()));
3680 
3681  if (text)
3682  {
3683  TextProperties textprop = string_it.value();
3684 
3685  QString newText = text->GetTemplateText();
3686 
3687  QRegExp regexp("%(([^\\|%]+)?\\||\\|(.))?([\\w#]+)(\\|(.+))?%");
3688  regexp.setMinimal(true);
3689 
3690  if (!newText.isEmpty() && newText.contains(regexp))
3691  {
3692  int pos = 0;
3693  QString tempString = newText;
3694 
3695  while ((pos = regexp.indexIn(newText, pos)) != -1)
3696  {
3697  QString key = regexp.cap(4).toLower().trimmed();
3698  QString replacement;
3699  QString value = m_strings.value(key).text;
3700 
3701  if (!value.isEmpty())
3702  {
3703  replacement = QString("%1%2%3%4")
3704  .arg(regexp.cap(2))
3705  .arg(regexp.cap(3))
3706  .arg(m_strings.value(key).text)
3707  .arg(regexp.cap(6));
3708  }
3709 
3710  tempString.replace(regexp.cap(0), replacement);
3711  pos += regexp.matchedLength();
3712  }
3713 
3714  newText = tempString;
3715  }
3716  else
3717  newText = textprop.text;
3718 
3719  if (newText.isEmpty())
3720  text->Reset();
3721  else
3722  text->SetText(newText);
3723 
3724  text->SetFontState(textprop.state.isEmpty() ? m_fontState : textprop.state);
3725  }
3726 
3727  ++string_it;
3728  }
3729 
3730  MythUIImage *image;
3731  InfoMap::iterator imagefile_it = m_imageFilenames.begin();
3732 
3733  while (imagefile_it != m_imageFilenames.end())
3734  {
3735  image = dynamic_cast<MythUIImage *>
3736  (buttonstate->GetChild(imagefile_it.key()));
3737 
3738  if (image)
3739  {
3740  if (!imagefile_it.value().isEmpty())
3741  {
3742  image->SetFilename(imagefile_it.value());
3743  image->Load();
3744  }
3745  else
3746  image->Reset();
3747  }
3748 
3749  ++imagefile_it;
3750  }
3751 
3752  QMap<QString, MythImage *>::iterator image_it = m_images.begin();
3753 
3754  while (image_it != m_images.end())
3755  {
3756  image = dynamic_cast<MythUIImage *>
3757  (buttonstate->GetChild(image_it.key()));
3758 
3759  if (image)
3760  {
3761  if (image_it.value())
3762  image->SetImage(image_it.value());
3763  else
3764  image->Reset();
3765  }
3766 
3767  ++image_it;
3768  }
3769 
3770  MythUIStateType *statetype;
3771  InfoMap::iterator state_it = m_states.begin();
3772 
3773  while (state_it != m_states.end())
3774  {
3775  statetype = dynamic_cast<MythUIStateType *>
3776  (buttonstate->GetChild(state_it.key()));
3777 
3778  if (statetype)
3779  {
3780  if (!statetype->DisplayState(state_it.value()))
3781  statetype->Reset();
3782  }
3783 
3784  ++state_it;
3785  }
3786 
3787  // There is no need to check the return value here, since we already
3788  // checked that the state exists with GetState() earlier
3789  button->DisplayState(state);
3790 }
3791 
3792 //---------------------------------------------------------
3793 // SearchButtonListDialog
3794 //---------------------------------------------------------
3796 {
3797  if (!CopyWindowFromBase("MythSearchListDialog", this))
3798  return false;
3799 
3800  bool err = false;
3801  UIUtilE::Assign(this, m_searchEdit, "searchedit", &err);
3802  UIUtilE::Assign(this, m_prevButton, "prevbutton", &err);
3803  UIUtilE::Assign(this, m_nextButton, "nextbutton", &err);
3804  UIUtilW::Assign(this, m_searchState, "searchstate");
3805 
3806  if (err)
3807  {
3808  LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'MythSearchListDialog'");
3809  return false;
3810  }
3811 
3813 
3814  connect(m_searchEdit, SIGNAL(valueChanged()), SLOT(searchChanged()));
3815  connect(m_prevButton, SIGNAL(Clicked()), SLOT(prevClicked()));
3816  connect(m_nextButton, SIGNAL(Clicked()), SLOT(nextClicked()));
3817 
3818  BuildFocusList();
3819 
3820  return true;
3821 }
3822 
3824 {
3825  if (GetFocusWidget() && GetFocusWidget()->keyPressEvent(event))
3826  return true;
3827 
3828  QStringList actions;
3829  bool handled = GetMythMainWindow()->TranslateKeyPress("Global", event, actions, false);
3830 
3831  for (int i = 0; i < actions.size() && !handled; ++i)
3832  {
3833  QString action = actions[i];
3834  handled = true;
3835 
3836  if (action == "0")
3837  {
3839  searchChanged();
3840  }
3841  else
3842  handled = false;
3843  }
3844 
3845  if (!handled && MythScreenType::keyPressEvent(event))
3846  handled = true;
3847 
3848  return handled;
3849 }
3850 
3852 {
3853  bool found = m_parentList->Find(m_searchEdit->GetText(), m_startsWith);
3854 
3855  if (m_searchState)
3856  m_searchState->DisplayState(found ? "found" : "notfound");
3857 }
3858 
3860 {
3861  bool found = m_parentList->FindNext();
3862 
3863  if (m_searchState)
3864  m_searchState->DisplayState(found ? "found" : "notfound");
3865 }
3866 
3868 {
3869  bool found = m_parentList->FindPrev();
3870 
3871  if (m_searchState)
3872  m_searchState->DisplayState(found ? "found" : "notfound");
3873 }
3874 
3876 {
3878  return;
3879 
3880  int maximum = m_itemCount <= static_cast<int>(m_itemsVisible) ?
3881  0 : m_itemCount;
3882  m_scrollBar->SetMaximum(maximum);
3886 }
This widget is used for grouping other widgets for display when a particular named state is called.
ScrollStyle m_scrollStyle
MythUITextEdit * m_searchEdit
int minButtonHeight(const MythRect &area)
void CalculateArea(const MythRect &parentArea)
Definition: mythrect.cpp:32
void setHeight(const QString &sHeight)
Definition: mythrect.cpp:233
int NormX(const int width)
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
QVector< MythUIStateType * > m_ButtonList
void CopyFrom(MythUIType *base) override
Copy this widgets state from another.
MythUIButtonListItem * GetItemNext(MythUIButtonListItem *item) const
MythUIButtonList(MythUIType *parent, const QString &name)
void SetImage(MythImage *img)
Should not be used unless absolutely necessary since it bypasses the image caching and threaded loade...
MythPoint m_searchPosition
CHECKED_STATE
Definition: lcddevice.h:13
bool TranslateKeyPress(const QString &context, QKeyEvent *e, QStringList &actions, bool allowJumps=true)
Get a list of actions for a keypress in the given context.
void LosingFocus()
MythUIButtonList * m_parent
static int parseAlignment(const QString &text)
void CalculateArrowStates(void)
All purpose text widget, displays a text string.
Definition: mythuitext.h:28
QHash< QString, QString > m_actionRemap
MythImage * GetImage(const QString &name="")
Gets a MythImage which has been assigned to this button item, as with SetImage() it should only be us...
void SetData(QVariant data)
void SetRedraw(void)
Definition: mythuitype.cpp:295
bool FindText(const QString &searchStr, const QString &fieldList="**ALL**", bool startsWith=false) const
void DeleteChild(const QString &name)
Delete a named child of this UIType.
Definition: mythuitype.cpp:147
int DecrRef(void) override
Decrements reference count and deletes on 0.
Definition: mythimage.cpp:55
void SetImageFromMap(const InfoMap &imageMap)
Image widget, displays a single image or multiple images in sequence.
Definition: mythuiimage.h:97
uint getLCDHeight(void)
Definition: lcddevice.h:287
void Enabling()
bool Create(void) override
virtual bool MoveDown(MovementUnit unit=MoveItem, uint amount=0)
QString GetImageFilename(const QString &name="") const
void SetActive(bool active)
bool DistributeButtons(void)
virtual void SetText(const QString &text)
Definition: mythuitext.cpp:136
int GetItemPos(MythUIButtonListItem *item) const
MythScreenStack * GetStack(const QString &stackname)
MythUIStateType * m_buttontemplate
void setDrawArrow(bool flag)
unsigned int uint
Definition: compat.h:140
MythUIButton * m_prevButton
Gesture gesture(void) const
Get the gesture type.
Definition: mythgesture.h:107
MythUIButtonListItem * GetItemAt(int pos) const
QMap< QString, TextProperties > m_strings
void ConnectDependants(bool recurse=false)
void SetScrollBarPosition(void)
QStringList m_lcdColumns
void DrawSelf(MythPainter *p, int xoffset, int yoffset, int alphaMod, QRect clipRect) override
virtual void SetToRealButton(MythUIStateType *button, bool selected)
The base class on which all widgets and screens are based.
Definition: mythuitype.h:63
bool Load(bool allowLoadInBackground=true, bool forceStat=false)
Load the image(s), wraps ImageLoader::LoadImage()
void RemoveItem(MythUIButtonListItem *item)
QString GetXMLLocation(void) const
Definition: mythuitype.h:155
void switchToMenu(QList< LCDMenuItem > &menuItems, const QString &app_name="", bool popMenu=true)
Definition: lcddevice.cpp:589
void BuildFocusList(void)
void SetPosition(int x, int y)
Convenience method, calls SetPosition(const MythPoint&) Override that instead to change functionality...
Definition: mythuitype.cpp:519
QMap< QString, MythImage * > m_images
static MythPoint parsePoint(const QString &text, bool normalize=true)
void SetImage(MythImage *image, const QString &name="")
Sets an image directly, should only be used in special circumstances since it bypasses the cache.
unsigned char r
Definition: ParseText.cpp:329
bool MoveToNamedPosition(const QString &position_name)
void setEnabled(bool flag)
bool DistributeCols(int &first_button, int &last_button, int &first_item, int &last_item, int &selected_column, int &selected_row, int &skip_cols, int **col_widths, QList< int > &row_heights, int &top_height, int &bottom_height, bool &wrapped)
void SetButtonArea(const MythRect &rect)
void Reset(void) override
Reset the widget to it's original state, should not reset changes made by the theme.
Definition: mythuigroup.cpp:4
NextButtonListPageEvent(int start, int pageSize)
MythUIScrollBar * m_scrollBar
static LCD * Get(void)
Definition: lcddevice.cpp:65
void setCheckable(bool flag)
virtual bool MoveUp(MovementUnit unit=MoveItem, uint amount=0)
void LoadInBackground(int start=0, int pageSize=20)
MythUIButtonListItem * GetItemByData(const QVariant &data)
virtual void SetVisible(bool visible)
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
void TakingFocus()
void ItemVisible(MythUIButtonListItem *item)
void CopyFrom(MythUIType *base) override
Copy this widgets state from another.
void setX(const QString &sX)
Definition: mythrect.cpp:202
void itemSelected(MythUIButtonListItem *item)
MythUIStateType * m_downArrow
bool MoveToTop(void)
void InsertItem(MythUIButtonListItem *item, int listPosition=-1)
A C++ ripoff of the stroke library for MythTV.
QHash< QString, QString > InfoMap
Definition: mythtypes.h:15
int NormY(const int height)
void itemLoaded(MythUIButtonListItem *item)
MythUIGroup * PrepareButton(int buttonIdx, int itemIdx, int &selectedIdx, int &button_shift)
virtual void SetMinArea(const MythRect &rect)
Set the minimum area based on the given size.
Definition: mythuitype.cpp:798
static bool CopyWindowFromBase(const QString &windowname, MythScreenType *win)
virtual MythRect GetArea(void) const
If the object has a minimum area defined, return it, other wise return the default area.
Definition: mythuitype.cpp:863
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
void DependChanged(bool isDefault)
static QString getFirstText(QDomElement &element)
bool ParseElement(const QString &filename, QDomElement &element, bool showWarnings) override
Parse the xml definition of this widget setting the state of the object accordingly.
int IncrRef(void) override
Increments reference count.
Definition: mythimage.cpp:47
bool DistributeRow(int &first_button, int &last_button, int &first_item, int &last_item, int &selected_column, int &skip_cols, bool grow_left, bool grow_right, int **col_widths, int &row_height, int total_height, int split_height, int &col_cnt, bool &wrapped)
void Reset(void) override
Reset the widget to it's original state, should not reset changes made by the theme.
Definition: mythuitext.cpp:84
Create a group of widgets.
Definition: mythuigroup.h:11
bool m_Initiator
Definition: mythuitype.h:241
void setY(const QString &sY)
Definition: mythrect.cpp:212
Wrapper around QRect allowing us to handle percentage and other relative values for areas in mythui.
Definition: mythrect.h:17
void Reset(void) override
Reset the image back to the default defined in the theme.
void Finalize(void) override
Perform any post-xml parsing initialisation tasks.
QVariant GetDataValue() const
static bool Assign(ContainerType *container, UIType *&item, const QString &name, bool *err=nullptr)
Definition: mythuiutils.h:27
MythRect m_Area
Definition: mythuitype.h:249
void SetText(const QString &text, const QString &name="", const QString &state="")
MythUIType * GetChildAt(const QPoint &p, bool recursive=true, bool focusable=true) const
Return the first MythUIType at the given coordinates.
Definition: mythuitype.cpp:223
MythUIButton * m_nextButton
MythUIType * GetState(const QString &name)
void SetAllChecked(MythUIButtonListItem::CheckState state)
void setWidth(const QString &sWidth)
Definition: mythrect.cpp:222
A custom event that represents a mouse gesture.
Definition: mythgesture.h:39
const char * name
Definition: ParseText.cpp:328
MythUIStateType * m_upArrow
List widget, displays list items in a variety of themeable arrangements and can trigger signals when ...
bool MoveUpDown(bool flag)
MythUIStateType * m_searchState
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
CheckState state() const
void SetText(const QString &text, bool moveCursor=true)
MythUIType * GetFocusWidget(void) const
bool MoveItemUpDown(MythUIButtonListItem *item, bool up)
void Reset() override
Reset the widget to it's original state, should not reset changes made by the theme.
MythMainWindow * GetMythMainWindow(void)
QString GetText(const QString &name="") const
virtual QString GetValue() const
void CreateCopy(MythUIType *parent) override
Copy the state of this widget to the one given, it must be of the same type.
void itemVisible(MythUIButtonListItem *item)
ArrangeType m_arrange
bool keyPressEvent(QKeyEvent *) override
Key event handler.
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
virtual void Reset(void)
Reset the widget to it's original state, should not reset changes made by the theme.
Definition: mythuitype.cpp:69
bool isValid(void) const
Definition: mythrect.h:98
MythUIButtonList * parent() const
void SetLCDTitles(const QString &title, const QString &columnList="")
virtual MythRect GetFullArea(void) const
Definition: mythuitype.cpp:871
void SetFilename(const QString &filename)
Must be followed by a call to Load() to load the image.
MythPoint m_MinSize
Definition: mythuitype.h:251
MythPoint topLeft(void) const
Definition: mythrect.cpp:244
void CalculateButtonPositions(void)
MythRect GetButtonArea(void) const
void SetFontState(const QString &state, const QString &name="")
bool gestureEvent(MythGestureEvent *event) override
Mouse click/movement handler, receives mouse gesture events from the QCoreApplication event loop.
virtual void CopyFrom(MythUIType *base)
Copy this widgets state from another.
void SetDrawFromBottom(bool draw)
QString GetTemplateText(void) const
Definition: mythuitext.h:49
bool IsEnabled(void) const
Definition: mythuitype.h:94
bool DisplayState(const QString &name)
void InitButton(int itemIdx, MythUIStateType *&realButton, MythUIButtonListItem *&buttonItem)
Definition: lcddevice.h:165
MythUIType * m_Parent
Definition: mythuitype.h:270
void Reset(void) override
Reset the widget to it's original state, should not reset changes made by the theme.
void SetItemCurrent(MythUIButtonListItem *item)
static MythRect parseRect(const QString &text, bool normalize=true)
virtual QPoint GetButtonPosition(int column, int row) const
MythUIButtonListItem(MythUIButtonList *lbtype, const QString &text, const QString &image="", bool checkable=false, CheckState state=CantCheck, bool showArrow=false, int listPosition=-1)
bool m_EnableInitiator
Definition: mythuitype.h:240
void customEvent(QEvent *) override
void itemClicked(MythUIButtonListItem *item)
MythUIButtonListItem * GetItemFirst() const
int GetCurrentPos() const
void SetFontState(const QString &)
Definition: mythuitext.cpp:225
void Disabling()
virtual int GetIntValue() const
bool DoFind(bool doMove, bool searchForward)
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Definition: mythuitype.cpp:132
void DisplayState(const QString &state, const QString &name)
void SetTextFromMap(const InfoMap &infoMap, const QString &state="")
QList< MythUIButtonListItem * > m_itemList
static bool parseBool(const QString &text)
void SetCanTakeFocus(bool set=true)
Set whether this widget can take focus.
Definition: mythuitype.cpp:344
MythUIType * GetCurrentState()
void FindEnabledDown(MovementUnit unit)
If the current item is not enabled, find the next enabled one.
void FindEnabledUp(MovementUnit unit)
int minButtonWidth(const MythRect &area)
virtual bool ParseElement(const QString &filename, QDomElement &element, bool showWarnings)
Parse the xml definition of this widget setting the state of the object accordingly.
void setChecked(CheckState state)
QMap< int, MythUIButtonListItem * > m_ButtonToItem
bool Find(const QString &searchStr, bool startsWith=false)
QString GetText(void) const
MythUIButtonList * m_parentList
void SetSliderPosition(int)
virtual void Finalize(void)
Perform any post-xml parsing initialisation tasks.
void SetValueByData(const QVariant &data)
MythUIButtonListItem * GetItemCurrent() const
bool m_HasFocus
Definition: mythuitype.h:237
QString GetKey(const QString &context, const QString &action) const
void SetStatesFromMap(const InfoMap &stateMap)
virtual void CalculateVisibleItems(void)