I'm trying to redo this Qt code https://github.com/laserpants/qt-material-widgets but using Python & PyQt4 and I'm encountering problem with animation.
I want to recreate the checkbox of the example, everything is working fine except the animation; it doesn't update.
The main issue is that I want to keep the stateMachine and the transitions for the button, but the solutions I found does not use them.
I just want the icon to fade-in & out when clicked
any Idea why this is not working?
class materialCheckBox(QWidget):
clicked = pyqtSignal()
def __init__(self, parent):
super(materialCheckBox,self).__init__(parent)
self.setProperty("value", bool)
checkedIcon = materialIcon(self, "C:/Users/User/.qgis2/python/plugins/Material/icons/baseline-check_box-24px.svg")
uncheckedIcon = materialIcon(self, "C:/Users/User/.qgis2/python/plugins/Material/icons/baseline-check_box_outline_blank-24px.svg")
self.stateMachine = QStateMachine()
self.checkedState = QState()
self.checkedState.assignProperty(self, "value", True)
self.checkedState.assignProperty(checkedIcon, "opacity", 1.0)
self.checkedState.assignProperty(uncheckedIcon, "opacity", 0.0)
self.uncheckedState = QState()
self.uncheckedState.assignProperty(self, "value", False)
self.uncheckedState.assignProperty(checkedIcon, "opacity", 0.0)
self.uncheckedState.assignProperty(uncheckedIcon, "opacity", 1.0)
self.stateMachine.addState(self.checkedState)
self.stateMachine.addState(self.uncheckedState)
self.stateMachine.setInitialState(self.uncheckedState)
transition1 = self.checkedState.addTransition(self.clicked, self.uncheckedState)
animation1 = QPropertyAnimation(checkedIcon, "opacity", self)
animation1.setDuration(2000)
transition1.addAnimation(animation1)
animation2 = QPropertyAnimation(uncheckedIcon, "opacity", self)
animation2.setDuration(2000)
transition1.addAnimation(animation2)
transition2 = self.uncheckedState.addTransition(self.clicked, self.checkedState)
animation3 = QPropertyAnimation(checkedIcon, "opacity", self)
animation3.setDuration(2000)
transition2.addAnimation(animation3)
animation4 = QPropertyAnimation(uncheckedIcon, "opacity", self)
animation4.setDuration(2000)
transition2.addAnimation(animation4)
self.stateMachine.start()
self.clicked.connect(self.update)
self.setGeometry(0, 0, 24, 24)
def isChecked(self):
return self.property("value")
def mousePressEvent(self, event):
self.clicked.emit()
class materialIcon(QWidget):
def __init__(self, parent, address):
super(materialIcon, self).__init__(parent)
self.icon = QPixmap(address)
self.setProperty("opacity", float)
def paintEvent(self, event):
painter = QPainter(self)
painter.begin(self)
painter.setOpacity(self.property("opacity"))
mask = QPainter(self.icon)
mask.begin(self.icon)
mask.setCompositionMode(QPainter.CompositionMode_SourceIn)
mask.fillRect(self.icon.rect(), QColor(0, 158, 227))
mask.end()
painter.drawPixmap(0, 0, self.icon)
painter.end()
You have to call the update() method every time the opacity changes, so it's better to create a pyqtProperty than a dynamic property:
import os
from PyQt4 import QtCore, QtGui
root_path = os.path.dirname(os.path.realpath(__file__))
icons_path = file = os.path.join(root_path, "icons")
class MaterialCheckBox(QtGui.QWidget):
clicked = QtCore.pyqtSignal()
toggled = QtCore.pyqtSignal(bool)
def __init__(self, parent=None):
super(MaterialCheckBox, self).__init__(parent)
self._is_checked = False
checkedIcon = MaterialIcon(
self, os.path.join(icons_path, "baseline-check_box-24px.svg")
)
uncheckedIcon = MaterialIcon(
self,
os.path.join(
icons_path, "baseline-check_box_outline_blank-24px.svg"
),
)
stateMachine = QtCore.QStateMachine(self)
checkedState = QtCore.QState()
checkedState.assignProperty(self, b"checked", True)
checkedState.assignProperty(checkedIcon, b"opacity", 1.0)
checkedState.assignProperty(uncheckedIcon, b"opacity", 0.0)
uncheckedState = QtCore.QState()
uncheckedState.assignProperty(self, b"checked", False)
uncheckedState.assignProperty(checkedIcon, b"opacity", 0.0)
uncheckedState.assignProperty(uncheckedIcon, b"opacity", 1.0)
stateMachine.addState(checkedState)
stateMachine.addState(uncheckedState)
stateMachine.setInitialState(uncheckedState)
duration = 2000
transition1 = checkedState.addTransition(self.clicked, uncheckedState)
animation1 = QtCore.QPropertyAnimation(
checkedIcon, b"opacity", self, duration=duration
)
transition1.addAnimation(animation1)
animation2 = QtCore.QPropertyAnimation(
uncheckedIcon, b"opacity", self, duration=duration
)
transition1.addAnimation(animation2)
transition2 = uncheckedState.addTransition(self.clicked, checkedState)
animation3 = QtCore.QPropertyAnimation(
checkedIcon, b"opacity", self, duration=duration
)
transition2.addAnimation(animation3)
animation4 = QtCore.QPropertyAnimation(
uncheckedIcon, b"opacity", self, duration=duration
)
transition2.addAnimation(animation4)
stateMachine.start()
def sizeHint(self):
return QtCore.QSize(24, 24)
def isChecked(self):
return self._is_checked
def setChecked(self, value):
if self._is_checked != value:
self._is_checked = value
self.toggled.emit(self._is_checked)
checked = QtCore.pyqtProperty(
bool, fget=isChecked, fset=setChecked, notify=toggled
)
def mousePressEvent(self, event):
self.clicked.emit()
self.update()
super(MaterialCheckBox, self).mousePressEvent(event)
class MaterialIcon(QtGui.QWidget):
opacityChanged = QtCore.pyqtSignal()
def __init__(self, parent, address):
super(MaterialIcon, self).__init__(parent)
self.icon = QtGui.QPixmap(address)
self._opacity = 0.0
def opacity(self):
return self._opacity
def setOpacity(self, o):
if o != self._opacity:
self._opacity = o
self.opacityChanged.emit()
self.update()
opacity = QtCore.pyqtProperty(
float, fget=opacity, fset=setOpacity, notify=opacityChanged
)
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.setOpacity(self.opacity)
mask = QtGui.QPainter(self.icon)
mask.setCompositionMode(QtGui.QPainter.CompositionMode_SourceIn)
mask.fillRect(self.icon.rect(), QtGui.QColor(0, 158, 227))
mask.end()
painter.drawPixmap(0, 0, self.icon)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
w = MaterialCheckBox()
w.show()
sys.exit(app.exec_())
The complete example is here.
Related
I have created a custom QGraphicsWidget with the ability to resize the widget in the scene. I can also add predefined widgets such as buttons, labels, etc. to my custom widget. I now have two problems.
The first being that the widget doesn't change the size (to re-adjust) upon inserting a new label or LineEdit widget as a result newly inserted widget stays out of the custom widget border.
The second problem is encountered when I try to change the setContentMargins of the QGraphicsLayout to something other than 0. For example QGraphicsLayout.setContentMargins(1, 1, 1, 20) will delay the cursor in the LineEdit widget.
Here is the image.
(Drag the grey triangle to change size)
import sys
from PyQt5 import QtWidgets, QtCore, QtGui, Qt
from PyQt5.QtCore import Qt, QRectF, QPointF
from PyQt5.QtGui import QBrush, QPainterPath, QPainter, QColor, QPen, QPixmap
from PyQt5.QtWidgets import QGraphicsRectItem, QApplication, QGraphicsView, QGraphicsScene, QGraphicsItem
class Container(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.setSpacing(0)
self.setStyleSheet('Container{background:transparent;}')
class GraphicsFrame(QtWidgets.QGraphicsWidget):
def __init__(self, *args, **kwargs):
super(GraphicsFrame, self).__init__()
x, y, h, w = args
rect = QRectF(x, y, h, w)
self.setGeometry(rect)
self.setMinimumSize(150, 150)
self.setMaximumSize(400, 800)
self.setAcceptHoverEvents(True)
self.setFlag(QGraphicsItem.ItemIsMovable, True)
self.setFlag(QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
self.setFlag(QGraphicsItem.ItemIsFocusable, True)
self.mousePressPos = None
self.mousePressRect = None
self.handleSelected = None
self.polygon = QtGui.QPolygon([
QtCore.QPoint(int(self.rect().width()-10), int(self.rect().height()-20)),
QtCore.QPoint(int(self.rect().width()-10), int(self.rect().height()-10)),
QtCore.QPoint(int(self.rect().width()-20), int(self.rect().height()-10))
])
graphic_layout = QtWidgets.QGraphicsLinearLayout(Qt.Vertical, self)
graphic_layout.setContentsMargins(0, 0, 0, 20) # changing this will cause the second problem
self.container = Container()
proxyWidget = QtWidgets.QGraphicsProxyWidget(self)
proxyWidget.setWidget(self.container)
graphic_layout.addItem(proxyWidget)
self.contentLayout = QtWidgets.QFormLayout()
self.contentLayout.setContentsMargins(10, 10, 20, 20)
self.contentLayout.setSpacing(5)
self.container.layout.addLayout(self.contentLayout)
self.options = []
def addOption(self, color=Qt.white, lbl=None, widget=None):
self.insertOption(-1, lbl, widget, color)
def insertOption(self, index, lbl, widget, color=Qt.white):
if index < 0:
index = self.contentLayout.count()
self.contentLayout.addRow(lbl, widget)
self.options.insert(index, (widget, color))
def update_polygon(self):
self.polygon = QtGui.QPolygon([
QtCore.QPoint(int(self.rect().width() - 10), int(self.rect().height() - 20)),
QtCore.QPoint(int(self.rect().width() - 10), int(self.rect().height() - 10)),
QtCore.QPoint(int(self.rect().width() - 20), int(self.rect().height() - 10))
])
def hoverMoveEvent(self, event):
if self.polygon.containsPoint(event.pos().toPoint(), Qt.OddEvenFill):
self.setCursor(Qt.SizeFDiagCursor)
else:
self.unsetCursor()
super(GraphicsFrame, self).hoverMoveEvent(event)
def mousePressEvent(self, event):
self.handleSelected = self.polygon.containsPoint(event.pos().toPoint(), Qt.OddEvenFill)
if self.handleSelected:
self.mousePressPos = event.pos()
self.mousePressRect = self.boundingRect()
super(GraphicsFrame, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
if self.handleSelected:
self.Resize(event.pos())
else:
super(GraphicsFrame, self).mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
super(GraphicsFrame, self).mouseReleaseEvent(event)
self.handleSelected = False
self.mousePressPos = None
self.mousePressRect = None
self.update()
def paint(self, painter, option, widget):
painter.save()
painter.setBrush(QBrush(QColor(37, 181, 247)))
pen = QPen(Qt.white)
pen.setWidth(2)
if self.isSelected():
pen.setColor(Qt.yellow)
painter.setPen(pen)
painter.drawRoundedRect(self.rect(), 4, 4)
painter.setPen(QtCore.Qt.white)
painter.setBrush(QtCore.Qt.gray)
painter.drawPolygon(self.polygon)
super().paint(painter, option, widget)
painter.restore()
def Resize(self, mousePos):
"""
Perform shape interactive resize.
"""
if self.handleSelected:
self.prepareGeometryChange()
width, height = self.geometry().width()+(mousePos.x()-self.mousePressPos.x()),\
self.geometry().height()+(mousePos.y()-self.mousePressPos.y())
self.setGeometry(QRectF(self.geometry().x(), self.geometry().y(), width, height))
self.contentLayout.setGeometry(QtCore.QRect(0, 30, width-10, height-20))
self.mousePressPos = mousePos
self.update_polygon()
self.updateGeometry()
def main():
app = QApplication(sys.argv)
grview = QGraphicsView()
scene = QGraphicsScene()
grview.setViewportUpdateMode(grview.FullViewportUpdate)
scene.addPixmap(QPixmap('01.png'))
grview.setScene(scene)
item = GraphicsFrame(0, 0, 300, 150)
scene.addItem(item)
item.addOption(Qt.green, lbl=QtWidgets.QLabel('I am a label'), widget=QtWidgets.QLineEdit())
item.addOption(lbl=QtWidgets.QLabel('why'), widget=QtWidgets.QLineEdit())
item.addOption(lbl=QtWidgets.QLabel('How'), widget=QtWidgets.QLineEdit())
item.addOption(lbl=QtWidgets.QLabel('Nooo.'), widget=QtWidgets.QLineEdit())
item.addOption(lbl=QtWidgets.QLabel('Nooo.'), widget=QtWidgets.QLineEdit())
item.addOption(lbl=QtWidgets.QLabel('Nooo.'), widget=QtWidgets.QLineEdit())
item2 = GraphicsFrame(50, 50, 300, 150)
scene.addItem(item2)
grview.fitInView(scene.sceneRect(), Qt.KeepAspectRatio)
grview.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
As already suggested to you more than once, using a QGraphicsWidget with a QGraphicsLayout is not a good idea if you are only using it to embed a QGraphicsProxyWidget, as you will certainly have to face unexpected behavior when changing geometries unless you really know what you're doing.
Then, prepareGeometryChange and updateGeometry are completely unnecessary for QGraphicsWidget, and resizing the widget using the item geometries is absolutely wrong for two reasons: first of all, it's up the graphics layout to manage the content size, then you're using scene coordinates, and since you're using scaling, those coordinates will not be correct as they should be transformed in widget's coordinate.
Since using a QSizeGrip is not doable due to the continuously changing scene rect (which, I have to say, is not always a good idea if done along with interactive resizing of contents), you can use a simple QGraphicsPathItem for it, and use that as a reference for the resizing, which is far more simple than continuously move the polygon and draw it.
class SizeGrip(QtWidgets.QGraphicsPathItem):
def __init__(self, parent):
super().__init__(parent)
path = QtGui.QPainterPath()
path.moveTo(0, 10)
path.lineTo(10, 10)
path.lineTo(10, 0)
path.closeSubpath()
self.setPath(path)
self.setPen(QtGui.QPen(Qt.white))
self.setBrush(QtGui.QBrush(Qt.white))
self.setCursor(Qt.SizeFDiagCursor)
class GraphicsFrame(QtWidgets.QGraphicsItem):
def __init__(self, *args, **kwargs):
super(GraphicsFrame, self).__init__()
x, y, w, h = args
self.setPos(x, y)
self.setAcceptHoverEvents(True)
self.setFlag(QGraphicsItem.ItemIsMovable, True)
self.setFlag(QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
self.setFlag(QGraphicsItem.ItemIsFocusable, True)
self.container = Container()
self.proxy = QtWidgets.QGraphicsProxyWidget(self)
self.proxy.setWidget(self.container)
self.proxy.setMinimumSize(150, 150)
self.proxy.setMaximumSize(400, 800)
self.proxy.resize(w, h)
self.contentLayout = QtWidgets.QFormLayout()
self.contentLayout.setContentsMargins(10, 10, 20, 20)
self.contentLayout.setSpacing(5)
self.container.layout.addLayout(self.contentLayout)
self.options = []
self.sizeGrip = SizeGrip(self)
self.mousePressPos = None
self.proxy.geometryChanged.connect(self.resized)
self.resized()
def addOption(self, color=Qt.white, lbl=None, widget=None):
self.insertOption(-1, lbl, widget, color)
def insertOption(self, index, lbl, widget, color=Qt.white):
if index < 0:
index = self.contentLayout.count()
self.contentLayout.addRow(lbl, widget)
self.options.insert(index, (widget, color))
def mousePressEvent(self, event):
gripShape = self.sizeGrip.shape().translated(self.sizeGrip.pos())
if event.button() == Qt.LeftButton and gripShape.contains(event.pos()):
self.mousePressPos = event.pos()
else:
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if self.mousePressPos:
delta = event.pos() - self.mousePressPos
geo = self.proxy.geometry()
bottomRight = geo.bottomRight()
geo.setBottomRight(bottomRight + delta)
self.proxy.setGeometry(geo)
diff = self.proxy.geometry().bottomRight() - bottomRight
if diff.x():
self.mousePressPos.setX(event.pos().x())
if diff.y():
self.mousePressPos.setY(event.pos().y())
else:
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self.mousePressPos = None
super().mouseReleaseEvent(event)
def resized(self):
rect = self.boundingRect()
self.sizeGrip.setPos(rect.bottomRight() + QtCore.QPointF(-20, -20))
def boundingRect(self):
return self.proxy.boundingRect().adjusted(-11, -11, 11, 11)
def paint(self, painter, option, widget):
painter.save()
painter.setBrush(QBrush(QColor(37, 181, 247)))
painter.drawRoundedRect(self.boundingRect().adjusted(0, 0, -.5, -.5), 4, 4)
painter.restore()
Do note that using fitInView() before showing the view is not a good idea, especially if using proxy widgets and layouts.
I am looking for a widget which is a list box, and one entry has different attributes represented by column names.
I want to be able to select multiple entries from the list using a check box on the left side.
Please, also tell me how to configure it. Thanks.
The solution in this case is to use a QTableView (customizing some aspects like the header checkbox):
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class HeaderView(QtWidgets.QHeaderView):
checked = QtCore.pyqtSignal(bool)
def __init__(self, orientation, parent=None):
super().__init__(orientation, parent)
self._checkable_column = -1
self._state = False
self._column_down = -1
#property
def checkable_column(self):
return self._checkable_column
#checkable_column.setter
def checkable_column(self, c):
self._checkable_column = c
#property
def state(self):
return self._state
#state.setter
def state(self, c):
if self.checkable_column == -1:
return
self._state = c
self.checked.emit(c)
self.updateSection(self.checkable_column)
def paintSection(self, painter, rect, logicalIndex):
painter.save()
super().paintSection(painter, rect, logicalIndex)
painter.restore()
if logicalIndex != self.checkable_column:
return
opt = QtWidgets.QStyleOptionButton()
checkbox_rect = self.style().subElementRect(
QtWidgets.QStyle.SE_CheckBoxIndicator, opt, None
)
checkbox_rect.moveCenter(rect.center())
opt.rect = checkbox_rect
opt.state = QtWidgets.QStyle.State_Enabled | QtWidgets.QStyle.State_Active
if logicalIndex == self._column_down:
opt.state |= QtWidgets.QStyle.State_Sunken
opt.state |= (
QtWidgets.QStyle.State_On if self.state else QtWidgets.QStyle.State_Off
)
self.style().drawPrimitive(QtWidgets.QStyle.PE_IndicatorCheckBox, opt, painter)
def mousePressEvent(self, event):
super().mousePressEvent(event)
li = self.logicalIndexAt(event.pos())
self._column_down = li
self.updateSection(li)
def mouseReleaseEvent(self, event):
super().mouseReleaseEvent(event)
li = self.logicalIndexAt(event.pos())
self._column_down = -1
if li == self.checkable_column:
self.state = not self.state
class Dialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.model = QtGui.QStandardItemModel(0, 4, self)
self.model.setHorizontalHeaderLabels(["", "First Name", "Last Name", "Company"])
for state, firstname, lastname, company in (
(True, "Larry", "Ellison", "Oracle"),
(True, "Steve", "Jobs", "Apple"),
(True, "Steve", "Ballmer", "Microsoft"),
(True, "Bill", "Gates", "Microsoft"),
):
it_state = QtGui.QStandardItem()
it_state.setEditable(False)
it_state.setCheckable(True)
it_state.setCheckState(QtCore.Qt.Checked if state else QtCore.Qt.UnChecked)
it_firstname = QtGui.QStandardItem(firstname)
it_lastname = QtGui.QStandardItem(lastname)
it_company = QtGui.QStandardItem(company)
self.model.appendRow([it_state, it_firstname, it_lastname, it_company])
self.view = QtWidgets.QTableView(
showGrid=False, selectionBehavior=QtWidgets.QAbstractItemView.SelectRows
)
self.view.setModel(self.model)
headerview = HeaderView(QtCore.Qt.Horizontal, self.view)
headerview.checkable_column = 0
headerview.checked.connect(self.change_state_of_model)
self.view.setHorizontalHeader(headerview)
self.view.verticalHeader().hide()
self.view.horizontalHeader().setMinimumSectionSize(0)
self.view.horizontalHeader().setSectionResizeMode(
0, QtWidgets.QHeaderView.ResizeToContents
)
self.view.horizontalHeader().setStretchLastSection(True)
self.box = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.Ok
| QtWidgets.QDialogButtonBox.Cancel
| QtWidgets.QDialogButtonBox.NoButton
| QtWidgets.QDialogButtonBox.Help,
QtCore.Qt.Vertical,
)
hlay = QtWidgets.QHBoxLayout(self)
hlay.addWidget(self.view)
hlay.addWidget(self.box)
self.box.accepted.connect(self.accept)
self.box.rejected.connect(self.reject)
self.box.helpRequested.connect(self.on_helpRequested)
self.resize(500, 240)
#QtCore.pyqtSlot()
def on_helpRequested(self):
QtWidgets.QMessageBox.aboutQt(self)
#QtCore.pyqtSlot(bool)
def change_state_of_model(self, state):
for i in range(self.model.rowCount()):
it = self.model.item(i)
if it is not None:
it.setCheckState(QtCore.Qt.Checked if state else QtCore.Qt.Unchecked)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = Dialog()
w.show()
sys.exit(app.exec_())
I would like to get values from Qdialog window into Qmainwindow after closing Qdailog or Qwidget window. Actually I do not know how to do this.
The idea is when user selects a root value from QtableWidget, as shown below in the figure, Data display on the QWidget and I want to transform or pass these values into my Qmainwindow, and my second window in this case is Circular.py would disappear, but my values should be available in the Qmainwindow.
Visualisation of windows.
The Code, "main.py"
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from Circular import *
class Foo(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(Foo, self).__init__(parent)
self.setGeometry(QtCore.QRect(200, 100, 600, 360))
self.boo = Boo()
self.setCentralWidget(self.boo)
class Boo(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Boo, self).__init__(parent)
Openbutton = QtWidgets.QPushButton('Getting values')
Alay = QtWidgets.QVBoxLayout(self)
Alay.addWidget(Openbutton)
Openbutton.clicked.connect(self.buttonfunc)
def buttonfunc(self):
app.setStyleSheet(QSS)
subwindow=CircularDialog()
subwindow.setWindowModality(QtCore.Qt.ApplicationModal)
subwindow.show()
subwindow.exec_()
print('Test')
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = Foo()
w.show()
sys.exit(app.exec_())
Second window code "Circular.py"
Please note that this code is previuosly posted here.
import sys
import os
from PyQt5 import QtCore, QtGui, QtWidgets
iconroot = os.path.dirname(__file__)
ORGANIZATION_NAME = 'Circular App'
ORGANIZATION_DOMAIN = 'Circular shape'
APPLICATION_NAME = 'Circulargeometry program'
SETTINGS_TRAY = 'settings/tray'
QSS = """
QTreeWidget{
border:none;
}
QTreeView::branch:has-siblings:!adjoins-item {
border-image: url(images/vline.png) 0;
}
QTreeView::branch:has-siblings:adjoins-item {
border-image: url(images/branch-more.png) 0;
}
QTreeView::branch:!has-children:!has-siblings:adjoins-item {
border-image: url(images/branch-end.png) 0;
}
QTreeView::branch:has-children:!has-siblings:closed,
QTreeView::branch:closed:has-children:has-siblings {
border-image: none;
image: url(images/branch-closed.png);
}
QTreeView::branch:open:has-children:!has-siblings,
QTreeView::branch:open:has-children:has-siblings {
border-image: none;
image: url(images/branch-open.png);
}
"""
class TreeWidget(QtWidgets.QTreeWidget):
currentTextChanged = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
super(TreeWidget, self).__init__(parent)
self.currentItemChanged.connect(self.onCurrentItemChanged)
self.setHeaderLabel('Standard Section Library')
self.setRootIsDecorated(True)
self.setAlternatingRowColors(True)
self.readSettings()
self.expandAll()
def onCurrentItemChanged(self, current, previous):
if current not in [self.topLevelItem(ix) for ix in range(self.topLevelItemCount())]:
self.currentTextChanged.emit(current.text(0))
def readSettings(self):
settings = QtCore.QSettings()
settings.beginGroup("TreeWidget")
values = settings.value("items")
if values is None:
self.loadDefault()
else:
TreeWidget.dataToChild(values, self.invisibleRootItem())
self.customized_item = None
for ix in range(self.topLevelItemCount()):
tlevel_item = self.topLevelItem(ix)
if tlevel_item.text(0) == "Customized":
self.customized_item = tlevel_item
settings.endGroup()
def writeSettings(self):
settings = QtCore.QSettings()
settings.beginGroup("TreeWidget")
settings.setValue("items", TreeWidget.dataFromChild(self.invisibleRootItem()))
settings.endGroup()
def loadDefault(self):
standardsectionlist = ["D100","D150","D200","D250","D300","D350","D400","D450","D500",
"D550","D600","D650","D700","D750","D800","D850","D900","D950","D1000"]
rootItem = QtWidgets.QTreeWidgetItem(self, ['Circular shapes'])
rootItem.setIcon(0, QtGui.QIcon(os.path.join(iconroot,"images/circularcolumnnorebar.png")))
for element in standardsectionlist:
rootItem.addChild(QtWidgets.QTreeWidgetItem([element]))
self.customized_item = QtWidgets.QTreeWidgetItem(self, ["Customized"])
self.customized_item.setIcon(0, QtGui.QIcon(os.path.join(iconroot,"images/circularcolumnnorebar.png")))
#staticmethod
def dataToChild(info, item):
TreeWidget.tupleToItem(info["data"], item)
for val in info["childrens"]:
child = QtWidgets.QTreeWidgetItem()
item.addChild(child)
TreeWidget.dataToChild(val, child)
#staticmethod
def tupleToItem(t, item):
# set values to item
ba, isSelected = t
ds = QtCore.QDataStream(ba)
ds >> item
item.setSelected(isSelected)
#staticmethod
def dataFromChild(item):
l = []
for i in range(item.childCount()):
child = item.child(i)
l.append(TreeWidget.dataFromChild(child))
return {"childrens": l, "data": TreeWidget.itemToTuple(item)}
#staticmethod
def itemToTuple(item):
# return values from item
ba = QtCore.QByteArray()
ds = QtCore.QDataStream(ba, QtCore.QIODevice.WriteOnly)
ds << item
return ba, item.isSelected()
class InfoWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(InfoWidget, self).__init__(parent)
hlay = QtWidgets.QHBoxLayout(self)
plabel = QtWidgets.QLabel()
pixmap = QtGui.QPixmap(os.path.join(iconroot, "images/circularcolumnnorebard.png"))\
.scaled(230, 230, QtCore.Qt.KeepAspectRatio)
plabel.setPixmap(pixmap)
hlay.addWidget(plabel)
self.ilabel = QtWidgets.QLabel()
hlay.addWidget(self.ilabel)
hlay.addStretch()
self.readSettings()
#QtCore.pyqtSlot(str)
def setData(self, text):
try:
circular_section = int(text.translate({ord('D'): ""}))
area = (3.1416/4)*(circular_section**2)
inertia = (3.1416/64)*circular_section**4
fmt = "D = {}mm\nA = {:0.2E}mm2\n I = {:0.2E}mm4"
self.ilabel.setText(fmt.format(circular_section, area, inertia))
except ValueError:
pass
return print(circular_section)
def readSettings(self):
settings = QtCore.QSettings()
settings.beginGroup("InfoWidget")
self.ilabel.setText(settings.value("text", ""))
settings.endGroup()
def writeSettings(self):
settings = QtCore.QSettings()
settings.beginGroup("InfoWidget")
settings.setValue("text", self.ilabel.text())
settings.endGroup()
class CircularDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super(CircularDialog, self).__init__(parent)
self.setWindowTitle("Frequently used shape")
self.setWindowIcon(QtGui.QIcon(os.path.join(iconroot+"/images/circularcolumnnorebar.png")))
grid = QtWidgets.QGridLayout(self)
self.tree = TreeWidget()
self.infoWidget = InfoWidget()
section_lay = QtWidgets.QHBoxLayout()
section_label = QtWidgets.QLabel("Section name: ")
self.section_edit = QtWidgets.QLineEdit('Define en name to section')
section_lay.addWidget(section_label)
section_lay.addWidget(self.section_edit)
self.tree.currentTextChanged.connect(self.infoWidget.setData)
button_layout = QtWidgets.QVBoxLayout()
add_button = QtWidgets.QPushButton("Add")
add_button.clicked.connect(self.addItem)
delete_button = QtWidgets.QPushButton("Delete")
delete_button.clicked.connect(self.removeItem)
button_layout.addWidget(add_button, alignment=QtCore.Qt.AlignBottom)
button_layout.addWidget(delete_button, alignment=QtCore.Qt.AlignTop)
buttonBox = QtWidgets.QDialogButtonBox()
buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
buttonBox.accepted.connect(self.accept)
buttonBox.rejected.connect(self.reject)
self.accepted.connect(self.save_all_data)
self.rejected.connect(self.save_all_data)
grid.addLayout(section_lay, 0, 0)
grid.addWidget(self.tree, 1, 0)
grid.addLayout(button_layout, 1, 1)
grid.addWidget(self.infoWidget, 2, 0, 1, 2)
grid.addWidget(buttonBox, 3, 0, 1, 2)
self.readSettings()
def readSettings(self):
settings = QtCore.QSettings()
settings.beginGroup("CircularDialog")
self.setGeometry(settings.value("geometry", QtCore.QRect(300, 300, 400, 600)))
self.section_edit.setText(settings.value("SectionInfo", "Define en name to section"))
settings.endGroup()
def writeSettings(self):
settings = QtCore.QSettings()
settings.beginGroup("CircularDialog")
settings.setValue("geometry", self.geometry())
settings.setValue("SectionInfo",self.section_edit.text())
settings.endGroup()
def closeEvent(self, event):
self.save_all_data()
super(CircularDialog, self).closeEvent(event)
def save_all_data(self):
for children in self.findChildren(QtWidgets.QWidget) + [self]:
if hasattr(children, "writeSettings"):
children.writeSettings()
def addItem(self):
text, ok = QtWidgets.QInputDialog.getText(self, "Add custom section",
"Enter section geometry f.ex as D325 or just 325 in mm: ")
if ok:
it = QtWidgets.QTreeWidgetItem([text])
self.tree.customized_item.addChild(it)
def removeItem(self):
it = self.tree.customized_item.takeChild(0)
del it
if __name__ == '__main__':
QtCore.QCoreApplication.setApplicationName(ORGANIZATION_NAME)
QtCore.QCoreApplication.setOrganizationDomain(ORGANIZATION_DOMAIN)
QtCore.QCoreApplication.setApplicationName(APPLICATION_NAME)
app = QtWidgets.QApplication(sys.argv)
app.setStyleSheet(QSS)
w = CircularDialog()
w.show()
sys.exit(app.exec_())
The first thing to do is verify that if you accept or not using exec_ () that returns a code:QDialog::Accepted, if you want to get the text you must use the relationship tree:
def buttonfunc(self):
app.setStyleSheet(QSS)
subwindow=CircularDialog()
subwindow.setWindowModality(QtCore.Qt.ApplicationModal)
if subwindow.exec_() == QtWidgets.QDialog.Accepted:
print('Test', subwindow.infoWidget.ilabel.text())
I want to show a spinner while a task is being performed in my pyqt5 application. I found this nice implementation of a spinner, so I tried it: https://github.com/z3ntu/QtWaitingSpinner
The demo works ok, but in the demo the spinner is shown into an empty area of the dialog. I would like it to be an overlay which covers the whole dialog.
The author of QtWaitingSpinner suggests that "As an alternative example, the code below will create a spinner that (1) blocks all user input to the main application for as long as the spinner is active, (2) automatically centers itself on its parent widget every time "start" is called and (3) makes use of the default shape, size and color settings." with the following code:
spinner = QtWaitingSpinner(self, True, True, Qt.ApplicationModal)
spinner.start() # starts spinning
But I tried this implementation as an example, and it didn't work:
from PyQt5.QtWidgets import QApplication, QDialog, QTabWidget, QWidget, QGroupBox, QPushButton, QVBoxLayout
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon
import requests
import urllib
from waitingspinnerwidget import QtWaitingSpinner
class DownloadDataDialog(QDialog):
def __init__(self, parent=None):
super(DownloadDataDialog, self).__init__(parent)
self.spinner = QtWaitingSpinner(self, True, True, Qt.ApplicationModal)
tabWidget = QTabWidget(self)
tabWidget.addTab(MyTab(tabWidget), "MyTab")
mainLayout = QVBoxLayout()
mainLayout.addWidget(tabWidget)
self.setLayout(mainLayout)
self.setWindowTitle("Download option chain data from web")
class MyTab(QWidget):
def __init__(self, parent=None):
super(MyTab, self).__init__(parent)
dataGroup = QGroupBox('Data')
getButton = QPushButton('Download')
getButton.clicked.connect(self.download_data)
dataLayout = QVBoxLayout()
dataLayout.addWidget(getButton)
dataGroup.setLayout(dataLayout)
mainLayout = QVBoxLayout()
mainLayout.addWidget(dataGroup)
mainLayout.addStretch(1)
self.setLayout(mainLayout)
def download_data(self):
self.parent().parent().parent().spinner.start()
url = 'http://www.meff.es/docs/Ficheros/Descarga/dRV/RV180912.zip'
filepath = None
try:
filepath = self.download_data_file(url)
except Exception as e:
print(e)
self.parent().parent().parent().spinner.stop()
if filepath:
#TODO doing stuff here
self.parent().parent().parent().close()
else:
pass #TODO show error dialog
def download_data_file(self, download_url):
# Build request URL and download the file
destination = 'test.zip'
urllib.request.urlretrieve(download_url, destination)
return destination
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
tabdialog = DownloadDataDialog()
tabdialog.show()
sys.exit(app.exec_())
So my intention is creating an invisible layer, setting the spinner as its only widget, and showing the translucid layer over the whole dialog window.
Any idea of how I should do that?
Once I also had that problem so I modified the library, first activate the flags: QtCore.Qt.Dialog | QtCore.Qt.FramelessWindowHint, and the other change must be done in updatePosition() method:
def updatePosition(self):
if self.parentWidget() and self._centerOnParent:
parentRect = QtCore.QRect(self.parentWidget().mapToGlobal(QtCore.QPoint(0, 0)), self.parentWidget().size())
self.move(QtWidgets.QStyle.alignedRect(QtCore.Qt.LeftToRight, QtCore.Qt.AlignCenter, self.size(), parentRect).topLeft())
The result is as follows:
waitingspinnerwidget.py
import math
from PyQt5 import QtCore, QtGui, QtWidgets
class QtWaitingSpinner(QtWidgets.QWidget):
def __init__(self, parent=None, centerOnParent=True, disableParentWhenSpinning=False, modality=QtCore.Qt.NonModal):
super().__init__(parent, flags=QtCore.Qt.Dialog | QtCore.Qt.FramelessWindowHint)
self._centerOnParent = centerOnParent
self._disableParentWhenSpinning = disableParentWhenSpinning
# WAS IN initialize()
self._color = QtGui.QColor(QtCore.Qt.black)
self._roundness = 100.0
self._minimumTrailOpacity = 3.14159265358979323846
self._trailFadePercentage = 80.0
self._revolutionsPerSecond = 1.57079632679489661923
self._numberOfLines = 20
self._lineLength = 10
self._lineWidth = 2
self._innerRadius = 10
self._currentCounter = 0
self._isSpinning = False
self._timer = QtCore.QTimer(self)
self._timer.timeout.connect(self.rotate)
self.updateSize()
self.updateTimer()
self.hide()
# END initialize()
self.setWindowModality(modality)
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
def paintEvent(self, QPaintEvent):
self.updatePosition()
painter = QtGui.QPainter(self)
painter.fillRect(self.rect(), QtCore.Qt.transparent)
painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
if self._currentCounter >= self._numberOfLines:
self._currentCounter = 0
painter.setPen(QtCore.Qt.NoPen)
for i in range(0, self._numberOfLines):
painter.save()
painter.translate(self._innerRadius + self._lineLength, self._innerRadius + self._lineLength)
rotateAngle = float(360 * i) / float(self._numberOfLines)
painter.rotate(rotateAngle)
painter.translate(self._innerRadius, 0)
distance = self.lineCountDistanceFromPrimary(i, self._currentCounter, self._numberOfLines)
color = self.currentLineColor(distance, self._numberOfLines, self._trailFadePercentage,
self._minimumTrailOpacity, self._color)
painter.setBrush(color)
painter.drawRoundedRect(QtCore.QRect(0, -self._lineWidth / 2, self._lineLength, self._lineWidth), self._roundness,
self._roundness, QtCore.Qt.RelativeSize)
painter.restore()
def start(self):
self.updatePosition()
self._isSpinning = True
self.show()
if self.parentWidget and self._disableParentWhenSpinning:
self.parentWidget().setEnabled(False)
if not self._timer.isActive():
self._timer.start()
self._currentCounter = 0
def stop(self):
self._isSpinning = False
self.hide()
if self.parentWidget() and self._disableParentWhenSpinning:
self.parentWidget().setEnabled(True)
if self._timer.isActive():
self._timer.stop()
self._currentCounter = 0
def setNumberOfLines(self, lines):
self._numberOfLines = lines
self._currentCounter = 0
self.updateTimer()
def setLineLength(self, length):
self._lineLength = length
self.updateSize()
def setLineWidth(self, width):
self._lineWidth = width
self.updateSize()
def setInnerRadius(self, radius):
self._innerRadius = radius
self.updateSize()
def color(self):
return self._color
def roundness(self):
return self._roundness
def minimumTrailOpacity(self):
return self._minimumTrailOpacity
def trailFadePercentage(self):
return self._trailFadePercentage
def revolutionsPersSecond(self):
return self._revolutionsPerSecond
def numberOfLines(self):
return self._numberOfLines
def lineLength(self):
return self._lineLength
def lineWidth(self):
return self._lineWidth
def innerRadius(self):
return self._innerRadius
def isSpinning(self):
return self._isSpinning
def setRoundness(self, roundness):
self._roundness = max(0.0, min(100.0, roundness))
def setColor(self, color=QtCore.Qt.black):
self._color = QColor(color)
def setRevolutionsPerSecond(self, revolutionsPerSecond):
self._revolutionsPerSecond = revolutionsPerSecond
self.updateTimer()
def setTrailFadePercentage(self, trail):
self._trailFadePercentage = trail
def setMinimumTrailOpacity(self, minimumTrailOpacity):
self._minimumTrailOpacity = minimumTrailOpacity
def rotate(self):
self._currentCounter += 1
if self._currentCounter >= self._numberOfLines:
self._currentCounter = 0
self.update()
def updateSize(self):
size = (self._innerRadius + self._lineLength) * 2
self.setFixedSize(size, size)
def updateTimer(self):
self._timer.setInterval(1000 / (self._numberOfLines * self._revolutionsPerSecond))
def updatePosition(self):
if self.parentWidget() and self._centerOnParent:
parentRect = QtCore.QRect(self.parentWidget().mapToGlobal(QtCore.QPoint(0, 0)), self.parentWidget().size())
self.move(QtWidgets.QStyle.alignedRect(QtCore.Qt.LeftToRight, QtCore.Qt.AlignCenter, self.size(), parentRect).topLeft())
def lineCountDistanceFromPrimary(self, current, primary, totalNrOfLines):
distance = primary - current
if distance < 0:
distance += totalNrOfLines
return distance
def currentLineColor(self, countDistance, totalNrOfLines, trailFadePerc, minOpacity, colorinput):
color = QtGui.QColor(colorinput)
if countDistance == 0:
return color
minAlphaF = minOpacity / 100.0
distanceThreshold = int(math.ceil((totalNrOfLines - 1) * trailFadePerc / 100.0))
if countDistance > distanceThreshold:
color.setAlphaF(minAlphaF)
else:
alphaDiff = color.alphaF() - minAlphaF
gradient = alphaDiff / float(distanceThreshold + 1)
resultAlpha = color.alphaF() - gradient * countDistance
# If alpha is out of bounds, clip it.
resultAlpha = min(1.0, max(0.0, resultAlpha))
color.setAlphaF(resultAlpha)
return color
With the above we solve one of those problems, the other problem is that urllib.request.urlretrieve() is blocking so it will cause the GUI to freeze, so the solution is to move it to another thread, using a previous response we can do it in the following way:
from PyQt5 import QtCore, QtGui, QtWidgets
import urllib.request
from waitingspinnerwidget import QtWaitingSpinner
class RequestRunnable(QtCore.QRunnable):
def __init__(self, url, destination, dialog):
super(RequestRunnable, self).__init__()
self._url = url
self._destination = destination
self.w = dialog
def run(self):
urllib.request.urlretrieve(self._url, self._destination)
QMetaObject.invokeMethod(self.w, "FinishedDownload", QtCore.Qt.QueuedConnection)
class DownloadDataDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super(DownloadDataDialog, self).__init__(parent)
self.spinner = QtWaitingSpinner(self, True, True, QtCore.Qt.ApplicationModal)
tabWidget = QtWidgets.QTabWidget(self)
tabWidget.addTab(MyTab(), "MyTab")
mainLayout = QtWidgets.QVBoxLayout(self)
mainLayout.addWidget(tabWidget)
self.setWindowTitle("Download option chain data from web")
class MyTab(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MyTab, self).__init__(parent)
dataGroup = QtWidgets.QGroupBox('Data')
getButton = QtWidgets.QPushButton('Download')
getButton.clicked.connect(self.download_data)
dataLayout = QtWidgets.QVBoxLayout(self)
dataLayout.addWidget(getButton)
mainLayout = QtWidgets.QVBoxLayout(self)
mainLayout.addWidget(dataGroup)
mainLayout.addStretch(1)
def download_data(self):
self.parentWidget().window().spinner.start()
url = 'http://www.meff.es/docs/Ficheros/Descarga/dRV/RV180912.zip'
destination = 'test.zip'
runnable = RequestRunnable(url, destination, self)
QtCore.QThreadPool.globalInstance().start(runnable)
#QtCore.pyqtSlot()
def FinishedDownload(self):
self.parentWidget().window().spinner.stop()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
tabdialog = DownloadDataDialog()
tabdialog.show()
sys.exit(app.exec_())
How could I archive this:
- I need to drag and drop a tab from its tabBar to other tabBar in a splitted widget?
I already subclass the QtabBar and implement the drag and drop events, i already can drag it with the right pixmap and etc, and also i can drop it into the same tabBar, but not in the other one ..
got this error in the output telling me that im not providing the right arguments, here is the code, that i simplified for make it and example, and plus a .JPG of the window.
class EsceneTest(qg.QMainWindow):
def __init__(self,parent=getMayaWindow()):
super(EsceneTest,self).__init__(parent)
#---------------------------------------------------------#
#check for open Window first
winName = windowTitle
if cmds.window(winName, exists =1):
cmds.deleteUI(winName, wnd=True)
self.setAttribute(qc.Qt.WA_DeleteOnClose)
self._initUI()
def _initUI(self):
self.setObjectName(windowObject)
self.setWindowTitle(windowTitle)
self.setMinimumWidth(450)
self.setMinimumHeight(500)
self.resize(1080, 800) # re-size the window
centralWidget = qg.QWidget()
centralWidget.setObjectName('centralWidget')
self.setCentralWidget(centralWidget)
central_layout = qg.QVBoxLayout(centralWidget)
######################
# tab container
#
self.tabWidget = qg.QTabWidget()
self.tabWidget.setAcceptDrops(True)
self.tab_layout = qg.QVBoxLayout(self.tabWidget)
central_layout.addWidget(self.tabWidget)
#######################
# TabBar
#
custom_tabbar = ColtabBar()
self.tabWidget.setTabBar(custom_tabbar)
#######################
# ViewportTab
#
tabCentral_wdg = qg.QWidget()
self.top_lyt = qg.QVBoxLayout(tabCentral_wdg)
self.tab_layout.addLayout(self.top_lyt)
fixedHBox_lyt = qg.QHBoxLayout()
self.top_lyt.addLayout(fixedHBox_lyt)
self.tabWidget.addTab(tabCentral_wdg,'- Viewport')
#######################
# Example ExtraTab
#
tabTwo_wdg = qg.QWidget()
tabTwo_wdg_lyt = qg.QHBoxLayout(tabTwo_wdg)
self.tab_layout.addLayout(tabTwo_wdg_lyt)
label = qg.QLabel(' -- This is an example -- ')
label.setStyleSheet("""
background : qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgb(53, 57, 60), stop:1 rgb(33, 34, 36));
border-style : none;
font-size: 40px;
font-family: Calibri;
color : rgb(200,200,100);
""")
label.setAlignment(qc.Qt.AlignVCenter | qc.Qt.AlignHCenter )
tabTwo_wdg_lyt.addWidget(label)
tab_panel_lyt = qg.QVBoxLayout(label)
self.tabWidget.addTab(tabTwo_wdg,'- ExtraExample')
############################
# Q Splitter Widget to insert the dragged Tabs
#
split = qg.QSplitter(qc.Qt.Orientation.Vertical, self)
central_layout.addWidget(split)
tab_splitted = qg.QTabWidget()
split.setLayout(qg.QVBoxLayout())
split.insertWidget(0,tab_splitted)
tabBar_2 = ColtabBar()
tab_splitted.setTabBar(tabBar_2)
tabBar_2.addTab('- Insert-Here')
#---------------------------------------------------------------------------------------------#
class ColtabBar(qg.QTabBar):
def __init__(self):
super(ColtabBar, self).__init__()
self.indexTab = None
self.setAcceptDrops(True)
##################################
# Events
def mouseMoveEvent(self, e):
if e.buttons() != qc.Qt.MiddleButton:
return
globalPos = self.mapToGlobal(e.pos())
posInTab = self.mapFromGlobal(globalPos)
self.indexTab = self.tabAt(e.pos())
tabRect = self.tabRect(self.indexTab)
pixmap = qg.QPixmap(tabRect.size())
self.render(pixmap,qc.QPoint(),qg.QRegion(tabRect))
mimeData = qc.QMimeData()
drag = qg.QDrag(self)
drag.setMimeData(mimeData)
drag.setPixmap(pixmap)
cursor = qg.QCursor(qc.Qt.OpenHandCursor)
drag.setHotSpot(e.pos() - posInTab)
drag.setDragCursor(cursor.pixmap(),qc.Qt.MoveAction)
dropAction = drag.exec_(qc.Qt.MoveAction)
def mousePressEvent(self, e):
#super(qg.QWidget).mousePressEvent(e)
if e.button() == qc.Qt.RightButton:
print('press')
if e.button() == qc.Qt.LeftButton:
globalPos = self.mapToGlobal(e.pos())
posInTab = self.mapFromGlobal(globalPos)
self.indexTab = self.tabAt(e.pos())
self.setCurrentIndex(self.indexTab)
def dragEnterEvent(self, e):
e.accept()
def dropEvent(self, e):
e.setDropAction(qc.Qt.MoveAction)
e.accept()
self.insertTab(self.indexTab, self.tabText(self.indexTab))
self.removeTab(self.indexTab)
the ColtabBar is the subclass where im doing the drag and drop events.
IMAGE - >
After many hours and have eaten many manyyyy pages of Qt today over the web, I did it in my way, now I can drag and drop tabs from one tabBar to the other and vice-versa and not just from selection the current tab, i could select every tab that I want in my tab bar and will show me the pixmap of the little tab while dragging...
Here is the code:
** EDITED **
I made it more bullet proof, I had a bug when I was using more than 2 tabs with the index, now is working better, and when I drop it in the same widget it return the event and not execute the code, plus the hovering tabs select with the right mouse button as well .. I hope this can help anybody in the future.
TABINDEX = int()
def getTabIndex(index):
global TABINDEX
if index == -1 or index == TABINDEX:
return
TABINDEX = index
print (TABINDEX)
return TABINDEX
class ColtTab(qg.QTabWidget):
def __init__(self):
super(ColtTab,self).__init__()
self.setAcceptDrops(True)
self.tabBar = self.tabBar()
self.tabBar.setMouseTracking(True)
self.setDocumentMode(True)
self.indexTab = int()
self.setMovable(True)
self.setStyleSheet(style_sheet_file)
# test for hovering and selecting tabs automatic while mouser over then - not working for now...
def eventFilter(self, obj, event):
if obj == self.tabBar:
if event.type() == qc.QEvent.MouseMove:
index=self.tabBar.tabAt(event.pos())
self.tabBar.setCurrentIndex (index)
return True
else:
return
else:
return
##################################
# Events
#
def mouseMoveEvent(self, e):
if e.buttons() != qc.Qt.MiddleButton:
return
globalPos = self.mapToGlobal(e.pos())
#print(globalPos)
tabBar = self.tabBar
#print(tabBar)
posInTab = tabBar.mapFromGlobal(globalPos)
#print(posInTab)
self.indexTab = tabBar.tabAt(e.pos())
#print(self.indexTab)
tabRect = tabBar.tabRect(self.indexTab)
#print(tabRect)
#print(tabRect.size())
pixmap = qg.QPixmap(tabRect.size())
tabBar.render(pixmap,qc.QPoint(),qg.QRegion(tabRect))
mimeData = qc.QMimeData()
drag = qg.QDrag(tabBar)
drag.setMimeData(mimeData)
drag.setPixmap(pixmap)
cursor = qg.QCursor(qc.Qt.OpenHandCursor)
drag.setHotSpot(e.pos() - posInTab)
drag.setDragCursor(cursor.pixmap(),qc.Qt.MoveAction)
dropAction = drag.exec_(qc.Qt.MoveAction)
def mousePressEvent(self, e):
if e.button() == qc.Qt.RightButton:
self.tabBar.installEventFilter(self)
print('Right button pressed')
super(ColtTab, self).mousePressEvent(e)
def dragEnterEvent(self, e):
e.accept()
if e.source().parentWidget() != self:
return
# Helper function for retrieving the Tab index into a global Var
getTabIndex(self.indexOf(self.widget(self.indexTab)))
def dragLeaveEvent(self,e):
e.accept()
def dropEvent(self, e):
if e.source().parentWidget() == self:
return
e.setDropAction(qc.Qt.MoveAction)
e.accept()
counter = self.count()
if counter == 0:
self.addTab(e.source().parentWidget().widget(TABINDEX),e.source().tabText(TABINDEX))
else:
self.insertTab(counter + 1 ,e.source().parentWidget().widget(TABINDEX),e.source().tabText(TABINDEX))
print ('Tab dropped')
def mouseReleaseEvent(self, e):
if e.button() == qc.Qt.RightButton:
print('Right button released')
self.tabBar.removeEventFilter(self)
super(ColtTab, self).mouseReleaseEvent(e)
#---------------------------------------------------------------------------------#
Pic ->
Found this thread useful. Used your solution to create a self contained generic example in PyQt5. May help someone in the future.
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class Tabs(QTabWidget):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
self.setAcceptDrops(True)
self.tabBar = self.tabBar()
self.tabBar.setMouseTracking(True)
self.indexTab = None
self.setMovable(True)
self.addTab(QWidget(self), 'Tab One')
self.addTab(QWidget(self), 'Tab Two')
def mouseMoveEvent(self, e):
if e.buttons() != Qt.RightButton:
return
globalPos = self.mapToGlobal(e.pos())
tabBar = self.tabBar
posInTab = tabBar.mapFromGlobal(globalPos)
self.indexTab = tabBar.tabAt(e.pos())
tabRect = tabBar.tabRect(self.indexTab)
pixmap = QPixmap(tabRect.size())
tabBar.render(pixmap,QPoint(),QRegion(tabRect))
mimeData = QMimeData()
drag = QDrag(tabBar)
drag.setMimeData(mimeData)
drag.setPixmap(pixmap)
cursor = QCursor(Qt.OpenHandCursor)
drag.setHotSpot(e.pos() - posInTab)
drag.setDragCursor(cursor.pixmap(),Qt.MoveAction)
dropAction = drag.exec_(Qt.MoveAction)
def dragEnterEvent(self, e):
e.accept()
if e.source().parentWidget() != self:
return
print(self.indexOf(self.widget(self.indexTab)))
self.parent.TABINDEX = self.indexOf(self.widget(self.indexTab))
def dragLeaveEvent(self,e):
e.accept()
def dropEvent(self, e):
print(self.parent.TABINDEX)
if e.source().parentWidget() == self:
return
e.setDropAction(Qt.MoveAction)
e.accept()
counter = self.count()
if counter == 0:
self.addTab(e.source().parentWidget().widget(self.parent.TABINDEX),e.source().tabText(self.parent.TABINDEX))
else:
self.insertTab(counter + 1 ,e.source().parentWidget().widget(self.parent.TABINDEX),e.source().tabText(self.parent.TABINDEX))
class Window(QWidget):
def __init__(self):
super().__init__()
self.TABINDEX = 0
tabWidgetOne = Tabs(self)
tabWidgetTwo = Tabs(self)
layout = QHBoxLayout()
self.moveWidget = None
layout.addWidget(tabWidgetOne)
layout.addWidget(tabWidgetTwo)
self.setLayout(layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
This is modified from someone elses code, perhaps one of the examples above.
Anyhow, it's a minimal code for tab-to-tab or tab-to-window drag / droping of tab's contents.
from PyQt5.QtWidgets import QTabWidget
from PyQt5.QtCore import Qt, QPoint, QMimeData
from PyQt5.QtGui import QPixmap, QRegion, QDrag, QCursor
class TabWidget(QTabWidget):
def __init__(self, parent=None, new=None):
super().__init__(parent)
self.setAcceptDrops(True)
self.tabBar().setMouseTracking(True)
self.setMovable(True)
if new:
TabWidget.setup(self)
def __setstate__(self, data):
self.__init__(new=False)
self.setParent(data['parent'])
for widget, tabname in data['tabs']:
self.addTab(widget, tabname)
TabWidget.setup(self)
def __getstate__(self):
data = {
'parent' : self.parent(),
'tabs' : [],
}
tab_list = data['tabs']
for k in range(self.count()):
tab_name = self.tabText(k)
widget = self.widget(k)
tab_list.append((widget, tab_name))
return data
def setup(self):
pass
def mouseMoveEvent(self, e):
if e.buttons() != Qt.RightButton:
return
globalPos = self.mapToGlobal(e.pos())
tabBar = self.tabBar()
posInTab = tabBar.mapFromGlobal(globalPos)
index = tabBar.tabAt(e.pos())
tabBar.dragged_content = self.widget(index)
tabBar.dragged_tabname = self.tabText(index)
tabRect = tabBar.tabRect(index)
pixmap = QPixmap(tabRect.size())
tabBar.render(pixmap,QPoint(),QRegion(tabRect))
mimeData = QMimeData()
drag = QDrag(tabBar)
drag.setMimeData(mimeData)
drag.setPixmap(pixmap)
cursor = QCursor(Qt.OpenHandCursor)
drag.setHotSpot(e.pos() - posInTab)
drag.setDragCursor(cursor.pixmap(),Qt.MoveAction)
drag.exec_(Qt.MoveAction)
def dragEnterEvent(self, e):
e.accept()
#self.parent().dragged_index = self.indexOf(self.widget(self.dragged_index))
def dragLeaveEvent(self,e):
e.accept()
def dropEvent(self, e):
if e.source().parentWidget() == self:
return
e.setDropAction(Qt.MoveAction)
e.accept()
tabBar = e.source()
self.addTab(tabBar.dragged_content, tabBar.dragged_tabname)
if __name__ == '__main__':
from PyQt5.QtWidgets import QWidget, QApplication, QHBoxLayout
import sys
class Window(QWidget):
def __init__(self):
super().__init__()
self.dragged_index = None
tabWidgetOne = TabWidget(self)
tabWidgetTwo = TabWidget(self)
tabWidgetOne.addTab(QWidget(), "tab1")
tabWidgetTwo.addTab(QWidget(), "tab2")
layout = QHBoxLayout()
self.moveWidget = None
layout.addWidget(tabWidgetOne)
layout.addWidget(tabWidgetTwo)
self.setLayout(layout)
app = QApplication(sys.argv)
window = Window()
window1 = Window()
window.show()
window1.show()
sys.exit(app.exec_())