diff --git a/SearchTools/GatherTools.py b/SearchTools/GatherTools.py
index 441d003..e69de29 100644
--- a/SearchTools/GatherTools.py
+++ b/SearchTools/GatherTools.py
@@ -1,2 +0,0 @@
-#          action = json.loads(str(index.model().data(index.siblingAtColumn(2))))
-#²        actionHandlers[action['handler']](action)
diff --git a/SearchTools/SearchTools.py b/SearchTools/SearchTools.py
index fef7d7a..a91d0d0 100644
--- a/SearchTools/SearchTools.py
+++ b/SearchTools/SearchTools.py
@@ -3,17 +3,53 @@ if True:
 
   def toolbarAction(act):
     print('show toolbar ' + act['toolbar'] + ' from workbenches ' + repr(act['workbenches']))
-  def toolAction(act):
-    print('start action for tool ' + act['toolbar'] + '.' + act['tool'] + ' from workbenches ' + repr(act['workbenches']))
   def subToolAction(act):
-    print('start action for tool ' + act['toolbar'] + '.' + act['tool'] + '.' + act['subTool'] + ' from workbenches ' + repr(act['workbenches']))
-  def documentObjectAction():
-    print('select object ' + o.Document.Name + '.' + o.Name)
-  def documentAction():
-    print('switch to document ' + o.Document.Name)
+    toolPath = act['toolbar'] + '.' + act['tool']
+    if 'subTool' in act:
+      toolPath = toolPath + '.' + act['subTool']
+    def runTool():
+      for the_toolbar in mw.findChildren(QtGui.QToolBar, act['toolbar']):
+        for tbt in the_toolbar.findChildren(QtGui.QToolButton):
+          if tbt.text() == act['tool']:
+            action = None
+            if 'subTool' in act:
+              men = tbt.menu()
+              if men:
+                for mac in men.actions():
+                  if mac.text() == act['subTool']:
+                    action = mac
+                    break
+            else:
+              action = tbt.defaultAction()
+            if 'showMenu' in act and act['showMenu']:
+              print('Popup submenu of tool ' + toolPath + ' available in workbenches ' + repr(act['workbenches']))
+              the_toolbar.show()
+              tbt.showMenu()
+              return True
+            elif action is not None:
+              print('Run action of tool ' + toolPath + ' available in workbenches ' + repr(act['workbenches']))
+              action.trigger()
+              return True
+      return False
+    if runTool():
+      return
+    else:
+      for workbench in act['workbenches']:
+        print('Activating workbench ' + workbench + ' to access tool ' + toolPath)
+        Gui.activateWorkbench(workbench)
+        if runTool():
+          return
+    print('Tool ' + toolPath + ' not found, was it offered by an extension that is no longer present?')
+  def documentObjectAction(act):
+    print('select object ' + act['document'] + '.' + act['object'])
+    Gui.Selection.addSelection(act['document'], act['object'])
+  def documentAction(act):
+    # Todo: this should also select the document in the tree view
+    print('switch to document ' + act['document'])
+    App.setActiveDocument(act['document'])
   actionHandlers = {
     'toolbarAction': toolbarAction,
-    'toolAction': toolAction,
+    'toolAction': subToolAction,
     'subToolAction': subToolAction,
     'documentObjectAction': documentObjectAction,
     'documentAction': documentAction
@@ -48,14 +84,22 @@ if True:
       self.listView = QtGui.QListView(self)
       self.listView.setWindowFlags(QtGui.Qt.ToolTip)
       self.listView.setWindowFlag(QtGui.Qt.FramelessWindowHint)
+      self.listView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
       self.listView.setModel(self.proxyModel)
       self.listView.setItemDelegate(itemDelegate) # https://stackoverflow.com/a/65930408/324969
       # make the QListView non-editable
       self.listView.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
+      # Create pane for showing extra info about the currently-selected tool
+      #self.extraInfo = QtGui.QLabel()
+      self.extraInfo = QtGui.QTextEdit()
+      self.extraInfo.setReadOnly(True)
+      self.extraInfo.setWindowFlags(QtGui.Qt.ToolTip)
+      self.extraInfo.setWindowFlag(QtGui.Qt.FramelessWindowHint)
+      self.extraInfo.setAlignment(QtCore.Qt.AlignTop)
       # Connect signals and slots
       self.textChanged.connect(self.filterModel)
-      self.listView.clicked.connect(self.selectResult)
-      self.listView.selectionModel().selectionChanged.connect(self.showExtraInfo)
+      self.listView.clicked.connect(lambda x: self.selectResult('select', x))
+      self.listView.selectionModel().selectionChanged.connect(self.onSelectionChanged)
       # Thanks to https://saurabhg.com/programming/search-box-using-qlineedit/ for indicating a few useful options
       ico = QtGui.QIcon(':/icons/help-browser.svg')
       #ico = QtGui.QIcon(':/icons/WhatsThis.svg')
@@ -69,15 +113,15 @@ if True:
       self.showList()
       super(SearchBox, self).focusInEvent(qFocusEvent)
     def focusOutEvent(self, qFocusEvent):
-      self.listView.hide()
+      self.hideList()
       super(SearchBox, self).focusOutEvent(qFocusEvent)
     def keyPressEvent(self, qKeyEvent):
       key = qKeyEvent.key()
       listMovementKeys = {
         QtCore.Qt.Key_Down:     lambda current, nbRows: (current + 1) % nbRows,
         QtCore.Qt.Key_Up:       lambda current, nbRows: (current - 1) % nbRows,
-        QtCore.Qt.Key_PageDown: lambda current, nbRows: max(current + min(1, self.maxVisibleRows / 2), nbRows),
-        QtCore.Qt.Key_PageUp:   lambda current, nbRows: min(current - min(1, self.maxVisibleRows / 2), 0),
+        QtCore.Qt.Key_PageDown: lambda current, nbRows: min(current + max(1, self.maxVisibleRows / 2), nbRows - 1),
+        QtCore.Qt.Key_PageUp:   lambda current, nbRows: max(current - max(1, self.maxVisibleRows / 2), 0),
       }
       acceptKeys = {
         QtCore.Qt.Key_Enter:  'select',
@@ -101,14 +145,67 @@ if True:
       elif key in acceptKeys:
         self.showList()
         if currentIndex.isValid():
-            self.selectResult(acceptKeys[key], currentIndex, currentIndex.data())
+            self.selectResult(acceptKeys[key], currentIndex)
       elif key in cancelKeys:
-        self.listView.hide()
+        self.hideList()
         self.clearFocus()
       else:
         self.showList()
         super(SearchBox, self).keyPressEvent(qKeyEvent)
     def showList(self):
+      self.setFloatingWidgetsGeometry()
+      self.listView.show()
+      self.showExtraInfo()
+    def hideList(self):
+      self.listView.hide()
+      self.hideExtraInfo()
+    def hideExtraInfo(self):
+      self.extraInfo.hide()
+    def selectResult(self, mode, index):
+      action = str(index.model().itemData(index.siblingAtColumn(2))[0])
+      self.hideList()
+      # TODO: allow other options, e.g. some items could act as combinators / cumulative filters
+      self.setText('')
+      self.filterModel(self.text())
+      # TODO: emit index relative to the base model
+      self.resultSelected.emit(index, action)
+    def filterModel(self, userInput):
+      def matches(s):
+        return userInput.lower() in s.lower()
+      def filterGroup(group):
+        if matches(group['text']):
+          # If a group matches, include the entire subtree (might need to disable this if it causes too much noise)
+          return group
+        else:
+          subitems = filterGroups(group['subitems'])
+          if len(subitems) > 0 or matches(group['text']):
+            return { 'text': group['text'], 'icon': group['icon'], 'action': group['action'], 'toolTipHTML':group['toolTipHTML'], 'subitems': subitems }
+          else:
+            return None
+      def filterGroups(groups):
+        groups = (filterGroup(group) for group in groups)
+        return [group for group in groups if group is not None]
+      def addGroups(filteredGroups, depth=0):
+        for group in filteredGroups:
+          mdl.appendRow([QtGui.QStandardItem(group['icon'] or QtGui.QIcon(), group['text']),
+                         QtGui.QStandardItem(str(depth)),
+                         QtGui.QStandardItem(group['action']),
+                         QtGui.QStandardItem(group['toolTipHTML'])])
+          addGroups(group['subitems'], depth+1)
+      mdl = QtGui.QStandardItemModel()
+      mdl.appendColumn([])
+      addGroups(filterGroups(self.itemGroups))
+      self.proxyModel.setSourceModel(mdl)
+      # TODO: try to find the already-highlighted item
+      nbRows = self.listView.model().rowCount()
+      if nbRows > 0:
+        index = self.listView.model().index(0, 0)
+        self.listView.setCurrentIndex(index)
+        self.setExtraInfo(index)
+      else:
+        self.clearExtraInfo()
+      #self.showList()
+    def setFloatingWidgetsGeometry(self):
       def getScreenPosition(widget):
         geo = widget.geometry()
         parent = widget.parent()
@@ -123,174 +220,46 @@ if True:
       # TODO: this can still bump into the bottom of the screen, in that case we should flip
       w = max(siz.width(), hint_w)
       h = 100
+      extraw = w # choose a preferred width that doesn't change all the time,
+                 # self.extraInfo.sizeHint().width() would change for every item.
+      extrax = x - extraw
       if screen is not None:
         scr = screen.geometry()
         x = min(scr.x() + scr.width() - hint_w, x)
-      self.listView.setGeometry(x, y, w, h)
-      self.listView.show()
-    def selectResult(self):
-      self.listView.hide()
-      # TODO: allow other options, e.g. some items could act as combinators / cumulative filters
-      self.setText('')
-      self.filterModel(self.text())
-      self.resultSelected.emit("TODO: index", "TODO: str")
-    def filterModel(self, userInput):
-      def matches(s):
-        return userInput.lower() in s.lower()
-      def filterGroup(group):
-        if matches(group['text']):
-          # If a group matches, include the entire subtree (might need to disable this if it causes too much noise)
-          return group
+        extraleftw = x - scr.x()
+        extrarightw = scr.x() + scr.width() - x
+        # flip the extraInfo if it doesn't fit on the screen
+        if extraleftw < extraw and extrarightw > extraleftw:
+          extrax = x + w
+          extraw = min(extrarightw, extraw)
         else:
-          subitems = filterGroups(group['subitems'])
-          if len(subitems) > 0 or matches(group['text']):
-            return { 'text': group['text'], 'icon': group['icon'], 'action': group['action'], 'subitems': subitems }
-          else:
-            return None
-      def filterGroups(groups):
-        groups = (filterGroup(group) for group in groups)
-        return [group for group in groups if group is not None]
-      def addGroups(filteredGroups, depth=0):
-        for group in filteredGroups:
-          mdl.appendRow([QtGui.QStandardItem(group['icon'] or QtGui.QIcon(), group['text']),
-                         QtGui.QStandardItem(str(depth)),
-                         QtGui.QStandardItem(group['action'])])
-          addGroups(group['subitems'], depth+1)
-      mdl = QtGui.QStandardItemModel()
-      mdl.appendColumn([])
-      addGroups(filterGroups(self.itemGroups))
+          extrax = x - extraw
+          extraw = min(extraleftw, extraw)
+      self.listView.setGeometry(x, y, w, h)
+      self.extraInfo.setGeometry(extrax, y, extraw, h)
+    def onSelectionChanged(self, selected, deselected):
+      # The list has .setSelectionMode(QtGui.QAbstractItemView.SingleSelection),
+      # so there is always at most one index in selected.indexes() and at most one
+      # index in deselected.indexes()
+      selected = selected.indexes()
+      deselected = deselected.indexes()
+      if len(selected) > 0:
+        index = selected[0]
+        self.setExtraInfo(index)
+        # Poor attempt to circumvent a glitch where the extra info pane stays visible after pressing Return
+        if not self.listView.isHidden():
+          self.showExtraInfo()
+      elif len(deselected) > 0:
+        self.hideExtraInfo()
+    def setExtraInfo(self, index):
+      toolTipHTML = str(index.model().itemData(index.siblingAtColumn(3))[0])
+      self.extraInfo.setText(toolTipHTML)
+      self.setFloatingWidgetsGeometry()
+    def clearExtraInfo(self, index):
+      self.extraInfo.setText('')
+    def showExtraInfo(self):
+      self.extraInfo.show()
 
-      print('setSourceModel for userInput ' + repr(userInput) + ' with ' + str(mdl.rowCount()) + ' rows.')
-      self.proxyModel.setSourceModel(mdl)
-      #print('TODO: do the actual filtering')
-      #flt = QtCore.QSortFilterProxyModel()
-      #flt.setSourceModel(self.model)
-      #flt.setFilterCaseSensitivity(QtCore.Qt.CaseSensitivity.CaseInsensitive)
-      #flt.setFilterWildcard(query)
-      #self.listView.setModel(flt)
-      # TODO: try to find the already-highlighted item
-      nbRows = self.listView.model().rowCount()
-      if nbRows > 0:
-        index = self.listView.model().index(0, 0)
-        self.listView.setCurrentIndex(index)
-      #self.showList()
-    def showExtraInfo(selected, deselected):
-      print('show extra info...', repr(selected))
-      pass
-  #mdl = QtCore.QStringListModel(['aaa', 'aab', 'aac', 'bxy', 'bac'])
-  #sbx = SearchBox(mdl, 10, None)
-  #sbx.show()
-
-  # Inspired by https://stackoverflow.com/a/7767999/324969
-  #class SearchQCompleter(QtGui.QCompleter):
-  #  def __init__(self, model, itemDelegate):
-  #    super(SearchQCompleter, self).__init__()
-  #    super(SearchQCompleter, self).setModel(QtCore.QIdentityProxyModel())
-  #    #https://stackoverflow.com/a/65930408/324969
-  #    super(SearchQCompleter, self).popup().setItemDelegate(itemDelegate)
-  #    self.setModel(model)
-  #  
-  #  def setModel(self, itemGroups):
-  #    self.itemGroups = itemGroups
-  #    self.filterModel('')
-  #  
-  #  def filterModel(self, userInput):
-  #    def matches(s):
-  #      return userInput.lower() in s.lower()
-  #    def filterGroup(group):
-  #      if matches(group['text']):
-  #        # If a group matches, include the entire subtree (might need to disable this if it causes too much noise)
-  #        return group
-  #      else:
-  #        subitems = filterGroups(group['subitems'])
-  #        if len(subitems) > 0 or matches(group['text']):
-  #          return { 'text': group['text'], 'icon': group['icon'], 'action': group['action'], 'subitems': subitems }
-  #        else:
-  #          return None
-  #    def filterGroups(groups):
-  #      groups = (filterGroup(group) for group in groups)
-  #      return [group for group in groups if group is not None]
-  #    def addGroups(filteredGroups, depth=0):
-  #      for group in filteredGroups:
-  #        mdl.appendRow([QtGui.QStandardItem(group['icon'] or QtGui.QIcon(), group['text']),
-  #                       QtGui.QStandardItem(str(depth)),
-  #                       QtGui.QStandardItem(group['action'])])
-  #        addGroups(group['subitems'], depth+1)
-  #    mdl = QtGui.QStandardItemModel()
-  #    mdl.appendColumn([])
-  #    addGroups(filterGroups(itemGroups))
-  #
-  #    print('setSourceModel for userInput ' + repr(userInput) + ' with ' + str(mdl.rowCount()) + ' rows.')
-  #    self.model().setSourceModel(mdl)
-  #    # https://stackoverflow.com/a/65930408/324969
-  #  
-  #  # the splitPath(self, path) method is called every time the input string changes, before
-  #  # drawing the completion list, we latch onto this method to also update the model to contain
-  #  # the appropriate results, as given by the custom filterAcceptsRow method above.
-  #  def splitPath(self, path):
-  #    self.filterModel(path)
-  #    # Pretend that the user endered the empty string, so that all items from the filteredProxyModel match.
-  #    return ''
-  #
-  #class SearchBox(QtGui.QLineEdit):
-  #  resultSelected = QtCore.Signal(int, str)
-  #  def __init__(self, itemGroups):
-  #    super(SearchBox, self).__init__()
-  #    qom = SearchQCompleter(itemGroups, IndentedItemDelegate())
-  #    qom.setMaxVisibleItems(20)
-  #    #qom.setCompletionMode(QtGui.QCompleter.CompletionMode.PopupCompletion)
-  #    # Thanks to https://saurabhg.com/programming/search-box-using-qlineedit/ for indicating a few useful options
-  #    ico = QtGui.QIcon(':/icons/help-browser.svg')
-  #    #ico = QtGui.QIcon(':/icons/WhatsThis.svg')
-  #    self.addAction(ico, QtGui.QLineEdit.LeadingPosition)
-  #    self.setClearButtonEnabled(True)
-  #    self.setPlaceholderText('Search tools, prefs & tree')
-  #    self.setFixedWidth(200)
-  #    # Signals & slots for enter / click
-  #    def completerActivated(index):
-  #      print('fooooooooooooo')
-  #      print(qom.model().rowCount(), index.row())
-  #      # TODO: run the action!
-  #      result = str(qom.model().data(index.siblingAtColumn(1)))
-  #      print('res='+result)
-  #      self.clear()
-  #      self.completer().setCompletionPrefix('')
-  #      self.resultSelected.emit(str(index), result)
-  #    def returnPressed():
-  #      #self.clear()
-  #      #self.completer().setCompletionPrefix('')
-  #      pass
-  #      #text = sea.text()
-  #      #self.clear()
-  #      #self.resultSelected.emit('text returnPressed' + text)
-  #    self.returnPressed.connect(returnPressed)
-  #    #QtCore.QObject.connect(self.completer(), QtCore.SIGNAL('activated(QModelIndex)'), completerActivated) #, QtCore.Qt.ConnectionType.QueuedConnection)
-  #    qom.activated.connect(completerActivated, QtCore.Qt.ConnectionType.DirectConnection) #, QtCore.Qt.ConnectionType.QueuedConnection)
-  #    #self.completer().activated.connect(returnPressedOrCompleterActivated)
-  #    def textChanged():
-  #      print('textChanged')
-  #      # Workaround: Clear completion prefix and still show the completion box when doing backspace after typing a single character
-  #      if self.text() == '':
-  #        self.completer().setCompletionPrefix(self.text())
-  #        self.completer().complete()
-  #    self.textChanged.connect(textChanged)
-  #    QtCore.QObject.connect(qom.popup(), QtCore.SIGNAL('clicked(QModelIndex)'), lambda x: print(x))
-  #    self.setCompleter(qom)
-  #  def focusInEvent(self, e):
-  #    super(SearchBox, self).focusInEvent(e)
-  #    self.completer().setCompletionPrefix(self.text())
-  #    self.completer().complete()
-  #    self.completer().setCurrentRow(1) # Does not work
-  #    #d=QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Down, QtCore.Qt.NoModifier)
-  #    #QtCore.QCoreApplication.postEvent(self, d)
-  #  def mousePressEvent(self, e):
-  #    super(SearchBox, self).mousePressEvent(e)
-  #    self.completer().setCompletionPrefix(self.text())
-  #    self.completer().complete()
-  #    self.completer().setCurrentRow(1) # Does not work
-  #    #d=QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Down, QtCore.Qt.NoModifier)
-  #    #QtCore.QCoreApplication.postEvent(self, d)
-  #
   mw = Gui.getMainWindow()
   mdi = mw.findChild(QtGui.QMdiArea)
 
@@ -299,16 +268,20 @@ if True:
   mwx = QtGui.QMainWindow()
   mbr = mw.findChildren(QtGui.QToolBar, 'File')[0]
 
-  all_tbs = set()
-  for wbname, workbench in Gui.listWorkbenches().items():
-    try:
-      tbs = workbench.listToolbars()
-    except:
-      continue
-    # careful, tbs contains all the toolbars of the workbench, including shared toolbars
-    for tb in tbs:
-      all_tbs.add(tb)
-  
+  def getAllToolbars():
+    all_tbs = dict()
+    for wbname, workbench in Gui.listWorkbenches().items():
+      try:
+        tbs = workbench.listToolbars()
+      except:
+        continue
+      # careful, tbs contains all the toolbars of the workbench, including shared toolbars
+      for tb in tbs:
+        if tb not in all_tbs:
+          all_tbs[tb] = set()
+        all_tbs[tb].add(wbname)
+    return all_tbs
+
   import json
   def serializeIcon(icon):
     iconPixmaps = {}
@@ -331,7 +304,8 @@ if True:
       'workbenches': tool['workbenches'],
       'toolbar': tool['toolbar'],
       'text': tool['text'],
-      'icon': serializeIcon(tool['icon'])
+      'icon': serializeIcon(tool['icon']),
+      'toolTipHTML': tool['toolTipHTML']
     }
   
   def deserializeIcon(iconPixmaps):
@@ -352,76 +326,68 @@ if True:
       'workbenches': tool['workbenches'],
       'toolbar': tool['toolbar'],
       'text': tool['text'],
-      'icon': deserializeIcon(tool['icon'])
+      'icon': deserializeIcon(tool['icon']),
+      'toolTipHTML': tool['toolTipHTML']
     }
   
