Commit 5b4d158c authored by Damien Naudet's avatar Damien Naudet

WIP

parent efeb31ee
......@@ -33,10 +33,12 @@ from .Widgets import (AcqParamsWidget,
# from .Utils import (viewWidgetFromProjectEvent,
# processWidgetFromViewEvent,
# processDoneEvent)
from .view.IntensityView import IntensityView
from .project.XsocsProject import XsocsProject
from .model.TreeView import TreeView
from .model.Model import Model
from .project.XsocsH5Factory import XsocsH5Factory
from .project.IntensityGroup import IntensityGroup, IntensityItem
from .project.XsocsH5Factory import XsocsH5Factory, h5NodeToProjectItem
from .project.Hdf5Nodes import setH5NodeFactory, H5File
......@@ -44,6 +46,15 @@ _COMPANY_NAME = 'ESRF'
_APP_NAME = 'XSOCS'
class ProjectTree(TreeView):
sigDelegateEvent = Qt.Signal(object, object)
def delegateEvent(self, column, node, *args, **kwargs):
# TODO : proper event
event = (args and args[0]) or None
self.sigDelegateEvent.emit(node, event)
class XsocsGui(Qt.QMainWindow):
__firstInitSig = Qt.Signal()
......@@ -54,6 +65,9 @@ class XsocsGui(Qt.QMainWindow):
setH5NodeFactory(XsocsH5Factory)
self.__intensityView = None
self.__qspaceView = None
mdiArea = Qt.QMdiArea()
self.setCentralWidget(mdiArea)
self.__createViews()
......@@ -68,12 +82,35 @@ class XsocsGui(Qt.QMainWindow):
self.__readSettings()
def __createViews(self):
tree = TreeView()
tree = ProjectTree()
tree.setShowUniqueGroup(False)
tree.sigDelegateEvent.connect(self.__viewEvent)
model = Model(parent=tree)
model.startModel()
tree.setModel(model)
self.setCentralWidget(tree)
def __viewEvent(self, node, event):
projectItem = h5NodeToProjectItem(node)
if projectItem is None:
raise ValueError('Unknwown event for node {0} : {1}.'
''.format(node, event))
if isinstance(projectItem, (IntensityGroup, IntensityItem))\
and event['event'] == 'scatter':
self.__showIntensity(projectItem)
else:
ValueError('Unknwown event for item {0} : {1}.'
''.format(projectItem, event))
def __showIntensity(self, item):
# intensity, positions = item.getScatterData()
view = self.__intensityView
if not view:
self.__intensityView = view = IntensityView(item, self)
# view.setPlotData(positions.pos_0, positions.pos_1, intensity)
view.show()
def showEvent(self, event):
super(XsocsGui, self).showEvent(event)
if not self.__widget_setup:
......@@ -117,8 +154,6 @@ class XsocsGui(Qt.QMainWindow):
def __setupProject(self, projectFile=None):
mode = 'r+'
project = XsocsProject(projectFile, mode=mode)
# self.__sessionDock.widget().setXsocsProject(wkSpace)
# self.__dataDock.widget().setXsocsProject(wkSpace)
rootNode = H5File(h5File=projectFile)
self.centralWidget().model().appendGroup(rootNode)
self.__project = project
......@@ -129,29 +164,6 @@ class XsocsGui(Qt.QMainWindow):
style = Qt.QApplication.style()
self.__actions = actions = {}
# # open an existing session
# icon = style.standardIcon(Qt.QStyle.SP_DialogOpenButton)
# openAct = Qt.QAction(icon, '&Open', self)
# openAct.setShortcuts(Qt.QKeySequence.Open)
# openAct.setStatusTip('Open session')
# openAct.triggered.connect(self.__loadProject)
# actions['open'] = openAct
#
# # new session
# icon = style.standardIcon(Qt.QStyle.SP_FileDialogNewFolder)
# newAct = Qt.QAction(icon, '&Load', self)
# newAct.setStatusTip('Load data')
# newAct.setShortcuts(Qt.QKeySequence.New)
# newAct.triggered.connect(self.__loadData)
# actions['new'] = newAct
#
# # import data from spec
# icon = style.standardIcon(Qt.QStyle.SP_DialogOkButton)
# importAct = Qt.QAction(icon, '&Import', self)
# importAct.setStatusTip('Import SPEC data')
# importAct.triggered.connect(self.__importData)
# actions['import'] = importAct
# exit the application
icon = style.standardIcon(Qt.QStyle.SP_DialogCancelButton)
quitAct = Qt.QAction(icon, 'E&xit', self)
......@@ -165,14 +177,6 @@ class XsocsGui(Qt.QMainWindow):
aboutAct.setStatusTip('Show the application\'s About box')
actions['about'] = aboutAct
# # toggle session dockwidget visibility
# sessionAct = self.__sessionDock.toggleViewAction()
# actions['sessionDock'] = sessionAct
#
# # toggle data dockwidget visibility
# dataAct = self.__dataDock.toggleViewAction()
# actions['dataDock'] = dataAct
def __createMenus(self):
self.__menus = menus = {}
......@@ -180,15 +184,9 @@ class XsocsGui(Qt.QMainWindow):
menuBar = self.menuBar()
fileMenu = menuBar.addMenu('&File')
# fileMenu.addAction(actions['open'])
# fileMenu.addAction(actions['new'])
fileMenu.addSeparator()
fileMenu.addAction(actions['quit'])
# viewMenu = menuBar.addMenu('&View')
# viewMenu.addAction(actions['sessionDock'])
# viewMenu.addAction(actions['dataDock'])
menuBar.addSeparator()
helpMenu = menuBar.addMenu('&Help')
......
......@@ -30,11 +30,14 @@ __license__ = "MIT"
__date__ = "01/11/2016"
import weakref
from collections import namedtuple
from silx.gui import qt as Qt
from .ModelDef import ModelColumns, ModelRoles
from .ModelDef import ModelRoles
from silx.utils.weakref import WeakMethod
EditorInfo = namedtuple('EditorInfo', ['klass', 'persistent'])
......@@ -44,7 +47,7 @@ class EditorMixin(object):
"""
To be used with a Qt.QWidget base.
"""
sigValueChanged = Qt.Signal()
# sigValueChanged = Qt.Signal()
persistent = False
node = property(lambda self: self.__node)
......@@ -56,31 +59,42 @@ class EditorMixin(object):
self.__node = index.data(ModelRoles.InternalDataRole)
self.__column = index.column()
self.setAutoFillBackground(True)
self.__modelCb = None
self.__viewCb = None
@classmethod
def paint(cls, painter, option, index):
return False
def valueChanged(self, *args, **kwargs):
self.sigValueChanged.emit()
def notifyModel(self, *args, **kwargs):
if not self.__modelCb:
return
modelCb = self.__modelCb()
if modelCb:
modelCb(self, *args, **kwargs)
def notifyView(self, *args, **kwargs):
if not self.__viewCb:
return
viewCb = self.__viewCb()
if viewCb:
viewCb(self, *args, **kwargs)
def setViewCallback(self, callback):
self.__viewCb = WeakMethod(callback)
def setModelCallback(self, callback):
self.__modelCb = WeakMethod(callback)
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)
if node and not node.setEditorData(self, index.column()):
value = index.data(Qt.Qt.EditRole)
return self.setValue(value)
return self.setModelValue(value)
return True
......@@ -89,14 +103,3 @@ class EditorMixin(object):
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
......@@ -29,10 +29,12 @@ __authors__ = ["D. Naudet"]
__license__ = "MIT"
__date__ = "01/11/2016"
from functools import partial
from silx.gui import qt as Qt
from .ModelDef import ModelColumns, ModelRoles
from .NodeEditor import EditorMixin
class TreeView(Qt.QTreeView):
......@@ -41,8 +43,12 @@ class TreeView(Qt.QTreeView):
super(TreeView, self).__init__(parent)
delegate = ItemDelegate(self)
self.setItemDelegateForColumn(ModelColumns.NameColumn, delegate)
delegate.sigDelegateEvent.connect(partial(self.__delegateEvent,
ModelColumns.NameColumn))
delegate = ItemDelegate(self)
self.setItemDelegateForColumn(ModelColumns.ValueColumn, delegate)
delegate.sigDelegateEvent.connect(partial(self.__delegateEvent,
ModelColumns.ValueColumn))
# WARNING : had to set this as a queued connection, otherwise
# there was a crash after the slot was called (conflict with
# __setHiddenNodes probably)
......@@ -55,6 +61,21 @@ class TreeView(Qt.QTreeView):
showUniqueGroup = property(lambda self: self.__showUniqueGroup)
def disableDelegateForColumn(self, column, disable):
self.__openPersistentEditors(Qt.QModelIndex(), openEditor=False)
if disable:
self.setItemDelegateForColumn(column, None)
else:
self.setItemDelegateForColumn(column, ItemDelegate(self))
self.__openPersistentEditors(Qt.QModelIndex(), openEditor=True)
def __delegateEvent(self, column, node, args, kwargs):
self.delegateEvent(column, node, *args, **kwargs)
def delegateEvent(self, column, node, *args, **kwargs):
# TODO : proper class event
pass
def keyReleaseEvent(self, event):
# TODO : better filtering
key = event.key()
......@@ -182,7 +203,7 @@ class TreeView(Qt.QTreeView):
class ItemDelegate(Qt.QStyledItemDelegate):
sigDelegateEvent = Qt.Signal(object)
sigDelegateEvent = Qt.Signal(object, object, object)
def __init__(self, parent=None):
super(ItemDelegate, self).__init__(parent)
......@@ -192,11 +213,17 @@ class ItemDelegate(Qt.QStyledItemDelegate):
if node:
editor = node.getEditor(parent, option, index)
if editor:
if isinstance(editor, EditorMixin):
editor.setViewCallback(self.__notifyView)
editor.setModelCallback(self.__notifyModel)
return editor
return super(ItemDelegate, self).createEditor(parent, option, index)
def _editorValueChanged(self, *args, **kwargs):
self.commitData.emit(self.sender())
def __notifyModel(self, editor, *args, **kwargs):
self.commitData.emit(editor)
def __notifyView(self, editor, *args, **kwargs):
self.sigDelegateEvent.emit(editor.node, args, kwargs)
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
......
......@@ -51,10 +51,13 @@ def getNodeClass(nodeType, attrs=None):
if attrs:
for key, value in attrs.items():
info = _registeredAttributes.get(key)
if info and info[0] == value:
return info[1]
if info:
klass = info.get(value)
if klass:
break
if not klass:
return _registeredNodes.get(nodeType)
klass = _registeredNodes.get(nodeType)
return klass
def registerNodeClass(klass):
......@@ -71,12 +74,16 @@ def registerNodeClass(klass):
_registeredNodes[nodeType] = klass
attribute = klass.attribute
# TODO : improve this...
if attribute:
if attribute[0] in _registeredAttributes:
raise AttributeError('Failed to register attribute {0}.'
'Already registered.'
''.format(klass.__name__))
_registeredAttributes[attribute[0]] = (attribute[1], klass)
if attribute[1] in _registeredAttributes[attribute[0]]:
raise AttributeError('Failed to register attribute {0}.'
'Already registered.'
''.format(klass.__name__))
else:
_registeredAttributes[attribute[0]] = {}
_registeredAttributes[attribute[0]][attribute[1]] = klass
def H5NodeClassDef(nodeType,
......@@ -124,7 +131,8 @@ def H5NodeFactory(h5File, h5Path):
if klass is not None:
return klass(h5File=h5File, h5Path=h5Path)
else:
raise ValueError('Node creation failed.')
raise ValueError('Node creation failed. '
'(was : {0}:{1})'.format(h5File, h5Path))
_H5NodeFactory = H5NodeFactory
......@@ -147,7 +155,7 @@ class H5Base(Node):
self.__h5Path = (h5Path != '/' and h5Path.rstrip('/')) or h5Path
self.className = os.path.basename(h5File).rpartition('.')[0]
super(H5Base, self).__init__(**kwargs)
super(H5Base, self).__init__(subject=self, **kwargs)
self.setData(h5File, Qt.Qt.ToolTipRole)
self.setData(ModelColumns.NameColumn, os.path.basename(self.h5Path))
......
......@@ -46,16 +46,55 @@ from ...io.XsocsH5 import XsocsH5
from .ProjectItem import ProjectItem
from .ProjectDef import ItemClassDef
from .Hdf5Nodes import H5NodeClassDef
@ItemClassDef('IntensityItem')
class IntensityItem(ProjectItem):
@property
def entry(self):
return self.path.rpartition('/')[-1]
@property
def name(self):
entry = self.entry
angle = self.xsocsH5.scan_angle(entry)
if angle is not None:
return str(angle)
return entry
def getScatterData(self):
entry = self.entry
if entry == 'Total':
entry = self.xsocsH5.entries[0]
intensity = self._get_array_data(self.path)
scanPositions = self.xsocsH5.scan_positions(entry)
return intensity, scanPositions
@ItemClassDef('IntensityGroup')
class IntensityGroup(ProjectItem):
IntensityPathTpl = '{0}/{1}'
def _createItem(self):
path_tpl = self.path + '/' + '{0}'
path_tpl = self.IntensityPathTpl.format(self.path, '{0}')
getIntensity(self.filename, path_tpl, self.gui)
with self:
entries = self.xsocsH5.entries()
intensity = self._get_array_data(path_tpl.format(entries[0]))
for entry in entries[1:]:
intensity += self._get_array_data(path_tpl.format(entry))
itemPath = self.path + '/Total'
item = IntensityItem(self.filename, itemPath, mode=self.mode, data=intensity)
def getScatterData(self):
entry = self.xsocsH5.entries()[0]
entryPath = self.IntensityPathTpl.format(self.path, entry)
intensity = self._get_array_data(entryPath)
scanPositions = self.xsocsH5.scan_positions(entry)
return intensity, scanPositions
def _getIntensity(entry, entry_f, projectLock, projectFile, pathTpl, queue):
queue.put({'id': entry,
......@@ -68,10 +107,10 @@ def _getIntensity(entry, entry_f, projectLock, projectFile, pathTpl, queue):
with XsocsH5(entry_f) as entryH5:
cumul = entryH5.image_cumul(entry)
angle = entryH5.scan_angle(entry)
dsetPath = pathTpl.format(str(angle))
dsetPath = pathTpl.format(str(entry))
projectLock.acquire()
XsocsH5(projectFile, mode='r+')._set_array_data(dsetPath, cumul)
IntensityItem(projectFile, dsetPath, mode='r+', data=cumul)
projectLock.release()
except:
state = 'error'
......@@ -250,28 +289,7 @@ class ProgressNode(Node):
return ModelDataList(text, None, Qt.Qt.DisplayRole)
from ..model.NodeEditor import EditorMixin
from .Hdf5Nodes import H5GroupNode
class IntensityGroupEditor(EditorMixin, Qt.QWidget):
persistent = True
def __init__(self, parent, option, index):
super(IntensityGroupEditor, self).__init__(parent, option, index)
layout = Qt.QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
icon = icons.getQIcon('item-1dim')
button = Qt.QToolButton()
button.setIcon(icon)
layout.addWidget(button)
layout.addStretch(1)
@H5NodeClassDef('IntensityNode',
attribute=('XsocsClass', 'IntensityGroup'))
class IntensityNode(H5GroupNode):
editors = IntensityGroupEditor
# node = index.data(ModelRoles.InternalDataRole)
# item = HybridItem(node.projectFile, node.path)
......
......@@ -48,11 +48,16 @@ class ProjectItem(XsocsH5Base):
gui = property(lambda self: self.__gui()
if self.__gui is not None else None)
def __init__(self, h5File, nodePath='/', mode='r', processLevel=None, gui=None):
def __init__(self,
h5File,
nodePath='/',
mode='r',
processLevel=None,
data=None,
gui=None):
# TODO : check if parent already has a child with the same name
super(ProjectItem, self).__init__(h5File, mode=mode)
self.__nodePath = nodePath
self.__processLevel = None
self.__xsocsFile = None
self.__gui = weakref.ref(gui) if gui is not None else None
......@@ -61,15 +66,27 @@ class ProjectItem(XsocsH5Base):
self._loadItem()
else:
with self._get_file() as h5f:
if data is not None:
h5f[nodePath] = data
item = h5f[nodePath]
else:
item = h5f.require_group(nodePath)
if self.XsocsClass is not None:
grp = h5f.require_group(self.path)
grp.attrs['XsocsClass'] = np.string_(self.XsocsClass)
if self.__processLevel is not None:
grp = h5f.require_group(self.path)
grp.attrs['XsocsLevel'] = self.__processLevel
del grp
item.attrs['XsocsClass'] = np.string_(self.XsocsClass)
if processLevel is not None:
item.attrs['XsocsLevel'] = processLevel
del item
self._createItem()
def cast(self):
className = self.xsocsClass
klass = getItemClass(className)
if klass:
return klass(self.filename, self.path, mode=self.mode)
def projectRoot(self):
return ProjectItem(self.filename, '/', mode=self.mode).cast()
def setHidden(self, hidden, path=None):
if path is None:
path = self.__nodePath
......@@ -145,11 +162,11 @@ class ProjectItem(XsocsH5Base):
"""
pass
@property
def processLevel(self):
if self.__processLevel is None:
with self._get_file() as h5f:
processLevel = h5f[self.__nodePath].attrs.get('XsocsLevel')
self.__processLevel = processLevel
return self.__processLevel
with self._get_file() as h5f:
return h5f[self.__nodePath].attrs.get('XsocsLevel')
@property
def xsocsClass(self):
return self.attribute(self.path, 'XsocsClass')
\ 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__ = "01/11/2016"
from silx.gui import qt as Qt, icons
from ..model.ModelDef import ModelColumns
from .IntensityGroup import IntensityGroup, IntensityItem
from ..model.NodeEditor import EditorMixin
from .Hdf5Nodes import H5GroupNode, H5NodeClassDef, H5DatasetNode
class ScatterPlotButton(EditorMixin, Qt.QWidget):
persistent = True
sigValueChanged = Qt.Signal()
def __init__(self, parent, option, index):
super(ScatterPlotButton, self).__init__(parent, option, index)
layout = Qt.QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
icon = icons.getQIcon('item-1dim')
button = Qt.QToolButton()
button.setIcon(icon)
layout.addWidget(button)
layout.addStretch(1)
button.clicked.connect(self.__clicked)
def __clicked(self):
node = self.node
event = {'event': 'scatter'}
self.notifyView(event)
@H5NodeClassDef('IntensityGroupNode',
attribute=('XsocsClass', 'IntensityGroup'))
class IntensityGroupNode(H5GroupNode):
editors = ScatterPlotButton
@H5NodeClassDef('IntensityNode',
attribute=('XsocsClass', 'IntensityItem'))
class IntensityNode(H5DatasetNode):
# editors = ScatterPlotButton
def _setupNode(self):
with IntensityItem(self.h5File, self.h5Path, mode='r') as item:
self.setData(ModelColumns.NameColumn,
str(item.name),
Qt.Qt.DisplayRole)
......@@ -62,7 +62,6 @@ class ScanPositionsItem(ProjectItem):
pathTpl = self.path + '/' + '{0}'
with self:
itemPath = pathTpl.format('pos_0')
print itemPath
pos_0 = self._get_array_data(itemPath)
itemPath = pathTpl.format('pos_1')
pos_1 = self._get_array_data(itemPath)
......@@ -74,7 +73,6 @@ class ScanPositionsItem(ProjectItem):
n_0 = self._get_scalar_data(itemPath)
itemPath = pathTpl.format('n_1')
n_1 = self._get_scalar_data(itemPath)
print n_0
return ScanPositions(motor_0=motor_0,
pos_0=pos_0,
motor_1=motor_1,
......
......@@ -34,15 +34,18 @@ from .Hdf5Nodes import H5Base, H5NodeFactory
from .ProjectItem import ProjectItem
def h5NodeToProjectItem(h5Node, mode='r+'):
def h5NodeToProjectItem(h5Node