This picture is a pretty good representation of what I'm trying to emulate.
The goal is to create items or widgets, looking like the example above, that a user could create on a QSlider by a MouseDoubleClicked event, and which would remain at the Tick position it was originally created (it would remain immobile). I've already made a few attempts using either QLabels with Pixmaps or a combination of QGraphicsItems and QGraphicsView, in vain.
Still, I have the feeling that I'm most likely over complicating things, and that there might be a simpler way to achieve that.
What would be your approach to make those "markers"?
EDIT: I've tried my best to edit one of my previous attempts, in order to make it a Minimal Reproducible Example. Might still be too long though, but here it goes.
import random
from PySide2 import QtCore, QtGui, QtWidgets
class Marker(QtWidgets.QLabel):
def __init__(self, parent=None):
super(Marker, self).__init__(parent)
self._slider = None
self.setAcceptDrops(True)
pix = QtGui.QPixmap(30, 30)
pix.fill(QtGui.QColor("transparent"))
paint = QtGui.QPainter(pix)
slider_color = QtGui.QColor(random.randint(130, 180), random.randint(130, 180), random.randint(130, 180))
handle_pen = QtGui.QPen(QtGui.QColor(slider_color.darker(200)))
handle_pen.setWidth(3)
paint.setPen(handle_pen)
paint.setBrush(QtGui.QBrush(slider_color, QtCore.Qt.SolidPattern))
points = QtGui.QPolygon([
QtCore.QPoint(5, 5),
QtCore.QPoint(5, 19),
QtCore.QPoint(13, 27),
QtCore.QPoint(21, 19),
QtCore.QPoint(21, 5),
])
paint.drawPolygon(points)
del paint
self.setPixmap(pix)
class myTimeline(QtWidgets.QWidget):
def __init__(self, parent=None):
super(myTimeline, self).__init__(parent)
layout = QtWidgets.QGridLayout(self)
self.slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
self.slider.setMinimum(0)
self.slider.setMaximum(50)
self.slider.setTickPosition(QtWidgets.QSlider.TicksAbove)
self.slider.setTickInterval(1)
self.slider.setSingleStep(1)
self.slider.setAcceptDrops(True)
self.resize(self.width(), 50)
layout.addWidget(self.slider)
def create_marker(self):
bookmark = Marker(self)
opt = QtWidgets.QStyleOptionSlider()
self.slider.initStyleOption(opt)
rect = self.slider.style().subControlRect(
QtWidgets.QStyle.CC_Slider,
opt,
QtWidgets.QStyle.SC_SliderHandle,
self.slider
)
bookmark.move(rect.center().x(), 0)
bookmark.show()
def mouseDoubleClickEvent(self, event):
self.create_marker()
Your approach is indeed correct, it only has a few issues:
The geometry of the marker should be updated to reflect its contents. This is required as QLabel is a very special type of widget, and usually adapts its size only when added to a layout or is a top level window.
The marker pixmap is not correctly aligned (it's slightly on the left of its center).
The marker positioning should not only use the rect
center, but also the marker width and the slider position (since you are adding the slider to the parent and there's a layout, the slider is actually off by the size of the layout's contents margins).
The markers should be repositioned everytime the widget is resized, so they should keep a reference to their value.
Markers should be transparent for mouse events, otherwise they would block mouse control on the slider.
Given the above, I suggest you the following:
class Marker(QtWidgets.QLabel):
def __init__(self, value, parent=None):
super(Marker, self).__init__(parent)
self.value = value
# ...
# correctly centered polygon
points = QtGui.QPolygon([
QtCore.QPoint(7, 5),
QtCore.QPoint(7, 19),
QtCore.QPoint(15, 27),
QtCore.QPoint(22, 19),
QtCore.QPoint(22, 5),
])
paint.drawPolygon(points)
del paint
self.setPixmap(pix)
# this is important!
self.adjustSize()
class myTimeline(QtWidgets.QWidget):
def __init__(self, parent=None):
# ...
self.markers = []
def mouseDoubleClickEvent(self, event):
self.create_marker()
def create_marker(self):
bookmark = Marker(self.slider.value(), self)
bookmark.show()
bookmark.installEventFilter(self)
self.markers.append(bookmark)
self.updateMarkers()
def updateMarkers(self):
opt = QtWidgets.QStyleOptionSlider()
self.slider.initStyleOption(opt)
for marker in self.markers:
opt.sliderValue = opt.sliderPosition = marker.value
rect = self.slider.style().subControlRect(
QtWidgets.QStyle.CC_Slider,
opt,
QtWidgets.QStyle.SC_SliderHandle,
)
marker.move(rect.center().x() - marker.width() / 2 + self.slider.x(), 0)
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.MouseButtonPress:
return True
return super().eventFilter(source, event)
def resizeEvent(self, event):
super().resizeEvent(event)
self.updateMarkers()