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()
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))
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])
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)
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)
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())
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)
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()
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()
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'])
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()})
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)
def onClose(self): self.app.lastWindowClosed.connect(qt.pyqtSignal(quit()))
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
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])
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()
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))