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&nbsp;&nbsp;<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