2024-07-18 14:40:56 +08:00
|
|
|
from PyQt6 import QtCore, QtWidgets, QtGui
|
2024-05-02 15:58:54 +08:00
|
|
|
|
|
|
|
from artiq.gui.flowlayout import FlowLayout
|
2023-11-22 17:31:30 +08:00
|
|
|
|
|
|
|
|
|
|
|
class VDragDropSplitter(QtWidgets.QSplitter):
|
2024-01-24 10:37:54 +08:00
|
|
|
dropped = QtCore.pyqtSignal(int, int)
|
|
|
|
|
2023-11-22 17:31:30 +08:00
|
|
|
def __init__(self, parent):
|
|
|
|
QtWidgets.QSplitter.__init__(self, parent=parent)
|
|
|
|
self.setAcceptDrops(True)
|
|
|
|
self.setContentsMargins(0, 0, 0, 0)
|
2024-07-18 14:40:56 +08:00
|
|
|
self.setOrientation(QtCore.Qt.Orientation.Vertical)
|
2023-11-22 17:31:30 +08:00
|
|
|
self.setChildrenCollapsible(False)
|
|
|
|
|
|
|
|
def resetSizes(self):
|
|
|
|
self.setSizes(self.count() * [1])
|
|
|
|
|
|
|
|
def dragEnterEvent(self, e):
|
|
|
|
e.accept()
|
|
|
|
|
|
|
|
def dragLeaveEvent(self, e):
|
|
|
|
self.setRubberBand(-1)
|
|
|
|
e.accept()
|
|
|
|
|
|
|
|
def dragMoveEvent(self, e):
|
2024-07-29 10:29:43 +08:00
|
|
|
pos = e.position()
|
2023-11-22 17:31:30 +08:00
|
|
|
src = e.source()
|
|
|
|
src_i = self.indexOf(src)
|
|
|
|
self.setRubberBand(self.height())
|
|
|
|
# case 0: smaller than source widget
|
|
|
|
if pos.y() < src.y():
|
|
|
|
for n in range(src_i):
|
|
|
|
w = self.widget(n)
|
|
|
|
if pos.y() < w.y() + w.size().height():
|
|
|
|
self.setRubberBand(w.y())
|
|
|
|
break
|
|
|
|
# case 2: greater than source widget
|
|
|
|
elif pos.y() > src.y() + src.size().height():
|
|
|
|
for n in range(src_i + 1, self.count()):
|
|
|
|
w = self.widget(n)
|
|
|
|
if pos.y() < w.y():
|
|
|
|
self.setRubberBand(w.y())
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
self.setRubberBand(-1)
|
|
|
|
e.accept()
|
|
|
|
|
|
|
|
def dropEvent(self, e):
|
|
|
|
self.setRubberBand(-1)
|
2024-07-29 10:29:43 +08:00
|
|
|
pos = e.position()
|
2023-11-22 17:31:30 +08:00
|
|
|
src = e.source()
|
|
|
|
src_i = self.indexOf(src)
|
|
|
|
for n in range(self.count()):
|
|
|
|
w = self.widget(n)
|
|
|
|
if pos.y() < w.y() + w.size().height():
|
2024-01-24 10:37:54 +08:00
|
|
|
self.dropped.emit(src_i, n)
|
2023-11-22 17:31:30 +08:00
|
|
|
break
|
|
|
|
e.accept()
|
|
|
|
|
|
|
|
|
|
|
|
# Scroll area with auto-scroll on vertical drag
|
|
|
|
class VDragScrollArea(QtWidgets.QScrollArea):
|
|
|
|
def __init__(self, parent):
|
|
|
|
QtWidgets.QScrollArea.__init__(self, parent)
|
|
|
|
self.installEventFilter(self)
|
|
|
|
self._margin = 40
|
|
|
|
self._timer = QtCore.QTimer(self)
|
|
|
|
self._timer.setInterval(20)
|
|
|
|
self._timer.timeout.connect(self._on_auto_scroll)
|
|
|
|
self._direction = 0
|
|
|
|
self._speed = 10
|
|
|
|
|
|
|
|
def setAutoScrollMargin(self, margin):
|
|
|
|
self._margin = margin
|
|
|
|
|
|
|
|
def setAutoScrollSpeed(self, speed):
|
|
|
|
self._speed = speed
|
|
|
|
|
|
|
|
def eventFilter(self, obj, e):
|
2024-07-18 14:40:56 +08:00
|
|
|
if e.type() == QtCore.QEvent.Type.DragMove:
|
2023-11-22 17:31:30 +08:00
|
|
|
val = self.verticalScrollBar().value()
|
|
|
|
height = self.viewport().height()
|
2024-07-29 10:29:43 +08:00
|
|
|
y = e.position().y()
|
2023-11-22 17:31:30 +08:00
|
|
|
self._direction = 0
|
|
|
|
if y < val + self._margin:
|
|
|
|
self._direction = -1
|
|
|
|
elif y > height + val - self._margin:
|
|
|
|
self._direction = 1
|
|
|
|
if not self._timer.isActive():
|
|
|
|
self._timer.start()
|
2024-07-18 14:40:56 +08:00
|
|
|
elif e.type() in (QtCore.QEvent.Type.Drop, QtCore.QEvent.Type.DragLeave):
|
2023-11-22 17:31:30 +08:00
|
|
|
self._timer.stop()
|
|
|
|
return False
|
|
|
|
|
|
|
|
def _on_auto_scroll(self):
|
|
|
|
val = self.verticalScrollBar().value()
|
|
|
|
min_ = self.verticalScrollBar().minimum()
|
|
|
|
max_ = self.verticalScrollBar().maximum()
|
|
|
|
dy = self._direction * self._speed
|
|
|
|
new_val = min(max_, max(min_, val + dy))
|
|
|
|
self.verticalScrollBar().setValue(new_val)
|
2024-05-02 15:58:54 +08:00
|
|
|
|
|
|
|
|
|
|
|
# Widget with FlowLayout and drag and drop support between widgets
|
|
|
|
class DragDropFlowLayoutWidget(QtWidgets.QWidget):
|
|
|
|
def __init__(self):
|
|
|
|
QtWidgets.QWidget.__init__(self)
|
|
|
|
self.layout = FlowLayout()
|
|
|
|
self.setLayout(self.layout)
|
|
|
|
self.setAcceptDrops(True)
|
|
|
|
|
|
|
|
def _get_index(self, pos):
|
|
|
|
for i in range(self.layout.count()):
|
|
|
|
if self.itemAt(i).geometry().contains(pos):
|
|
|
|
return i
|
|
|
|
return -1
|
|
|
|
|
|
|
|
def mousePressEvent(self, event):
|
2024-07-18 14:40:56 +08:00
|
|
|
if event.buttons() == QtCore.Qt.MouseButton.LeftButton \
|
|
|
|
and event.modifiers() == QtCore.Qt.KeyboardModifier.ShiftModifier:
|
2024-07-29 10:29:43 +08:00
|
|
|
index = self._get_index(event.position())
|
2024-05-02 15:58:54 +08:00
|
|
|
if index == -1:
|
|
|
|
return
|
|
|
|
drag = QtGui.QDrag(self)
|
|
|
|
mime = QtCore.QMimeData()
|
|
|
|
mime.setData("index", str(index).encode())
|
|
|
|
drag.setMimeData(mime)
|
|
|
|
pixmapi = QtWidgets.QApplication.style().standardIcon(
|
2024-07-18 14:40:56 +08:00
|
|
|
QtWidgets.QStyle.StandardPixmap.SP_FileIcon)
|
2024-05-02 15:58:54 +08:00
|
|
|
drag.setPixmap(pixmapi.pixmap(32))
|
|
|
|
drag.exec_(QtCore.Qt.MoveAction)
|
|
|
|
event.accept()
|
|
|
|
|
|
|
|
def dragEnterEvent(self, event):
|
|
|
|
event.accept()
|
|
|
|
|
|
|
|
def dropEvent(self, event):
|
2024-07-29 10:29:43 +08:00
|
|
|
index = self._get_index(event.position())
|
2024-05-02 15:58:54 +08:00
|
|
|
source_layout = event.source()
|
|
|
|
source_index = int(bytes(event.mimeData().data("index")).decode())
|
|
|
|
if source_layout == self:
|
|
|
|
if index == source_index:
|
|
|
|
return
|
|
|
|
widget = self.layout.itemAt(source_index).widget()
|
|
|
|
self.layout.removeWidget(widget)
|
|
|
|
self.layout.addWidget(widget)
|
|
|
|
self.layout.itemList.insert(index, self.layout.itemList.pop())
|
|
|
|
else:
|
|
|
|
widget = source_layout.layout.itemAt(source_index).widget()
|
|
|
|
source_layout.layout.removeWidget(widget)
|
|
|
|
self.layout.addWidget(widget)
|
|
|
|
if index != -1:
|
|
|
|
self.layout.itemList.insert(index, self.layout.itemList.pop())
|
|
|
|
event.accept()
|
|
|
|
|
|
|
|
def addWidget(self, widget):
|
|
|
|
self.layout.addWidget(widget)
|
|
|
|
|
|
|
|
def count(self):
|
|
|
|
return self.layout.count()
|
|
|
|
|
|
|
|
def itemAt(self, i):
|
|
|
|
return self.layout.itemAt(i)
|