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

Add delete button for layers #65

Closed
wants to merge 16 commits into from
15 changes: 12 additions & 3 deletions examples/layers.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@
"metadata": {},
"outputs": [],
"source": [
"viewer.layers.swap(3,1)"
"viewer.layers.swap(0,1)"
]
},
{
Expand All @@ -108,7 +108,7 @@
"metadata": {},
"outputs": [],
"source": [
"viewer.layers.reorder([2,1,3,0])"
"viewer.layers.swap(0,3)"
]
},
{
Expand All @@ -124,7 +124,16 @@
"metadata": {},
"outputs": [],
"source": [
"viewer.layers.pop(0)"
"viewer.layers.remove(viewer.layers[0])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"viewer.layers.pop(1)"
]
},
{
Expand Down
21 changes: 16 additions & 5 deletions gui/elements/_layer_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from vispy.util.event import EmitterGroup, Event
from ..layers._base_layer import Layer

from .qt import QtLayerList
from .qt import QtLayerPanel


class ItemEvent(Event):
Expand Down Expand Up @@ -47,7 +47,7 @@ class LayerList:

def __init__(self, viewer=None):
self._list = []
self._qt = QtLayerList()
self._qt = QtLayerPanel(self)
self._viewer = None
self.total = 0
self.events = EmitterGroup(source=self,
Expand Down Expand Up @@ -297,7 +297,7 @@ def _add(self, event):
"""Callback when an item is added to set its order and viewer.
"""
layer = event.item
self._qt.insert(event.index, len(self), layer)
self._qt.layersList.insert(event.index, len(self), layer)
layer._order = -len(self)
layer.viewer = self.viewer

Expand All @@ -306,7 +306,7 @@ def _remove(self, event):
and reset its order.
"""
layer = event.item
self._qt.remove(layer)
self._qt.layersList.remove(layer)
layer.viewer = None
layer._order = 0

Expand All @@ -316,7 +316,18 @@ def _reorder(self, event):
"""
for i in range(len(self)):
self[i]._order = -i
self._qt.reorder(self)
self._qt.layersList.reorder()
canvas = self.viewer._canvas
canvas._draw_order.clear()
canvas.update()

def remove_selected(self):
"""Removes selected items from list.
"""
to_delete = []
for i in range(len(self)):
if self[i].selected:
to_delete.append(i)
to_delete.reverse()
for i in to_delete:
self.pop(i)
2 changes: 1 addition & 1 deletion gui/elements/qt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from PyQt5.QtWidgets import QApplication as QtApplication

from ._viewer import QtViewer
from ._layerList import QtLayerList
from ._layerPanel import QtLayerPanel
from ._controls import QtControls
from ._imageLayer import QtImageLayer
from ._markersLayer import QtMarkersLayer
Expand Down
38 changes: 33 additions & 5 deletions gui/elements/qt/_layer.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from PyQt5.QtWidgets import QSlider, QLineEdit, QHBoxLayout, QFrame, QVBoxLayout, QCheckBox, QWidget
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPalette
from PyQt5.QtWidgets import QSlider, QLineEdit, QHBoxLayout, QFrame, QVBoxLayout, QCheckBox, QWidget, QApplication
from PyQt5.QtCore import Qt, QMimeData
from PyQt5.QtGui import QPalette, QDrag
from os.path import dirname, join, realpath
import weakref

dir_path = dirname(realpath(__file__))
path_on = join(dir_path,'icons','eye_on.png')
path_off = join(dir_path,'icons','eye_off.png')

class QtLayer(QFrame):
def __init__(self, layer):
Expand All @@ -19,7 +18,6 @@ def __init__(self, layer):

cb = QCheckBox(self)
cb.setStyleSheet("QCheckBox::indicator {width: 18px; height: 18px;}"
#{}"QCheckBox::indicator:unchecked {image: url(" + path_off + ");}"
"QCheckBox::indicator:checked {image: url(" + path_on + ");}")
cb.setToolTip('Layer visibility')
cb.setChecked(self.layer.visible)
Expand All @@ -45,12 +43,14 @@ def __init__(self, layer):
textbox.setText(layer.name)
textbox.setToolTip('Layer name')
textbox.setFixedWidth(80)
textbox.setAcceptDrops(False)
textbox.editingFinished.connect(lambda text=textbox: self.changeText(text))
layout.addWidget(textbox)

self.setLayout(layout)
self.setFixedHeight(55)
self.setSelected(True)
self.setToolTip('Click to select\nDrag to rearrange')

def setSelected(self, state):
if state:
Expand Down Expand Up @@ -90,6 +90,34 @@ def mouseReleaseEvent(self, event):
self.unselectAll()
self.setSelected(True)

def mousePressEvent(self, event):
self.dragStartPosition = event.pos()

def mouseMoveEvent(self, event):
if (event.pos()- self.dragStartPosition).manhattanLength() < QApplication.startDragDistance():
return
mimeData = QMimeData()
if not self.layer.selected:
name = self.layer.name
else:
name = ''
for layer in self.layer.viewer.layers:
if layer.selected:
name = layer.name + '; ' + name
name = name[:-2]
sofroniewn marked this conversation as resolved.
Show resolved Hide resolved
mimeData.setText(name)
drag = QDrag(self)
drag.setMimeData(mimeData)
drag.setHotSpot(event.pos() - self.rect().topLeft())
dropAction = drag.exec_(Qt.MoveAction | Qt.CopyAction)

if dropAction == Qt.CopyAction:
if not self.layer.selected:
index = self.layer.viewer.layers.index(self.layer)
self.layer.viewer.layers.pop(index)
else:
self.layer.viewer.layers.remove_selected()

def unselectAll(self):
if self.layer.viewer is not None:
for layer in self.layer.viewer.layers:
Expand Down
16 changes: 16 additions & 0 deletions gui/elements/qt/_layerDivider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QFrame

class QtDivider(QFrame):
def __init__(self):
super().__init__()
self.unselectedStlyeSheet = "QFrame {border: 3px solid rgb(236,236,236); background-color:rgb(236,236,236); border-radius: 3px;}"
self.selectedStlyeSheet = "QFrame {border: 3px solid rgb(71,143,205); background-color:rgb(71,143,205); border-radius: 3px;}"
sofroniewn marked this conversation as resolved.
Show resolved Hide resolved
self.setSelected(False)
self.setFixedHeight(4)

def setSelected(self, bool):
if bool:
self.setStyleSheet(self.selectedStlyeSheet)
else:
self.setStyleSheet(self.unselectedStlyeSheet)
98 changes: 91 additions & 7 deletions gui/elements/qt/_layerList.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,129 @@
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QScrollArea
from ._layerDivider import QtDivider

import weakref

class QtLayerList(QScrollArea):
def __init__(self):
def __init__(self, layers):
super().__init__()

self.layers = weakref.proxy(layers)
sofroniewn marked this conversation as resolved.
Show resolved Hide resolved
self.setWidgetResizable(True)
#self.setFixedWidth(315)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scrollWidget = QWidget()
self.setWidget(scrollWidget)
self.layersLayout = QVBoxLayout(scrollWidget)
self.layersLayout.addWidget(QtDivider())
self.layersLayout.addStretch(1)
self.setAcceptDrops(True)
self.setToolTip('Layer list')


def insert(self, index, total, layer):
"""Inserts a layer widget at a specific index
"""
if layer._qt is not None:
self.layersLayout.insertWidget(total - index-1, layer._qt)
self.layersLayout.insertWidget(2*(total - index)-1, layer._qt)
self.layersLayout.insertWidget(2*(total - index), QtDivider())

def remove(self, layer):
"""Removes a layer widget
"""
if layer._qt is not None:
index = self.layersLayout.indexOf(layer._qt)
divider = self.layersLayout.itemAt(index+1).widget()
self.layersLayout.removeWidget(layer._qt)
layer._qt.deleteLater()
layer._qt = None
self.layersLayout.removeWidget(divider)
divider.deleteLater()
divider = None

def reorder(self, layerList):
def reorder(self):
"""Reorders list of layer widgets by looping through all
widgets in list sequentially removing them and inserting
them into the correct place in final list.
"""
for i in range(len(layerList)):
layer = layerList[i]
total = len(self.layers)
for i in range(total):
layer = self.layers[i]
if layer._qt is not None:
index = self.layersLayout.indexOf(layer._qt)
divider = self.layersLayout.itemAt(index+1).widget()
self.layersLayout.removeWidget(layer._qt)
self.layersLayout.insertWidget(len(layerList) - i-1,layer._qt)
self.layersLayout.removeWidget(divider)
self.layersLayout.insertWidget(2*(total - i)-1,layer._qt)
self.layersLayout.insertWidget(2*(total - i),divider)

def mouseReleaseEvent(self, event):
"""Unselects all layer widgets
"""
self.layersLayout.itemAt(0).widget().unselectAll()
if self.layersLayout.count() > 1:
self.layersLayout.itemAt(1).widget().unselectAll()

def dragLeaveEvent(self, event):
event.ignore()
for i in range(0, self.layersLayout.count(), 2):
self.layersLayout.itemAt(i).widget().setSelected(False)

def dragEnterEvent(self, event):
event.accept()
dividers = []
for i in range(0, self.layersLayout.count(), 2):
widget = self.layersLayout.itemAt(i).widget()
dividers.append(widget.y()+widget.frameGeometry().height()/2)
self.centers = [(dividers[i+1]+dividers[i])/2 for i in range(len(dividers)-1)]

def dragMoveEvent(self, event):
cord = event.pos().y()
divider_index = next((i for i, x in enumerate(self.centers) if x > cord), len(self.centers))
layerWidget = event.source()
layers = layerWidget.layer.viewer.layers
index = layers.index(layerWidget.layer)
total = len(layers)
insert_index = total - divider_index
if not (insert_index == index) and not (insert_index-1 == index):
state = True
else:
state = False
for i in range(0, self.layersLayout.count(), 2):
if i == 2*divider_index:
self.layersLayout.itemAt(i).widget().setSelected(state)
else:
self.layersLayout.itemAt(i).widget().setSelected(False)

def dropEvent(self, event):
for i in range(0, self.layersLayout.count(), 2):
self.layersLayout.itemAt(i).widget().setSelected(False)
cord = event.pos().y()
divider_index = next((i for i, x in enumerate(self.centers) if x > cord), len(self.centers))
layerWidget = event.source()
layers = layerWidget.layer.viewer.layers
index = layers.index(layerWidget.layer)
total = len(layers)
insert_index = total - divider_index
indices = [i for i in range(total)]
if layerWidget.layer.selected:
selected = []
for i in range(total):
if layers[i].selected:
selected.append(i)
else:
selected = [index]
for i in selected:
indices.remove(i)
offset = sum([i<insert_index for i in selected])
j = insert_index - offset
for i in selected:
indices.insert(j,i)
j = j+1
if not indices == [i for i in range(total)]:
layers.reorder(indices)
event.accept()
else:
event.ignore()
if not layerWidget.layer.selected:
layerWidget.unselectAll()
layerWidget.setSelected(True)
65 changes: 65 additions & 0 deletions gui/elements/qt/_layerPanel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QFrame
from ._layerList import QtLayerList
from os.path import dirname, join, realpath
import weakref

dir_path = dirname(realpath(__file__))
path_delete = join(dir_path,'icons','delete.png')
AhmetCanSolak marked this conversation as resolved.
Show resolved Hide resolved

class QtLayerPanel(QWidget):
def __init__(self, layers):
super().__init__()

layout = QVBoxLayout()
self.layersList = QtLayerList(layers)
self.layersControls = QtLayerControls(layers)
layout.addWidget(self.layersControls)
layout.addWidget(self.layersList)
self.setLayout(layout)

class QDeleteButton(QPushButton):
def __init__(self, layers):
super().__init__()

self.layers = weakref.proxy(layers)

self.setIcon(QIcon(path_delete))
self.setFixedWidth(28)
self.setFixedHeight(28)
self.setToolTip('Delete layers')
self.clicked.connect(self.on_click)
self.setAcceptDrops(True)
styleSheet = """QPushButton {background-color:lightGray; border-radius: 3px;}
QPushButton:pressed {background-color:rgb(71,143,205); border-radius: 3px;}
QPushButton:hover {background-color:rgb(71,143,205); border-radius: 3px;}"""
self.setStyleSheet(styleSheet)

def on_click(self):
self.layers.remove_selected()

def dragEnterEvent(self, event):
event.accept()
self.hover = True
self.update()

def dragLeaveEvent(self, event):
event.ignore()
self.hover = False
self.update()

def dropEvent(self, event):
event.setDropAction(Qt.CopyAction)
event.accept()

class QtLayerControls(QFrame):
def __init__(self, layers):
super().__init__()

layout = QHBoxLayout()
pb = QDeleteButton(layers)
sofroniewn marked this conversation as resolved.
Show resolved Hide resolved
layout.addWidget(pb)
layout.addStretch(0)

self.setLayout(layout)
Binary file added gui/elements/qt/icons/delete.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.