Commit efeb31ee authored by Damien Naudet's avatar Damien Naudet

WIP

parent ba364dd9
This diff is collapsed.
......@@ -27,53 +27,142 @@ from __future__ import absolute_import
__authors__ = ["D. Naudet"]
__license__ = "MIT"
__date__ = "15/09/2016"
__date__ = "01/11/2016"
from silx.gui import qt as Qt
from .ModelDef import nodeFactory, ModelColumns
from .Node import Node
from .ModelDef import ModelColumns, ModelRoles
def nodeEditorFromIndex(index):
xsocsClass = index.internalPointer().__class__
if xsocsClass:
return xsocsClass.editor
return None
class RootNode(Node):
def __init__(self, *args, **kwargs):
super(RootNode, self).__init__(*args, **kwargs)
for colIdx in range(ModelColumns.ColumnMax):
self.setData(colIdx,
ModelColumns.ColumnNames[colIdx],
Qt.Qt.DisplayRole)
class ProjectModel(Qt.QAbstractItemModel):
class Model(Qt.QAbstractItemModel):
nameColumn, valueColumn = range(2)
def __init__(self, projectFile, parent=None):
super(ProjectModel, self).__init__(parent)
self.__root = nodeFactory(projectFile, '/', nodeType='RootNode')
sigEndRemove = Qt.Signal(object)
sigRowsRemoved = Qt.Signal(object, int, int)
def __init__(self, parent=None):
super(Model, self).__init__(parent)
self.__root = RootNode(nodeName='__root__')
self.__root.sigInternalDataChanged.connect(self.__internalDataChanged)
self.__root.sigChildAdded.connect(self.__nodeAdded)
self.__root.sigChildRemoved.connect(self.__nodeRemoved)
def startModel(self):
self.__root.start()
def stopModel(self):
self.__root.stop()
def appendGroup(self, group, groupName=None):
if groupName is not None:
group.nodeName = groupName
self.beginInsertRows(Qt.QModelIndex(),
self.rowCount(),
self.rowCount())
self.__root.appendChild(group)
self.endInsertRows()
def __nodeAdded(self, indices, child):
# TODO : refactor this piece of code,
# also used in __internalDataChanged
modelIndex = Qt.QModelIndex()
if len(indices) == 1:
# added to root node
parentNode = self.__root
else:
for index in reversed(indices[1:]):
modelIndex = self.index(index,
0,
modelIndex)
parentNode = modelIndex.data(ModelRoles.InternalDataRole)
self.beginInsertRows(modelIndex,
indices[0],
indices[0])
parentNode._appendChild(child)
self.endInsertRows()
def __nodeRemoved(self, indices):
# TODO : refactor this piece of code,
# also used in __internalDataChanged
modelIndex = Qt.QModelIndex()
for index in reversed(indices[1:]):
modelIndex = self.index(index,
0,
modelIndex)
parent = modelIndex.data(ModelRoles.InternalDataRole)
self.beginRemoveRows(modelIndex,
indices[0],
indices[0])
child = parent.child(indices[0])
parent._removeChild(child)
self.endRemoveRows()
self.sigRowsRemoved.emit(modelIndex, indices[0], indices[0])
def __internalDataChanged(self, indices):
modelIndex = Qt.QModelIndex()
for index in reversed(indices[1:]):
modelIndex = self.index(index,
indices[0],
modelIndex)
self.dataChanged.emit(modelIndex, modelIndex)
# noinspection PyMethodOverriding
def parent(self, index):
if not index.isValid():
return Qt.QModelIndex()
node = index.internalPointer()
node = index.data(ModelRoles.InternalDataRole)
if node is None:
return Qt.QModelIndex()
if node == self.__root:
return Qt.QModelIndex()
parent = node.parent()
# Dirty (?) hack
# Sometimes when removing rows the view gets kinda lost,
# and asks for indices that have been removed.
# closing all editors before removing a row fixed one problem.
# but there is still an issue if the removed row was selected.
# This seems to fix it... maybe
if parent is None:
return Qt.QModelIndex()
if parent == self.__root:
return Qt.QModelIndex()
return self.createIndex(parent.row(), 0, parent)
row = parent.row()
if row < 0:
return Qt.QModelIndex()
return self.createIndex(row, 0, parent)
def flags(self, index):
if not index.isValid():
return Qt.Qt.NoItemFlags
node = index.data(ModelRoles.InternalDataRole)
return node.flags(index.column())
def columnCount(self, parent=Qt.QModelIndex(), **kwargs):
return ModelColumns.ColumnMax
def headerData(self, section, orientation, role=Qt.Qt.DisplayRole):
if role == Qt.Qt.DisplayRole and orientation == Qt.Qt.Horizontal:
if section == 0:
return 'Name'
if section == 1:
return 'Value'
return self.__root.data(section, role=Qt.Qt.DisplayRole)
return None
def data(self, index, role=Qt.Qt.DisplayRole, **kwargs):
......@@ -81,8 +170,9 @@ class ProjectModel(Qt.QAbstractItemModel):
raise ValueError('Invalid index.')
node = index.internalPointer()
if node is not None:
return node.data(index.column(), role)
data = node.data(index.column(), role)
return data
def index(self, row, column, parent=Qt.QModelIndex(), **kwargs):
if not self.hasIndex(row, column, parent):
......@@ -90,8 +180,7 @@ class ProjectModel(Qt.QAbstractItemModel):
if not parent.isValid():
node = self.__root
else:
node = parent.internalPointer()
node = parent.data(ModelRoles.InternalDataRole)
child = node.child(row)
if child is not None:
......@@ -100,14 +189,20 @@ class ProjectModel(Qt.QAbstractItemModel):
def refresh(self):
self.beginResetModel()
self.__root.reload()
self.__root.refresh()
self.endResetModel()
def rowCount(self, parent=Qt.QModelIndex(), **kwargs):
if not parent.isValid():
node = self.__root
else:
node = parent.internalPointer()
node = parent.data(ModelRoles.InternalDataRole)
if node is not None:
return node.childCount()
return 0
def setData(self, index, value, role=Qt.Qt.EditRole):
if not index.isValid():
return False
node = index.data(role=ModelRoles.InternalDataRole)
return node.setData(index.column(), value, role)
......@@ -27,75 +27,18 @@ from __future__ import absolute_import
__authors__ = ["D. Naudet"]
__license__ = "MIT"
__date__ = "15/09/2016"
__date__ = "01/11/2016"
import h5py
from silx.gui import qt as Qt
_registeredNodes = {}
class ModelRoles(object):
(IsXsocsNodeRole, XsocsNodeType,
XsocsProcessId, InternalDataRole, RoleMax) = \
range(Qt.Qt.UserRole, Qt.Qt.UserRole + 5)
from silx.gui import qt as Qt
class ModelColumns(object):
NameColumn, ValueColumn, ColumnMax = range(3)
ColumnNames = ['Name', 'Value']
def getNodeClass(nodeType):
return _registeredNodes.get(nodeType)
def registerNodeClass(klass):
global _registeredNodes
nodeType = klass.nodeType
if nodeType in _registeredNodes:
raise AttributeError('Failed to register node type {0}.'
'Already registered.'
''.format(klass.__name__))
# TODO : some kind of checks on the klass
_registeredNodes[nodeType] = klass
def NodeClassDef(nodeType, icon=None, editor=None):
def inner(cls):
cls.nodeType = nodeType
cls.editor = editor
cls.icon = icon
registerNodeClass(cls)
return cls
return inner
def nodeFactory(projectFile, path, parent=None, nodeType=None):
klass = None
if nodeType is not None:
klass = getNodeClass(nodeType)
if klass is None:
raise ValueError('Unknown class type {0}.'.format(nodeType))
else:
with h5py.File(projectFile) as h5f:
item = h5f[path]
xsocsType = item.attrs.get('XsocsType')
itemClass = h5f.get(path, getclass=True)
itemLink = h5f.get(path, getclass=True, getlink=True)
del item
if xsocsType is not None:
klass = getNodeClass(xsocsType)
if klass is None:
klass = getNodeClass(itemLink)
if klass is None:
klass = getNodeClass(itemClass)
if klass is None:
klass = getNodeClass('default')
if klass is not None:
return klass(projectFile, path, parent)
else:
raise ValueError('Node creation failed.')
class ModelRoles(object):
(InternalDataRole, EditorClassRole,
PersistentEditorRole, EnabledRole, RoleMax) = \
range(Qt.Qt.UserRole, Qt.Qt.UserRole + 5)
This diff is collapsed.
......@@ -27,64 +27,76 @@ from __future__ import absolute_import
__authors__ = ["D. Naudet"]
__license__ = "MIT"
__date__ = "15/09/2016"
__date__ = "01/11/2016"
from functools import partial
from collections import namedtuple
from silx.gui import qt as Qt, icons
from silx.gui import qt as Qt
from .ModelDef import ModelColumns, ModelRoles
from .Nodes import XsocsNode
from ..project.HybridItem import HybridItem
from .ModelDef import NodeClassDef, ModelRoles
from .ProjectNode import DelegateEvent, NodeDelegate
EditorInfo = namedtuple('EditorInfo', ['klass', 'persistent'])
class HybridItemEvent(DelegateEvent):
HybridEventData = namedtuple('HybridEventData', ['evtType', 'path'])
class EditorMixin(object):
"""
To be used with a Qt.QWidget base.
"""
sigValueChanged = Qt.Signal()
persistent = False
class HybridItemDelegate(NodeDelegate):
node = property(lambda self: self.__node)
column = property(lambda self: self.__column)
def __init__(self, parent, option, index):
super(HybridItemDelegate, self).__init__(parent, option, index)
if not index.isValid():
return
layout = Qt.QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
super(EditorMixin, self).__init__(parent)
self.__node = index.data(ModelRoles.InternalDataRole)
self.__column = index.column()
self.setAutoFillBackground(True)
@classmethod
def paint(cls, painter, option, index):
return False
def valueChanged(self, *args, **kwargs):
self.sigValueChanged.emit()
def sizeHint(self):
return Qt.QSize(0, 0)
# def nodeChanged(self, node):
# self.setEnabled(node.flags(ModelColumns.ValueColumn) &
# Qt.Qt.ItemIsEnabled)
# return self.updateFromNode(node)
#
# def updateFromNode(self, node):
# return False
def setEditorData(self, index):
node = index.data(ModelRoles.InternalDataRole)
item = HybridItem(node.projectFile, node.path)
if item.hasScatter():
icon = icons.getQIcon('item-1dim')
bn = Qt.QToolButton()
bn.setIcon(icon)
bn.clicked.connect(partial(self.__onClicked, eventType='scatter'))
layout.addWidget(bn, Qt.Qt.AlignLeft)
if item.hasImage():
icon = icons.getQIcon('item-2dim')
bn = Qt.QToolButton()
bn.setIcon(icon)
bn.clicked.connect(partial(self.__onClicked, eventType='image'))
layout.addWidget(bn, Qt.Qt.AlignLeft)
layout.addStretch(1)
def __onClicked(self, eventType=None):
persistentIndex = self.index
index = persistentIndex.model().index(persistentIndex.row(),
persistentIndex.column(),
persistentIndex.parent())
node = index.internalPointer()
data = HybridItemEvent.HybridEventData(evtType=eventType,
path=node.path)
event = HybridItemEvent(self.index, data=data)
self.sigEditorEvent.emit(event)
@NodeClassDef(nodeType='HybridItem',
icon='item-ndim',
editor=HybridItemDelegate)
class HybridNode(XsocsNode):
def childCount(self):
return 0
if node and not node.setEditorData(self, index.column()):
value = index.data(Qt.Qt.EditRole)
return self.setValue(value)
return True
def setModelValue(self, value):
return False
def getEditorData(self):
pass
# def widget(self):
# return self.findChild(Qt.QWidget)
# def setWidget(self, widget):
# if not isinstance(widget, Qt.QWidget):
# raise ValueError('Expected a QWidget instance.')
# self.layout().addWidget(widget, 0, 0, Qt.Qt.AlignLeft)
# def setupFromNode(self, node):
# pass
......@@ -27,35 +27,156 @@ from __future__ import absolute_import
__authors__ = ["D. Naudet"]
__license__ = "MIT"
__date__ = "15/09/2016"
__date__ = "01/11/2016"
from silx.gui import qt as Qt
import h5py
from .ModelDef import NodeClassDef, ModelColumns, ModelRoles
from .ProjectNode import ProjectNode
from .Node import Node
from .NodeEditor import EditorMixin
#
# class SliderEditor(NodeEditor):
# persistent = True
#
# def __init__(self, parent, option, index):
# super(SliderEditor, self).__init__(parent, option, index)
# slider = Qt.QSlider()
# slider.setOrientation(Qt.Qt.Horizontal)
# slider.valueChanged.connect(self.valueChanged)
# self.setWidget(slider)
#
# def getEditorData(self):
# return self.widget().value()
#
# def updateFromNode(self, node):
# if isinstance(node, ValMinMaxNode):
# slider = self.widget()
# slider.setMinimum(node.minValue)
# slider.setMaximum(node.maxValue)
# value = node.data(self.column, role=Qt.Qt.EditRole)
# if value is not None:
# slider.setValue(value)
# else:
# # TODO : ERROR
# pass
# return True
# return False
#
#
# class ValMinMaxNode(Node):
# editors = SliderEditor
#
# minValue = 0
# maxValue = 100
@NodeClassDef(nodeType='RootNode')
class RootNode(ProjectNode):
pass
class ProgressBarEditor(EditorMixin):
editable = False
@NodeClassDef(nodeType=h5py.Group, icon='folder', editor=None)
class GroupNode(ProjectNode):
pass
@classmethod
def paint(cls, painter, option, index):
progress = index.data(Qt.Qt.EditRole)
progressBarOption = Qt.QStyleOptionProgressBar()
progressBarOption.rect = option.rect
progressBarOption.minimum = 0
progressBarOption.maximum = 100
progressBarOption.progress = progress
progressBarOption.text = '{0}%'.format(progress)
progressBarOption.textVisible = True
class XsocsNode(ProjectNode):
def __init__(self, *args, **kwargs):
super(XsocsNode, self).__init__(*args, **kwargs)
self.setData(ModelColumns.NameColumn,
data=True,
role=ModelRoles.IsXsocsNodeRole)
with h5py.File(self.projectFile) as h5f:
item = h5f[self.path]
processId = item.attrs.get('XsocsLevel')
del item
for colIdx in range(ModelColumns.ColumnMax):
self.setData(colIdx,
data=processId,
role=ModelRoles.XsocsProcessId)
Qt.QApplication.style().drawControl(Qt.QStyle.CE_ProgressBar,
progressBarOption,
painter)
return True
#
#
# class DoubleEditor(NodeEditor):
# persistent = False
#
# def __init__(self, *args, **kwargs):
# super(DoubleEditor, self).__init__(*args, **kwargs)
# edit = Qt.QLineEdit()
# edit.setValidator(Qt.QDoubleValidator())
# edit.editingFinished.connect(self.valueChanged)
# self.setWidget(edit)
#
# def getEditorData(self):
# return float(self.widget().text())
#
# def updateFromNode(self, node):
# # TODO : try/catch/log
# edit = self.widget()
# edit.setText(str(node.value(self.column)))
# return True
#
#
# class QColorEditor(NodeEditor):
# persistent = True
#
# def __init__(self, *args, **kwargs):
# super(QColorEditor, self).__init__(*args, **kwargs)
# base = Qt.QWidget()
# layout = Qt.QHBoxLayout(base)
# layout.setContentsMargins(0, 0, 0, 0)
# button = Qt.QToolButton()
# icon = Qt.QIcon(Qt.QPixmap(32, 32))
# button.setIcon(icon)
# layout.addWidget(button)
# self.setWidget(base)
# button.clicked.connect(self.__showColorDialog)
# layout.addStretch(1)
#
# self.__color = None
# self.__dialog = None
# self.__previousColor = None
#
# def getEditorData(self):
# return self.__color
#
# def updateFromNode(self, node):
# if isinstance(node, QColorNode):
# qColor = node.data(self.column, Qt.Qt.EditRole)
# if qColor is not None:
# self._setColor(qColor)
# else:
# # TODO : error
# pass
# return True
#
# return False
#
# def _setColor(self, qColor):
# widget = self.widget()
# button = widget.findChild(Qt.QToolButton)
# pixmap = Qt.QPixmap(32, 32)
# pixmap.fill(qColor)
# button.setIcon(Qt.QIcon(pixmap))
# self.__currentColor = qColor
#
# def __showColorDialog(self):
# if self.__dialog is not None:
# self.__dialog.reject()
# return
# self.__dialog = dialog = Qt.QColorDialog()
# dialog.setOption(Qt.QColorDialog.ShowAlphaChannel, True)
# self.__previousColor = self.__currentColor
# dialog.setAttribute(Qt.Qt.WA_DeleteOnClose)
# dialog.currentColorChanged.connect(self.__colorChanged)
# dialog.finished.connect(self.__dialogClosed)
# dialog.show()
#
# def __colorChanged(self, color):
# self.__color = color
# self._setColor(color)
# self.valueChanged(color)
#
# def __dialogClosed(self, result):
# if result == Qt.QDialog.Rejected:
# self.__colorChanged(self.__previousColor)
# self.__dialog = None
# self.__previousColor = None
#
#
# class QColorNode(Node):
# editors = QColorEditor
\ No newline at end of file
# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2015-2016 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ###########################################################################*/
from __future__ import absolute_import
__authors__ = ["D. Naudet"]
__license__ = "MIT"
__date__ = "15/09/2016"
import weakref
import h5py
from silx.gui import icons
from silx.gui import qt as Qt
from .ModelDef import nodeFactory, ModelColumns, ModelRoles
class ProjectNode(object):
nodeType = None
icon = None
editor = None
def __init__(self, projectFile, path, parent=None):
self.__projectFile = projectFile
self.__path = path
self.__parent = parent
self.__children = None
self.__childCount = None
self.__data = [{}, {}]
icon = self.icon
if icon is not None:
if isinstance(icon, str):
self.setData(ModelColumns.NameColumn,
icons.getQIcon(self.icon),
role=Qt.Qt.DecorationRole)
elif isinstance(icon, Qt.QStyle.StandardPixmap):
style = Qt.QApplication.style()
self.setData(ModelColumns.NameColumn,
style.standardIcon(icon),
role=Qt.Qt.DecorationRole)
nodeName = path.rstrip('/').split('/')[-1]
self.setData(ModelColumns.NameColumn,
nodeName,
role=Qt.Qt.DisplayRole)
for colIdx in range(ModelColumns.ColumnMax):
self.setData(colIdx,
data=self.nodeType,
role=ModelRoles.XsocsNodeType)
self.setData(colIdx,
data=weakref.proxy(self),
role=ModelRoles.InternalDataRole)
def reload(self):
self.__children = None
self._children()
def parent(self):
return self.__parent
def data(self, column, role=Qt.Qt.DisplayRole):
if column < 0 or column > len(self.__data):
return None
return self.__data[column].get(role)
def setData(self, column, data, role=Qt.Qt.DisplayRole):
# WARNING, stores a ref to the data!
# TODO : check data type
if column < 0 or column > len(self.__data):
return
self.__data[column][role] = data
def _children(self):
if self.__children is not None:
return self.__children
# TODO : find a better way to do this
# because it wont work for a dataset
self.__children = children = []
with h5py.File(self.__projectFile) as h5f:
# not using value.name in case this item is an external
# link : value.name is relative to the external file's root.
paths = [self.__path + '/' + key
for key in h5f[self.__path].keys()]
newChildren = [nodeFactory(self.__projectFile, path, parent=self)
for path in paths]
children.extend(newChildren)
self.__childCount = len(children)
return children
def row(self):
if self.__parent:
# noinspection PyProtectedMember
return self.__parent._children().index(self)
def childCount(self):
if self.__childCount is None:
with h5py.File(self.__projectFile) as h5f:
self.__childCount = len(h5f[self.path].keys())