|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2008 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a sidebar class. |
|
8 """ |
|
9 |
|
10 import enum |
|
11 import json |
|
12 |
|
13 from PyQt6.QtCore import pyqtSlot, Qt, QSize |
|
14 from PyQt6.QtWidgets import QWidget, QStackedWidget, QBoxLayout |
|
15 |
|
16 from .EricIconBar import EricIconBar |
|
17 |
|
18 |
|
19 class EricSideBarSide(enum.Enum): |
|
20 """ |
|
21 Class defining the sidebar sides. |
|
22 """ |
|
23 NORTH = 0 |
|
24 EAST = 1 |
|
25 SOUTH = 2 |
|
26 WEST = 3 |
|
27 |
|
28 |
|
29 class EricSideBar(QWidget): |
|
30 """ |
|
31 Class implementing a sidebar with a widget area, that is hidden or shown, |
|
32 if the current tab is clicked again. |
|
33 """ |
|
34 Version = 4 |
|
35 |
|
36 def __init__(self, orientation=None, |
|
37 iconBarSize=EricIconBar.DefaultBarSize, parent=None): |
|
38 """ |
|
39 Constructor |
|
40 |
|
41 @param orientation orientation of the sidebar widget |
|
42 @type EricSideBarSide |
|
43 @param iconBarSize size category for the bar (one of 'xs', 'sm', 'md', |
|
44 'lg', 'xl', 'xxl') |
|
45 @type str |
|
46 @param parent parent widget |
|
47 @type QWidget |
|
48 """ |
|
49 super().__init__(parent) |
|
50 |
|
51 # initial layout is done for NORTH |
|
52 self.__iconBar = EricIconBar(orientation=Qt.Orientation.Horizontal, |
|
53 barSize=iconBarSize) |
|
54 |
|
55 self.__stackedWidget = QStackedWidget(self) |
|
56 self.__stackedWidget.setContentsMargins(0, 0, 0, 0) |
|
57 |
|
58 self.layout = QBoxLayout(QBoxLayout.Direction.TopToBottom) |
|
59 self.layout.setContentsMargins(0, 0, 0, 0) |
|
60 self.layout.setSpacing(3) |
|
61 self.layout.addWidget(self.__iconBar) |
|
62 self.layout.addWidget(self.__stackedWidget) |
|
63 self.setLayout(self.layout) |
|
64 |
|
65 self.__minimized = False |
|
66 self.__minSize = 0 |
|
67 self.__maxSize = 0 |
|
68 self.__bigSize = QSize() |
|
69 |
|
70 self.__hasFocus = False |
|
71 # flag storing if this widget or any child has the focus |
|
72 self.__autoHide = False |
|
73 |
|
74 self.__orientation = EricSideBarSide.NORTH |
|
75 if orientation is None: |
|
76 orientation = EricSideBarSide.NORTH |
|
77 self.setOrientation(orientation) |
|
78 |
|
79 self.__iconBar.currentChanged.connect( |
|
80 self.__stackedWidget.setCurrentIndex) |
|
81 self.__iconBar.currentChanged.connect( |
|
82 self.__currentIconChanged) |
|
83 self.__iconBar.currentClicked.connect( |
|
84 self.__currentIconClicked) |
|
85 |
|
86 def __shrinkIt(self): |
|
87 """ |
|
88 Private method to shrink the sidebar. |
|
89 """ |
|
90 self.__minimized = True |
|
91 self.__bigSize = self.size() |
|
92 if self.__orientation in ( |
|
93 EricSideBarSide.NORTH, EricSideBarSide.SOUTH |
|
94 ): |
|
95 self.__minSize = self.minimumSizeHint().height() |
|
96 self.__maxSize = self.maximumHeight() |
|
97 else: |
|
98 self.__minSize = self.minimumSizeHint().width() |
|
99 self.__maxSize = self.maximumWidth() |
|
100 |
|
101 self.__stackedWidget.hide() |
|
102 |
|
103 if self.__orientation in ( |
|
104 EricSideBarSide.NORTH, EricSideBarSide.SOUTH |
|
105 ): |
|
106 self.setFixedHeight(self.__iconBar.minimumSizeHint().height()) |
|
107 else: |
|
108 self.setFixedWidth(self.__iconBar.minimumSizeHint().width()) |
|
109 |
|
110 def __expandIt(self): |
|
111 """ |
|
112 Private method to expand the sidebar. |
|
113 """ |
|
114 self.__minimized = False |
|
115 self.__stackedWidget.show() |
|
116 self.resize(self.__bigSize) |
|
117 if self.__orientation in ( |
|
118 EricSideBarSide.NORTH, EricSideBarSide.SOUTH |
|
119 ): |
|
120 minSize = max(self.__minSize, self.minimumSizeHint().height()) |
|
121 self.setMinimumHeight(minSize) |
|
122 self.setMaximumHeight(self.__maxSize) |
|
123 else: |
|
124 minSize = max(self.__minSize, self.minimumSizeHint().width()) |
|
125 self.setMinimumWidth(minSize) |
|
126 self.setMaximumWidth(self.__maxSize) |
|
127 |
|
128 def isMinimized(self): |
|
129 """ |
|
130 Public method to check the minimized state. |
|
131 |
|
132 @return flag indicating the minimized state (boolean) |
|
133 """ |
|
134 return self.__minimized |
|
135 |
|
136 @pyqtSlot(int) |
|
137 def __currentIconChanged(self, index): |
|
138 """ |
|
139 Private slot to handle a change of the current icon. |
|
140 |
|
141 @param index index of the current icon |
|
142 @type int |
|
143 """ |
|
144 if self.isMinimized(): |
|
145 self.__expandIt() |
|
146 |
|
147 @pyqtSlot(int) |
|
148 def __currentIconClicked(self, index): |
|
149 """ |
|
150 Private slot to handle a click of the current icon. |
|
151 |
|
152 @param index index of the clicked icon |
|
153 @type int |
|
154 """ |
|
155 if self.isMinimized(): |
|
156 self.__expandIt() |
|
157 else: |
|
158 self.__shrinkIt() |
|
159 |
|
160 def addTab(self, widget, icon, label=None): |
|
161 """ |
|
162 Public method to add a tab to the sidebar. |
|
163 |
|
164 @param widget reference to the widget to add |
|
165 @type QWidget |
|
166 @param icon reference to the icon of the widget |
|
167 @type QIcon |
|
168 @param label the label text of the widget |
|
169 @type str |
|
170 """ |
|
171 self.__iconBar.addIcon(icon, label) |
|
172 self.__stackedWidget.addWidget(widget) |
|
173 if self.__orientation in ( |
|
174 EricSideBarSide.NORTH, EricSideBarSide.SOUTH |
|
175 ): |
|
176 self.__minSize = self.minimumSizeHint().height() |
|
177 else: |
|
178 self.__minSize = self.minimumSizeHint().width() |
|
179 |
|
180 def insertTab(self, index, widget, icon, label=None): |
|
181 """ |
|
182 Public method to insert a tab into the sidebar. |
|
183 |
|
184 @param index the index to insert the tab at |
|
185 @type int |
|
186 @param widget reference to the widget to insert |
|
187 @type QWidget |
|
188 @param icon reference to the icon of the widget |
|
189 @type QIcon |
|
190 @param label the label text of the widget |
|
191 @type str |
|
192 """ |
|
193 self.__iconBar.insertIcon(index, icon, label) |
|
194 |
|
195 self.__stackedWidget.insertWidget(index, widget) |
|
196 if self.__orientation in ( |
|
197 EricSideBarSide.NORTH, EricSideBarSide.SOUTH |
|
198 ): |
|
199 self.__minSize = self.minimumSizeHint().height() |
|
200 else: |
|
201 self.__minSize = self.minimumSizeHint().width() |
|
202 |
|
203 def removeTab(self, index): |
|
204 """ |
|
205 Public method to remove a tab. |
|
206 |
|
207 @param index the index of the tab to remove |
|
208 @type int |
|
209 """ |
|
210 self.__stackedWidget.removeWidget(self.__stackedWidget.widget(index)) |
|
211 self.__iconBar.removeIcon(index) |
|
212 if self.__orientation in ( |
|
213 EricSideBarSide.NORTH, EricSideBarSide.SOUTH |
|
214 ): |
|
215 self.__minSize = self.minimumSizeHint().height() |
|
216 else: |
|
217 self.__minSize = self.minimumSizeHint().width() |
|
218 |
|
219 def clear(self): |
|
220 """ |
|
221 Public method to remove all tabs. |
|
222 """ |
|
223 while self.count() > 0: |
|
224 self.removeTab(0) |
|
225 |
|
226 def prevTab(self): |
|
227 """ |
|
228 Public slot used to show the previous tab. |
|
229 """ |
|
230 ind = self.currentIndex() - 1 |
|
231 if ind == -1: |
|
232 ind = self.count() - 1 |
|
233 |
|
234 self.setCurrentIndex(ind) |
|
235 self.currentWidget().setFocus() |
|
236 |
|
237 def nextTab(self): |
|
238 """ |
|
239 Public slot used to show the next tab. |
|
240 """ |
|
241 ind = self.currentIndex() + 1 |
|
242 if ind == self.count(): |
|
243 ind = 0 |
|
244 |
|
245 self.setCurrentIndex(ind) |
|
246 self.currentWidget().setFocus() |
|
247 |
|
248 def count(self): |
|
249 """ |
|
250 Public method to get the number of tabs. |
|
251 |
|
252 @return number of tabs in the sidebar (integer) |
|
253 """ |
|
254 return self.__iconBar.count() |
|
255 |
|
256 def currentIndex(self): |
|
257 """ |
|
258 Public method to get the index of the current tab. |
|
259 |
|
260 @return index of the current tab (integer) |
|
261 """ |
|
262 return self.__stackedWidget.currentIndex() |
|
263 |
|
264 def setCurrentIndex(self, index): |
|
265 """ |
|
266 Public slot to set the current index. |
|
267 |
|
268 @param index the index to set as the current index (integer) |
|
269 """ |
|
270 self.__iconBar.setCurrentIndex(index) |
|
271 self.__stackedWidget.setCurrentIndex(index) |
|
272 if self.isMinimized(): |
|
273 self.__expandIt() |
|
274 |
|
275 def currentWidget(self): |
|
276 """ |
|
277 Public method to get a reference to the current widget. |
|
278 |
|
279 @return reference to the current widget (QWidget) |
|
280 """ |
|
281 return self.__stackedWidget.currentWidget() |
|
282 |
|
283 def setCurrentWidget(self, widget): |
|
284 """ |
|
285 Public slot to set the current widget. |
|
286 |
|
287 @param widget reference to the widget to become the current widget |
|
288 (QWidget) |
|
289 """ |
|
290 self.__stackedWidget.setCurrentWidget(widget) |
|
291 self.__iconBar.setCurrentIndex(self.__stackedWidget.currentIndex()) |
|
292 if self.isMinimized(): |
|
293 self.__expandIt() |
|
294 |
|
295 def indexOf(self, widget): |
|
296 """ |
|
297 Public method to get the index of the given widget. |
|
298 |
|
299 @param widget reference to the widget to get the index of (QWidget) |
|
300 @return index of the given widget (integer) |
|
301 """ |
|
302 return self.__stackedWidget.indexOf(widget) |
|
303 |
|
304 def orientation(self): |
|
305 """ |
|
306 Public method to get the orientation of the sidebar. |
|
307 |
|
308 @return orientation of the sidebar |
|
309 @rtype EricSideBarSide |
|
310 """ |
|
311 return self.__orientation |
|
312 |
|
313 def setOrientation(self, orient): |
|
314 """ |
|
315 Public method to set the orientation of the sidebar. |
|
316 |
|
317 @param orient orientation of the sidebar |
|
318 @type EricSideBarSide |
|
319 """ |
|
320 if orient == EricSideBarSide.NORTH: |
|
321 self.__iconBar.setOrientation(Qt.Orientation.Horizontal) |
|
322 self.layout.setDirection(QBoxLayout.Direction.TopToBottom) |
|
323 elif orient == EricSideBarSide.EAST: |
|
324 self.__iconBar.setOrientation(Qt.Orientation.Vertical) |
|
325 self.layout.setDirection(QBoxLayout.Direction.RightToLeft) |
|
326 elif orient == EricSideBarSide.SOUTH: |
|
327 self.__iconBar.setOrientation(Qt.Orientation.Horizontal) |
|
328 self.layout.setDirection(QBoxLayout.Direction.BottomToTop) |
|
329 elif orient == EricSideBarSide.WEST: |
|
330 self.__iconBar.setOrientation(Qt.Orientation.Vertical) |
|
331 self.layout.setDirection(QBoxLayout.Direction.LeftToRight) |
|
332 self.__orientation = orient |
|
333 |
|
334 def widget(self, index): |
|
335 """ |
|
336 Public method to get a reference to the widget associated with a tab. |
|
337 |
|
338 @param index index of the tab (integer) |
|
339 @return reference to the widget (QWidget) |
|
340 """ |
|
341 return self.__stackedWidget.widget(index) |
|
342 |
|
343 def setIconBarColor(self, color): |
|
344 """ |
|
345 Public method to set the icon bar color. |
|
346 |
|
347 @param color icon bar color |
|
348 @type QColor |
|
349 """ |
|
350 self.__iconBar.setColor(color) |
|
351 |
|
352 def iconBarColor(self): |
|
353 """ |
|
354 Public method to get the icon bar color. |
|
355 |
|
356 @return icon bar color |
|
357 @rtype QColor |
|
358 """ |
|
359 return self.__iconBar.color() |
|
360 |
|
361 def setIconBarSize(self, barSize): |
|
362 """ |
|
363 Public method to set the icon bar size. |
|
364 |
|
365 @param barSize size category for the bar (one of 'xs', 'sm', 'md', |
|
366 'lg', 'xl', 'xxl') |
|
367 @type str |
|
368 """ |
|
369 self.__iconBar.setBarSize(barSize) |
|
370 if self.isMinimized(): |
|
371 self.__shrinkIt() |
|
372 else: |
|
373 self.__expandIt() |
|
374 |
|
375 def barSize(self): |
|
376 """ |
|
377 Public method to get the icon bar size. |
|
378 |
|
379 @return barSize size category for the bar (one of 'xs', 'sm', 'md', |
|
380 'lg', 'xl', 'xxl') |
|
381 @rtype str |
|
382 """ |
|
383 return self.__iconBar.barSize() |
|
384 |
|
385 def saveState(self): |
|
386 """ |
|
387 Public method to save the state of the sidebar. |
|
388 |
|
389 @return saved state as a byte array (QByteArray) |
|
390 """ |
|
391 self.__bigSize = self.size() |
|
392 if self.__orientation in ( |
|
393 EricSideBarSide.NORTH, EricSideBarSide.SOUTH |
|
394 ): |
|
395 self.__minSize = self.minimumSizeHint().height() |
|
396 self.__maxSize = self.maximumHeight() |
|
397 else: |
|
398 self.__minSize = self.minimumSizeHint().width() |
|
399 self.__maxSize = self.maximumWidth() |
|
400 |
|
401 dataDict = { |
|
402 "version": self.Version, |
|
403 "minimized": self.__minimized, |
|
404 "big_size": [self.__bigSize.width(), self.__bigSize.height()], |
|
405 "min_size": self.__minSize, |
|
406 "max_size": self.__maxSize, |
|
407 } |
|
408 data = json.dumps(dataDict) |
|
409 |
|
410 return data |
|
411 |
|
412 def restoreState(self, state): |
|
413 """ |
|
414 Public method to restore the state of the sidebar. |
|
415 |
|
416 @param state byte array containing the saved state (QByteArray) |
|
417 @return flag indicating success (boolean) |
|
418 """ |
|
419 if not isinstance(state, str) or state == "": |
|
420 return False |
|
421 |
|
422 try: |
|
423 stateDict = json.loads(state) |
|
424 except json.JSONDecodeError: |
|
425 return False |
|
426 |
|
427 if not stateDict: |
|
428 return False |
|
429 |
|
430 if self.__orientation in ( |
|
431 EricSideBarSide.NORTH, EricSideBarSide.SOUTH |
|
432 ): |
|
433 minSize = self.layout.minimumSize().height() |
|
434 maxSize = self.maximumHeight() |
|
435 else: |
|
436 minSize = self.layout.minimumSize().width() |
|
437 maxSize = self.maximumWidth() |
|
438 |
|
439 if stateDict["version"] in (2, 3, 4): |
|
440 if stateDict["minimized"] and not self.__minimized: |
|
441 self.__shrinkIt() |
|
442 |
|
443 self.__bigSize = QSize(*stateDict["big_size"]) |
|
444 self.__minSize = max(stateDict["min_size"], minSize) |
|
445 self.__maxSize = max(stateDict["max_size"], maxSize) |
|
446 |
|
447 if not stateDict["minimized"]: |
|
448 self.__expandIt() |
|
449 |
|
450 return True |
|
451 |
|
452 return False |