Menu.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873
  1. #include "Menu.h"
  2. #include "Common/StyleSheet.h"
  3. #include "Common/Icon.h"
  4. #include "Common/SmoothScroll.h"
  5. #include "QFluentWidgets.h"
  6. #include <QApplication>
  7. #include <QClipboard>
  8. #include <QMimeData>
  9. #include <QScreen>
  10. #include <QEvent>
  11. #include <QPainter>
  12. #include <QTimer>
  13. #include <QDebug>
  14. #include <QMouseEvent>
  15. CustomMenuStyle::CustomMenuStyle(int iconSize) : QProxyStyle(), m_iconSize(iconSize) { }
  16. int CustomMenuStyle::pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const
  17. {
  18. if (metric == QStyle::PM_SmallIconSize) {
  19. return m_iconSize;
  20. }
  21. return QProxyStyle::pixelMetric(metric, option, widget);
  22. }
  23. DWMMenu::DWMMenu(const QString &title, QWidget *parent) : QMenu(title, parent), m_customMenuStyle(new CustomMenuStyle)
  24. {
  25. // WindowEffect 阴影效果
  26. setWindowFlags(Qt::FramelessWindowHint | Qt::Popup | Qt::NoDropShadowWindowHint);
  27. setAttribute(Qt::WA_StyledBackground);
  28. setStyle(m_customMenuStyle.data());
  29. FluentStyleSheet::apply("MENU", this);
  30. }
  31. bool DWMMenu::event(QEvent *event)
  32. {
  33. if (event->type() == QEvent::WinIdChange) {
  34. // windowEffect.addMenuShadowEffect(self.winId())
  35. }
  36. return QMenu::event(event);
  37. }
  38. MenuSeparator::MenuSeparator(QWidget *parent) : QWidget(parent)
  39. {
  40. setFixedHeight(9);
  41. }
  42. void MenuSeparator::paintEvent(QPaintEvent * /*event*/)
  43. {
  44. QPainter painter(this);
  45. int c = QFWIns.isDarkTheme() ? 255 : 0;
  46. QPen pen(QColor(c, c, c, 25), 1);
  47. pen.setCosmetic(true);
  48. painter.setPen(pen);
  49. painter.drawLine(0, 4, width(), 4);
  50. }
  51. /**
  52. * @brief Round corner menu
  53. * @param parent
  54. */
  55. RoundMenu::RoundMenu(const QString &title, QWidget *parent)
  56. : QWidget(parent),
  57. m_title(title),
  58. m_isSubMenu(false),
  59. m_parentMenu(nullptr),
  60. m_menuItem(nullptr),
  61. m_lastHoverItem(nullptr),
  62. m_lastHoverSubMenuItem(nullptr),
  63. m_isHideBySystem(true),
  64. m_itemHeight(28),
  65. m_hBoxLayout(new QHBoxLayout(this)),
  66. m_view(new MenuActionListWidget(this)),
  67. m_ani(new QPropertyAnimation(this, "pos", this)),
  68. m_timer(new QTimer(this)),
  69. m_shadowEffect(nullptr),
  70. m_eventLoop(nullptr)
  71. {
  72. initWidgets();
  73. }
  74. /// add shadow to dialog
  75. void RoundMenu::setShadowEffect(qreal blurRadius, const QPointF &offset, const QColor &color)
  76. {
  77. m_shadowEffect = new QGraphicsDropShadowEffect(m_view);
  78. m_shadowEffect->setBlurRadius(blurRadius);
  79. m_shadowEffect->setOffset(offset);
  80. m_shadowEffect->setColor(color);
  81. m_view->setGraphicsEffect(nullptr);
  82. m_view->setGraphicsEffect(m_shadowEffect);
  83. }
  84. void RoundMenu::initWidgets()
  85. {
  86. setWindowFlags(Qt::Popup | Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint);
  87. setAttribute(Qt::WA_TranslucentBackground);
  88. setMouseTracking(true);
  89. m_timer->setSingleShot(true);
  90. m_timer->setInterval(400);
  91. connect(m_timer, &QTimer::timeout, this, &RoundMenu::onShowMenuTimeOut);
  92. setShadowEffect();
  93. m_hBoxLayout->setContentsMargins(12, 8, 12, 20);
  94. m_hBoxLayout->addWidget(m_view, 1, Qt::AlignCenter);
  95. setLayout(m_hBoxLayout);
  96. FluentStyleSheet::apply("MENU", this);
  97. connect(m_view, &MenuActionListWidget::itemClicked, this, &RoundMenu::onItemClicked);
  98. connect(m_view, &MenuActionListWidget::itemEntered, this, &RoundMenu::onItemEntered);
  99. connect(m_ani, &QPropertyAnimation::valueChanged, this, &RoundMenu::onSlideValueChanged);
  100. }
  101. QString RoundMenu::title() const
  102. {
  103. return m_title;
  104. }
  105. QIcon RoundMenu::icon() const
  106. {
  107. return m_icon;
  108. }
  109. void RoundMenu::setIcon(const QIcon &icon)
  110. {
  111. m_icon = icon;
  112. }
  113. /// clear all actions
  114. void RoundMenu::clear()
  115. {
  116. while (!m_actions.isEmpty()) {
  117. removeAction(m_actions.last());
  118. }
  119. }
  120. /// remove action from menu
  121. void RoundMenu::removeAction(QAction *action)
  122. {
  123. if (!m_actions.contains(action)) {
  124. return;
  125. }
  126. int index = m_actions.indexOf(action);
  127. m_actions.removeAt(index);
  128. action->setProperty("item", 0);
  129. QListWidgetItem *item = m_view->takeItem(index);
  130. item->setData(Qt::UserRole, 0);
  131. // delete widget
  132. QWidget *widget = m_view->itemWidget(item);
  133. if (widget) {
  134. widget->deleteLater();
  135. widget = nullptr;
  136. }
  137. delete item;
  138. action->deleteLater();
  139. }
  140. /// add action to menu
  141. void RoundMenu::addAction(QAction *const action)
  142. {
  143. QListWidgetItem *item = createActionItem(action);
  144. if (!item) {
  145. return;
  146. }
  147. m_view->addItem(item);
  148. adjustSize();
  149. }
  150. void RoundMenu::insertAction(QAction *const before, QAction *const action)
  151. {
  152. if (!m_actions.contains(before)) {
  153. return;
  154. }
  155. QListWidgetItem *beforeItem = before->property("item").value<QListWidgetItem *>();
  156. if (!beforeItem) {
  157. return;
  158. }
  159. int index = m_view->row(beforeItem);
  160. QListWidgetItem *item = createActionItem(action, before);
  161. m_view->insertItem(index, item);
  162. adjustSize();
  163. }
  164. void RoundMenu::addActions(const QList<QAction *> &actions)
  165. {
  166. for (auto a : actions) {
  167. addAction(a);
  168. }
  169. }
  170. void RoundMenu::insertActions(QAction *const before, const QList<QAction *> &actions)
  171. {
  172. for (auto a : actions) {
  173. insertAction(before, a);
  174. }
  175. }
  176. void RoundMenu::setDefaultAction(QAction *const action)
  177. {
  178. if (!m_actions.contains(action)) {
  179. return;
  180. }
  181. int index = m_actions.indexOf(action);
  182. m_view->setCurrentRow(index);
  183. }
  184. void RoundMenu::addMenu(RoundMenu *const menu)
  185. {
  186. if (!menu) {
  187. return;
  188. }
  189. auto itemW = createSubMenuItem(menu);
  190. if (itemW.first == nullptr || itemW.second == nullptr) {
  191. return;
  192. }
  193. m_view->addItem(itemW.first);
  194. m_view->setItemWidget(itemW.first, itemW.second);
  195. this->adjustSize();
  196. }
  197. void RoundMenu::insertMenu(QAction *const before, RoundMenu *const menu)
  198. {
  199. if (!menu) {
  200. qCritical() << "`menu` should be an instance of `RoundMenu`.";
  201. return;
  202. }
  203. if (!m_actions.contains(before)) {
  204. qCritical() << "`before` should be in menu action list";
  205. return;
  206. }
  207. auto itemW = createSubMenuItem(menu);
  208. if (itemW.first == nullptr || itemW.second == nullptr) {
  209. return;
  210. }
  211. m_view->insertItem(m_view->row(before->property("item").value<QListWidgetItem *>()), itemW.first);
  212. m_view->setItemWidget(itemW.first, itemW.second);
  213. this->adjustSize();
  214. }
  215. void RoundMenu::setItemHeight(int itemHeight)
  216. {
  217. if (m_itemHeight == itemHeight) {
  218. return;
  219. }
  220. m_itemHeight = itemHeight;
  221. m_view->setItemHeight(itemHeight);
  222. }
  223. void RoundMenu::adjustSize()
  224. {
  225. QMargins m = this->layout()->contentsMargins();
  226. int w = m_view->width() + m.left() + m.right();
  227. int h = m_view->height() + m.top() + m.bottom();
  228. setFixedSize(w, h);
  229. }
  230. void RoundMenu::setParentMenu(RoundMenu *parent, QListWidgetItem *item)
  231. {
  232. m_parentMenu = parent;
  233. m_menuItem = item;
  234. m_isSubMenu = parent == nullptr ? false : true;
  235. }
  236. QListWidgetItem *RoundMenu::createActionItem(QAction *const action, QAction *const before)
  237. {
  238. if (!before) {
  239. m_actions.append(action);
  240. } else if (m_actions.contains(before)) {
  241. int index = m_actions.indexOf(before);
  242. m_actions.insert(index, action);
  243. } else {
  244. qCritical() << QString("`before` is not in the action list");
  245. return nullptr;
  246. }
  247. QListWidgetItem *item = new QListWidgetItem(createItemIcon(action), action->text());
  248. int w = 0;
  249. if (!hasItemIcon()) {
  250. w = 28 + m_view->fontMetrics().width(action->text());
  251. } else {
  252. // add a blank character to increase space between icon and text
  253. item->setText(" " + item->text());
  254. w = 60 + m_view->fontMetrics().width(item->text());
  255. }
  256. item->setSizeHint(QSize(w, m_itemHeight));
  257. item->setData(Qt::UserRole, QVariant::fromValue<QAction *>(action));
  258. action->setProperty("item", QVariant::fromValue<QListWidgetItem *>(item));
  259. connect(action, &QAction::changed, this, &RoundMenu::onActionChanged);
  260. return item;
  261. }
  262. bool RoundMenu::hasItemIcon()
  263. {
  264. for (auto a : m_actions) {
  265. if (!a->icon().isNull()) {
  266. return true;
  267. }
  268. }
  269. for (auto m : m_subMenus) {
  270. if (!m->icon().isNull()) {
  271. return true;
  272. }
  273. }
  274. return false;
  275. }
  276. QIcon RoundMenu::createItemIcon(QAction *const action)
  277. {
  278. bool hasIcon = hasItemIcon();
  279. if (hasIcon && action->icon().isNull()) {
  280. QPixmap pixmap(m_view->iconSize());
  281. pixmap.fill(Qt::transparent);
  282. return QIcon(pixmap);
  283. } else if (!hasIcon) {
  284. return QIcon();
  285. }
  286. QIcon icon(new MenuIconEngine(action->icon()));
  287. return icon;
  288. }
  289. QIcon RoundMenu::createItemIcon(RoundMenu *const menu)
  290. {
  291. bool hasIcon = hasItemIcon();
  292. if (hasIcon && menu->icon().isNull()) {
  293. QPixmap pixmap(m_view->iconSize());
  294. pixmap.fill(Qt::transparent);
  295. return QIcon(pixmap);
  296. } else if (!hasIcon) {
  297. return QIcon();
  298. }
  299. QIcon icon(new MenuIconEngine(menu->icon()));
  300. return icon;
  301. }
  302. QPair<QListWidgetItem *, SubMenuItemWidget *> RoundMenu::createSubMenuItem(RoundMenu *const menu)
  303. {
  304. if (!menu) {
  305. return QPair<QListWidgetItem *, SubMenuItemWidget *>(nullptr, nullptr);
  306. }
  307. m_subMenus.append(menu);
  308. QListWidgetItem *item = new QListWidgetItem(createItemIcon(menu), menu->title());
  309. int w = 0;
  310. if (!hasItemIcon()) {
  311. w = 48 + m_view->fontMetrics().width(menu->title());
  312. } else {
  313. // add a blank character to increase space between icon and text
  314. item->setText(" " + item->text());
  315. w = 60 + m_view->fontMetrics().width(item->text());
  316. }
  317. // add submenu item
  318. menu->setParentMenu(this, item);
  319. item->setSizeHint(QSize(w, m_itemHeight));
  320. item->setData(Qt::UserRole, QVariant::fromValue<RoundMenu *>(menu));
  321. SubMenuItemWidget *subw = new SubMenuItemWidget(menu, item, this);
  322. connect(subw, &SubMenuItemWidget::showMenuSig, this, &RoundMenu::showSubMenu);
  323. subw->resize(item->sizeHint());
  324. return QPair<QListWidgetItem *, SubMenuItemWidget *>(item, subw);
  325. }
  326. void RoundMenu::hideMenu(bool hideBySystem)
  327. {
  328. m_isHideBySystem = hideBySystem;
  329. m_view->clearSelection();
  330. if (m_isSubMenu) {
  331. hide();
  332. } else {
  333. close();
  334. }
  335. }
  336. void RoundMenu::closeParentMenu()
  337. {
  338. RoundMenu *menu = this;
  339. while (menu->m_parentMenu) {
  340. menu->close();
  341. menu = menu->parentMenu();
  342. }
  343. menu->close();
  344. }
  345. void RoundMenu::mouseMoveEvent(QMouseEvent *event)
  346. {
  347. if (!m_isSubMenu) {
  348. return;
  349. }
  350. // hide submenu when mouse moves out of submenu item
  351. QPoint pos = event->globalPos();
  352. MenuActionListWidget *view = m_parentMenu->view();
  353. // get the rect of menu item
  354. QMargins margin = view->viewportMargins();
  355. QRect rect = view->visualItemRect(m_menuItem).translated(view->mapToGlobal(QPoint()));
  356. rect = rect.translated(margin.left(), margin.top() + 2);
  357. if (m_parentMenu->geometry().contains(pos) && !rect.contains(pos) && !this->geometry().contains(pos)) {
  358. view->clearSelection();
  359. hideMenu(false);
  360. // update style
  361. int index = view->row(m_menuItem);
  362. if (index > 0) {
  363. view->item(index - 1)->setFlags(Qt::ItemIsEnabled);
  364. }
  365. if (index < view->count() - 1) {
  366. view->item(index + 1)->setFlags(Qt::ItemIsEnabled);
  367. }
  368. }
  369. }
  370. void RoundMenu::closeEvent(QCloseEvent *event)
  371. {
  372. // 关闭窗口时结束事件循环,在exec()方法中返回选择结果;
  373. if (m_eventLoop != nullptr) {
  374. m_eventLoop->exit();
  375. }
  376. event->accept();
  377. emit closedSignal();
  378. }
  379. void RoundMenu::hideEvent(QHideEvent *event)
  380. {
  381. if (m_isHideBySystem && m_isSubMenu) {
  382. closeParentMenu();
  383. }
  384. m_isHideBySystem = true;
  385. event->accept();
  386. }
  387. void RoundMenu::onShowMenuTimeOut()
  388. {
  389. if ((m_lastHoverSubMenuItem == nullptr) || (m_lastHoverItem == m_lastHoverSubMenuItem)) {
  390. return;
  391. }
  392. SubMenuItemWidget *sub = qobject_cast<SubMenuItemWidget *>(m_view->itemWidget(m_lastHoverSubMenuItem));
  393. RoundMenu *w = sub->roundMenu();
  394. if (w && w->parentMenu()->isHidden()) {
  395. return;
  396. }
  397. QPoint pos = w->mapToGlobal(QPoint(w->width() + 5, -5));
  398. w->exec(pos);
  399. }
  400. void RoundMenu::onItemClicked(QListWidgetItem *item)
  401. {
  402. QAction *action = item->data(Qt::UserRole).value<QAction *>();
  403. if (!m_actions.contains(action)) {
  404. return;
  405. }
  406. hideMenu();
  407. if (!m_isSubMenu) {
  408. action->trigger();
  409. return;
  410. }
  411. // close parent menu
  412. closeParentMenu();
  413. action->trigger();
  414. }
  415. void RoundMenu::onItemEntered(QListWidgetItem *item)
  416. {
  417. m_lastHoverItem = item;
  418. RoundMenu *menu = item->data(Qt::UserRole).value<RoundMenu *>();
  419. if (!menu) {
  420. return;
  421. }
  422. showSubMenu(item);
  423. }
  424. void RoundMenu::onSlideValueChanged(const QVariant &value)
  425. {
  426. QMargins m = this->layout()->contentsMargins();
  427. int w = m_view->width() + m.left() + m.right() + 120;
  428. int h = m_view->height() + m.top() + m.bottom() + 20;
  429. int y = m_ani->endValue().toPoint().y() - value.toPoint().y();
  430. this->setMask(QRegion(0, y, w, h));
  431. }
  432. void RoundMenu::onActionChanged()
  433. {
  434. QAction *action = static_cast<QAction *>(sender());
  435. QListWidgetItem *item = action->property("item").value<QListWidgetItem *>();
  436. item->setIcon(createItemIcon(action));
  437. int w = 0;
  438. if (!hasItemIcon()) {
  439. item->setText(action->text());
  440. w = 28 + m_view->fontMetrics().width(action->text());
  441. } else {
  442. // add a blank character to increase space between icon and text
  443. item->setText(" " + action->text());
  444. w = 60 + m_view->fontMetrics().width(item->text());
  445. }
  446. item->setSizeHint(QSize(w, m_itemHeight));
  447. m_view->adjustSize();
  448. this->adjustSize();
  449. }
  450. void RoundMenu::showSubMenu(QListWidgetItem *const item)
  451. {
  452. m_lastHoverItem = item;
  453. m_lastHoverSubMenuItem = item;
  454. // delay 400 ms to anti-shake
  455. m_timer->stop();
  456. m_timer->start();
  457. }
  458. QList<QAction *> RoundMenu::menuActions() const
  459. {
  460. return m_actions;
  461. }
  462. MenuActionListWidget *RoundMenu::view() const
  463. {
  464. return m_view;
  465. }
  466. void RoundMenu::addSeparator()
  467. {
  468. QMargins m = m_view->viewportMargins();
  469. int w = m_view->width() - m.left() - m.right();
  470. // icon separator
  471. MenuSeparator *separator = new MenuSeparator(m_view);
  472. separator->resize(w, separator->height());
  473. // add separator to list widget
  474. QListWidgetItem *item = new QListWidgetItem(m_view);
  475. item->setFlags(Qt::NoItemFlags);
  476. item->setSizeHint(QSize(w, separator->height()));
  477. m_view->addItem(item);
  478. m_view->setItemWidget(item, separator);
  479. this->adjustSize();
  480. }
  481. RoundMenu *RoundMenu::parentMenu() const
  482. {
  483. return m_parentMenu;
  484. }
  485. /**
  486. * @brief show menu
  487. * @param pos: pop-up position
  488. * @param ani: Whether to show pop-up animation
  489. */
  490. void RoundMenu::exec(const QPoint &pos, bool ani)
  491. {
  492. if (isVisible()) {
  493. return;
  494. }
  495. QPoint showPos;
  496. QRect rect = QApplication::screenAt(QCursor::pos())->availableGeometry();
  497. int w = width() + 5;
  498. int h = height() + 5;
  499. showPos.setX(qMin(pos.x() - layout()->contentsMargins().left(), rect.right() - w));
  500. showPos.setY(qMin(pos.y() - 4, rect.bottom() - h));
  501. if (ani) {
  502. m_ani->setStartValue(showPos - QPoint(0, int(h / 2)));
  503. m_ani->setEndValue(pos);
  504. m_ani->setDuration(250);
  505. m_ani->setEasingCurve(QEasingCurve::OutQuad);
  506. m_ani->start();
  507. } else {
  508. move(showPos);
  509. }
  510. this->setWindowModality(Qt::ApplicationModal); // 设置为模态状态
  511. show();
  512. if (m_isSubMenu) {
  513. m_menuItem->setSelected(true);
  514. // temporarily disable item to change style
  515. MenuActionListWidget *view = m_parentMenu->view();
  516. int index = view->row(m_menuItem);
  517. if (index > 0) {
  518. view->item(index - 1)->setFlags(Qt::NoItemFlags);
  519. }
  520. if (index < view->count() - 1) {
  521. view->item(index + 1)->setFlags(Qt::NoItemFlags);
  522. }
  523. }
  524. // 使用事件循环QEventLoop
  525. // ,不让exec()方法结束,在用户选择确定或者取消后,关闭窗口结束事件循环,并返回最后用户选择的结果;
  526. // 根据返回结果得到用户按下了确定还是取消,采取相应的操作。从而模拟出QDialog类的exec()方法;
  527. m_eventLoop = new QEventLoop(this);
  528. m_eventLoop->exec();
  529. }
  530. SubMenuItemWidget::SubMenuItemWidget(QMenu *menu, QListWidgetItem *item, QWidget *parent)
  531. : QWidget(parent), m_menu(menu), m_roundMenu(nullptr), m_item(item)
  532. {
  533. }
  534. SubMenuItemWidget::SubMenuItemWidget(RoundMenu *menu, QListWidgetItem *item, QWidget *parent)
  535. : QWidget(parent), m_menu(nullptr), m_roundMenu(menu), m_item(item)
  536. {
  537. }
  538. void SubMenuItemWidget::enterEvent(QEvent *event)
  539. {
  540. QWidget::enterEvent(event);
  541. emit showMenuSig(m_item);
  542. }
  543. void SubMenuItemWidget::paintEvent(QPaintEvent * /*event*/)
  544. {
  545. QPainter painter(this);
  546. painter.setRenderHints(QPainter::Antialiasing);
  547. // draw right arrow
  548. // NOTE: 未完成
  549. QScopedPointer<FluentIcon> ficon(NEWFLICON(FluentIcon, CHEVRON_RIGHT));
  550. ficon->render(&painter, QRect(width() - 10, height() / 2 - 9 / 2, 9, 9));
  551. }
  552. RoundMenu *SubMenuItemWidget::roundMenu() const
  553. {
  554. return m_roundMenu;
  555. }
  556. MenuActionListWidget::MenuActionListWidget(QWidget *parent) : QListWidget(parent)
  557. {
  558. setViewportMargins(5, 6, 5, 6);
  559. setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  560. setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  561. setTextElideMode(Qt::ElideNone);
  562. setDragEnabled(false);
  563. setMouseTracking(true);
  564. setVerticalScrollMode(ScrollPerPixel);
  565. setIconSize(QSize(14, 14));
  566. m_smoothScroll.reset(new SmoothScroll(this, Qt::Vertical));
  567. setStyleSheet("MenuActionListWidget{font: 14px \"Segoe UI\", \"Microsoft YaHei\"}");
  568. }
  569. void MenuActionListWidget::insertItem(int row, QListWidgetItem *item)
  570. {
  571. QListWidget::insertItem(row, item);
  572. adjustSize();
  573. }
  574. void MenuActionListWidget::addItem(QListWidgetItem *item)
  575. {
  576. QListWidget::addItem(item);
  577. adjustSize();
  578. }
  579. QListWidgetItem *MenuActionListWidget::takeItem(int row)
  580. {
  581. QListWidgetItem *item = QListWidget::takeItem(row);
  582. adjustSize();
  583. return item;
  584. }
  585. void MenuActionListWidget::adjustSize()
  586. {
  587. QSize size;
  588. for (int i = 0; i < this->count(); i++) {
  589. QSize s = this->item(i)->sizeHint();
  590. size.setWidth(qMax(s.width(), size.width()));
  591. size.setHeight(size.height() + s.height());
  592. }
  593. // adjust the height of viewport
  594. QSize ss = QApplication::screenAt(QCursor::pos())->availableSize();
  595. int w = ss.width() - 100;
  596. int h = ss.height() - 100;
  597. QSize vSize(qMin(w - 12, size.width()), qMin(h - 12, size.height()));
  598. viewport()->adjustSize();
  599. // adjust the height of list widget
  600. QMargins m = viewportMargins();
  601. size += QSize(m.left() + m.right() + 2, m.top() + m.bottom());
  602. size.setHeight(qMin(h, size.height() + 3));
  603. size.setWidth(qMin(w, size.width()));
  604. setFixedSize(size);
  605. }
  606. void MenuActionListWidget::setItemHeight(int height)
  607. {
  608. for (int i = 0; i < this->count(); ++i) {
  609. QListWidgetItem *item = this->item(i);
  610. item->setSizeHint(QSize(item->sizeHint().width(), height));
  611. }
  612. this->adjustSize();
  613. }
  614. void MenuActionListWidget::wheelEvent(QWheelEvent *event)
  615. {
  616. // only process the wheel events triggered by mouse
  617. if ((m_smoothScroll->smoothMode() == SmoothMode::NO_SMOOTH) || (abs(event->angleDelta().y()) % 120 != 0)) {
  618. QListWidget::wheelEvent(event);
  619. } else {
  620. m_smoothScroll->wheelEvent(event);
  621. }
  622. }
  623. EditMenu::EditMenu(QWidget *parent)
  624. : RoundMenu("", parent),
  625. cutAct(nullptr),
  626. copyAct(nullptr),
  627. pasteAct(nullptr),
  628. cancelAct(nullptr),
  629. selectAllAct(nullptr)
  630. {
  631. createActions();
  632. }
  633. void EditMenu::createActions()
  634. {
  635. cutAct = new QAction(QIcon(":/qfluentwidgets/images/icons/Cut_black.svg"), tr("Cut"), this);
  636. cutAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_X));
  637. copyAct = new QAction(QIcon(":/qfluentwidgets/images/icons/Copy_black.svg"), tr("Copy"), this);
  638. copyAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_C));
  639. pasteAct = new QAction(QIcon(":/qfluentwidgets/images/icons/Paste_black.svg"), tr("Paste"), this);
  640. pasteAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_V));
  641. cancelAct = new QAction(QIcon(":/qfluentwidgets/images/icons/Cancel_black.svg"), tr("Cancel"), this);
  642. cancelAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Z));
  643. selectAllAct = new QAction(tr("Select all"), this);
  644. selectAllAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_A));
  645. actionList << cutAct << copyAct << pasteAct << cancelAct << selectAllAct;
  646. }
  647. void EditMenu::exec(const QPoint &pos, bool ani)
  648. {
  649. // clear();
  650. // createActions();
  651. if (QApplication::clipboard()->mimeData()->hasText()) {
  652. if (!parentText().isEmpty()) {
  653. if (!parentSelectedText().isEmpty()) {
  654. addActions(actionList);
  655. } else {
  656. for (int i = 2; i < actionList.count(); ++i) {
  657. addAction(actionList.at(i));
  658. }
  659. }
  660. } else {
  661. addAction(pasteAct);
  662. }
  663. } else {
  664. if (!parentText().isEmpty()) {
  665. if (!parentSelectedText().isEmpty()) {
  666. for (int i = 0; i < actionList.count(); ++i) {
  667. if (i != 2) {
  668. addAction(actionList.at(i));
  669. }
  670. }
  671. } else {
  672. for (int i = 3; i < actionList.count(); ++i) {
  673. addAction(actionList.at(i));
  674. }
  675. }
  676. } else {
  677. return;
  678. }
  679. }
  680. RoundMenu::exec(pos, ani);
  681. }
  682. LineEditMenu::LineEditMenu(QLineEdit *parent) : EditMenu(parent)
  683. {
  684. m_selectionStart = parent->selectionStart();
  685. m_selectionLength = parent->selectionLength();
  686. }
  687. void LineEditMenu::onItemClicked(QListWidgetItem *item)
  688. {
  689. QLineEdit *p = qobject_cast<QLineEdit *>(parent());
  690. if (m_selectionStart >= 0) {
  691. p->setSelection(m_selectionStart, m_selectionLength);
  692. }
  693. EditMenu::onItemClicked(item);
  694. }
  695. QString LineEditMenu::parentText()
  696. {
  697. QLineEdit *p = qobject_cast<QLineEdit *>(parent());
  698. return p->text();
  699. }
  700. QString LineEditMenu::parentSelectedText()
  701. {
  702. QLineEdit *p = qobject_cast<QLineEdit *>(parent());
  703. return p->selectedText();
  704. }
  705. TextEditMenu::TextEditMenu(QTextEdit *parent) : EditMenu(parent), m_textEdit(parent), m_plainTextEdit(nullptr)
  706. {
  707. m_selectionStart = parent->textCursor().selectionStart();
  708. m_selectionLength = parent->textCursor().selectionEnd() - parent->textCursor().selectionStart() + 1;
  709. }
  710. TextEditMenu::TextEditMenu(QPlainTextEdit *parent) : EditMenu(parent), m_textEdit(nullptr), m_plainTextEdit(parent)
  711. {
  712. m_selectionStart = parent->textCursor().selectionStart();
  713. m_selectionLength = parent->textCursor().selectionEnd() - parent->textCursor().selectionStart() + 1;
  714. }
  715. void TextEditMenu::onItemClicked(QListWidgetItem *item)
  716. {
  717. if (m_selectionStart >= 0) {
  718. QTextCursor cursor;
  719. if (m_textEdit) {
  720. cursor = m_textEdit->textCursor();
  721. } else {
  722. cursor = m_plainTextEdit->textCursor();
  723. }
  724. cursor.setPosition(m_selectionStart);
  725. cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, m_selectionLength);
  726. }
  727. EditMenu::onItemClicked(item);
  728. }
  729. QString TextEditMenu::parentText()
  730. {
  731. if (m_textEdit) {
  732. return m_textEdit->toPlainText();
  733. } else {
  734. return m_plainTextEdit->toPlainText();
  735. }
  736. }
  737. QString TextEditMenu::parentSelectedText()
  738. {
  739. if (m_textEdit) {
  740. return m_textEdit->textCursor().selectedText();
  741. } else {
  742. return m_plainTextEdit->textCursor().selectedText();
  743. }
  744. }