Пример #1
0
class SphinxWorker(qt.QObject):
    html_ready = qt.pyqtSignal()

    def prepare(self, docs, docNames, docArgspec="", docNote="", img_path=""):
        srcdir = osp.join(CONFDIR, '_sources')
        if not os.path.exists(srcdir):
            os.makedirs(srcdir)

        for doc, docName in zip(docs, docNames):
            docName = docName.replace(' ', '_')
            fname = osp.join(srcdir, docName) + '.rst'
            with codecs.open(fname, 'w', encoding='utf-8') as f:
                f.write(".. title:: {0}\n".format(docName))
                f.write(doc)

        fname = osp.join(srcdir, 'content.rst')
        with codecs.open(fname, 'w', encoding='utf-8') as f:
            f.write(".. toctree::\n   :maxdepth: 3\n\n")
            for docName in docNames:
                docName = docName.replace(' ', '_')
                f.write("   {0}.rst\n".format(docName))

        self.docArgspec = docArgspec
        self.docNote = docNote
        # self.img_path = img_path

    def render(self):
        cntx = generate_context(name='',
                                argspec=self.docArgspec,
                                note=self.docNote)
        sphinxify(cntx)
        self.thread().terminate()
        self.html_ready.emit()
Пример #2
0
class PeriodicCombo(qt.QComboBox):
    """
    Combo list with all atomic elements of the periodic table

    .. image:: img/PeriodicCombo.png

    :param bool detailed: True (default) display element symbol, Z and name.
        False display only element symbol and Z.
    :param elements: List of items (:class:`PeriodicTableItem` objects) to
        be represented in the table. By default, take elements from
        a predefined list with minimal information (symbol, atomic number,
        name, mass).
    """
    sigSelectionChanged = qt.pyqtSignal(object)
    """Signal emitted when the selection changes. Send
    :class:`PeriodicTableItem` object representing selected
    element
    """
    def __init__(self, parent=None, detailed=True, elements=None):
        qt.QComboBox.__init__(self, parent)

        # add all elements from global list
        if elements is None:
            elements = _defaultTableItems
        for i, elmt in enumerate(elements):
            if detailed:
                txt = "%2s (%d) - %s" % (elmt.symbol, elmt.Z, elmt.name)
            else:
                txt = "%2s (%d)" % (elmt.symbol, elmt.Z)
            self.insertItem(i, txt)

        self.currentIndexChanged[int].connect(self.__selectionChanged)

    def __selectionChanged(self, idx):
        """Emit :attr:`sigSelectionChanged`"""
        self.sigSelectionChanged.emit(_defaultTableItems[idx])

    def getSelection(self):
        """Get selected element

        :return: Selected element
        :rtype: PeriodicTableItem
        """
        return _defaultTableItems[self.currentIndex()]

    def setSelection(self, symbol):
        """Set selected item in combobox by giving the atomic symbol

        :param symbol: Symbol of element to be selected
        """
        # accept PeriodicTableItem for getter/setter consistency
        if isinstance(symbol, PeriodicTableItem):
            symbol = symbol.symbol
        symblist = [elmt.symbol for elmt in _defaultTableItems]
        self.setCurrentIndex(symblist.index(symbol))
Пример #3
0
class LoadProjectDlg(qt.QFileDialog):
    ready = qt.pyqtSignal(list)

    def __init__(self, parent=None, dirname=''):
        super(LoadProjectDlg, self).__init__(parent=parent,
                                             caption='Load project',
                                             directory=dirname)
        self.setOption(qt.QFileDialog.DontUseNativeDialog, True)
        self.setAcceptMode(qt.QFileDialog.AcceptOpen)
        self.setFileMode(qt.QFileDialog.ExistingFile)
        self.setViewMode(qt.QFileDialog.Detail)
        self.setNameFilter("ParSeq Project File (*.pspj)")
        # self.setProxyModel(QTooltipProxyModel(self))

        self.currentChanged.connect(self.updatePreview)
        self.finished.connect(self.onFinish)

        self.splitter = self.layout().itemAtPosition(1, 0).widget()
        self.previewPanel = QPreviewPanel(self)
        self.splitter.addWidget(self.previewPanel)

        self.setMinimumWidth(1200)

    def updatePreview(self, path):
        if path.endswith('.pspj'):
            fname = path.replace('.pspj', '')
        else:
            return
        self.previewPanel.pms = []
        self.previewPanel.pmNames = []
        for i, (name, node) in enumerate(csi.nodes.items()):
            pngName = fname + '-{0}-{1}.png'.format(i + 1, name)
            pmName = u'{0} \u2013 {1}'.format(i + 1, name)
            if os.path.exists(pngName):
                self.previewPanel.pms.append(qt.QPixmap(pngName))
                self.previewPanel.pmNames.append(pmName)
        self.previewPanel.updatePreview()

    def onFinish(self, result):
        if not result:
            return
        resFile = self.selectedFiles()
        self.ready.emit([resFile])
Пример #4
0
class Transformer(qt.QObject):
    ready = qt.pyqtSignal(qt.QWidget, float)

    def prepare(self,
                transform,
                params={},
                runDownstream=True,
                dataItems=None,
                starter=None):
        self.transform = transform
        self.params = params
        self.runDownstream = runDownstream
        self.dataItems = dataItems
        self.starter = starter

    def run(self):
        self.timeStart = time.time()
        self.transform.run(params=self.params,
                           runDownstream=self.runDownstream,
                           dataItems=self.dataItems)
        self.thread().terminate()
        self.timeEnd = time.time()
        self.timeDuration = self.timeEnd - self.timeStart
        self.ready.emit(self.starter, self.timeDuration)
Пример #5
0
class PeriodicList(qt.QTreeWidget):
    """List of atomic elements in a :class:`QTreeView`

    :param QWidget parent: Parent widget
    :param bool detailed: True (default) display element symbol, Z and name.
        False display only element symbol and Z.
    :param single: *True* for single element selection with mouse click,
        *False* for multiple element selection mode.
    """
    sigSelectionChanged = qt.pyqtSignal(object)
    """When any element is selected/unselected in the widget, it emits
    this signal and sends a list of currently selected
    :class:`PeriodicTableItem` objects.
    """
    def __init__(self,
                 parent=None,
                 detailed=True,
                 single=False,
                 elements=None):
        qt.QTreeWidget.__init__(self, parent)

        self.detailed = detailed

        headers = ["Z", "Symbol"]
        if detailed:
            headers.append("Name")
            self.setColumnCount(3)
        else:
            self.setColumnCount(2)
        self.setHeaderLabels(headers)
        self.header().setStretchLastSection(False)

        self.setRootIsDecorated(0)
        self.itemClicked.connect(self.__selectionChanged)
        self.setSelectionMode(qt.QAbstractItemView.SingleSelection if single
                              else qt.QAbstractItemView.ExtendedSelection)
        self.__fill_widget(elements)
        self.resizeColumnToContents(0)
        self.resizeColumnToContents(1)
        if detailed:
            self.resizeColumnToContents(2)

    def __fill_widget(self, elements):
        """Fill tree widget with elements """
        if elements is None:
            elements = _defaultTableItems

        self.tree_items = []

        previous_item = None
        for elmt in elements:
            if previous_item is None:
                item = qt.QTreeWidgetItem(self)
            else:
                item = qt.QTreeWidgetItem(self, previous_item)
            item.setText(0, str(elmt.Z))
            item.setText(1, elmt.symbol)
            if self.detailed:
                item.setText(2, elmt.name)
            self.tree_items.append(item)
            previous_item = item

    def __selectionChanged(self, treeItem, column):
        """Emit a :attr:`sigSelectionChanged` and send a list of
        :class:`PeriodicTableItem` objects."""
        self.sigSelectionChanged.emit(self.getSelection())

    def getSelection(self):
        """Get a list of selected elements, as a list of :class:`PeriodicTableItem`
        objects.

        :return: Selected elements
        :rtype: list(PeriodicTableItem)"""
        return [
            _defaultTableItems[idx] for idx in range(len(self.tree_items))
            if self.tree_items[idx].isSelected()
        ]

    # setSelection is a bad name (name of a QTreeWidget method)
    def setSelectedElements(self, symbolList):
        """

        :param symbolList: List of atomic symbols ["H", "He", "Li"...]
            to be selected in the widget
        """
        # accept PeriodicTableItem for getter/setter consistency
        if isinstance(symbolList[0], PeriodicTableItem):
            symbolList = [elmt.symbol for elmt in symbolList]
        for idx in range(len(self.tree_items)):
            self.tree_items[idx].setSelected(
                _defaultTableItems[idx].symbol in symbolList)
Пример #6
0
class PeriodicTable(qt.QWidget):
    """Periodic Table widget

    The following example shows how to connect clicking to selection::

         from silx.gui import qt
         from silx.gui.widgets.PeriodicTable import PeriodicTable
         app = qt.QApplication([])
         pt = PeriodicTable()
         pt.sigElementClicked.connect(pt.elementToggle)
         pt.show()
         app.exec_()

    To print all selected elements each time a new element is selected::

        def my_slot(item):
            pt.elementToggle(item)
            selected_elements = pt.getSelection()
            for e in selected_elements:
                print(e.symbol)

        pt.sigElementClicked.connect(my_slot)

    """
    sigElementClicked = qt.pyqtSignal(object)
    """When any element is clicked in the table, the widget emits
    this signal and sends a :class:`PeriodicTableItem` object.
    """

    sigSelectionChanged = qt.pyqtSignal(object)
    """When any element is selected/unselected in the table, the widget emits
    this signal and sends a list of :class:`PeriodicTableItem` objects.

    .. note::

        To enable selection of elements, you must set *selectable=True*
        when you instantiate the widget. Alternatively, you can also connect
        :attr:`sigElementClicked` to :meth:`elementToggle` manually::

            pt = PeriodicTable()
            pt.sigElementClicked.connect(pt.elementToggle)


    :param parent: parent QWidget
    :param str name: Widget window title
    :param elements: List of items (:class:`PeriodicTableItem` objects) to
        be represented in the table. By default, take elements from
        a predefined list with minimal information (symbol, atomic number,
        name, mass).
    :param bool selectable: If *True*, multiple elements can be
        selected by clicking with the mouse. If *False* (default),
        selection is only possible with method :meth:`setSelection`.
    """
    def __init__(self,
                 parent=None,
                 name="PeriodicTable",
                 elements=None,
                 selectable=False):
        self.selectable = selectable
        qt.QWidget.__init__(self, parent)
        self.setWindowTitle(name)
        self.gridLayout = qt.QGridLayout(self)
        self.gridLayout.setContentsMargins(0, 0, 0, 0)
        self.gridLayout.addItem(qt.QSpacerItem(0, 5), 7, 0)

        for idx in range(10):
            self.gridLayout.setRowStretch(idx, 3)
        # row 8 (above lanthanoids  is empty)
        self.gridLayout.setRowStretch(7, 2)

        # Element information displayed when cursor enters a cell
        self.eltLabel = qt.QLabel(self)
        f = self.eltLabel.font()
        f.setBold(1)
        self.eltLabel.setFont(f)
        self.eltLabel.setAlignment(qt.Qt.AlignHCenter)
        self.gridLayout.addWidget(self.eltLabel, 1, 1, 3, 10)

        self._eltCurrent = None
        """Current :class:`_ElementButton` (last clicked)"""

        self._eltButtons = OrderedDict()
        """Dictionary of all :class:`_ElementButton`. Keys are the symbols
        ("H", "He", "Li"...)"""

        if elements is None:
            elements = _defaultTableItems
        # fill cells with elements
        for elmt in elements:
            self.__addElement(elmt)

    def __addElement(self, elmt):
        """Add one :class:`_ElementButton` widget into the grid,
        connect its signals to interact with the cursor"""
        b = _ElementButton(elmt, self)
        b.setAutoDefault(False)

        self._eltButtons[elmt.symbol] = b
        self.gridLayout.addWidget(b, elmt.row, elmt.col)

        b.sigElementEnter.connect(self.elementEnter)
        b.sigElementLeave.connect(self._elementLeave)
        b.sigElementClicked.connect(self._elementClicked)

    def elementEnter(self, item):
        """Update label with element info (e.g. "Nb(41) - niobium")
        when mouse cursor hovers an element.

        :param PeriodicTableItem item: Element entered by cursor
        """
        self.eltLabel.setText("%s(%d) - %s" % (item.symbol, item.Z, item.name))

    def _elementLeave(self, item):
        """Clear label when the cursor leaves the cell

        :param PeriodicTableItem item: Element left
        """
        self.eltLabel.setText("")

    def _elementClicked(self, item):
        """Emit :attr:`sigElementClicked`,
        toggle selected state of element
        
        :param PeriodicTableItem item: Element clicked
        """
        if self._eltCurrent is not None:
            self._eltCurrent.setCurrent(False)
        self._eltButtons[item.symbol].setCurrent(True)
        self._eltCurrent = self._eltButtons[item.symbol]
        if self.selectable:
            self.elementToggle(item)
        self.sigElementClicked.emit(item)

    def getSelection(self):
        """Return a list of selected elements, as a list of :class:`PeriodicTableItem`
        objects.

        :return: Selected items
        :rtype: list(PeriodicTableItem)
        """
        return [b.item for b in self._eltButtons.values() if b.isSelected()]

    def setSelection(self, symbols):
        """Set selected elements.

        This causes the sigSelectionChanged signal
        to be emitted, even if the selection didn't actually change.

        :param list(str) symbols: List of symbols of elements to be selected
            (e.g. *["Fe", "Hg", "Li"]*)
        """
        # accept list of PeriodicTableItems as input, because getSelection
        # returns these objects and it makes sense to have getter and setter
        # use same type of data
        if isinstance(symbols[0], PeriodicTableItem):
            symbols = [elmt.symbol for elmt in symbols]

        for (e, b) in self._eltButtons.items():
            b.setSelected(e in symbols)
        self.sigSelectionChanged.emit(self.getSelection())

    def setElementSelected(self, symbol, state):
        """Modify *selected* status of a single element (select or unselect)

        :param str symbol: PeriodicTableItem symbol to be selected
        :param bool state: *True* to select, *False* to unselect
        """
        self._eltButtons[symbol].setSelected(state)
        self.sigSelectionChanged.emit(self.getSelection())

    def isElementSelected(self, symbol):
        """Return *True* if element is selected, else *False*

        :param str symbol: PeriodicTableItem symbol
        :return: *True* if element is selected, else *False*
        """
        return self._eltButtons[symbol].isSelected()

    def elementToggle(self, item):
        """Toggle selected/unselected state for element

        :param item: PeriodicTableItem object
        """
        b = self._eltButtons[item.symbol]
        b.setSelected(not b.isSelected())
        self.sigSelectionChanged.emit(self.getSelection())
