1
|
|
|
""" |
2
|
|
|
============== |
3
|
|
|
Tool Box Widget |
4
|
|
|
============== |
5
|
|
|
|
6
|
|
|
A reimplementation of the :class:`QToolBox` widget that keeps all the tabs |
7
|
|
|
in a single :class:`QScrollArea` instance and can keep multiple open tabs. |
8
|
|
|
|
9
|
|
|
""" |
10
|
|
|
|
11
|
|
|
from collections import namedtuple |
12
|
|
|
from operator import eq, attrgetter |
13
|
|
|
|
14
|
|
|
from PyQt4.QtGui import ( |
|
|
|
|
15
|
|
|
QWidget, QFrame, QSizePolicy, QIcon, QFontMetrics, QPainter, QStyle, |
16
|
|
|
QStyleOptionToolButton, QStyleOptionToolBoxV2, QPalette, QBrush, QPen, |
17
|
|
|
QColor, QScrollArea, QVBoxLayout, QToolButton, QAction, QActionGroup |
18
|
|
|
) |
19
|
|
|
|
20
|
|
|
from PyQt4.QtCore import ( |
|
|
|
|
21
|
|
|
Qt, QObject, QSize, QRect, QPoint, QSignalMapper, QEvent |
22
|
|
|
) |
23
|
|
|
|
24
|
|
|
from PyQt4.QtCore import pyqtSignal as Signal, pyqtProperty as Property |
|
|
|
|
25
|
|
|
|
26
|
|
|
from .utils import brush_darker |
27
|
|
|
|
28
|
|
|
_ToolBoxPage = namedtuple( |
29
|
|
|
"_ToolBoxPage", |
30
|
|
|
["index", |
31
|
|
|
"widget", |
32
|
|
|
"action", |
33
|
|
|
"button"] |
34
|
|
|
) |
35
|
|
|
|
36
|
|
|
|
37
|
|
|
FOCUS_OUTLINE_COLOR = "#609ED7" |
38
|
|
|
|
39
|
|
|
|
40
|
|
|
class ToolBoxTabButton(QToolButton): |
41
|
|
|
""" |
42
|
|
|
A tab button for an item in a :class:`ToolBox`. |
43
|
|
|
""" |
44
|
|
|
|
45
|
|
|
def setNativeStyling(self, state): |
46
|
|
|
""" |
47
|
|
|
Render tab buttons as native (or css styled) :class:`QToolButtons`. |
48
|
|
|
If set to `False` (default) the button is pained using a custom |
49
|
|
|
paint routine. |
50
|
|
|
|
51
|
|
|
""" |
52
|
|
|
self.__nativeStyling = state |
53
|
|
|
self.update() |
54
|
|
|
|
55
|
|
|
def nativeStyling(self): |
56
|
|
|
""" |
57
|
|
|
Use :class:`QStyle`'s to paint the class:`QToolButton` look. |
58
|
|
|
""" |
59
|
|
|
return self.__nativeStyling |
60
|
|
|
|
61
|
|
|
nativeStyling_ = Property(bool, |
62
|
|
|
fget=nativeStyling, |
63
|
|
|
fset=setNativeStyling, |
64
|
|
|
designable=True) |
65
|
|
|
|
66
|
|
|
def __init__(self, *args, **kwargs): |
67
|
|
|
self.__nativeStyling = False |
68
|
|
|
self.position = QStyleOptionToolBoxV2.OnlyOneTab |
69
|
|
|
self.selected = QStyleOptionToolBoxV2.NotAdjacent |
70
|
|
|
|
71
|
|
|
QToolButton.__init__(self, *args, **kwargs) |
72
|
|
|
|
73
|
|
|
def paintEvent(self, event): |
74
|
|
|
if self.__nativeStyling: |
75
|
|
|
QToolButton.paintEvent(self, event) |
76
|
|
|
else: |
77
|
|
|
self.__paintEventNoStyle() |
78
|
|
|
|
79
|
|
|
def __paintEventNoStyle(self): |
80
|
|
|
p = QPainter(self) |
81
|
|
|
opt = QStyleOptionToolButton() |
82
|
|
|
self.initStyleOption(opt) |
83
|
|
|
|
84
|
|
|
fm = QFontMetrics(opt.font) |
85
|
|
|
palette = opt.palette |
86
|
|
|
|
87
|
|
|
# highlight brush is used as the background for the icon and background |
88
|
|
|
# when the tab is expanded and as mouse hover color (lighter). |
89
|
|
|
brush_highlight = palette.highlight() |
90
|
|
|
if opt.state & QStyle.State_Sunken: |
91
|
|
|
# State 'down' pressed during a mouse press (slightly darker). |
92
|
|
|
background_brush = brush_darker(brush_highlight, 110) |
93
|
|
|
elif opt.state & QStyle.State_MouseOver: |
94
|
|
|
background_brush = brush_darker(brush_highlight, 95) |
95
|
|
|
elif opt.state & QStyle.State_On: |
96
|
|
|
background_brush = brush_highlight |
97
|
|
|
else: |
98
|
|
|
# The default button brush. |
99
|
|
|
background_brush = palette.button() |
100
|
|
|
|
101
|
|
|
rect = opt.rect |
102
|
|
|
icon = opt.icon |
103
|
|
|
icon_size = opt.iconSize |
104
|
|
|
|
105
|
|
|
# TODO: add shift for pressed as set by the style (PM_ButtonShift...) |
|
|
|
|
106
|
|
|
|
107
|
|
|
pm = None |
108
|
|
|
if not icon.isNull(): |
109
|
|
|
if opt.state & QStyle.State_Enabled: |
110
|
|
|
mode = QIcon.Normal |
111
|
|
|
else: |
112
|
|
|
mode = QIcon.Disabled |
113
|
|
|
|
114
|
|
|
pm = opt.icon.pixmap( |
115
|
|
|
rect.size().boundedTo(icon_size), mode, |
116
|
|
|
QIcon.On if opt.state & QStyle.State_On else QIcon.Off) |
117
|
|
|
|
118
|
|
|
icon_area_rect = QRect(rect) |
119
|
|
|
icon_area_rect.setRight(int(icon_area_rect.height() * 1.26)) |
120
|
|
|
|
121
|
|
|
text_rect = QRect(rect) |
122
|
|
|
text_rect.setLeft(icon_area_rect.right() + 10) |
123
|
|
|
|
124
|
|
|
# Background (TODO: Should the tab button have native |
125
|
|
|
# toolbutton shape, drawn using PE_PanelButtonTool or even |
126
|
|
|
# QToolBox tab shape) |
127
|
|
|
|
128
|
|
|
# Default outline pen |
129
|
|
|
pen = QPen(palette.color(QPalette.Mid)) |
130
|
|
|
|
131
|
|
|
p.save() |
132
|
|
|
p.setPen(Qt.NoPen) |
133
|
|
|
p.setBrush(QBrush(background_brush)) |
134
|
|
|
p.drawRect(rect) |
135
|
|
|
|
136
|
|
|
# Draw the background behind the icon if the background_brush |
137
|
|
|
# is different. |
138
|
|
|
if not opt.state & QStyle.State_On: |
139
|
|
|
p.setBrush(brush_highlight) |
140
|
|
|
p.drawRect(icon_area_rect) |
141
|
|
|
# Line between the icon and text |
142
|
|
|
p.setPen(pen) |
143
|
|
|
p.drawLine(icon_area_rect.topRight(), |
144
|
|
|
icon_area_rect.bottomRight()) |
145
|
|
|
|
146
|
|
|
if opt.state & QStyle.State_HasFocus: |
147
|
|
|
# Set the focus frame pen and draw the border |
148
|
|
|
pen = QPen(QColor(FOCUS_OUTLINE_COLOR)) |
149
|
|
|
p.setPen(pen) |
150
|
|
|
p.setBrush(Qt.NoBrush) |
151
|
|
|
# Adjust for pen |
152
|
|
|
rect = rect.adjusted(0, 0, -1, -1) |
153
|
|
|
p.drawRect(rect) |
154
|
|
|
|
155
|
|
|
else: |
156
|
|
|
p.setPen(pen) |
157
|
|
|
# Draw the top/bottom border |
158
|
|
|
if self.position == QStyleOptionToolBoxV2.OnlyOneTab or \ |
159
|
|
|
self.position == QStyleOptionToolBoxV2.Beginning or \ |
160
|
|
|
self.selected & \ |
161
|
|
|
QStyleOptionToolBoxV2.PreviousIsSelected: |
162
|
|
|
|
163
|
|
|
p.drawLine(rect.topLeft(), rect.topRight()) |
164
|
|
|
|
165
|
|
|
p.drawLine(rect.bottomLeft(), rect.bottomRight()) |
166
|
|
|
|
167
|
|
|
p.restore() |
168
|
|
|
|
169
|
|
|
p.save() |
170
|
|
|
text = fm.elidedText(opt.text, Qt.ElideRight, text_rect.width()) |
171
|
|
|
p.setPen(QPen(palette.color(QPalette.ButtonText))) |
172
|
|
|
p.setFont(opt.font) |
173
|
|
|
|
174
|
|
|
p.drawText(text_rect, |
175
|
|
|
int(Qt.AlignVCenter | Qt.AlignLeft) | \ |
176
|
|
|
int(Qt.TextSingleLine), |
177
|
|
|
text) |
178
|
|
|
if pm: |
179
|
|
|
pm_rect = QRect(QPoint(0, 0), pm.size()) |
180
|
|
|
centered_rect = QRect(pm_rect) |
181
|
|
|
centered_rect.moveCenter(icon_area_rect.center()) |
182
|
|
|
p.drawPixmap(centered_rect, pm, pm_rect) |
183
|
|
|
p.restore() |
184
|
|
|
|
185
|
|
|
|
186
|
|
|
class _ToolBoxScrollArea(QScrollArea): |
187
|
|
|
def eventFilter(self, obj, event): |
188
|
|
|
if obj is self.widget() and event.type() == QEvent.Resize: |
189
|
|
|
if event.size() == event.oldSize() and self.widgetResizable(): |
190
|
|
|
# This is driving me insane. This should not have happened. |
191
|
|
|
# Before the event is sent QWidget specifically makes sure the |
192
|
|
|
# sizes are different, but somehow I still get this, and enter |
193
|
|
|
# an infinite recursion if I enter QScrollArea.eventFilter. |
194
|
|
|
# I can only duplicate this on one development machine a |
195
|
|
|
# Mac OSX using fink and Qt 4.7.3 |
196
|
|
|
return False |
197
|
|
|
|
198
|
|
|
return QScrollArea.eventFilter(self, obj, event) |
199
|
|
|
|
200
|
|
|
|
201
|
|
|
class ToolBox(QFrame): |
202
|
|
|
""" |
203
|
|
|
A tool box widget. |
204
|
|
|
""" |
205
|
|
|
# Emitted when a tab is toggled. |
206
|
|
|
tabToogled = Signal(int, bool) |
207
|
|
|
|
208
|
|
|
def setExclusive(self, exclusive): |
209
|
|
|
""" |
210
|
|
|
Set exclusive tabs (only one tab can be open at a time). |
211
|
|
|
""" |
212
|
|
|
if self.__exclusive != exclusive: |
213
|
|
|
self.__exclusive = exclusive |
214
|
|
|
self.__tabActionGroup.setExclusive(exclusive) |
215
|
|
|
checked = self.__tabActionGroup.checkedAction() |
216
|
|
|
if checked is None: |
217
|
|
|
# The action group can be out of sync with the actions state |
218
|
|
|
# when switching between exclusive states. |
219
|
|
|
actions_checked = [page.action for page in self.__pages |
220
|
|
|
if page.action.isChecked()] |
221
|
|
|
if actions_checked: |
222
|
|
|
checked = actions_checked[0] |
223
|
|
|
|
224
|
|
|
# Trigger/toggle remaining open pages |
225
|
|
|
if exclusive and checked is not None: |
226
|
|
|
for page in self.__pages: |
227
|
|
|
if checked != page.action and page.action.isChecked(): |
228
|
|
|
page.action.trigger() |
229
|
|
|
|
230
|
|
|
def exclusive(self): |
231
|
|
|
""" |
232
|
|
|
Are the tabs in the toolbox exclusive. |
233
|
|
|
""" |
234
|
|
|
return self.__exclusive |
235
|
|
|
|
236
|
|
|
exclusive_ = Property(bool, |
237
|
|
|
fget=exclusive, |
238
|
|
|
fset=setExclusive, |
239
|
|
|
designable=True, |
240
|
|
|
doc="Exclusive tabs") |
241
|
|
|
|
242
|
|
|
def __init__(self, parent=None, **kwargs): |
243
|
|
|
QFrame.__init__(self, parent, **kwargs) |
244
|
|
|
|
245
|
|
|
self.__pages = [] |
246
|
|
|
self.__tabButtonHeight = -1 |
247
|
|
|
self.__tabIconSize = QSize() |
248
|
|
|
self.__exclusive = False |
249
|
|
|
self.__setupUi() |
250
|
|
|
|
251
|
|
|
def __setupUi(self): |
252
|
|
|
layout = QVBoxLayout() |
253
|
|
|
layout.setContentsMargins(0, 0, 0, 0) |
254
|
|
|
|
255
|
|
|
# Scroll area for the contents. |
256
|
|
|
self.__scrollArea = \ |
257
|
|
|
_ToolBoxScrollArea(self, objectName="toolbox-scroll-area") |
258
|
|
|
|
259
|
|
|
self.__scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) |
260
|
|
|
self.__scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) |
261
|
|
|
self.__scrollArea.setSizePolicy(QSizePolicy.MinimumExpanding, |
262
|
|
|
QSizePolicy.MinimumExpanding) |
263
|
|
|
self.__scrollArea.setFrameStyle(QScrollArea.NoFrame) |
264
|
|
|
self.__scrollArea.setWidgetResizable(True) |
265
|
|
|
|
266
|
|
|
# A widget with all of the contents. |
267
|
|
|
# The tabs/contents are placed in the layout inside this widget |
268
|
|
|
self.__contents = QWidget(self.__scrollArea, |
269
|
|
|
objectName="toolbox-contents") |
270
|
|
|
|
271
|
|
|
# The layout where all the tab/pages are placed |
272
|
|
|
self.__contentsLayout = QVBoxLayout() |
273
|
|
|
self.__contentsLayout.setContentsMargins(0, 0, 0, 0) |
274
|
|
|
self.__contentsLayout.setSizeConstraint(QVBoxLayout.SetMinAndMaxSize) |
275
|
|
|
self.__contentsLayout.setSpacing(0) |
276
|
|
|
|
277
|
|
|
self.__contents.setLayout(self.__contentsLayout) |
278
|
|
|
|
279
|
|
|
self.__scrollArea.setWidget(self.__contents) |
280
|
|
|
|
281
|
|
|
layout.addWidget(self.__scrollArea) |
282
|
|
|
|
283
|
|
|
self.setLayout(layout) |
284
|
|
|
self.setSizePolicy(QSizePolicy.Fixed, |
285
|
|
|
QSizePolicy.MinimumExpanding) |
286
|
|
|
|
287
|
|
|
self.__tabActionGroup = \ |
288
|
|
|
QActionGroup(self, objectName="toolbox-tab-action-group") |
289
|
|
|
|
290
|
|
|
self.__tabActionGroup.setExclusive(self.__exclusive) |
291
|
|
|
|
292
|
|
|
self.__actionMapper = QSignalMapper(self) |
293
|
|
|
self.__actionMapper.mapped[QObject].connect(self.__onTabActionToogled) |
294
|
|
|
|
295
|
|
|
def setTabButtonHeight(self, height): |
296
|
|
|
""" |
297
|
|
|
Set the tab button height. |
298
|
|
|
""" |
299
|
|
|
if self.__tabButtonHeight != height: |
300
|
|
|
self.__tabButtonHeight = height |
301
|
|
|
for page in self.__pages: |
302
|
|
|
page.button.setFixedHeight(height) |
303
|
|
|
|
304
|
|
|
def tabButtonHeight(self): |
305
|
|
|
""" |
306
|
|
|
Return the tab button height. |
307
|
|
|
""" |
308
|
|
|
return self.__tabButtonHeight |
309
|
|
|
|
310
|
|
|
def setTabIconSize(self, size): |
311
|
|
|
""" |
312
|
|
|
Set the tab button icon size. |
313
|
|
|
""" |
314
|
|
|
if self.__tabIconSize != size: |
315
|
|
|
self.__tabIconSize = size |
316
|
|
|
for page in self.__pages: |
317
|
|
|
page.button.setIconSize(size) |
318
|
|
|
|
319
|
|
|
def tabIconSize(self): |
320
|
|
|
""" |
321
|
|
|
Return the tab icon size. |
322
|
|
|
""" |
323
|
|
|
return self.__tabIconSize |
324
|
|
|
|
325
|
|
|
def tabButton(self, index): |
326
|
|
|
""" |
327
|
|
|
Return the tab button at `index` |
328
|
|
|
""" |
329
|
|
|
return self.__pages[index].button |
330
|
|
|
|
331
|
|
|
def tabAction(self, index): |
332
|
|
|
""" |
333
|
|
|
Return open/close action for the tab at `index`. |
334
|
|
|
""" |
335
|
|
|
return self.__pages[index].action |
336
|
|
|
|
337
|
|
|
def addItem(self, widget, text, icon=None, toolTip=None): |
338
|
|
|
""" |
339
|
|
|
Append the `widget` in a new tab and return its index. |
340
|
|
|
|
341
|
|
|
Parameters |
342
|
|
|
---------- |
343
|
|
|
widget : :class:`QWidget` |
344
|
|
|
A widget to be inserted. The toolbox takes ownership |
345
|
|
|
of the widget. |
346
|
|
|
|
347
|
|
|
text : str |
348
|
|
|
Name/title of the new tab. |
349
|
|
|
|
350
|
|
|
icon : :class:`QIcon`, optional |
351
|
|
|
An icon for the tab button. |
352
|
|
|
|
353
|
|
|
toolTip : str, optional |
354
|
|
|
Tool tip for the tab button. |
355
|
|
|
|
356
|
|
|
""" |
357
|
|
|
return self.insertItem(self.count(), widget, text, icon, toolTip) |
358
|
|
|
|
359
|
|
|
def insertItem(self, index, widget, text, icon=None, toolTip=None): |
360
|
|
|
""" |
361
|
|
|
Insert the `widget` in a new tab at position `index`. |
362
|
|
|
|
363
|
|
|
See also |
364
|
|
|
-------- |
365
|
|
|
ToolBox.addItem |
366
|
|
|
|
367
|
|
|
""" |
368
|
|
|
button = self.createTabButton(widget, text, icon, toolTip) |
369
|
|
|
|
370
|
|
|
self.__contentsLayout.insertWidget(index * 2, button) |
371
|
|
|
self.__contentsLayout.insertWidget(index * 2 + 1, widget) |
372
|
|
|
|
373
|
|
|
widget.hide() |
374
|
|
|
|
375
|
|
|
page = _ToolBoxPage(index, widget, button.defaultAction(), button) |
376
|
|
|
self.__pages.insert(index, page) |
377
|
|
|
|
378
|
|
|
for i in range(index + 1, self.count()): |
379
|
|
|
self.__pages[i] = self.__pages[i]._replace(index=i) |
380
|
|
|
|
381
|
|
|
self.__updatePositions() |
382
|
|
|
|
383
|
|
|
# Show (open) the first tab. |
384
|
|
|
if self.count() == 1 and index == 0: |
385
|
|
|
page.action.trigger() |
386
|
|
|
|
387
|
|
|
self.__updateSelected() |
388
|
|
|
|
389
|
|
|
self.updateGeometry() |
390
|
|
|
return index |
391
|
|
|
|
392
|
|
|
def removeItem(self, index): |
393
|
|
|
""" |
394
|
|
|
Remove the widget at `index`. |
395
|
|
|
|
396
|
|
|
.. note:: The widget hidden but is is not deleted. |
397
|
|
|
|
398
|
|
|
""" |
399
|
|
|
self.__contentsLayout.takeAt(2 * index + 1) |
400
|
|
|
self.__contentsLayout.takeAt(2 * index) |
401
|
|
|
page = self.__pages.pop(index) |
402
|
|
|
|
403
|
|
|
# Update the page indexes |
404
|
|
|
for i in range(index, self.count()): |
405
|
|
|
self.__pages[i] = self.__pages[i]._replace(index=i) |
406
|
|
|
|
407
|
|
|
page.button.deleteLater() |
408
|
|
|
|
409
|
|
|
# Hide the widget and reparent to self |
410
|
|
|
# This follows QToolBox.removeItem |
411
|
|
|
page.widget.hide() |
412
|
|
|
page.widget.setParent(self) |
413
|
|
|
|
414
|
|
|
self.__updatePositions() |
415
|
|
|
self.__updateSelected() |
416
|
|
|
|
417
|
|
|
self.updateGeometry() |
418
|
|
|
|
419
|
|
|
def count(self): |
420
|
|
|
""" |
421
|
|
|
Return the number of widgets inserted in the toolbox. |
422
|
|
|
""" |
423
|
|
|
return len(self.__pages) |
424
|
|
|
|
425
|
|
|
def widget(self, index): |
426
|
|
|
""" |
427
|
|
|
Return the widget at `index`. |
428
|
|
|
""" |
429
|
|
|
return self.__pages[index].widget |
430
|
|
|
|
431
|
|
|
def createTabButton(self, widget, text, icon=None, toolTip=None): |
|
|
|
|
432
|
|
|
""" |
433
|
|
|
Create the tab button for `widget`. |
434
|
|
|
""" |
435
|
|
|
action = QAction(text, self) |
436
|
|
|
action.setCheckable(True) |
437
|
|
|
|
438
|
|
|
if icon: |
439
|
|
|
action.setIcon(icon) |
440
|
|
|
|
441
|
|
|
if toolTip: |
442
|
|
|
action.setToolTip(toolTip) |
443
|
|
|
self.__tabActionGroup.addAction(action) |
444
|
|
|
self.__actionMapper.setMapping(action, action) |
445
|
|
|
action.toggled.connect(self.__actionMapper.map) |
446
|
|
|
|
447
|
|
|
button = ToolBoxTabButton(self, objectName="toolbox-tab-button") |
448
|
|
|
button.setDefaultAction(action) |
449
|
|
|
button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) |
450
|
|
|
button.setSizePolicy(QSizePolicy.Expanding, |
451
|
|
|
QSizePolicy.Fixed) |
452
|
|
|
|
453
|
|
|
if self.__tabIconSize.isValid(): |
454
|
|
|
button.setIconSize(self.__tabIconSize) |
455
|
|
|
|
456
|
|
|
if self.__tabButtonHeight > 0: |
457
|
|
|
button.setFixedHeight(self.__tabButtonHeight) |
458
|
|
|
|
459
|
|
|
return button |
460
|
|
|
|
461
|
|
|
def ensureWidgetVisible(self, child, xmargin=50, ymargin=50): |
462
|
|
|
""" |
463
|
|
|
Scroll the contents so child widget instance is visible inside |
464
|
|
|
the viewport. |
465
|
|
|
|
466
|
|
|
""" |
467
|
|
|
self.__scrollArea.ensureWidgetVisible(child, xmargin, ymargin) |
468
|
|
|
|
469
|
|
|
def sizeHint(self): |
470
|
|
|
hint = self.__contentsLayout.sizeHint() |
471
|
|
|
|
472
|
|
|
if self.count(): |
473
|
|
|
# Compute max width of hidden widgets also. |
474
|
|
|
scroll = self.__scrollArea |
475
|
|
|
scroll_w = scroll.verticalScrollBar().sizeHint().width() |
476
|
|
|
frame_w = self.frameWidth() * 2 + scroll.frameWidth() * 2 |
477
|
|
|
max_w = max([p.widget.sizeHint().width() for p in self.__pages]) |
478
|
|
|
hint = QSize(max(max_w, hint.width()) + scroll_w + frame_w, |
479
|
|
|
hint.height()) |
480
|
|
|
|
481
|
|
|
return QSize(200, 200).expandedTo(hint) |
482
|
|
|
|
483
|
|
|
def __onTabActionToogled(self, action): |
484
|
|
|
page = find(self.__pages, action, key=attrgetter("action")) |
485
|
|
|
on = action.isChecked() |
486
|
|
|
page.widget.setVisible(on) |
487
|
|
|
index = page.index |
488
|
|
|
|
489
|
|
|
if index > 0: |
490
|
|
|
# Update the `previous` tab buttons style hints |
491
|
|
|
previous = self.__pages[index - 1].button |
492
|
|
|
flag = QStyleOptionToolBoxV2.NextIsSelected |
493
|
|
|
if on: |
494
|
|
|
previous.selected |= flag |
495
|
|
|
else: |
496
|
|
|
previous.selected &= ~flag |
497
|
|
|
|
498
|
|
|
previous.update() |
499
|
|
|
|
500
|
|
|
if index < self.count() - 1: |
501
|
|
|
next = self.__pages[index + 1].button |
|
|
|
|
502
|
|
|
flag = QStyleOptionToolBoxV2.PreviousIsSelected |
503
|
|
|
if on: |
504
|
|
|
next.selected |= flag |
505
|
|
|
else: |
506
|
|
|
next.selected &= ~flag |
507
|
|
|
|
508
|
|
|
next.update() |
509
|
|
|
|
510
|
|
|
self.tabToogled.emit(index, on) |
511
|
|
|
|
512
|
|
|
self.__contentsLayout.invalidate() |
513
|
|
|
|
514
|
|
|
def __updateSelected(self): |
515
|
|
|
"""Update the tab buttons selected style flags. |
516
|
|
|
""" |
517
|
|
|
if self.count() == 0: |
518
|
|
|
return |
519
|
|
|
|
520
|
|
|
opt = QStyleOptionToolBoxV2 |
521
|
|
|
|
522
|
|
|
def update(button, next_sel, prev_sel): |
523
|
|
|
if next_sel: |
524
|
|
|
button.selected |= opt.NextIsSelected |
525
|
|
|
else: |
526
|
|
|
button.selected &= ~opt.NextIsSelected |
527
|
|
|
|
528
|
|
|
if prev_sel: |
529
|
|
|
button.selected |= opt.PreviousIsSelected |
530
|
|
|
else: |
531
|
|
|
button.selected &= ~ opt.PreviousIsSelected |
532
|
|
|
|
533
|
|
|
button.update() |
534
|
|
|
|
535
|
|
|
if self.count() == 1: |
536
|
|
|
update(self.__pages[0].button, False, False) |
537
|
|
|
elif self.count() >= 2: |
538
|
|
|
pages = self.__pages |
539
|
|
|
for i in range(1, self.count() - 1): |
540
|
|
|
update(pages[i].button, |
541
|
|
|
pages[i + 1].action.isChecked(), |
542
|
|
|
pages[i - 1].action.isChecked()) |
543
|
|
|
|
544
|
|
|
def __updatePositions(self): |
545
|
|
|
"""Update the tab buttons position style flags. |
546
|
|
|
""" |
547
|
|
|
if self.count() == 0: |
548
|
|
|
return |
549
|
|
|
elif self.count() == 1: |
550
|
|
|
self.__pages[0].button.position = QStyleOptionToolBoxV2.OnlyOneTab |
551
|
|
|
else: |
552
|
|
|
self.__pages[0].button.position = QStyleOptionToolBoxV2.Beginning |
553
|
|
|
self.__pages[-1].button.position = QStyleOptionToolBoxV2.End |
554
|
|
|
for p in self.__pages[1:-1]: |
555
|
|
|
p.button.position = QStyleOptionToolBoxV2.Middle |
556
|
|
|
|
557
|
|
|
for p in self.__pages: |
558
|
|
|
p.button.update() |
559
|
|
|
|
560
|
|
|
|
561
|
|
|
def identity(arg): |
562
|
|
|
return arg |
563
|
|
|
|
564
|
|
|
|
565
|
|
|
def find(iterable, *what, **kwargs): |
566
|
|
|
""" |
567
|
|
|
find(iterable, [what, [key=None, [predicate=operator.eq]]]) |
568
|
|
|
""" |
569
|
|
|
if what: |
570
|
|
|
what = what[0] |
571
|
|
|
key, predicate = kwargs.get("key", identity), kwargs.get("predicate", eq) |
572
|
|
|
for item in iterable: |
573
|
|
|
item_key = key(item) |
574
|
|
|
if predicate(item_key, what): |
575
|
|
|
return item |
576
|
|
|
else: |
|
|
|
|
577
|
|
|
raise ValueError(what) |
578
|
|
|
|
This can be caused by one of the following:
1. Missing Dependencies
This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.
2. Missing __init__.py files
This error could also result from missing
__init__.py
files in your module folders. Make sure that you place one file in each sub-folder.