8 #include <QCoreApplication>
9 #include <QDomDocument>
11 #include <QRegularExpression>
29 #define LOC QString("MythUIButtonList(%1): ").arg(objectName())
42 const QRect area,
bool showArrow,
45 m_showArrow(showArrow), m_showScrollBar(showScrollBar)
156 int width = area.width();
164 width += (area.x() * 2 - 1);
186 int height = area.height();
194 height += (area.y() * 2 - 1);
217 buttonIdx += button_shift;
221 QString name = QString(
"buttonlist button %1").arg(
m_maxVisible);
224 button->ConnectDependants(
true);
236 if (selectedIdx >= 0)
254 selectedIdx = buttonIdx;
263 int &first_item,
int &last_item,
264 int &selected_column,
int &skip_cols,
265 bool grow_left,
bool grow_right,
266 int **col_widths,
int &row_height,
267 int total_height,
int split_height,
268 int &col_cnt,
bool &wrapped)
275 bool underflow =
false;
277 int selectedIdx = -1;
278 int button_shift = 0;
282 if (last_item + 1 >
m_itemCount || last_item < 0 || first_item < 0)
292 selectedIdx, button_shift);
297 selectedIdx, button_shift);
300 if (buttonstate ==
nullptr)
302 LOG(VB_GENERAL, LOG_ERR, QString(
"Failed to query buttonlist state: %1")
318 bool hsplit = vsplit && grow_left && grow_right;
323 left_width = right_width = (width / 2);
347 if (total_height > 0 &&
348 ((vsplit ? split_height : total_height) +
351 LOG(VB_GUI, LOG_DEBUG,
352 QString(
"%1 Height exceeded %2 + (%3) + %4 = %5 which is > %6")
353 .arg(vsplit ?
"Centering" :
"Total")
357 first_button += button_shift;
358 last_button += button_shift;
362 LOG(VB_GUI, LOG_DEBUG, QString(
"Added button item %1 width %2 height %3")
363 .arg(grow_right ? last_item : first_item)
364 .arg(width).arg(row_height));
366 int initial_first_button = first_button;
367 int initial_last_button = last_button;
368 int initial_first_item = first_item;
369 int initial_last_item = last_item;
409 if (last_item + 1 < end)
413 selectedIdx, button_shift);
415 if (buttonstate ==
nullptr)
421 if (*col_widths && width < (*col_widths)[col_idx])
422 width = (*col_widths)[col_idx];
425 if ((hsplit ? right_width : left_width + right_width) +
428 int total = hsplit ? right_width : left_width + right_width;
429 LOG(VB_GUI, LOG_DEBUG,
430 QString(
"button on right would exceed width: "
431 "%1+(%2)+%3 == %4 which is > %5")
446 row_height = std::max(row_height, height);
448 LOG(VB_GUI, LOG_DEBUG,
449 QString(
"Added button item %1 "
450 "R.width %2 height %3 total width %4+%5"
452 .arg(last_item).arg(width).arg(height)
453 .arg(left_width).arg(right_width).arg(max_width));
484 if (first_item > end)
486 buttonstate =
PrepareButton(first_button - 1, first_item - 1,
487 selectedIdx, button_shift);
489 if (buttonstate ==
nullptr)
495 if (*col_widths && width < (*col_widths)[col_idx])
496 width = (*col_widths)[col_idx];
499 if ((hsplit ? left_width : left_width + right_width) +
502 int total = hsplit ? left_width : left_width + right_width;
503 LOG(VB_GUI, LOG_DEBUG,
504 QString(
"button on left would exceed width: "
505 "%1+(%2)+%3 == %4 which is > %5")
520 row_height = std::max(row_height, height);
522 LOG(VB_GUI, LOG_DEBUG,
523 QString(
"Added button item %1 "
524 "L.width %2 height %3 total width %4+%5"
526 .arg(first_item).arg(width).arg(height)
527 .arg(left_width).arg(right_width).arg(max_width));
544 if (total_height > 0 &&
545 ((vsplit ? split_height : total_height) +
548 LOG(VB_GUI, LOG_DEBUG,
549 QString(
"%1 Height exceeded %2 + (%3) + %4 = %5 which is > %6")
550 .arg(vsplit ?
"Centering" :
"Total")
554 first_button = initial_first_button + button_shift;
555 last_button = initial_last_button + button_shift;
556 first_item = initial_first_item;
557 last_item = initial_last_item;
561 if (*col_widths ==
nullptr)
567 *col_widths =
new int[
static_cast<size_t>(col_cnt)];
569 for (col_idx = 0; col_idx < col_cnt; ++col_idx)
570 (*col_widths)[col_idx] = 0;
575 first_button += button_shift;
576 last_button += button_shift;
584 begin = first_button;
585 end = first_button + col_cnt;
589 end = last_button + 1;
590 begin = end - col_cnt;
593 for (buttonIdx = begin, col_idx = 0;
594 buttonIdx < end; ++buttonIdx, ++col_idx)
603 (*col_widths)[col_idx] = std::max((*col_widths)[col_idx], width);
606 if (selectedIdx == buttonIdx)
607 selected_column = col_idx;
614 if (total_height && underflow && col_cnt <
m_columns)
624 int &first_item,
int &last_item,
625 int &selected_column,
int &selected_row,
626 int &skip_cols,
int **col_widths,
627 QList<int> & row_heights,
628 int &top_height,
int &bottom_height,
660 if (last_item + 1 < end)
664 first_item, ++last_item, selected_column,
665 skip_cols,
false,
true, col_widths, height,
666 top_height + bottom_height, bottom_height,
672 if (selected_row == -1 && selected_column != -1)
673 selected_row = row_heights.size();
675 row_heights.push_back(height);
706 if (first_item > end)
710 --first_item, last_item, selected_column,
711 skip_cols,
true,
false, col_widths, height,
712 top_height + bottom_height, top_height,
718 if (selected_row == -1 && selected_column != -1)
719 selected_row = row_heights.size();
720 else if (selected_row != -1)
723 row_heights.push_front(height);
744 int first_button = 0;
746 int start_button = 0;
751 int *col_widths =
nullptr;
753 int selected_column = -1;
754 int selected_row = -1;
755 bool wrapped =
false;
756 bool grow_left =
true;
759 int bottom_height = 0;
761 QList<int> row_heights;
763 LOG(VB_GUI, LOG_DEBUG, QString(
"DistributeButtons: "
764 "selected item %1 total items %2")
785 first_item = last_item = start_item;
803 first_item = last_item = 0;
832 first_button = last_button = start_button;
837 first_item, last_item, selected_column,
838 skip_cols, grow_left,
true, &col_widths,
839 height, 0, 0, col_cnt, wrapped))
854 first_item = last_item = start_item;
860 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
871 first_button = last_button = start_button;
874 selected_column = selected_row = -1;
877 first_item, last_item, selected_column,
878 skip_cols, grow_left,
true, &col_widths,
879 height, 0, 0, col_cnt, wrapped))
886 if (selected_column != -1)
889 row_heights.push_back(height);
892 top_height = bottom_height = (height / 2);
894 bottom_height = height;
901 first_item, last_item,
902 selected_column, selected_row,
903 skip_cols, &col_widths, row_heights,
904 top_height, bottom_height, wrapped))
908 col_widths =
nullptr;
914 m_rows = row_heights.size();
916 LOG(VB_GUI, LOG_DEBUG,
917 QString(
"%1 rows, %2 columns fit inside parent area %3x%4")
921 if (col_widths ==
nullptr)
925 int left_spacing = 0;
926 int right_spacing = 0;
928 int bottom_spacing = 0;
937 status_msg =
"Row heights: ";
939 for (
int row = 0; row <
m_rows; ++row)
944 if (row == selected_row)
947 top_height += (row_heights[row] / 2);
948 bottom_height += ((row_heights[row] / 2) + (row_heights[row] % 2));
964 status_msg += QString(
"%1").arg(row_heights[row]);
966 if (row == selected_row)
976 top_spacing = bottom_spacing = 0;
983 top_spacing = bottom_spacing =
999 if (!
m_topRows || top_spacing > bottom_spacing)
1000 top_spacing = bottom_spacing;
1002 bottom_spacing = top_spacing;
1009 (top_height + bottom_height)) /
1015 top_height += (top_spacing *
m_topRows);
1029 y += std::max(bottom_height - top_height, 0);
1030 total = std::max(top_height, bottom_height) * 2;
1034 total = top_height + bottom_height;
1044 (top_height + bottom_height), 0);
1048 status_msg += QString(
" spacing top %1 bottom %2 fixed %3 offset %4")
1049 .arg(top_spacing).arg(bottom_spacing)
1052 LOG(VB_GUI, LOG_DEBUG, status_msg);
1058 int right_width = 0;
1061 status_msg =
"Col widths: ";
1063 for (
int col = 0; col <
m_columns; ++col)
1068 if (col == selected_column)
1071 left_width += (col_widths[col] / 2);
1072 right_width += ((col_widths[col] / 2) + (col_widths[col] % 2));
1088 status_msg += QString(
"%1").arg(col_widths[col]);
1090 if (col == selected_column)
1100 left_spacing = right_spacing = 0;
1107 left_spacing = right_spacing =
1124 left_spacing = right_spacing;
1126 right_spacing = left_spacing;
1133 (left_width + right_width)) /
1153 x_init += std::max(right_width - left_width, 0);
1154 total = std::max(left_width, right_width) * 2;
1158 total = left_width + right_width;
1168 (left_width + right_width), 0);
1170 min_rect.
setX(x_init);
1172 status_msg += QString(
" spacing left %1 right %2 fixed %3 offset %4")
1173 .arg(left_spacing).arg(right_spacing)
1175 LOG(VB_GUI, LOG_DEBUG, status_msg);
1183 int buttonIdx = first_button - skip_cols;
1188 int vertical_spacing = top_spacing;
1190 for (
int row = 0; row <
m_rows; ++row)
1193 int horizontal_spacing = left_spacing;
1195 for (
int col = 0; col <
m_columns && buttonIdx <= last_button; ++col)
1197 if (buttonIdx >= first_button)
1205 MythRect area = buttonstate->GetArea();
1231 if (col == selected_column)
1233 horizontal_spacing = right_spacing;
1234 if (row == selected_row)
1238 x += col_widths[col] + horizontal_spacing;
1242 if (row == selected_row)
1243 vertical_spacing = bottom_spacing;
1245 y += row_heights[row] + vertical_spacing;
1247 min_rect.
setWidth(x - min_rect.x());
1253 for (buttonIdx = 0; buttonIdx < first_button; ++buttonIdx)
1257 for (buttonIdx =
m_maxVisible - 1; buttonIdx > last_button; --buttonIdx)
1273 delete[] col_widths;
1329 QList<MythUIButtonListItem *>::iterator it =
m_itemList.begin() +
1355 for (
int i = 0; i < button; ++i)
1358 bool seenSelected =
false;
1373 if (!realButton || !buttonItem)
1376 bool selected =
false;
1380 seenSelected =
true;
1429 QMap<int, MythUIButtonListItem*>::const_iterator i =
m_buttonToItem.constBegin();
1433 i.value()->setVisible(
false);
1485 if (listPosition >= 0 && listPosition <=
m_itemList.count())
1522 QMap<int, MythUIButtonListItem*>::iterator it =
m_buttonToItem.begin();
1525 if (it.value() == item)
1573 if (item->GetData() == data)
1628 return item->
GetText().toInt();
1671 QListIterator<MythUIButtonListItem *> it(
m_itemList);
1673 if (!it.findNext(item))
1676 return it.previous();
1715 if (item->GetData() == data)
1737 QString name(
"buttonlist button 0");
1740 button->ConnectDependants(
true);
1780 for (; pos >= 0; --pos)
1789 if (buttonstate ==
nullptr)
1791 LOG(VB_GENERAL, LOG_ERR,
1792 "PageUp: Failed to query buttonlist state");
1797 buttonstate->GetArea().width() / 2 >= max_width)
1833 for (; pos >= 0; pos -= dec)
1842 if (buttonstate ==
nullptr)
1844 LOG(VB_GENERAL, LOG_ERR,
1845 "PageUp: Failed to query buttonlist state");
1850 buttonstate->GetArea().height() / 2 >= max_height)
1886 for (; pos < num_items; ++pos)
1895 if (buttonstate ==
nullptr)
1897 LOG(VB_GENERAL, LOG_ERR,
1898 "PageDown: Failed to query buttonlist state");
1903 buttonstate->GetArea().width() / 2 >= max_width)
1913 return num_items - 1;
1939 for (; pos < num_items; pos += inc)
1950 LOG(VB_GENERAL, LOG_ERR,
1951 "PageDown: Failed to query buttonlist state");
1956 buttonstate->GetArea().height() / 2 >= max_height)
1966 return num_items - 1;
2070 for (
uint i = 0; i < amount; ++i)
2104 if (m_selPosition < 0 || m_selPosition >=
m_itemList.size() ||
2149 if (m_selPosition < 0 || m_selPosition >=
m_itemList.size() ||
2288 for (
uint i = 0; i < amount; ++i)
2326 bool found_it =
false;
2327 int selectedPosition = 0;
2328 QList<MythUIButtonListItem *>::iterator it =
m_itemList.begin();
2332 if ((*it)->GetText() == position_name)
2362 bool dolast =
false;
2401 QMutableListIterator<MythUIButtonListItem *> it(
m_itemList);
2403 while (it.hasNext())
2404 it.next()->setChecked(state);
2431 LOG(VB_GENERAL, LOG_ERR, QString(
"(%1) Statetype buttonitem is "
2432 "required in mythuibuttonlist: %2")
2444 if (buttonActiveState)
2445 buttonItemArea = buttonActiveState->
GetArea();
2468 QString name = QString(
"buttonlist button %1").arg(i);
2471 button->ConnectDependants(
true);
2494 if (buttonSelectedState)
2530 QStringList actions;
2531 bool handled =
false;
2535 for (
const QString&
action : std::as_const(actions))
2544 QKeySequence a(key);
2548 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
2550 Qt::KeyboardModifiers modifiers = Qt::NoModifier;
2551 QStringList parts = key.split(
'+');
2552 for (
int j = 0; j < parts.count(); ++j)
2554 if (parts[j].toUpper() ==
"CTRL")
2555 modifiers |= Qt::ControlModifier;
2556 if (parts[j].toUpper() ==
"SHIFT")
2557 modifiers |= Qt::ShiftModifier;
2558 if (parts[j].toUpper() ==
"ALT")
2559 modifiers |= Qt::AltModifier;
2560 if (parts[j].toUpper() ==
"META")
2561 modifiers |= Qt::MetaModifier;
2564 int keyCode = a[0].key();
2565 Qt::KeyboardModifiers modifiers = a[0].keyboardModifiers();
2568 QCoreApplication::postEvent(
2570 new QKeyEvent(QEvent::KeyPress, keyCode, modifiers, key));
2571 QCoreApplication::postEvent(
2573 new QKeyEvent(QEvent::KeyRelease, keyCode, modifiers, key));
2579 for (
int i = 0; i < actions.size() && !handled; ++i)
2581 const QString&
action = actions[i];
2591 else if (
action ==
"DOWN")
2598 else if (
action ==
"RIGHT")
2614 else if (
action ==
"LEFT")
2630 else if (
action ==
"PAGEUP")
2634 else if (
action ==
"PAGEDOWN")
2638 else if (
action ==
"PAGETOP")
2642 else if (
action ==
"PAGEMIDDLE")
2646 else if (
action ==
"PAGEBOTTOM")
2650 else if (
action ==
"SELECT")
2657 else if (
action ==
"SEARCH")
2675 bool handled =
false;
2682 QPoint position =
event->GetPosition() -
2694 QString name =
object->objectName();
2696 if (name ==
"upscrollarrow")
2700 else if (name ==
"downscrollarrow")
2704 else if (name.startsWith(
"buttonlist button"))
2706 int pos = name.section(
' ', 2, 2).toInt();
2781 (QEvent::Type) QEvent::registerEventType();
2789 int cur = npe->m_start;
2790 for (; cur < npe->m_start + npe->m_pageSize && cur <
GetCount(); ++cur)
2792 const int loginterval = (cur < 1000 ? 100 : 500);
2793 if (cur > 200 && cur % loginterval == 0)
2794 LOG(VB_GUI, LOG_INFO,
2795 QString(
"Build background buttonlist item %1").arg(cur));
2885 const QString &
filename, QDomElement &element,
bool showWarnings)
2887 if (element.tagName() ==
"buttonarea")
2889 else if (element.tagName() ==
"layout")
2893 if (layout ==
"grid")
2895 else if (layout ==
"horizontal")
2900 else if (element.tagName() ==
"arrange")
2904 if (arrange ==
"fill")
2906 else if (arrange ==
"spread")
2908 else if (arrange ==
"stack")
2914 else if (element.tagName() ==
"align")
2919 else if (element.tagName() ==
"scrollstyle")
2923 if (layout ==
"center")
2925 else if (layout ==
"groupcenter")
2927 else if (layout ==
"free")
2930 else if (element.tagName() ==
"wrapstyle")
2934 if (wrapstyle ==
"captive")
2936 else if (wrapstyle ==
"none")
2938 else if (wrapstyle ==
"selection")
2940 else if (wrapstyle ==
"flowing")
2942 else if (wrapstyle ==
"items")
2945 else if (element.tagName() ==
"showarrow")
2949 else if (element.tagName() ==
"showscrollbar")
2953 else if (element.tagName() ==
"spacing")
2958 else if (element.tagName() ==
"drawfrombottom")
2965 else if (element.tagName() ==
"searchposition")
2969 else if (element.tagName() ==
"triggerevent")
2972 if (!trigger.isEmpty())
2974 QString
action = element.attribute(
"action",
"");
2981 QString context = element.attribute(
"context",
"");
2983 QStringList keys = keylist.split(
',', Qt::SkipEmptyParts);
3064 QString name = QString(
"buttonlist button %1").arg(i);
3096 if (lcddev ==
nullptr)
3100 QList<LCDMenuItem> menuItems;
3105 for (
int r = start; r < end; ++r)
3127 text +=
" ~ " + props.
text;
3135 text +=
" ~ " + item->
GetText();
3139 if (!text.isEmpty())
3140 menuItems.append(
LCDMenuItem(selected, state, text));
3145 if (!menuItems.isEmpty())
3162 QRect dialogArea = dlg->GetArea();
3165 x = (screenArea.width() - dialogArea.width()) / 2;
3168 y = (screenArea.height() - dialogArea.height()) / 2;
3170 dlg->SetPosition(x, y);
3185 return DoFind(
false,
true);
3190 return DoFind(
true,
true);
3195 return DoFind(
true,
false);
3207 int currPos = startPos;
3253 if (startPos == currPos)
3263 QString text, QString image,
3265 bool showArrow,
int listPosition)
3266 : m_parent(lbtype), m_text(std::move(text)), m_imageFilename(std::move(image)),
3267 m_checkable(checkable), m_state(state), m_showArrow(showArrow)
3270 LOG(VB_GENERAL, LOG_ERR,
"Cannot add a button to a non-existent list!");
3280 const QString &text,
3281 QVariant data,
int listPosition)
3284 LOG(VB_GENERAL, LOG_ERR,
"Cannot add a button to a non-existent list!");
3288 m_data = std::move(data);
3310 QMap<QString, MythImage*>::iterator it;
3320 const QString &state)
3322 if (!name.isEmpty())
3325 textprop.
text = text;
3339 const QString &state)
3341 InfoMap::const_iterator map_it = infoMap.begin();
3343 while (map_it != infoMap.end())
3346 textprop.
text = (*map_it);
3375 if (!result.isEmpty())
3390 if (!result.isEmpty())
3391 return {result,
""};
3399 bool startsWith)
const
3401 if (fieldList.isEmpty())
3404 return m_text.startsWith(searchStr, Qt::CaseInsensitive);
3405 return m_text.contains(searchStr, Qt::CaseInsensitive);
3407 if (fieldList ==
"**ALL**")
3411 if (
m_text.startsWith(searchStr, Qt::CaseInsensitive))
3416 if (
m_text.contains(searchStr, Qt::CaseInsensitive))
3420 QMap<QString, TextProperties>::const_iterator i =
m_strings.constBegin();
3426 if (i.value().text.startsWith(searchStr, Qt::CaseInsensitive))
3431 if (i.value().text.contains(searchStr, Qt::CaseInsensitive))
3440 QStringList fields = fieldList.split(
',', Qt::SkipEmptyParts);
3441 for (
int x = 0; x < fields.count(); ++x)
3443 if (
m_strings.contains(fields.at(x).trimmed()))
3447 if (
m_strings[fields.at(x)].text.startsWith(searchStr, Qt::CaseInsensitive))
3452 if (
m_strings[fields.at(x)].text.contains(searchStr, Qt::CaseInsensitive))
3463 const QString &name)
3465 if (!name.isEmpty())
3484 if (!name.isEmpty())
3486 QMap<QString, MythImage*>::iterator it =
m_images.find(name);
3525 if (!name.isEmpty())
3527 QMap<QString, MythImage*>::iterator it =
m_images.find(name);
3544 const QString &
filename,
const QString &name,
bool force_reload)
3546 bool do_update = force_reload;
3548 if (!name.isEmpty())
3581 if (!result.isEmpty())
3614 const QString &name)
3619 bool do_update =
false;
3620 InfoMap::iterator it =
m_states.find(name);
3627 else if (*it !=
state)
3656 if (!result.isEmpty())
3712 m_data = std::move(data);
3744 buttonimage->
Load();
3779 if (!buttonprogress)
3787 if (!buttonprogress)
3801 static const QRegularExpression re {R
"(%(([^\|%]+)?\||\|(.))?([\w#]+)(\|(.+?))?%)",
3802 QRegularExpression::DotMatchesEverythingOption};
3804 if (!newText.isEmpty() && newText.contains(re))
3806 QString tempString = newText;
3808 QRegularExpressionMatchIterator i = re.globalMatch(newText);
3809 while (i.hasNext()) {
3810 QRegularExpressionMatch match = i.next();
3811 QString key = match.captured(4).toLower().trimmed();
3812 QString replacement;
3815 if (!value.isEmpty())
3817 replacement = QString(
"%1%2%3%4")
3818 .arg(match.captured(2),
3824 tempString.replace(match.captured(0), replacement);
3827 newText = tempString;
3831 newText = textprop.
text;
3834 if (newText.isEmpty())
3908 LOG(VB_GUI, LOG_WARNING,
"Theme Error: Missing buttonlist state: disabled");
3914 LOG(VB_GUI, LOG_WARNING,
"Theme Error: Missing buttonlist state: inactive");
3922 LOG(VB_GENERAL, LOG_CRIT, QString(
"Theme Error: Missing buttonlist state: %1")
3927 buttonstate->Reset();
3929 QList<MythUIType *> descendants = buttonstate->GetAllDescendants();
3930 for (
MythUIType *obj : std::as_const(descendants))
3932 QString name = obj->objectName();
3933 if (name ==
"buttontext")
3935 else if (name ==
"buttonimage")
3937 else if (name ==
"buttonarrow")
3939 else if (name ==
"buttoncheck")
3941 else if (name ==
"buttonprogress1")
3943 else if (name ==
"buttonprogress2")
3947 if (!textprop.
text.isEmpty())
3958 if (!luState.isEmpty())
3983 LOG(VB_GENERAL, LOG_ERR,
"Cannot load screen 'MythSearchListDialog'");
4003 QStringList actions;
4006 for (
int i = 0; i < actions.size() && !handled; ++i)
4008 const QString&
action = actions[i];