def __setupUi(self): layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) view = QTreeView(objectName="tool-tree-view") view.setUniformRowHeights(True) view.setFrameStyle(QTreeView.NoFrame) view.setModel(self.__model) view.setRootIsDecorated(False) view.setHeaderHidden(True) view.setItemsExpandable(True) view.setEditTriggers(QTreeView.NoEditTriggers) view.setItemDelegate(ToolTreeItemDelegate(self)) view.activated.connect(self.__onActivated) view.clicked.connect(self.__onActivated) view.entered.connect(self.__onEntered) view.installEventFilter(self) self.__view = view layout.addWidget(view) self.setLayout(layout)
class XNavigationEdit(XLineEdit): """ """ navigationChanged = Signal() __designer_icon__ = projexui.resources.find('img/ui/navigate.png') def __init__( self, parent = None ): super(XNavigationEdit, self).__init__( parent ) # define custom properties self._separator = '/' self._partsEditingEnabled = True self._originalText = '' self._scrollWidget = QScrollArea(self) self._partsWidget = QWidget(self._scrollWidget) self._buttonGroup = QButtonGroup(self) self._scrollAmount = 0 self._navigationModel = None # create the completer tree palette = self.palette() palette.setColor(palette.Base, palette.color(palette.Window)) palette.setColor(palette.Text, palette.color(palette.WindowText)) bg = palette.color(palette.Highlight) abg = bg.darker(115) fg = palette.color(palette.HighlightedText) sbg = 'rgb(%s, %s, %s)' % (bg.red(), bg.green(), bg.blue()) sabg = 'rgb(%s, %s, %s)' % (abg.red(), abg.green(), abg.blue()) sfg = 'rgb(%s, %s, %s)' % (fg.red(), fg.green(), fg.blue()) style = 'QTreeView::item:hover { '\ ' color: %s;'\ ' background: qlineargradient(x1:0,'\ ' y1:0,'\ ' x2:0,'\ ' y2:1,'\ ' stop: 0 %s,'\ ' stop: 1 %s);'\ '}' % (sfg, sbg, sabg) self._completerTree = QTreeView(self) self._completerTree.setStyleSheet(style) self._completerTree.header().hide() self._completerTree.setFrameShape(QTreeView.Box) self._completerTree.setFrameShadow(QTreeView.Plain) self._completerTree.setPalette(palette) self._completerTree.setEditTriggers(QTreeView.NoEditTriggers) self._completerTree.setWindowFlags(Qt.Popup) self._completerTree.installEventFilter(self) self._completerTree.setRootIsDecorated(False) self._completerTree.setItemsExpandable(False) # create the editing widget layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addStretch() self._scrollWidget.setFrameShape( QScrollArea.NoFrame ) self._scrollWidget.setFocusPolicy(Qt.NoFocus) self._scrollWidget.setWidget(self._partsWidget) self._scrollWidget.setWidgetResizable(True) self._scrollWidget.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self._scrollWidget.setAlignment(Qt.AlignTop | Qt.AlignRight) self._scrollWidget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self._scrollWidget.setContentsMargins(0, 0, 0, 0) self._scrollWidget.setViewportMargins(0, 0, 0, 0) self._scrollWidget.move(2, 2) self._partsWidget.setLayout(layout) self._partsWidget.setCursor(Qt.ArrowCursor) self._partsWidget.setAutoFillBackground(True) self._partsWidget.setFixedHeight(self.height() - 12) palette = self._partsWidget.palette() palette.setColor(palette.Background, palette.color(palette.Base)) self._partsWidget.setPalette(palette) # create connections self._completerTree.clicked.connect( self.navigateToIndex ) self._buttonGroup.buttonClicked.connect( self.handleButtonClick ) self._scrollWidget.horizontalScrollBar().valueChanged.connect( self.scrollParts ) def acceptEdit( self ): """ Accepts the current text and rebuilds the parts widget. """ if ( self._partsWidget.isVisible() ): return False use_completion = self.completer().popup().isVisible() completion = self.completer().currentCompletion() self._completerTree.hide() self.completer().popup().hide() if ( use_completion ): self.setText(completion) else: self.rebuild() return True def cancelEdit( self ): """ Rejects the current edit and shows the parts widget. """ if ( self._partsWidget.isVisible() ): return False self._completerTree.hide() self.completer().popup().hide() self.setText(self._originalText) return True def currentItem( self ): """ Returns the current navigation item from the current path. :return <XNavigationItem> || None """ model = self.navigationModel() if ( not model ): return None return model.itemByPath(self.text()) def eventFilter( self, object, event ): """ Filters the events for the inputed object through this edit. :param object | <QObject> event | <QEvent> :return <bool> | consumed """ if ( event.type() == event.KeyPress ): if ( event.key() == Qt.Key_Escape ): self._completerTree.hide() self.completer().popup().hide() self.cancelEdit() elif ( event.key() in (Qt.Key_Return, Qt.Key_Enter) ): self.acceptEdit() return True elif ( event.key() == Qt.Key_Tab ): if ( self.completer().popup().isVisible() ): text = str(self.completer().currentCompletion()) super(XNavigationEdit, self).setText(text) return True else: self.acceptEdit() return False elif ( event.type() == event.MouseButtonPress ): if ( not self._completerTree.rect().contains(event.pos()) ): self._completerTree.hide() self.completer().popup().hide() self.cancelEdit() return False def focusOutEvent( self, event ): """ Overloads the focus out event to cancel editing when the widget loses focus. :param event | <QFocusEvent> """ super(XNavigationEdit, self).focusOutEvent(event) self.cancelEdit() def handleButtonClick( self, button ): """ Handle the event when a user clicks on one of the part buttons. :param button | <QToolButton> """ path = button.property('path') is_completer = button.property('is_completer') # popup a completion menu if ( unwrapVariant(is_completer) ): model = self.navigationModel() if ( not model ): return sep = self.separator() path = str(unwrapVariant(path)) item = model.itemByPath(path, includeRoot = True) if ( not item ): return curr_path = str(self.text()).strip(self.separator()) curr_path = curr_path.replace(path, '').strip(self.separator()) child_name = '' if ( curr_path ): child_name = curr_path.split(self.separator())[0] index = model.indexFromItem(item) self._completerTree.move(QCursor.pos()) self._completerTree.setRootIndex(index) self._completerTree.verticalScrollBar().setValue(0) if ( child_name ): child_item = None for i in range(item.rowCount()): child = item.child(i) if ( child.text() == child_name ): child_item = child break if ( child_item ): child_index = model.indexFromItem(child_item) self._completerTree.setCurrentIndex(child_index) self._completerTree.scrollTo(child_index) self._completerTree.show() self._completerTree.setUpdatesEnabled(True) else: self.setText(unwrapVariant(path)) def keyPressEvent( self, event ): """ Overloads the key press event to listen for escape calls to cancel the parts editing. :param event | <QKeyPressEvent> """ if ( self.scrollWidget().isHidden() ): if ( event.key() == Qt.Key_Escape ): self.cancelEdit() return elif ( event.key() in (Qt.Key_Return, Qt.Key_Enter) ): self.acceptEdit() return elif ( event.key() == Qt.Key_A and event.modifiers() == Qt.ControlModifier ): self.startEdit() super(XNavigationEdit, self).keyPressEvent(event) def mouseDoubleClickEvent( self, event ): """ Overloads the system to enable editing when a user double clicks. :param event | <QMouseEvent> """ super(XNavigationEdit, self).mouseDoubleClickEvent(event) self.startEdit() def navigationModel( self ): """ Returns the navigation model linked with this edit. :return <XNavigationModel> || None """ return self._navigationModel def navigateToIndex( self, index ): """ Navigates to the inputed action's path. :param action | <QAction> """ self._completerTree.hide() item = self._navigationModel.itemFromIndex(index) self.setText(self._navigationModel.itemPath(item)) def parts( self ): """ Returns the parts that are used for this system. :return [<str>, ..] """ path = str(self.text()).strip(self.separator()) if ( not path ): return [] return path.split(self.separator()) def partsWidget( self ): """ Returns the widget that contains the parts system. :return <QScrollArea> """ return self._partsWidget def startEdit( self ): """ Rebuilds the pathing based on the parts. """ self._originalText = self.text() self.scrollWidget().hide() self.setFocus() self.selectAll() def rebuild( self ): """ Rebuilds the parts widget with the latest text. """ navitem = self.currentItem() if ( navitem ): navitem.initialize() self.setUpdatesEnabled(False) self.scrollWidget().show() self._originalText = '' partsw = self.partsWidget() for button in self._buttonGroup.buttons(): self._buttonGroup.removeButton(button) button.close() button.setParent(None) button.deleteLater() # create the root button layout = partsw.layout() parts = self.parts() button = QToolButton(partsw) button.setAutoRaise(True) button.setMaximumWidth(12) button.setArrowType(Qt.RightArrow) button.setProperty('path', wrapVariant('')) button.setProperty('is_completer', wrapVariant(True)) last_button = button self._buttonGroup.addButton(button) layout.insertWidget(0, button) # check to see if we have a navigation model setup if ( self._navigationModel ): last_item = self._navigationModel.itemByPath(self.text()) show_last = last_item and last_item.rowCount() > 0 else: show_last = False # load the navigation system count = len(parts) for i, part in enumerate(parts): path = self.separator().join(parts[:i+1]) button = QToolButton(partsw) button.setAutoRaise(True) button.setText(part) if ( self._navigationModel ): item = self._navigationModel.itemByPath(path) if ( item ): button.setIcon(item.icon()) button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) button.setProperty('path', wrapVariant(path)) button.setProperty('is_completer', wrapVariant(False)) self._buttonGroup.addButton(button) layout.insertWidget((i * 2) + 1, button) # determine if we should show the final button if ( show_last or i < (count - 1) ): button = QToolButton(partsw) button.setAutoRaise(True) button.setMaximumWidth(12) button.setArrowType(Qt.RightArrow) button.setProperty('path', wrapVariant(path)) button.setProperty('is_completer', wrapVariant(True)) self._buttonGroup.addButton(button) layout.insertWidget((i * 2) + 2, button) last_button = button if ( self.scrollWidget().width() < partsw.width() ): self.scrollParts(partsw.width() - self.scrollWidget().width()) self.setUpdatesEnabled(True) self.navigationChanged.emit() def resizeEvent( self, event ): """ Resizes the current widget and its parts widget. :param event | <QResizeEvent> """ super(XNavigationEdit, self).resizeEvent(event) w = self.width() h = self.height() self._scrollWidget.resize(w - 4, h - 4) if ( self._scrollWidget.width() < self._partsWidget.width() ): self.scrollParts( self._partsWidget.width() - self._scrollWidget.width() ) def scrollParts( self, amount ): """ Scrolls the parts to offset the scrolling amount. :param amount | <int> """ change = self._scrollAmount - amount self._partsWidget.scroll(change, 0) self._scrollAmount = amount def scrollWidget( self ): """ Returns the scrolling widget. :return <QScrollArea> """ return self._scrollWidget def separator( self ): """ Returns the separation character that is used for this edit. :return <str> """ return self._separator def setTopLevelItems( self, items ): """ Initializes the navigation system to start with the inputed root \ item. :param item | <XNavigationItem> """ if ( not self._navigationModel ): self.setNavigationModel(XNavigationModel(self)) self._navigationModel.setTopLevelItems(items) def setNavigationModel( self, model ): """ Sets the navigation model for this edit. :param model | <XNavigationModel> """ self._navigationModel = model self._completerTree.setModel(model) if ( model ): model.setSeparator(self.separator()) completer = XNavigationCompleter(model, self) self.setCompleter(completer) completer.popup().installEventFilter(self) else: self.setCompleter(None) self.rebuild() def setParts( self, parts ): """ Sets the path for this edit widget by providing the parts to the path. :param parts | [<str>, ..] """ self.setText(self.separator().join(map(str, parts))) def setSeparator( self, separator ): """ Sets the separator to the inputed character. :param separator | <str> """ self._separator = separator if ( self._navigationModel ): self._navigationModel.setSeparator(separator) self.rebuild() def setText( self, text ): """ Sets the text for this edit to the inputed text. :param text | <str> """ super(XNavigationEdit, self).setText(text) self.scrollWidget().show() if ( text == '' or self._originalText != text ): self.rebuild()
class VariableTreeBox(QGroupBox): class VariableTreeButton(QToolButton): def __init__(self, *args, **kwargs): QToolButton.__init__(self, *args, **kwargs) baseIcon = QIcon(":go-next.svg") secondIcon = QIcon(":go-previous.svg") self.__icons__ = [baseIcon, secondIcon] self.setIcon(baseIcon) def switchIcon(self): self.__icons__.reverse() self.setIcon(self.__icons__[0]) def __init__(self, id, name="Choose Variables", model=None): QGroupBox.__init__(self) if model is None: model = TreeModel() self.setTitle(name) self.setToolTip("<p>Select variables for analysis</p>") self.id = id self.pairs = {} layout = HBoxLayout() self.variableTreeView = QTreeView() self.variableTreeView.setModel(model) self.variableTreeView.setToolTip("Select variables from here") self.variableTreeView.setSelectionMode(QAbstractItemView.ExtendedSelection) self.variableTreeView.installEventFilter(self) layout.addWidget(self.variableTreeView) self.widgetsLayout = VBoxLayout() layout.addLayout(self.widgetsLayout) self.setLayout(layout) def addItem(self, widget): hbox = HBoxLayout() button = self.VariableTreeButton() button.setToolTip("Specify variable") self.connect(button, SIGNAL("clicked()"), self.move) widget.widget.installEventFilter(self) hbox.addWidget(button) hbox.addWidget(widget) self.widgetsLayout.addLayout(hbox) self.widgetsLayout.addStretch() self.pairs[button] = widget def eventFilter(self, object, event): if event.type() == QEvent.FocusIn: self.switchButton(object) return False def move(self): sender = self.sender() receiver = self.pairs[sender] path = QString() if isinstance(receiver, VariableLineEdit): if receiver.widget.text().isEmpty(): indexes = self.variableTreeView.selectedIndexes() if len(indexes) < 1: return path = self.variableTreeView.model().parentTree(indexes[0]) sender.switchIcon() receiver.widget.setText(path) elif isinstance(receiver, VariableListBox): add = self.variableTreeView.hasFocus() if add: indexes = self.variableTreeView.selectedIndexes() paths = [self.variableTreeView.model().parentTree(index) for index in indexes if index.column() == 0] receiver.widget.addItems(paths) else: indexes = receiver.widget.selectedItems() for index in indexes: item = receiver.widget.takeItem(receiver.widget.row(index)) del item def switchButton(self, sender): if not isinstance(sender, QLineEdit): for key, value in self.pairs.iteritems(): if value.widget == sender: key.setIcon(QIcon(":go-previous.svg")) else: key.setIcon(QIcon(":go-next.svg")) def parameterValues(self): params = {} for button, widget in self.pairs.iteritems(): params.update(widget.parameterValues()) return params
class _LocatorDialog(QDialog): """Locator widget and implementation """ def __init__(self, parent, commandClasses): QDialog.__init__(self, parent) self._terminated = False self._commandClasses = commandClasses self._createUi() self._loadingTimer = QTimer(self) self._loadingTimer.setSingleShot(True) self._loadingTimer.setInterval(200) self._loadingTimer.timeout.connect(self._applyLoadingCompleter) self._completerLoaderThread = _CompleterLoaderThread(self) self.finished.connect(self._terminate) self._command = None self._updateCurrentCommand() def _createUi(self): self.setWindowTitle(core.project().path() or 'Locator') self.setLayout(QVBoxLayout()) self.layout().setContentsMargins(0, 0, 0, 0) self.layout().setSpacing(1) biggerFont = self.font() biggerFont.setPointSizeF(biggerFont.pointSizeF() * 2) self.setFont(biggerFont) self._edit = _CompletableLineEdit(self) self._edit.updateCurrentCommand.connect(self._updateCurrentCommand) self._edit.enterPressed.connect(self._onEnterPressed) self._edit.installEventFilter(self) # catch Up, Down self.layout().addWidget(self._edit) self.setFocusProxy(self._edit) self._table = QTreeView(self) self._table.setFont(biggerFont) self._model = _CompleterModel() self._table.setModel(self._model) self._table.setItemDelegate(HTMLDelegate(self._table)) self._table.setRootIsDecorated(False) self._table.setHeaderHidden(True) self._table.clicked.connect(self._onItemClicked) self._table.setAlternatingRowColors(True) self._table.installEventFilter( self) # catch focus and give to the edit self.layout().addWidget(self._table) width = QFontMetrics(self.font()).width('x' * 64) # width of 64 'x' letters self.resize(width, width * 0.62) def _terminate(self): if not self._terminated: if self._command is not None: self._command.terminate() self._command = None self._edit.terminate() self._completerLoaderThread.terminate() core.workspace().focusCurrentDocument() self._terminated = True def _updateCurrentCommand(self): """Try to parse line edit text and set current command """ if self._terminated: return newCommand = self._parseCurrentCommand() if newCommand is not self._command: if self._command is not None: self._command.updateCompleter.disconnect( self._updateCompletion) self._command.terminate() self._command = newCommand if self._command is not None: self._command.updateCompleter.connect(self._updateCompletion) self._updateCompletion() def _updateCompletion(self): """User edited text or moved cursor. Update inline and TreeView completion """ if self._command is not None: completer = self._command.completer() if completer is not None and completer.mustBeLoaded: self._loadingTimer.start() self._completerLoaderThread.loadCompleter( self._command, completer) else: self._applyCompleter(self._command, completer) else: self._applyCompleter(None, _HelpCompleter(self._commandClasses)) def _applyLoadingCompleter(self): """Set 'Loading...' message """ self._applyCompleter(None, StatusCompleter('<i>Loading...</i>')) def onCompleterLoaded(self, command, completer): """The method called from _CompleterLoaderThread when the completer is ready This code works in the GUI thread """ self._applyCompleter(command, completer) def _applyCompleter(self, command, completer): """Apply completer. Called by _updateCompletion or by thread function when Completer is constructed """ self._loadingTimer.stop() if command is not None: command.onCompleterLoaded(completer) if completer is None: completer = _HelpCompleter([command]) if self._edit.cursorPosition() == len( self._edit.text()): # if cursor at the end of text self._edit.setInlineCompletion(completer.inline()) self._model.setCompleter(completer) if completer.columnCount() > 1: self._table.resizeColumnToContents(0) self._table.setColumnWidth(0, self._table.columnWidth(0) + 20) # 20 px spacing between columns selItem = completer.autoSelectItem() if selItem: index = self._model.createIndex(selItem[0], selItem[1]) self._table.setCurrentIndex(index) def _onItemClicked(self, index): """Item in the TreeView has been clicked. Open file, if user selected it """ if self._command is not None: fullText = self._model.completer.getFullText(index.row()) if fullText is not None: self._command.onItemClicked(fullText) if self._tryExecCurrentCommand(): self.accept() return else: self._edit.setText(self._command.lineEditText()) self._updateCurrentCommand() self._edit.setFocus() def _onEnterPressed(self): """User pressed Enter or clicked item. Execute command, if possible """ if self._table.currentIndex().isValid(): self._onItemClicked(self._table.currentIndex()) else: self._tryExecCurrentCommand() def _tryExecCurrentCommand(self): if self._command is not None and self._command.isReadyToExecute(): self._command.execute() self.accept() return True else: return False def _chooseCommand(self, words): for cmd in self._commandClasses: if cmd.command == words[0]: return cmd, words[1:] isPath = words and (words[0].startswith('/') or words[0].startswith('./') or words[0].startswith('../') or words[0].startswith('~/') or words[0][1:3] == ':\\' or words[0][1:3] == ':/') isNumber = len(words) == 1 and all([c.isdigit() for c in words[0]]) def matches(cmd): if isPath: return cmd.isDefaultPathCommand elif isNumber: return cmd.isDefaultNumericCommand else: return cmd.isDefaultCommand for cmd in self._commandClasses: if matches(cmd): return cmd, words def _parseCurrentCommand(self): """ Parse text and try to get (command, completable word index) Return None if failed to parse """ # Split line text = self._edit.commandText() words = splitLine(text) if not words: return None # Find command cmdClass, args = self._chooseCommand(words) if isinstance(self._command, cmdClass): command = self._command else: command = cmdClass() # Try to make command object try: command.setArgs(args) except InvalidCmdArgs: return None else: return command def eventFilter(self, obj, event): if obj is self._edit: if event.type() == QEvent.KeyPress and \ event.key() in (Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp, Qt.Key_PageDown): return self._table.event(event) elif obj is self._table: if event.type() == QEvent.FocusIn: self._edit.setFocus() return True return False
class Window(QMainWindow): def __init__(self): super(Window, self).__init__() central_widget = QWidget() self._current_path = None self._use_suffix = False self._file_model = QFileSystemModel() self._file_model.setNameFilters(['*.jpg', '*.png']) self._file_model.setNameFilterDisables(False) self._file_model.setRootPath(QDir.rootPath()) self._file_selection_model = QItemSelectionModel(self._file_model) self._file_selection_model.currentChanged.connect(self._on_current_file_changed) self._file_tree = QTreeView(parent=self) self._file_tree.collapsed.connect(self._on_tree_expanded_collapsed) self._file_tree.expanded.connect(self._on_tree_expanded_collapsed) self._file_tree.setModel(self._file_model) self._file_tree.setSelectionModel(self._file_selection_model) self._file_tree.setColumnHidden(1, True) self._file_tree.setColumnHidden(2, True) self._file_tree.setColumnHidden(3, True) self._file_tree.header().hide() self._viewer = Viewer(Loader(24)) self._splitter = QSplitter(); self._splitter.addWidget(self._file_tree) self._splitter.addWidget(self._viewer) self._splitter.setStretchFactor(0, 0) self._splitter.setStretchFactor(1, 1) self._splitter.setCollapsible(0, False) self._layout = QGridLayout() self._layout.addWidget(self._splitter) self._switch_to_normal() central_widget.setLayout(self._layout) self._file_tree.installEventFilter(self); self.resize(800, 600) self.setWindowTitle('pyQtures') self.setCentralWidget(central_widget) self.show() def eventFilter(self, widget, event): if event.type() == QEvent.KeyPress: if event.key() == Qt.Key_Tab: self._toggle_path_suffix() return True return QMainWindow.eventFilter(self, widget, event) def _toggle_path_suffix(self): self._use_suffix = not self._use_suffix self._update_path() def _switch_to_fullscreen(self): self._splitter.widget(0).hide() self._layout.setMargin(0) self.showFullScreen() def _switch_to_normal(self): self._splitter.widget(0).show() self._layout.setMargin(4) self.showNormal() def keyPressEvent(self, key_event): # Signal handler. key = key_event.key() if self.isFullScreen(): self._full_screen_key_handler(key) else: self._normal_key_handler(key) def _full_screen_key_handler(self, key): if Qt.Key_Escape == key: self._switch_to_normal() elif Qt.Key_Return == key: self._switch_to_normal() elif Qt.Key_Up == key: self._go_to_sibling_image(-1) elif Qt.Key_Down == key: self._go_to_sibling_image(1) elif Qt.Key_Tab == key: self._toggle_path_suffix() def _go_to_sibling_image(self, offset): current = self._file_selection_model.currentIndex() nxt = current.sibling(current.row() + offset, current.column()) if (nxt.parent() != current.parent()): return # TODO(eustas): Iterate through dirs? self._file_selection_model.setCurrentIndex(nxt, QItemSelectionModel.SelectCurrent) def _normal_key_handler(self, key): if Qt.Key_Escape == key: QCoreApplication.instance().quit() elif Qt.Key_Return == key: self._switch_to_fullscreen() def _on_current_file_changed(self, new_current): new_path = self._file_model.filePath(new_current) if not self._current_path == new_path: self._current_path = new_path self._update_path() def _update_path(self): if not self._use_suffix: self._viewer.set_path(self._current_path) return self._viewer.reset_path() if not self._current_path: return selected_file = QFileInfo(self._current_path) if not selected_file.exists(): return selected_dir = selected_file.absoluteDir() file_name = selected_file.fileName() if not selected_dir.exists(): return if not selected_dir.cd('converted'): return suffixed_path = selected_dir.absoluteFilePath(file_name) self._viewer.set_path(suffixed_path) def _on_tree_expanded_collapsed(self, unused_index): QTimer.singleShot(1, lambda: self._file_tree.resizeColumnToContents(0))
class NavigatorDock(DockWidget): def __init__(self): DockWidget.__init__(self, core.mainWindow(), '&Navigator', QIcon(':/enkiicons/goto.png'), "Alt+N") self._tags = [] self._tree = QTreeView(self) self._tree.installEventFilter(self) self._tree.setHeaderHidden(True) self.setFocusProxy(self._tree) self._filterEdit = LineEdit(self) self._filterEdit.setClearButtonVisible(True) self._filterEdit.textEdited.connect(self._applyFilter) self._filterEdit.clearButtonClicked.connect(self._applyFilter) self._filterEdit.clearButtonClicked.connect(self._tree.setFocus) self._filterEdit.clearButtonClicked.connect(self._hideFilter) self._filterEdit.installEventFilter(self) self._displayWidget = QWidget(self) layout = QVBoxLayout(self._displayWidget) layout.addWidget(self._tree) layout.addWidget(self._filterEdit) layout.setContentsMargins(0, 0, 0, 0) self.setWidget(self._displayWidget) self._tagModel = _TagModel(self._tree) self._tagModel.jumpToTagDone.connect(self._hideFilter) self._tree.setModel(self._tagModel) self._tree.activated.connect(self._tagModel.onActivated) self._tree.clicked.connect(self._tagModel.onActivated) self._tagModel.modelAboutToBeReset.connect(self._onModelAboutToBeReset) self._tagModel.modelReset.connect(self._onModelReset) self._currentTagPath = None self._errorLabel = None self._installed = False def install(self): if not self._installed: core.mainWindow().addDockWidget(Qt.RightDockWidgetArea, self) core.actionManager().addAction("mView/aNavigator", self.showAction()) self._installed = True def remove(self): if self._installed: core.mainWindow().removeDockWidget(self) core.actionManager().removeAction("mView/aNavigator") self.hide() self._installed = False def setTags(self, tags): self._tags = tags self._setFilteredTags(tags) self._hideFilter() if self.widget() is not self._displayWidget: self.setWidget(self._displayWidget) self._displayWidget.show() if self._errorLabel is not None: self._errorLabel.hide() def _setFilteredTags(self, tags): self._tagModel.setTags(tags) def onError(self, error): self._displayWidget.hide() if self._errorLabel is None: self._errorLabel = QLabel(self) self._errorLabel.setWordWrap(True) self._errorLabel.setText(error) if not self.widget() is self._errorLabel: self.setWidget(self._errorLabel) self._errorLabel.show() self._displayWidget.hide() def _onModelAboutToBeReset(self): currIndex = self._tree.currentIndex() self._currentTagPath = self._tagModel.tagPathForIndex( currIndex) if currIndex.isValid() else None def _onModelReset(self): self._tree.expandAll() # restore current item if self._currentTagPath is not None: index = self._tagModel.indexForTagPath(self._currentTagPath) if index.isValid(): self._tree.setCurrentIndex(index) def eventFilter(self, object_, event): if object_ is self._tree: if event.type() == QEvent.KeyPress: if event.key() == Qt.Key_Backspace: if event.modifiers() == Qt.ControlModifier: self._onTreeCtrlBackspace() else: self._onTreeBackspace() return True elif event.text() and \ (event.text().isalnum() or event.text() == '_'): self._onTreeTextTyped(event.text()) return True elif object_ is self._filterEdit: if event.type() == QEvent.KeyPress: if event.key() in (Qt.Key_Up, Qt.Key_Down): self._tree.setFocus() self._tree.event(event) return True elif event.key() in (Qt.Key_Enter, Qt.Key_Return): currIndex = self._tree.currentIndex() if currIndex.isValid(): self._tagModel.onActivated(currIndex) return DockWidget.eventFilter(self, object_, event) def _hideFilter(self): hadText = self._filterEdit.text() != '' self._filterEdit.clear() self._filterEdit.hide() if hadText: self._applyFilter() def _applyFilter(self): text = self._filterEdit.text() if text: if not text.startswith('*'): text = '*' + text if not text.endswith('*'): text = text + '*' wildcard = text.lower() filteredTags = _filterTags(wildcard, self._tags) self._setFilteredTags(filteredTags) self._tree.expandAll() if filteredTags: firstMatchingTag = _findFirstMatching(wildcard, filteredTags) path = _tagPath(firstMatchingTag) index = self._tagModel.indexForTagPath(path) self._tree.setCurrentIndex(index) else: self._setFilteredTags(self._tags) if text: self._filterEdit.show() elif not self._filterEdit.hasFocus(): self._hideFilter() def _onTreeTextTyped(self, text): self._filterEdit.setText(self._filterEdit.text() + text) self._applyFilter() def _onTreeBackspace(self): text = self._filterEdit.text() if text: self._filterEdit.setText(text[:-1]) self._applyFilter() def _onTreeCtrlBackspace(self): self._hideFilter() self._applyFilter()