text: """
  This document contains the tree widget used to display the editor document
  outline.
  
  """
  import weakref
  from pyqode.core import icons
  from pyqode.core.panels import FoldingPanel
  from pyqode.core.modes.outline import OutlineMode
  from qtpy 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.
  
      """
  
      sync_with_editor_changed = QtCore.Signal(bool)
  
      @property
      def sync_with_editor(self):
          return self._sync_with_editor
  
      @sync_with_editor.setter
      def sync_with_editor(self, value):
          if value != self.sync_with_editor:
              self._sync_with_editor = value
              self._action_sync.setChecked(value)
              self.sync_with_editor_changed.emit(value)
  
      def __init__(self, parent=None):
          super(OutlineTreeWidget, self).__init__(parent)
          self._context_actions = []
          self._definitions = None
          self._flattened_defs = None
          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
          self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
          self.customContextMenuRequested.connect(self._show_context_menu)
          self._sync_with_editor = True
          self._action_sync = QtWidgets.QAction(
              QtGui.QIcon.fromTheme('view-refresh'), _('Sync with editor'), None)
          self._action_sync.setCheckable(True)
          self._action_sync.setChecked(self._sync_with_editor)
          self._action_sync.toggled.connect(self._on_action_sync_toggled)
          self.add_context_action(self._action_sync)
  
      def set_editor(self, editor):
          """
          Sets the current editor. The widget display the structure of that
          editor.
  
          :param editor: CodeEdit
          """
          try:
              self._editor.cursorPositionChanged.disconnect(self.sync)
          except (AttributeError, TypeError, RuntimeError, ReferenceError):
              pass
          try:
              self._outline_mode.document_changed.disconnect(
                  self._on_changed)
          except (AttributeError, TypeError, RuntimeError, ReferenceError):
              pass
          try:
              self._folding_panel.trigger_state_changed.disconnect(
                  self._on_block_state_changed)
          except (AttributeError, TypeError, RuntimeError, ReferenceError):
              pass
          if editor:
              self._editor = weakref.proxy(editor)
          else:
              self._editor = None
  
          if editor is not None:
              editor.cursorPositionChanged.connect(self.sync)
              try:
                  self._folding_panel = weakref.proxy(
                      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 = weakref.proxy(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
          self.sync()
  
      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 flatten(definitions):
              """
              Flattens the document structure tree as a simple sequential list.
              """
              ret_val = []
              for de in definitions:
                  ret_val.append(de)
                  for sub_d in de.children:
                      ret_val.append(sub_d)
                      ret_val += flatten(sub_d.children)
              return ret_val
  
          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)
              name.tree_item = ti
              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
  
          self._definitions = definitions
          self._flattened_defs = flatten(self._definitions)
  
          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
  
      def sync(self):
          if not self.sync_with_editor or self._editor is None or \
                  not self._definitions:
              return
  
          to_select = None
          previous = None
          current_line = TextHelper(self._editor).current_line_nbr()
          for d in self._flattened_defs:
              if d.line == current_line:
                  to_select = d.tree_item
              elif d.line > current_line:
                  to_select = d.tree_item
                  if previous is not None:
                      to_select = previous.tree_item
              previous = d
              if to_select is not None:
                  break
          else:
              if previous:
                  to_select = previous.tree_item
  
          if to_select:
              try:
                  self.setCurrentItem(to_select)
              except RuntimeError:
                  # RuntimeError: wrapped C/C++ object of type QTreeWidgetItem
                  # has been deleted
                  pass
  
      def add_context_action(self, action):
          self._context_actions.append(action)
  
      def _show_context_menu(self, pos):
          mnu = QtWidgets.QMenu(self)
          for action in self._context_actions:
              mnu.addAction(action)
          mnu.exec_(self.mapToGlobal(pos))
  
      def _on_action_sync_toggled(self, value):
          self.sync_with_editor = value
          if value:
              self.sync()
  