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 start_button = std::max(
static_cast<int>(
m_buttonList.size()) / 2, 0);
852 first_button = last_button = start_button;
855 selected_column = selected_row = -1;
858 first_item, last_item, selected_column,
859 skip_cols, grow_left,
true, &col_widths,
860 height, 0, 0, col_cnt, wrapped))
867 if (selected_column != -1)
870 row_heights.push_back(height);
873 top_height = bottom_height = (height / 2);
875 bottom_height = height;
882 first_item, last_item,
883 selected_column, selected_row,
884 skip_cols, &col_widths, row_heights,
885 top_height, bottom_height, wrapped))
889 col_widths =
nullptr;
895 m_rows = row_heights.size();
897 LOG(VB_GUI, LOG_DEBUG,
898 QString(
"%1 rows, %2 columns fit inside parent area %3x%4")
902 if (col_widths ==
nullptr)
906 int left_spacing = 0;
907 int right_spacing = 0;
909 int bottom_spacing = 0;
918 status_msg =
"Row heights: ";
920 for (
int row = 0; row <
m_rows; ++row)
925 if (row == selected_row)
928 top_height += (row_heights[row] / 2);
929 bottom_height += ((row_heights[row] / 2) + (row_heights[row] % 2));
945 status_msg += QString(
"%1").arg(row_heights[row]);
947 if (row == selected_row)
957 top_spacing = bottom_spacing = 0;
964 top_spacing = bottom_spacing =
980 if (!
m_topRows || top_spacing > bottom_spacing)
981 top_spacing = bottom_spacing;
983 bottom_spacing = top_spacing;
990 (top_height + bottom_height)) /
1010 y += std::max(bottom_height - top_height, 0);
1011 total = std::max(top_height, bottom_height) * 2;
1014 total = top_height + bottom_height;
1023 (top_height + bottom_height), 0);
1027 status_msg += QString(
" spacing top %1 bottom %2 fixed %3 offset %4")
1028 .arg(top_spacing).arg(bottom_spacing)
1031 LOG(VB_GUI, LOG_DEBUG, status_msg);
1037 int right_width = 0;
1040 status_msg =
"Col widths: ";
1042 for (
int col = 0; col <
m_columns; ++col)
1047 if (col == selected_column)
1050 left_width += (col_widths[col] / 2);
1051 right_width += ((col_widths[col] / 2) + (col_widths[col] % 2));
1067 status_msg += QString(
"%1").arg(col_widths[col]);
1069 if (col == selected_column)
1079 left_spacing = right_spacing = 0;
1086 left_spacing = right_spacing =
1103 left_spacing = right_spacing;
1105 right_spacing = left_spacing;
1112 (left_width + right_width)) /
1132 x_init += std::max(right_width - left_width, 0);
1133 total = std::max(left_width, right_width) * 2;
1136 total = left_width + right_width;
1145 (left_width + right_width), 0);
1147 min_rect.
setX(x_init);
1149 status_msg += QString(
" spacing left %1 right %2 fixed %3 offset %4")
1150 .arg(left_spacing).arg(right_spacing)
1152 LOG(VB_GUI, LOG_DEBUG, status_msg);
1160 int buttonIdx = first_button - skip_cols;
1165 int vertical_spacing = top_spacing;
1167 for (
int row = 0; row <
m_rows; ++row)
1170 int horizontal_spacing = left_spacing;
1172 for (
int col = 0; col <
m_columns && buttonIdx <= last_button; ++col)
1174 if (buttonIdx >= first_button)
1182 MythRect area = buttonstate->GetArea();
1208 if (col == selected_column)
1210 horizontal_spacing = right_spacing;
1211 if (row == selected_row)
1215 x += col_widths[col] + horizontal_spacing;
1219 if (row == selected_row)
1220 vertical_spacing = bottom_spacing;
1222 y += row_heights[row] + vertical_spacing;
1224 min_rect.
setWidth(x - min_rect.x());
1230 for (buttonIdx = 0; buttonIdx < first_button; ++buttonIdx)
1234 for (buttonIdx =
m_maxVisible - 1; buttonIdx > last_button; --buttonIdx)
1250 delete[] col_widths;
1306 QList<MythUIButtonListItem *>::iterator it =
m_itemList.begin() +
1330 for (
int i = 0; i < button; ++i)
1333 bool seenSelected =
false;
1348 if (!realButton || !buttonItem)
1351 bool selected =
false;
1355 seenSelected =
true;
1404 QMap<int, MythUIButtonListItem*>::const_iterator i =
m_buttonToItem.constBegin();
1408 i.value()->setVisible(
false);
1460 if (listPosition >= 0 && listPosition <=
m_itemList.count())
1495 QMap<int, MythUIButtonListItem*>::iterator it =
m_buttonToItem.begin();
1498 if (it.value() == item)
1546 if (item->GetData() == data)
1601 return item->
GetText().toInt();
1644 QListIterator<MythUIButtonListItem *> it(
m_itemList);
1646 if (!it.findNext(item))
1649 return it.previous();
1688 if (item->GetData() == data)
1710 QString name(
"buttonlist button 0");
1713 button->ConnectDependants(
true);
1753 for (; pos >= 0; --pos)
1762 if (buttonstate ==
nullptr)
1764 LOG(VB_GENERAL, LOG_ERR,
1765 "PageUp: Failed to query buttonlist state");
1770 buttonstate->GetArea().width() / 2 >= max_width)
1806 for (; pos >= 0; pos -= dec)
1815 if (buttonstate ==
nullptr)
1817 LOG(VB_GENERAL, LOG_ERR,
1818 "PageUp: Failed to query buttonlist state");
1823 buttonstate->GetArea().height() / 2 >= max_height)
1859 for (; pos < num_items; ++pos)
1868 if (buttonstate ==
nullptr)
1870 LOG(VB_GENERAL, LOG_ERR,
1871 "PageDown: Failed to query buttonlist state");
1876 buttonstate->GetArea().width() / 2 >= max_width)
1886 return num_items - 1;
1912 for (; pos < num_items; pos += inc)
1923 LOG(VB_GENERAL, LOG_ERR,
1924 "PageDown: Failed to query buttonlist state");
1929 buttonstate->GetArea().height() / 2 >= max_height)
1939 return num_items - 1;
2039 for (
uint i = 0; i < amount; ++i)
2071 if (m_selPosition < 0 || m_selPosition >=
m_itemList.size() ||
2116 if (m_selPosition < 0 || m_selPosition >=
m_itemList.size() ||
2251 for (
uint i = 0; i < amount; ++i)
2287 bool found_it =
false;
2288 int selectedPosition = 0;
2289 QList<MythUIButtonListItem *>::iterator it =
m_itemList.begin();
2293 if ((*it)->GetText() == position_name)
2323 bool dolast =
false;
2358 QMutableListIterator<MythUIButtonListItem *> it(
m_itemList);
2360 while (it.hasNext())
2361 it.next()->setChecked(state);
2388 LOG(VB_GENERAL, LOG_ERR, QString(
"(%1) Statetype buttonitem is "
2389 "required in mythuibuttonlist: %2")
2401 if (buttonActiveState)
2402 buttonItemArea = buttonActiveState->
GetArea();
2425 QString name = QString(
"buttonlist button %1").arg(i);
2428 button->ConnectDependants(
true);
2451 if (buttonSelectedState)
2489 QStringList actions;
2490 bool handled =
false;
2494 for (
const QString&
action : qAsConst(actions))
2503 QKeySequence a(key);
2507 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
2509 Qt::KeyboardModifiers modifiers = Qt::NoModifier;
2510 QStringList parts = key.split(
'+');
2511 for (
int j = 0; j < parts.count(); ++j)
2513 if (parts[j].toUpper() ==
"CTRL")
2514 modifiers |= Qt::ControlModifier;
2515 if (parts[j].toUpper() ==
"SHIFT")
2516 modifiers |= Qt::ShiftModifier;
2517 if (parts[j].toUpper() ==
"ALT")
2518 modifiers |= Qt::AltModifier;
2519 if (parts[j].toUpper() ==
"META")
2520 modifiers |= Qt::MetaModifier;
2523 int keyCode = a[0].key();
2524 Qt::KeyboardModifiers modifiers = a[0].keyboardModifiers();
2527 QCoreApplication::postEvent(
2529 new QKeyEvent(QEvent::KeyPress, keyCode, modifiers, key));
2530 QCoreApplication::postEvent(
2532 new QKeyEvent(QEvent::KeyRelease, keyCode, modifiers, key));
2538 for (
int i = 0; i < actions.size() && !handled; ++i)
2540 QString
action = actions[i];
2550 else if (
action ==
"DOWN")
2557 else if (
action ==
"RIGHT")
2571 else if (
action ==
"LEFT")
2585 else if (
action ==
"PAGEUP")
2589 else if (
action ==
"PAGEDOWN")
2593 else if (
action ==
"PAGETOP")
2597 else if (
action ==
"PAGEMIDDLE")
2601 else if (
action ==
"PAGEBOTTOM")
2605 else if (
action ==
"SELECT")
2612 else if (
action ==
"SEARCH")
2628 bool handled =
false;
2635 QPoint position =
event->GetPosition() -
2647 QString name =
object->objectName();
2649 if (name ==
"upscrollarrow")
2653 else if (name ==
"downscrollarrow")
2657 else if (name.startsWith(
"buttonlist button"))
2659 int pos = name.section(
' ', 2, 2).toInt();
2732 (QEvent::Type) QEvent::registerEventType();
2740 int cur = npe->m_start;
2741 for (; cur < npe->m_start + npe->m_pageSize && cur <
GetCount(); ++cur)
2743 const int loginterval = (cur < 1000 ? 100 : 500);
2744 if (cur > 200 && cur % loginterval == 0)
2745 LOG(VB_GUI, LOG_INFO,
2746 QString(
"Build background buttonlist item %1").arg(cur));
2836 const QString &
filename, QDomElement &element,
bool showWarnings)
2838 if (element.tagName() ==
"buttonarea")
2840 else if (element.tagName() ==
"layout")
2844 if (layout ==
"grid")
2846 else if (layout ==
"horizontal")
2851 else if (element.tagName() ==
"arrange")
2855 if (arrange ==
"fill")
2857 else if (arrange ==
"spread")
2859 else if (arrange ==
"stack")
2865 else if (element.tagName() ==
"align")
2870 else if (element.tagName() ==
"scrollstyle")
2874 if (layout ==
"center")
2876 else if (layout ==
"groupcenter")
2878 else if (layout ==
"free")
2881 else if (element.tagName() ==
"wrapstyle")
2885 if (wrapstyle ==
"captive")
2887 else if (wrapstyle ==
"none")
2889 else if (wrapstyle ==
"selection")
2891 else if (wrapstyle ==
"flowing")
2893 else if (wrapstyle ==
"items")
2896 else if (element.tagName() ==
"showarrow")
2898 else if (element.tagName() ==
"showscrollbar")
2900 else if (element.tagName() ==
"spacing")
2905 else if (element.tagName() ==
"drawfrombottom")
2912 else if (element.tagName() ==
"searchposition")
2916 else if (element.tagName() ==
"triggerevent")
2919 if (!trigger.isEmpty())
2921 QString
action = element.attribute(
"action",
"");
2928 QString context = element.attribute(
"context",
"");
2930 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
2931 QStringList keys = keylist.split(
',', QString::SkipEmptyParts);
2933 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 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
3388 QStringList fields = fieldList.split(
',', QString::SkipEmptyParts);
3390 QStringList fields = fieldList.split(
',', Qt::SkipEmptyParts);
3393 for (
int x = 0; x < fields.count(); ++x)
3395 if (
m_strings.contains(fields.at(x).trimmed()))
3399 if (
m_strings[fields.at(x)].text.startsWith(searchStr, Qt::CaseInsensitive))
3404 if (
m_strings[fields.at(x)].text.contains(searchStr, Qt::CaseInsensitive))
3415 const QString &name)
3417 if (!name.isEmpty())
3434 if (!name.isEmpty())
3436 QMap<QString, MythImage*>::iterator it =
m_images.find(name);
3475 if (!name.isEmpty())
3477 QMap<QString, MythImage*>::iterator it =
m_images.find(name);
3494 const QString &
filename,
const QString &name,
bool force_reload)
3496 bool do_update = force_reload;
3498 if (!name.isEmpty())
3531 if (!result.isEmpty())
3564 const QString &name)
3569 bool do_update =
false;
3570 InfoMap::iterator it =
m_states.find(name);
3577 else if (*it !=
state)
3606 if (!result.isEmpty())
3662 m_data = std::move(data);
3694 buttonimage->
Load();
3727 if (!buttonprogress)
3735 if (!buttonprogress)
3749 static const QRegularExpression re {R
"(%(([^\|%]+)?\||\|(.))?([\w#]+)(\|(.+?))?%)",
3750 QRegularExpression::DotMatchesEverythingOption};
3752 if (!newText.isEmpty() && newText.contains(re))
3754 QString tempString = newText;
3756 QRegularExpressionMatchIterator i = re.globalMatch(newText);
3757 while (i.hasNext()) {
3758 QRegularExpressionMatch match = i.next();
3759 QString key = match.captured(4).toLower().trimmed();
3760 QString replacement;
3763 if (!value.isEmpty())
3765 replacement = QString(
"%1%2%3%4")
3766 .arg(match.captured(2),
3772 tempString.replace(match.captured(0), replacement);
3775 newText = tempString;
3778 newText = textprop.
text;
3780 if (newText.isEmpty())
3850 LOG(VB_GUI, LOG_WARNING,
"Theme Error: Missing buttonlist state: disabled");
3856 LOG(VB_GUI, LOG_WARNING,
"Theme Error: Missing buttonlist state: inactive");
3864 LOG(VB_GENERAL, LOG_CRIT, QString(
"Theme Error: Missing buttonlist state: %1")
3869 buttonstate->Reset();
3871 QList<MythUIType *> descendants = buttonstate->GetAllDescendants();
3874 QString name = obj->objectName();
3875 if (name ==
"buttontext")
3877 else if (name ==
"buttonimage")
3879 else if (name ==
"buttonarrow")
3881 else if (name ==
"buttoncheck")
3883 else if (name ==
"buttonprogress1")
3885 else if (name ==
"buttonprogress2")
3889 if (!textprop.
text.isEmpty())
3900 if (!luState.isEmpty())
3925 LOG(VB_GENERAL, LOG_ERR,
"Cannot load screen 'MythSearchListDialog'");
3945 QStringList actions;
3948 for (
int i = 0; i < actions.size() && !handled; ++i)
3950 QString
action = actions[i];