Пример #7
0
class _ElementButton(qt.QPushButton):
    """Atomic element button, used as a cell in the periodic table
    """
    sigElementEnter = qt.pyqtSignal(object)
    """Signal emitted as the cursor enters the widget"""
    sigElementLeave = qt.pyqtSignal(object)
    """Signal emitted as the cursor leaves the widget"""
    sigElementClicked = qt.pyqtSignal(object)
    """Signal emitted when the widget is clicked"""
    def __init__(self, item, parent=None):
        """

        :param parent: Parent widget
        :param PeriodicTableItem item: :class:`PeriodicTableItem` object
        """
        qt.QPushButton.__init__(self, parent)

        self.item = item
        """:class:`PeriodicTableItem` object represented by this button"""

        self.setText(item.symbol)
        self.setFlat(1)
        self.setCheckable(0)

        self.setSizePolicy(
            qt.QSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Expanding))

        self.selected = False
        self.current = False

        # selection colors
        self.selected_color = qt.QColor(qt.Qt.yellow)
        self.current_color = qt.QColor(qt.Qt.gray)
        self.selected_current_color = qt.QColor(qt.Qt.darkYellow)

        # element colors

        if hasattr(item, "bgcolor"):
            self.bgcolor = qt.QColor(item.bgcolor)
        else:
            self.bgcolor = qt.QColor("#FFFFFF")

        self.brush = qt.QBrush()
        self.__setBrush()

        self.clicked.connect(self.clickedSlot)

    def sizeHint(self):
        return qt.QSize(40, 40)

    def setCurrent(self, b):
        """Set this element button as current.
        Multiple buttons can be selected.

        :param b: boolean
        """
        self.current = b
        self.__setBrush()

    def isCurrent(self):
        """
        :return: True if element button is current
        """
        return self.current

    def isSelected(self):
        """
        :return: True if element button is selected
        """
        return self.selected

    def setSelected(self, b):
        """Set this element button as selected.
        Only a single button can be selected.

        :param b: boolean
        """
        self.selected = b
        self.__setBrush()

    def __setBrush(self):
        """Selected cells are yellow when not current.
        The current cell is dark yellow when selected or grey when not
        selected.
        Other cells have no bg color by default, unless specified at
        instantiation (:attr:`bgcolor`)"""
        palette = self.palette()
        # if self.current and self.selected:
        #     self.brush = qt.QBrush(self.selected_current_color)
        # el
        if self.selected:
            self.brush = qt.QBrush(self.selected_color)
        # elif self.current:
        #     self.brush = qt.QBrush(self.current_color)
        elif self.bgcolor is not None:
            self.brush = qt.QBrush(self.bgcolor)
        else:
            self.brush = qt.QBrush()
        palette.setBrush(self.backgroundRole(), self.brush)
        self.setPalette(palette)
        self.update()

    def paintEvent(self, pEvent):
        # get button geometry
        widgGeom = self.rect()
        paintGeom = qt.QRect(widgGeom.left() + 1,
                             widgGeom.top() + 1,
                             widgGeom.width() - 2,
                             widgGeom.height() - 2)

        # paint background color
        painter = qt.QPainter(self)
        if self.brush is not None:
            painter.fillRect(paintGeom, self.brush)
        # paint frame
        pen = qt.QPen(qt.Qt.black)
        pen.setWidth(1 if not self.isCurrent() else 5)
        painter.setPen(pen)
        painter.drawRect(paintGeom)
        painter.end()
        qt.QPushButton.paintEvent(self, pEvent)

    def enterEvent(self, e):
        """Emit a :attr:`sigElementEnter` signal and send a
        :class:`PeriodicTableItem` object"""
        self.sigElementEnter.emit(self.item)

    def leaveEvent(self, e):
        """Emit a :attr:`sigElementLeave` signal and send a
        :class:`PeriodicTableItem` object"""
        self.sigElementLeave.emit(self.item)

    def clickedSlot(self):
        """Emit a :attr:`sigElementClicked` signal and send a
        :class:`PeriodicTableItem` object"""
        self.sigElementClicked.emit(self.item)
Пример #8
0
class FrameBrowser(qt.QWidget):
    """Frame browser widget, with 4 buttons/icons and a line edit to provide
    a way of selecting a frame index in a stack of images.

    .. image:: img/FrameBrowser.png

    It can be used in more generic case to select an integer within a range.

    :param QWidget parent: Parent widget
    :param int n: Number of frames. This will set the range
        of frame indices to 0--n-1.
        If None, the range is initialized to the default QSlider range (0--99)."""
    sigIndexChanged = qt.pyqtSignal(object)

    def __init__(self, parent=None, n=None):
        qt.QWidget.__init__(self, parent)

        # Use the font size as the icon size to avoid to create bigger buttons
        fontMetric = self.fontMetrics()
        iconSize = qt.QSize(fontMetric.height(), fontMetric.height())

        self.mainLayout = qt.QHBoxLayout(self)
        self.mainLayout.setContentsMargins(0, 0, 0, 0)
        self.mainLayout.setSpacing(0)
        self.firstButton = qt.QPushButton(self)
        self.firstButton.setIcon(icons.getQIcon("first"))
        self.firstButton.setIconSize(iconSize)
        self.previousButton = qt.QPushButton(self)
        self.previousButton.setIcon(icons.getQIcon("previous"))
        self.previousButton.setIconSize(iconSize)
        self._lineEdit = qt.QLineEdit(self)

        self._label = qt.QLabel(self)
        self.nextButton = qt.QPushButton(self)
        self.nextButton.setIcon(icons.getQIcon("next"))
        self.nextButton.setIconSize(iconSize)
        self.lastButton = qt.QPushButton(self)
        self.lastButton.setIcon(icons.getQIcon("last"))
        self.lastButton.setIconSize(iconSize)

        self.mainLayout.addWidget(self.firstButton)
        self.mainLayout.addWidget(self.previousButton)
        self.mainLayout.addWidget(self._lineEdit)
        self.mainLayout.addWidget(self._label)
        self.mainLayout.addWidget(self.nextButton)
        self.mainLayout.addWidget(self.lastButton)

        if n is None:
            first = qt.QSlider().minimum()
            last = qt.QSlider().maximum()
        else:
            first, last = 0, n

        self._lineEdit.setFixedWidth(self._lineEdit.fontMetrics().width('%05d' % last))
        validator = qt.QIntValidator(first, last, self._lineEdit)
        self._lineEdit.setValidator(validator)
        self._lineEdit.setText("%d" % first)
        self._label.setText("of %d" % last)

        self._index = first
        """0-based index"""

        self.firstButton.clicked.connect(self._firstClicked)
        self.previousButton.clicked.connect(self._previousClicked)
        self.nextButton.clicked.connect(self._nextClicked)
        self.lastButton.clicked.connect(self._lastClicked)
        self._lineEdit.editingFinished.connect(self._textChangedSlot)

    def lineEdit(self):
        """Returns the line edit provided by this widget.

        :rtype: qt.QLineEdit
        """
        return self._lineEdit

    def limitWidget(self):
        """Returns the widget displaying axes limits.

        :rtype: qt.QLabel
        """
        return self._label

    def _firstClicked(self):
        """Select first/lowest frame number"""
        self._lineEdit.setText("%d" % self._lineEdit.validator().bottom())
        self._textChangedSlot()

    def _previousClicked(self):
        """Select previous frame number"""
        if self._index > self._lineEdit.validator().bottom():
            self._lineEdit.setText("%d" % (self._index - 1))
            self._textChangedSlot()

    def _nextClicked(self):
        """Select next frame number"""
        if self._index < (self._lineEdit.validator().top()):
            self._lineEdit.setText("%d" % (self._index + 1))
            self._textChangedSlot()

    def _lastClicked(self):
        """Select last/highest frame number"""
        self._lineEdit.setText("%d" % self._lineEdit.validator().top())
        self._textChangedSlot()

    def _textChangedSlot(self):
        """Select frame number typed in the line edit widget"""
        txt = self._lineEdit.text()
        if not len(txt):
            self._lineEdit.setText("%d" % self._index)
            return
        new_value = int(txt)
        if new_value == self._index:
            return
        ddict = {
            "event": "indexChanged",
            "old": self._index,
            "new": new_value,
            "id": id(self)
        }
        self._index = new_value
        self.sigIndexChanged.emit(ddict)

    def setRange(self, first, last):
        """Set minimum and maximum frame indices
        Initialize the frame index to *first*.
        Update the label text to *" limits: first, last"*

        :param int first: Minimum frame index
        :param int last: Maximum frame index"""
        return self.setLimits(first, last)

    def setLimits(self, first, last):
        """Set minimum and maximum frame indices.
        Initialize the frame index to *first*.
        Update the label text to *" limits: first, last"*

        :param int first: Minimum frame index
        :param int last: Maximum frame index"""
        bottom = min(first, last)
        top = max(first, last)
        self._lineEdit.validator().setTop(top)
        self._lineEdit.validator().setBottom(bottom)
        self._index = bottom
        self._lineEdit.setText("%d" % self._index)
        self._label.setText(" limits: %d, %d " % (bottom, top))

    def setNFrames(self, nframes):
        """Set minimum=0 and maximum=nframes-1 frame numbers.
        Initialize the frame index to 0.
        Update the label text to *"1 of nframes"*

        :param int nframes: Number of frames"""
        bottom = 0
        top = nframes - 1
        self._lineEdit.validator().setTop(top)
        self._lineEdit.validator().setBottom(bottom)
        self._index = bottom
        self._lineEdit.setText("%d" % self._index)
        # display 1-based index in label
        self._label.setText(" %d of %d " % (self._index + 1, top + 1))

    def getCurrentIndex(self):
        """Get 0-based frame index
        """
        return self._index

    def setValue(self, value):
        """Set 0-based frame index

        :param int value: Frame number"""
        self._lineEdit.setText("%d" % value)
        self._textChangedSlot()
