menu_action.cpp 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. // This file is part of Desktop App Toolkit,
  2. // a set of libraries for developing nice desktop applications.
  3. //
  4. // For license and copyright information please follow this link:
  5. // https://github.com/desktop-app/legal/blob/master/LEGAL
  6. //
  7. #include "ui/widgets/menu/menu_action.h"
  8. #include "ui/effects/ripple_animation.h"
  9. #include "ui/painter.h"
  10. #include <QtGui/QtEvents>
  11. namespace Ui::Menu {
  12. namespace {
  13. [[nodiscard]] TextWithEntities ParseMenuItem(const QString &text) {
  14. auto result = TextWithEntities();
  15. result.text.reserve(text.size());
  16. auto afterAmpersand = false;
  17. for (const auto &ch : text) {
  18. if (afterAmpersand) {
  19. afterAmpersand = false;
  20. if (ch == '&') {
  21. result.text.append(ch);
  22. } else {
  23. result.entities.append(EntityInText{
  24. EntityType::Underline,
  25. int(result.text.size()),
  26. 1 });
  27. result.text.append(ch);
  28. }
  29. } else if (ch == '&') {
  30. afterAmpersand = true;
  31. } else {
  32. result.text.append(ch);
  33. }
  34. }
  35. return result;
  36. }
  37. TextParseOptions MenuTextOptions = {
  38. TextParseLinks, // flags
  39. 0, // maxw
  40. 0, // maxh
  41. Qt::LayoutDirectionAuto, // dir
  42. };
  43. } // namespace
  44. Action::Action(
  45. not_null<RpWidget*> parent,
  46. const style::Menu &st,
  47. not_null<QAction*> action,
  48. const style::icon *icon,
  49. const style::icon *iconOver)
  50. : ItemBase(parent, st)
  51. , _action(action)
  52. , _st(st)
  53. , _icon(icon)
  54. , _iconOver(iconOver)
  55. , _height(_st.itemPadding.top()
  56. + _st.itemStyle.font->height
  57. + _st.itemPadding.bottom()) {
  58. setAcceptBoth(true);
  59. initResizeHook(parent->sizeValue());
  60. processAction();
  61. enableMouseSelecting();
  62. connect(_action, &QAction::changed, [=] { processAction(); });
  63. }
  64. bool Action::hasSubmenu() const {
  65. return _action->menu() != nullptr;
  66. }
  67. void Action::paintEvent(QPaintEvent *e) {
  68. Painter p(this);
  69. paint(p);
  70. }
  71. void Action::paintBackground(QPainter &p, bool selected) {
  72. if (selected && _st.itemBgOver->c.alpha() < 255) {
  73. p.fillRect(0, 0, width(), _height, _st.itemBg);
  74. }
  75. p.fillRect(
  76. QRect(0, 0, width(), _height),
  77. selected ? _st.itemBgOver : _st.itemBg);
  78. }
  79. void Action::paintText(Painter &p) {
  80. _text.drawLeftElided(
  81. p,
  82. _st.itemPadding.left(),
  83. _st.itemPadding.top(),
  84. _textWidth,
  85. width());
  86. }
  87. void Action::paint(Painter &p) {
  88. const auto enabled = isEnabled();
  89. const auto selected = isSelected();
  90. paintBackground(p, selected);
  91. if (enabled) {
  92. RippleButton::paintRipple(p, 0, 0);
  93. }
  94. if (const auto icon = (selected ? _iconOver : _icon)) {
  95. icon->paint(p, _st.itemIconPosition, width());
  96. }
  97. p.setPen(selected ? _st.itemFgOver : (enabled ? _st.itemFg : _st.itemFgDisabled));
  98. paintText(p);
  99. if (hasSubmenu()) {
  100. const auto skip = _st.itemRightSkip;
  101. const auto left = width() - skip - _st.arrow.width();
  102. const auto top = (_height - _st.arrow.height()) / 2;
  103. if (enabled) {
  104. _st.arrow.paint(p, left, top, width());
  105. } else {
  106. _st.arrow.paint(
  107. p,
  108. left,
  109. top,
  110. width(),
  111. _st.itemFgDisabled->c);
  112. }
  113. } else if (!_shortcut.isEmpty()) {
  114. p.setPen(selected
  115. ? _st.itemFgShortcutOver
  116. : (enabled ? _st.itemFgShortcut : _st.itemFgShortcutDisabled));
  117. p.drawTextRight(
  118. _st.itemPadding.right(),
  119. _st.itemPadding.top(),
  120. width(),
  121. _shortcut);
  122. }
  123. }
  124. void Action::processAction() {
  125. setPointerCursor(isEnabled());
  126. if (_action->text().isEmpty()) {
  127. _shortcut = QString();
  128. _text.clear();
  129. return;
  130. }
  131. const auto actionTextParts = _action->text().split('\t');
  132. const auto actionText = actionTextParts.empty()
  133. ? QString()
  134. : actionTextParts[0];
  135. const auto actionShortcut = (actionTextParts.size() > 1)
  136. ? actionTextParts[1]
  137. : QString();
  138. setMarkedText(ParseMenuItem(actionText), actionShortcut);
  139. }
  140. void Action::setMarkedText(
  141. TextWithEntities text,
  142. QString shortcut,
  143. const Text::MarkedContext &context) {
  144. _text.setMarkedText(_st.itemStyle, text, MenuTextOptions, context);
  145. const auto textWidth = _text.maxWidth();
  146. const auto &padding = _st.itemPadding;
  147. const auto additionalWidth = hasSubmenu()
  148. ? (_st.itemRightSkip + _st.arrow.width())
  149. : (!shortcut.isEmpty())
  150. ? (_st.itemRightSkip + _st.itemStyle.font->width(shortcut))
  151. : 0;
  152. const auto goodWidth = padding.left()
  153. + textWidth
  154. + additionalWidth
  155. + padding.right();
  156. const auto w = std::clamp(goodWidth, _st.widthMin, _st.widthMax);
  157. _textWidth = w - (goodWidth - textWidth);
  158. _shortcut = shortcut;
  159. setMinWidth(w);
  160. update();
  161. }
  162. bool Action::isEnabled() const {
  163. return _action->isEnabled();
  164. }
  165. not_null<QAction*> Action::action() const {
  166. return _action;
  167. }
  168. QPoint Action::prepareRippleStartPosition() const {
  169. return mapFromGlobal(QCursor::pos());
  170. }
  171. QImage Action::prepareRippleMask() const {
  172. return Ui::RippleAnimation::RectMask(size());
  173. }
  174. int Action::contentHeight() const {
  175. return _height;
  176. }
  177. void Action::handleKeyPress(not_null<QKeyEvent*> e) {
  178. if (!isSelected()) {
  179. return;
  180. }
  181. const auto key = e->key();
  182. if (key == Qt::Key_Enter || key == Qt::Key_Return) {
  183. setClicked(TriggeredSource::Keyboard);
  184. return;
  185. }
  186. }
  187. void Action::setIcon(
  188. const style::icon *icon,
  189. const style::icon *iconOver) {
  190. _icon = icon;
  191. _iconOver = iconOver ? iconOver : icon;
  192. update();
  193. }
  194. } // namespace Ui::Menu