""" This document contains the tree widget used to display the editor document outline. """ from pyqode.core import icons from pyqode.core.panels import FoldingPanel from pyqode.core.modes.outline import OutlineMode from pyqode.qt import QtCore, QtGui, QtWidgets from pyqode.core.api import TextBlockHelper, TextBlockUserData, TextHelper class OutlineTreeWidget(QtWidgets.QTreeWidget): """ Displays the outline of a CodeEdit. To use this widget: 1. add an OutlineMode to CodeEdit 2. call set_editor with a CodeEdit instance to show it's outline. """ def __init__(self, parent=None): super(OutlineTreeWidget, self).__init__(parent) self._editor = None self._outline_mode = None self._folding_panel = None self._expanded_items = [] self.setHeaderHidden(True) self.itemClicked.connect(self._on_item_clicked) self.itemCollapsed.connect(self._on_item_state_changed) self.itemExpanded.connect(self._on_item_state_changed) self._updating = True def set_editor(self, editor): """ Sets the current editor. The widget display the structure of that editor. :param editor: CodeEdit """ if self._outline_mode: try: self._outline_mode.document_changed.disconnect( self._on_changed) except (TypeError, RuntimeError): pass if self._folding_panel: try: self._folding_panel.trigger_state_changed.disconnect( self._on_block_state_changed) except (TypeError, RuntimeError): pass self._editor = editor if self._editor is not None: try: self._folding_panel = editor.panels.get(FoldingPanel) except KeyError: pass else: self._folding_panel.trigger_state_changed.connect( self._on_block_state_changed) try: analyser = editor.modes.get(OutlineMode) except KeyError: self._outline_mode = None else: self._outline_mode = analyser analyser.document_changed.connect(self._on_changed) self._on_changed() def _on_item_state_changed(self, item): if self._updating: return block = item.data(0, QtCore.Qt.UserRole).block assert isinstance(item, QtWidgets.QTreeWidgetItem) item_state = not item.isExpanded() block_state = TextBlockHelper.is_collapsed(block) if item_state != block_state: self._updating = True self._folding_panel.toggle_fold_trigger(block) self._updating = False def _on_block_state_changed(self, block, state): if self._updating: return data = block.userData() if data is not None: try: item_state = not data.tree_item.isExpanded() if item_state != state: if state: self.collapseItem(data.tree_item) else: self.expandItem(data.tree_item) except AttributeError: # a block that is not represented in the tree view has # folded/unfolded, just ignore it pass def _on_changed(self): """ Update the tree items """ self._updating = True to_collapse = [] self.clear() if self._editor and self._outline_mode and self._folding_panel: items, to_collapse = self.to_tree_widget_items( self._outline_mode.definitions, to_collapse=to_collapse) if len(items): self.addTopLevelItems(items) self.expandAll() for item in reversed(to_collapse): self.collapseItem(item) self._updating = False return # no data root = QtWidgets.QTreeWidgetItem() root.setText(0, 'No data') root.setIcon(0, icons.icon( 'dialog-information', ':/pyqode-icons/rc/dialog-info.png', 'fa.info-circle')) self.addTopLevelItem(root) self._updating = False def _on_item_clicked(self, item): """ Go to the item position in the editor. """ if item: name = item.data(0, QtCore.Qt.UserRole) if name: go = name.block.blockNumber() helper = TextHelper(self._editor) if helper.current_line_nbr() != go: helper.goto_line(go, column=name.column) self._editor.setFocus() def to_tree_widget_items(self, definitions, to_collapse=None): """ Converts the list of top level definitions to a list of top level tree items. """ def convert(name, editor, to_collapse): ti = QtWidgets.QTreeWidgetItem() ti.setText(0, name.name) if isinstance(name.icon, list): icon = QtGui.QIcon.fromTheme( name.icon[0], QtGui.QIcon(name.icon[1])) else: icon = QtGui.QIcon(name.icon) ti.setIcon(0, icon) name.block = editor.document().findBlockByNumber(name.line) ti.setData(0, QtCore.Qt.UserRole, name) ti.setToolTip(0, name.description) block_data = name.block.userData() if block_data is None: block_data = TextBlockUserData() name.block.setUserData(block_data) block_data.tree_item = ti if to_collapse is not None and \ TextBlockHelper.is_collapsed(name.block): to_collapse.append(ti) for ch in name.children: ti_ch, to_collapse = convert(ch, editor, to_collapse) if ti_ch: ti.addChild(ti_ch) return ti, to_collapse items = [] for d in definitions: value, to_collapse = convert(d, self._editor, to_collapse) items.append(value) if to_collapse is not None: return items, to_collapse return items