|
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__.pyfiles in your module folders. Make sure that you place one file in each sub-folder.