-  #serialized = serializeTools([{'workbenches': ['wb1', 'wb2'], 'toolbar': 'tbr', 'text': 'aa', 'icon': ic}])
-  #serialized = serializeTools([])
-
-  #TODO:save in App.getUserAppDataDir() ################################################################################################################
+  # TODO: save serialized tools in App.getUserAppDataDir() ################################################################################################################
+  #       + never cache the document objects
 
   def GatherTools():
     itemGroups = []
-    for toolbarName in all_tbs:
+    all_tbs = getAllToolbars()
+    for toolbarName, toolbarIsInWorkbenches in all_tbs.items():
+      toolbarIsInWorkbenches = sorted(list(toolbarIsInWorkbenches))
       for the_toolbar in mw.findChildren(QtGui.QToolBar, toolbarName):
-          group = []
-
-          for tbt in the_toolbar.findChildren(QtGui.QToolButton):
-            text = tbt.text()
-            act = tbt.defaultAction()
-            if text != '':
-                # TODO: there also is the tooltip
-                icon = tbt.icon()
-                #sim.appendRow([QtGui.QStandardItem(icon, 't:' + text), QtGui.QStandardItem('tool')])
-                men = tbt.menu()
+        group = []
+        for tbt in the_toolbar.findChildren(QtGui.QToolButton):
+          text = tbt.text()
+          act = tbt.defaultAction()
+          if text != '':
+              # TODO: there also is the tooltip
+              icon = tbt.icon()
+              men = tbt.menu()
+              subgroup = []
+              if men:
                 subgroup = []
