Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

silx.app.view.CustomPlotSelectionWindow : Add dataset directly from silx + drop overlay on plot #4141

Merged
merged 9 commits into from
Jun 27, 2024
84 changes: 73 additions & 11 deletions src/silx/app/view/CustomPlotSelectionWindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def paint(self, painter, option, index):
painter.drawText(
option.rect.adjusted(3, 3, -3, -3),
qt.Qt.AlignLeft | qt.Qt.AlignVCenter,
"Drop a 1D dataset",
" Drop a 1D dataset",
)
painter.restore()
else:
Expand Down Expand Up @@ -359,24 +359,28 @@ def _createRemoveButton(self, row: int, parentItem: qt.QStandardItem | None):
if parentItem is None:
parentItem = self.model().getXParent()
index = self.model().index(0, 2)
button = qt.QToolButton(self)
button.setIcon(icons.getQIcon("remove"))
button.clicked.connect(
functools.partial(self._removeFile, None, parentItem)
)
button = self._getRemoveButton(None, parentItem)
self.setIndexWidget(index, button)
return

# If the parentItem is not None, the remove button is for a Y dataset
removeItem = parentItem.child(row, 2)
if removeItem:
button = qt.QToolButton(self)
button.setIcon(icons.getQIcon("remove"))
button.clicked.connect(
functools.partial(self._removeFile, removeItem, parentItem)
)
button = self._getRemoveButton(removeItem, parentItem)
self.setIndexWidget(removeItem.index(), button)

def _getRemoveButton(
self, removeItem: qt.QStandardItem | None, parentItem: qt.QStandardItem
) -> qt.QToolButton:
"""Return a remove button widget."""
button = qt.QToolButton(self)
button.setIcon(icons.getQIcon("remove"))
button.setStyleSheet("QToolButton { border-radius: 0px; }")
button.clicked.connect(
functools.partial(self._removeFile, removeItem, parentItem)
)
return button

def _removeFile(
self, removeItem: qt.QStandardItem | None, parentItem: qt.QStandardItem
):
Expand Down Expand Up @@ -501,6 +505,7 @@ def __init__(self, parent=None):
super().__init__(parent)
self.setAcceptDrops(True)
self._treeView = None
self.dropOverlay = DropOverlay(self)

def setTreeView(self, treeView: qt.QTreeView):
"""Set the TreeView widget for the plot."""
Expand All @@ -509,9 +514,20 @@ def setTreeView(self, treeView: qt.QTreeView):
def dragEnterEvent(self, event):
super().dragEnterEvent(event)
self._treeView.acceptDragEvent(event)
if event.isAccepted():
self._showDropOverlay(event)

def dragMoveEvent(self, event):
super().dragMoveEvent(event)
self._showDropOverlay(event)

def dragLeaveEvent(self, event):
super().dragLeaveEvent(event)
self.dropOverlay.hideOverlay()

def dropEvent(self, event):
super().dropEvent(event)
self.dropOverlay.hideOverlay()
byteString = event.mimeData().data("application/x-silx-uri")
url = silx.io.url.DataUrl(byteString.data().decode("utf-8"))

Expand All @@ -535,8 +551,27 @@ def setXAxisLabel(self, label: str):
xAxis = self.getXAxis()
xAxis.setLabel(label)

def _showDropOverlay(self, event):
"""Show the drop overlay at the drop position."""
plotArea = self.getWidgetHandle()
dropPosition = plotArea.mapFrom(self, event.pos())
offset = plotArea.mapTo(self, qt.QPoint(0, 0))

plotBounds = self.getPlotBoundsInPixels()
left, top, width, height = plotBounds
yAreaTop = top + height

if dropPosition.y() > yAreaTop:
rect = qt.QRect(left + offset.x(), yAreaTop + offset.y(), width, 50)
else:
rect = qt.QRect(left + offset.x(), top + offset.y(), width, height)

self.dropOverlay.showOverlay(rect)


class _PlotToolBar(qt.QToolBar):
"""Toolbar widget for the plot."""

def __init__(self, parent=None):
super().__init__(parent)

Expand All @@ -548,6 +583,33 @@ def addClearAction(self, treeView: qt.QTreeView):
self.addAction(clearAction)


class DropOverlay(qt.QWidget):
"""Overlay widget for displaying drop zones on the plot."""

def __init__(self, parent=None):
super().__init__(parent)
self.setAttribute(qt.Qt.WA_TransparentForMouseEvents)
self.setAttribute(qt.Qt.WA_NoSystemBackground)
self.hide()

def showOverlay(self, rect):
"""Show the overlay at the given rectangle."""
self.setGeometry(rect)
self.show()

def hideOverlay(self):
"""Hide the overlay."""
self.hide()

def paintEvent(self, event):
"""Paint the overlay."""
painter = qt.QPainter(self)
painter.setRenderHint(qt.QPainter.Antialiasing)
brush_color = qt.QColor(0, 0, 0, 50)
painter.setBrush(qt.QBrush(brush_color))
painter.drawRect(self.rect())


class CustomPlotSelectionWindow(qt.QMainWindow):
sigVisibilityChanged = qt.Signal(bool)

Expand Down
24 changes: 23 additions & 1 deletion src/silx/app/view/Viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -962,11 +962,24 @@ def __makeSureCustomNxDataWindowIsVisible(self):
self.__customNxdataWindow.setVisible(True)
self._displayCustomNxdataWindow.setChecked(True)

def __makeSureCustomPlotSelectionWindowIsVisible(self):
if not self._customPlotSelectionWindow.isVisible():
self._customPlotSelectionWindow.setVisible(True)
self._displayCustomPlotSelectionWindow.setChecked(True)

def useAsNewCustomSignal(self, h5dataset):
self.__makeSureCustomNxDataWindowIsVisible()
model = self.__customNxdata.model()
model.createFromSignal(h5dataset)

def setToPlotSelection(self, h5dataset):
self.__makeSureCustomPlotSelectionWindowIsVisible()
self._customPlotSelectionWindow.treeView.setX(h5dataset)

def addToPlotSelection(self, h5dataset):
self.__makeSureCustomPlotSelectionWindowIsVisible()
self._customPlotSelectionWindow.treeView.addY(h5dataset)

def useAsNewCustomNxdata(self, h5nxdata):
self.__makeSureCustomNxDataWindowIsVisible()
model = self.__customNxdata.model()
Expand All @@ -986,7 +999,7 @@ def customContextMenu(self, event):

for obj in selectedObjects:
h5 = obj.h5py_object

name = obj.name
if name.startswith("/"):
name = name[1:]
Expand All @@ -1002,6 +1015,15 @@ def customContextMenu(self, event):
action.triggered.connect(lambda: self.useAsNewCustomSignal(h5))
menu.addAction(action)

if h5.ndim == 1:
action = qt.QAction("Set abscissa to plot selection", event.source())
action.triggered.connect(lambda: self.setToPlotSelection(obj.data_url))
menu.addAction(action)

action = qt.QAction("Add ordinate to plot selection", event.source())
action.triggered.connect(lambda: self.addToPlotSelection(obj.data_url))
menu.addAction(action)

if silx.io.is_group(h5) and silx.io.nxdata.is_valid_nxdata(h5):
action = qt.QAction("Use as a new custom NXdata", event.source())
action.triggered.connect(lambda: self.useAsNewCustomNxdata(h5))
Expand Down