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);
184 int height = area.height();
192 height += (area.y() * 2 - 1);
213 buttonIdx += button_shift;
217 QString name = QString(
"buttonlist button %1").arg(
m_maxVisible);
220 button->ConnectDependants(
true);
232 if (selectedIdx >= 0)
248 selectedIdx = buttonIdx;
257 int &first_item,
int &last_item,
258 int &selected_column,
int &skip_cols,
259 bool grow_left,
bool grow_right,
260 int **col_widths,
int &row_height,
261 int total_height,
int split_height,
262 int &col_cnt,
bool &wrapped)
269 bool underflow =
false;
271 int selectedIdx = -1;
272 int button_shift = 0;
276 if (last_item + 1 >
m_itemCount || last_item < 0 || first_item < 0)
286 selectedIdx, button_shift);
291 selectedIdx, button_shift);
294 if (buttonstate ==
nullptr)
296 LOG(VB_GENERAL, LOG_ERR, QString(
"Failed to query buttonlist state: %1")
312 bool hsplit = vsplit && grow_left && grow_right;
317 left_width = right_width = (width / 2);
341 if (total_height > 0 &&
342 ((vsplit ? split_height : total_height) +
345 LOG(VB_GUI, LOG_DEBUG,
346 QString(
"%1 Height exceeded %2 + (%3) + %4 = %5 which is > %6")
347 .arg(vsplit ?
"Centering" :
"Total")
351 first_button += button_shift;
352 last_button += button_shift;
356 LOG(VB_GUI, LOG_DEBUG, QString(
"Added button item %1 width %2 height %3")
357 .arg(grow_right ? last_item : first_item)
358 .arg(width).arg(row_height));
360 int initial_first_button = first_button;
361 int initial_last_button = last_button;
362 int initial_first_item = first_item;
363 int initial_last_item = last_item;
401 if (last_item + 1 < end)
405 selectedIdx, button_shift);
407 if (buttonstate ==
nullptr)
413 if (*col_widths && width < (*col_widths)[col_idx])
414 width = (*col_widths)[col_idx];
417 if ((hsplit ? right_width : left_width + right_width) +
420 int total = hsplit ? right_width : left_width + right_width;
421 LOG(VB_GUI, LOG_DEBUG,
422 QString(
"button on right would exceed width: "
423 "%1+(%2)+%3 == %4 which is > %5")
438 if (row_height < height)
441 LOG(VB_GUI, LOG_DEBUG,
442 QString(
"Added button item %1 "
443 "R.width %2 height %3 total width %4+%5"
445 .arg(last_item).arg(width).arg(height)
446 .arg(left_width).arg(right_width).arg(max_width));
473 if (first_item > end)
475 buttonstate =
PrepareButton(first_button - 1, first_item - 1,
476 selectedIdx, button_shift);
478 if (buttonstate ==
nullptr)
484 if (*col_widths && width < (*col_widths)[col_idx])
485 width = (*col_widths)[col_idx];
488 if ((hsplit ? left_width : left_width + right_width) +
491 int total = hsplit ? left_width : left_width + right_width;
492 LOG(VB_GUI, LOG_DEBUG,
493 QString(
"button on left would exceed width: "
494 "%1+(%2)+%3 == %4 which is > %5")
509 if (row_height < height)
512 LOG(VB_GUI, LOG_DEBUG,
513 QString(
"Added button item %1 "
514 "L.width %2 height %3 total width %4+%5"
516 .arg(first_item).arg(width).arg(height)
517 .arg(left_width).arg(right_width).arg(max_width));
534 if (total_height > 0 &&
535 ((vsplit ? split_height : total_height) +
538 LOG(VB_GUI, LOG_DEBUG,
539 QString(
"%1 Height exceeded %2 + (%3) + %4 = %5 which is > %6")
540 .arg(vsplit ?
"Centering" :
"Total")
544 first_button = initial_first_button + button_shift;
545 last_button = initial_last_button + button_shift;
546 first_item = initial_first_item;
547 last_item = initial_last_item;
551 if (*col_widths ==
nullptr)
557 *col_widths =
new int[
static_cast<size_t>(col_cnt)];
559 for (col_idx = 0; col_idx < col_cnt; ++col_idx)
560 (*col_widths)[col_idx] = 0;
565 first_button += button_shift;
566 last_button += button_shift;
574 begin = first_button;
575 end = first_button + col_cnt;
579 end = last_button + 1;
580 begin = end - col_cnt;
583 for (buttonIdx = begin, col_idx = 0;
584 buttonIdx < end; ++buttonIdx, ++col_idx)
593 if ((*col_widths)[col_idx] < width)
594 (*col_widths)[col_idx] = width;
597 if (selectedIdx == buttonIdx)
598 selected_column = col_idx;
605 if (total_height && underflow && col_cnt <
m_columns)
615 int &first_item,
int &last_item,
616 int &selected_column,
int &selected_row,
617 int &skip_cols,
int **col_widths,
618 QList<int> & row_heights,
619 int &top_height,
int &bottom_height,
649 if (last_item + 1 < end)
653 first_item, ++last_item, selected_column,
654 skip_cols,
false,
true, col_widths, height,
655 top_height + bottom_height, bottom_height,
661 if (selected_row == -1 && selected_column != -1)
662 selected_row = row_heights.size();
664 row_heights.push_back(height);
693 if (first_item > end)
697 --first_item, last_item, selected_column,
698 skip_cols,
true,
false, col_widths, height,
699 top_height + bottom_height, top_height,
705 if (selected_row == -1 && selected_column != -1)
706 selected_row = row_heights.size();
707 else if (selected_row != -1)
710 row_heights.push_front(height);
731 int first_button = 0;
733 int start_button = 0;
738 int *col_widths =
nullptr;
740 int selected_column = -1;
741 int selected_row = -1;
742 bool wrapped =
false;
743 bool grow_left =
true;
746 int bottom_height = 0;
748 QList<int> row_heights;
750 LOG(VB_GUI, LOG_DEBUG, QString(
"DistributeButtons: "
751 "selected item %1 total items %2")
772 first_item = last_item = start_item;
790 first_item = last_item = 0;
817 first_button = last_button = start_button;
822 first_item, last_item, selected_column,
823 skip_cols, grow_left,
true, &col_widths,
824 height, 0, 0, col_cnt, wrapped))
839 first_item = last_item = start_item;
845 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
856 first_button = last_button = start_button;
859 selected_column = selected_row = -1;
862 first_item, last_item, selected_column,
863 skip_cols, grow_left,
true, &col_widths,
864 height, 0, 0, col_cnt, wrapped))
871 if (selected_column != -1)
874 row_heights.push_back(height);
877 top_height = bottom_height = (height / 2);
879 bottom_height = height;
886 first_item, last_item,
887 selected_column, selected_row,
888 skip_cols, &col_widths, row_heights,
889 top_height, bottom_height, wrapped))
893 col_widths =
nullptr;
899 m_rows = row_heights.size();
901 LOG(VB_GUI, LOG_DEBUG,
902 QString(
"%1 rows, %2 columns fit inside parent area %3x%4")
906 if (col_widths ==
nullptr)
910 int left_spacing = 0;
911 int right_spacing = 0;
913 int bottom_spacing = 0;
922 status_msg =
"Row heights: ";
924 for (
int row = 0; row <
m_rows; ++row)
929 if (row == selected_row)
932 top_height += (row_heights[row] / 2);
933 bottom_height += ((row_heights[row] / 2) + (row_heights[row] % 2));
949 status_msg += QString(
"%1").arg(row_heights[row]);
951 if (row == selected_row)
961 top_spacing = bottom_spacing = 0;
968 top_spacing = bottom_spacing =
984 if (!
m_topRows || top_spacing > bottom_spacing)
985 top_spacing = bottom_spacing;
987 bottom_spacing = top_spacing;
994 (top_height + bottom_height)) /
1000 top_height += (top_spacing *
m_topRows);
1014 y += std::max(bottom_height - top_height, 0);
1015 total = std::max(top_height, bottom_height) * 2;
1018 total = top_height + bottom_height;
1027 (top_height + bottom_height), 0);
1031 status_msg += QString(
" spacing top %1 bottom %2 fixed %3 offset %4")
1032 .arg(top_spacing).arg(bottom_spacing)
1035 LOG(VB_GUI, LOG_DEBUG, status_msg);
1041 int right_width = 0;
1044 status_msg =
"Col widths: ";
1046 for (
int col = 0; col <
m_columns; ++col)
1051 if (col == selected_column)
1054 left_width += (col_widths[col] / 2);
1055 right_width += ((col_widths[col] / 2) + (col_widths[col] % 2));
1071 status_msg += QString(
"%1").arg(col_widths[col]);
1073 if (col == selected_column)
1083 left_spacing = right_spacing = 0;
1090 left_spacing = right_spacing =
1107 left_spacing = right_spacing;
1109 right_spacing = left_spacing;
1116 (left_width + right_width)) /
1136 x_init += std::max(right_width - left_width, 0);
1137 total = std::max(left_width, right_width) * 2;
1140 total = left_width + right_width;
1149 (left_width + right_width), 0);
1151 min_rect.
setX(x_init);
1153 status_msg += QString(
" spacing left %1 right %2 fixed %3 offset %4")
1154 .arg(left_spacing).arg(right_spacing)
1156 LOG(VB_GUI, LOG_DEBUG, status_msg);
1164 int buttonIdx = first_button - skip_cols;
1169 int vertical_spacing = top_spacing;
1171 for (
int row = 0; row <
m_rows; ++row)
1174 int horizontal_spacing = left_spacing;
1176 for (
int col = 0; col <
m_columns && buttonIdx <= last_button; ++col)
1178 if (buttonIdx >= first_button)
1186 MythRect area = buttonstate->GetArea();
1212 if (col == selected_column)
1214 horizontal_spacing = right_spacing;
1215 if (row == selected_row)
1219 x += col_widths[col] + horizontal_spacing;
1223 if (row == selected_row)
1224 vertical_spacing = bottom_spacing;
1226 y += row_heights[row] + vertical_spacing;
1228 min_rect.
setWidth(x - min_rect.x());
1234 for (buttonIdx = 0; buttonIdx < first_button; ++buttonIdx)
1238 for (buttonIdx =
m_maxVisible - 1; buttonIdx > last_button; --buttonIdx)
1254 delete[] col_widths;
1310 QList<MythUIButtonListItem *>::iterator it =
m_itemList.begin() +
1334 for (
int i = 0; i < button; ++i)
1337 bool seenSelected =
false;
1352 if (!realButton || !buttonItem)
1355 bool selected =
false;
1359 seenSelected =
true;
1408 QMap<int, MythUIButtonListItem*>::const_iterator i =
m_buttonToItem.constBegin();
1412 i.value()->setVisible(
false);
1464 if (listPosition >= 0 && listPosition <=
m_itemList.count())
1499 QMap<int, MythUIButtonListItem*>::iterator it =
m_buttonToItem.begin();
1502 if (it.value() == item)
1550 if (item->GetData() == data)
1605 return item->
GetText().toInt();
1648 QListIterator<MythUIButtonListItem *> it(
m_itemList);
1650 if (!it.findNext(item))
1653 return it.previous();
1692 if (item->GetData() == data)
1714 QString name(
"buttonlist button 0");
1717 button->ConnectDependants(
true);
1757 for (; pos >= 0; --pos)
1766 if (buttonstate ==
nullptr)
1768 LOG(VB_GENERAL, LOG_ERR,
1769 "PageUp: Failed to query buttonlist state");
1774 buttonstate->GetArea().width() / 2 >= max_width)
1810 for (; pos >= 0; pos -= dec)
1819 if (buttonstate ==
nullptr)
1821 LOG(VB_GENERAL, LOG_ERR,
1822 "PageUp: Failed to query buttonlist state");
1827 buttonstate->GetArea().height() / 2 >= max_height)
1863 for (; pos < num_items; ++pos)
1872 if (buttonstate ==
nullptr)
1874 LOG(VB_GENERAL, LOG_ERR,
1875 "PageDown: Failed to query buttonlist state");
1880 buttonstate->GetArea().width() / 2 >= max_width)
1890 return num_items - 1;
1916 for (; pos < num_items; pos += inc)
1927 LOG(VB_GENERAL, LOG_ERR,
1928 "PageDown: Failed to query buttonlist state");
1933 buttonstate->GetArea().height() / 2 >= max_height)
1943 return num_items - 1;
2043 for (
uint i = 0; i < amount; ++i)
2075 if (m_selPosition < 0 || m_selPosition >=
m_itemList.size() ||
2120 if (m_selPosition < 0 || m_selPosition >=
m_itemList.size() ||
2255 for (
uint i = 0; i < amount; ++i)
2291 bool found_it =
false;
2292 int selectedPosition = 0;
2293 QList<MythUIButtonListItem *>::iterator it =
m_itemList.begin();
2297 if ((*it)->GetText() == position_name)
2327 bool dolast =
false;
2362 QMutableListIterator<MythUIButtonListItem *> it(
m_itemList);
2364 while (it.hasNext())
2365 it.next()->setChecked(state);
2392 LOG(VB_GENERAL, LOG_ERR, QString(
"(%1) Statetype buttonitem is "
2393 "required in mythuibuttonlist: %2")
2405 if (buttonActiveState)
2406 buttonItemArea = buttonActiveState->
GetArea();
2429 QString name = QString(
"buttonlist button %1").arg(i);
2432 button->ConnectDependants(
true);
2455 if (buttonSelectedState)
2493 QStringList actions;
2494 bool handled =
false;
2498 for (
const QString&
action : std::as_const(actions))
2507 QKeySequence a(key);
2511 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
2513 Qt::KeyboardModifiers modifiers = Qt::NoModifier;
2514 QStringList parts = key.split(
'+');
2515 for (
int j = 0; j < parts.count(); ++j)
2517 if (parts[j].toUpper() ==
"CTRL")
2518 modifiers |= Qt::ControlModifier;
2519 if (parts[j].toUpper() ==
"SHIFT")
2520 modifiers |= Qt::ShiftModifier;
2521 if (parts[j].toUpper() ==
"ALT")
2522 modifiers |= Qt::AltModifier;
2523 if (parts[j].toUpper() ==
"META")
2524 modifiers |= Qt::MetaModifier;
2527 int keyCode = a[0].key();
2528 Qt::KeyboardModifiers modifiers = a[0].keyboardModifiers();
2531 QCoreApplication::postEvent(
2533 new QKeyEvent(QEvent::KeyPress, keyCode, modifiers, key));
2534 QCoreApplication::postEvent(
2536 new QKeyEvent(QEvent::KeyRelease, keyCode, modifiers, key));
2542 for (
int i = 0; i < actions.size() && !handled; ++i)
2544 QString
action = actions[i];
2554 else if (
action ==
"DOWN")
2561 else if (
action ==
"RIGHT")
2575 else if (
action ==
"LEFT")
2589 else if (
action ==
"PAGEUP")
2593 else if (
action ==
"PAGEDOWN")
2597 else if (
action ==
"PAGETOP")
2601 else if (
action ==
"PAGEMIDDLE")
2605 else if (
action ==
"PAGEBOTTOM")
2609 else if (
action ==
"SELECT")
2616 else if (
action ==
"SEARCH")
2632 bool handled =
false;
2639 QPoint position =
event->GetPosition() -
2651 QString name =
object->objectName();
2653 if (name ==
"upscrollarrow")
2657 else if (name ==
"downscrollarrow")
2661 else if (name.startsWith(
"buttonlist button"))
2663 int pos = name.section(
' ', 2, 2).toInt();
2736 (QEvent::Type) QEvent::registerEventType();
2744 int cur = npe->m_start;
2745 for (; cur < npe->m_start + npe->m_pageSize && cur <
GetCount(); ++cur)
2747 const int loginterval = (cur < 1000 ? 100 : 500);
2748 if (cur > 200 && cur % loginterval == 0)
2749 LOG(VB_GUI, LOG_INFO,
2750 QString(
"Build background buttonlist item %1").arg(cur));
2840 const QString &
filename, QDomElement &element,
bool showWarnings)
2842 if (element.tagName() ==
"buttonarea")
2844 else if (element.tagName() ==
"layout")
2848 if (layout ==
"grid")
2850 else if (layout ==
"horizontal")
2855 else if (element.tagName() ==
"arrange")
2859 if (arrange ==
"fill")
2861 else if (arrange ==
"spread")
2863 else if (arrange ==
"stack")
2869 else if (element.tagName() ==
"align")
2874 else if (element.tagName() ==
"scrollstyle")
2878 if (layout ==
"center")
2880 else if (layout ==
"groupcenter")
2882 else if (layout ==
"free")
2885 else if (element.tagName() ==
"wrapstyle")
2889 if (wrapstyle ==
"captive")
2891 else if (wrapstyle ==
"none")
2893 else if (wrapstyle ==
"selection")
2895 else if (wrapstyle ==
"flowing")
2897 else if (wrapstyle ==
"items")
2900 else if (element.tagName() ==
"showarrow")
2902 else if (element.tagName() ==
"showscrollbar")
2904 else if (element.tagName() ==
"spacing")
2909 else if (element.tagName() ==
"drawfrombottom")
2916 else if (element.tagName() ==
"searchposition")
2920 else if (element.tagName() ==
"triggerevent")
2923 if (!trigger.isEmpty())
2925 QString
action = element.attribute(
"action",
"");
2932 QString context = element.attribute(
"context",
"");
2934 QStringList keys = keylist.split(
',', Qt::SkipEmptyParts);
3015 QString name = QString(
"buttonlist button %1").arg(i);
3047 if (lcddev ==
nullptr)
3051 QList<LCDMenuItem> menuItems;
3056 for (
int r = start; r < end; ++r)
3078 text +=
" ~ " + props.
text;
3086 text +=
" ~ " + item->
GetText();
3090 if (!text.isEmpty())
3091 menuItems.append(
LCDMenuItem(selected, state, text));
3096 if (!menuItems.isEmpty())
3113 QRect dialogArea = dlg->GetArea();
3116 x = (screenArea.width() - dialogArea.width()) / 2;
3119 y = (screenArea.height() - dialogArea.height()) / 2;
3121 dlg->SetPosition(x, y);
3134 return DoFind(
false,
true);
3139 return DoFind(
true,
true);
3144 return DoFind(
true,
false);
3156 int currPos = startPos;
3202 if (startPos == currPos)
3212 QString text, QString image,
3214 bool showArrow,
int listPosition)
3215 : m_parent(lbtype), m_text(
std::move(text)), m_imageFilename(
std::move(image)),
3216 m_checkable(checkable), m_state(state), m_showArrow(showArrow)
3219 LOG(VB_GENERAL, LOG_ERR,
"Cannot add a button to a non-existent list!");
3229 const QString &text,
3230 QVariant data,
int listPosition)
3233 LOG(VB_GENERAL, LOG_ERR,
"Cannot add a button to a non-existent list!");
3237 m_data = std::move(data);
3259 QMap<QString, MythImage*>::iterator it;
3269 const QString &state)
3271 if (!name.isEmpty())
3274 textprop.
text = text;
3286 const QString &state)
3288 InfoMap::const_iterator map_it = infoMap.begin();
3290 while (map_it != infoMap.end())
3293 textprop.
text = (*map_it);
3322 if (!result.isEmpty())
3337 if (!result.isEmpty())
3338 return {result,
""};
3346 bool startsWith)
const
3348 if (fieldList.isEmpty())
3351 return m_text.startsWith(searchStr, Qt::CaseInsensitive);
3352 return m_text.contains(searchStr, Qt::CaseInsensitive);
3354 if (fieldList ==
"**ALL**")
3358 if (
m_text.startsWith(searchStr, Qt::CaseInsensitive))
3363 if (
m_text.contains(searchStr, Qt::CaseInsensitive))
3367 QMap<QString, TextProperties>::const_iterator i =
m_strings.constBegin();
3373 if (i.value().text.startsWith(searchStr, Qt::CaseInsensitive))
3378 if (i.value().text.contains(searchStr, Qt::CaseInsensitive))
3387 QStringList fields = fieldList.split(
',', Qt::SkipEmptyParts);
3388 for (
int x = 0; x < fields.count(); ++x)
3390 if (
m_strings.contains(fields.at(x).trimmed()))
3394 if (
m_strings[fields.at(x)].text.startsWith(searchStr, Qt::CaseInsensitive))
3399 if (
m_strings[fields.at(x)].text.contains(searchStr, Qt::CaseInsensitive))
3410 const QString &name)
3412 if (!name.isEmpty())
3429 if (!name.isEmpty())
3431 QMap<QString, MythImage*>::iterator it =
m_images.find(name);
3470 if (!name.isEmpty())
3472 QMap<QString, MythImage*>::iterator it =
m_images.find(name);
3489 const QString &
filename,
const QString &name,
bool force_reload)
3491 bool do_update = force_reload;
3493 if (!name.isEmpty())
3526 if (!result.isEmpty())
3559 const QString &name)
3564 bool do_update =
false;
3565 InfoMap::iterator it =
m_states.find(name);
3572 else if (*it !=
state)
3601 if (!result.isEmpty())
3657 m_data = std::move(data);
3689 buttonimage->
Load();
3722 if (!buttonprogress)
3730 if (!buttonprogress)
3744 static const QRegularExpression re {R
"(%(([^\|%]+)?\||\|(.))?([\w#]+)(\|(.+?))?%)",
3745 QRegularExpression::DotMatchesEverythingOption};
3747 if (!newText.isEmpty() && newText.contains(re))
3749 QString tempString = newText;
3751 QRegularExpressionMatchIterator i = re.globalMatch(newText);
3752 while (i.hasNext()) {
3753 QRegularExpressionMatch match = i.next();
3754 QString key = match.captured(4).toLower().trimmed();
3755 QString replacement;
3758 if (!value.isEmpty())
3760 replacement = QString(
"%1%2%3%4")
3761 .arg(match.captured(2),
3767 tempString.replace(match.captured(0), replacement);
3770 newText = tempString;
3773 newText = textprop.
text;
3775 if (newText.isEmpty())
3845 LOG(VB_GUI, LOG_WARNING,
"Theme Error: Missing buttonlist state: disabled");
3851 LOG(VB_GUI, LOG_WARNING,
"Theme Error: Missing buttonlist state: inactive");
3859 LOG(VB_GENERAL, LOG_CRIT, QString(
"Theme Error: Missing buttonlist state: %1")
3864 buttonstate->Reset();
3866 QList<MythUIType *> descendants = buttonstate->GetAllDescendants();
3867 for (
MythUIType *obj : std::as_const(descendants))
3869 QString name = obj->objectName();
3870 if (name ==
"buttontext")
3872 else if (name ==
"buttonimage")
3874 else if (name ==
"buttonarrow")
3876 else if (name ==
"buttoncheck")
3878 else if (name ==
"buttonprogress1")
3880 else if (name ==
"buttonprogress2")
3884 if (!textprop.
text.isEmpty())
3895 if (!luState.isEmpty())
3920 LOG(VB_GENERAL, LOG_ERR,
"Cannot load screen 'MythSearchListDialog'");
3940 QStringList actions;
3943 for (
int i = 0; i < actions.size() && !handled; ++i)
3945 QString
action = actions[i];