Пример #9
0
class DataTreeModel(qt.QAbstractItemModel):

    needReplot = qt.pyqtSignal()

    def __init__(self, parent=None):
        super(DataTreeModel, self).__init__(parent)
        self.rootItem = csi.dataRootItem

    def rowCount(self, parent=qt.QModelIndex()):
        parentItem = parent.internalPointer() if parent.isValid() else\
            self.rootItem
        return parentItem.child_count()

    def columnCount(self, parent):
        return len(csi.modelLeadingColumns) + len(csi.modelDataColumns)

    def flags(self, index):
        if not index.isValid():
            return qt.Qt.NoItemFlags
        # res = super(DataTreeModel, self).flags(index) |
        res = qt.Qt.ItemIsSelectable | qt.Qt.ItemIsDragEnabled | \
            qt.Qt.ItemIsDropEnabled
        if index.column() == 1:
            res |= qt.Qt.ItemIsUserCheckable
            isDim1 = True
            if csi.currentNode is not None:
                isDim1 = csi.currentNode.plotDimension == 1
            if csi.dataRootItem.isVisible and isDim1:
                res |= qt.Qt.ItemIsEnabled
        else:
            res |= qt.Qt.ItemIsEnabled

        cond = index.column() == 0  # editable for all items in column 0
        #        item = index.internalPointer()
        #        cond = cond and item.childItems  # editable only if a group
        if cond:
            res |= qt.Qt.ItemIsEditable
        return res

    def data(self, index, role=qt.Qt.DisplayRole):
        if not index.isValid():
            return
        item = index.internalPointer()
        if role in (qt.Qt.DisplayRole, qt.Qt.EditRole):
            return item.data(index.column())
        elif role == qt.Qt.CheckStateRole:
            if index.column() == 1:
                return int(
                    qt.Qt.Checked if item.isVisible else qt.Qt.Unchecked)
        elif role == qt.Qt.ToolTipRole:
            if index.column() == 0:
                return item.tooltip()
        elif role == qt.Qt.BackgroundRole:
            if item.beingTransformed and index.column() == 0:
                return BUSY_BKGND
            if item.childItems:  # is a group
                return GROUP_BKGND
            color = BKGND[item.get_state(index.column())]
            if color is not None:
                return color
        elif role == qt.Qt.ForegroundRole:
            if index.column() < len(csi.modelLeadingColumns):
                return qt.QColor(FONT_COLOR_TAG[item.colorTag])
            else:
                return qt.QColor(item.color())
        elif role == qt.Qt.FontRole:
            if item.childItems and (index.column() == 0):  # group in bold
                myFont = qt.QFont()
                myFont.setBold(True)
                return myFont
        elif role == qt.Qt.TextAlignmentRole:
            if index.column() == 1:
                return qt.Qt.AlignCenter

    def setData(self, index, value, role=qt.Qt.EditRole):
        if role == qt.Qt.EditRole:
            item = index.internalPointer()
            item.set_data(index.column(), str(value))
            #            item.aliasExtra = None
            self.dataChanged.emit(index, index)
            self.needReplot.emit()
            return True
        if role == qt.Qt.CheckStateRole:
            item = index.internalPointer()
            self.setVisible(item, value)
            return True
        return False

    def setVisible(self, item, value, emit=True):
        item.set_visible(value)
        if emit:
            self.dataChanged.emit(qt.QModelIndex(), qt.QModelIndex())
            self.needReplot.emit()
        if item is csi.dataRootItem:  # by click on header
            for it in csi.selectedItems:
                self.setVisible(it, True, True)
            return
        if item.parentItem is csi.dataRootItem:
            return
        # make group (un)checked if all group items are (un)checked:
        siblingsEqual = False
        for itemSib in item.parentItem.childItems:
            if itemSib.isVisible != item.isVisible:
                break
        else:
            siblingsEqual = True
        if siblingsEqual and (item.parentItem.isVisible != item.isVisible):
            item.parentItem.set_visible(value)

    def index(self, row, column=0, parent=None):
        if parent is None:
            parent = qt.QModelIndex()
        if not self.hasIndex(row, column, parent):
            return qt.QModelIndex()
        parentItem = parent.internalPointer() if parent.isValid() else\
            self.rootItem
        rowItem = parentItem.child(row)
        if rowItem is None:
            return qt.QModelIndex()
        return self.createIndex(row, column, rowItem)

    def indexFromItem(self, item):
        return self.createIndex(item.row(), 0, item)

    def parent(self, index):
        if not index.isValid():
            return qt.QModelIndex()
        item = index.internalPointer()
        parentItem = item.parentItem
        if parentItem is self.rootItem:
            return qt.QModelIndex()
        try:
            return self.createIndex(parentItem.row(), 0, parentItem)
        except (TypeError, AttributeError):
            return qt.QModelIndex()

    def getItems(self, indexes):
        items = []
        for index in indexes:
            item = index.internalPointer()
            childN = item.child_count()
            if childN > 0:  # is a group
                subInd = [self.index(i, 0, index) for i in range(childN)]
                items += [i for i in self.getItems(subInd) if i not in items]
            else:
                if item not in items:  # inclusion check that keeps the order
                    items.append(item)
        return items

    def getTopItems(self, indexes):
        items = [i.internalPointer() for i in indexes]
        return [item for item in items if item.parentItem not in items]

    def importData(self, data, parentItem=None, insertAt=None, **kwargs):
        if parentItem is None:
            parentItem = self.rootItem
        self.beginResetModel()
        items = parentItem.insert_data(data, insertAt, **kwargs)
        topItems = [it for it in items if it in parentItem.childItems]
        bottomItems = [it for it in items if it not in parentItem.childItems]

        if len(csi.transforms.values()) > 0:
            tr = list(csi.transforms.values())[0]
            if True:  # with a threaded transform
                csi.transformer.prepare(tr,
                                        dataItems=bottomItems + topItems,
                                        starter=tr.widget)
                csi.transformer.thread().start()
            else:  # in the same thread
                tr.run(dataItems=bottomItems + topItems)

        self.endResetModel()
        self.dataChanged.emit(qt.QModelIndex(), qt.QModelIndex())

        mode = qt.QItemSelectionModel.Select | qt.QItemSelectionModel.Rows
        for item in items:
            row = item.row()
            index = self.createIndex(row, 0, item)
            csi.selectionModel.select(index, mode)
        return items

    def removeData(self, data):
        gur.pushDataToUndo(data.copy(), [it.parentItem for it in data],
                           [it.row() for it in data],
                           strChange='remove')
        self.beginResetModel()
        for item in reversed(data):
            item.delete()
        self.endResetModel()
        self.dataChanged.emit(qt.QModelIndex(), qt.QModelIndex())
        self.needReplot.emit()

    def undoRemove(self, undoEntry):
        if undoEntry[-1] != 'remove':
            return
        self.beginResetModel()
        for data, parentItem, row in zip(*undoEntry[0:3]):
            if parentItem is not csi.dataRootItem and parentItem.row() is None:
                csi.dataRootItem.childItems.append(data)
                data.parentItem = csi.dataRootItem
            else:
                parentItem.childItems.insert(row, data)
        self.endResetModel()
        self.dataChanged.emit(qt.QModelIndex(), qt.QModelIndex())
        self.needReplot.emit()

    def moveItem(self, item, to):  # to = +1(up) or -1(down)
        parentItem = item.parentItem
        row = item.row()
        if row is None:
            return
        endRow = 0 if to == +1 else parentItem.child_count() - 1
        if parentItem is self.rootItem and row == endRow:
            return

        siblings = parentItem.childItems
        self.beginResetModel()
        if row == endRow:
            insertAt = parentItem.row() if to == +1 else parentItem.row() + 1
            parentItem.parentItem.childItems.insert(insertAt, item)
            item.parentItem = parentItem.parentItem
            del siblings[row]
            if parentItem.child_count() == 0:
                parentItem.delete()
        elif (siblings[row - to].child_count() > 0):
            insertAt = len(siblings[row - to].childItems) if to == +1 else 0
            siblings[row - to].childItems.insert(insertAt, item)
            item.parentItem = siblings[row - to]
            del siblings[row]
            if parentItem.child_count() == 0:
                parentItem.delete()
        else:
            siblings[row - to], siblings[row] = siblings[row], siblings[row -
                                                                        to]
        self.endResetModel()
        item.parentItem.init_colors()
        if not (parentItem is item.parentItem):
            parentItem.init_colors()
        self.dataChanged.emit(qt.QModelIndex(), qt.QModelIndex())

    def groupItems(self, items):
        parentItem, row = items[0].parentItem, items[0].row()
        self.beginResetModel()

        # make group name:
        cs = items[0].alias
        for item in items[1:]:
            cs = cco.common_substring(cs, item.alias)
        groupName = "{0}_{1}items".format(cs, len(items)) if len(cs) > 0 else\
            "new group"
        group = parentItem.insert_item(groupName, row)
        for item in items:
            parentItem, row = item.parentItem, item.row()
            group.childItems.append(item)
            item.parentItem = group
            del parentItem.childItems[row]
            if parentItem.child_count() == 0:
                parentItem.delete()
        self.endResetModel()
        if hasattr(group.parentItem, 'colorAutoUpdate'):
            group.colorAutoUpdate = group.parentItem.colorAutoUpdate
        group.init_colors()
        group.parentItem.init_colors()
        self.dataChanged.emit(qt.QModelIndex(), qt.QModelIndex())
        return group

    def ungroup(self, group):
        parentItem, row = group.parentItem, group.row()
        self.beginResetModel()
        for item in reversed(group.childItems):
            parentItem.childItems.insert(row, item)
            item.parentItem = parentItem
        parentItem.childItems.remove(group)
        self.endResetModel()
        parentItem.init_colors()
        self.dataChanged.emit(qt.QModelIndex(), qt.QModelIndex())

    def supportedDropActions(self):
        return qt.Qt.MoveAction | qt.Qt.CopyAction

    def mimeTypes(self):
        return [cco.MIME_TYPE_DATA, cco.MIME_TYPE_TEXT, cco.MIME_TYPE_HDF5]

    def mimeData(self, indexes):
        indexes0 = [index for index in indexes if index.column() == 0]
        mimedata = qt.QMimeData()
        items = self.getTopItems(indexes0)
        bstream = pickle.dumps([item.climb_rows() for item in items])
        mimedata.setData(cco.MIME_TYPE_DATA, bstream)
        return mimedata

#    def canDropMimeData(self, data, action, row, column, parent):
#        print(data.formats())
#        return super(DataTreeModel, self).canDropMimeData(
#             data, action, row, column, parent)

    def dropMimeData(self, mimedata, action, row, column, parent):
        if mimedata.hasFormat(cco.MIME_TYPE_DATA):
            toItem = parent.internalPointer()
            if toItem is None:
                toItem = csi.dataRootItem
                newParentItem, newRow = toItem, toItem.child_count()
                parents = []
            else:
                newParentItem, newRow = toItem.parentItem, toItem.row()
                parents = [toItem.parentItem]
            rowss = pickle.loads(mimedata.data(cco.MIME_TYPE_DATA))
            dropedItems = []
            for rows in rowss:
                parentItem = self.rootItem
                for r in reversed(rows):
                    item = parentItem.child(r)
                    parentItem = item
                dropedItems.append(item)
                if item.parentItem not in parents:
                    parents.append(item.parentItem)
            for item in dropedItems:
                if item.is_ancestor_of(toItem):
                    msg = qt.QMessageBox()
                    msg.setIcon(qt.QMessageBox.Warning)
                    msg.setText("Cannot drop a group onto its child. Ignored")
                    msg.setWindowTitle("Illegal drop")
                    msg.setStandardButtons(qt.QMessageBox.Close)
                    msg.exec_()
                    return False

            self.beginResetModel()
            if toItem is csi.dataRootItem:
                rdropedItems = dropedItems
            else:
                rdropedItems = reversed(dropedItems)
            for item in rdropedItems:
                oldParentItem, oldRow = item.parentItem, item.row()
                if newParentItem is oldParentItem:
                    sibl = newParentItem.childItems
                    sibl.insert(newRow, sibl.pop(oldRow))
                else:
                    newParentItem.childItems.insert(newRow, item)
                    item.parentItem = newParentItem
                    del oldParentItem.childItems[oldRow]
                    if oldParentItem.child_count() == 0:
                        oldParentItem.delete()
            self.endResetModel()
            for parent in parents:
                parent.init_colors()
            self.dataChanged.emit(qt.QModelIndex(), qt.QModelIndex())
            self.needReplot.emit()
            return True
        elif mimedata.hasFormat(cco.MIME_TYPE_TEXT) or \
                mimedata.hasFormat(cco.MIME_TYPE_HDF5):
            toItem = parent.internalPointer()
            if mimedata.hasFormat(cco.MIME_TYPE_TEXT):
                urls = [url.toLocalFile() for url in reversed(mimedata.urls())]
            else:
                urls = pickle.loads(mimedata.data(cco.MIME_TYPE_HDF5))[::-1]
            if toItem is None:
                toItem = csi.dataRootItem
                urls = urls[::-1]

            if toItem.child_count() > 0:  # is a group
                parentItem, insertAt = toItem, None
            else:
                parentItem, insertAt = toItem.parentItem, toItem.row()
            if csi.currentNode is None:
                return False
            node = csi.currentNode
            if hasattr(node, 'widget'):
                items = node.widget.loadFiles(urls, parentItem, insertAt)


#            if DEBUG > 0:
#                if items is not None:
#                    for item in items:
#                        item.colorTag = 3
            return True
        else:
            return False

    def invalidateData(self):
        self.dataChanged.emit(qt.QModelIndex(), qt.QModelIndex())
        self.needReplot.emit()
Пример #10
0
class MapWidget(CustomPlotWindow):
    """
    A re-implementation of Plot2D, with customized tools and signals.
    """

    clickSelectionChanged = qt.pyqtSignal(float, float)
    indexSelectionChanged = qt.pyqtSignal(int)
    selectionCleared = qt.pyqtSignal()

    def __init__(self, parent=None):

        super(MapWidget, self).__init__(parent=parent,
                                        backend=None,
                                        resetzoom=True,
                                        autoScale=False,
                                        logScale=False,
                                        grid=False,
                                        curveStyle=False,
                                        colormap=True,
                                        aspectRatio=True,
                                        yInverted=True,
                                        copy=True,
                                        save=True,
                                        print_=False,
                                        control=False,
                                        roi=False,
                                        mask=True)
        if parent is None:
            self.setWindowTitle('comMapWidget')

        self.setGraphTitle('Scan map')
        self.setKeepDataAspectRatio(True)
        self.setYAxisInverted(True)

        # customize the mask tools for use as ROI selectors
        # unfortunately, tooltip and icon reset each other, so only changing the icon.
        self.getMaskToolsDockWidget().setWindowTitle('scan map ROI')
        roiAction = self.getMaskAction()
        roiAction.setToolTip('Select a scan map region of interest')
        roiAction.setIcon(getQIcon('image-select-box'))

        # Remove the mask action from where it was
        self.toolBar().removeAction(roiAction)

        # Rebuild the zoom/pan toolbar and add selection tools
        tb = self.getInteractiveModeToolBar()
        for w in tb.actions():
            tb.removeAction(w)
        zoomAction = qt.QAction(getQIcon('zoom'),
                                'Zoom',
                                parent=self,
                                checkable=True)
        panAction = qt.QAction(getQIcon('pan'),
                               'Pan',
                               parent=self,
                               checkable=True)
        selectAction = qt.QAction(getQIcon('image-select-add'),
                                  'Select single scan point',
                                  parent=self,
                                  checkable=True)
        clearAction = qt.QAction(getQIcon('image-select-erase'),
                                 'Clear selections',
                                 parent=self,
                                 checkable=False)
        tb.addAction(zoomAction)
        tb.addAction(panAction)
        tb.addAction(selectAction)
        tb.addAction(roiAction)
        tb.addAction(clearAction)
        group = qt.QActionGroup(self)
        group.addAction(zoomAction)
        group.addAction(panAction)
        group.addAction(selectAction)
        group.addAction(roiAction)
        zoomAction.setChecked(True)

        def setZoomMode(active):
            if active:
                self.setInteractiveMode('zoom')

        def setPanMode(active):
            if active:
                self.setInteractiveMode('pan')

        def setSelectMode(active):
            if active:
                self.setInteractiveMode('select')

        zoomAction.toggled.connect(setZoomMode)
        panAction.toggled.connect(setPanMode)
        selectAction.toggled.connect(setSelectMode)
        self.selectAction = selectAction
        self.roiAction = roiAction
        self.clearAction = clearAction
        self.sigPlotSignal.connect(self.filterMouseEvents)
        clearAction.triggered.connect(self.selectionCleared)

        # Add the index clicker
        self.indexBox = qt.QSpinBox(
            toolTip='Select a specific position by index')
        self.indexBox.setMinimum(0)
        tb.addWidget(self.indexBox)
        self.indexBox.valueChanged.connect(self.indexSelectionChanged)

        # add a button to toggle positions
        self.positionsAction = qt.QAction('positions', self, checkable=True)
        self.toolBar().addAction(self.positionsAction)

        # add the interpolation button
        self.interpolToolbar = qt.QToolBar('Interpolation options')
        self.interpolBox = qt.QSpinBox(
            toolTip='Map oversampling relative to average step size')
        self.interpolBox.setRange(1, 50)
        self.interpolBox.setValue(5)
        self.interpolToolbar.addWidget(qt.QLabel(' N:'))
        self.interpolToolbar.addWidget(self.interpolBox)
        self.addToolBar(self.interpolToolbar)
        self.interpolToolbar.hide()
        a = self.interpolToolbar.toggleViewAction().setChecked(False)

        # add a profile tool
        self.profile = ProfileToolBar(plot=self)
        self.addToolBar(self.profile)

        # set default colormap
        self.setDefaultColormap({
            'name': 'gray',
            'autoscale': True,
            'normalization': 'linear'
        })

    def filterMouseEvents(self, event):
        if event['event'] == 'mouseClicked' and self.selectAction.isChecked():
            self.clickSelectionChanged.emit(event['x'], event['y'])
