widgetutils.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. #include "widgetutils.h"
  2. #include <QGuiApplication>
  3. #include <QScreen>
  4. #include <QWidget>
  5. #include <QStyle>
  6. #include <QAbstractScrollArea>
  7. #include <QScrollBar>
  8. #include <QApplication>
  9. #include <QDesktopServices>
  10. #include <QKeyEvent>
  11. #include <QActionGroup>
  12. #include <QAction>
  13. #include <QKeySequence>
  14. #include <QScrollArea>
  15. #include <QTimer>
  16. #include <QShortcut>
  17. #include <QListView>
  18. #include <QModelIndex>
  19. #include <QFontDatabase>
  20. #include <QMenu>
  21. #include <QDebug>
  22. #include <QLineEdit>
  23. #include <QLayout>
  24. #include <QPushButton>
  25. #include <QSplitter>
  26. #include <QFormLayout>
  27. #include <core/global.h>
  28. #include <widgets/messageboxhelper.h>
  29. #include <widgets/mainwindow.h>
  30. using namespace vnotex;
  31. void WidgetUtils::setPropertyDynamically(QWidget *p_widget,
  32. const char *p_prop,
  33. const QVariant &p_val)
  34. {
  35. p_widget->setProperty(p_prop, p_val);
  36. updateStyle(p_widget);
  37. }
  38. void WidgetUtils::updateStyle(QWidget *p_widget)
  39. {
  40. p_widget->style()->unpolish(p_widget);
  41. p_widget->style()->polish(p_widget);
  42. p_widget->update();
  43. }
  44. qreal WidgetUtils::calculateScaleFactor(const QScreen *p_screen)
  45. {
  46. static qreal factor = -1;
  47. if (factor < 0 || p_screen) {
  48. auto screen = p_screen ? p_screen : QGuiApplication::primaryScreen();
  49. factor = screen->devicePixelRatio();
  50. qDebug() << screen->name() << "dpi" << factor;
  51. }
  52. return factor;
  53. }
  54. bool WidgetUtils::isScrollBarVisible(QAbstractScrollArea *p_widget, bool p_horizontal)
  55. {
  56. auto scrollBar = p_horizontal ? p_widget->horizontalScrollBar() : p_widget->verticalScrollBar();
  57. if (scrollBar && scrollBar->isVisible() && scrollBar->minimum() != scrollBar->maximum()) {
  58. return true;
  59. }
  60. return false;
  61. }
  62. QSize WidgetUtils::availableScreenSize(QWidget *p_widget)
  63. {
  64. return p_widget->screen()->availableGeometry().size();
  65. }
  66. void WidgetUtils::openUrlByDesktop(const QUrl &p_url)
  67. {
  68. const auto scheme = p_url.scheme();
  69. if (scheme != "http" && scheme != "https") {
  70. // Prompt for user.
  71. int ret = MessageBoxHelper::questionYesNo(MessageBoxHelper::Warning,
  72. MainWindow::tr("Are you sure to open link (%1)?").arg(p_url.toString()),
  73. MainWindow::tr("Malicious link might do harm to your device."),
  74. QString(),
  75. nullptr);
  76. if (ret == QMessageBox::No) {
  77. return;
  78. }
  79. }
  80. QDesktopServices::openUrl(p_url);
  81. }
  82. bool WidgetUtils::processKeyEventLikeVi(QWidget *p_widget,
  83. QKeyEvent *p_event,
  84. QWidget *p_escTargetWidget)
  85. {
  86. Q_ASSERT(p_widget);
  87. bool eventHandled = false;
  88. int key = p_event->key();
  89. int modifiers = p_event->modifiers();
  90. if (!p_escTargetWidget) {
  91. p_escTargetWidget = p_widget;
  92. }
  93. switch (key) {
  94. case Qt::Key_BracketLeft:
  95. {
  96. if (isViControlModifier(modifiers)) {
  97. auto escEvent = new QKeyEvent(QEvent::KeyPress,
  98. Qt::Key_Escape,
  99. Qt::NoModifier);
  100. QCoreApplication::postEvent(p_escTargetWidget, escEvent);
  101. eventHandled = true;
  102. }
  103. break;
  104. }
  105. case Qt::Key_J:
  106. {
  107. if (isViControlModifier(modifiers)) {
  108. // The event must be allocated on the heap since the post event queue will take ownership
  109. // of the event and delete it once it has been posted.
  110. auto downEvent = new QKeyEvent(QEvent::KeyPress,
  111. Qt::Key_Down,
  112. Qt::NoModifier);
  113. QCoreApplication::postEvent(p_widget, downEvent);
  114. eventHandled = true;
  115. }
  116. break;
  117. }
  118. case Qt::Key_K:
  119. {
  120. if (isViControlModifier(modifiers)) {
  121. auto upEvent = new QKeyEvent(QEvent::KeyPress,
  122. Qt::Key_Up,
  123. Qt::NoModifier);
  124. QCoreApplication::postEvent(p_widget, upEvent);
  125. eventHandled = true;
  126. }
  127. break;
  128. }
  129. case Qt::Key_H:
  130. {
  131. if (isViControlModifier(modifiers)) {
  132. auto upEvent = new QKeyEvent(QEvent::KeyPress,
  133. Qt::Key_Left,
  134. Qt::NoModifier);
  135. QCoreApplication::postEvent(p_widget, upEvent);
  136. eventHandled = true;
  137. }
  138. break;
  139. }
  140. case Qt::Key_L:
  141. {
  142. if (isViControlModifier(modifiers)) {
  143. auto upEvent = new QKeyEvent(QEvent::KeyPress,
  144. Qt::Key_Right,
  145. Qt::NoModifier);
  146. QCoreApplication::postEvent(p_widget, upEvent);
  147. eventHandled = true;
  148. }
  149. break;
  150. }
  151. default:
  152. break;
  153. }
  154. if (eventHandled) {
  155. p_event->accept();
  156. }
  157. return eventHandled;
  158. }
  159. bool WidgetUtils::isViControlModifier(int p_modifiers)
  160. {
  161. #if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
  162. return p_modifiers == Qt::MetaModifier;
  163. #else
  164. return p_modifiers == Qt::ControlModifier;
  165. #endif
  166. }
  167. void WidgetUtils::clearActionGroup(QActionGroup *p_actGroup)
  168. {
  169. auto actions = p_actGroup->actions();
  170. for (auto action : actions) {
  171. p_actGroup->removeAction(action);
  172. }
  173. }
  174. void WidgetUtils::addActionShortcut(QAction *p_action,
  175. const QString &p_shortcut,
  176. Qt::ShortcutContext p_context)
  177. {
  178. QKeySequence kseq(p_shortcut);
  179. if (kseq.isEmpty()) {
  180. return;
  181. }
  182. p_action->setShortcut(kseq);
  183. p_action->setShortcutContext(p_context);
  184. p_action->setText(QString("%1\t%2").arg(p_action->text(), kseq.toString(QKeySequence::NativeText)));
  185. }
  186. void WidgetUtils::addActionShortcutText(QAction *p_action, const QString &p_shortcut)
  187. {
  188. if (p_shortcut.isEmpty()) {
  189. return;
  190. }
  191. QKeySequence kseq(p_shortcut);
  192. if (kseq.isEmpty()) {
  193. return;
  194. }
  195. p_action->setText(QString("%1\t%2").arg(p_action->text(), kseq.toString(QKeySequence::NativeText)));
  196. }
  197. void WidgetUtils::addButtonShortcutText(QPushButton *p_button, const QString &p_shortcut)
  198. {
  199. if (p_shortcut.isEmpty()) {
  200. return;
  201. }
  202. QKeySequence kseq(p_shortcut);
  203. if (kseq.isEmpty()) {
  204. return;
  205. }
  206. p_button->setText(QString("%1 (%2)").arg(p_button->text(), kseq.toString(QKeySequence::NativeText)));
  207. }
  208. void WidgetUtils::updateSize(QWidget *p_widget)
  209. {
  210. p_widget->adjustSize();
  211. p_widget->updateGeometry();
  212. }
  213. void WidgetUtils::resizeToHideScrollBarLater(QScrollArea *p_scroll, bool p_vertical, bool p_horizontal)
  214. {
  215. QTimer::singleShot(200, p_scroll, [p_scroll, p_vertical, p_horizontal]() {
  216. WidgetUtils::resizeToHideScrollBar(p_scroll, p_vertical, p_horizontal);
  217. });
  218. }
  219. void WidgetUtils::resizeToHideScrollBar(QScrollArea *p_scroll, bool p_vertical, bool p_horizontal)
  220. {
  221. bool changed = false;
  222. auto parentWidget = p_scroll->parentWidget();
  223. if (p_horizontal && WidgetUtils::isScrollBarVisible(p_scroll, true)) {
  224. auto scrollBar = p_scroll->horizontalScrollBar();
  225. auto delta = scrollBar->maximum() - scrollBar->minimum();
  226. auto availableSize = WidgetUtils::availableScreenSize(p_scroll);
  227. if (parentWidget) {
  228. int newWidth = parentWidget->width() + delta;
  229. if (newWidth <= availableSize.width()) {
  230. changed = true;
  231. p_scroll->resize(p_scroll->width() + delta, p_scroll->height());
  232. auto geo = parentWidget->geometry();
  233. parentWidget->setGeometry(geo.x() - delta / 2,
  234. geo.y(),
  235. newWidth,
  236. geo.height());
  237. }
  238. } else {
  239. int newWidth = p_scroll->width() + delta;
  240. if (newWidth <= availableSize.width()) {
  241. changed = true;
  242. p_scroll->resize(newWidth, p_scroll->height());
  243. }
  244. }
  245. }
  246. if (p_vertical && WidgetUtils::isScrollBarVisible(p_scroll, false)) {
  247. auto scrollBar = p_scroll->verticalScrollBar();
  248. auto delta = scrollBar->maximum() - scrollBar->minimum();
  249. auto availableSize = WidgetUtils::availableScreenSize(p_scroll);
  250. if (parentWidget) {
  251. int newHeight = parentWidget->height() + delta;
  252. if (newHeight <= availableSize.height()) {
  253. changed = true;
  254. p_scroll->resize(p_scroll->width(), p_scroll->height() + delta);
  255. auto geo = parentWidget->geometry();
  256. parentWidget->setGeometry(geo.x(),
  257. geo.y() - delta / 2,
  258. geo.width(),
  259. newHeight);
  260. }
  261. } else {
  262. int newHeight = p_scroll->height() + delta;
  263. if (newHeight <= availableSize.height()) {
  264. changed = true;
  265. p_scroll->resize(p_scroll->width(), newHeight);
  266. }
  267. }
  268. }
  269. if (changed) {
  270. p_scroll->updateGeometry();
  271. }
  272. }
  273. QShortcut *WidgetUtils::createShortcut(const QString &p_shortcut,
  274. QWidget *p_widget,
  275. Qt::ShortcutContext p_context)
  276. {
  277. QKeySequence kseq(p_shortcut);
  278. if (kseq.isEmpty()) {
  279. return nullptr;
  280. }
  281. auto shortcut = new QShortcut(kseq, p_widget, nullptr, nullptr, p_context);
  282. if (shortcut->key().isEmpty()) {
  283. delete shortcut;
  284. return nullptr;
  285. }
  286. return shortcut;
  287. }
  288. bool WidgetUtils::isMetaKey(int p_key)
  289. {
  290. return p_key == Qt::Key_Control
  291. || p_key == Qt::Key_Shift
  292. || p_key == Qt::Key_Meta
  293. #if defined(Q_OS_LINUX)
  294. // For mapping Caps as Ctrl in KDE.
  295. || p_key == Qt::Key_CapsLock
  296. #endif
  297. || p_key == Qt::Key_Alt;
  298. }
  299. QVector<QModelIndex> WidgetUtils::getVisibleIndexes(const QListView *p_view)
  300. {
  301. QVector<QModelIndex> indexes;
  302. auto firstItem = p_view->indexAt(QPoint(0, 0));
  303. if (!firstItem.isValid()) {
  304. return indexes;
  305. }
  306. auto lastItem = p_view->indexAt(p_view->viewport()->rect().bottomLeft());
  307. int firstRow = firstItem.row();
  308. int lastRow = lastItem.isValid() ? lastItem.row() : (p_view->model()->rowCount() - 1);
  309. for (int i = firstRow; i <= lastRow; ++i) {
  310. if (p_view->isRowHidden(i)) {
  311. continue;
  312. }
  313. auto item = firstItem.siblingAtRow(i);
  314. if (item.isValid()) {
  315. indexes.append(item);
  316. }
  317. }
  318. return indexes;
  319. }
  320. QString WidgetUtils::getMonospaceFont()
  321. {
  322. static QString font;
  323. if (font.isNull()) {
  324. QStringList candidates;
  325. candidates << QStringLiteral("YaHei Consolas Hybrid")
  326. << QStringLiteral("Consolas")
  327. << QStringLiteral("Monaco")
  328. << QStringLiteral("Andale Mono")
  329. << QStringLiteral("Monospace")
  330. << QStringLiteral("Courier New");
  331. auto availFamilies = QFontDatabase().families();
  332. for (const auto &candidate : candidates) {
  333. QString family = candidate.trimmed().toLower();
  334. for (auto availFamily : availFamilies) {
  335. availFamily.remove(QRegularExpression("\\[.*\\]"));
  336. if (family == availFamily.trimmed().toLower()) {
  337. font = availFamily;
  338. return font;
  339. }
  340. }
  341. }
  342. // Fallback to current font.
  343. font = QFont().family();
  344. }
  345. return font;
  346. }
  347. QAction *WidgetUtils::findActionByObjectName(const QList<QAction *> &p_actions, const QString &p_objName)
  348. {
  349. for (auto act : p_actions) {
  350. if (act->objectName() == p_objName) {
  351. return act;
  352. }
  353. }
  354. return nullptr;
  355. }
  356. // Insert @p_action into @p_menu after action @p_after.
  357. void WidgetUtils::insertActionAfter(QMenu *p_menu, QAction *p_after, QAction *p_action)
  358. {
  359. p_menu->insertAction(p_after, p_action);
  360. if (p_after) {
  361. p_menu->removeAction(p_after);
  362. p_menu->insertAction(p_action, p_after);
  363. }
  364. }
  365. void WidgetUtils::selectBaseName(QLineEdit *p_lineEdit)
  366. {
  367. auto text = p_lineEdit->text();
  368. int dotIndex = text.lastIndexOf(QLatin1Char('.'));
  369. p_lineEdit->setSelection(0, (dotIndex == -1) ? text.size() : dotIndex);
  370. }
  371. void WidgetUtils::setContentsMargins(QLayout *p_layout)
  372. {
  373. // Use 0 bottom margin to align dock widgets with the content area.
  374. p_layout->setContentsMargins(CONTENTS_MARGIN, CONTENTS_MARGIN, CONTENTS_MARGIN, 0);
  375. }
  376. bool WidgetUtils::distributeWidgetsOfSplitter(QSplitter *p_splitter)
  377. {
  378. if (!p_splitter) {
  379. return false;
  380. }
  381. if (p_splitter->count() == 0) {
  382. return false;
  383. } else if (p_splitter->count() == 1) {
  384. return true;
  385. }
  386. auto sizes = p_splitter->sizes();
  387. int totalWidth = 0;
  388. for (auto sz : sizes) {
  389. totalWidth += sz;
  390. }
  391. int newWidth = totalWidth / sizes.size();
  392. if (newWidth == 0) {
  393. return false;
  394. }
  395. bool changed = false;
  396. for (int i = 0; i < sizes.size(); ++i) {
  397. if (sizes[i] != newWidth) {
  398. sizes[i] = newWidth;
  399. changed = true;
  400. }
  401. }
  402. if (changed) {
  403. p_splitter->setSizes(sizes);
  404. return true;
  405. }
  406. return false;
  407. }
  408. void WidgetUtils::clearLayout(QFormLayout *p_layout)
  409. {
  410. for (int i = p_layout->rowCount() - 1; i >= 0; --i) {
  411. p_layout->removeRow(i);
  412. }
  413. }
  414. // Different from QWidget::isAncestorOf(): unnecessary to be within the same window.
  415. bool WidgetUtils::isOrAncestorOf(const QWidget *p_widget, const QWidget *p_child)
  416. {
  417. Q_ASSERT(p_widget);
  418. if (!p_child) {
  419. return false;
  420. }
  421. const QWidget *pa = p_child;
  422. while (pa) {
  423. if (pa == p_widget) {
  424. return true;
  425. }
  426. pa = pa->parentWidget();
  427. }
  428. return false;
  429. }