[Needs Feedback] Add PyQt6. Remove PyQt4

- fully qualify enum values for Qt6
- add resource file per Qt version
- TabWidget: use QPointF instead of QPoint for constructing mouse event

Change-Id: I07da61c36c4228a60f6b5b9dacbead27c0a2409d
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30585
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
This commit is contained in:
Alexander Zaft
2023-03-06 14:34:02 +01:00
committed by Markus Zolliker
parent 952cbe76a5
commit d4b80eabd6
20 changed files with 8852 additions and 6029 deletions

View File

@ -40,7 +40,7 @@ def main(argv=None):
app = QApplication(argv)
window = MainWindow(args.file)
window.show()
return app.exec_()
return app.exec()
if __name__ == '__main__':

View File

@ -72,7 +72,7 @@ def main(argv=None):
app.aboutToQuit.connect(win._onQuit)
win.show()
return app.exec_()
return app.exec()
if __name__ == '__main__':

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -132,12 +132,12 @@ class MainWindow(QMainWindow):
def close_tab(self, index):
if self.tabWidget.widget(index).saved:
reply = QMessageBox.Close
reply = QMessageBox.StandardButton.Close
else:
reply = self.show_save_message(self.tabWidget.tabText(index))
if reply == QMessageBox.Cancel:
if reply == QMessageBox.StandardButton.Cancel:
return
if reply == QMessageBox.Save:
if reply == QMessageBox.StandardButton.Save:
self.save_tab(index)
self.tabWidget.removeTab(index)
@ -155,11 +155,11 @@ class MainWindow(QMainWindow):
reply = self.show_save_message()
break
if not reply:
reply = QMessageBox.Close
if reply == QMessageBox.Cancel:
reply = QMessageBox.StandardButton.Close
if reply == QMessageBox.StandardButton.Cancel:
event.ignore()
return
if reply == QMessageBox.Save:
if reply == QMessageBox.StandardButton.Save:
for i in range(0, self.tabWidget.count()):
self.save_tab(i)
event.accept()
@ -173,8 +173,10 @@ class MainWindow(QMainWindow):
Your changes will be lost if you don't save them!
</p>
''' % file_name,
QMessageBox.Cancel | QMessageBox.Close |
QMessageBox.Save, QMessageBox.Save)
QMessageBox.StandardButton.Cancel |
QMessageBox.StandardButton.Close |
QMessageBox.StandardButton.Save,
QMessageBox.StandardButton.Save)
def new_node(self, name, file_path=None):
node = NodeDisplay(file_path)

View File

@ -33,7 +33,7 @@ class NodeDisplay(QWidget):
self.created = self.tree_widget.set_file(file_path)
self.tree_widget.save_status_changed.connect(self.change_save_status)
self.tree_widget.currentItemChanged.connect(self.set_scroll_area)
self.scroll_area_layout.setAlignment(Qt.AlignTop)
self.scroll_area_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
self.set_scroll_area(self.tree_widget.get_selected_item(), None)
self.splitter.setSizes([1, 1])
@ -45,15 +45,15 @@ class NodeDisplay(QWidget):
self.scroll_area_layout.addWidget(current.widget)
for index in range(0, current.childCount()):
child_layout = QHBoxLayout()
spacer = QSpacerItem(30, 0, QSizePolicy.Fixed,
QSizePolicy.Minimum)
spacer = QSpacerItem(30, 0, QSizePolicy.Policy.Fixed,
QSizePolicy.Policy.Minimum)
child_layout.addSpacerItem(spacer)
child_layout.addWidget(current.child(index).widget)
self.scroll_area_layout.addLayout(child_layout)
for sub_index in range(0, current.child(index).childCount()):
sub_child_layout = QHBoxLayout()
sub_spacer = QSpacerItem(60, 0, QSizePolicy.Fixed,
QSizePolicy.Minimum)
sub_spacer = QSpacerItem(60, 0, QSizePolicy.Policy.Fixed,
QSizePolicy.Policy.Minimum)
sub_child_layout.addSpacerItem(sub_spacer)
sub_child_layout.addWidget(
current.child(index).child(sub_index).widget)

View File

@ -56,7 +56,7 @@ class TreeWidgetItem(QTreeWidgetItem):
else:
setTreeIcon(self, 'empty.png')
font = QFont()
font.setWeight(QFont.Bold)
font.setWeight(QFont.Weight.Bold)
self.setFont(0, font)
self.setText(0, self.name)
self.duplicates = 0
@ -140,7 +140,8 @@ class ValueWidget(QWidget):
self.edit_btn = QPushButton()
setIcon(self.edit_btn, 'edit.png')
self.edit_btn.setIconSize(QSize(18, 18))
self.edit_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.edit_btn.setSizePolicy(QSizePolicy.Policy.Fixed,
QSizePolicy.Policy.Fixed)
self.edit_btn.setFlat(True)
layout = QHBoxLayout()
layout.addWidget(self.name_label)
@ -215,7 +216,7 @@ class ChangeNameDialog(QDialog):
self.name.textChanged.connect(self.check_input)
def get_values(self):
if self.exec_() == QDialog.Accepted:
if self.exec() == QDialog.DialogCode.Accepted:
return self.name.text()
return None

View File

@ -49,12 +49,12 @@ def set_name_edit_style(invalid, name_edit, button_box=None):
name_edit.setStyleSheet("color: red")
name_edit.setToolTip('Invalid name: name already taken')
if button_box:
button_box.button(QDialogButtonBox.Ok).setEnabled(False)
button_box.button(QDialogButtonBox.StandardButton.Ok).setEnabled(False)
else:
name_edit.setStyleSheet("color: black")
name_edit.setToolTip('')
if button_box:
button_box.button(QDialogButtonBox.Ok).setEnabled(True)
button_box.button(QDialogButtonBox.StandardButton.Ok).setEnabled(True)
def setTreeIcon(widget, icon_name, subdir='ui', icondir='icons'):
@ -92,16 +92,16 @@ def get_file_paths(widget, open_file=True):
dialog = QFileDialog(widget)
if open_file:
title = 'Open file'
dialog.setAcceptMode(QFileDialog.AcceptOpen)
dialog.setFileMode(QFileDialog.ExistingFiles)
dialog.setAcceptMode(QFileDialog.AcceptMode.AcceptOpen)
dialog.setFileMode(QFileDialog.FileMode.ExistingFiles)
else:
title = 'Save file'
dialog.setAcceptMode(QFileDialog.AcceptSave)
dialog.setFileMode(QFileDialog.AnyFile)
dialog.setAcceptMode(QFileDialog.AcceptMode.AcceptSave)
dialog.setFileMode(QFileDialog.FileMode.AnyFile)
dialog.setWindowTitle(title)
dialog.setNameFilter('*.cfg')
dialog.setDefaultSuffix('.cfg')
dialog.exec_()
dialog.exec()
return dialog.selectedFiles()

View File

@ -51,8 +51,8 @@ class TreeWidget(QTreeWidget):
super().__init__(parent)
self.file_path = None
self.setIconSize(QSize(24, 24))
self.setSelectionMode(QTreeWidget.SingleSelection)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.setSelectionMode(QTreeWidget.SelectionMode.SingleSelection)
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.context_pos = QPoint(0, 0)
self.menu = QMenu()
self.context_actions = []
@ -345,7 +345,8 @@ class AddDialog(QDialog):
for i, name in enumerate(self.invalid_names):
self.invalid_names[i] = name.lower()
if kind in [NODE, MODULE, INTERFACE]:
self.button_box.button(QDialogButtonBox.Ok).setEnabled(False)
self.button_box.button(
QDialogButtonBox.StandardButton.Ok).setEnabled(False)
self.name = QLineEdit()
# TODO: input mask
self.name.textChanged.connect(self.check_input)
@ -385,12 +386,15 @@ class AddDialog(QDialog):
self.value.setFocus()
if self.add_layout.rowCount() == 2:
self.setTabOrder(self.name, self.value)
self.setTabOrder(self.value, self.button_box.button(QDialogButtonBox.Ok))
self.setTabOrder(self.button_box.button(QDialogButtonBox.Ok),
self.button_box.button(QDialogButtonBox.Cancel))
self.setTabOrder(self.value, self.button_box.button(
QDialogButtonBox.StandardButton.Ok))
self.setTabOrder(self.button_box.button(
QDialogButtonBox.StandardButton.Ok),
self.button_box.button(
QDialogButtonBox.StandardButton.Cancel))
def get_values(self):
if self.exec_() == QDialog.Accepted:
if self.exec() == QDialog.DialogCode.Accepted:
if self.kind in [NODE, MODULE, INTERFACE]:
return [self.name.text(), self.get_value()]
if self.kind in [PARAMETER, PROPERTY, COMMENT]:
@ -406,7 +410,7 @@ class AddDialog(QDialog):
class TabBar(QTabBar):
def __init__(self, parent=None):
super().__init__(parent)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.context_pos = QPoint(0, 0)
self.menu = QMenu()
close = self.menu.addAction('&Close')
@ -417,7 +421,7 @@ class TabBar(QTabBar):
self.setMovable(True)
def mouseReleaseEvent(self, event):
if event.button() == Qt.MidButton:
if event.button() == Qt.MouseButton.MidButton:
self.close_tab_at_pos(event.pos())
QTabBar.mouseReleaseEvent(self, event)

View File

@ -9,15 +9,17 @@ class CollapsibleWidget(QWidget):
self.widget = QWidget()
self.widgetContainer = QWidget()
self.button.setArrowType(Qt.RightArrow)
self.button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.button.setArrowType(Qt.ArrowType.RightArrow)
self.button.setToolButtonStyle(
Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
self.button.setStyleSheet("QToolButton { border: none; }")
self.button.setCheckable(True)
self.button.toggled.connect(self._collapse)
line = QFrame()
line.setFrameShape(QFrame.HLine)
line.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum)
line.setFrameShape(QFrame.Shape.HLine)
line.setSizePolicy(QSizePolicy.Policy.Expanding,
QSizePolicy.Policy.Maximum)
l = QVBoxLayout()
l.setContentsMargins(0, 0, 0, 0)
@ -26,7 +28,7 @@ class CollapsibleWidget(QWidget):
self.widgetContainer.setMaximumHeight(0)
layout = QGridLayout()
layout.addWidget(self.button, 0, 0, Qt.AlignLeft)
layout.addWidget(self.button, 0, 0, Qt.AlignmentFlag.AlignLeft)
layout.addWidget(line, 0, 1, 1, 1)
layout.addWidget(self.widgetContainer, 1, 0, -1, -1)
layout.setContentsMargins(0, 6, 0, 0)
@ -34,10 +36,10 @@ class CollapsibleWidget(QWidget):
def _collapse(self, expand):
if expand:
self.button.setArrowType(Qt.DownArrow)
self.button.setArrowType(Qt.ArrowType.DownArrow)
self.widgetContainer.setMaximumHeight(self.widget.maximumHeight())
else:
self.button.setArrowType(Qt.RightArrow)
self.button.setArrowType(Qt.ArrowType.RightArrow)
self.widgetContainer.setMaximumHeight(0)
self.setMaximumHeight(self.button.maximumHeight())

View File

@ -58,8 +58,8 @@ class CommandDialog(QDialog):
def get_value(self):
return True, self.widgets[0].get_value()
def exec_(self):
if super().exec_():
def exec(self):
if super().exec():
return self.get_value()
return None
@ -69,14 +69,14 @@ def showCommandResultDialog(command, args, result, extras=''):
args = '' if args is None else repr(args)
m.setText('calling: %s(%s)\nyielded: %r\nqualifiers: %s' %
(command, args, result, extras))
m.exec_()
m.exec()
def showErrorDialog(command, args, error):
m = QMessageBox()
args = '' if args is None else repr(args)
m.setText('calling: %s(%s)\nraised %r' % (command, args, error))
m.exec_()
m.exec()
class ParameterGroup(QWidget):
@ -130,7 +130,7 @@ class CommandButton(QPushButton):
self.setEnabled(False)
if self._argintype:
dlg = CommandDialog(self._cmdname, self._argintype)
args = dlg.exec_()
args = dlg.exec()
if args: # not 'Cancel' clicked
self._cb(self._cmdname, args[1])
else:
@ -276,12 +276,14 @@ class ModuleCtrl(QWidget):
for prop in sorted(props):
label = QLabel(prop + ':')
label.setFont(self._labelfont)
label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
label.setSizePolicy(QSizePolicy.Policy.Minimum,
QSizePolicy.Policy.Preferred)
# make 'display' label
view = QLabel(str(props[prop]))
view.setFont(self.font())
view.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
view.setSizePolicy(QSizePolicy.Policy.Expanding,
QSizePolicy.Policy.Preferred)
view.setWordWrap(True)
self.propertyGroupBox.layout().addWidget(label, row, 0)

View File

@ -1,7 +1,5 @@
from frappy.gui.qt import QIcon, Qt, QTreeWidget, QTreeWidgetItem, pyqtSignal
import frappy.gui.resources # pylint: disable=unused-import
class ParamItem(QTreeWidgetItem):
def __init__(self, node, module, param):
@ -129,7 +127,7 @@ class ModuleOverview(QTreeWidget):
self.itemExpanded.connect(self._resizeColumns)
self.itemCollapsed.connect(self._resizeColumns)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
# self.customContextMenuRequested.connect(self._contextMenu)
self._node.newData.connect(self._updateValue)

View File

@ -1,7 +1,6 @@
from frappy.gui.qt import QDialog, QIcon, QLabel, QLineEdit, QMessageBox, \
QPushButton, QToolButton, QWidget, pyqtSignal
import frappy.gui.resources # pylint: disable=unused-import
from frappy.gui.util import Colors, loadUi
from frappy.gui.valuewidgets import get_widget
@ -33,8 +32,8 @@ class CommandDialog(QDialog):
def get_value(self):
return True, self.widgets[0].get_value()
def exec_(self):
if super().exec_():
def exec(self):
if super().exec():
return self.get_value()
return None
@ -44,14 +43,14 @@ def showCommandResultDialog(command, args, result, extras=''):
args = '' if args is None else repr(args)
m.setText('calling: %s(%s)\nyielded: %r\nqualifiers: %s' %
(command, args, result, extras))
m.exec_()
m.exec()
def showErrorDialog(command, args, error):
m = QMessageBox()
args = '' if args is None else repr(args)
m.setText('calling: %s(%s)\nraised %r' % (command, args, error))
m.exec_()
m.exec()
class CommandButton(QPushButton):
@ -72,7 +71,7 @@ class CommandButton(QPushButton):
#self.setEnabled(False)
if self._argintype:
dlg = CommandDialog(self._cmdname, self._argintype)
args = dlg.exec_()
args = dlg.exec()
if args: # not 'Cancel' clicked
self._cb(self._cmdname, args[1])
else:

View File

@ -5,7 +5,6 @@ from frappy.gui.qt import QCursor, QFont, QFontMetrics, QGridLayout, QIcon, \
QInputDialog, QLabel, QMenu, QPlainTextEdit, QTextCursor, QVBoxLayout, \
QWidget, pyqtSignal, pyqtSlot, toHtmlEscaped
import frappy.gui.resources # pylint: disable=unused-import
from frappy.errors import SECoPError
from frappy.gui.modulectrl import ModuleCtrl
from frappy.gui.moduleoverview import ModuleOverview
@ -78,7 +77,7 @@ class Console(QWidget):
content += msg
self.logTextBrowser.setHtml(content)
self.logTextBrowser.moveCursor(QTextCursor.End)
self.logTextBrowser.moveCursor(QTextCursor.MoveOperation.End)
def _getLogWidth(self):
fontMetrics = QFontMetrics(QFont('Monospace'))
@ -240,8 +239,8 @@ class NodeWidget(QWidget):
opt_clear = menu.addAction('Clear Selection')
opt_plot.triggered.connect(lambda: self._requestPlot(item))
opt_clear.triggered.connect(self.tree.clearTreeSelection)
#menu.exec_(self.mapToGlobal(pos))
menu.exec_(QCursor.pos())
#menu.exec(self.mapToGlobal(pos))
menu.exec(QCursor.pos())
def _requestPlot(self, item, plot=None):
module = item.module
@ -252,13 +251,14 @@ class NodeWidget(QWidget):
plots = {'%s -> %s' % (m,p): (m,p) for (m,p) in self._activePlots}
dialog = QInputDialog()
#dialog.setInputMode()
dialog.setOption(QInputDialog.UseListViewForComboBoxItems)
dialog.setOption(
QInputDialog.InputDialogOption.UseListViewForComboBoxItems)
dialog.setComboBoxItems(plots.keys())
dialog.setTextValue(list(plots)[0])
dialog.setWindowTitle('Plot %s with...' % param)
dialog.setLabelText('')
if dialog.exec_() == QInputDialog.Accepted:
if dialog.exec() == QInputDialog.DialogCode.Accepted:
item = dialog.textValue()
self.plotParam(module, param, self._activePlots[plots[item]])

View File

@ -55,12 +55,14 @@ class ParameterView(QWidget):
for prop in sorted(props):
label = QLabel(prop + ':')
label.setFont(font)
label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
label.setSizePolicy(QSizePolicy.Policy.Minimum,
QSizePolicy.Policy.Preferred)
# make 'display' label
view = QLabel(str(props[prop]))
view.setFont(self.font())
view.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
view.setSizePolicy(QSizePolicy.Policy.Expanding,
QSizePolicy.Policy.Preferred)
view.setWordWrap(True)
self.propertyGroupBox.layout().addWidget(label, row, 0)

View File

@ -1,5 +1,9 @@
import time
from frappy.gui.qt import QLabel, Qt, QVBoxLayout, QWidget, pyqtSignal
from frappy.gui.util import Colors
try:
import numpy as np
import pyqtgraph as pg
@ -7,10 +11,6 @@ except ImportError:
pg = None
np = None
from frappy.gui.qt import QLabel, Qt, QVBoxLayout, QWidget, pyqtSignal
from frappy.gui.util import Colors
def getPlotWidget(parent):
if pg:
@ -32,7 +32,7 @@ class PlotPlaceHolderWidget(QWidget):
super().__init__(parent)
l = QVBoxLayout()
label = QLabel("pyqtgraph is not installed!")
label.setAlignment(Qt.AlignCenter)
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
l.addWidget(label)
self.setLayout(l)
self.setMinimumWidth(300)

View File

@ -19,19 +19,38 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
#
# *****************************************************************************
"""Import needed stuff from PyQt4/PyQt5"""
"""Import needed stuff from PyQt5/PyQt6"""
# pylint: disable=unused-import
import sys
try:
# Do not abort on exceptions in signal handlers.
# pylint: disable=unnecessary-lambda
sys.excepthook = lambda *args: sys.__excepthook__(*args)
from xml.sax.saxutils import escape as toHtmlEscaped
try:
from PyQt6 import uic
from PyQt6.QtCore import QByteArray, QEvent, QMimeData, QObject, QPoint, \
QPointF, QRectF, QSettings, QSize, Qt, pyqtSignal, pyqtSlot
from PyQt6.QtGui import QAction, QBrush, QColor, QCursor, QDrag, QFont, \
QFontMetrics, QIcon, QKeySequence, QMouseEvent, QPainter, QPalette, \
QPen, QPixmap, QPolygonF, QShortcut, QStandardItem, \
QStandardItemModel, QTextCursor
from PyQt6.QtWidgets import QApplication, QCheckBox, QComboBox, QDialog, \
QDialogButtonBox, QDoubleSpinBox, QFileDialog, QFrame, QGridLayout, \
QGroupBox, QHBoxLayout, QInputDialog, QLabel, QLineEdit, QMainWindow, \
QMenu, QMessageBox, QPlainTextEdit, QPushButton, QRadioButton, \
QScrollArea, QSizePolicy, QSpacerItem, QSpinBox, QStyle, \
QStyleOptionTab, QStylePainter, QTabBar, QTabWidget, QTextEdit, \
QToolButton, QTreeView, QTreeWidget, QTreeWidgetItem, QVBoxLayout, \
QWidget
import frappy.gui.cfg_editor.icon_rc_qt6
import frappy.gui.resources_qt6
except ImportError as e:
from PyQt5 import uic
from PyQt5.QtCore import QByteArray, QEvent, QMimeData, QObject, QPoint, \
QPointF, QRectF, QSettings, QSize, Qt, pyqtSignal, pyqtSlot
@ -49,22 +68,4 @@ try:
QTreeWidgetItem, QVBoxLayout, QWidget
import frappy.gui.cfg_editor.icon_rc_qt5
except ImportError:
from PyQt4 import uic
from PyQt4.QtCore import QObject, QPoint, QPointF, QRectF, QSize, Qt, \
pyqtSignal, pyqtSlot
from PyQt4.QtGui import QAbstractItemView, QAction, QApplication, QBrush, \
QCheckBox, QColor, QComboBox, QDialog, QDialogButtonBox, \
QDoubleSpinBox, QFileDialog, QFont, QFontMetrics, QFrame, \
QGridLayout, QGroupBox, QHBoxLayout, QIcon, QInputDialog, QLabel, \
QLineEdit, QMainWindow, QMenu, QMessageBox, QPainter, QPen, \
QPlainTextEdit, QPolygonF, QPushButton, QRadioButton, QScrollArea, \
QSizePolicy, QSpacerItem, QSpinBox, QStandardItem, \
QStandardItemModel, QTabBar, QTextCursor, QTextEdit, QTreeView, \
QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
import frappy.gui.cfg_editor.icon_rc_qt4
def toHtmlEscaped(s):
return Qt.escape(s)
import frappy.gui.resources_qt5

2792
frappy/gui/resources_qt6.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -25,8 +25,9 @@
"""Detachable TabWidget, taken from NICOS GUI TearOffTabBar."""
from frappy.gui.qt import QApplication, QCursor, QDrag, QEvent, QMainWindow, \
QMimeData, QMouseEvent, QPixmap, QPoint, QSize, QStyle, QStyleOptionTab, \
QStylePainter, Qt, QTabBar, QTabWidget, QWidget, pyqtSignal, pyqtSlot
QMimeData, QMouseEvent, QPixmap, QPoint, QPointF, QSize, QStyle, \
QStyleOptionTab, QStylePainter, Qt, QTabBar, QTabWidget, QWidget, \
pyqtSignal, pyqtSlot
# def findTab(tab, w):
# widget = w
@ -55,33 +56,36 @@ class TearOffTabBar(QTabBar):
def __init__(self, parent=None):
QTabBar.__init__(self, parent)
self.setAcceptDrops(True)
self.setElideMode(Qt.ElideRight)
self.setSelectionBehaviorOnRemove(QTabBar.SelectLeftTab)
self.setElideMode(Qt.TextElideMode.ElideRight)
self.setSelectionBehaviorOnRemove(QTabBar.SelectionBehavior.SelectLeftTab)
self.setMovable(False)
self._dragInitiated = False
self._dragDroppedPos = QPoint()
self._dragStartPos = QPoint()
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
if event.button() == Qt.MouseButton.LeftButton:
self._dragStartPos = event.pos()
self._dragInitiated = False
self._dragDroppedPos = QPoint()
QTabBar.mousePressEvent(self, event)
def mouseMoveEvent(self, event):
if not event.buttons() & Qt.LeftButton:
if not event.buttons() & Qt.MouseButton.LeftButton:
return
if not self._dragStartPos.isNull() and \
self.tabAt(self._dragStartPos) != -1 and \
(event.pos() - self._dragStartPos).manhattanLength() \
< QApplication.startDragDistance():
self._dragInitiated = True
if (event.buttons() == Qt.LeftButton) and self._dragInitiated and \
if (event.buttons() == Qt.MouseButton.LeftButton) and \
self._dragInitiated and \
not self.geometry().contains(event.pos()):
finishMoveEvent = QMouseEvent(QEvent.MouseMove, event.pos(),
Qt.NoButton, Qt.NoButton,
Qt.NoModifier)
finishMoveEvent = QMouseEvent(QEvent.Type.MouseMove,
QPointF(event.pos()),
Qt.MouseButton.NoButton,
Qt.MouseButton.NoButton,
Qt.KeyboardModifier.NoModifier)
QTabBar.mouseMoveEvent(self, finishMoveEvent)
drag = QDrag(self)
@ -90,17 +94,17 @@ class TearOffTabBar(QTabBar):
drag.setMimeData(mimedata)
pixmap = self.parentWidget().currentWidget().grab()
pixmap = pixmap.scaled(640, 480, Qt.KeepAspectRatio)
pixmap = pixmap.scaled(640, 480, Qt.AspectRatioMode.KeepAspectRatio)
drag.setPixmap(pixmap)
drag.setDragCursor(QPixmap(), Qt.LinkAction)
drag.setDragCursor(QPixmap(), Qt.DropAction.LinkAction)
dragged = drag.exec(Qt.MoveAction)
if dragged == Qt.IgnoreAction:
dragged = drag.exec(Qt.DropAction.MoveAction)
if dragged == Qt.DropAction.IgnoreAction:
# moved outside of tab widget
event.accept()
self.tabDetached.emit(self.tabAt(self._dragStartPos),
QCursor.pos())
elif dragged == Qt.MoveAction:
elif dragged == Qt.DropAction.MoveAction:
# moved inside of tab widget
if not self._dragDroppedPos.isNull():
event.accept()
@ -137,10 +141,11 @@ class LeftTabBar(TearOffTabBar):
self.initStyleOption(option, index)
tabRect = self.tabRect(index)
tabRect.moveLeft(10)
painter.drawControl(QStyle.CE_TabBarTabShape, option)
painter.drawControl(QStyle.ControlElement.CE_TabBarTabShape, option)
text = self.tabText(index)
painter.drawText(tabRect, Qt.AlignVCenter | Qt.TextDontClip |
Qt.TextShowMnemonic, text)
painter.drawText(tabRect, Qt.AlignmentFlag.AlignVCenter |
Qt.TextFlag.TextDontClip |
Qt.TextFlag.TextShowMnemonic, text)
def tabSizeHint(self, index):
fm = self.fontMetrics()
@ -479,7 +484,7 @@ class DetachedWindow(QMainWindow):
self.tabIdx = -1
QMainWindow.__init__(self, parent)
self.setWindowTitle(title)
self.setWindowModality(Qt.NonModal)
self.setWindowModality(Qt.WindowModality.NonModal)
# self.sgroup = SettingGroup(title)
# with self.sgroup as settings:
# loadBasicWindowSettings(self, settings)