Пример #11
0
class BackgroundParamWidget(qt.QWidget):
    """Background configuration composite widget.

    Strip and snip filters parameters can be adjusted using input widgets.

    Updating the widgets causes :attr:`sigBackgroundParamWidgetSignal` to
    be emitted.
    """
    sigBackgroundParamWidgetSignal = qt.pyqtSignal(object)

    def __init__(self, parent=None):
        qt.QWidget.__init__(self, parent)

        self.mainLayout = qt.QGridLayout(self)
        self.mainLayout.setColumnStretch(1, 1)

        # Algorithm choice ---------------------------------------------------
        self.algorithmComboLabel = qt.QLabel(self)
        self.algorithmComboLabel.setText("Background algorithm")
        self.algorithmCombo = qt.QComboBox(self)
        self.algorithmCombo.addItem("Strip")
        self.algorithmCombo.addItem("Snip")
        self.algorithmCombo.activated[int].connect(
                self._algorithmComboActivated)

        # Strip parameters ---------------------------------------------------
        self.stripWidthLabel = qt.QLabel(self)
        self.stripWidthLabel.setText("Strip Width")

        self.stripWidthSpin = qt.QSpinBox(self)
        self.stripWidthSpin.setMaximum(100)
        self.stripWidthSpin.setMinimum(1)
        self.stripWidthSpin.valueChanged[int].connect(self._emitSignal)

        self.stripIterLabel = qt.QLabel(self)
        self.stripIterLabel.setText("Strip Iterations")
        self.stripIterValue = qt.QLineEdit(self)
        validator = qt.QIntValidator(self.stripIterValue)
        self.stripIterValue._v = validator
        self.stripIterValue.setText("0")
        self.stripIterValue.editingFinished[()].connect(self._emitSignal)
        self.stripIterValue.setToolTip(
                "Number of iterations for strip algorithm.\n" +
                "If greater than 999, an 2nd pass of strip filter is " +
                "applied to remove artifacts created by first pass.")

        # Snip parameters ----------------------------------------------------
        self.snipWidthLabel = qt.QLabel(self)
        self.snipWidthLabel.setText("Snip Width")

        self.snipWidthSpin = qt.QSpinBox(self)
        self.snipWidthSpin.setMaximum(300)
        self.snipWidthSpin.setMinimum(0)
        self.snipWidthSpin.valueChanged[int].connect(self._emitSignal)


        # Smoothing parameters -----------------------------------------------
        self.smoothingFlagCheck = qt.QCheckBox(self)
        self.smoothingFlagCheck.setText("Smoothing Width (Savitsky-Golay)")
        self.smoothingFlagCheck.toggled.connect(self._smoothingToggled)

        self.smoothingSpin = qt.QSpinBox(self)
        self.smoothingSpin.setMinimum(3)
        #self.smoothingSpin.setMaximum(40)
        self.smoothingSpin.setSingleStep(2)
        self.smoothingSpin.valueChanged[int].connect(self._emitSignal)

        # Anchors ------------------------------------------------------------

        self.anchorsGroup = qt.QWidget(self)
        anchorsLayout = qt.QHBoxLayout(self.anchorsGroup)
        anchorsLayout.setSpacing(2)
        anchorsLayout.setContentsMargins(0, 0, 0, 0)

        self.anchorsFlagCheck = qt.QCheckBox(self.anchorsGroup)
        self.anchorsFlagCheck.setText("Use anchors")
        self.anchorsFlagCheck.setToolTip(
                "Define X coordinates of points that must remain fixed")
        self.anchorsFlagCheck.stateChanged[int].connect(
                self._anchorsToggled)
        anchorsLayout.addWidget(self.anchorsFlagCheck)

        maxnchannel = 16384 * 4    # Fixme ?
        self.anchorsList = []
        num_anchors = 4
        for i in range(num_anchors):
            anchorSpin = qt.QSpinBox(self.anchorsGroup)
            anchorSpin.setMinimum(0)
            anchorSpin.setMaximum(maxnchannel)
            anchorSpin.valueChanged[int].connect(self._emitSignal)
            anchorsLayout.addWidget(anchorSpin)
            self.anchorsList.append(anchorSpin)

        # Layout ------------------------------------------------------------
        self.mainLayout.addWidget(self.algorithmComboLabel, 0, 0)
        self.mainLayout.addWidget(self.algorithmCombo, 0, 2)
        self.mainLayout.addWidget(self.stripWidthLabel, 1, 0)
        self.mainLayout.addWidget(self.stripWidthSpin, 1, 2)
        self.mainLayout.addWidget(self.stripIterLabel, 2, 0)
        self.mainLayout.addWidget(self.stripIterValue, 2, 2)
        self.mainLayout.addWidget(self.snipWidthLabel, 3, 0)
        self.mainLayout.addWidget(self.snipWidthSpin, 3, 2)
        self.mainLayout.addWidget(self.smoothingFlagCheck, 4, 0)
        self.mainLayout.addWidget(self.smoothingSpin, 4, 2)
        self.mainLayout.addWidget(self.anchorsGroup, 5, 0, 1, 4)

        # Initialize interface -----------------------------------------------
        self._setAlgorithm("strip")
        self.smoothingFlagCheck.setChecked(False)
        self._smoothingToggled(is_checked=False)
        self.anchorsFlagCheck.setChecked(False)
        self._anchorsToggled(is_checked=False)

    def _algorithmComboActivated(self, algorithm_index):
        self._setAlgorithm("strip" if algorithm_index == 0 else "snip")

    def _setAlgorithm(self, algorithm):
        """Enable/disable snip and snip input widgets, depending on the
        chosen algorithm.
        :param algorithm: "snip" or "strip"
        """
        if algorithm not in ["strip", "snip"]:
            raise ValueError(
                    "Unknown background filter algorithm %s" % algorithm)

        self.algorithm = algorithm
        self.stripWidthSpin.setEnabled(algorithm == "strip")
        self.stripIterValue.setEnabled(algorithm == "strip")
        self.snipWidthSpin.setEnabled(algorithm == "snip")

    def _smoothingToggled(self, is_checked):
        """Enable/disable smoothing input widgets, emit dictionary"""
        self.smoothingSpin.setEnabled(is_checked)
        self._emitSignal()

    def _anchorsToggled(self, is_checked):
        """Enable/disable all spin widgets defining anchor X coordinates,
        emit signal.
        """
        for anchor_spin in self.anchorsList:
            anchor_spin.setEnabled(is_checked)
        self._emitSignal()

    def setParameters(self, ddict):
        """Set values for all input widgets.

        :param dict ddict: Input dictionary, must have the same
            keys as the dictionary output by :meth:`getParameters`
        """
        if "algorithm" in ddict:
            self._setAlgorithm(ddict["algorithm"])

        if "SnipWidth" in ddict:
            self.snipWidthSpin.setValue(int(ddict["SnipWidth"]))

        if "StripWidth" in ddict:
            self.stripWidthSpin.setValue(int(ddict["StripWidth"]))

        if "StripIterations" in ddict:
            self.stripIterValue.setText("%d" % int(ddict["StripIterations"]))

        if "SmoothingFlag" in ddict:
            self.smoothingFlagCheck.setChecked(bool(ddict["SmoothingFlag"]))

        if "SmoothingWidth" in ddict:
            self.smoothingSpin.setValue(int(ddict["SmoothingWidth"]))

        if "AnchorsFlag" in ddict:
            self.anchorsFlagCheck.setChecked(bool(ddict["AnchorsFlag"]))

        if "AnchorsList" in ddict:
            anchorslist = ddict["AnchorsList"]
            if anchorslist in [None, 'None']:
                anchorslist = []
            for spin in self.anchorsList:
                spin.setValue(0)

            i = 0
            for value in anchorslist:
                self.anchorsList[i].setValue(int(value))
                i += 1

    def getParameters(self):
        """Return dictionary of parameters defined in the GUI

        The returned dictionary contains following values:

            - *algorithm*: *"strip"* or *"snip"*
            - *StripWidth*: width of strip iterator
            - *StripIterations*: number of iterations
            - *StripThreshold*: curvature parameter (currently fixed to 1.0)
            - *SnipWidth*: width of snip algorithm
            - *SmoothingFlag*: flag to enable/disable smoothing
            - *SmoothingWidth*: width of Savitsky-Golay smoothing filter
            - *AnchorsFlag*: flag to enable/disable anchors
            - *AnchorsList*: list of anchors (X coordinates of fixed values)
        """
        stripitertext = self.stripIterValue.text()
        stripiter = int(stripitertext) if len(stripitertext) else 0

        return {"algorithm": self.algorithm,
                "StripThreshold": 1.0,
                "SnipWidth": self.snipWidthSpin.value(),
                "StripIterations": stripiter,
                "StripWidth": self.stripWidthSpin.value(),
                "SmoothingFlag": self.smoothingFlagCheck.isChecked(),
                "SmoothingWidth": self.smoothingSpin.value(),
                "AnchorsFlag": self.anchorsFlagCheck.isChecked(),
                "AnchorsList": [spin.value() for spin in self.anchorsList]}

    def _emitSignal(self, dummy=None):
        self.sigBackgroundParamWidgetSignal.emit(
            {'event': 'ParametersChanged',
             'parameters': self.getParameters()})
Пример #12
0
class PlotArea(qt.QMdiArea):

    changed = qt.pyqtSignal()

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

        #: Context menu
        self.setContextMenuPolicy(qt.Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.showContextMenu)

        #: Set the order of the subwindows returned by subWindowList.
        self.setActivationOrder(qt.QMdiArea.CreationOrder)

        self.setWindowTitle('PlotArea')
        self.setMinimumSize(960, 960)
        self.setGeometry(0, 0, 1280, 1024)

    def getPlotWindow(self, index):
        """get the PlotWindow widget object for a given index"""
        return self.subWindowList()[index].widget()

    def plotWindows(self):
        widgets = WeakList()
        for subWindow in self.subWindowList():
            widgets.append(subWindow.widget())
        return widgets

    def showContextMenu(self, position):
        menu = qt.QMenu('Plot Area Menu', self)

        action = qt.QAction('Add Plot1D', self, triggered=self.addPlot1D)
        menu.addAction(action)

        action = qt.QAction('Add Plot2D Window',
                            self,
                            triggered=self.addPlot2D)
        menu.addAction(action)

        menu.addSeparator()

        action = qt.QAction('Cascade Windows',
                            self,
                            triggered=self.cascadeSubWindows)
        menu.addAction(action)

        action = qt.QAction('Tile Windows',
                            self,
                            triggered=self.tileSubWindows)
        menu.addAction(action)

        menu.exec_(self.mapToGlobal(position))

    def addPlot1D(self, title=None):
        return self.addPlotWindow(plotType='1D', title=title)

    def addPlot2D(self, title=None):
        return self.addPlotWindow(plotType='2D', title=title)

    def addPlotWindow(self, *args, plotType='1D', title=None):
        """add a plot window in the mdi Area

        Parameters
        ----------
        plotType : str
            type of plot:
                '1D' (= curves)
                '2D' (= images),
        """
        subWindow = MdiSubWindow(parent=self)
        if plotType == '2D':
            plotWindow = Plot2D(parent=subWindow, title=title)
        else:
            plotWindow = Plot1D(parent=subWindow, title=title)
        plotWindow.setIndex(len(self.plotWindows()))
        subWindow.setWidget(plotWindow)
        subWindow.show()
        self.changed.emit()
        return plotWindow

    def renumberPlotWindows(self):
        for index, plotWindow in enumerate(self.plotWindows()):
            plotWindow.setIndex(index)
Пример #13
0
 def onClose(self):
     self.app.lastWindowClosed.connect(qt.pyqtSignal(quit()))