-                if men:
-                  subgroup = []
-                  for mac in men.actions():
-                    if mac.text():
-                      #all_actions.append(mac.trigger)
-                      action = { 'handler': 'subToolAction', 'workbenches': [], 'toolbar': toolbarName, 'tool': text, 'subtool': mac.text() }
-                      #print('whaaaat', str(len(all_actions)))
-                      subgroup.append({'icon':mac.icon(), 'text':mac.text(), 'action':json.dumps(action), 'subitems':[]})
-                #all_actions.append(tbt.actions().trigger)
-                #global lalala
-                #lalala=tbt
-                #print('whuuuut', str(len(all_actions)))
-                action = { 'handler': 'toolAction', 'workbenches': [], 'toolbar': toolbarName, 'tool': text }
-                group.append({'icon':icon, 'text':text, 'action': json.dumps(action), 'subitems': subgroup})
-
-                #viu = mw.findChildren(QtGui.QToolBar, 'View')[0]
-                #tbt = viu.findChildren(QtGui.QToolButton)
-                #men = tbt[3].menu()
-                #acs = men.actions()          # QtGui.QAction list
-                #act = tbt[2].defaultAction() # QtGui.QAction
-                #act.trigger()
-          action = { 'handler': 'toolbarAction', 'workbenches': [], 'toolbar': toolbarName }
-          itemGroups.append({
-            'icon': QtGui.QIcon(':/icons/Group.svg'),
-            'text': toolbarName,
-            'action': json.dumps(action),
-            'subitems': group
-          })
+                for mac in men.actions():
+                  if mac.text():
+                    action = { 'handler': 'subToolAction', 'workbenches': toolbarIsInWorkbenches, 'toolbar': toolbarName, 'tool': text, 'subTool': mac.text() }
+                    subgroup.append({'icon':mac.icon(), 'text':mac.text(), 'toolTipHTML': mac.toolTip(), 'action':json.dumps(action), 'subitems':[]})
+              # The default action of a menu changes dynamically, instead of triggering the last action, just show the menu.
+              action = { 'handler': 'toolAction', 'workbenches': toolbarIsInWorkbenches, 'toolbar': toolbarName, 'tool': text, 'showMenu': bool(men) }
+              group.append({'icon':icon, 'text':text, 'toolTipHTML': tbt.toolTip(), 'action': json.dumps(action), 'subitems': subgroup})
+        # TODO: move the 'workbenches' field to the itemgroup
+        action = { 'handler': 'toolbarAction', 'workbenches': toolbarIsInWorkbenches, 'toolbar': toolbarName }
+        itemGroups.append({
+          'icon': QtGui.QIcon(':/icons/Group.svg'),
+          'text': toolbarName,
+          'toolTipHTML': '<p>Display toolbar ' + toolbarName + '</p><p>This toolbar appears in the following workbenches: <ul>' + ''.join(['<li>' + wb + '</li>' for wb in toolbarIsInWorkbenches]) + '</ul></p>',
+          'action': json.dumps(action),
+          'subitems': group
+        })
     #
     def document(doc):
       group = []
       for o in doc.Objects:
