|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2008 - 2010 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a sidebar class. |
|
8 """ |
|
9 |
|
10 from PyQt4.QtCore import SIGNAL, SLOT, QEvent, QSize, Qt, QByteArray, \ |
|
11 QDataStream, QIODevice |
|
12 from PyQt4.QtGui import QTabBar, QWidget, QStackedWidget, QBoxLayout, QToolButton |
|
13 |
|
14 from E5Gui.E5Application import e5App |
|
15 |
|
16 import UI.PixmapCache |
|
17 |
|
18 class E5SideBar(QWidget): |
|
19 """ |
|
20 Class implementing a sidebar with a widget area, that is hidden or shown, if the |
|
21 current tab is clicked again. |
|
22 """ |
|
23 Version = 1 |
|
24 |
|
25 North = 0 |
|
26 East = 1 |
|
27 South = 2 |
|
28 West = 3 |
|
29 |
|
30 def __init__(self, orientation = None, parent = None): |
|
31 """ |
|
32 Constructor |
|
33 |
|
34 @param orientation orientation of the sidebar widget (North, East, South, West) |
|
35 @param parent parent widget (QWidget) |
|
36 """ |
|
37 QWidget.__init__(self, parent) |
|
38 |
|
39 self.__tabBar = QTabBar() |
|
40 self.__tabBar.setDrawBase(True) |
|
41 self.__tabBar.setShape(QTabBar.RoundedNorth) |
|
42 self.__stackedWidget = QStackedWidget(self) |
|
43 self.__stackedWidget.setContentsMargins(0, 0, 0, 0) |
|
44 self.__autoHideButton = QToolButton() |
|
45 self.__autoHideButton.setCheckable(True) |
|
46 self.__autoHideButton.setIcon(UI.PixmapCache.getIcon("autoHideOff.png")) |
|
47 self.__autoHideButton.setChecked(True) |
|
48 self.__autoHideButton.setToolTip( |
|
49 self.trUtf8("Deselect to activate automatic collapsing")) |
|
50 self.barLayout = QBoxLayout(QBoxLayout.LeftToRight) |
|
51 self.barLayout.setMargin(0) |
|
52 self.layout = QBoxLayout(QBoxLayout.TopToBottom) |
|
53 self.layout.setMargin(0) |
|
54 self.layout.setSpacing(0) |
|
55 self.barLayout.addWidget(self.__autoHideButton) |
|
56 self.barLayout.addWidget(self.__tabBar) |
|
57 self.layout.addLayout(self.barLayout) |
|
58 self.layout.addWidget(self.__stackedWidget) |
|
59 self.setLayout(self.layout) |
|
60 |
|
61 self.__minimized = False |
|
62 self.__minSize = 0 |
|
63 self.__maxSize = 0 |
|
64 self.__bigSize = QSize() |
|
65 |
|
66 self.splitter = None |
|
67 self.splitterSizes = [] |
|
68 |
|
69 self.__hasFocus = False # flag storing if this widget or any child has the focus |
|
70 self.__autoHide = False |
|
71 |
|
72 self.__tabBar.installEventFilter(self) |
|
73 |
|
74 self.__orientation = E5SideBar.North |
|
75 if orientation is None: |
|
76 orientation = E5SideBar.North |
|
77 self.setOrientation(orientation) |
|
78 |
|
79 self.connect(self.__tabBar, SIGNAL("currentChanged(int)"), |
|
80 self.__stackedWidget, SLOT("setCurrentIndex(int)")) |
|
81 self.connect(e5App(), SIGNAL("focusChanged(QWidget*, QWidget*)"), |
|
82 self.__appFocusChanged) |
|
83 self.connect(self.__autoHideButton, SIGNAL("toggled(bool)"), |
|
84 self.__autoHideToggled) |
|
85 |
|
86 def setSplitter(self, splitter): |
|
87 """ |
|
88 Public method to set the splitter managing the sidebar. |
|
89 |
|
90 @param splitter reference to the splitter (QSplitter) |
|
91 """ |
|
92 self.splitter = splitter |
|
93 |
|
94 def shrink(self): |
|
95 """ |
|
96 Public method to shrink the sidebar. |
|
97 """ |
|
98 self.__minimized = True |
|
99 self.__bigSize = self.size() |
|
100 if self.__orientation in [E5SideBar.North, E5SideBar.South]: |
|
101 self.__minSize = self.minimumHeight() |
|
102 self.__maxSize = self.maximumHeight() |
|
103 else: |
|
104 self.__minSize = self.minimumWidth() |
|
105 self.__maxSize = self.maximumWidth() |
|
106 self.splitterSizes = self.splitter.sizes() |
|
107 |
|
108 self.__stackedWidget.hide() |
|
109 |
|
110 if self.__orientation in [E5SideBar.North, E5SideBar.South]: |
|
111 self.setFixedHeight(self.__tabBar.minimumSizeHint().height()) |
|
112 else: |
|
113 self.setFixedWidth(self.__tabBar.minimumSizeHint().width()) |
|
114 |
|
115 def expand(self): |
|
116 """ |
|
117 Public method to expand the sidebar. |
|
118 """ |
|
119 self.__minimized = False |
|
120 self.__stackedWidget.show() |
|
121 self.resize(self.__bigSize) |
|
122 if self.__orientation in [E5SideBar.North, E5SideBar.South]: |
|
123 self.setMinimumHeight(self.__minSize) |
|
124 self.setMaximumHeight(self.__maxSize) |
|
125 else: |
|
126 self.setMinimumWidth(self.__minSize) |
|
127 self.setMaximumWidth(self.__maxSize) |
|
128 self.splitter.setSizes(self.splitterSizes) |
|
129 |
|
130 def isMinimized(self): |
|
131 """ |
|
132 Public method to check the minimized state. |
|
133 |
|
134 @return flag indicating the minimized state (boolean) |
|
135 """ |
|
136 return self.__minimized |
|
137 |
|
138 def isAutoHiding(self): |
|
139 """ |
|
140 Public method to check, if the auto hide function is active. |
|
141 |
|
142 @return flag indicating the state of auto hiding (boolean) |
|
143 """ |
|
144 return self.__autoHide |
|
145 |
|
146 def eventFilter(self, obj, evt): |
|
147 """ |
|
148 Protected method to handle some events for the tabbar. |
|
149 |
|
150 @param obj reference to the object (QObject) |
|
151 @param evt reference to the event object (QEvent) |
|
152 @return flag indicating, if the event was handled (boolean) |
|
153 """ |
|
154 if obj == self.__tabBar: |
|
155 if evt.type() == QEvent.MouseButtonPress: |
|
156 pos = evt.pos() |
|
157 index = -1 |
|
158 for i in range(self.__tabBar.count()): |
|
159 if self.__tabBar.tabRect(i).contains(pos): |
|
160 index = i |
|
161 break |
|
162 |
|
163 if i == self.__tabBar.currentIndex(): |
|
164 if self.isMinimized(): |
|
165 self.expand() |
|
166 else: |
|
167 self.shrink() |
|
168 return True |
|
169 elif self.isMinimized(): |
|
170 self.expand() |
|
171 elif evt.type() == QEvent.Wheel and not self.__stackedWidget.isHidden(): |
|
172 if evt.delta() > 0: |
|
173 self.prevTab() |
|
174 else: |
|
175 self.nextTab() |
|
176 return True |
|
177 |
|
178 return QWidget.eventFilter(self, obj, evt) |
|
179 |
|
180 def addTab(self, widget, iconOrLabel, label = None): |
|
181 """ |
|
182 Public method to add a tab to the sidebar. |
|
183 |
|
184 @param widget reference to the widget to add (QWidget) |
|
185 @param iconOrLabel reference to the icon or the labeltext of the tab |
|
186 (QIcon, string) |
|
187 @param label the labeltext of the tab (string) (only to be |
|
188 used, if the second parameter is a QIcon) |
|
189 """ |
|
190 if label: |
|
191 self.__tabBar.addTab(iconOrLabel, label) |
|
192 else: |
|
193 self.__tabBar.addTab(iconOrLabel) |
|
194 self.__stackedWidget.addWidget(widget) |
|
195 |
|
196 def insertTab(self, index, widget, iconOrLabel, label = None): |
|
197 """ |
|
198 Public method to insert a tab into the sidebar. |
|
199 |
|
200 @param index the index to insert the tab at (integer) |
|
201 @param widget reference to the widget to insert (QWidget) |
|
202 @param iconOrLabel reference to the icon or the labeltext of the tab |
|
203 (QIcon, string) |
|
204 @param label the labeltext of the tab (string) (only to be |
|
205 used, if the second parameter is a QIcon) |
|
206 """ |
|
207 if label: |
|
208 self.__tabBar.insertTab(index, iconOrLabel, label) |
|
209 else: |
|
210 self.__tabBar.insertTab(index, iconOrLabel) |
|
211 self.__stackedWidget.insertWidget(index, widget) |
|
212 |
|
213 def removeTab(self, index): |
|
214 """ |
|
215 Public method to remove a tab. |
|
216 |
|
217 @param index the index of the tab to remove (integer) |
|
218 """ |
|
219 self.__stackedWidget.removeWidget(self.__stackedWidget.widget(index)) |
|
220 self.__tabBar.removeTab(index) |
|
221 |
|
222 def clear(self): |
|
223 """ |
|
224 Public method to remove all tabs. |
|
225 """ |
|
226 while self.count() > 0: |
|
227 self.removeTab(0) |
|
228 |
|
229 def prevTab(self): |
|
230 """ |
|
231 Public slot used to show the previous tab. |
|
232 """ |
|
233 ind = self.currentIndex() - 1 |
|
234 if ind == -1: |
|
235 ind = self.count() - 1 |
|
236 |
|
237 self.setCurrentIndex(ind) |
|
238 self.currentWidget().setFocus() |
|
239 |
|
240 def nextTab(self): |
|
241 """ |
|
242 Public slot used to show the next tab. |
|
243 """ |
|
244 ind = self.currentIndex() + 1 |
|
245 if ind == self.count(): |
|
246 ind = 0 |
|
247 |
|
248 self.setCurrentIndex(ind) |
|
249 self.currentWidget().setFocus() |
|
250 |
|
251 def count(self): |
|
252 """ |
|
253 Public method to get the number of tabs. |
|
254 |
|
255 @return number of tabs in the sidebar (integer) |
|
256 """ |
|
257 return self.__tabBar.count() |
|
258 |
|
259 def currentIndex(self): |
|
260 """ |
|
261 Public method to get the index of the current tab. |
|
262 |
|
263 @return index of the current tab (integer) |
|
264 """ |
|
265 return self.__stackedWidget.currentIndex() |
|
266 |
|
267 def setCurrentIndex(self, index): |
|
268 """ |
|
269 Public slot to set the current index. |
|
270 |
|
271 @param index the index to set as the current index (integer) |
|
272 """ |
|
273 self.__tabBar.setCurrentIndex(index) |
|
274 self.__stackedWidget.setCurrentIndex(index) |
|
275 if self.isMinimized(): |
|
276 self.expand() |
|
277 |
|
278 def currentWidget(self): |
|
279 """ |
|
280 Public method to get a reference to the current widget. |
|
281 |
|
282 @return reference to the current widget (QWidget) |
|
283 """ |
|
284 return self.__stackedWidget.currentWidget() |
|
285 |
|
286 def setCurrentWidget(self, widget): |
|
287 """ |
|
288 Public slot to set the current widget. |
|
289 |
|
290 @param widget reference to the widget to become the current widget (QWidget) |
|
291 """ |
|
292 self.__stackedWidget.setCurrentWidget(widget) |
|
293 self.__tabBar.setCurrentIndex(self.__stackedWidget.currentIndex()) |
|
294 if self.isMinimized(): |
|
295 self.expand() |
|
296 |
|
297 def indexOf(self, widget): |
|
298 """ |
|
299 Public method to get the index of the given widget. |
|
300 |
|
301 @param widget reference to the widget to get the index of (QWidget) |
|
302 @return index of the given widget (integer) |
|
303 """ |
|
304 return self.__stackedWidget.indexOf(widget) |
|
305 |
|
306 def isTabEnabled(self, index): |
|
307 """ |
|
308 Public method to check, if a tab is enabled. |
|
309 |
|
310 @param index index of the tab to check (integer) |
|
311 @return flag indicating the enabled state (boolean) |
|
312 """ |
|
313 return self.__tabBar.isTabEnabled(index) |
|
314 |
|
315 def setTabEnabled(self, index, enabled): |
|
316 """ |
|
317 Public method to set the enabled state of a tab. |
|
318 |
|
319 @param index index of the tab to set (integer) |
|
320 @param enabled enabled state to set (boolean) |
|
321 """ |
|
322 self.__tabBar.setTabEnabled(index, enabled) |
|
323 |
|
324 def orientation(self): |
|
325 """ |
|
326 Public method to get the orientation of the sidebar. |
|
327 |
|
328 @return orientation of the sidebar (North, East, South, West) |
|
329 """ |
|
330 return self.__orientation |
|
331 |
|
332 def setOrientation(self, orient): |
|
333 """ |
|
334 Public method to set the orientation of the sidebar. |
|
335 @param orient orientation of the sidebar (North, East, South, West) |
|
336 """ |
|
337 if orient == E5SideBar.North: |
|
338 self.__tabBar.setShape(QTabBar.RoundedNorth) |
|
339 self.barLayout.setDirection(QBoxLayout.LeftToRight) |
|
340 self.layout.setDirection(QBoxLayout.TopToBottom) |
|
341 self.layout.setAlignment(self.barLayout, Qt.AlignLeft) |
|
342 elif orient == E5SideBar.East: |
|
343 self.__tabBar.setShape(QTabBar.RoundedEast) |
|
344 self.barLayout.setDirection(QBoxLayout.TopToBottom) |
|
345 self.layout.setDirection(QBoxLayout.RightToLeft) |
|
346 self.layout.setAlignment(self.barLayout, Qt.AlignTop) |
|
347 elif orient == E5SideBar.South: |
|
348 self.__tabBar.setShape(QTabBar.RoundedSouth) |
|
349 self.barLayout.setDirection(QBoxLayout.LeftToRight) |
|
350 self.layout.setDirection(QBoxLayout.BottomToTop) |
|
351 self.layout.setAlignment(self.barLayout, Qt.AlignLeft) |
|
352 elif orient == E5SideBar.West: |
|
353 self.__tabBar.setShape(QTabBar.RoundedWest) |
|
354 self.barLayout.setDirection(QBoxLayout.TopToBottom) |
|
355 self.layout.setDirection(QBoxLayout.LeftToRight) |
|
356 self.layout.setAlignment(self.barLayout, Qt.AlignTop) |
|
357 self.__orientation = orient |
|
358 |
|
359 def tabIcon(self, index): |
|
360 """ |
|
361 Public method to get the icon of a tab. |
|
362 |
|
363 @param index index of the tab (integer) |
|
364 @return icon of the tab (QIcon) |
|
365 """ |
|
366 return self.__tabBar.tabIcon(index) |
|
367 |
|
368 def setTabIcon(self, index, icon): |
|
369 """ |
|
370 Public method to set the icon of a tab. |
|
371 |
|
372 @param index index of the tab (integer) |
|
373 @param icon icon to be set (QIcon) |
|
374 """ |
|
375 self.__tabBar.setTabIcon(index, icon) |
|
376 |
|
377 def tabText(self, index): |
|
378 """ |
|
379 Public method to get the text of a tab. |
|
380 |
|
381 @param index index of the tab (integer) |
|
382 @return text of the tab (string) |
|
383 """ |
|
384 return self.__tabBar.tabText(index) |
|
385 |
|
386 def setTabText(self, index, text): |
|
387 """ |
|
388 Public method to set the text of a tab. |
|
389 |
|
390 @param index index of the tab (integer) |
|
391 @param text text to set (string) |
|
392 """ |
|
393 self.__tabBar.setTabText(index, text) |
|
394 |
|
395 def tabToolTip(self, index): |
|
396 """ |
|
397 Public method to get the tooltip text of a tab. |
|
398 |
|
399 @param index index of the tab (integer) |
|
400 @return tooltip text of the tab (string) |
|
401 """ |
|
402 return self.__tabBar.tabToolTip(index) |
|
403 |
|
404 def setTabToolTip(self, index, tip): |
|
405 """ |
|
406 Public method to set the tooltip text of a tab. |
|
407 |
|
408 @param index index of the tab (integer) |
|
409 @param tooltip text text to set (string) |
|
410 """ |
|
411 self.__tabBar.setTabToolTip(index, tip) |
|
412 |
|
413 def tabWhatsThis(self, index): |
|
414 """ |
|
415 Public method to get the WhatsThis text of a tab. |
|
416 |
|
417 @param index index of the tab (integer) |
|
418 @return WhatsThis text of the tab (string) |
|
419 """ |
|
420 return self.__tabBar.tabWhatsThis(index) |
|
421 |
|
422 def setTabWhatsThis(self, index, text): |
|
423 """ |
|
424 Public method to set the WhatsThis text of a tab. |
|
425 |
|
426 @param index index of the tab (integer) |
|
427 @param WhatsThis text text to set (string) |
|
428 """ |
|
429 self.__tabBar.setTabWhatsThis(index, text) |
|
430 |
|
431 def widget(self, index): |
|
432 """ |
|
433 Public method to get a reference to the widget associated with a tab. |
|
434 |
|
435 @param index index of the tab (integer) |
|
436 @return reference to the widget (QWidget) |
|
437 """ |
|
438 return self.__stackedWidget.widget(index) |
|
439 |
|
440 def saveState(self): |
|
441 """ |
|
442 Public method to save the state of the sidebar. |
|
443 |
|
444 @return saved state as a byte array (QByteArray) |
|
445 """ |
|
446 if len(self.splitterSizes) == 0: |
|
447 self.splitterSizes = self.splitter.sizes() |
|
448 self.__bigSize = self.size() |
|
449 if self.__orientation in [E5SideBar.North, E5SideBar.South]: |
|
450 self.__minSize = self.minimumHeight() |
|
451 self.__maxSize = self.maximumHeight() |
|
452 else: |
|
453 self.__minSize = self.minimumWidth() |
|
454 self.__maxSize = self.maximumWidth() |
|
455 |
|
456 data = QByteArray() |
|
457 stream = QDataStream(data, QIODevice.WriteOnly) |
|
458 |
|
459 stream.writeUInt16(self.Version) |
|
460 stream.writeBool(self.__minimized) |
|
461 stream << self.__bigSize |
|
462 stream.writeUInt16(self.__minSize) |
|
463 stream.writeUInt16(self.__maxSize) |
|
464 stream.writeUInt16(len(self.splitterSizes)) |
|
465 for size in self.splitterSizes: |
|
466 stream.writeUInt16(size) |
|
467 stream.writeBool(self.__autoHide) |
|
468 |
|
469 return data |
|
470 |
|
471 def restoreState(self, state): |
|
472 """ |
|
473 Public method to restore the state of the sidebar. |
|
474 |
|
475 @param state byte array containing the saved state (QByteArray) |
|
476 @return flag indicating success (boolean) |
|
477 """ |
|
478 if state.isEmpty(): |
|
479 return False |
|
480 |
|
481 data = QByteArray(state) |
|
482 stream = QDataStream(data, QIODevice.ReadOnly) |
|
483 version = stream.readUInt16() |
|
484 minimized = stream.readBool() |
|
485 |
|
486 if minimized: |
|
487 self.shrink() |
|
488 |
|
489 stream >> self.__bigSize |
|
490 self.__minSize = stream.readUInt16() |
|
491 self.__maxSize = stream.readUInt16() |
|
492 count = stream.readUInt16() |
|
493 self.splitterSizes = [] |
|
494 for i in range(count): |
|
495 self.splitterSizes.append(stream.readUInt16()) |
|
496 |
|
497 self.__autoHide = stream.readBool() |
|
498 self.__autoHideButton.setChecked(not self.__autoHide) |
|
499 |
|
500 if not minimized: |
|
501 self.expand() |
|
502 |
|
503 return True |
|
504 |
|
505 ####################################################################### |
|
506 ## methods below implement the autohide functionality |
|
507 ####################################################################### |
|
508 |
|
509 def __autoHideToggled(self, checked): |
|
510 """ |
|
511 Private slot to handle the toggling of the autohide button. |
|
512 |
|
513 @param checked flag indicating the checked state of the button (boolean) |
|
514 """ |
|
515 self.__autoHide = not checked |
|
516 if self.__autoHide: |
|
517 self.__autoHideButton.setIcon(UI.PixmapCache.getIcon("autoHideOn")) |
|
518 else: |
|
519 self.__autoHideButton.setIcon(UI.PixmapCache.getIcon("autoHideOff")) |
|
520 |
|
521 def __appFocusChanged(self, old, now): |
|
522 """ |
|
523 Private slot to handle a change of the focus. |
|
524 |
|
525 @param old reference to the widget, that lost focus (QWidget or None) |
|
526 @param now reference to the widget having the focus (QWidget or None) |
|
527 """ |
|
528 self.__hasFocus = self.isAncestorOf(now) |
|
529 if self.__autoHide and not self.__hasFocus and not self.isMinimized(): |
|
530 self.shrink() |
|
531 elif self.__autoHide and self.__hasFocus and self.isMinimized(): |
|
532 self.expand() |
|
533 |
|
534 def enterEvent(self, event): |
|
535 """ |
|
536 Protected method to handle the mouse entering this widget. |
|
537 |
|
538 @param event reference to the event (QEvent) |
|
539 """ |
|
540 if self.__autoHide and self.isMinimized(): |
|
541 self.expand() |
|
542 |
|
543 def leaveEvent(self, event): |
|
544 """ |
|
545 Protected method to handle the mouse leaving this widget. |
|
546 |
|
547 @param event reference to the event (QEvent) |
|
548 """ |
|
549 if self.__autoHide and not self.__hasFocus and not self.isMinimized(): |
|
550 self.shrink() |