Пример #14
0
class FileSystemWithHdf5Model(ModelBase):
    resetRootPath = qt.pyqtSignal(qt.QModelIndex)
    requestSaveExpand = qt.pyqtSignal()
    requestRestoreExpand = qt.pyqtSignal()

    def __init__(self, transformNode=None, parent=None):
        super(FileSystemWithHdf5Model, self).__init__(parent)
        self.transformNode = transformNode
        if ModelBase == qt.QFileSystemModel:
            self.fsModel = self
        elif ModelBase == qt.QAbstractItemModel:
            self.fsModel = qt.QFileSystemModel(self)
        self.h5Model = MyHdf5TreeModel(self)
        self.h5ModelRoot = self.h5Model.nodeFromIndex(qt.QModelIndex())
        if useProxyModel:
            self.h5ProxyModel = NexusSortFilterProxyModel(self)
            self.h5ProxyModel.setSourceModel(self.h5Model)
            self.h5ProxyModel.getNxIcon = \
                self.h5ProxyModel._NexusSortFilterProxyModel__getNxIcon
        self.h5Model.setFileMoveEnabled(False)
        # this won't handle renames, deletes, and moves:
        self.h5Model.nodesH5 = []
        self.nodesHead = []
        self.nodesNoHead = []
        self._roothPath = None
        self.layoutAboutToBeChanged.connect(self._resetModel)
        self.layoutChanged.connect(self._restoreExpand)

    def _resetModel(self):
        """Without reset it crashes if hdf5 nodes are expanded."""
        self.requestSaveExpand.emit()
        self.beginResetModel()
        self.endResetModel()
        if self._roothPath is not None:
            rtIndex = self.setRootPath(self._roothPath)
            self.resetRootPath.emit(rtIndex)

    def _restoreExpand(self):
        self.requestRestoreExpand.emit()

    def headerData(self, section, orientation, role):
        if section == 3:
            return "Date Modified / HDF5 Value"
        if self.fsModel is self:
            return super(FileSystemWithHdf5Model, self).headerData(
                section, orientation, role)
        return self.fsModel.headerData(section, orientation, role)

    def flags(self, index):
        if not index.isValid():
            return qt.Qt.NoItemFlags
        res = super(FileSystemWithHdf5Model, self).flags(index) | \
            qt.Qt.ItemIsEnabled | qt.Qt.ItemIsSelectable | \
            qt.Qt.ItemIsDragEnabled
        return res

    def _mapIndex(self, indexFrom, modelFrom, modelTo):
        if modelFrom is modelTo:
            return indexFrom
        if not indexFrom.isValid():
            return qt.QModelIndex()
        assert indexFrom.model() is modelFrom
        pntr = indexFrom.internalPointer()
        return modelTo.createIndex(indexFrom.row(), indexFrom.column(), pntr)

    def mapFromFS(self, indexFS):
        return self._mapIndex(indexFS, self.fsModel, self)

    def mapFromH5(self, indexH5):
        return self._mapIndex(indexH5, self.h5Model, self)

    def mapToFS(self, index):
        return self._mapIndex(index, self, self.fsModel)

    def mapToH5(self, index):
        return self._mapIndex(index, self, self.h5Model)

    def setRootPath(self, dirname):
        self._roothPath = dirname
        if self.fsModel is self:
            return super(FileSystemWithHdf5Model, self).setRootPath(dirname)
        return self.mapFromFS(self.fsModel.setRootPath(dirname))

    def nodeType(self, index):
        if not index.isValid():
            return NODE_FS
        id0 = index.internalId()
        if id0 in self.h5Model.nodesH5:
            return NODE_HDF5
        elif id0 in self.nodesHead:
            return NODE_HDF5_HEAD
        else:
            return NODE_FS

    def mapFStoH5(self, indexFS):
        index = self.mapFromFS(indexFS)
        if index.internalId() in self.nodesHead:
            fileInfo = self.fsModel.fileInfo(indexFS)
            filename = fileInfo.filePath()
            hdf5Obj = self.h5Model.hdf5ObjFromFileName(filename)
            if hdf5Obj is not None:
                return self.h5Model.findIndex(hdf5Obj)
        return qt.QModelIndex()

    def mapH5toFS(self, indexH5):
        parentH5 = self.h5Model.parent(indexH5)
        if not parentH5.isValid():
            hdf5Obj = self.h5Model.nodeFromIndex(indexH5)
            return self.indexFileName(hdf5Obj.obj.file.filename)
        else:
            return qt.QModelIndex()

    def rowCount(self, parent=qt.QModelIndex()):
        nodeType = self.nodeType(parent)
        if nodeType == NODE_FS:
            if self.fsModel is self:
                return super(FileSystemWithHdf5Model, self).rowCount(parent)
            return self.fsModel.rowCount(self.mapToFS(parent))
        elif nodeType == NODE_HDF5_HEAD:
            return self.h5Model.rowCount(self.mapFStoH5(self.mapToFS(parent)))
        elif nodeType == NODE_HDF5:
            return self.h5Model.rowCount(self.mapToH5(parent))
        else:
            raise ValueError('unknown node type in `rowCount`')

    def columnCount(self, parent=qt.QModelIndex()):
        nodeType = self.nodeType(parent)
        if nodeType == NODE_FS:
            if self.fsModel is self:
                return super(FileSystemWithHdf5Model, self).columnCount(parent)
            return self.fsModel.columnCount(self.mapToFS(parent))
        elif nodeType == NODE_HDF5_HEAD:
            return self.h5Model.columnCount(
                self.mapFStoH5(self.mapToFS(parent)))
        elif nodeType == NODE_HDF5:
            return self.h5Model.columnCount(self.mapToH5(parent))
        else:
            raise ValueError('unknown node type in `columnCount`')

    def hasChildren(self, parent):
        nodeType = self.nodeType(parent)
        if nodeType == NODE_FS:
            if self.fsModel is self:
                return super(FileSystemWithHdf5Model, self).hasChildren(parent)
            return self.fsModel.hasChildren(self.mapToFS(parent))
        elif nodeType == NODE_HDF5_HEAD:
            return True
        if nodeType == NODE_HDF5:
            return self.h5Model.hasChildren(self.mapToH5(parent))
        else:
            raise ValueError('unknown node type in `hasChildren`')

    def canFetchMore(self, parent):
        nodeType = self.nodeType(parent)
        if nodeType == NODE_FS:
            if self.fsModel is self:
                return \
                    super(FileSystemWithHdf5Model, self).canFetchMore(parent)
            return self.fsModel.canFetchMore(self.mapToFS(parent))
        elif nodeType == NODE_HDF5_HEAD:
            return self.h5Model.canFetchMore(
                self.mapFStoH5(self.mapToFS(parent)))
        if nodeType == NODE_HDF5:
            return self.h5Model.canFetchMore(self.mapToH5(parent))
        else:
            raise ValueError('unknown node type in `canFetchMore`')

    def fetchMore(self, parent):
        nodeType = self.nodeType(parent)
        if nodeType == NODE_FS:
            if self.fsModel is self:
                return \
                    super(FileSystemWithHdf5Model, self).fetchMore(parent)
            return self.fsModel.fetchMore(self.mapToFS(parent))
        elif nodeType == NODE_HDF5_HEAD:
            return self.h5Model.fetchMore(
                self.mapFStoH5(self.mapToFS(parent)))
        if nodeType == NODE_HDF5:
            return self.h5Model.fetchMore(self.mapToH5(parent))
        else:
            raise ValueError('unknown node type in `fetchMore`')

    def canLoadColDataset(self, indexFS):
        return True

    def interpretArrayFormula(self, dataStr, treeObj, kind):
        """Returnes a list of (expr, d[xx]-styled-expr, data-keys, shape).
        *dataStr* may have several expressions with the syntax of a list or a
        tuple or just one expression if it is a simple string.
        """
        try:
            # to expand list comprehension or string expressions
            dataStr = str(eval(dataStr))
        except:  # noqa
            pass

        if ((dataStr.startswith('[') and dataStr.endswith(']')) or
                (dataStr.startswith('(') and dataStr.endswith(')'))):
            dataStr = dataStr[1:-1]
        dataStr = [s.strip() for s in dataStr.split(',')]
        out = []
        for colStr in dataStr:
            keys = re.findall(r'\[(.*?)\]', colStr)
            if len(keys) == 0:
                keys = [colStr]
                colStrD = 'd[r"{0}"]'.format(colStr)
            else:
                colStrD = colStr
                # remove outer quotes:
                keys = [k[1:-1] if k.startswith(('"', "'")) else k
                        for k in keys]
            d = {}
            if kind == 'h5':
                for k in keys:
                    if k.startswith("silx:"):
                        tryInd = self.indexFromH5Path(k)
                        if tryInd == qt.QModelIndex():  # doesn't exist
                            return
                        shape = self.h5Model.nodeFromIndex(tryInd).obj.shape
                    else:
                        shape = self.hasH5ChildPath(treeObj, k)
                    if shape is None:
                        return
                    d[k] = np.ones([2 for dim in shape])
            else:  # arrays from column file
                for k in keys:
                    kl = k.lower()
                    if "col" in kl:
                        kn = int(kl[kl.find('col')+3])
                    else:
                        kn = int(k)
                    d[k] = treeObj[kn]
                    d[kn] = d[k]
                    locals()[k] = k
                shape = 2,
            try:
                eval(colStrD)
                out.append((colStr, colStrD, keys, shape))
            except:  # noqa
                return
        return out

    def hasH5ChildPath(self, node, path):
        pathInH5 = '/'.join((node.obj.name, path))
        try:
            test = node.obj[pathInH5]  # test for existence
            return test.shape
        except KeyError:
            return

    def tryLoadColDataset(self, indexFS):
        if not indexFS.isValid():
            return
        cf = self.transformNode.widget.columnFormat
        df = cf.getDataFormat(needHeader=True)
        if not df:
            return
        fileInfo = self.fsModel.fileInfo(indexFS)
        fname = fileInfo.filePath()
        lres = []
        try:
            cdf = df.copy()
            cco.get_header(fname, cdf)
            cdf['skip_header'] = cdf.pop('skiprows', 0)
            dataS = cdf.pop('dataSource', [])
            cdf.pop('conversionFactors', [])
            with np.warnings.catch_warnings():
                np.warnings.simplefilter("ignore")
                arrs = np.genfromtxt(fname, unpack=True, max_rows=2, **cdf)
            if len(arrs) == 0:
                return

            for data in dataS:
                if len(data) == 0:
                    return
                colEval = self.interpretArrayFormula(data, arrs, 'col')
                if colEval is None:
                    return
                lres.append(colEval)
        except Exception as e:
            # print('tryLoadColDataset:', e)
            return
        return lres, df

    def tryLoadHDF5Dataset(self, indexH5):
        if not indexH5.isValid():
            return
        nodeH5 = self.h5Model.nodeFromIndex(indexH5)
        if not nodeH5.isGroupObj():
            return
        cf = self.transformNode.widget.columnFormat
        df = cf.getDataFormat(needHeader=False)
        if not df:
            return

        lres = []
        try:
            datas = df.get('dataSource', [])  # from dataEdits
            slices = df.get('slices', [])  # from dataEdits
            for data, slc, nd in zip(
                    datas, slices, self.transformNode.getPropList('ndim')):
                if len(data) == 0:
                    return
                colEval = self.interpretArrayFormula(data, nodeH5, 'h5')
                if colEval is None:
                    return
                if nd:
                    if len(colEval[0][3]) < nd:
                        return
                    elif len(colEval[0][3]) == nd:
                        pass
                    elif len(colEval[0][3]) > nd:
                        if 'axis' in slc or 'sum' in slc:  # sum axes
                            sumlist = slc[slc.find('=')+1:].split(',')
                            if len(colEval[0][3]) - len(sumlist) != nd:
                                return
                        else:
                            slicelist = [i for i in slc.split(',') if ':' in i]
                            if len(slicelist) != nd:
                                return
                lres.append(colEval)
        except:  # noqa
            return
        return lres, df

    def tryLoadDataset(self, index):
        if not index.isValid():
            return
        nodeType = self.nodeType(index)
        if nodeType == NODE_FS:
            indexFS = self.mapToFS(index)
            fileInfo = self.fsModel.fileInfo(indexFS)
            if not is_text_file(fileInfo.filePath()):
                return
            return self.tryLoadColDataset(indexFS)
        if nodeType == NODE_HDF5_HEAD:
            indexFS = self.mapToFS(index)
            indexH5 = self.mapFStoH5(indexFS)
        elif nodeType == NODE_HDF5:
            indexH5 = self.mapToH5(index)
        return self.tryLoadHDF5Dataset(indexH5)

    def getHDF5ArrayPath(self, index):
        if not index.isValid():
            return
        nodeType = self.nodeType(index)
        if nodeType not in (NODE_HDF5_HEAD, NODE_HDF5):
            return
        if nodeType == NODE_HDF5_HEAD:
            indexFS = self.mapToFS(index)
            indexH5 = self.mapFStoH5(indexFS)
        elif nodeType == NODE_HDF5:
            indexH5 = self.mapToH5(index)

        if not indexH5.isValid():
            return
        class_ = self.h5Model.nodeFromIndex(indexH5).h5Class
        if class_ == silx_io.utils.H5Type.DATASET:
            obj = self.h5Model.nodeFromIndex(indexH5).obj
            try:
                if (len(obj.shape) >= 1):
                    return obj.name
            except:  # noqa
                pass
        return

    def getHDF5FullPath(self, index):
        if not index.isValid():
            return
        nodeType = self.nodeType(index)
        if nodeType not in (NODE_HDF5_HEAD, NODE_HDF5):
            return
        if nodeType == NODE_HDF5_HEAD:
            indexFS = self.mapToFS(index)
            indexH5 = self.mapFStoH5(indexFS)
        elif nodeType == NODE_HDF5:
            indexH5 = self.mapToH5(index)

        return 'silx:' + '::'.join(
            (self.h5Model.nodeFromIndex(indexH5).obj.file.filename,
             self.h5Model.nodeFromIndex(indexH5).obj.name))

    def data(self, index, role=qt.Qt.DisplayRole):
        if not index.isValid():
            return
        if role == LOAD_DATASET_ROLE:
            return self.tryLoadDataset(index)
        if role == USE_HDF5_ARRAY_ROLE:
            return self.getHDF5ArrayPath(index)

        nodeType = self.nodeType(index)
        if nodeType == NODE_FS:
            indexFS = self.mapToFS(index)
            if role == LOAD_ITEM_PATH_ROLE:
                return self.filePath(indexFS)
            if role == qt.Qt.ForegroundRole:
                fileInfo = self.fsModel.fileInfo(index)
                if is_text_file(fileInfo.filePath()):
                    return qt.QColor(gco.COLOR_FS_COLUMN_FILE)
            if self.fsModel is self:
                return super(FileSystemWithHdf5Model, self).data(index, role)
            return self.fsModel.data(indexFS, role)
        elif nodeType == NODE_HDF5:
            if role == LOAD_ITEM_PATH_ROLE:
                return self.getHDF5FullPath(index)
            indexH5 = self.mapToH5(index)
            if useProxyModel:
                return self.h5ProxyModel.data(
                    self.h5ProxyModel.mapFromSource(indexH5), role)
            else:
                return self.h5Model.data(indexH5, role)
        elif nodeType == NODE_HDF5_HEAD:
            indexFS = self.mapToFS(index)
            fileInfo = self.fsModel.fileInfo(indexFS)
            if role == qt.Qt.ToolTipRole:
                indexH5 = self.mapFStoH5(indexFS)
                return self.h5Model.data(indexH5, role)
            elif role == qt.Qt.ForegroundRole:
                return qt.QColor(gco.COLOR_HDF5_HEAD)
            elif role == qt.Qt.DecorationRole:
                if useProxyModel and \
                        index.column() == self.h5Model.NAME_COLUMN:
                    ic = super(FileSystemWithHdf5Model, self).data(index, role)
                    return self.h5ProxyModel.getNxIcon(ic)
            if self.fsModel is self:
                return super(FileSystemWithHdf5Model, self).data(index, role)
            return self.fsModel.data(indexFS, role)
        else:
            raise ValueError('unknown node type in `data`')

    def parent(self, index):
        if not index.isValid():
            return qt.QModelIndex()
        nodeType = self.nodeType(index)
        if nodeType == NODE_HDF5:
            indexH5 = self.mapToH5(index)
            parentH5 = self.h5Model.parent(indexH5)
            if not parentH5.isValid():
                return qt.QModelIndex()
            grandparentH5 = self.h5Model.parent(parentH5)
            if not grandparentH5.isValid():
                hdf5Obj = self.h5Model.nodeFromIndex(parentH5)
                return self.indexFileName(hdf5Obj.obj.file.filename)
            return self.mapFromH5(parentH5)
        else:
            if self.fsModel is self:
                return super(FileSystemWithHdf5Model, self).parent(index)
            pind = self.mapFromFS(self.fsModel.parent(self.mapToFS(index)))
            return pind

    def index(self, row, column, parent=qt.QModelIndex()):
        if not self.hasIndex(row, column, parent):
            return qt.QModelIndex()
        parentType = self.nodeType(parent)
        if parentType in (NODE_HDF5, NODE_HDF5_HEAD):
            if False:  # useProxyModel: !!! doesn't help in sorting !!!
                if parentType == NODE_HDF5:
                    parentProxyH5 = self.h5ProxyModel.mapFromSource(
                        self.mapToH5(parent))
                elif parentType == NODE_HDF5_HEAD:
                    parentProxyH5 = self.h5ProxyModel.mapFromSource(
                        self.mapFStoH5(self.mapToFS(parent)))
                indexH5 = self.h5ProxyModel.index(row, column, parentProxyH5)
                index = self.mapFromH5(self.h5ProxyModel.mapToSource(indexH5))
            else:
                if parentType == NODE_HDF5:
                    parentH5 = self.mapToH5(parent)
                elif parentType == NODE_HDF5_HEAD:
                    parentH5 = self.mapFStoH5(self.mapToFS(parent))
                indexH5 = self.h5Model.index(row, column, parentH5)
                index = self.mapFromH5(indexH5)

            if indexH5.internalId() not in self.h5Model.nodesH5:
                self.h5Model.nodesH5.append(indexH5.internalId())
            return index

        if self.fsModel is self:
            indexFS = super(FileSystemWithHdf5Model, self).index(
                row, column, parent)
        else:
            indexFS = self.fsModel.index(row, column, self.mapToFS(parent))
        fileInfo = self.fsModel.fileInfo(indexFS)
        filename = fileInfo.filePath()
        index = self.mapFromFS(indexFS)
        if (index.internalId() not in self.nodesHead and
                index.internalId() not in self.nodesNoHead):
            try:
                if os.path.splitext(filename)[1] not in \
                        silx_io.utils.NEXUS_HDF5_EXT:
                    # = [".h5", ".nx5", ".nxs",  ".hdf", ".hdf5", ".cxi"]
                    raise IOError()
                with silx_io.open(filename) as h5f:
                    if not silx_io.is_file(h5f):
                        raise IOError()
                    self.beginInsertRows(parent, row, -1)
                    self.h5Model.appendFile(filename)
                    # self.h5Model.insertFileAsync(filename)  # not any better
                    self.endInsertRows()
                    self.nodesHead.append(index.internalId())
                    self.layoutChanged.emit()
            except IOError:
                self.nodesNoHead.append(index.internalId())
        return index

    def synchronizeHdf5Index(self, index):
        h5pyObject = self.data(index, role=H5PY_OBJECT_ROLE)
        if isinstance(h5pyObject, type('')):
            filename = h5pyObject
        else:
            filename = h5pyObject.file.filename
        indexFS = self.indexFileName(filename)
        indexH5 = self.mapFStoH5(indexFS)
        indexHead = self.mapFromFS(indexFS)

        h5py_object = self.h5Model.data(indexH5, role=H5PY_OBJECT_ROLE)
