1 #include "common/common_pch.h"
2 
3 #include <QAbstractItemView>
4 #include <QCheckBox>
5 #include <QComboBox>
6 #include <QDebug>
7 #include <QGridLayout>
8 #include <QGroupBox>
9 #include <QIcon>
10 #include <QLineEdit>
11 #include <QList>
12 #include <QPushButton>
13 #include <QRadioButton>
14 #include <QScrollArea>
15 #include <QSettings>
16 #include <QSpinBox>
17 #include <QString>
18 
19 #include "common/bitvalue.h"
20 #include "common/kax_analyzer.h"
21 #include "common/list_utils.h"
22 #include "common/qt.h"
23 #include "mkvtoolnix-gui/main_window/main_window.h"
24 #include "mkvtoolnix-gui/util/file_dialog.h"
25 #include "mkvtoolnix-gui/util/language_display_widget.h"
26 #include "mkvtoolnix-gui/util/message_box.h"
27 #include "mkvtoolnix-gui/util/settings.h"
28 #include "mkvtoolnix-gui/util/settings_names.h"
29 #include "mkvtoolnix-gui/util/widget.h"
30 
31 namespace mtx::gui::Util {
32 
33 QIcon
loadIcon(QString const & name,QList<int> const & sizes)34 loadIcon(QString const &name,
35          QList<int> const &sizes) {
36   QIcon icon;
37   for (auto size : sizes)
38     icon.addFile(QString{":/icons/%1x%1/%2"}.arg(size).arg(name));
39 
40   return icon;
41 }
42 
43 bool
setComboBoxIndexIf(QComboBox * comboBox,std::function<bool (QString const &,QVariant const &)> test)44 setComboBoxIndexIf(QComboBox *comboBox,
45                    std::function<bool(QString const &, QVariant const &)> test) {
46   auto count = comboBox->count();
47   for (int idx = 0; count > idx; ++idx)
48     if (test(comboBox->itemText(idx), comboBox->itemData(idx))) {
49       comboBox->setCurrentIndex(idx);
50       return true;
51     }
52 
53   return false;
54 }
55 
56 bool
setComboBoxTextByData(QComboBox * comboBox,QString const & data)57 setComboBoxTextByData(QComboBox *comboBox,
58                       QString const &data) {
59   return setComboBoxIndexIf(comboBox, [&data](QString const &, QVariant const &itemData) { return itemData.isValid() && (itemData.toString() == data); });
60 }
61 
62 void
setComboBoxTexts(QComboBox * comboBox,QStringList const & texts)63 setComboBoxTexts(QComboBox *comboBox,
64                  QStringList const &texts) {
65   auto numItems    = comboBox->count();
66   auto numTexts    = texts.count();
67   auto textIdx     = 0;
68   auto comboBoxIdx = 0;
69 
70   while ((comboBoxIdx < numItems) && (textIdx < numTexts)) {
71     if (comboBox->itemData(comboBoxIdx).isValid()) {
72       comboBox->setItemText(comboBoxIdx, texts[textIdx]);
73       ++textIdx;
74     }
75 
76     ++comboBoxIdx;
77   }
78 }
79 
80 void
enableWidgets(QList<QWidget * > const & widgets,bool enable)81 enableWidgets(QList<QWidget *> const &widgets,
82               bool enable) {
83   for (auto &widget : widgets)
84     widget->setEnabled(enable);
85 }
86 
87 void
enableChildren(QObject * parent,bool enable)88 enableChildren(QObject *parent,
89                bool enable) {
90   for (auto const &child : parent->children()) {
91     auto widget = qobject_cast<QWidget *>(child);
92     if (widget)
93       widget->setEnabled(enable);
94   }
95 }
96 
97 QPushButton *
buttonForRole(QDialogButtonBox * box,QDialogButtonBox::ButtonRole role)98 buttonForRole(QDialogButtonBox *box,
99               QDialogButtonBox::ButtonRole role) {
100   auto buttons = box->buttons();
101   auto button  = std::find_if(buttons.begin(), buttons.end(), [box, role](auto *b) { return box->buttonRole(b) == role; });
102   return button == buttons.end() ? nullptr : static_cast<QPushButton *>(*button);
103 }
104 
105 void
setToolTip(QWidget * widget,QString const & toolTip)106 setToolTip(QWidget *widget,
107            QString const &toolTip) {
108   if (dynamic_cast<LanguageDisplayWidget *>(widget)) {
109     static_cast<LanguageDisplayWidget &>(*widget).setAdditionalToolTip(toolTip);
110     return;
111   }
112 
113   if (Util::Settings::get().m_uiDisableToolTips) {
114     widget->setToolTip({});
115     return;
116   }
117 
118   // Qt up to and including 5.3 only word-wraps tool tips
119   // automatically if the format is recognized to be Rich Text. See
120   // http://doc.qt.io/qt-5/qstandarditem.html
121 
122   widget->setToolTip(toolTip.isEmpty() || toolTip.startsWith('<') ? toolTip : Q("<span>%1</span>").arg(toolTip.toHtmlEscaped()));
123 }
124 
125 void
saveWidgetGeometry(QWidget * widget)126 saveWidgetGeometry(QWidget *widget) {
127   auto reg = Util::Settings::registry();
128 
129   reg->beginGroup(s_grpWindowGeometry);
130   reg->setValue(widget->objectName(), widget->saveGeometry());
131   reg->endGroup();
132 }
133 
134 void
restoreWidgetGeometry(QWidget * widget)135 restoreWidgetGeometry(QWidget *widget) {
136   auto reg = Util::Settings::registry();
137 
138   reg->beginGroup(s_grpWindowGeometry);
139   widget->restoreGeometry(reg->value(widget->objectName()).toByteArray());
140   reg->endGroup();
141 }
142 
143 QWidget *
tabWidgetCloseTabButton(QTabWidget & tabWidget,int tabIdx)144 tabWidgetCloseTabButton(QTabWidget &tabWidget,
145                         int tabIdx) {
146   auto tabBar = tabWidget.tabBar();
147   auto result = mtx::first_of<QWidget *>([](QWidget *button) { return !!button; }, tabBar->tabButton(tabIdx, QTabBar::LeftSide), tabBar->tabButton(tabIdx, QTabBar::RightSide));
148 
149   return result.value_or(nullptr);
150 }
151 
152 void
fixScrollAreaBackground(QScrollArea * scrollArea)153 fixScrollAreaBackground(QScrollArea *scrollArea) {
154   scrollArea->setBackgroundRole(QPalette::Base);
155 }
156 
157 void
preventScrollingWithoutFocus(QObject * parent)158 preventScrollingWithoutFocus(QObject *parent) {
159   auto install = [](QWidget *widget) {
160     widget->installEventFilter(MainWindow::get());
161     widget->setFocusPolicy(Qt::StrongFocus);
162   };
163 
164   for (auto const &child : parent->findChildren<QCheckBox *>())
165     install(child);
166 
167   for (auto const &child : parent->findChildren<QComboBox *>())
168     install(child);
169 
170   for (auto const &child : parent->findChildren<QRadioButton *>())
171     install(child);
172 
173   for (auto const &child : parent->findChildren<QSpinBox *>())
174     install(child);
175 }
176 
177 void
fixComboBoxViewWidth(QComboBox & comboBox)178 fixComboBoxViewWidth(QComboBox &comboBox) {
179   comboBox.setSizeAdjustPolicy(QComboBox::AdjustToContents);
180   comboBox.view()->setMinimumWidth(comboBox.sizeHint().width());
181 }
182 
183 void
addSegmentUIDFromFileToLineEdit(QWidget & parent,QLineEdit & lineEdit,bool append)184 addSegmentUIDFromFileToLineEdit(QWidget &parent,
185                                 QLineEdit &lineEdit,
186                                 bool append) {
187   auto &settings = Util::Settings::get();
188   auto dir       = settings.lastOpenDirPath();
189   auto filter    = QY("Matroska and WebM files") + Q(" (*.mkv *.mka *.mks *.mk3d *.webm);;")
190                  + QY("All files")               + Q(" (*)");
191   auto fileName  = Util::getOpenFileName(&parent, QY("Select Matroska file to read segment UID from"), dir, filter);
192 
193   if (fileName.isEmpty())
194     return;
195 
196   settings.m_lastOpenDir.setPath(QFileInfo{fileName}.path());
197   settings.save();
198 
199   try {
200     auto segmentUID = kax_analyzer_c::read_segment_uid_from(to_utf8(fileName));
201     auto uidString  = QString{};
202     auto src        = segmentUID->data();
203 
204     for (int idx = 0, numBytes = segmentUID->byte_size(); idx < numBytes; ++idx)
205       uidString += Q("%1").arg(QString::number(src[idx], 16), 2, '0').toUpper();
206 
207     if (!append || lineEdit.text().isEmpty())
208       lineEdit.setText(uidString);
209 
210     else
211       lineEdit.setText(Q("%1,%2").arg(lineEdit.text()).arg(uidString));
212 
213   } catch (mtx::kax_analyzer_x &ex) {
214     Util::MessageBox::critical(&parent)
215       ->title(QY("Reading the segment UID failed"))
216       .text(Q(ex.what()))
217       .exec();
218   }
219 }
220 
221 #if defined(SYS_APPLE)
222 constexpr auto OS_SPECIFIC_ELIDE_POSITION = Qt::ElideRight;
223 #else
224 constexpr auto OS_SPECIFIC_ELIDE_POSITION = Qt::ElideMiddle;
225 #endif
226 
227 void
setupTabWidgetHeaders(QTabWidget & tabWidget)228 setupTabWidgetHeaders(QTabWidget &tabWidget) {
229   auto &cfg = Util::Settings::get();
230 
231   tabWidget.setTabPosition(Util::Settings::get().m_tabPosition);
232   tabWidget.setElideMode(cfg.m_elideTabHeaderLabels ? OS_SPECIFIC_ELIDE_POSITION : Qt::ElideNone);
233 }
234 
235 void
autoGroupBoxGridLayout(QGroupBox & box,unsigned int numColumns)236 autoGroupBoxGridLayout(QGroupBox &box,
237                        unsigned int numColumns) {
238   auto previousLayout = dynamic_cast<QGridLayout *>(box.layout());
239   if (!previousLayout) {
240     qDebug() << "autoGroupBoxGridLayout: current layout is not a grid layout?";
241     return;
242   }
243 
244   QVector<QObject *> entries;
245   auto numPreviousColumns = previousLayout->columnCount();
246   auto numPreviousRows    = previousLayout->rowCount();
247 
248   for (auto column = 0; column < numPreviousColumns; column += 2) {
249     for (auto row = 0; row < numPreviousRows; ++row) {
250       for (auto offset = 0; offset < 2; ++offset) {
251         auto item = previousLayout->itemAtPosition(row, column + offset);
252         if (item && item->widget())
253           entries << item->widget();
254       }
255     }
256   }
257 
258   auto newLayout = new QGridLayout;
259   auto numRows   = ((entries.size() / 2) + numColumns - 1) / numColumns;
260   auto position  = 0;
261 
262 
263   for (auto entry : entries) {
264     auto column = ((position / 2 / numRows) * 2) + (position % 2);
265     auto row    = (position / 2) % numRows;
266 
267     newLayout->addWidget(static_cast<QWidget *>(entry), row, column);
268     ++position;
269   }
270 
271   delete previousLayout;
272   box.setLayout(newLayout);
273 }
274 
275 }
276