I found a C++ example which modifies the QSpinBox button direction as left and right, and it works. So I used PyQt-4.7 to imitate the example, but the button didn't show. Below is my code:
from PyQt4 import QtGui, QtCore
from PyQt4.QtGui import QStyle, QCommonStyle
class ScientificDoubleSpinBox(QtGui.QDoubleSpinBox):
def __init__(self, *args, **kwargs):
super(ScientificDoubleSpinBox, self).__init__(*args, **kwargs)
self.setMinimum(0)
self.setMaximum(1000)
self.setDecimals(1000)
style = SpinBoxStyle()
#here
self.setStyle(style)
class SpinBoxStyle(QtGui.QCommonStyle):
def __init__(self):
super(SpinBoxStyle, self).__init__()
def drawComplexControl(self, control, option, painter, widget = None):
if(control == QtGui.QStyle.CC_SpinBox):
self.drawSpinBoxButton(QtGui.QStyle.SC_SpinBoxDown,option,painter);
self.drawSpinBoxButton(QtGui.QStyle.SC_SpinBoxUp,option,painter);
rect = self.subControlRect(QtGui.QStyle.CC_SpinBox,option,QtGui.QStyle.SC_SpinBoxEditField, widget).adjusted(-1,0,+1,0)
painter.setPen(QtGui.QPen(option.palette.mid(),1.0))
painter.drawLine(rect.topLeft(),rect.bottomLeft())
painter.drawLine(rect.topRight(),rect.bottomRight())
else:
return QCommonStyle().drawComplexControl(which,option,painter)
def drawSpinBoxButton(self, subControl, option, painter, widget = None):
buttonWidth = 16
arrow = QtGui.QStyle.PE_IndicatorSpinDown
buttonRect = option.rect
if((subControl==QtGui.QStyle.SC_SpinBoxUp)!=(option.direction==1)): #Qt.RightToLeft
arrow = QtGui.QStyle.PE_IndicatorSpinUp
buttonRect.translate(buttonRect.width()/2,0)
else:
buttonRect.translate(buttonRect.width()/2-buttonWidth,0)
buttonRect.setWidth((buttonRect.width()+1)*2);
#????
#buttonOpt = qstyleoption_cast(option)
buttonOpt = QtGui.QStyleOption(option)
painter.save()
painter.setClipRect(buttonRect, 2) #Qt.IntersectClip
#????
#if(not(option.activeSubControls & subControl)):
#buttonOpt.state &=not(QtGui.QStyle.State_MouseOver|QtGui.QStyle.State_On|QtGui.QStyle.State_Sunken)
arrowOpt= QtGui.QStyleOption(buttonOpt)
arrowOpt.rect = self.subControlRect(QtGui.QStyle.CC_SpinBox,option,subControl, widget);
if(arrowOpt.rect.isValid()):
self.drawPrimitive(arrow,arrowOpt,painter)
painter.restore()
def subControlRect(self, whichControl, option, whichSubControl, widget):
if(whichControl==QtGui.QStyle.CC_SpinBox):
frameWidth = self.pixelMetric(QtGui.QStyle.PM_DefaultFrameWidth, option, widget)
buttonWidth = 16;
if whichSubControl == QtGui.QStyle.SC_SpinBoxFrame:
return option.rect
elif whichSubControl == QtGui.QStyle.SC_SpinBoxEditField:
return option.rect.adjusted(0, +frameWidth,-buttonWidth*4,-frameWidth)
elif whichControl == QtGui.QStyle.SC_SpinBoxDown:
return self.visualRect(option.direction, option.rect, QtCore.QRect(option.rect.right()-buttonWidth*2,
option.rect.y(), buttonWidth, option.rect.height()))
elif whichControl == QtGui.QStyle.SC_SpinBoxUp:
return self.visualRect(option.direction, option.rect, QtCore.QRect(option.rect.right()-buttonWidth,
option.rect.y(), buttonWidth, option.rect.height()))
else:
return QtCore.QRect()
else:
return QCommonStyle.subControlRect(whichControl,option,whichSubControl)
Related
I know how to drag rows
QTableView.verticalHeader().setSectionsMovable(True)
QTableView.verticalHeader().setDragEnabled(True)
QTableView.verticalHeader().setDragDropMode(qtw.QAbstractItemView.InternalMove)
but I want to be able to drag and drop just a single (or a pair of) cell.
Can anyone point me in the right direction?
PS: my current idea would be to intercept the clicked() -> dragEnter() -> dragLeave() or fork to dragDrop() -> dragIndicatorPosition()
events, but it sounds kinda convoluted
I did read this but I am confused on how to implement it, especially the section "Enabling drag and drop for items" seems to address exactly what I need. I'll see if I can post a "slim" example.
EDIT:
here an example with some other stuff. in MyStandardItemModel I try to do the trick:
from PyQt5 import QtCore, QtWidgets, QtSql
from PyQt5.QtCore import QModelIndex
from PyQt5.QtGui import QStandardItemModel
from PyQt5.QtWidgets import QApplication, QTableView, QTableWidget
class MyStandardItemModel(QStandardItemModel):
def __init__(self, parent=None, *arg, **kwargs):
super().__init__(parent, *arg, **kwargs)
self.__readonly_cols = []
def flags(self, index: QtCore.QModelIndex) -> QtCore.Qt.ItemFlags:
try:
default_Flags = QStandardItemModel.flags(self,index)
if (index.column() in self.__readonly_cols):
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
else:
if (index.isValid()):
return default_Flags | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled
else:
return default_Flags
except Exception as ex:
print(ex)
def setReadOnly(self, columns: [int]):
for i in columns:
if i <= (self.columnCount() - 1) and i not in self.__readonly_cols:
self.__readonly_cols.append(i)
def resetReadOnly(self):
self.__readonly_cols = []
class MyTableView(QtWidgets.QTableView):
def __init__(self, parent=None, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
class CheckBoxDelegate(QtWidgets.QItemDelegate):
"""
A delegate that places a fully functioning QCheckBox cell of the column to which it's applied.
"""
# signal to inform clicking
user_click = QtCore.pyqtSignal(int, int, bool)
def __init__(self, parent):
QtWidgets.QItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
"""
Important, otherwise an editor is created if the user clicks in this cell.
"""
return None
def paint(self, painter, option, index):
"""
Paint a checkbox without the label.
"""
self.drawCheck(painter, option, option.rect, QtCore.Qt.Unchecked if int(index.data()) == 0 else QtCore.Qt.Checked)
def editorEvent(self, event, model, option, index):
'''
Change the data in the model and the state of the checkbox
if the user presses the left mousebutton and this cell is editable. Otherwise do nothing.
'''
if not int(index.flags() & QtCore.Qt.ItemIsEditable) > 0:
return False
if event.type() == QtCore.QEvent.MouseButtonRelease and event.button() == QtCore.Qt.LeftButton:
# Change the checkbox-state
self.setModelData(None, model, index)
return True
# if event.type() == QtCore.QEvent.MouseButtonPress or event.type() == QtCore.QEvent.MouseMove:
# return False
return False
def setModelData (self, editor, model, index):
'''
The user wanted to change the old state in the opposite.
'''
try:
if int(index.data()) == 0:
model.setData(index, 1, QtCore.Qt.EditRole)
ret = True
else:
model.setData(index, 0, QtCore.Qt.EditRole)
ret = False
# emit signal with row, col, and the status
self.user_click.emit(index.row(), index.column(), ret)
except Exception as ex:
print(ex)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
model = MyStandardItemModel(5, 5)
header_labels = ['', 'Signal', 'Type', 'Routing', 'Input']
model.setHorizontalHeaderLabels(header_labels)
tableView = MyTableView()
tableView.setModel(model)
delegate = CheckBoxDelegate(None)
tableView.setItemDelegateForColumn(0, delegate)
for row in range(5):
for column in range(4):
index = model.index(row, column, QModelIndex())
model.setData(index, 0)
model.setReadOnly([1,2])
tableView.setWindowTitle("CheckBox, readonly and drag&drop example")
tableView.show()
sys.exit(app.exec_())
I am attempting to design a label class that inherits from the PyQt5 base QLabel class that is able to track another widget. Here is the current code for my class:
class AttachedLabel(QLabel):
def __init__(self, attachedTo, *args, side="left", ** kwargs):
super().__init__(*args, **kwargs) # Run parent initialization
# Define instance variables
self.attached = attachedTo
self.side = side
# Update label position
self.updatePos()
def updatePos(self):
# Get "attached widget" position and dimensions
x = self.attached.geometry().x()
y = self.attached.geometry().y()
aWidth = self.attached.geometry().width()
aHeight = self.attached.geometry().height()
# Get own dimensions
width = self.geometry().width()
height = self.geometry().height()
if self.side == "top": # Above of attached widget
self.setGeometry(x, y-height, width, height)
elif self.side == "bottom": # Below attached widget
self.setGeometry(x, y+height+aHeight, width, height)
elif self.side == "right": # Right of attached widget
self.setGeometry(x + width + aWidth, y, width, height)
else: # Left of attached widget
self.setGeometry(x - width, y, width, height)
I want to be able to instantiate the label like so:
AttachedLabel(self.pushButton, self.centralwidget)
where self.pushButton is the widget it is supposed to be following. The issue is that I don't know how to detect when the widget moves in order to run my updatePos() function. I would ideally only update the label position when the other widget moves, but I want to refrain from havign to add extra code to the class of the widget that is being tracked. I have tried overriding the paintEvent, but that only triggers when the object itself needs to be redrawn, so it doesn't even function as a sub-optimal solution.
Is there some built-in method I can use/override to detect when the widget moves or when the screen itself is updated?
You have to use an eventFilter intersecting the QEvent::Move event and you should also track the resize through the QEvent::Resize event.
from dataclasses import dataclass, field
import random
from PyQt5 import QtCore, QtWidgets
class GeometryTracker(QtCore.QObject):
geometryChanged = QtCore.pyqtSignal()
def __init__(self, widget):
super().__init__(widget)
self._widget = widget
self.widget.installEventFilter(self)
#property
def widget(self):
return self._widget
def eventFilter(self, source, event):
if self.widget is source and event.type() in (
QtCore.QEvent.Move,
QtCore.QEvent.Resize,
):
self.geometryChanged.emit()
return super().eventFilter(source, event)
#dataclass
class TrackerManager:
widget1: field(default_factory=QtWidgets.QWidget)
widget2: field(default_factory=QtWidgets.QWidget)
alignment: QtCore.Qt.Alignment = QtCore.Qt.AlignLeft
enabled: bool = True
valid_alignments = (
QtCore.Qt.AlignLeft,
QtCore.Qt.AlignRight,
QtCore.Qt.AlignHCenter,
QtCore.Qt.AlignTop,
QtCore.Qt.AlignBottom,
QtCore.Qt.AlignVCenter,
)
def __post_init__(self):
self._traker = GeometryTracker(self.widget1)
self._traker.geometryChanged.connect(self.update)
if not any(self.alignment & flag for flag in self.valid_alignments):
raise ValueError("alignment is not valid")
def update(self):
if not self.enabled:
return
r = self.widget1.rect()
p1 = r.center()
c1 = r.center()
if self.alignment & QtCore.Qt.AlignLeft:
p1.setX(r.left())
if self.alignment & QtCore.Qt.AlignRight:
p1.setX(r.right())
if self.alignment & QtCore.Qt.AlignTop:
p1.setY(r.top())
if self.alignment & QtCore.Qt.AlignBottom:
p1.setY(r.bottom())
p2 = self.convert_position(p1)
c2 = self.convert_position(c1)
g = self.widget2.geometry()
g.moveCenter(c2)
if self.alignment & QtCore.Qt.AlignLeft:
g.moveRight(p2.x())
if self.alignment & QtCore.Qt.AlignRight:
g.moveLeft(p2.x())
if self.alignment & QtCore.Qt.AlignTop:
g.moveBottom(p2.y())
if self.alignment & QtCore.Qt.AlignBottom:
g.moveTop(p2.y())
self.widget2.setGeometry(g)
def convert_position(self, point):
gp = self.widget1.mapToGlobal(point)
if self.widget2.isWindow():
return gp
return self.widget2.parent().mapFromGlobal(gp)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.button = QtWidgets.QPushButton("Press me", self)
self.label = QtWidgets.QLabel(
"Tracker\nLabel", self, alignment=QtCore.Qt.AlignCenter
)
self.label.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents, True)
self.label.setFixedSize(200, 200)
self.label.setStyleSheet(
"background-color: salmon; border: 1px solid black; font-size: 40pt;"
)
self.resize(640, 480)
self.manager = TrackerManager(
widget1=self.button,
widget2=self.label,
alignment=QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter,
)
self.move_button()
def move_button(self):
pos = QtCore.QPoint(*random.sample(range(400), 2))
animation = QtCore.QPropertyAnimation(
targetObject=self.button,
parent=self,
propertyName=b"pos",
duration=1000,
startValue=self.button.pos(),
endValue=pos,
)
animation.finished.connect(self.move_button)
animation.start(QtCore.QAbstractAnimation.DeleteWhenStopped)
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Consider this example, modified from QStyledItemDelegate paint refresh issues :
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class MyElement(object):
def __init__(self, numid):
self.numid = numid
self.strid = "Hello world {}".format(numid)
self.param = 'a' if numid%2==0 else 'b'
def __repr__(self):
return "(numid {}, strid '{}', param '{}')".format(self.numid, self.strid, self.param)
elements = [ MyElement(i) for i in range(20) ]
print(elements)
class ElementListModel(QtCore.QAbstractListModel):
def __init__(self, elements = [], parent = None):
super(ElementListModel, self).__init__()
self.__elements = elements
def rowCount(self, parent):
return len(self.__elements)
def data(self, index, role):
thiselement = self.__elements[index.row()]
if role == QtCore.Qt.DisplayRole:
return str( thiselement.strid )
elif role == QtCore.Qt.DecorationRole:
return QtGui.QColor(thiselement.numid*10,thiselement.numid,0)
class ElementThumbDelegate(QtWidgets.QStyledItemDelegate): #(QtGui.QStyledItemDelegate):
def __init__(self, view, parent=None):
super(ElementThumbDelegate, self).__init__(parent)
def paint(self, painter, options, index):
super(ElementThumbDelegate, self).paint(painter, options, index)
#painter.setRenderHint(QtGui.QPainter.Antialiasing)
#painter.setPen(QtGui.QColor(255, 255, 255))
#painter.setBrush(QtGui.QColor(10, 10, 10))
#painter.drawRect(options.rect)
#painter.drawText(options.rect, QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter, str(index.data()))
#def sizeHint(self, options, index):
# return QtCore.QSize(50, 50)
def main():
app = QtWidgets.QApplication(sys.argv)
viewer = QtWidgets.QListView()
viewModel = ElementListModel(elements)
viewer.setModel(viewModel)
#viewer.setViewMode(QtWidgets.QListView.IconMode)
viewer.setItemDelegate(ElementThumbDelegate(viewer))
viewer.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
It results with this:
Note that there is a box by default to the left of the item, which you can "color" via DecorationRole in the data method of the ListModel (and apparently, you can also store an icon there, if you return a QIcon instead of QColor, but I've never tried it).
My question is:
How can I draw a border around that icon space/box, depending on some property? In the example above, if MyElement.param == 'a' of a given element in the list, then I would want a light blue (RGB: (38, 76, 100), or #62c2ff) border of width 2 pixels drawn around the "icon space/box" - just like I've manually done in the mockup image in the circled area; otherwise I would not want a border
How could I additionally draw a single letter in the center of that space/box, depending on some property? For instance, if a MyElement.param == 'b' of a given element in the list, then I'd like the letter 'b' written in white in the middle of the "icon space/box" - otherwise, I would not want an extra text written in that space.
The paint() method of ElementThumbDelegate should have otherwise been enough of a pointer on how to do this; but if you uncomment it, you'll see the entire item is changed - not just the left icon box/space.
The class that performs the painting is the delegate who takes the information from the DecorationRole role to create the icon, so the solution is to create the custom icon depending on the item information. That creation can be done in the model or in the delegate, in this case I will use the second option but for this the item must be exposed through a custom role like Qt.UserRole:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class MyElement(object):
def __init__(self, numid):
self.numid = numid
self.strid = "Hello world {}".format(numid)
self.param = "a" if numid % 2 == 0 else "b"
def __repr__(self):
return "(numid {}, strid '{}', param '{}')".format(
self.numid, self.strid, self.param
)
elements = [MyElement(i) for i in range(20)]
class ElementListModel(QtCore.QAbstractListModel):
def __init__(self, elements=[], parent=None):
super(ElementListModel, self).__init__()
self.__elements = elements
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.__elements)
def data(self, index, role):
if not index.isValid() or not (0 <= index.row() < self.rowCount()):
return
thiselement = self.__elements[index.row()]
if role == QtCore.Qt.DisplayRole:
return str(thiselement.strid)
if role == QtCore.Qt.UserRole:
return thiselement
class ElementThumbDelegate(QtWidgets.QStyledItemDelegate):
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
thiselement = index.data(QtCore.Qt.UserRole)
if isinstance(thiselement, MyElement):
if thiselement.param == "a":
option.features |= QtWidgets.QStyleOptionViewItem.HasDecoration
pixmap = QtGui.QPixmap(option.decorationSize)
pixmap.fill(QtGui.QColor("#62c2ff"))
painter = QtGui.QPainter(pixmap)
color = QtGui.QColor(thiselement.numid * 10, thiselement.numid, 0)
painter.fillRect(pixmap.rect().adjusted(2, 2, -2, -2), color)
painter.end()
option.icon = QtGui.QIcon(pixmap)
if thiselement.param == "b":
option.features |= QtWidgets.QStyleOptionViewItem.HasDecoration
pixmap = QtGui.QPixmap(option.decorationSize)
color = QtGui.QColor(thiselement.numid * 10, thiselement.numid, 0)
pixmap.fill(color)
painter = QtGui.QPainter(pixmap)
painter.setPen(QtGui.QColor("white"))
painter.drawText(pixmap.rect(), QtCore.Qt.AlignCenter, "b")
painter.end()
option.icon = QtGui.QIcon(pixmap)
def main():
app = QtWidgets.QApplication(sys.argv)
viewer = QtWidgets.QListView()
viewModel = ElementListModel(elements)
viewer.setModel(viewModel)
viewer.setItemDelegate(ElementThumbDelegate(viewer))
viewer.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
How can i position the Checkbox of an item in a QListView at the top left? I tried using a stylesheet but when i resize my item, the checkbox does not maintain it's position in the top left. It becomes hidden.
Does anyone have any suggestions on how to achieve this? Do I use QItemDelegate or QStyledItemDelegate to solve my problem?
My goal is to make it appear like this...
import os, sys, re
from Qt import QtWidgets, QtGui, QtCore
from PictureShop.Resources import StyleUtils
################################################################################
# Dummy Values
################################################################################
values = ['MomBod','Colonel','Tater','Tot','Ginger','Donut','Sport','LaLa','Itchy','Bruiser','Cotton','Cumulus','Toodles']
################################################################################
# Widgets
################################################################################
class ViewerWidget(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.resize(500,500)
self.uiIconSize = QtWidgets.QSlider(QtCore.Qt.Horizontal)
self.uiIconSize.setRange(32,256)
self.uiIconSize.setValue(96)
self.uiIconSize.setMinimumWidth(100)
self.viewerModel = QtGui.QStandardItemModel()
self.uiListView = QtWidgets.QListView()
self.uiListView.setSpacing(5)
self.uiListView.setMovement(QtWidgets.QListView.Static)
self.uiListView.setViewMode(QtWidgets.QListView.IconMode)
self.uiListView.setLayoutMode(QtWidgets.QListView.Batched)
self.uiListView.setBatchSize(100)
self.uiListView.setFlow(QtWidgets.QListView.LeftToRight)
self.uiListView.setWrapping(True)
self.uiListView.setResizeMode(QtWidgets.QListView.Adjust)
self.uiListView.setDragEnabled(False)
self.uiListView.setUniformItemSizes(True)
self.uiListView.setSelectionMode(QtWidgets.QListView.ExtendedSelection)
self.uiListView.setModel(self.viewerModel)
# layout
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.uiListView)
self.layout.addWidget(self.uiIconSize)
self.setLayout(self.layout)
# Signals
self.uiIconSize.valueChanged.connect(self.slotChangedIconSize)
self.uiIconSize.setValue(96)
self.uiListView.setIconSize(QtCore.QSize(96,96))
# Init
self.populateModel()
self.setStyleSheet('''
QWidget::indicator {
subcontrol-position: top center;
position: relative;
left: 30px;
top: -20px;
}
''')
# Methods
def populateModel(self):
model = self.viewerModel
model.clear()
icon = QtGui.QIcon('C:/Users/jmartini/Desktop/brokenImage.png')
for x in values:
newItem = QtGui.QStandardItem()
newItem.setCheckable(True)
newItem.setData(icon, role=QtCore.Qt.DecorationRole)
model.appendRow(newItem)
# Slots
def slotChangedIconSize(self):
size = self.uiIconSize.value()
self.uiListView.setIconSize(QtCore.QSize(size,size))
################################################################################
# Unit Testing
################################################################################
def test_ViewerWidget():
app = QtWidgets.QApplication(sys.argv)
app.setOrganizationName('mine')
app.setApplicationName('browser')
ex = ViewerWidget()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
pass
test_ViewerWidget()
Help from answers below. I have a bug shown here:
Got a working solution based on comments below:
import os, sys, re
from Qt import QtWidgets, QtGui, QtCore
################################################################################
# Dummy Values
################################################################################
values = ['MomBod','Colonel','Tater','Tot','Ginger','Donut','Sport','LaLa','Itchy','Bruiser','Cotton','Cumulus','Toodles']
class ItemDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self, parent=None, *args):
QtWidgets.QStyledItemDelegate.__init__(self, parent, *args)
# overrides
def sizeHint(self, option, index):
isw, ish = option.decorationSize.toTuple()
return QtCore.QSize(isw, ish)
def getCheckboxRect(self, option):
return QtCore.QRect(4, 4, 18, 18).translated(option.rect.topLeft())
def paint(self, painter, option, index):
painter.save()
# Draw
isw, ish = option.decorationSize.toTuple()
x, y, dx, dy = option.rect.x(), option.rect.y(), option.rect.width(), option.rect.height()
# Decoration
pic = index.data(QtCore.Qt.DecorationRole)
if pic:
painter.drawPixmap(x, y, pic.pixmap(isw, ish))
# Indicate Selected
painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))
if option.state & QtWidgets.QStyle.State_Selected:
painter.setBrush(QtGui.QBrush(QtGui.QColor(0,70,240,128)))
else:
painter.setBrush(QtGui.QBrush(QtCore.Qt.NoBrush))
painter.drawRect(QtCore.QRect(x, y, dx, dy))
# Checkstate
value = index.data(QtCore.Qt.CheckStateRole)
if value is not None:
opt = QtWidgets.QStyleOptionViewItem()
opt.rect = self.getCheckboxRect(option)
opt.state = opt.state & ~QtWidgets.QStyle.State_HasFocus
if value == QtCore.Qt.Unchecked:
opt.state |= QtWidgets.QStyle.State_Off
elif value == QtCore.Qt.PartiallyChecked:
opt.state |= QtWidgets.QStyle.State_NoChange
elif value == QtCore.Qt.Checked:
opt.state = QtWidgets.QStyle.State_On
style = QtWidgets.QApplication.style()
style.drawPrimitive(
QtWidgets.QStyle.PE_IndicatorViewItemCheck, opt, painter, None
)
painter.restore()
def editorEvent(self, event, model, option, index):
flags = model.flags(index)
if (
not (flags & QtCore.Qt.ItemIsUserCheckable)
or not (option.state & QtWidgets.QStyle.State_Enabled)
or not (flags & QtCore.Qt.ItemIsEnabled)
):
return False
value = index.data(QtCore.Qt.CheckStateRole)
if value is None:
return False
style = QtWidgets.QApplication.style()
if event.type() in (
QtCore.QEvent.MouseButtonRelease,
QtCore.QEvent.MouseButtonDblClick,
QtCore.QEvent.MouseButtonPress,
):
viewOpt = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(viewOpt, index)
checkRect = self.getCheckboxRect(viewOpt)
if event.button() != QtCore.Qt.LeftButton or not checkRect.contains(
event.pos()
):
return False
if event.type() in (
QtCore.QEvent.MouseButtonPress,
QtCore.QEvent.MouseButtonDblClick,
):
return True
elif event.type() == QtCore.QEvent.KeyPress:
if event.key() not in (QtCore.Qt.Key_Space, QtCore.Qt.Key_Select):
return False
else:
return False
state = value
if flags & QtCore.Qt.ItemIsTristate:
state = QtCore.Qt.CheckState((state + 1) % 3)
else:
state = (
QtCore.Qt.Unchecked if state == QtCore.Qt.Checked else QtCore.Qt.Checked
)
return model.setData(index, state, QtCore.Qt.CheckStateRole)
################################################################################
# Widgets
################################################################################
class ViewerWidget(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.resize(500,500)
self.uiIconSize = QtWidgets.QSlider(QtCore.Qt.Horizontal)
self.uiIconSize.setRange(32,256)
self.uiIconSize.setValue(96)
self.uiIconSize.setMinimumWidth(100)
self.viewerModel = QtGui.QStandardItemModel()
self.uiListView = QtWidgets.QListView()
self.uiListView.setSpacing(5)
self.uiListView.setMovement(QtWidgets.QListView.Static)
self.uiListView.setViewMode(QtWidgets.QListView.IconMode)
self.uiListView.setLayoutMode(QtWidgets.QListView.Batched)
self.uiListView.setBatchSize(100)
self.uiListView.setFlow(QtWidgets.QListView.LeftToRight)
self.uiListView.setWrapping(True)
self.uiListView.setResizeMode(QtWidgets.QListView.Adjust)
self.uiListView.setDragEnabled(False)
self.uiListView.setUniformItemSizes(True)
self.uiListView.setSelectionMode(QtWidgets.QListView.ExtendedSelection)
self.uiListView.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.uiListView.setModel(self.viewerModel)
self.uiListView.setItemDelegate(ItemDelegate())
# layout
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.uiListView)
self.layout.addWidget(self.uiIconSize)
self.setLayout(self.layout)
# Signals
self.uiIconSize.valueChanged.connect(self.slotChangedIconSize)
self.uiIconSize.setValue(96)
self.uiListView.setIconSize(QtCore.QSize(96,96))
# Init
self.populateModel()
# Methods
def populateModel(self):
model = self.viewerModel
model.clear()
icon = QtGui.QIcon("C:/Users/JokerMartini-Asus/Desktop/thumbnail_image.png")
for x in values:
newItem = QtGui.QStandardItem()
newItem.setCheckable(True)
newItem.setData(icon, role=QtCore.Qt.DecorationRole)
model.appendRow(newItem)
# Slots
def slotChangedIconSize(self):
size = self.uiIconSize.value()
self.uiListView.setIconSize(QtCore.QSize(size,size))
################################################################################
# Unit Testing
################################################################################
def test_ViewerWidget():
app = QtWidgets.QApplication(sys.argv)
app.setOrganizationName('mine')
app.setApplicationName('browser')
ex = ViewerWidget()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
pass
test_ViewerWidget()
Using a delegate you can paint after the icon is painted:
class Delegate(QtWidgets.QStyledItemDelegate):
def initStyleOption(self, option, index):
super(Delegate, self).initStyleOption(option, index)
option.features &= ~QtWidgets.QStyleOptionViewItem.HasCheckIndicator
def paint(self, painter, option, index):
super(Delegate, self).paint(painter, option, index)
value = index.data(QtCore.Qt.CheckStateRole)
if value is not None:
opt = QtWidgets.QStyleOptionViewItem()
opt.rect = self.checkRect(option)
opt.state = opt.state & ~QtWidgets.QStyle.State_HasFocus
if value == QtCore.Qt.Unchecked:
opt.state |= QtWidgets.QStyle.State_Off
elif value == QtCore.Qt.PartiallyChecked:
opt.state |= QtWidgets.QStyle.State_NoChange
elif value == QtCore.Qt.Checked:
opt.state = QtWidgets.QStyle.State_On
widget = option.widget
style = QtWidgets.QApplication.style() if widget is None else widget.style()
style.drawPrimitive(
QtWidgets.QStyle.PE_IndicatorViewItemCheck, opt, painter, widget
)
def checkRect(self, option):
height = option.rect.height()
x, y, w, h = (f * height for f in (0.15, 0.15, 0.2, 0.2))
return QtCore.QRect(x, y, w, h).translated(option.rect.topLeft())
def editorEvent(self, event, model, option, index):
# https://code.qt.io/cgit/qt/qtbase.git/tree/src/widgets/itemviews/qstyleditemdelegate.cpp?h=5.13#n278
flags = model.flags(index)
if (
not (flags & QtCore.Qt.ItemIsUserCheckable)
or not (option.state & QtWidgets.QStyle.State_Enabled)
or not (flags & QtCore.Qt.ItemIsEnabled)
):
return False
value = index.data(QtCore.Qt.CheckStateRole)
if value is None:
return False
widget = option.widget
style = widget.style() if widget is not None else QtWidgets.QApplication.style()
if event.type() in (
QtCore.QEvent.MouseButtonRelease,
QtCore.QEvent.MouseButtonDblClick,
QtCore.QEvent.MouseButtonPress,
):
viewOpt = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(viewOpt, index)
checkRect = self.checkRect(viewOpt)
if event.button() != QtCore.Qt.LeftButton or not checkRect.contains(
event.pos()
):
return False
if event.type() in (
QtCore.QEvent.MouseButtonPress,
QtCore.QEvent.MouseButtonDblClick,
):
return True
elif event.type() == QtCore.QEvent.KeyPress:
if event.key() not in (QtCore.Qt.Key_Space, QtCore.Qt.Key_Select):
return False
else:
return False
state = value
if flags & QtCore.Qt.ItemIsUserTristate:
state = QtCore.Qt.CheckState((state + 1) % 3)
else:
state = (
QtCore.Qt.Unchecked if state == QtCore.Qt.Checked else QtCore.Qt.Checked
)
return model.setData(index, state, QtCore.Qt.CheckStateRole)
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_())