#        if not h5py_object.ntype is h5py.File:
#            return

        self.beginResetModel()
        self.h5Model.beginResetModel()
#        self.nodesHead.remove(indexHead.internalId())
        self.h5Model.removeH5pyObject(h5py_object)
        self.h5Model.insertFile(filename, indexH5.row())
        self.h5Model.endResetModel()
        self.endResetModel()
        return indexHead

    def indexFileName(self, fName):
        if self.fsModel is self:
            return super(FileSystemWithHdf5Model, self).index(fName)
        else:
            return self.fsModel.index(fName)

    def indexFromH5Path(self, path, fallbackToHead=False):
        fNameStart = path.find("silx:")
        if fNameStart < 0:
            return qt.QModelIndex()
        fNameEnd = path.find("::/")
        if fNameEnd < 0:
            return qt.QModelIndex()
        fnameH5 = path[fNameStart+5:fNameEnd]
        fnameH5sub = path[fNameEnd+3:]

        headIndexFS = self.indexFileName(fnameH5)
        headIndexH5 = self.mapFStoH5(headIndexFS)
        if headIndexH5 == qt.QModelIndex():
            if self.canFetchMore(headIndexFS):
                self.fetchMore(headIndexFS)
            headIndexH5 = self.mapFStoH5(headIndexFS)
            return headIndexFS if fallbackToHead else qt.QModelIndex()
        indexH5 = self.h5Model.indexFromPath(headIndexH5, fnameH5sub)
        return self.mapFromH5(indexH5)

    def mimeData(self, indexes, checkValidity=True):
        indexes0 = [index for index in indexes if index.column() == 0]
        nodeTypes = [self.nodeType(index) for index in indexes0]
        if nodeTypes.count(nodeTypes[0]) != len(nodeTypes):  # not all equal
            return
        if nodeTypes[0] in (NODE_HDF5_HEAD, NODE_FS):
            indexesFS = []
            for index in indexes0:
                indexFS = self.mapToFS(index)
                if checkValidity:
                    if self.tryLoadColDataset(indexFS) is None:
                        return
                indexesFS.append(indexFS)
            if ModelBase == qt.QFileSystemModel:
                return super(FileSystemWithHdf5Model, self).mimeData(indexesFS)
            else:
                return self.fsModel.mimeData(indexesFS)
        elif nodeTypes[0] == NODE_HDF5:
            paths = []
            for index in indexes0:
                indexH5 = self.mapToH5(index)
                if checkValidity:
                    if self.tryLoadHDF5Dataset(indexH5) is None:
                        return
                try:
                    path = 'silx:' + '::'.join(
                        (self.h5Model.nodeFromIndex(indexH5).obj.file.filename,
                         self.h5Model.nodeFromIndex(indexH5).obj.name))
                    paths.append(path)
                except:  # noqa
                    return
            mimedata = qt.QMimeData()
            mimedata.setData(cco.MIME_TYPE_HDF5, pickle.dumps(paths))
            return mimedata
Пример #15
0
class SaveProjectDlg(qt.QFileDialog):
    ready = qt.pyqtSignal(list)

    def __init__(self, parent=None, dirname=''):
        super(SaveProjectDlg, self).__init__(parent=parent,
                                             caption='Save project',
                                             directory=dirname)
        self.setOption(qt.QFileDialog.DontUseNativeDialog, True)
        self.setAcceptMode(qt.QFileDialog.AcceptSave)
        self.setFileMode(qt.QFileDialog.AnyFile)
        self.setViewMode(qt.QFileDialog.Detail)
        self.setNameFilter("ParSeq Project File (*.pspj)")

        selNames = [it.alias for it in csi.selectedItems]
        combinedNames = cco.combine_names(selNames)
        sellen = len(csi.selectedItems)
        exportStr = 'export data of {0} selected item{1}: {2}'.format(
            sellen, 's' if sellen > 1 else '', combinedNames) if sellen < 4 \
            else 'export data of {0} selected items'.format(sellen)
        self.saveData = qt.QGroupBox(exportStr, self)
        self.saveData.setCheckable(True)
        self.saveData.setChecked(True)

        layoutC = qt.QHBoxLayout()
        layoutC.setContentsMargins(0, 2, 0, 0)

        saveDataFrom = qt.QGroupBox('from nodes', self)
        layoutF = qt.QVBoxLayout()
        layoutF.setContentsMargins(4, 4, 4, 4)
        self.saveNodeCBs = []
        for i, (name, node) in enumerate(csi.nodes.items()):
            tabName = u'{0} \u2013 {1}'.format(i + 1, name)
            nodeCB = qt.QCheckBox(tabName, self)
            self.saveNodeCBs.append(nodeCB)
            layoutF.addWidget(nodeCB)
        nodeCB.setChecked(True)
        saveDataFrom.setLayout(layoutF)
        layoutC.addWidget(saveDataFrom)

        saveDataAs = qt.QGroupBox('as', self)
        layoutA = qt.QVBoxLayout()
        layoutA.setContentsMargins(4, 4, 4, 4)
        self.saveAsCBs = []
        for i, dtype in enumerate(ftypes):
            asCB = qt.QCheckBox(dtype, self)
            self.saveAsCBs.append(asCB)
            layoutA.addWidget(asCB)
        self.saveAsCBs[0].setChecked(True)
        saveDataAs.setLayout(layoutA)
        layoutC.addWidget(saveDataAs)

        layoutP = qt.QVBoxLayout()
        self.scriptCBmpl = qt.QCheckBox(
            'save a matplotlib plotting script\nfor the exported data', self)
        layoutP.addWidget(self.scriptCBmpl, alignment=qt.Qt.AlignTop)
        self.scriptCBsilx = qt.QCheckBox(
            'save a silx plotting script\nfor the exported data', self)
        layoutP.addWidget(self.scriptCBsilx, alignment=qt.Qt.AlignTop)
        layoutP.addStretch()
        layoutC.addLayout(layoutP)

        self.saveData.setLayout(layoutC)
        self.layout().addWidget(self.saveData,
                                self.layout().rowCount(), 0, 1, 2)
        self.layout().setRowStretch(self.layout().rowCount(), 0)

        self.finished.connect(self.onFinish)

    def onFinish(self, result):
        if not result:
            return
        resFile = self.selectedFiles()
        saveData = self.saveData.isChecked()
        if not saveData:
            self.ready.emit([resFile])
            return
        saveNodes = [node.isChecked() for node in self.saveNodeCBs]
        asTypes = [
            ext for ftype, ext in zip(self.saveAsCBs, fexts)
            if ftype.isChecked()
        ]
        saveScriptMpl = self.scriptCBmpl.isChecked()
        saveScriptSilx = self.scriptCBsilx.isChecked()
        self.ready.emit(
            [resFile, saveNodes, asTypes, saveScriptMpl, saveScriptSilx])
Пример #16
0
class HorizontalSliderWithBrowser(qt.QAbstractSlider):
    """
    Slider widget combining a :class:`QSlider` and a :class:`FrameBrowser`.

    .. image:: img/HorizontalSliderWithBrowser.png

    The data model is an integer within a range.

    The default value is the default :class:`QSlider` value (0),
    and the default range is the default QSlider range (0 -- 99)

    The signal emitted when the value is changed is the usual QAbstractSlider
    signal :attr:`valueChanged`. The signal carries the value (as an integer).

    :param QWidget parent: Optional parent widget
    """
    sigIndexChanged = qt.pyqtSignal(object)

    def __init__(self, parent=None):
        qt.QAbstractSlider.__init__(self, parent)
        self.setOrientation(qt.Qt.Horizontal)

        self.mainLayout = qt.QHBoxLayout(self)
        self.mainLayout.setContentsMargins(0, 0, 0, 0)
        self.mainLayout.setSpacing(2)

        self._slider = qt.QSlider(self)
        self._slider.setOrientation(qt.Qt.Horizontal)

        self._browser = FrameBrowser(self)

        self.mainLayout.addWidget(self._slider, 1)
        self.mainLayout.addWidget(self._browser)

        self._slider.valueChanged[int].connect(self._sliderSlot)
        self._browser.sigIndexChanged.connect(self._browserSlot)

    def lineEdit(self):
        """Returns the line edit provided by this widget.

        :rtype: qt.QLineEdit
        """
        return self._browser.lineEdit()

    def limitWidget(self):
        """Returns the widget displaying axes limits.

        :rtype: qt.QLabel
        """
        return self._browser.limitWidget()

    def setMinimum(self, value):
        """Set minimum value

        :param int value: Minimum value"""
        self._slider.setMinimum(value)
        maximum = self._slider.maximum()
        self._browser.setRange(value, maximum)

    def setMaximum(self, value):
        """Set maximum value

        :param int value: Maximum value
        """
        self._slider.setMaximum(value)
        minimum = self._slider.minimum()
        self._browser.setRange(minimum, value)

    def setRange(self, first, last):
        """Set minimum/maximum values

        :param int first: Minimum value
        :param int last: Maximum value"""
        self._slider.setRange(first, last)
        self._browser.setRange(first, last)

    def _sliderSlot(self, value):
        """Emit selected value when slider is activated
        """
        self._browser.setValue(value)
        self.valueChanged.emit(value)

    def _browserSlot(self, ddict):
        """Emit selected value when browser state is changed"""
        self._slider.setValue(ddict['new'])

    def setValue(self, value):
        """Set value

        :param int value: value"""
        self._slider.setValue(value)
        self._browser.setValue(value)

    def value(self):
        """Get selected value"""
        return self._slider.value()