-        #all_actions.append(lambda: Gui.Selection.addSelection(o.Document.Name, o.Name))
+        #all_actions.append(lambda: )
         action = { 'handler': 'documentObjectAction', 'document': o.Document.Name, 'object': o.Name }
         item = {
           'icon': o.ViewObject.Icon if o.ViewObject and o.ViewObject.Icon else None,
           'text': o.Label + ' (' + o.Name + ')',
+          # TODO: preview of the object
+          'toolTipHTML': '<p>' + o.Label + '</p><p><code>App.getDocument(' + repr(o.Document.Name) + ').getObject(' + repr(o.Name) + ')</code></p><p>' + '</p><p><img src="data:image/png;base64,.............."></p>',
           'action': json.dumps(action),
           'subitems': []
         }
         group.append(item)
 
-      # Todo: this should also select the document in the tree view
-      action = { 'handler': 'documentAction', 'document': o.Document.Name }
+      action = { 'handler': 'documentAction', 'document': doc.Name }
       itemGroups.append({
         'icon': QtGui.QIcon(':/icons/Document.svg'),
         'text': doc.Label + ' (' + doc.Name + ')',
+        # TODO: preview of the document
+        'toolTipHTML': '<p>' + doc.Label + '</p><p><code>App.getDocument(' + repr(doc.Name) + ')</code></p><p><img src="data:image/png;base64,.............."></p>',
         'action':json.dumps(action),
         'subitems': group })
     if App.ActiveDocument:
@@ -435,6 +401,7 @@ if True:
     return {
       'icon': serializeIcon(itemGroup['icon']),
       'text': itemGroup['text'],
+      'toolTipHTML': itemGroup['toolTipHTML'],
       'action': itemGroup['action'],
       'subitems': serializeItemGroups(itemGroup['subitems'])
     }
@@ -443,10 +410,10 @@ if True:
   def serialize(itemGroups):
     return json.dumps(serializeItemGroups(itemGroups))
   def deserializeItemGroup(itemGroup):
-    #print('dIG' + text + " : " + repr(itemGroup['icon'])[:100] + "......................." + repr(itemGroup['icon'])[-100:])
     return {
       'icon': deserializeIcon(itemGroup['icon']),
       'text': itemGroup['text'],
+      'toolTipHTML': itemGroup['toolTipHTML'],
       'action': itemGroup['action'],
       'subitems': deserializeItemGroups(itemGroup['subitems'])
     }
@@ -460,7 +427,10 @@ if True:
 
   #
   sea = SearchBox(itemGroups)
-  sea.resultSelected.connect(lambda x, y: print('aaa' + repr(y) + 'end'))
+  def onResultSelected(index, metadata):
+    action = json.loads(metadata)
+    actionHandlers[action['handler']](action)
+  sea.resultSelected.connect(onResultSelected)
   wax = QtGui.QWidgetAction(None)
   wax.setDefaultWidget(sea)
   #mbr.addWidget(sea)
