1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2017 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a specialized tool button subclass. |
|
8 """ |
|
9 |
|
10 import enum |
|
11 |
|
12 from PyQt6.QtCore import pyqtSlot, pyqtSignal, Qt, QTimer, QSize |
|
13 from PyQt6.QtWidgets import ( |
|
14 QToolButton, QStyle, QStyleOptionToolButton, QStyleOption, QApplication, |
|
15 QLabel |
|
16 ) |
|
17 |
|
18 |
|
19 class E5ToolButtonOptions(enum.IntEnum): |
|
20 """ |
|
21 Class defining the tool button options. |
|
22 """ |
|
23 DEFAULT = 0 |
|
24 SHOW_MENU_INSIDE = 1 |
|
25 TOOLBAR_LOOKUP = 2 |
|
26 |
|
27 |
|
28 class E5ToolButton(QToolButton): |
|
29 """ |
|
30 Class implementing a specialized tool button subclass. |
|
31 |
|
32 @signal aboutToShowMenu() emitted before the tool button menu is shown |
|
33 @signal aboutToHideMenu() emitted before the tool button menu is hidden |
|
34 @signal middleClicked() emitted when the middle mouse button was clicked |
|
35 @signal controlClicked() emitted when the left mouse button was |
|
36 clicked while pressing the Ctrl key |
|
37 @signal doubleClicked() emitted when the left mouse button was |
|
38 double clicked |
|
39 """ |
|
40 aboutToShowMenu = pyqtSignal() |
|
41 aboutToHideMenu = pyqtSignal() |
|
42 middleClicked = pyqtSignal() |
|
43 controlClicked = pyqtSignal() |
|
44 doubleClicked = pyqtSignal() |
|
45 |
|
46 def __init__(self, parent=None): |
|
47 """ |
|
48 Constructor |
|
49 |
|
50 @param parent reference to the parent widget |
|
51 @type QWidget |
|
52 """ |
|
53 super().__init__(parent) |
|
54 |
|
55 self.setMinimumWidth(16) |
|
56 |
|
57 self.__menu = None |
|
58 self.__options = E5ToolButtonOptions.DEFAULT |
|
59 |
|
60 self.__badgeLabel = QLabel(self) |
|
61 font = self.__badgeLabel.font() |
|
62 font.setPixelSize(self.__badgeLabel.height() / 2.5) |
|
63 self.__badgeLabel.setFont(font) |
|
64 self.__badgeLabel.hide() |
|
65 |
|
66 opt = QStyleOptionToolButton() |
|
67 self.initStyleOption(opt) |
|
68 |
|
69 self.__pressTimer = QTimer() |
|
70 self.__pressTimer.setSingleShot(True) |
|
71 self.__pressTimer.setInterval( |
|
72 QApplication.style().styleHint( |
|
73 QStyle.StyleHint.SH_ToolButton_PopupDelay, opt, self)) |
|
74 self.__pressTimer.timeout.connect(self.__showMenu) |
|
75 |
|
76 ################################################################## |
|
77 ## Menu handling methods below. |
|
78 ## |
|
79 ## The menu is handled in E5ToolButton and is not passed to |
|
80 ## QToolButton. No menu indicator will be shown in the button. |
|
81 ################################################################## |
|
82 |
|
83 def menu(self): |
|
84 """ |
|
85 Public method to get a reference to the tool button menu. |
|
86 |
|
87 @return reference to the tool button menu |
|
88 @rtype QMenu |
|
89 """ |
|
90 return self.__menu |
|
91 |
|
92 def setMenu(self, menu): |
|
93 """ |
|
94 Public method to set the tool button menu. |
|
95 |
|
96 @param menu reference to the tool button menu |
|
97 @type QMenu |
|
98 """ |
|
99 if menu is not None: |
|
100 if self.__menu: |
|
101 self.__menu.aboutToHide.disconnect(self.__menuAboutToHide) |
|
102 |
|
103 self.__menu = menu |
|
104 self.__menu.aboutToHide.connect(self.__menuAboutToHide) |
|
105 |
|
106 def showMenuInside(self): |
|
107 """ |
|
108 Public method to check, if the menu edge shall be aligned with |
|
109 the button. |
|
110 |
|
111 @return flag indicating that the menu edge shall be aligned |
|
112 @rtype bool |
|
113 """ |
|
114 return bool(self.__options & E5ToolButtonOptions.SHOW_MENU_INSIDE) |
|
115 |
|
116 def setShowMenuInside(self, enable): |
|
117 """ |
|
118 Public method to set a flag to show the menu edge aligned with |
|
119 the button. |
|
120 |
|
121 @param enable flag indicating to align the menu edge to the button |
|
122 @type bool |
|
123 """ |
|
124 if enable: |
|
125 self.__options |= E5ToolButtonOptions.SHOW_MENU_INSIDE |
|
126 else: |
|
127 self.__options &= ~E5ToolButtonOptions.SHOW_MENU_INSIDE |
|
128 |
|
129 @pyqtSlot() |
|
130 def __showMenu(self): |
|
131 """ |
|
132 Private slot to show the tool button menu. |
|
133 """ |
|
134 if self.__menu is None or self.__menu.isVisible(): |
|
135 return |
|
136 |
|
137 self.aboutToShowMenu.emit() |
|
138 |
|
139 if self.__options & E5ToolButtonOptions.SHOW_MENU_INSIDE: |
|
140 pos = self.mapToGlobal(self.rect().bottomRight()) |
|
141 if ( |
|
142 QApplication.layoutDirection() == |
|
143 Qt.LayoutDirection.RightToLeft |
|
144 ): |
|
145 pos.setX(pos.x() - self.rect().width()) |
|
146 else: |
|
147 pos.setX(pos.x() - self.__menu.sizeHint().width()) |
|
148 else: |
|
149 pos = self.mapToGlobal(self.rect().bottomLeft()) |
|
150 |
|
151 self.__menu.popup(pos) |
|
152 |
|
153 @pyqtSlot() |
|
154 def __menuAboutToHide(self): |
|
155 """ |
|
156 Private slot to handle the tool button menu about to be hidden. |
|
157 """ |
|
158 self.setDown(False) |
|
159 self.aboutToHideMenu.emit() |
|
160 |
|
161 ################################################################## |
|
162 ## Methods to handle the tool button look |
|
163 ################################################################## |
|
164 |
|
165 def toolbarButtonLook(self): |
|
166 """ |
|
167 Public method to check, if the button has the toolbar look. |
|
168 |
|
169 @return flag indicating toolbar look |
|
170 @rtype bool |
|
171 """ |
|
172 return bool(self.__options & E5ToolButtonOptions.TOOLBAR_LOOKUP) |
|
173 |
|
174 def setToolbarButtonLook(self, enable): |
|
175 """ |
|
176 Public method to set the toolbar look state. |
|
177 |
|
178 @param enable flag indicating toolbar look |
|
179 @type bool |
|
180 """ |
|
181 if enable: |
|
182 self.__options |= E5ToolButtonOptions.TOOLBAR_LOOKUP |
|
183 |
|
184 opt = QStyleOption() |
|
185 opt.initFrom(self) |
|
186 size = self.style().pixelMetric( |
|
187 QStyle.PixelMetric.PM_ToolBarIconSize, opt, self) |
|
188 self.setIconSize(QSize(size, size)) |
|
189 else: |
|
190 self.__options &= ~E5ToolButtonOptions.TOOLBAR_LOOKUP |
|
191 |
|
192 self.setProperty("toolbar-look", enable) |
|
193 self.style().unpolish(self) |
|
194 self.style().polish(self) |
|
195 |
|
196 ################################################################## |
|
197 ## Methods to handle some event types |
|
198 ################################################################## |
|
199 |
|
200 def mousePressEvent(self, evt): |
|
201 """ |
|
202 Protected method to handle mouse press events. |
|
203 |
|
204 @param evt reference to the mouse event |
|
205 @type QMouseEvent |
|
206 """ |
|
207 if self.popupMode() == QToolButton.ToolButtonPopupMode.DelayedPopup: |
|
208 self.__pressTimer.start() |
|
209 |
|
210 if ( |
|
211 (evt.buttons() == Qt.MouseButton.LeftButton and |
|
212 self.__menu is not None and |
|
213 (self.popupMode() == |
|
214 QToolButton.ToolButtonPopupMode.InstantPopup)) or |
|
215 (evt.buttons() == Qt.MouseButton.RightButton and |
|
216 self.__menu is not None) |
|
217 ): |
|
218 self.setDown(True) |
|
219 self.__showMenu() |
|
220 else: |
|
221 super().mousePressEvent(evt) |
|
222 |
|
223 def mouseReleaseEvent(self, evt): |
|
224 """ |
|
225 Protected method to handle mouse release events. |
|
226 |
|
227 @param evt reference to the mouse event |
|
228 @type QMouseEvent |
|
229 """ |
|
230 self.__pressTimer.stop() |
|
231 |
|
232 if ( |
|
233 evt.button() == Qt.MouseButton.MiddleButton and |
|
234 self.rect().contains(evt.position().toPoint()) |
|
235 ): |
|
236 self.middleClicked.emit() |
|
237 self.setDown(False) |
|
238 elif ( |
|
239 evt.button() == Qt.MouseButton.LeftButton and |
|
240 self.rect().contains(evt.position().toPoint()) and |
|
241 evt.modifiers() == Qt.KeyboardModifier.ControlModifier |
|
242 ): |
|
243 self.controlClicked.emit() |
|
244 self.setDown(False) |
|
245 else: |
|
246 super().mouseReleaseEvent(evt) |
|
247 |
|
248 def mouseDoubleClickEvent(self, evt): |
|
249 """ |
|
250 Protected method to handle mouse double click events. |
|
251 |
|
252 @param evt reference to the mouse event |
|
253 @type QMouseEvent |
|
254 """ |
|
255 super().mouseDoubleClickEvent(evt) |
|
256 |
|
257 self.__pressTimer.stop() |
|
258 |
|
259 if evt.buttons() == Qt.MouseButton.LeftButton: |
|
260 self.doubleClicked.emit() |
|
261 |
|
262 def contextMenuEvent(self, evt): |
|
263 """ |
|
264 Protected method to handle context menu events. |
|
265 |
|
266 @param evt reference to the context menu event |
|
267 @type QContextMenuEvent |
|
268 """ |
|
269 # block to prevent showing the context menu and the tool button menu |
|
270 if self.__menu is not None: |
|
271 return |
|
272 |
|
273 super().contextMenuEvent(evt) |
|
274 |
|
275 ################################################################## |
|
276 ## Methods to handle the tool button badge |
|
277 ################################################################## |
|
278 |
|
279 def setBadgeText(self, text): |
|
280 """ |
|
281 Public method to set the badge text. |
|
282 |
|
283 @param text badge text to be set |
|
284 @type str |
|
285 """ |
|
286 if text: |
|
287 self.__badgeLabel.setText(text) |
|
288 self.__badgeLabel.resize(self.__badgeLabel.sizeHint()) |
|
289 self.__badgeLabel.move(self.width() - self.__badgeLabel.width(), 0) |
|
290 self.__badgeLabel.show() |
|
291 else: |
|
292 self.__badgeLabel.clear() |
|
293 self.__badgeLabel.hide() |
|
294 |
|
295 def badgeText(self): |
|
296 """ |
|
297 Public method to get the badge text. |
|
298 |
|
299 @return badge text |
|
300 @rtype str |
|
301 """ |
|
302 return self.__badgeLabel.text() |
|