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()
Related
I'm fairly new to PyQt
I'm trying to drawing a line from 1 QLabel to another.
My 2 QLabel are located on another QLabel which acts as an image in my GUI.
I've managed to track the mouse event and move the label around, but I cannot draw the line between them using QPainter.
Thank you in advance :)
This is my MouseTracking class
class MouseTracker(QtCore.QObject):
positionChanged = QtCore.pyqtSignal(QtCore.QPoint)
def __init__(self, widget):
super().__init__(widget)
self._widget = widget
self.widget.setMouseTracking(True)
self.widget.installEventFilter(self)
#property
def widget(self):
return self._widget
def eventFilter(self, o, e):
if e.type() == QtCore.QEvent.MouseMove:
self.positionChanged.emit(e.pos())
return super().eventFilter(o, e)
This is my DraggableLabel class:
class DraggableLabel(QLabel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.LabelIsMoving = False
self.setStyleSheet("border-color: rgb(238, 0, 0); border-width : 2.0px; border-style:inset; background: transparent;")
self.origin = None
# self.setDragEnabled(True)
def mousePressEvent(self, event):
if not self.origin:
# update the origin point, we'll need that later
self.origin = self.pos()
if event.button() == Qt.LeftButton:
self.LabelIsMoving = True
self.mousePos = event.pos()
# print(event.pos())
def mouseMoveEvent(self, event):
if event.buttons() == Qt.LeftButton:
# move the box
self.move(self.pos() + event.pos() - self.mousePos)
# print(event.pos())
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton:
print(event.pos())
def paintEvent(self, event):
painter = QPainter()
painter.setBrush(Qt.red)
# painter.setPen(qRgb(200,0,0))
painter.drawLine(10, 10, 200, 200)
This is my custom class for the QTabwigdet (since I need to control and track the position of 2 QLabels whenever the user add/insert a new Tab)
class DynamicTab(QWidget):
def __init__(self):
super(DynamicTab, self).__init__()
# self.count = 0
self.setMouseTracking(True)
self.setAcceptDrops(True)
self.bool = True
self.layout = QVBoxLayout(self)
self.label = QLabel()
self.layout.addChildWidget(self.label)
self.icon1 = DraggableLabel(parent=self)
#pixmap for icon 1
pixmap = QPixmap('icon1.png')
# currentTab.setLayout(QVBoxLayout())
# currentTab.layout.setWidget(QRadioButton())
self.icon1.setPixmap(pixmap)
self.icon1.setScaledContents(True)
self.icon1.setFixedSize(20, 20)
self.icon2 = DraggableLabel(parent=self)
pixmap = QPixmap('icon1.png')
# currentTab.setLayout(QVBoxLayout())
# currentTab.layout.setWidget(QRadioButton())
self.icon2.setPixmap(pixmap)
self.icon2.setScaledContents(True)
self.icon2.setFixedSize(20, 20)
#self.label.move(event.x() - self.label_pos.x(), event.y() - self.label_pos.y())
MainWindow and main method:
class UI_MainWindow(QMainWindow):
def __init__(self):
super(UI_MainWindow, self).__init__()
self.setWindowTitle("QHBoxLayout")
self.PictureTab = QTabWidget
def __setupUI__(self):
# super(UI_MainWindow, self).__init__()
self.setWindowTitle("QHBoxLayout")
loadUi("IIML_test2.ui", self)
self.tabChanged(self.PictureTab)
# self.tabChanged(self.tabWidget)
self.changeTabText(self.PictureTab, index=0, TabText="Patient1")
self.Button_ImportNew.clicked.connect(lambda: self.insertTab(self.PictureTab))
# self.PictureTab.currentChanged.connect(lambda: self.tabChanged(QtabWidget=self.PictureTab))
# self.tabWidget.currentChanged.connect(lambda: self.tabChanged(QtabWidget=self.tabWidget))
def tabChanged(self, QtabWidget):
QtabWidget.currentChanged.connect(lambda : print("Tab was changed to ", QtabWidget.currentIndex()))
def changeTabText(self, QTabWidget, index, TabText):
QTabWidget.setTabText(index, TabText)
def insertTab(self, QtabWidget):
# QFileDialog.getOpenFileNames(self, 'Open File', '.')
QtabWidget.addTab(DynamicTab(), "New Tab")
# get number of active tab
count = QtabWidget.count()
# change the view to the last added tab
currentTab = QtabWidget.widget(count-1)
QtabWidget.setCurrentWidget(currentTab)
pixmap = QPixmap('cat.jpg')
#currentTab.setLayout(QVBoxLayout())
#currentTab.layout.setWidget(QRadioButton())
# currentTab.setImage("cat.jpg")
currentTab.label.setPixmap(pixmap)
currentTab.label.setScaledContents(True)
currentTab.label.setFixedSize(self.label.width(), self.label.height())
tracker = MouseTracker(currentTab.label)
tracker.positionChanged.connect(self.on_positionChanged)
self.label_position = QtWidgets.QLabel(currentTab.label, alignment=QtCore.Qt.AlignCenter)
self.label_position.setStyleSheet('background-color: white; border: 1px solid black')
currentTab.label.show()
# print(currentTab.label)
#QtCore.pyqtSlot(QtCore.QPoint)
def on_positionChanged(self, pos):
delta = QtCore.QPoint(30, -15)
self.label_position.show()
self.label_position.move(pos + delta)
self.label_position.setText("(%d, %d)" % (pos.x(), pos.y()))
self.label_position.adjustSize()
# def SetupUI(self, MainWindow):
#
# self.setLayout(self.MainLayout)
if __name__ == '__main__':
app = QApplication(sys.argv)
UI_MainWindow = UI_MainWindow()
UI_MainWindow.__setupUI__()
widget = QtWidgets.QStackedWidget()
widget.addWidget(UI_MainWindow)
widget.setFixedHeight(900)
widget.setFixedWidth(1173)
widget.show()
try:
sys.exit(app.exec_())
except:
print("Exiting")
My concept: I have a DynamicTab (QTabWidget) which acts as a picture opener (whenever the user press Import Now). The child of this Widget are 3 Qlabels: self.label is the picture it self and two other Qlabels are the icon1 and icon2 which I'm trying to interact/drag with (Draggable Label)
My Problem: I'm trying to track my mouse movement and custom the painter to paint accordingly. I'm trying that out by telling the painter class to paint whenever I grab the label and move it with my mouse (Hence, draggable). However, I can only track the mouse position inside the main QLabel (the main picture) whenever I'm not holding or clicking my left mouse.
Any help will be appreciated here.
Thank you guys.
Painting can only happen within the widget rectangle, so you cannot draw outside the boundaries of DraggableLabel.
The solution is to create a further custom widget that shares the same parent, and then draw the line that connects the center of the other two.
In the following example I install an event filter on the two draggable labels which will update the size of the custom widget based on them (so that its geometry will always include those two geometries) and call self.update() which schedules a repainting. Note that since the widget is created above the other two, it might capture mouse events that are intended for the others; to prevent that, the Qt.WA_TransparentForMouseEvents attribute must be set.
class Line(QWidget):
def __init__(self, obj1, obj2, parent):
super().__init__(parent)
self.obj1 = obj1
self.obj2 = obj2
self.obj1.installEventFilter(self)
self.obj2.installEventFilter(self)
self.setAttribute(Qt.WA_TransparentForMouseEvents)
def eventFilter(self, obj, event):
if event.type() in (event.Move, event.Resize):
rect = self.obj1.geometry() | self.obj2.geometry()
corner = rect.bottomRight()
self.resize(corner.x(), corner.y())
self.update()
return super().eventFilter(obj, event)
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(painter.Antialiasing)
painter.setPen(QColor(200, 0, 0))
painter.drawLine(
self.obj1.geometry().center(),
self.obj2.geometry().center()
)
class DynamicTab(QWidget):
def __init__(self):
# ...
self.line = Line(self.icon1, self.icon2, self)
Notes:
to simplify things, I only use resize() (not setGeometry()), in this way the widget will always be placed on the top left corner of the parent and we can directly get the other widget's coordinates without any conversion;
the custom widget is placed above the other two because it is added after them; if you want to place it under them, use self.line.lower();
the painter must always be initialized with the paint device argument, either by using QPainter(obj) or painter.begin(obj), otherwise no painting will happen (and you'll get lots of errors in the output);
do not use layout.addChildWidget() (which is used internally by the layout), but the proper addWidget() function of the layout;
the stylesheet border syntax can be shortened with border: 2px inset rgb(238, 0, 0);;
the first lines of insertTab could be simpler: currentTab = DynamicTab() QtabWidget.addTab(currentTab, "New Tab");
currentTab.label.setFixedSize(self.label.size());
QMainWindow is generally intended as a top level widget, it's normally discouraged to add it to a QStackedWidget; note that if you did that because of a Youtube tutorial, that tutorial is known for suggesting terrible practices (like the final try/except block) which should not be followed;
only classes and constants should have capitalized names, not variables and functions which should always start with a lowercase letter;
I want to run a AnimationGroup with different changing animations.
But the problem is that the function clear()
self.group = QtCore.QSequentialAnimationGroup(self)
def anim(self):
if X == True:
self.group.addAnimation(self.animation_1)
self.group.addAnimation(self.animation_2)
elif X == False:
self.group.addAnimation(self.animation_3)
self.group.addAnimation(self.animation_4)
self.group.start()
self.group.clear()
displays an error
RuntimeError: wrapped C/C++ object of type QVariantAnimation has been deleted
I can’t constantly create a new group.
def anim(self):
self.group = QtCore.QSequentialAnimationGroup(self)
if X == True:
self.group.addAnimation(self.animation_1)
self.group.addAnimation(self.animation_2)
elif X == False:
self.group.addAnimation(self.animation_3)
self.group.addAnimation(self.animation_4)
self.group.start()
self.group.clear()
I tried to use removeAnimation
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
class Rad(QtWidgets.QWidget):
def __init__(self, group, pos, parent=None):
super(Rad, self).__init__(parent)
self.resize(50, 50)
lay = QtWidgets.QVBoxLayout(self)
self.radio = QtWidgets.QRadioButton()
self.label = QtWidgets.QLabel()
self.label.setText('but-{}'.format(pos))
self.group = group
self.pos = pos
lay.addWidget(self.radio)
lay.addWidget(self.label)
self.radio.toggled.connect(self.fun)
self.animation = QtCore.QVariantAnimation()
self.animation.setDuration(1000)
self.animation.valueChanged.connect(self.value)
self.animation.setStartValue(100)
self.animation.setEndValue(0)
self.can = False
def value(self, val):
self.move(self.pos, val)
def fun(self):
self.animation.setStartValue(
100
if not self.can
else 0
)
self.animation.setEndValue(
0
if not self.can
else 100
)
if self.group.animationAt(1) == None :
print("Bad")
else :
print("Good")
self.group.removeAnimation(self.group.animationAt(0))
self.group.addAnimation(self.animation)
self.group.start()
self.can = not self.can
class Test(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.group = QtCore.QSequentialAnimationGroup(self)
self.buts = QtWidgets.QButtonGroup(self, exclusive=True)
wid_1 = Rad(self.group, 200, self)
wid_2 = Rad(self.group, 100, self)
wid_3 = Rad(self.group, 0, self)
self.buts.addButton(wid_1.radio, 0)
self.buts.addButton(wid_2.radio,1)
self.buts.addButton(wid_3.radio, 2)
wid_1.setStyleSheet('background:brown;')
wid_2.setStyleSheet('background:yellow;')
wid_3.setStyleSheet('background:green;')
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = Test()
w.resize(500, 500)
w.show()
sys.exit(app.exec_())
And now the animation has become sharp
And in the console output
QAnimationGroup::animationAt: index is out of bounds
From what I can deduce is that you want the pressed item to move down and if any item was in the low position then it must return to its position. If so then my answer should work.
You should not use the clear method since that removes and deletes the animation, instead use takeAnimation(0) until there are no animations, and just add the new animations, but that logic should not be inside "Rad" but in the Test class:
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
class Rad(QtWidgets.QWidget):
def __init__(self, text, parent=None):
super(Rad, self).__init__(parent)
self.resize(50, 50)
self.radio = QtWidgets.QRadioButton()
self.label = QtWidgets.QLabel()
self.label.setText(text)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.radio)
lay.addWidget(self.label)
self.animation = QtCore.QVariantAnimation()
self.animation.setDuration(1000)
self.animation.valueChanged.connect(self.on_value_changed)
self.animation.setStartValue(100)
self.animation.setEndValue(0)
#QtCore.pyqtSlot("QVariant")
def on_value_changed(self, val):
pos = self.pos()
pos.setY(val)
self.move(pos)
def swap_values(self):
self.animation.blockSignals(True)
start_value = self.animation.startValue()
end_value = self.animation.endValue()
self.animation.setStartValue(end_value)
self.animation.setEndValue(start_value)
self.animation.blockSignals(False)
class Test(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.buts = QtWidgets.QButtonGroup(self, exclusive=True)
wid_1 = Rad("but-200", self)
wid_1.setStyleSheet("background:brown;")
wid_1.move(200, 0)
wid_2 = Rad("but-100", self)
wid_2.setStyleSheet("background:yellow;")
wid_2.move(100, 0)
wid_3 = Rad("but-0", self)
wid_3.setStyleSheet("background:green;")
wid_3.move(0, 0)
self.buts.addButton(wid_1.radio, 0)
self.buts.addButton(wid_2.radio, 1)
self.buts.addButton(wid_3.radio, 2)
self.buts.buttonToggled.connect(self.on_button_toggled)
self.group = QtCore.QSequentialAnimationGroup(self)
self.last_widget = None
#QtCore.pyqtSlot(QtWidgets.QAbstractButton, bool)
def on_button_toggled(self, button, state):
if state:
wid = button.parent()
if self.group.animationCount() > 0:
self.group.takeAnimation(0)
if isinstance(self.last_widget, Rad):
self.last_widget.swap_values()
self.group.addAnimation(self.last_widget.animation)
wid.swap_values()
self.group.addAnimation(wid.animation)
self.group.start()
self.last_widget = wid
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = Test()
w.resize(500, 500)
w.show()
sys.exit(app.exec_())
I am trying to make a GUI with PyQt5. It will have a notification button with an icon. I want to add a small bubble with the number of notifications on the icon.
If a number is not possible, I would like to use a red dot as a backup method.
But how should I keep track of the new notifications (like a listener for notification) and change the icon while the window is running?
I have been googling about this problem, but only mobile development stuff and non-PyQt5 related results come up.
Expected result: Let's say we have a list. And the icon of the button will automatically change when a new item is added to the list. Then when the button is clicked, the icon will change back.
A possible solution is to create a widget that has a layout where you place a QToolButton and at the top right a QLabel with a QPixmap that has the number
from PyQt5 import QtCore, QtGui, QtWidgets
def create_pixmap(point, radius=64):
rect = QtCore.QRect(QtCore.QPoint(), 2 * radius * QtCore.QSize(1, 1))
pixmap = QtGui.QPixmap(rect.size())
rect.adjust(1, 1, -1, -1)
pixmap.fill(QtCore.Qt.transparent)
painter = QtGui.QPainter(pixmap)
painter.setRenderHints(
QtGui.QPainter.Antialiasing | QtGui.QPainter.TextAntialiasing
)
pen = painter.pen()
painter.setPen(QtCore.Qt.NoPen)
gradient = QtGui.QLinearGradient()
gradient.setColorAt(1, QtGui.QColor("#FD6684"))
gradient.setColorAt(0, QtGui.QColor("#E0253F"))
gradient.setStart(0, rect.height())
gradient.setFinalStop(0, 0)
painter.setBrush(QtGui.QBrush(gradient))
painter.drawEllipse(rect)
painter.setPen(pen)
painter.drawText(rect, QtCore.Qt.AlignCenter, str(point))
painter.end()
return pixmap
class NotificationButton(QtWidgets.QWidget):
scoreChanged = QtCore.pyqtSignal(int)
def __init__(self, score=0, icon=QtGui.QIcon(), radius=12, parent=None):
super(NotificationButton, self).__init__(parent)
self.m_score = score
self.m_radius = radius
self.setContentsMargins(0, self.m_radius, self.m_radius, 0)
self.m_button = QtWidgets.QToolButton(clicked=self.clear)
self.m_button.setContentsMargins(0, 0, 0, 0)
self.m_button.setIcon(icon)
self.m_button.setIconSize(QtCore.QSize(18, 18))
lay = QtWidgets.QVBoxLayout(self)
lay.setContentsMargins(0, 0, 0, 0)
lay.addWidget(self.m_button)
self.m_label = QtWidgets.QLabel(self)
self.m_label.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
self.m_label.raise_()
self.setSizePolicy(self.m_button.sizePolicy())
self.update_notification()
#QtCore.pyqtProperty(int, notify=scoreChanged)
def score(self):
return self.m_score
#score.setter
def score(self, score):
if self.m_score != score:
self.m_score = score
self.update_notification()
self.scoreChanged.emit(score)
#QtCore.pyqtSlot()
def clear(self):
self.score = 0
#QtCore.pyqtProperty(int)
def radius(self):
return self.m_radius
#radius.setter
def radius(self, radius):
self.m_radius = radius
self.update_notification()
def update_notification(self):
self.setContentsMargins(0, self.m_radius, self.m_radius, 0)
self.m_label.setPixmap(create_pixmap(self.m_score, self.m_radius))
self.m_label.adjustSize()
def resizeEvent(self, event):
self.m_label.move(self.width() - self.m_label.width(), 0)
super(NotificationButton, self).resizeEvent(event)
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.m_item_le = QtWidgets.QLineEdit("Stack Overflow")
add_button = QtWidgets.QPushButton("Add", clicked=self.add_item)
self.m_notification_button = NotificationButton(
icon=QtGui.QIcon("image.png")
)
self.m_list_widget = QtWidgets.QListWidget()
vlay = QtWidgets.QVBoxLayout(self)
hlay = QtWidgets.QHBoxLayout()
hlay.addWidget(self.m_item_le)
hlay.addWidget(add_button)
vlay.addLayout(hlay)
vlay.addWidget(
self.m_notification_button, alignment=QtCore.Qt.AlignRight
)
vlay.addWidget(self.m_list_widget)
#QtCore.pyqtSlot()
def add_item(self):
text = self.m_item_le.text()
self.m_list_widget.addItem(
"%s: %s" % (self.m_list_widget.count(), text)
)
self.m_notification_button.score += 1
self.m_list_widget.scrollToBottom()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
It would be nice if you show your code so far. Anyhow, these may help you solve your question:
You'll need two different icons: one to represent a dirty (just loaded) list and the other for the "clean" list
class YourClass(Dialog):
def __init__(self)
super().__init__()
self.lst = []
# ...
def setUI(self):
# ...
self.notButton = QPushButton(icon_off, '0')
self.notButton.clicked.connect(self.clearButton)
# ...
#pyqtSlot()
def clearButton(self):
self.notButton.setIcon(icon_clean)
def addToList(self, item):
self.lst.append(item)
self.notButton.setIcon(icon_dirty)
self.notButton.setText(str(len(self.lst)
A possible solution to updating the icon would be to have a separate image file for each icon and its associated notification number. You can keep track of the number of current notifications in a counter variable. Use that number to call the corresponding icon.
I'm adding a color parameter to the LineBand subclass of QWidget. I've found several examples of how to add additional parameters to a subclass in Python 3 and believe I've followed the advice. Yet, when I call the new version of the class using box = LineBand(self.widget2, color), I get the error File "C:/Users/...", line 63, in showBoxes ... box = LineBand(viewport, color) ... TypeError: __init__() takes 2 positional arguments but 3 were given. But, I'm only calling LineBand with 2 arguments, right? Below is the complete code. I've commented all the sections I've changed. I've also commented out the code that changes the background color of the text in order to see the colored lines more clearly (when they actually are drawn). The background color code works fine.
import sys
from PySide.QtCore import *
from PySide.QtGui import *
db = ((5,8,'A',Qt.darkMagenta),(20,35,'B',Qt.darkYellow),(45,60,'C',Qt.darkCyan)) # added color to db
class TextEditor(QTextEdit):
def __init__(self, parent=None):
super().__init__(parent)
text="This is example text that is several lines\nlong and also\nstrangely broken up and can be\nwrapped."
self.setText(text)
cursor = self.textCursor()
for n in range(0,len(db)):
row = db[n]
startChar = row[0]
endChar = row[1]
id = row[2]
color = row[3] # assign color from db to variable
cursor.setPosition(startChar)
cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor, endChar-startChar)
#charfmt = cursor.charFormat()
#charfmt.setBackground(QColor(color)) # assign color to highlight (background)
#cursor.setCharFormat(charfmt)
cursor.clearSelection()
self.setTextCursor(cursor)
def getBoundingRect(self, start, end):
cursor = self.textCursor()
cursor.setPosition(end)
last_rect = end_rect = self.cursorRect(cursor)
cursor.setPosition(start)
first_rect = start_rect = self.cursorRect(cursor)
if start_rect.y() != end_rect.y():
cursor.movePosition(QTextCursor.StartOfLine)
first_rect = last_rect = self.cursorRect(cursor)
while True:
cursor.movePosition(QTextCursor.EndOfLine)
rect = self.cursorRect(cursor)
if rect.y() < end_rect.y() and rect.x() > last_rect.x():
last_rect = rect
moved = cursor.movePosition(QTextCursor.NextCharacter)
if not moved or rect.y() > end_rect.y():
break
last_rect = last_rect.united(end_rect)
return first_rect.united(last_rect)
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
self.edit = TextEditor(self)
layout = QVBoxLayout(self)
layout.addWidget(self.edit)
self.boxes = []
def showBoxes(self):
while self.boxes:
self.boxes.pop().deleteLater()
viewport = self.edit.viewport()
for start, end, id, color in db: # get color too
rect = self.edit.getBoundingRect(start, end)
box = LineBand(viewport, color) # call LineBand with color as argument
box.setGeometry(rect)
box.show()
self.boxes.append(box)
def resizeEvent(self, event):
self.showBoxes()
super().resizeEvent(event)
class LineBand(QWidget):
def __init__(self, color): # define color within __init__
super().__init__(self)
self.color = color
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.setPen(QPen(color, 1.8)) # call setPen with color
painter.drawLine(self.rect().topLeft(), self.rect().bottomRight())
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.show()
window.showBoxes()
app.exec_()
sys.exit(app.exec_())
When a method is not overwritten it will be the same as the implemented method of the parent so if you want it to work you must add those parameters, since these depend many times on the parent a simple way is to use *args and **kwargs and pass the new parameter as the first parameter. In addition you must use self.color instead of color since color only exists in the constructor.
class Window(QWidget):
[...]
def showBoxes(self):
while self.boxes:
self.boxes.pop().deleteLater()
viewport = self.edit.viewport()
for start, end, id, color in db: # get color too
rect = self.edit.getBoundingRect(start, end)
box = LineBand(color, viewport) # call LineBand with color as argument
box.setGeometry(rect)
box.show()
self.boxes.append(box)
[...]
class LineBand(QWidget):
def __init__(self, color, *args, **kwargs):
QWidget.__init__(self, *args, **kwargs)
self.color = color
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.setPen(QPen(self.color, 1.8)) # call setPen with color
painter.drawLine(self.rect().topLeft(), self.rect().bottomRight())
Output:
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_())