src/eric7/EricWidgets/EricTabWidget.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8881
54e42bc2437a
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2005 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a TabWidget class substituting QTabWidget.
8 """
9
10 import contextlib
11
12 from PyQt6.QtCore import pyqtSignal, Qt, QPoint, QMimeData
13 from PyQt6.QtGui import QDrag
14 from PyQt6.QtWidgets import QTabWidget, QTabBar, QApplication, QStyle
15
16 from EricWidgets.EricAnimatedLabel import EricAnimatedLabel
17
18
19 class EricWheelTabBar(QTabBar):
20 """
21 Class implementing a tab bar class substituting QTabBar to support wheel
22 events.
23 """
24 def __init__(self, parent=None):
25 """
26 Constructor
27
28 @param parent reference to the parent widget (QWidget)
29 """
30 super().__init__(parent)
31 self._tabWidget = parent
32
33 def wheelEvent(self, event):
34 """
35 Protected slot to support wheel events.
36
37 @param event reference to the wheel event (QWheelEvent)
38 """
39 with contextlib.suppress(AttributeError):
40 delta = event.angleDelta().y()
41 if delta > 0:
42 self._tabWidget.prevTab()
43 elif delta < 0:
44 self._tabWidget.nextTab()
45
46 event.accept()
47
48
49 class EricDnDTabBar(EricWheelTabBar):
50 """
51 Class implementing a tab bar class substituting QTabBar.
52
53 @signal tabMoveRequested(int, int) emitted to signal a tab move request
54 giving the old and new index position
55 """
56 tabMoveRequested = pyqtSignal(int, int)
57
58 def __init__(self, parent=None):
59 """
60 Constructor
61
62 @param parent reference to the parent widget (QWidget)
63 """
64 EricWheelTabBar.__init__(self, parent)
65 self.setAcceptDrops(True)
66
67 self.__dragStartPos = QPoint()
68
69 def mousePressEvent(self, event):
70 """
71 Protected method to handle mouse press events.
72
73 @param event reference to the mouse press event (QMouseEvent)
74 """
75 if event.button() == Qt.MouseButton.LeftButton:
76 self.__dragStartPos = QPoint(event.position().toPoint())
77 EricWheelTabBar.mousePressEvent(self, event)
78
79 def mouseMoveEvent(self, event):
80 """
81 Protected method to handle mouse move events.
82
83 @param event reference to the mouse move event (QMouseEvent)
84 """
85 if (
86 event.buttons() == Qt.MouseButton.LeftButton and
87 (event.position().toPoint() - self.__dragStartPos)
88 .manhattanLength() > QApplication.startDragDistance()
89 ):
90 drag = QDrag(self)
91 mimeData = QMimeData()
92 index = self.tabAt(event.position().toPoint())
93 mimeData.setText(self.tabText(index))
94 mimeData.setData("action", b"tab-reordering")
95 mimeData.setData("tabbar-id", str(id(self)).encode("utf-8"))
96 drag.setMimeData(mimeData)
97 drag.exec()
98 EricWheelTabBar.mouseMoveEvent(self, event)
99
100 def dragEnterEvent(self, event):
101 """
102 Protected method to handle drag enter events.
103
104 @param event reference to the drag enter event (QDragEnterEvent)
105 """
106 mimeData = event.mimeData()
107 formats = mimeData.formats()
108 if (
109 "action" in formats and
110 mimeData.data("action") == b"tab-reordering" and
111 "tabbar-id" in formats and
112 int(mimeData.data("tabbar-id")) == id(self)
113 ):
114 event.acceptProposedAction()
115 EricWheelTabBar.dragEnterEvent(self, event)
116
117 def dropEvent(self, event):
118 """
119 Protected method to handle drop events.
120
121 @param event reference to the drop event (QDropEvent)
122 """
123 fromIndex = self.tabAt(self.__dragStartPos)
124 toIndex = self.tabAt(event.position().toPoint())
125 if fromIndex != toIndex:
126 self.tabMoveRequested.emit(fromIndex, toIndex)
127 event.acceptProposedAction()
128 EricWheelTabBar.dropEvent(self, event)
129
130
131 class EricTabWidget(QTabWidget):
132 """
133 Class implementing a tab widget class substituting QTabWidget.
134
135 It provides slots to show the previous and next tab and give
136 them the input focus and it allows to have a context menu for the tabs.
137
138 @signal customTabContextMenuRequested(const QPoint & point, int index)
139 emitted when a context menu for a tab is requested
140 """
141 customTabContextMenuRequested = pyqtSignal(QPoint, int)
142
143 def __init__(self, parent=None, dnd=False):
144 """
145 Constructor
146
147 @param parent reference to the parent widget (QWidget)
148 @param dnd flag indicating the support for Drag & Drop (boolean)
149 """
150 super().__init__(parent)
151
152 if dnd:
153 if not hasattr(self, 'setMovable'):
154 self.__tabBar = EricDnDTabBar(self)
155 self.__tabBar.tabMoveRequested.connect(self.moveTab)
156 self.setTabBar(self.__tabBar)
157 else:
158 self.__tabBar = EricWheelTabBar(self)
159 self.setTabBar(self.__tabBar)
160 self.setMovable(True)
161 else:
162 self.__tabBar = EricWheelTabBar(self)
163 self.setTabBar(self.__tabBar)
164
165 self.__lastCurrentIndex = -1
166 self.__currentIndex = -1
167 self.currentChanged.connect(self.__currentChanged)
168
169 def setCustomTabBar(self, dnd, tabBar):
170 """
171 Public method to set a custom tab bar.
172
173 @param dnd flag indicating the support for Drag & Drop (boolean)
174 @param tabBar reference to the tab bar to set (QTabBar)
175 """
176 self.__tabBar = tabBar
177 self.setTabBar(self.__tabBar)
178 if dnd:
179 if isinstance(tabBar, EricDnDTabBar):
180 self.__tabBar.tabMoveRequested.connect(self.moveTab)
181 else:
182 self.setMovable(True)
183
184 def __currentChanged(self, index):
185 """
186 Private slot to handle the currentChanged signal.
187
188 @param index index of the current tab
189 """
190 if index == -1:
191 self.__lastCurrentIndex = -1
192 else:
193 self.__lastCurrentIndex = self.__currentIndex
194 self.__currentIndex = index
195
196 def switchTab(self):
197 """
198 Public slot used to switch between the current and the previous
199 current tab.
200 """
201 if self.__lastCurrentIndex == -1 or self.__currentIndex == -1:
202 return
203
204 self.setCurrentIndex(self.__lastCurrentIndex)
205 self.currentWidget().setFocus()
206
207 def nextTab(self):
208 """
209 Public slot used to show the next tab.
210 """
211 ind = self.currentIndex() + 1
212 if ind == self.count():
213 ind = 0
214
215 self.setCurrentIndex(ind)
216 self.currentWidget().setFocus()
217
218 def prevTab(self):
219 """
220 Public slot used to show the previous tab.
221 """
222 ind = self.currentIndex() - 1
223 if ind == -1:
224 ind = self.count() - 1
225
226 self.setCurrentIndex(ind)
227 self.currentWidget().setFocus()
228
229 def setTabContextMenuPolicy(self, policy):
230 """
231 Public method to set the context menu policy of the tab.
232
233 @param policy context menu policy to set (Qt.ContextMenuPolicy)
234 """
235 self.tabBar().setContextMenuPolicy(policy)
236 if policy == Qt.ContextMenuPolicy.CustomContextMenu:
237 self.tabBar().customContextMenuRequested.connect(
238 self.__handleTabCustomContextMenuRequested)
239 else:
240 self.tabBar().customContextMenuRequested.disconnect(
241 self.__handleTabCustomContextMenuRequested)
242
243 def __handleTabCustomContextMenuRequested(self, point):
244 """
245 Private slot to handle the context menu request for the tabbar.
246
247 @param point point the context menu was requested (QPoint)
248 """
249 _tabbar = self.tabBar()
250 for index in range(_tabbar.count()):
251 rect = _tabbar.tabRect(index)
252 if rect.contains(point):
253 self.customTabContextMenuRequested.emit(
254 _tabbar.mapToParent(point), index)
255 return
256
257 self.customTabContextMenuRequested.emit(_tabbar.mapToParent(point), -1)
258
259 def selectTab(self, pos):
260 """
261 Public method to get the index of a tab given a position.
262
263 @param pos position determining the tab index (QPoint)
264 @return index of the tab (integer)
265 """
266 _tabbar = self.tabBar()
267 for index in range(_tabbar.count()):
268 rect = _tabbar.tabRect(index)
269 if rect.contains(pos):
270 return index
271
272 return -1
273
274 def moveTab(self, curIndex, newIndex):
275 """
276 Public method to move a tab to a new index.
277
278 @param curIndex index of tab to be moved (integer)
279 @param newIndex index the tab should be moved to (integer)
280 """
281 # step 1: save the tab data of tab to be moved
282 toolTip = self.tabToolTip(curIndex)
283 text = self.tabText(curIndex)
284 icon = self.tabIcon(curIndex)
285 whatsThis = self.tabWhatsThis(curIndex)
286 widget = self.widget(curIndex)
287 curWidget = self.currentWidget()
288
289 # step 2: move the tab
290 self.removeTab(curIndex)
291 self.insertTab(newIndex, widget, icon, text)
292
293 # step 3: set the tab data again
294 self.setTabToolTip(newIndex, toolTip)
295 self.setTabWhatsThis(newIndex, whatsThis)
296
297 # step 4: set current widget
298 self.setCurrentWidget(curWidget)
299
300 def __freeSide(self):
301 """
302 Private method to determine the free side of a tab.
303
304 @return free side (QTabBar.ButtonPosition)
305 """
306 side = self.__tabBar.style().styleHint(
307 QStyle.StyleHint.SH_TabBar_CloseButtonPosition,
308 None, None, None)
309 side = (
310 QTabBar.ButtonPosition.RightSide
311 if side == QTabBar.ButtonPosition.LeftSide else
312 QTabBar.ButtonPosition.LeftSide
313 )
314 return side
315
316 def animationLabel(self, index, animationFile, interval=100):
317 """
318 Public slot to set an animated icon.
319
320 @param index tab index
321 @type int
322 @param animationFile name of the file containing the animation
323 @type str
324 @param interval interval in milliseconds between animation frames
325 @type int
326 @return reference to the created label
327 @rtype EricAnimatedLabel
328 """
329 if index == -1:
330 return None
331
332 if hasattr(self.__tabBar, 'setTabButton'):
333 side = self.__freeSide()
334 animation = EricAnimatedLabel(
335 self, animationFile=animationFile, interval=interval)
336 self.__tabBar.setTabButton(index, side, None)
337 self.__tabBar.setTabButton(index, side, animation)
338 animation.start()
339 return animation
340 else:
341 return None
342
343 def resetAnimation(self, index):
344 """
345 Public slot to reset an animated icon.
346
347 @param index tab index (integer)
348 """
349 if index == -1:
350 return
351
352 if hasattr(self.__tabBar, 'tabButton'):
353 side = self.__freeSide()
354 animation = self.__tabBar.tabButton(index, side)
355 if animation is not None:
356 animation.stop()
357 self.__tabBar.setTabButton(index, side, None)
358 del animation

eric ide

mercurial