Пример #17
0
class MainWindowParSeq(qt.QMainWindow):
    beforeTransformSignal = qt.pyqtSignal(qt.QWidget)
    afterTransformSignal = qt.pyqtSignal(qt.QWidget)
    beforeDataTransformSignal = qt.pyqtSignal(list)
    afterDataTransformSignal = qt.pyqtSignal(list)

    chars2removeMap = {ord(c): '-' for c in '/*? '}

    def __init__(self, parent=None):
        super(MainWindowParSeq, self).__init__(parent)
        selfDir = os.path.dirname(__file__)
        self.iconDir = os.path.join(selfDir, '_images')
        self.runIcon = qt.QIcon(os.path.join(self.iconDir, 'parseq.ico'))
        # self.emptyIcon = qt.QIcon(qt.QPixmap.fromImage(qt.QImage.fromData(
        #     b'<svg version="1.1" viewBox="0 0  32"'
        #     b' xmlns="http://www.w3.org/2000/svg"></svg>')))
        # self.emptyIcon = qt.QIcon()

        transformThread = qt.QThread(self)
        csi.transformer = Transformer()
        csi.transformer.moveToThread(transformThread)
        transformThread.started.connect(
            partial(self.displayStatusMessage, u'calculating…'))
        transformThread.started.connect(csi.transformer.run)
        csi.transformer.ready.connect(
            partial(self.displayStatusMessage, u'ready'))
        csi.mainWindow = self
        self.setWindowTitle(u"ParSeq  \u2014  " + csi.pipelineName)

        self.initTabs()

        # self.settings = qt.QSettings('parseq.ini', qt.QSettings.IniFormat)
        self.setWindowIcon(qt.QIcon(os.path.join(self.iconDir, 'parseq.ico')))
        self.setWindowFlags(qt.Qt.Window)

        self.statusBar = self.statusBar()
        #        self.statusBar.setStyleSheet("QStatusBar {min-height: 20;}")

        self.statusBarLeft = qt.QLabel("ready")
        self.statusBarRight = qt.QLabel("")
        self.statusBar.addWidget(self.statusBarLeft)
        self.statusBar.addPermanentWidget(self.statusBarRight)

        self.restore_perspective()

        self.initToolbar()

        self.beforeTransformSignal.connect(partial(self.updateTabStatus, 1))
        self.afterTransformSignal.connect(partial(self.updateTabStatus, 0))
        self.beforeDataTransformSignal.connect(self.updateItemView)
        self.afterDataTransformSignal.connect(self.updateItemView)

        self.dataChanged()

    def initToolbar(self):
        self.loadAction = qt.QAction(
            qt.QIcon(os.path.join(self.iconDir, "icon-load-proj.png")),
            "Load project (Ctrl+O)", self)
        self.loadAction.setShortcut('Ctrl+O')
        self.loadAction.triggered.connect(self.slotLoadProject)

        self.saveAction = qt.QAction(
            qt.QIcon(os.path.join(self.iconDir, "icon-save-proj.png")),
            "Save project, data and plots (Ctrl+S)", self)
        self.saveAction.setShortcut('Ctrl+S')
        self.saveAction.triggered.connect(self.slotSaveProject)
        self.saveAction.setEnabled(False)

        self.undoAction = qt.QAction(
            qt.QIcon(os.path.join(self.iconDir, "icon-undo.png")),
            "Undo last action (Ctrl+Z)", self)
        self.undoAction.setShortcut('Ctrl+Z')
        self.undoAction.triggered.connect(partial(self.slotUndo, -1))
        undoMenu = qt.QMenu()
        subAction = qt.QAction('group sequential changes of same parameter',
                               self)
        subAction.setCheckable(True)
        subAction.setChecked(csi.undoGrouping)
        subAction.triggered.connect(self.undoGroup)
        undoMenu.addAction(subAction)
        undoMenu.addSeparator()
        undoMenu.nHeaderActions = len(undoMenu.actions())
        self.undoAction.setMenu(undoMenu)
        menu = self.undoAction.menu()
        menu.aboutToShow.connect(partial(self.populateUndoMenu, menu))

        self.redoAction = qt.QAction(
            qt.QIcon(os.path.join(self.iconDir, "icon-redo.png")),
            "Redo last undone action (Ctrl+Shift+Z)", self)
        self.redoAction.setShortcut('Ctrl+Shift+Z')
        self.redoAction.triggered.connect(partial(self.slotRedo, -1))
        redoMenu = qt.QMenu()
        self.redoAction.setMenu(redoMenu)
        menu = self.redoAction.menu()
        menu.aboutToShow.connect(partial(self.populateRedoMenu, menu))
        self.setEnableUredoRedo()

        infoAction = qt.QAction(
            qt.QIcon(os.path.join(self.iconDir, "icon-info.png")),
            "About ParSeq…", self)
        infoAction.setShortcut('Ctrl+I')
        infoAction.triggered.connect(self.slotAbout)

        helpAction = qt.QAction(
            qt.QIcon(os.path.join(self.iconDir, "icon-help.png")), "Help…",
            self)
        helpAction.setShortcut('Ctrl+?')
        helpAction.triggered.connect(self.slotAbout)

        self.toolbar = self.addToolBar("Toolbar")
        self.toolbar.setIconSize(qt.QSize(32, 32))
        self.toolbar.addAction(self.loadAction)
        self.toolbar.addAction(self.saveAction)
        self.toolbar.addSeparator()
        self.toolbar.addAction(self.undoAction)
        self.toolbar.addAction(self.redoAction)
        self.toolbar.addSeparator()
        self.toolbar.addAction(infoAction)
        self.toolbar.addAction(helpAction)

    def initTabs(self):
        self.setTabPosition(qt.Qt.AllDockWidgetAreas, qt.QTabWidget.West)

        dockFeatures = (qt.QDockWidget.DockWidgetMovable
                        | qt.QDockWidget.DockWidgetFloatable)  # |
        #                        qt.QDockWidget.DockWidgetVerticalTitleBar)
        self.docks = []
        for i, (name, node) in enumerate(csi.nodes.items()):
            tabName = u'{0} \u2013 {1}'.format(i + 1, name)
            dock = QDockWidgetNoClose(tabName, self)
            dock.setAllowedAreas(qt.Qt.AllDockWidgetAreas)
            dock.setFeatures(dockFeatures)
            dock.setStyleSheet("QDockWidget {font: bold; font-size: " +
                               fontSize + "pt;"
                               "padding-left: 5px}")
            self.addDockWidget(qt.Qt.TopDockWidgetArea, dock)
            nodeWidget = NodeWidget(node, self)
            dock.setWidget(nodeWidget)
            if i == 0:
                dock0, node0 = dock, nodeWidget
            else:
                self.tabifyDockWidget(dock0, dock)
            # the pipeline head(s) with initially opened file tree:
            first = 1 if len(node.upstreamNodes) == 0 else 0

            try:
                last = 0 if node.widget.transformWidget.hideInitialView \
                    else 1
            except AttributeError:
                last = 1
            nodeWidget.splitter.setSizes([first, 1, 1, last])
            self.docks.append((dock, nodeWidget, tabName))
            dock.visibilityChanged.connect(
                partial(self.nodeChanged, dock, node))
            dock.topLevelChanged.connect(partial(dock.changeWindowFlags, node))
        dock0.raise_()
        node0.tree.setFocus()
        csi.currentNode = node0.node

        self.makeHelpPages()

        self.tabWiget = None
        for tab in self.findChildren(qt.QTabBar):
            if tab.tabText(0) == self.docks[0][2]:
                self.tabWiget = tab
                break
        # self.tabWiget.setStyleSheet("QTabBar::tab { font:bold };")
        self.tabWiget.setStyleSheet(
            "QTabBar::tab {width:32; padding-bottom: 8; padding-top: 8};")

        self.setTabIcons()
        # for dock in self.docks:
        #     self.updateTabStatus(0, dock[1])

    def makeHelpPages(self):
        # copy images
        impath = os.path.join(csi.appPath, 'doc', '_images')
        if os.path.exists(impath):
            dst = os.path.join(gww.CONFDIR, '_images')
            # print(dest_impath, os.path.exists(dst))
            shutil.copytree(impath, dst, dirs_exist_ok=True)

        rawTexts, rawTextNames = [], []
        for i, (name, node) in enumerate(csi.nodes.items()):
            if hasattr(node.widget.transformWidget, 'extraGUISetup'):
                node.widget.transformWidget.extraGUISetup()
            tr = node.transformIn
            if tr is None:
                continue
            if tr.widgetClass is None:
                continue
            if not tr.widgetClass.__doc__:
                continue
            rawTexts.append(textwrap.dedent(tr.widgetClass.__doc__))
            rawTextNames.append(name)

        # make help pages
        if rawTexts:
            self.sphinxThread = qt.QThread(self)
            self.sphinxWorker = gww.SphinxWorker()
            self.sphinxWorker.moveToThread(self.sphinxThread)
            self.sphinxThread.started.connect(self.sphinxWorker.render)
            self.sphinxWorker.html_ready.connect(self._on_sphinx_html_ready)
            self.sphinxWorker.prepare(rawTexts, rawTextNames)
            self.sphinxThread.start()

    def _on_sphinx_html_ready(self):
        for name, node in csi.nodes.items():
            docName = name.replace(' ', '_')
            fname = os.path.join(gww.CONFDIR, docName) + '.html'
            if not os.path.exists(fname):
                continue
            html = 'file:///' + fname
            html = re.sub('\\\\', '/', html)
            node.widget.helpFile = fname
            node.widget.help.load(qt.QUrl(html))

    def setTabIcons(self):
        for itab, node in enumerate(csi.nodes.values()):
            self.tabWiget.setTabIcon(itab, node.widget.dimIcon)

    def dataChanged(self):
        for node in csi.nodes.values():
            node.widget.tree.dataChanged()

    def selChanged(self):
        if len(csi.selectedItems) == 0:
            return
        selNames = [it.alias for it in csi.selectedItems]
        combinedNames = cco.combine_names(selNames)
        dataCount = len(csi.allLoadedItems)
        self.saveAction.setEnabled(dataCount > 0)
        sellen = len(csi.selectedItems)
        if sellen:
            self.statusBarLeft.setText('{0} selected: {1}'.format(
                sellen, combinedNames))
        else:
            self.statusBarLeft.setText('')
        self.statusBarRight.setText('{0} loaded'.format(dataCount))

    def nodeChanged(self, dock, node, visible):
        if visible:
            csi.currentNode = node

    def undoGroup(self):
        csi.undoGrouping = not csi.undoGrouping

    def populateUndoMenu(self, menu):
        # menu = self.sender()
        for action in menu.actions()[menu.nHeaderActions:]:
            menu.removeAction(action)
        for ientry, entry in reversed(list(enumerate(csi.undo))):
            text = gur.getStrRepr(entry)
            subAction = qt.QAction(
                qt.QIcon(os.path.join(self.iconDir, "icon-undo.png")), text,
                self)
            subAction.triggered.connect(partial(self.slotUndo, ientry))
            menu.addAction(subAction)

    def populateRedoMenu(self, menu):
        # menu = self.sender()
        for action in menu.actions():
            menu.removeAction(action)
        for ientry, entry in reversed(list(enumerate(csi.redo))):
            text = gur.getStrRepr(entry)
            subAction = qt.QAction(
                qt.QIcon(os.path.join(self.iconDir, "icon-redo.png")), text,
                self)
            subAction.triggered.connect(partial(self.slotRedo, ientry))
            menu.addAction(subAction)

    def slotUndo(self, ind):
        gur.upplyUndo(ind)

    def slotRedo(self, ind):
        gur.upplyRedo(ind)

    def setEnableUredoRedo(self):
        self.undoAction.setEnabled(len(csi.undo) > 0)
        self.redoAction.setEnabled(len(csi.redo) > 0)

    def slotAbout(self):
        lineDialog = AboutDialog(self)
        lineDialog.exec_()

    def slotSaveProject(self):
        dlg = SaveProjectDlg(self)
        dlg.ready.connect(self.doSaveProject)
        dlg.open()

    def doSaveProject(self, res):
        fname = res[0][0]
        if not fname.endswith('.pspj'):
            fname += '.pspj'
        try:
            with open(fname, 'w') as f:
                f.write('try')
        except OSError:
            msg = qt.QMessageBox()
            msg.setIcon(qt.QMessageBox.Critical)
            msg.critical(self, "Cannot write file",
                         "Invalid file name {0}".format(fname))
            return
        self.save_project(fname)
        for i, (name, node) in enumerate(csi.nodes.items()):
            node.widget.saveGraph(fname, i, name)
        if len(res) < 5:
            return
        saveNodes, saveTypes, saveScriptMpl, saveScriptSilx = res[1:5]
        plots, h5plots = self.save_data(fname, saveNodes, saveTypes)
        if saveScriptMpl:
            self.save_script(fname, plots, h5plots, 'mpl')
        if saveScriptSilx:
            self.save_script(fname, plots, h5plots, 'silx')

    def slotLoadProject(self):
        dlg = LoadProjectDlg(self)
        dlg.ready.connect(self.doLoadProject)
        dlg.open()

    def doLoadProject(self, res):
        fname = res[0][0]
        self.load_project(fname)

    def closeEvent(self, event):
        self.save_perspective()
        if len(csi.selectedItems) > 0:
            csi.selectedItems[0].save_transform_params()
        config.write_configs()

        for dock in self.docks:
            dock[0].deleteLater()

    def updateItemView(self, items):
        for item in items:
            ind = csi.model.indexFromItem(item)
            # nodes = [csi.currentNode]
            nodes = csi.nodes.values()
            for node in nodes:
                node.widget.tree.dataChanged(ind, ind)
            node.widget.tree.update()

    def updateTabStatus(self, state, nodeWidget):
        if self.tabWiget is None:
            return
        docks, nodeWidgets, tabNames = list(zip(*self.docks))
        i = nodeWidgets.index(nodeWidget)

        if docks[i].isFloating():
            docks[i].setFloatingTabColor(state)
        else:
            color = 'deepskyblue' if state == 1 else 'black'
            # icon = self.runIcon if state == 1 else self.emptyIcon
            for itab in range(self.tabWiget.count()):
                if self.tabWiget.tabText(itab) == tabNames[i]:
                    break
            else:
                return
            self.tabWiget.setTabTextColor(itab, qt.QColor(color))
            # self.tabWiget.setTabIcon(itab, icon)

    def displayStatusMessage(self, txt, starter=None, duration=0):
        if 'ready' in txt:
            factor, unit, ff = (1e3, 'ms',
                                '{0:.0f}') if duration < 1 else (1, 's',
                                                                 '{0:.1f}')
            ss = 'finished in ' + ff + ' {1}'
            self.statusBarLeft.setText(ss.format(duration * factor, unit))
            return
        self.statusBarLeft.setText(txt)

    def save_perspective(self, configObject=config.configGUI):
        floating = [dock.isFloating() for dock, _, _ in self.docks]
        config.put(configObject, 'Docks', 'floating', str(floating))

        if csi.currentNode is not None:
            config.put(configObject, 'Docks', 'active', csi.currentNode.name)

        geometryStr = 'maximized' if self.isMaximized() else \
            str(self.geometry().getRect())
        config.put(configObject, 'Geometry', 'mainWindow', geometryStr)
        for dock, nodeWidget, tabName in self.docks:
            if dock.isFloating():
                geometryStr = 'maximized' if dock.isMaximized() else \
                    str(dock.geometry().getRect())
            else:
                geometryStr = '()'
            config.put(configObject, 'Geometry', nodeWidget.node.name,
                       geometryStr)

        config.put(configObject, 'Undo', 'grouping', str(csi.undoGrouping))

    def restore_perspective(self, configObject=config.configGUI):
        csi.undoGrouping = config.get(configObject, 'Undo', 'grouping', True)

        floatingStates = config.get(configObject, 'Docks', 'floating',
                                    [False for node in csi.nodes])
        active = config.get(configObject, 'Docks', 'active', '')

        for nodeStr, floatingState in zip(csi.nodes, floatingStates):
            for dock, nodeWidget, tabName in self.docks:
                if nodeStr == nodeWidget.node.name:
                    dock.setFloating(floatingState)
                    break
            else:
                continue
            geometry = config.get(configObject, 'Geometry', nodeStr, ())
            if geometry == 'maximized':
                dock.showMaximized()
            elif len(geometry) == 4:
                dock.setGeometry(*geometry)
            if nodeStr == active:
                dock.raise_()
                nodeWidget.tree.setFocus()
                csi.currentNode = nodeWidget.node

        geometry = config.get(configObject, 'Geometry', 'mainWindow', ())
        if geometry == 'maximized':
            self.showMaximized()
        elif len(geometry) == 4:
            self.setGeometry(*geometry)

    def load_project(self, fname):
        configProject = config.ConfigParser()
        configProject.optionxform = str  # makes it case sensitive
        try:
            configProject.read(fname)
        except Exception:
            msg = qt.QMessageBox()
            msg.setIcon(qt.QMessageBox.Critical)
            msg.critical(self, "Cannot load project",
                         "Invalid project file {0}".format(fname))
            return
        self.restore_perspective(configProject)
        dataTree = config.get(configProject, 'Data', 'tree', [])
        os.chdir(os.path.dirname(fname))
        csi.model.importData(dataTree, configData=configProject)

    def save_project(self, fname):
        configProject = config.ConfigParser(allow_no_value=True)
        configProject.optionxform = str  # makes it case sensitive
        config.put(configProject, 'Data', 'tree', repr(csi.dataRootItem))
        dirname = os.path.dirname(fname)
        for item in csi.dataRootItem.get_items(alsoGroupHeads=True):
            item.save_to_project(configProject, dirname)
        self.save_perspective(configProject)
        with open(fname, 'w+') as cf:
            configProject.write(cf)

    def save_data(self, fname, saveNodes, saveTypes):
        if fname.endswith('.pspj'):
            fname = fname.replace('.pspj', '')

        plots = []
        if 'txt' in saveTypes:
            for iNode, ((nodeName, node), saveNode) in enumerate(
                    zip(csi.nodes.items(), saveNodes)):
                if not saveNode:
                    continue
                if node.plotDimension == 1:
                    header = [node.plotXArray] + [y for y in node.plotYArrays]
                else:
                    continue

                curves = {}
                for it in csi.selectedItems:
                    dataToSave = [getattr(it, arr) for arr in header]
                    nname = nodeName.translate(self.chars2removeMap)
                    dname = it.alias.translate(self.chars2removeMap)
                    sname = u'{0}-{1}-{2}'.format(iNode + 1, nname, dname)
                    np.savetxt(sname + '.txt.gz',
                               np.column_stack(dataToSave),
                               fmt='%.12g',
                               header=' '.join(header))
                    curves[sname] = [
                        it.alias, it.color, header, it.plotProps[node.name]
                    ]

                    for iG, aG in enumerate(node.auxArrays):
                        dataAux, headerAux = [], []
                        for yN in aG:
                            try:
                                dataAux.append(getattr(it, yN))
                            except AttributeError:
                                break
                            headerAux.append(yN)
                        if len(dataAux) == 0:
                            continue
                        sname = u'{0}-{1}-{2}-aux{3}'.format(
                            iNode + 1, nname, dname, iG)
                        np.savetxt(sname + '.txt.gz',
                                   np.column_stack(dataAux),
                                   fmt='%.12g',
                                   header=' '.join(headerAux))
                        curves[sname] = [it.alias, it.color, headerAux]
                plots.append([
                    'txt', node.name, node.plotDimension,
                    node.widget.getAxisLabels(), curves
                ])

        if 'json' in saveTypes or 'pickle' in saveTypes:
            dataToSave = {}
            snames = []
            for it in csi.selectedItems:
                dname = it.alias.translate(self.chars2removeMap)
                snames.append(dname)
                dataToSave[it] = {}
            for node, saveNode in zip(csi.nodes.values(), saveNodes):
                if not saveNode:
                    continue
                if node.plotDimension == 1:
                    header = [node.plotXArray] + [y for y in node.plotYArrays]
                elif node.plotDimension == 2:
                    header = node.plot2DArray
                elif node.plotDimension == 3:
                    header = node.plot3DArray

                curves = {}
                for it, sname in zip(csi.selectedItems, snames):
                    for aN in node.arrays:
                        dataToSave[it][aN] = getattr(it, aN).tolist()
                    for aN in [j for i in node.auxArrays for j in i]:
                        try:
                            dataToSave[it][aN] = getattr(it, aN).tolist()
                        except AttributeError:
                            continue
                    curves[sname] = [
                        it.alias, it.color, header, it.plotProps[node.name]
                    ]
                    if node.auxArrays:
                        headerAux = []
                        for aG in node.auxArrays:
                            for yN in aG:
                                if not hasattr(it, yN):
                                    break
                            else:
                                headerAux.append(aG)
                        if headerAux:
                            curves[sname].append(headerAux)
                if 'json' in saveTypes and node.plotDimension == 1:
                    plots.append([
                        'json', node.name, node.plotDimension,
                        node.widget.getAxisLabels(), curves
                    ])
                if 'pickle' in saveTypes:
                    plots.append([
                        'pickle', node.name, node.plotDimension,
                        node.widget.getAxisLabels(), curves
                    ])

            for it, sname in zip(csi.selectedItems, snames):
                if 'json' in saveTypes and node.plotDimension == 1:
                    with open(sname + '.json', 'w') as f:
                        json.dump(dataToSave[it], f)
                if 'pickle' in saveTypes:
                    with open(sname + '.pickle', 'wb') as f:
                        pickle.dump(dataToSave[it], f)

        h5plots = []
        if 'h5' in saveTypes:
            dataToSave = {}
            snames = []
            for it in csi.selectedItems:
                dname = it.alias.translate(self.chars2removeMap)
                snames.append('data/' + dname)
                dataToSave[it] = {}
            for node, saveNode in zip(csi.nodes.values(), saveNodes):
                if not saveNode:
                    continue
                if node.plotDimension == 1:
                    header = [node.plotXArray] + [y for y in node.plotYArrays]
                elif node.plotDimension == 2:
                    header = node.plot2DArray
                elif node.plotDimension == 3:
                    header = node.plot3DArray

                curves = {}
                for it, sname in zip(csi.selectedItems, snames):
                    for aN in node.arrays:
                        dataToSave[it][aN] = getattr(it, aN)
                    for aN in [j for i in node.auxArrays for j in i]:
                        try:
                            dataToSave[it][aN] = getattr(it, aN)
                        except AttributeError:
                            continue
                    curves[sname] = [
                        it.alias, it.color, header, it.plotProps[node.name]
                    ]
                    if node.auxArrays:
                        headerAux = []
                        for aG in node.auxArrays:
                            for yN in aG:
                                if not hasattr(it, yN):
                                    break
                            else:
                                headerAux.append(aG)
                        if headerAux:
                            curves[sname].append(headerAux)
                h5plots.append([
                    node.name, node.plotDimension,
                    node.widget.getAxisLabels(), curves
                ])

            with h5py.File(fname + '.h5', 'w') as f:
                dataGrp = f.create_group('data')
                plotsGrp = f.create_group('plots')
                for it in csi.selectedItems:
                    dname = it.alias.translate(self.chars2removeMap)
                    if dname in f:
                        continue
                    grp = dataGrp.create_group(dname)
                    for aN in dataToSave[it]:
                        if aN in grp:
                            continue
                        com = None if np.isscalar(dataToSave[it][aN]) else\
                            'gzip'
                        grp.create_dataset(aN,
                                           data=dataToSave[it][aN],
                                           compression=com)
                    grp.create_dataset('transformParams',
                                       data=str(it.transformParams))
                for plot in h5plots:
                    grp = plotsGrp.create_group(plot[0])
                    grp.create_dataset('ndim', data=plot[1])
                    grp.create_dataset('axes', data=str(plot[2]))
                    grp.create_dataset('plots', data=str(plot[3]))

        return plots, h5plots

    def _script(self, lines, sname):
        for i, line in enumerate(lines):
            if 'def ' + sname in line:
                istart = i
            if 'end ' + sname in line:
                iend = i
                break
        else:
            return []
        return lines[istart - 2:iend + 1]

    def save_script(self, fname, plots, h5plots, lib='mpl'):
        if len(plots) == len(h5plots) == 0:
            print("no plots selected")
            return
        if fname.endswith('.pspj'):
            fname = fname.replace('.pspj', '')
        basefname = os.path.basename(fname)

        pyExportMod = os.path.join(__fdir__, 'plotExport.py')
        with open(pyExportMod, 'r') as f:
            lines = [line.rstrip('\n') for line in f]

        output = lines[:2]
        if lib == 'mpl':
            output.extend(lines[2:4])

        output.extend(self._script(lines, "readFile"))
        dims = set([plot[2] for plot in plots] + [plot[1] for plot in h5plots])
        for dim in [1, 2, 3]:
            if dim in dims:
                output.extend(self._script(lines, "read{0}D".format(dim)))
                output.extend(
                    self._script(lines, "plot{0}D{1}".format(dim, lib)))

        if len(h5plots) > 0:
            output.extend(self._script(lines, "getPlotsFromHDF5"))
        output.extend(self._script(lines, "plotSavedData"))
        output.extend(["", "", "if __name__ == '__main__':"])

        if len(plots) == 0:
            output.extend([
                "    h5name = '{0}.h5'".format(basefname),
                "    plots = getPlotsFromHDF5(h5name)"
            ])
        elif len(h5plots) == 0:
            output.append("    plots = {0}".format(
                autopep8.fix_code(repr(plots), options={'aggressive': 2})))
        else:
            output.extend([
                "    # you can get plot definitions from the h5 file:",
                "    # h5name = '{0}.h5'".format(basefname),
                "    # plots = getPlotsFromHDF5(h5name)", "",
                "    # ...or from the `plots` list:", "    plots = {0}".format(
                    autopep8.fix_code(repr(plots), options={'aggressive': 2}))
            ])
        if lib == 'silx':
            output.extend([
                "    from silx.gui import qt", "    app = qt.QApplication([])"
            ])
        output.extend(["    plotSavedData(plots, '{0}')".format(lib), ""])
        if lib == 'silx':
            output.extend(["    app.exec_()"])

        fnameOut = '{0}_{1}.py'.format(fname, lib)
        with open(fnameOut, 'w') as f:
            f.write('\n'.join(output))