class OpenedFileExplorer(DockWidget): """Opened File Explorer is list widget with list of opened files. It implements switching current file, files sorting. Uses _OpenedFileModel internally. Class instance created by Workspace. """ def __init__(self, workspace): DockWidget.__init__(self, workspace, "&Opened Files", QIcon(":/enkiicons/filtered.png"), "Alt+O") self._workspace = workspace self.setAllowedAreas( Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea ) self.tvFiles = QTreeView(self) self.tvFiles.setHeaderHidden(True) self.tvFiles.setEditTriggers(QAbstractItemView.SelectedClicked) self.tvFiles.setContextMenuPolicy(Qt.CustomContextMenu) self.tvFiles.setDragEnabled(True) self.tvFiles.setDragDropMode(QAbstractItemView.InternalMove) self.tvFiles.setRootIsDecorated(False) self.tvFiles.setTextElideMode(Qt.ElideMiddle) self.tvFiles.setUniformRowHeights( True ) self.tvFiles.customContextMenuRequested.connect(self._onTvFilesCustomContextMenuRequested) self.setWidget(self.tvFiles) self.setFocusProxy(self.tvFiles) self.model = _OpenedFileModel(self) # Not protected, because used by Configurator self.tvFiles.setModel( self.model ) self.tvFiles.setAttribute( Qt.WA_MacShowFocusRect, False ) self.tvFiles.setAttribute( Qt.WA_MacSmallSize ) self._workspace.currentDocumentChanged.connect(self._onCurrentDocumentChanged) # disconnected by startModifyModel() self.tvFiles.selectionModel().selectionChanged.connect(self._onSelectionModelSelectionChanged) self.tvFiles.activated.connect(self._workspace.focusCurrentDocument) core.actionManager().addAction("mView/aOpenedFiles", self.showAction()) core.uiSettingsManager().dialogAccepted.connect(self._applySettings) core.uiSettingsManager().aboutToExecute.connect(self._onSettingsDialogAboutToExecute) def del_(self): """Explicitly called destructor """ core.actionManager().removeAction("mView/aOpenedFiles") def _applySettings(self): """Settings dialogue has been accepted. Apply settings """ self.model.setSortMode(core.config()["Workspace"]["FileSortMode"]) def _onSettingsDialogAboutToExecute(self, dialog): """UI settings dialogue is about to execute. Add own option """ dialog.appendOption(ChoiseOption(dialog, core.config(), "Workspace/FileSortMode", {dialog.rbOpeningOrder: "OpeningOrder", dialog.rbFileName: "FileName", dialog.rbUri: "URL", dialog.rbSuffix: "Suffixes"})) def startModifyModel(self): """Blocks signals from model while it is modified by code """ self.tvFiles.selectionModel().selectionChanged.disconnect(self._onSelectionModelSelectionChanged) def finishModifyModel(self): """Unblocks signals from model """ self.tvFiles.selectionModel().selectionChanged.connect(self._onSelectionModelSelectionChanged) def _onSortTriggered(self, action ): """ One of sort actions has been triggered in the opened file list context menu """ mode = action.data() self.model.setSortMode( mode ) def _onCurrentDocumentChanged(self, oldDocument, currentDocument ): # pylint: disable=W0613 """ Current document has been changed on workspace """ if currentDocument is not None: index = self.model.documentIndex( currentDocument ) self.startModifyModel() self.tvFiles.setCurrentIndex( index ) # scroll the view self.tvFiles.scrollTo( index ) self.finishModifyModel() def _onSelectionModelSelectionChanged(self, selected, deselected ): # pylint: disable=W0613 """ Item selected in the list. Switch current document """ if not selected.indexes(): # empty list, last file closed return index = selected.indexes()[0] # backup/restore current focused widget as setting active mdi window will steal it focusWidget = self.window().focusWidget() # set current document document = self._workspace.sortedDocuments[index.row()] self._workspace.setCurrentDocument( document ) # restore focus widget if focusWidget : focusWidget.setFocus() def _onTvFilesCustomContextMenuRequested(self, pos ): """Connected automatically by uic """ menu = QMenu() menu.addAction( core.actionManager().action( "mFile/mClose/aCurrent" ) ) menu.addAction( core.actionManager().action( "mFile/mSave/aCurrent" ) ) menu.addAction( core.actionManager().action( "mFile/mReload/aCurrent" ) ) menu.addMenu( core.actionManager().action( "mFile/mFileSystem" ).menu() ) menu.addSeparator() # sort menu sortMenu = QMenu( self ) group = QActionGroup( sortMenu ) group.addAction( self.tr( "Opening order" ) ) group.addAction( self.tr( "File name" ) ) group.addAction( self.tr( "URL" ) ) group.addAction( self.tr( "Suffixes" ) ) group.triggered.connect(self._onSortTriggered) sortMenu.addActions( group.actions() ) for i, sortMode in enumerate(["OpeningOrder", "FileName", "URL", "Suffixes"]): action = group.actions()[i] action.setData( sortMode ) action.setCheckable( True ) if sortMode == self.model.sortMode(): action.setChecked( True ) aSortMenu = QAction( self.tr( "Sorting" ), self ) aSortMenu.setMenu( sortMenu ) aSortMenu.setIcon( QIcon( ":/enkiicons/sort.png" )) aSortMenu.setToolTip( aSortMenu.text() ) menu.addAction( sortMenu.menuAction() ) menu.exec_( self.tvFiles.mapToGlobal( pos ) )
class DSBrowser(QWidget): """browser for datastorage databases Nodes are identified by a string, containing fields separated by '|'. - first filed is a capital letter: 'R'oot, 'P'roject, sensor'G'roup, 'S'ensor and 'C'hart - second field is the database folder - third filed is the path of the node in the database - for charts the fourth field is the chart name (third is path of sensorgroup in this case) """ def __init__(self): QWidget.__init__(self) self.layout = QVBoxLayout(self) self.layout.setSpacing(0) self.layout.setMargin(0) self.toolBar = QFrame(self) self.toolBarLayout = QHBoxLayout(self.toolBar) self.toolBarLayout.setMargin(2) self.toolBarLayout.setSpacing(2) self.layout.addWidget(self.toolBar) self.loadButton = QToolButton(self.toolBar) self.loadButton.setText(QCoreApplication.translate("DataStorageBrowser", "Open...")) self.loadButton.setIcon(QIcon(QPixmap(SimuVis4.Icons.fileOpen))) self.loadButton.setToolTip(QCoreApplication.translate("DataStorageBrowser", "Open a datastorage database")) self.toolBarLayout.addWidget(self.loadButton) self.connect(self.loadButton, SIGNAL("pressed()"), self.loadDatabase) self.expandButton = QToolButton(self.toolBar) self.expandButton.setText("Expand/Collapse") self.expandButton.setIcon(QIcon(QPixmap(Icons.exp_col))) self.expandButton.setToolTip( QCoreApplication.translate("DataStorageBrowser", "Expand or collapse the whole tree") ) self.toolBarLayout.addWidget(self.expandButton) self.connect(self.expandButton, SIGNAL("pressed()"), self.expandCollapseAll) self.searchInput = MyLineEdit(self.toolBar) self.searchInput.setText(QCoreApplication.translate("DataStorageBrowser", "Enter search text here")) self.searchInput.setToolTip( QCoreApplication.translate( "DataStorageBrowser", "Enter search text using wildcards here, press ENTER again to go to next match!" ) ) self.toolBarLayout.addWidget(self.searchInput, 100) self.connect(self.searchInput, SIGNAL("returnPressed()"), self.searchItem) self.helpButton = QToolButton(self.toolBar) self.helpButton.setText(QCoreApplication.translate("DataStorageBrowser", "Help")) self.helpButton.setIcon(QIcon(QPixmap(SimuVis4.Icons.help))) self.helpButton.setToolTip(QCoreApplication.translate("DataStorageBrowser", "Show help for DataStorageBrowser")) self.toolBarLayout.addWidget(self.helpButton) self.connect(self.helpButton, SIGNAL("pressed()"), self.showHelp) self.splitter = QSplitter(self) self.splitter.setOrientation(Qt.Vertical) self.treeView = QTreeView(self.splitter) self.treeView.setAlternatingRowColors(True) self.treeView.setEditTriggers(QAbstractItemView.NoEditTriggers) self.treeView.setContextMenuPolicy(Qt.CustomContextMenu) self.treeView.setAutoExpandDelay(500) self.textBrowser = QTextBrowser(self.splitter) self.layout.addWidget(self.splitter) self.splitter.setStretchFactor(0, 60) self.splitter.setStretchFactor(1, 40) self.model = DSModel() self.treeView.setModel(self.model) self.treeView.setSortingEnabled(True) self.treeView.expandAll() self.connect(self.treeView.selectionModel(), SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.showItem) self.connect(self.treeView, SIGNAL("doubleClicked(QModelIndex)"), self.itemAction) self.connect(self.treeView, SIGNAL("customContextMenuRequested(QPoint)"), self.showContextMenu) self.selectedNode = None self.selectedMI = None self.searchText = "" self.searchResults = [] self.collExpand = SimuVis4.Misc.Switcher() self.statusBar = SimuVis4.Globals.mainWin.statusBar() def loadDatabase(self, dn=None): """load a database""" if not dn: dn = QFileDialog.getExistingDirectory( self, QCoreApplication.translate("DataStorageBrowser", "Select a folder containing a datastorage database"), SimuVis4.Globals.defaultFolder, ) if not dn.isEmpty(): dn = unicode(dn) SimuVis4.Globals.defaultFolder = dn else: return self.model.addDatabase(dn) self.treeView.collapseAll() self.treeView.expandToDepth(SimuVis4.Globals.config.getint("datastoragebrowser", "expand_tree_depth")) self.treeView.resizeColumnToContents(0) def showHelp(self): SimuVis4.HelpBrowser.showHelp("/plugin/DataStorageBrowser/index.html") def showItem(self, mi, pr): """show the item at model index mi""" t, n = self.model.dsNode(mi) txt = "" if t == "R": txt = rootInfo.substitute( name=n.name, title=escape(n.title), folder=n.h5dir, projects=len(n) ) + formatMetaData(n) elif t == "P": txt = projectInfo.substitute( name=n.name, path=escape(n.parent.name), title=escape(n.title), groups=len(n) ) + formatMetaData(n) elif t == "G": txt = groupInfo.substitute( name=n.name, path="/".join(n.path.split("/")[:-1]), title=escape(n.title), sensors=len(n), charts=len(n.getCharts()), start=formatTime(n.timegrid.start), stop=formatTime(n.timegrid.stop), step=n.timegrid.step, timezone=n.timegrid.timezone, ) + formatMetaData(n) elif t == "S": txt = sensorInfo.substitute( name=n.name, path="/".join(n.path.split("/")[:-1]), title=escape(n.title), start=formatTime(n.timegrid.start), stop=formatTime(n.timegrid.stop), step=n.timegrid.step, length=n.datalen(), timezone=n.timegrid.timezone, ) + formatMetaData(n) elif t == "C": txt = chartInfo.substitute(name=n.name, path=n.sensorgroup.path) self.textBrowser.setText(txt) msg = ": ".join(str(self.model.itemFromIndex(mi).data().toString()).split("|")[1:]) self.statusBar.showMessage(msg, 5000) def searchItem(self): """execute the search and highlight the (next) result""" txt = str(self.searchInput.text()) if txt != self.searchText: self.searchText = txt tmp = self.model.findItems( txt, Qt.MatchFixedString | Qt.MatchContains | Qt.MatchWildcard | Qt.MatchRecursive ) self.searchList = [i.index() for i in tmp] if self.searchList: mi = self.searchList.pop() self.treeView.setCurrentIndex(mi) self.treeView.expand(mi) self.treeView.scrollTo(mi) else: QMessageBox.information( self, QCoreApplication.translate("DataStorageBrowser", "No (more) matches!"), QCoreApplication.translate( "DataStorageBrowser", "No (more) matches found! Change you search text and try again!" ), ) self.searchText = "" def expandCollapseAll(self): if self.collExpand(): self.treeView.collapseAll() else: self.treeView.expandAll() def itemAction(self, mi): """default action (on doubleclick) for item at model index mi""" t, n = self.model.dsNode(mi) if t == "S": if qwtPlotWindowActive(): self.addToQwtPlot(n) else: self.showQwtPlot(n) elif t == "C": self.showChart(n) def showContextMenu(self, pos): """show context menu for item at pos""" mi = self.treeView.indexAt(pos) t, n = self.model.dsNode(mi) self.selectedNode = n self.selectedMI = mi m = QMenu() if t == "R": m.addAction(QCoreApplication.translate("DataStorageBrowser", "Close"), self.closeDatabase) m.addAction(QCoreApplication.translate("DataStorageBrowser", "Reload"), self.reloadDatabase) elif t == "P": m.addAction(QCoreApplication.translate("DataStorageBrowser", "New sensorgroup"), self.newSensorGroup) elif t == "G": nCharts = len(n.getCharts()) if nCharts > 0: txt = str(QCoreApplication.translate("DataStorageBrowser", "Show all charts (%d)")) % nCharts m.addAction(txt, self.showAllCharts) m.addAction( QCoreApplication.translate("DataStorageBrowser", "Save all charts as images"), self.saveAllChartImages, ) m.addAction(QCoreApplication.translate("DataStorageBrowser", "Add chart"), self.newChart) m.addAction(QCoreApplication.translate("DataStorageBrowser", "Add/update data"), self.importFiles) m.addAction(QCoreApplication.translate("DataStorageBrowser", "Export data"), self.exportSensors) elif t == "S": m.addAction(QCoreApplication.translate("DataStorageBrowser", "New plot (Qwt)"), self.showQwtPlot) if qwtPlotWindowActive(): m.addAction(QCoreApplication.translate("DataStorageBrowser", "Add to plot (Qwt)"), self.addToQwtPlot) elif t == "C": m.addAction(QCoreApplication.translate("DataStorageBrowser", "Show"), self.showChart) m.addAction(QCoreApplication.translate("DataStorageBrowser", "Delete"), self.deleteItem) if t in "RPGS": m.addSeparator() m.addAction(QCoreApplication.translate("DataStorageBrowser", "Edit metadata"), self.editMetadata) a = m.exec_(self.treeView.mapToGlobal(pos)) def newSensorGroup(self, mi=None): if mi is None: mi = self.selectedMI newSensorGroup(self.model, mi) def showChart(self, ch=None): if ch is None: ch = self.selectedNode showChartWindow(ch, maximized=showChartMaximized) def importFiles(self, mi=None): if mi is None: mi = self.selectedMI importFiles(self.model, mi) def showAllCharts(self, sg=None): if sg is None: sg = self.selectedNode showAllChartWindows(sg, maximized=showChartMaximized) def saveAllChartImages(self, sg=None): if sg is None: sg = self.selectedNode saveAllChartImages(sg) def exportSensors(self, sg=None): if sg is None: sg = self.selectedNode exportSensors(sg) def showQwtPlot(self, se=None): if se is None: se = self.selectedNode showQwtPlotWindow(se, maximized=showChartMaximized) def addToQwtPlot(self, se=None): if se is None: se = self.selectedNode addToQwtPlotWindow(se) def editMetadata(self, node=None): if node is None: node = self.selectedNode editMetadata(node) def closeDatabase(self, mi=None): if mi is None: mi = self.selectedMI self.model.closeDatabase(mi) def reloadDatabase(self, mi=None): if mi is None: mi = self.selectedMI dbPath = self.model.dsFolder(mi) self.model.closeDatabase(mi) self.loadDatabase(dbPath) def newChart(self, mi=None): """add a chart to sensorgroup at mi using the wizard""" if mi is None: mi = self.selectedMI showNewChartWizard(self.model, mi, self) def deleteItem(self, mi=None): """delete the item at mi""" if mi is None: mi = self.selectedMI self.model.deleteItem(mi)
class ResultView(QWidget): """This class represent a search result view. """ def __init__(self, filter='', attributes=[], resultlist=[], parent=None): """Initialize a result view for the `SearchPlugin`. :param filter: the filter applied on the search :type filter: string :param attributes: a list containing the attributes used in the search operation. Usually extracted from the `filter`. :type attributes: list :param resultlist: a list of `SmartDataObject` from the search operation. :type resultlist: list :param parent: the parent for this widget. :type parent: QWidget """ super(ResultView, self).__init__(parent) self.setObjectName('ResultView') self.layout = QtGui.QVBoxLayout(self) # Only display the no-result message if resultlist is empty if len(resultlist) == 0: self.retranslate(all=False) self.onNoResult() return # The proxy model is used for sort and filter support self.proxymodel = QSortFilterProxyModel(self) self.proxymodel.setDynamicSortFilter(True) self.headerdata = ['dn'] self.headerdata.extend(attributes) self.resultdata = resultlist # FIXME: should we create a custom item model ? self.model = QStandardItemModel(0, len(self.headerdata), parent=self) #self.model = ResultItemModel(self) #self.model = ResultItemModel(self.headerdata, self.resultdata, self) self.proxymodel.setSourceModel(self.model) self.resultview = QTreeView(self) self.resultview.setUniformRowHeights(True) self.resultview.setRootIsDecorated(False) self.resultview.setAlternatingRowColors(True) self.resultview.setSortingEnabled(True) self.resultview.setModel(self.proxymodel) # For right-click context menu self.resultview.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.resultview.setSelectionMode(QAbstractItemView.ExtendedSelection) self.layout.addWidget(self.resultview) # The filter box enables the user to filter the returned search # results. It becomes accessible with Ctrl-F (QKeySequence.Find) self.filterBox = ResultFilterWidget(self.headerdata, parent=self) self.filterBox.setVisible(False) self.layout.addWidget(self.filterBox) # We need to call the retranslate method before populating # the result data self.retranslate() #self.model.populateHeader(self.headerdata) #self.model.populateModel(self.resultdata) self.setHeaderData(self.headerdata) self.setResultData(self.resultdata) self.resultview.resizeColumnToContents(0) self.__createContextMenu() self.__connectSlots() def __connectSlots(self): """Connect signal and slots. """ self.resultview.customContextMenuRequested.connect( self.onContextMenuRequested) self.filterBox.inputEdit.textChanged['QString'].connect( self.onFilterInputChanged) self.filterBox.columnBox.currentIndexChanged[int].connect( self.onFilterColumnChanged) def __getVSpacer(self): return QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) def __createContextMenu(self): """Display the context menu. """ self.contextMenu = QtGui.QMenu() self.contextMenuView = QtGui.QAction(self) self.contextMenuDelete = QtGui.QAction(self) self.contextMenuExport = QtGui.QAction(self) self.contextMenu.addAction(self.contextMenuView) self.contextMenu.addAction(self.contextMenuDelete) self.contextMenu.addAction(self.contextMenuExport) # Connect the context menu actions to the correct slots self.contextMenuView.triggered.connect(self.onViewItemsSelected) self.contextMenuDelete.triggered.connect(self.onDeleteItemsSelected) self.contextMenuExport.triggered.connect(self.onExportItemsSelected) def onNoResult(self): """Adds a styled *no result* message to the main layout. """ font = QtGui.QFont() font.setBold(True) sadface = QtGui.QLabel(self) sadface.setPixmap(pixmapFromTheme('face-sad', ':/icons/48/face-sad')) noresult = QtGui.QLabel(self) noresult.setText(self.str_NO_RESULT) noresult.setFont(font) hlayout = QtGui.QHBoxLayout() hlayout.addItem(self.__getVSpacer()) hlayout.addWidget(sadface) hlayout.addWidget(noresult) hlayout.addItem(self.__getVSpacer()) self.layout.addLayout(hlayout) def setHeaderData(self, data=[]): """Populates the ``resultview`` model with header data. Parameters: - `data`: a list with header items. Usually this is the attributelist from the LDAP search. """ i = 0 for header in data: self.model.setHeaderData(i, QtCore.Qt.Horizontal, header) i += 1 def setResultData(self, data=[]): """Populates the ``resultview`` model with result data. Parameters: - `data`: a list containing the SmartDataObjects representing items in the LDAP search result. """ row = 0 for obj in data: self.model.insertRow(row) col = 0 for attr in self.headerdata: if self.isDistinguishedName(attr): modelData = obj.getPrettyDN() elif self.isObjectClass(attr): modelData = ','.join(obj.getObjectClasses()) elif obj.hasAttribute(attr): if obj.isAttributeBinary(attr): modelData = self.str_BINARY_DATA else: modelData = ','.join(obj.getAttributeValueList(attr)) self.model.setData(self.model.index(row, col), modelData) col += 1 row += 1 def isDistinguishedName(self, attr): """Returns ``True`` if `attr` is a distinguished name, ``False`` otherwise. Parameters: - `attr`: the LDAP string attribute value to check. """ return attr.lower() == 'dn' def isObjectClass(self, attr): """Returns ``True`` if `attr` is an object class, ``False`` otherwise. Parameters: - `attr`: the LDAP string attribute value to check. """ return attr.lower() == 'objectclass' def onContextMenuRequested(self, point): """Display the context menu """ # FIXME: In order to be able to export, delete and view search # result entries. We should make use of the various dialogs in # the Browser plugin. Unitl we have refactored the design in a # way that allow us to use these without accessing the browser # modules, we simple don't provide these options yet. return self.selection = self.resultview.selectedIndexes() deleteSupport = True exportSupport = True rowsselected = len(self.selection) / len(self.headerdata) if not rowsselected > 0: self.contextMenu.setEnabled(False) self.contextMenu.exec_(self.resultview.mapToGlobal(point)) return self.contextMenu.setEnabled(True) # Look over at Browser plugin for implementation of # multiselect and operation support validation print rowsselected self.contextMenuView.setEnabled(True) if rowsselected == 1: self.contextMenuView.setText(self.str_VIEW_ITEM) else: self.contextMenuView.setText(self.str_VIEW_ITEMS) if deleteSupport: self.contextMenuDelete.setEnabled(True) if rowsselected == 1: self.contextMenuDelete.setText(self.str_DELETE_ITEM) else: self.contextMenuDelete.setText(self.str_DELETE_ITEMS) if exportSupport: self.contextMenuExport.setEnabled(True) if rowsselected == 1: self.contextMenuExport.setText(self.str_EXPORT_ITEM) else: self.contextMenuExport.setText(self.str_EXPORT_ITEMS) # Finally we execute the context menu self.contextMenu.exec_(self.resultview.mapToGlobal(point)) def onViewItemsSelected(self): """Slot for the *view* context menu action. """ raise NotImplementedError( 'Need to implement a proper model for this to be supported') def onDeleteItemsSelected(self): """Slot for the *delete* context menu action. """ msg = 'Delete from the Search Plugin is not implemented jet.' dialog = DeleteDialog(self, msg) dialog.setDeleteItems([]) dialog.exec_() def onExportItemsSelected(self): """Slot for the 'export' context menu action. """ msg = 'Export from the Search Plugin is not implemented jet.' dialog = ExportDialog(self, msg) # Only for proof of concept dialog.setExportData([]) dialog.exec_() def onFilterBoxVisibilityChanged(self, visible): """Slot for the QKeySequence.Find. - `visible`: a boolean value indicating wether or not to toggle the filter box widget visibility on or off. """ if visible: self.filterBox.setVisible(True) self.filterBox.inputEdit.setFocus() else: # I belive it's common practise to clear the filter when # the filter box is closed. This is at least the way the # filter boxes works for most webbrowsers. self.filterBox.inputEdit.clear() self.filterBox.setVisible(False) self.resultview.setFocus() def onFilterInputChanged(self, filter=''): """Slot for the filter input in the result filter widget. We get the selected syntax from the syntax combobox """ # The PyQt4 QVariant is causing some problems here, when we try # to use the <combobox>.itemData directly, even though the data # holds valid QRexExp.PatternSyntax values. # We therefore need to explicitly make the QVariant and integer. i = self.filterBox.syntaxBox.currentIndex() syntaxIndex = self.filterBox.syntaxBox.itemData(i).toInt()[0] syntax = QtCore.QRegExp.PatternSyntax(syntaxIndex) # As of now we do filtering in a case insensitive way, until we # come up with a way to introduce case sensitivity selection in a # UI inexpensive way. We want to keep the filter widget as clean # and simple as possible. regex = QtCore.QRegExp(filter, QtCore.Qt.CaseInsensitive, syntax) self.proxymodel.setFilterRegExp(regex) def onFilterColumnChanged(self, index): """Slot for the column combobox in the filter box widget. """ self.proxymodel.setFilterKeyColumn(index) def retranslate(self, all=True): """For dynamic translation support. """ self.str_VIEW_ITEM = QtGui.QApplication.translate( 'ResultView', 'View Item') self.str_VIEW_ITEMS = QtGui.QApplication.translate( 'ResultView', 'View Items') self.str_DELETE_ITEM = QtGui.QApplication.translate( 'ResultView', 'Delete Item') self.str_DELETE_ITEMS = QtGui.QApplication.translate( 'ResultView', 'Delete Items') self.str_EXPORT_ITEM = QtGui.QApplication.translate( 'ResultView', 'Export Item') self.str_EXPORT_ITEMS = QtGui.QApplication.translate( 'ResultView', 'Export Items') self.str_NO_RESULT = QtGui.QApplication.translate( 'ResultView', 'Sorry, no result to display!') self.str_BINARY_DATA = QtGui.QApplication.translate( 'ResultView', 'Binary Data') if all: self.filterBox.retranslate()
class OpenedFileExplorer(DockWidget): """Opened File Explorer is list widget with list of opened files. It implements switching current file, files sorting. Uses _OpenedFileModel internally. Class instance created by Workspace. """ def __init__(self, workspace): DockWidget.__init__(self, workspace, "&Opened Files", QIcon(":/enkiicons/filtered.png"), "Alt+O") self._workspace = workspace self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.tvFiles = QTreeView(self) self.tvFiles.setHeaderHidden(True) self.tvFiles.setContextMenuPolicy(Qt.CustomContextMenu) self.tvFiles.setDragEnabled(True) self.tvFiles.setDragDropMode(QAbstractItemView.InternalMove) self.tvFiles.setRootIsDecorated(False) self.tvFiles.setTextElideMode(Qt.ElideMiddle) self.tvFiles.setUniformRowHeights(True) self.tvFiles.customContextMenuRequested.connect( self._onTvFilesCustomContextMenuRequested) self.setWidget(self.tvFiles) self.setFocusProxy(self.tvFiles) self.model = _OpenedFileModel( self) # Not protected, because used by Configurator self.tvFiles.setModel(self.model) self.tvFiles.setAttribute(Qt.WA_MacShowFocusRect, False) self.tvFiles.setAttribute(Qt.WA_MacSmallSize) self._workspace.currentDocumentChanged.connect( self._onCurrentDocumentChanged) # disconnected by startModifyModel() self.tvFiles.selectionModel().selectionChanged.connect( self._onSelectionModelSelectionChanged) self.tvFiles.activated.connect(self._workspace.focusCurrentDocument) core.actionManager().addAction("mView/aOpenedFiles", self.showAction()) core.uiSettingsManager().dialogAccepted.connect(self._applySettings) core.uiSettingsManager().aboutToExecute.connect( self._onSettingsDialogAboutToExecute) def del_(self): """Explicitly called destructor """ core.actionManager().removeAction("mView/aOpenedFiles") def _applySettings(self): """Settings dialogue has been accepted. Apply settings """ self.model.setSortMode(core.config()["Workspace"]["FileSortMode"]) def _onSettingsDialogAboutToExecute(self, dialog): """UI settings dialogue is about to execute. Add own option """ dialog.appendOption( ChoiseOption( dialog, core.config(), "Workspace/FileSortMode", { dialog.rbOpeningOrder: "OpeningOrder", dialog.rbFileName: "FileName", dialog.rbUri: "URL", dialog.rbSuffix: "Suffixes" })) def startModifyModel(self): """Blocks signals from model while it is modified by code """ self.tvFiles.selectionModel().selectionChanged.disconnect( self._onSelectionModelSelectionChanged) def finishModifyModel(self): """Unblocks signals from model """ self.tvFiles.selectionModel().selectionChanged.connect( self._onSelectionModelSelectionChanged) def _onSortTriggered(self, action): """ One of sort actions has been triggered in the opened file list context menu """ mode = action.data().toString() self.model.setSortMode(mode) def _onCurrentDocumentChanged(self, oldDocument, currentDocument): # pylint: disable=W0613 """ Current document has been changed on workspace """ if currentDocument is not None: index = self.model.documentIndex(currentDocument) self.startModifyModel() self.tvFiles.setCurrentIndex(index) # scroll the view self.tvFiles.scrollTo(index) self.finishModifyModel() def _onSelectionModelSelectionChanged(self, selected, deselected): # pylint: disable=W0613 """ Item selected in the list. Switch current document """ if not selected.indexes(): # empty list, last file closed return index = selected.indexes()[0] # backup/restore current focused widget as setting active mdi window will steal it focusWidget = self.window().focusWidget() # set current document document = self._workspace.sortedDocuments[index.row()] self._workspace.setCurrentDocument(document) # restore focus widget if focusWidget: focusWidget.setFocus() def _onTvFilesCustomContextMenuRequested(self, pos): """Connected automatically by uic """ menu = QMenu() menu.addAction(core.actionManager().action("mFile/mClose/aCurrent")) menu.addAction(core.actionManager().action("mFile/mSave/aCurrent")) menu.addAction(core.actionManager().action("mFile/mReload/aCurrent")) menu.addSeparator() # sort menu sortMenu = QMenu(self) group = QActionGroup(sortMenu) group.addAction(self.tr("Opening order")) group.addAction(self.tr("File name")) group.addAction(self.tr("URL")) group.addAction(self.tr("Suffixes")) group.triggered.connect(self._onSortTriggered) sortMenu.addActions(group.actions()) for i, sortMode in enumerate( ["OpeningOrder", "FileName", "URL", "Suffixes"]): action = group.actions()[i] action.setData(sortMode) action.setCheckable(True) if sortMode == self.model.sortMode(): action.setChecked(True) aSortMenu = QAction(self.tr("Sorting"), self) aSortMenu.setMenu(sortMenu) aSortMenu.setIcon(QIcon(":/enkiicons/sort.png")) aSortMenu.setToolTip(aSortMenu.text()) menu.addAction(sortMenu.menuAction()) menu.exec_(self.tvFiles.mapToGlobal(pos))
class DSBrowser(QWidget): """browser for datastorage databases Nodes are identified by a string, containing fields separated by '|'. - first filed is a capital letter: 'R'oot, 'P'roject, sensor'G'roup, 'S'ensor and 'C'hart - second field is the database folder - third filed is the path of the node in the database - for charts the fourth field is the chart name (third is path of sensorgroup in this case) """ def __init__(self): QWidget.__init__(self) self.layout = QVBoxLayout(self) self.layout.setSpacing(0) self.layout.setMargin(0) self.toolBar = QFrame(self) self.toolBarLayout = QHBoxLayout(self.toolBar) self.toolBarLayout.setMargin(2) self.toolBarLayout.setSpacing(2) self.layout.addWidget(self.toolBar) self.loadButton = QToolButton(self.toolBar) self.loadButton.setText(QCoreApplication.translate('DataStorageBrowser', 'Open...')) self.loadButton.setIcon(QIcon(QPixmap(SimuVis4.Icons.fileOpen))) self.loadButton.setToolTip(QCoreApplication.translate('DataStorageBrowser', 'Open a datastorage database')) self.toolBarLayout.addWidget(self.loadButton) self.connect(self.loadButton, SIGNAL('pressed()'), self.loadDatabase) self.dropButton = QToolButton(self.toolBar) self.dropButton.setText(QCoreApplication.translate('DataStorageBrowser', 'Close All')) self.dropButton.setIcon(QIcon(QPixmap(SimuVis4.Icons.clear))) self.dropButton.setToolTip(QCoreApplication.translate('DataStorageBrowser', 'Drop all open databases')) self.toolBarLayout.addWidget(self.dropButton) self.connect(self.dropButton, SIGNAL('pressed()'), self.dropDatabases) self.dropButton.setEnabled(False) self.toolBarLayout.addStretch(100) self.splitter = QSplitter(self) self.splitter.setOrientation(Qt.Vertical) self.treeView = QTreeView(self.splitter) self.treeView.setAlternatingRowColors(True) self.treeView.setEditTriggers(QAbstractItemView.NoEditTriggers) self.treeView.setContextMenuPolicy(Qt.CustomContextMenu) self.treeView.setAutoExpandDelay(500) self.textBrowser = QTextBrowser(self.splitter) self.layout.addWidget(self.splitter) self.splitter.setStretchFactor(0, 60) self.splitter.setStretchFactor(1, 40) self.model = DSModel() self.treeView.setModel(self.model) self.treeView.setSortingEnabled(True) self.treeView.expandAll() self.connect(self.treeView.selectionModel(), SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.showItem) self.connect(self.treeView, SIGNAL("doubleClicked(QModelIndex)"), self.itemAction) self.connect(self.treeView, SIGNAL("customContextMenuRequested(QPoint)"), self.showContextMenu) self.selectedNode = None self.selectedMI = None def loadDatabase(self, dn=None): """load a database""" if not dn: dn = QFileDialog.getExistingDirectory(self, QCoreApplication.translate('DataStorageBrowser', "Select a folder containing a datastorage database"), SimuVis4.Globals.defaultFolder) if not dn.isEmpty(): dn = unicode(dn) SimuVis4.Globals.defaultFolder = dn else: return self.model.addDatabase(dn) self.treeView.expandToDepth(1) def dropDatabases(self): # FIXME: implement it pass def showItem(self, mi, pr): """show the item at model index mi""" t, n = self.model.dsNode(mi) txt = "" if t == 'R': # FIXME: no metadata? txt = rootInfo.substitute(name=n.name, title=escape(n.title), folder=n.h5dir, projects=len(n)) # + formatMetaData(n) elif t == 'P': txt = projectInfo.substitute(name=n.name, title=escape(n.title), groups=len(n)) + formatMetaData(n) elif t == 'G': txt = groupInfo.substitute(name=n.name, title=escape(n.title), sensors=len(n), charts=len(n.charts)) + formatMetaData(n) elif t == 'S': txt = sensorInfo.substitute(name=n.name, title=escape(n.title), start=n.timegrid.start, stop=n.timegrid.stop, step=n.timegrid.step, length=n.datalen()) + formatMetaData(n) elif t == 'C': txt = chartInfo.substitute(name=n.name) self.textBrowser.setText(txt) def itemAction(self, mi): """default action (on doubleclick) for item at model index mi""" t, n = self.model.dsNode(mi) if t == 'R': pass elif t == 'P': pass elif t == 'G': pass elif t == 'S': print n self.showQwtPlot(n) elif t == 'C': self.showMplChart(n) def showContextMenu(self, pos): """show context menu for item at pos""" mi = self.treeView.indexAt(pos) t, n = self.model.dsNode(mi) self.selectedNode = n self.selectedMI = mi m = QMenu() if t in 'RPGS': p = m.addAction(QCoreApplication.translate('DataStorageBrowser', 'Edit metadata'), self.editMetadata) if t == 'R': pass elif t == 'P': pass elif t == 'G': m.addAction(QCoreApplication.translate('DataStorageBrowser', 'Add Chart'), self.newChart) elif t == 'S': m.addAction(QCoreApplication.translate('DataStorageBrowser', 'Plot (Qwt)'), self.showQwtPlot) elif t == 'C': m.addAction(QCoreApplication.translate('DataStorageBrowser', 'Show'), self.showMplChart) m.addAction(QCoreApplication.translate('DataStorageBrowser', 'Delete'), self.deleteItem) a = m.exec_(self.treeView.mapToGlobal(pos)) def showMplChart(self, node=None): if node is None: node = self.selectedNode showChartMplWindow(node, maximized=showChartMaximized) def showQwtPlot(self, node=None): if node is None: node = self.selectedNode showQwtPlotWindow(node, maximized=showChartMaximized) def editMetadata(self, node=None): if node is None: node = self.selectedNode editMetadata(node) def newChart(self, mi=None): """add a chart to sensorgroup at mi using the wizard""" if mi is None: mi = self.selectedMI showNewChartWizard(self.model, mi) def deleteItem(self, mi=None): """delete the item at mi""" if mi is None: mi = self.selectedMI self.model.deleteItem(mi)
class DSBrowser(QWidget): """browser for datastorage databases Nodes are identified by a string, containing fields separated by '|'. - first filed is a capital letter: 'R'oot, 'P'roject, sensor'G'roup, 'S'ensor and 'C'hart - second field is the database folder - third filed is the path of the node in the database - for charts the fourth field is the chart name (third is path of sensorgroup in this case) """ def __init__(self): QWidget.__init__(self) self.layout = QVBoxLayout(self) self.layout.setSpacing(0) self.layout.setMargin(0) self.toolBar = QFrame(self) self.toolBarLayout = QHBoxLayout(self.toolBar) self.toolBarLayout.setMargin(2) self.toolBarLayout.setSpacing(2) self.layout.addWidget(self.toolBar) self.loadButton = QToolButton(self.toolBar) self.loadButton.setText(QCoreApplication.translate('DataStorageBrowser', 'Open...')) self.loadButton.setIcon(QIcon(QPixmap(SimuVis4.Icons.fileOpen))) self.loadButton.setToolTip(QCoreApplication.translate('DataStorageBrowser', 'Open a datastorage database')) self.toolBarLayout.addWidget(self.loadButton) self.connect(self.loadButton, SIGNAL('pressed()'), self.loadDatabase) self.expandButton = QToolButton(self.toolBar) self.expandButton.setText('Expand/Collapse') self.expandButton.setIcon(QIcon(QPixmap(Icons.exp_col))) self.expandButton.setToolTip(QCoreApplication.translate('DataStorageBrowser', 'Expand or collapse the whole tree')) self.toolBarLayout.addWidget(self.expandButton) self.connect(self.expandButton, SIGNAL('pressed()'), self.expandCollapseAll) self.searchInput = QLineEdit(self.toolBar) self.searchInput.setText(QCoreApplication.translate('DataStorageBrowser', 'Enter search text here')) self.searchInput.setToolTip(QCoreApplication.translate('DataStorageBrowser', 'Enter search text using wildcards here, press ENTER again to go to next match!')) self.toolBarLayout.addWidget(self.searchInput, 100) self.connect(self.searchInput, SIGNAL('returnPressed()'), self.searchItem) self.helpButton = QToolButton(self.toolBar) self.helpButton.setText(QCoreApplication.translate('DataStorageBrowser', 'Help')) self.helpButton.setIcon(QIcon(QPixmap(SimuVis4.Icons.help))) self.helpButton.setToolTip(QCoreApplication.translate('DataStorageBrowser', 'Show help for DataStorageBrowser')) self.toolBarLayout.addWidget(self.helpButton) self.connect(self.helpButton, SIGNAL('pressed()'), self.showHelp) self.splitter = QSplitter(self) self.splitter.setOrientation(Qt.Vertical) self.treeView = QTreeView(self.splitter) self.treeView.setAlternatingRowColors(True) self.treeView.setEditTriggers(QAbstractItemView.NoEditTriggers) self.treeView.setContextMenuPolicy(Qt.CustomContextMenu) self.treeView.setAutoExpandDelay(500) self.textBrowser = QTextBrowser(self.splitter) self.layout.addWidget(self.splitter) self.splitter.setStretchFactor(0, 60) self.splitter.setStretchFactor(1, 40) self.model = DSModel() self.treeView.setModel(self.model) self.treeView.setSortingEnabled(True) self.treeView.expandAll() self.connect(self.treeView.selectionModel(), SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.showItem) self.connect(self.treeView, SIGNAL("doubleClicked(QModelIndex)"), self.itemAction) self.connect(self.treeView, SIGNAL("customContextMenuRequested(QPoint)"), self.showContextMenu) self.selectedNode = None self.selectedMI = None self.searchText = '' self.searchResults = [] self.collExpand = SimuVis4.Misc.Switcher() def loadDatabase(self, dn=None): """load a database""" if not dn: dn = QFileDialog.getExistingDirectory(self, QCoreApplication.translate('DataStorageBrowser', "Select a folder containing a datastorage database"), SimuVis4.Globals.defaultFolder) if not dn.isEmpty(): dn = unicode(dn) SimuVis4.Globals.defaultFolder = dn else: return self.model.addDatabase(dn) self.treeView.expandToDepth(1) self.treeView.resizeColumnToContents(0) def showHelp(self): SimuVis4.HelpBrowser.showHelp('/plugin/DataStorageBrowser/index.html') def showItem(self, mi, pr): """show the item at model index mi""" t, n = self.model.dsNode(mi) txt = "" if t == 'R': # FIXME: no metadata? txt = rootInfo.substitute(name=n.name, title=escape(n.title), folder=n.h5dir, projects=len(n)) + formatMetaData(n) elif t == 'P': txt = projectInfo.substitute(name=n.name, path=escape(n.parent.name), title=escape(n.title), groups=len(n)) + formatMetaData(n) elif t == 'G': txt = groupInfo.substitute(name=n.name, path='/'.join(n.path.split('/')[:-1]), title=escape(n.title), sensors=len(n), charts=len(n.charts)) + formatMetaData(n) elif t == 'S': txt = sensorInfo.substitute(name=n.name, path='/'.join(n.path.split('/')[:-1]), title=escape(n.title), start=n.timegrid.start, stop=n.timegrid.stop, step=n.timegrid.step, length=n.datalen()) + formatMetaData(n) elif t == 'C': txt = chartInfo.substitute(name=n.name, path=n.sensorgroup.path) self.textBrowser.setText(txt) def searchItem(self): """execute the search and highlight the (next) result""" txt = str(self.searchInput.text()) if txt != self.searchText: self.searchText = txt tmp = self.model.findItems(txt, Qt.MatchFixedString | Qt.MatchContains | Qt.MatchWildcard | Qt.MatchRecursive) self.searchList = [i.index() for i in tmp] if self.searchList: mi = self.searchList.pop() self.treeView.setCurrentIndex(mi) self.treeView.expand(mi) self.treeView.scrollTo(mi) else: QMessageBox.information(self, QCoreApplication.translate('DataStorageBrowser', 'No (more) matches!'), QCoreApplication.translate('DataStorageBrowser', 'No (more) matches found! Change you search criteria and try again!')) self.searchText = '' def expandCollapseAll(self): if self.collExpand(): self.treeView.collapseAll() else: self.treeView.expandAll() def itemAction(self, mi): """default action (on doubleclick) for item at model index mi""" t, n = self.model.dsNode(mi) if t == 'R': pass elif t == 'P': pass elif t == 'G': pass elif t == 'S': self.showQwtPlot(n) elif t == 'C': self.showMplChart(n) def showContextMenu(self, pos): """show context menu for item at pos""" mi = self.treeView.indexAt(pos) t, n = self.model.dsNode(mi) self.selectedNode = n self.selectedMI = mi m = QMenu() if t == 'R': m.addAction(QCoreApplication.translate('DataStorageBrowser', 'Close'), self.closeDatabase) elif t == 'P': pass elif t == 'G': nCharts = len(n.charts) if nCharts > 0: txt = str(QCoreApplication.translate('DataStorageBrowser', 'Show all Charts (%d)')) % nCharts m.addAction(txt, self.showAllCharts) m.addAction(QCoreApplication.translate('DataStorageBrowser', 'Add Chart'), self.newChart) elif t == 'S': m.addAction(QCoreApplication.translate('DataStorageBrowser', 'Plot (Qwt)'), self.showQwtPlot) elif t == 'C': m.addAction(QCoreApplication.translate('DataStorageBrowser', 'Show'), self.showMplChart) m.addAction(QCoreApplication.translate('DataStorageBrowser', 'Delete'), self.deleteItem) if t in 'RPGS': m.addSeparator() m.addAction(QCoreApplication.translate('DataStorageBrowser', 'Edit metadata'), self.editMetadata) a = m.exec_(self.treeView.mapToGlobal(pos)) def showMplChart(self, node=None): if node is None: node = self.selectedNode showChartMplWindow(node, maximized=showChartMaximized) def showAllCharts(self, node=None): if node is None: node = self.selectedNode for chart in node.charts.values(): showChartMplWindow(chart, maximized=showChartMaximized) def showQwtPlot(self, node=None): if node is None: node = self.selectedNode showQwtPlotWindow(node, maximized=showChartMaximized) def editMetadata(self, node=None): if node is None: node = self.selectedNode editMetadata(node) def closeDatabase(self, mi=None): if mi is None: mi = self.selectedMI self.model.closeDatabase(mi) def newChart(self, mi=None): """add a chart to sensorgroup at mi using the wizard""" if mi is None: mi = self.selectedMI showNewChartWizard(self.model, mi) def deleteItem(self, mi=None): """delete the item at mi""" if mi is None: mi = self.selectedMI self.model.deleteItem(mi)
class OpenedFileExplorer(DockWidget): """Opened File Explorer is list widget with list of opened files. It implements switching current file, files sorting. Uses _OpenedFileModel internally. Class instance created by Workspace. """ def __init__(self, workspace): DockWidget.__init__(self, workspace, "&Opened Files", QIcon(":/enkiicons/filtered.png"), "Alt+O") self._workspace = workspace self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.tvFiles = QTreeView(self) self.tvFiles.setHeaderHidden(True) self.tvFiles.setEditTriggers(QAbstractItemView.SelectedClicked) self.tvFiles.setContextMenuPolicy(Qt.CustomContextMenu) self.tvFiles.setDragEnabled(True) self.tvFiles.setDragDropMode(QAbstractItemView.InternalMove) self.tvFiles.setRootIsDecorated(False) self.tvFiles.setTextElideMode(Qt.ElideMiddle) self.tvFiles.setUniformRowHeights(True) self.tvFiles.customContextMenuRequested.connect( self._onTvFilesCustomContextMenuRequested) self.setWidget(self.tvFiles) self.setFocusProxy(self.tvFiles) self.model = _OpenedFileModel( self) # Not protected, because used by Configurator self.tvFiles.setModel(self.model) self.tvFiles.setAttribute(Qt.WA_MacShowFocusRect, False) self.tvFiles.setAttribute(Qt.WA_MacSmallSize) self._workspace.currentDocumentChanged.connect( self._onCurrentDocumentChanged) # disconnected by startModifyModel() self.tvFiles.selectionModel().selectionChanged.connect( self._onSelectionModelSelectionChanged) self.tvFiles.activated.connect(self._workspace.focusCurrentDocument) core.actionManager().addAction("mView/aOpenedFiles", self.showAction()) def del_(self): """Explicitly called destructor """ core.actionManager().removeAction("mView/aOpenedFiles") def startModifyModel(self): """Blocks signals from model while it is modified by code """ self.tvFiles.selectionModel().selectionChanged.disconnect( self._onSelectionModelSelectionChanged) def finishModifyModel(self): """Unblocks signals from model """ self.tvFiles.selectionModel().selectionChanged.connect( self._onSelectionModelSelectionChanged) def _onCurrentDocumentChanged(self, oldDocument, currentDocument): # pylint: disable=W0613 """ Current document has been changed on workspace """ if currentDocument is not None: index = self.model.documentIndex(currentDocument) self.startModifyModel() self.tvFiles.setCurrentIndex(index) # scroll the view self.tvFiles.scrollTo(index) self.finishModifyModel() def _onSelectionModelSelectionChanged(self, selected, deselected): # pylint: disable=W0613 """ Item selected in the list. Switch current document """ if not selected.indexes(): # empty list, last file closed return index = selected.indexes()[0] # backup/restore current focused widget as setting active mdi window will steal it focusWidget = self.window().focusWidget() # set current document document = self._workspace.sortedDocuments[index.row()] self._workspace.setCurrentDocument(document) # restore focus widget if focusWidget: focusWidget.setFocus() def _onTvFilesCustomContextMenuRequested(self, pos): """Connected automatically by uic """ menu = QMenu() menu.addAction(core.actionManager().action("mFile/mClose/aCurrent")) menu.addAction(core.actionManager().action("mFile/mSave/aCurrent")) menu.addAction(core.actionManager().action("mFile/mReload/aCurrent")) menu.addSeparator() menu.addAction( core.actionManager().action("mFile/mFileSystem/aRename")) toggleExecutableAction = core.actionManager().action( "mFile/mFileSystem/aToggleExecutable") if toggleExecutableAction: # not available on Windows menu.addAction(toggleExecutableAction) core.actionManager().action("mFile/mFileSystem").menu( ).aboutToShow.emit() # to update aToggleExecutable menu.exec_(self.tvFiles.mapToGlobal(pos))