1 #include "utilsUI.h"
2 #include "utilsSystem.h"
3 #include "utilsVersion.h"
4 #include "filedialog.h"
5
6 #if QT_VERSION<QT_VERSION_CHECK(5,14,0)
7 #include <QDesktopWidget>
8 #include <QWindow>
9 #endif
10
11 #include <QErrorMessage>
12
13
14
15 extern void hideSplash();
16
17 namespace UtilsUi {
18 /*!
19 * \brief show confirmation message box
20 * \param message
21 * \return yes=true
22 */
txsConfirm(const QString & message)23 bool txsConfirm(const QString &message)
24 {
25 hideSplash();
26 return QMessageBox::question(QApplication::activeWindow(), TEXSTUDIO, message, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes;
27 }
28 /*!
29 * \brief show confirmation with warning message box
30 * \param message
31 * \return yes=true
32 */
txsConfirmWarning(const QString & message)33 bool txsConfirmWarning(const QString &message)
34 {
35 hideSplash();
36 return QMessageBox::warning(QApplication::activeWindow(), TEXSTUDIO, message, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes;
37 }
38 /*!
39 * \brief show confirmation with warning message box
40 * \param message
41 * \param rememberChoice if true, return true and avoid Msg. rememberChoice is overwritten by checkbox result.
42 * \return yes=true
43 */
txsConfirmWarning(const QString & message,txsWarningState & rememberChoice)44 bool txsConfirmWarning(const QString &message,txsWarningState &rememberChoice)
45 {
46 switch (rememberChoice){
47 case RememberFalse: return false ;
48 case RememberTrue: return true ;
49 default: ;
50 }
51 hideSplash();
52 QMessageBox msg(QMessageBox::Warning,TEXSTUDIO,message,QMessageBox::Yes | QMessageBox::No,QApplication::activeWindow());
53 QCheckBox *cb=new QCheckBox(QApplication::tr("Remember choice ?"));
54 msg.setCheckBox(cb);
55 bool result=(msg.exec()==QMessageBox::Yes);
56 if(msg.checkBox()->checkState()==Qt::Checked){
57 if(result){
58 rememberChoice=RememberTrue;
59 }else{
60 rememberChoice=RememberFalse;
61 }
62 }
63 return result;
64 }
65 /*!
66 * \brief show confirmation with warning message box
67 * Here the available buttons can be set.
68 * \param message
69 * \param buttons
70 * \return pressed button
71 */
txsConfirmWarning(const QString & message,QMessageBox::StandardButtons buttons)72 QMessageBox::StandardButton txsConfirmWarning(const QString &message, QMessageBox::StandardButtons buttons)
73 {
74 hideSplash();
75 return QMessageBox::warning(QApplication::activeWindow(), TEXSTUDIO, message, buttons, QMessageBox::Yes);
76 }
77 /*!
78 * \brief show information message pop-up
79 * \param message
80 */
txsInformation(const QString & message)81 void txsInformation(const QString &message)
82 {
83 hideSplash();
84 QMessageBox::information(QApplication::activeWindow(), TEXSTUDIO, message, QMessageBox::Ok);
85 }
86 /*!
87 * \brief show warning message pop-up
88 * \param message
89 */
txsWarning(const QString & message)90 void txsWarning(const QString &message)
91 {
92 hideSplash();
93 QMessageBox::warning(QApplication::activeWindow(), TEXSTUDIO, message, QMessageBox::Ok);
94 }
95 /*!
96 * \brief show warning message pop-up
97 * \param message
98 * \param noWarnAgain
99 */
txsWarning(const QString & message,bool & noWarnAgain)100 void txsWarning(const QString &message, bool &noWarnAgain)
101 {
102 hideSplash();
103 QMessageBox msgBox(QMessageBox::Warning, TEXSTUDIO, message, QMessageBox::Ok, QApplication::activeWindow());
104 QCheckBox cbNoWarnAgain(QCoreApplication::translate("Texstudio", "Do not warn again.", "General warning dialog"), &msgBox);
105 cbNoWarnAgain.setChecked(noWarnAgain);
106 cbNoWarnAgain.blockSignals(true); // quick hack to prevent closing the message box
107 msgBox.addButton(&cbNoWarnAgain, QMessageBox::ActionRole);
108 msgBox.exec();
109 noWarnAgain = cbNoWarnAgain.isChecked();
110 }
111 /*!
112 * \brief show critical-error message box
113 * \param message
114 */
txsCritical(const QString & message)115 void txsCritical(const QString &message)
116 {
117 hideSplash();
118 QMessageBox::critical(QApplication::activeWindow(), TEXSTUDIO, message, QMessageBox::Ok);
119 }
120
createComboToolButton(QWidget * parent,const QStringList & list,const QList<QIcon> & icons,int height,const QObject * receiver,const char * member,int defaultIndex,QToolButton * combo)121 QToolButton *createComboToolButton(QWidget *parent, const QStringList &list, const QList<QIcon> &icons, int height, const QObject *receiver, const char *member, int defaultIndex, QToolButton *combo)
122 {
123 Q_UNUSED(icons)
124 const QFontMetrics &fm = parent->fontMetrics();
125 if (height == -1) height = 0;
126 else if (height == 0) {
127 if (parent->property("innerButtonHeight").isValid()) height = parent->property("innerButtonHeight").toInt();
128 else {
129 height = parent->height() - 2;
130 parent->setProperty("innerButtonHeight", height);
131 }
132 }
133
134 if (combo == nullptr)
135 combo = new QToolButton(parent);
136 if (height != 0)
137 combo->setMinimumHeight(height);
138 combo->setPopupMode(QToolButton::MenuButtonPopup);
139 combo->setToolButtonStyle(Qt::ToolButtonTextOnly);
140
141 // remove old actions
142 foreach (QAction *mAction, combo->actions())
143 combo->removeAction(mAction);
144
145 QMenu *mMenu = new QMenu(combo);
146 int max = 0;
147 bool defaultSet = false;
148 for (int i = 0; i < list.length(); i++) {
149 QString text = list[i];
150 //QIcon icon = (i<icons.length()) ? icons[i] : QIcon();
151 QAction *mAction = mMenu->addAction(text, receiver, member);
152 max = qMax(max, getFmWidth(fm, text + " "));
153 if (i == defaultIndex) {
154 combo->setDefaultAction(mAction);
155 defaultSet = true;
156 }
157 }
158 if (!defaultSet) {
159 if (list.isEmpty())
160 combo->setDefaultAction(new QAction("<" + QApplication::tr("none") + ">", combo));
161 else
162 combo->setDefaultAction(mMenu->actions().at(0));
163 }
164
165 combo->setMinimumWidth(max);
166 combo->setMenu(mMenu);
167 return combo;
168 }
169
comboToolButtonFromAction(QAction * action)170 QToolButton *comboToolButtonFromAction(QAction *action)
171 {
172 if (!action) return nullptr;
173 QToolButton *button = qobject_cast<QToolButton *>(action->parent());
174 if (!button) {
175 QMenu *menu = qobject_cast<QMenu *>(action->parent());
176 if (!menu) return nullptr;
177 button = qobject_cast<QToolButton *>(menu->parent());
178 if (!button) return nullptr;
179 }
180 return button;
181 }
182
createToolButtonForAction(QAction * action)183 QToolButton *createToolButtonForAction(QAction *action)
184 {
185 QToolButton *tb = new QToolButton();
186 if (!action->icon().isNull())
187 tb->setIcon(action->icon());
188 else
189 tb->setText(action->text());
190 tb->setToolTip(action->toolTip());
191 tb->setCheckable(action->isCheckable());
192 tb->setChecked(action->isChecked());
193 tb->connect(tb, SIGNAL(clicked(bool)), action, SLOT(setChecked(bool)));
194 tb->connect(action, SIGNAL(toggled(bool)), tb, SLOT(setChecked(bool)));
195 return tb;
196 }
197
setSubtreeExpanded(QTreeView * view,QModelIndex idx,bool expand)198 void setSubtreeExpanded(QTreeView *view, QModelIndex idx, bool expand)
199 {
200 for (int row = 0;; row++) {
201 QModelIndex node = view->model()->index(row, 0, idx);
202 if (!node.isValid())
203 break;
204 setSubtreeExpanded(view, node, expand);
205 }
206 view->setExpanded(idx, expand);
207 }
208
setSubtreeExpanded(QTreeWidgetItem * item,bool expand)209 void setSubtreeExpanded(QTreeWidgetItem *item, bool expand)
210 {
211 item->setExpanded( expand);
212 for(int i=0;i<item->childCount();++i){
213 setSubtreeExpanded(item->child(i), expand);
214 }
215 }
216
browse(QWidget * w,const QString & title,const QString & extension,const QString & startPath,bool list)217 bool browse(QWidget *w, const QString &title, const QString &extension, const QString &startPath, bool list)
218 {
219 QLineEdit *le = qobject_cast<QLineEdit *>(w);
220 QComboBox *cb = qobject_cast<QComboBox *>(w);
221 REQUIRE_RET(le || cb, false);
222 QString oldpath = le ? le->text() : cb->currentText();
223 QString path = oldpath;
224 #ifdef Q_OS_WIN32
225 QString pathSep = ";";
226 #else
227 QString pathSep = ":";
228 #endif
229 if (list && !path.isEmpty()) path = path.split(pathSep).last();
230 if (path.startsWith('"')) path.remove(0, 1);
231 if (path.endsWith('"')) path.chop(1);
232 if (path.isEmpty()) path = startPath;
233 if (extension == "/") path = QFileDialog::getExistingDirectory(nullptr, title, path);
234 else path = FileDialog::getOpenFileName(nullptr, title, path, extension, nullptr, QFileDialog::DontResolveSymlinks);
235 if (!path.isEmpty()) {
236 path = QDir::toNativeSeparators(path);
237 if (list && !oldpath.isEmpty()) path = oldpath + pathSep + path;
238 if (le) le->setText(path);
239 if (cb) cb->setEditText(path);
240 return true;
241 }
242 return false;
243 }
244
colorFromRGBAstr(const QString & hex,QColor fallback)245 QColor colorFromRGBAstr(const QString &hex, QColor fallback)
246 {
247 QString c = hex;
248 if (c.startsWith('#'))
249 c = c.mid(1);
250 if (c.length() != 8) return fallback;
251 bool r, g, b, a;
252 QColor color(c.mid(0, 2).toInt(&r, 16), c.mid(2, 2).toInt(&g, 16), c.mid(4, 2).toInt(&b, 16), c.mid(6, 2).toInt(&a, 16));
253 if (r && g && b && a)
254 return color;
255 return fallback;
256 }
257
258 /*!
259 Return a color with a more medium value. The amount of change is determined by factor. I.e. for a factor > 100
260 a dark color becomes lighter, a light color becomes darker. The reverse is true for factors < 100;
261 This is a generalization of QColor.lighter()/darker() which should provide a reasonable change in dark as well
262 as light contexts.
263 */
mediumLightColor(QColor color,int factor)264 QColor mediumLightColor(QColor color, int factor) {
265 if (color.value() == 0) { // special handling for black because lighter() does not work there [QTBUG-9343].
266 factor = qMax(0, factor - 100);
267 return QColor(factor, factor, factor); // i.e. QColor(50, 50, 50) for factor 150
268 }
269 if (color.value() < 128) {
270 return color.lighter(factor);
271 } else {
272 return color.darker(factor);
273 }
274 }
275
276 /*!
277 * return the window to which an object belongs or the given fallback widget
278 * Note: the signature is intentionally for a gerneric QObject, so that we can
279 * simply call windowForObject(sender()).
280 */
windowForObject(QObject * obj,QWidget * fallback)281 QWidget *windowForObject(QObject *obj, QWidget *fallback)
282 {
283 QWidget *w;
284 QAction *act = qobject_cast<QAction *>(obj);
285 if (act) {
286 w = act->parentWidget();
287 } else {
288 w = qobject_cast<QWidget *>(obj);
289 }
290 if (w) {
291 w = w->window();
292 }
293 if (w) {
294 return w;
295 }
296 return fallback;
297 }
298
299
300 /*!
301 interal
302 guesses a descriptive text from a text suited for a menu entry
303 This is copied from the internal function qt_strippedText() in qaction.cpp
304 */
strippedActionText(QString s)305 static QString strippedActionText(QString s) {
306 s.remove( QString::fromLatin1("...") );
307 for (int i = 0; i < s.size(); ++i) {
308 if (s.at(i) == QLatin1Char('&'))
309 s.remove(i, 1);
310 }
311 return s.trimmed();
312 }
313
314 /*!
315 interal
316 Adds possible shortcut information to the tooltip of the action.
317 This provides consistent behavior both with default and custom tooltips
318 when used in combination with removeShortcutToToolTip()
319 */
addShortcutToToolTip(QAction * action)320 void addShortcutToToolTip(QAction *action)
321 {
322 if (!action->shortcut().isEmpty() && !action->property("hasShortcutToolTip").toBool()) {
323 QString tooltip = action->property("tooltipBackup").toString();
324 if (tooltip.isEmpty()) {
325 tooltip = action->toolTip();
326 if (tooltip != strippedActionText(action->text())) {
327 action->setProperty("tooltipBackup", action->toolTip()); // action uses a custom tooltip. Backup so that we can restore it later.
328 }
329 }
330 QColor shortcutTextColor = QApplication::palette().color(QPalette::ToolTipText);
331 QString shortCutTextColorName;
332 if (shortcutTextColor.value() == 0) {
333 shortCutTextColorName = "gray"; // special handling for black because lighter() does not work there [QTBUG-9343].
334 } else {
335 int factor = (shortcutTextColor.value() < 128) ? 150 : 50;
336 shortCutTextColorName = shortcutTextColor.lighter(factor).name();
337 }
338 action->setToolTip(QString("<p style='white-space:pre'>%1 <code style='color:%2; font-size:small'>%3</code></p>")
339 .arg(tooltip, shortCutTextColorName, action->shortcut().toString(QKeySequence::NativeText)));
340 action->setProperty("hasShortcutToolTip", true);
341 }
342 }
343
344 /*!
345 interal
346 Removes possible shortcut information from the tooltip of the action.
347 This provides consistent behavior both with default and custom tooltips
348 when used in combination with addShortcutToToolTip()
349 */
removeShortcutFromToolTip(QAction * action)350 void removeShortcutFromToolTip(QAction *action)
351 {
352 action->setToolTip(action->property("tooltipBackup").toString());
353 action->setProperty("tooltipBackup", QVariant());
354 action->setProperty("hasShortcutToolTip", false);
355 }
356
357 /*!
358 Adds or removes shortcut information from the tooltip of the action.
359 This provides consistent behavior both with default and custom tooltips.
360 */
updateToolTipWithShortcut(QAction * action,bool showShortcut)361 void updateToolTipWithShortcut(QAction *action, bool showShortcut) {
362 if (showShortcut) {
363 addShortcutToToolTip(action);
364 } else {
365 removeShortcutFromToolTip(action);
366 }
367 }
368
369 /*!
370 * \brief Adds support for a single-finger panning gesture to the widget using a QScroller.
371 * \note ItemViews will be switched to ScrollPerPixel scrollMode.
372 */
enableTouchScrolling(QWidget * widget,bool enable)373 void enableTouchScrolling(QWidget *widget, bool enable) {
374 #if !defined(Q_OS_MAC)
375 if (enable) {
376 QAbstractItemView *view = qobject_cast<QAbstractItemView *>(widget);
377 if (view) {
378 // QScroller needs per pixel scrolling otherwise distances and speed
379 // are calculated incorrectly.
380 view->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
381 }
382 QScroller::grabGesture(widget, QScroller::TouchGesture);
383 } else {
384 QScroller::ungrabGesture(widget);
385 }
386 #endif
387 }
388
389 /*!
390 * Resize a window with width and height given in units of the application font height.
391 * This allows to scale dialogs to a size that you'll get almost the same context visible
392 * independent of UI scaling parameters.
393 */
resizeInFontHeight(QWidget * w,int width,int height)394 void resizeInFontHeight(QWidget *w, int width, int height)
395 {
396 int h = qApp->fontMetrics().height();
397 #if (QT_VERSION>=QT_VERSION_CHECK(5,14,0))
398 QRect r = w->screen()->availableGeometry();
399 #else
400 QDesktopWidget *dw = qApp->desktop();
401 QRect r = dw->availableGeometry(w);
402 #endif
403 QSize newSize = QSize(qMin(h * width, r.width()), qMin(h * height, r.height()));
404 //qDebug() << "resizeInFontHeight old size:" << w->width() / (float) h << w->height() / (float) h;
405 //qDebug() << "resizeInFontHeight new size:" << newSize.width() / (float) h << newSize.height() / (float) h;
406 w->resize(newSize);
407 }
408
409 /*!
410 * \brief Given font metrics return pixel size of a character
411 * \param[in] fm Font metrics
412 * \param[in] ch Character
413 * \returns Returns pixel size of character
414 */
getFmWidth(const QFontMetricsF & fm,QChar ch)415 qreal getFmWidth(const QFontMetricsF &fm, QChar ch)
416 {
417 #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
418 return fm.horizontalAdvance(ch);
419 #else
420 return fm.width(ch);
421 #endif
422 }
423
424 /*!
425 * \brief Given font metrics return pixel size of a character
426 * \param[in] fm Font metrics
427 * \param[in] ch Character
428 * \returns Returns pixel size of character
429 */
getFmWidth(const QFontMetrics & fm,QChar ch)430 int getFmWidth(const QFontMetrics &fm, QChar ch)
431 {
432 #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
433 return fm.horizontalAdvance(ch);
434 #else
435 return fm.width(ch);
436 #endif
437 }
438
439 /*!
440 * \brief Given font metrics return pixel size of a text string
441 * \param[in] fm Font metrics
442 * \param[in] text Text string
443 * \param[in] len Only calculate width of the first len characters of the string. If not specified,
444 * then -1 is assumed which means calculate width of the whole string.
445 * \returns Returns pixel size of the text string
446 */
getFmWidth(const QFontMetricsF & fm,const QString & text,int len)447 qreal getFmWidth(const QFontMetricsF &fm, const QString &text, int len)
448 {
449 #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
450 return fm.horizontalAdvance(text, len);
451 #else
452 return fm.width(text.left(len));
453 #endif
454 }
455
456 /*!
457 * \brief Given font metrics return pixel size of a text string
458 * \param[in] fm Font metrics
459 * \param[in] text Text string
460 * \param[in] len Only calculate width of the first len characters of the string. If not specified,
461 * then -1 is assumed which means calculate width of the whole string.
462 * \returns Returns pixel size of the text string
463 */
getFmWidth(const QFontMetrics & fm,const QString & text,int len)464 int getFmWidth(const QFontMetrics &fm, const QString &text, int len)
465 {
466 #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
467 return fm.horizontalAdvance(text, len);
468 #else
469 return fm.width(text, len);
470 #endif
471 }
472
473 /*!
474 * \brief Return the screen geometry for a given point
475 * \param[in] pos Position
476 * \returns The screen geometry at the given point
477 */
getAvailableGeometryAt(const QPoint & pos)478 QRect getAvailableGeometryAt(const QPoint &pos)
479 {
480 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
481 QScreen *pScreen = QGuiApplication::screenAt(pos);
482 if (pScreen == nullptr) {
483 return QRect();
484 }
485 return pScreen->availableGeometry();
486 #else
487 return QApplication::desktop()->availableGeometry(pos);
488 #endif
489 }
490
491
492 } // namespace UtilsUi
493