diff --git a/SearchTools/SearchTools_notes.py b/SearchTools/SearchTools_notes.py
index 6a242ad..bd25882 100644
--- a/SearchTools/SearchTools_notes.py
+++ b/SearchTools/SearchTools_notes.py
@@ -191,3 +191,153 @@
       #    return True
       #filteredProxyModel = FilterProxyModel()
       #filteredProxyModel.setSourceModel(mdl)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+  #mdl = QtCore.QStringListModel(['aaa', 'aab', 'aac', 'bxy', 'bac'])
+  #sbx = SearchBox(mdl, 10, None)
+  #sbx.show()
+
+  # Inspired by https://stackoverflow.com/a/7767999/324969
+  #class SearchQCompleter(QtGui.QCompleter):
+  #  def __init__(self, model, itemDelegate):
+  #    super(SearchQCompleter, self).__init__()
+  #    super(SearchQCompleter, self).setModel(QtCore.QIdentityProxyModel())
+  #    #https://stackoverflow.com/a/65930408/324969
+  #    super(SearchQCompleter, self).popup().setItemDelegate(itemDelegate)
+  #    self.setModel(model)
+  #  
+  #  def setModel(self, itemGroups):
+  #    self.itemGroups = itemGroups
+  #    self.filterModel('')
+  #  
+  #  def filterModel(self, userInput):
+  #    def matches(s):
+  #      return userInput.lower() in s.lower()
+  #    def filterGroup(group):
+  #      if matches(group['text']):
+  #        # If a group matches, include the entire subtree (might need to disable this if it causes too much noise)
+  #        return group
+  #      else:
+  #        subitems = filterGroups(group['subitems'])
+  #        if len(subitems) > 0 or matches(group['text']):
+  #          return { 'text': group['text'], 'icon': group['icon'], 'action': group['action'], 'subitems': subitems }
+  #        else:
+  #          return None
+  #    def filterGroups(groups):
+  #      groups = (filterGroup(group) for group in groups)
+  #      return [group for group in groups if group is not None]
+  #    def addGroups(filteredGroups, depth=0):
+  #      for group in filteredGroups:
+  #        mdl.appendRow([QtGui.QStandardItem(group['icon'] or QtGui.QIcon(), group['text']),
+  #                       QtGui.QStandardItem(str(depth)),
+  #                       QtGui.QStandardItem(group['action'])])
+  #        addGroups(group['subitems'], depth+1)
+  #    mdl = QtGui.QStandardItemModel()
+  #    mdl.appendColumn([])
+  #    addGroups(filterGroups(itemGroups))
+  #
+  #    print('setSourceModel for userInput ' + repr(userInput) + ' with ' + str(mdl.rowCount()) + ' rows.')
+  #    self.model().setSourceModel(mdl)
+  #    # https://stackoverflow.com/a/65930408/324969
+  #  
+  #  # the splitPath(self, path) method is called every time the input string changes, before
+  #  # drawing the completion list, we latch onto this method to also update the model to contain
+  #  # the appropriate results, as given by the custom filterAcceptsRow method above.
+  #  def splitPath(self, path):
+  #    self.filterModel(path)
+  #    # Pretend that the user endered the empty string, so that all items from the filteredProxyModel match.
+  #    return ''
+  #
+  #class SearchBox(QtGui.QLineEdit):
+  #  resultSelected = QtCore.Signal(int, str)
+  #  def __init__(self, itemGroups):
+  #    super(SearchBox, self).__init__()
+  #    qom = SearchQCompleter(itemGroups, IndentedItemDelegate())
+  #    qom.setMaxVisibleItems(20)
+  #    #qom.setCompletionMode(QtGui.QCompleter.CompletionMode.PopupCompletion)
+  #    # Thanks to https://saurabhg.com/programming/search-box-using-qlineedit/ for indicating a few useful options
+  #    ico = QtGui.QIcon(':/icons/help-browser.svg')
+  #    #ico = QtGui.QIcon(':/icons/WhatsThis.svg')
+  #    self.addAction(ico, QtGui.QLineEdit.LeadingPosition)
+  #    self.setClearButtonEnabled(True)
+  #    self.setPlaceholderText('Search tools, prefs & tree')
+  #    self.setFixedWidth(200)
+  #    # Signals & slots for enter / click
+  #    def completerActivated(index):
+  #      print('fooooooooooooo')
+  #      print(qom.model().rowCount(), index.row())
+  #      # TODO: run the action!
+  #      result = str(qom.model().data(index.siblingAtColumn(1)))
+  #      print('res='+result)
+  #      self.clear()
+  #      self.completer().setCompletionPrefix('')
+  #      self.resultSelected.emit(str(index), result)
+  #    def returnPressed():
+  #      #self.clear()
+  #      #self.completer().setCompletionPrefix('')
+  #      pass
+  #      #text = sea.text()
+  #      #self.clear()
+  #      #self.resultSelected.emit('text returnPressed' + text)
+  #    self.returnPressed.connect(returnPressed)
+  #    #QtCore.QObject.connect(self.completer(), QtCore.SIGNAL('activated(QModelIndex)'), completerActivated) #, QtCore.Qt.ConnectionType.QueuedConnection)
+  #    qom.activated.connect(completerActivated, QtCore.Qt.ConnectionType.DirectConnection) #, QtCore.Qt.ConnectionType.QueuedConnection)
+  #    #self.completer().activated.connect(returnPressedOrCompleterActivated)
+  #    def textChanged():
+  #      print('textChanged')
+  #      # Workaround: Clear completion prefix and still show the completion box when doing backspace after typing a single character
+  #      if self.text() == '':
+  #        self.completer().setCompletionPrefix(self.text())
+  #        self.completer().complete()
+  #    self.textChanged.connect(textChanged)
+  #    QtCore.QObject.connect(qom.popup(), QtCore.SIGNAL('clicked(QModelIndex)'), lambda x: print(x))
+  #    self.setCompleter(qom)
+  #  def focusInEvent(self, e):
+  #    super(SearchBox, self).focusInEvent(e)
+  #    self.completer().setCompletionPrefix(self.text())
+  #    self.completer().complete()
+  #    self.completer().setCurrentRow(1) # Does not work
+  #    #d=QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Down, QtCore.Qt.NoModifier)
+  #    #QtCore.QCoreApplication.postEvent(self, d)
+  #  def mousePressEvent(self, e):
+  #    super(SearchBox, self).mousePressEvent(e)
+  #    self.completer().setCompletionPrefix(self.text())
+  #    self.completer().complete()
+  #    self.completer().setCurrentRow(1) # Does not work
+  #    #d=QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Down, QtCore.Qt.NoModifier)
+  #    #QtCore.QCoreApplication.postEvent(self, d)
+  #
+
+                #sim.appendRow([QtGui.QStandardItem(icon, 't:' + text), QtGui.QStandardItem('tool')])
+
+                      #all_actions.append(mac.trigger)
+
+                      #print('whaaaat', str(len(all_actions)))
+
+                #all_actions.append(tbt.actions().trigger)
+                #global lalala
+                #lalala=tbt
+                #print('whuuuut', str(len(all_actions)))
+
+                #viu = mw.findChildren(QtGui.QToolBar, 'View')[0]
+                #tbt = viu.findChildren(QtGui.QToolButton)
+                #men = tbt[3].menu()
+                #acs = men.actions()          # QtGui.QAction list
+                #act = tbt[2].defaultAction() # QtGui.QAction
+                #act.trigger()