diff --git a/SearchTools.py b/SearchTools.py
index a91d0d0..ed97d48 100644
--- a/SearchTools.py
+++ b/SearchTools.py
@@ -1,431 +1,669 @@
-if True:
- from PySide import QtGui
+import os
+import FreeCAD as App
+import FreeCADGui
+from PySide import QtGui
+from PySide import QtCore
- def toolbarAction(act):
- print('show toolbar ' + act['toolbar'] + ' from workbenches ' + repr(act['workbenches']))
- def subToolAction(act):
- 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': subToolAction,
- 'subToolAction': subToolAction,
- 'documentObjectAction': documentObjectAction,
- 'documentAction': documentAction
- }
+"""
+from SearchTools import SearchTools
+from importlib import reload
+reload(SearchTools)
- # Inspired by https://stackoverflow.com/a/5443220/324969
- from PySide import QtGui
- #
- # Inspired by https://forum.qt.io/topic/69807/qtreeview-indent-entire-row
- class IndentedItemDelegate(QtGui.QStyledItemDelegate):
- def __init__(self):
- super(IndentedItemDelegate, self).__init__()
- def paint(self, painter, option, index):
- depth = int(option.widget.model().itemData(index.siblingAtColumn(1))[0])
- indent = 16 * depth
- option.rect.adjust(indent, 0, 0, 0)
- super(IndentedItemDelegate, self).paint(painter, option, index)
- #
- class SearchBox(QtGui.QLineEdit):
- resultSelected = QtCore.Signal(int, str)
- def __init__(self, itemGroups, itemDelegate = IndentedItemDelegate(), maxVisibleRows = 20, parent = None):
- # Call parent cosntructor
- super(SearchBox, self).__init__(parent)
- # Save arguments
- #self.model = model
- self.itemGroups = itemGroups
- self.maxVisibleRows = maxVisibleRows # TODO: use this to compute the correct height
- # Create proxy model
- self.proxyModel = QtCore.QIdentityProxyModel()
- #self.proxyModel.setModel(self.model)
- # Create list view
- 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(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')
- self.addAction(ico, QtGui.QLineEdit.LeadingPosition)
- self.setClearButtonEnabled(True)
- self.setPlaceholderText('Search tools, prefs & tree')
- self.setFixedWidth(200) # needed to avoid a change of width when the clear button appears/disappears
- # Initialize the model with the full list (assuming the text() is empty)
- self.filterModel(self.text())
- def focusInEvent(self, qFocusEvent):
+
+TODO for this project:
+* find a way to use the FreeCAD 3D viewer without segfaults or disappearing widgets
+* fix sync problem when moving too fast
+* split the list of tools vs. document objects (possibly already done?)
+* save to disk the list of tools
+* always display including when switching workbenches
+"""
+
+################################""
+
+class SafeViewer(QtGui.QWidget):
+ """FreeCAD uses a modified version of QuarterWidget, so the import pivy.quarter one will cause segfaults.
+ FreeCAD's FreeCADGui.createViewer() puts the viewer widget inside an MDI window, and detaching it without causing segfaults on exit is tricky.
+ This class contains some kludges to extract the viewer as a standalone widget and destroy it safely."""
+ def __init__(self, parent = None):
+ print('init')
+ super(SafeViewer, self).__init__()
+ self.viewer = FreeCADGui.createViewer()
+ self.graphicsView = self.viewer.graphicsView()
+ self.oldGraphicsViewParent = self.graphicsView.parent()
+ self.oldGraphicsViewParentParent = self.oldGraphicsViewParent.parent()
+ self.oldGraphicsViewParentParentParent = self.oldGraphicsViewParentParent.parent()
+
+ # Avoid segfault but still hide the undesired window by moving it to a new hidden MDI area.
+ self.hiddenQMDIArea = QtGui.QMdiArea()
+ self.hiddenQMDIArea.addSubWindow(self.oldGraphicsViewParentParentParent)
+
+ self.private_widget = self.oldGraphicsViewParent
+ self.private_widget.setParent(parent)
+
+ self.setLayout(QtGui.QVBoxLayout())
+ self.layout().addWidget(self.private_widget)
+
+ def fin(slf):
+ slf.finalizer()
+
+ import weakref
+ weakref.finalize(self, fin, self)
+
+ self.destroyed.connect(self.finalizer)
+
+ def finalizer(self):
+ print('fin')
+ # Cleanup in an order that doesn't cause a segfault:
+ self.private_widget.setParent(self.oldGraphicsViewParentParent)
+ self.oldGraphicsViewParentParentParent.close()
+ self.oldGraphicsViewParentParentParent = None
+ self.oldGraphicsViewParentParent = None
+ self.oldGraphicsViewParent = None
+ self.graphicsView = None
+ self.viewer = None
+ #self.parent = None
+ self.hiddenQMDIArea = None
+
+"""
+# Example use:
+from PySide import QtGui
+import pivy
+def mk(v):
+ w = QtGui.QMainWindow()
+ oldFocus = QtGui.QApplication.focusWidget()
+ sv.widget.setParent(w)
+ oldFocus.setFocus()
+ w.show()
+ col = pivy.coin.SoBaseColor()
+ col.rgb = (1, 0, 0)
+ trans = pivy.coin.SoTranslation()
+ trans.translation.setValue([0, 0, 0])
+ cub = pivy.coin.SoCube()
+ myCustomNode = pivy.coin.SoSeparator()
+ myCustomNode.addChild(col)
+ myCustomNode.addChild(trans)
+ myCustomNode.addChild(cub)
+ sv.viewer.getViewer().setSceneGraph(myCustomNode)
+ sv.viewer.fitAll()
+ return w
+sv = SafeViewer()
+ww=mk(sv)
+"""
+
+genericToolIcon = QtGui.QIcon(QtGui.QIcon(os.path.dirname(__file__) + '/Tango-Tools-spanner-hammer.svg'))
+
+def iconToBase64(icon, sz = QtCore.QSize(64,64), mode = QtGui.QIcon.Mode.Normal, state = QtGui.QIcon.State.On):
+ buf = QtCore.QBuffer()
+ buf.open(QtCore.QIODevice.WriteOnly)
+ icon.pixmap(sz, mode, state).save(buf, 'PNG')
+ return QtCore.QTextCodec.codecForName('UTF-8').toUnicode(buf.data().toBase64())
+def iconToHTML(icon, sz = 12, mode = QtGui.QIcon.Mode.Normal, state = QtGui.QIcon.State.On):
+ return '
'
+def refreshToolsAction(act):
+ print('Refresh list of tools')
+ refreshToolbars()
+def toolbarAction(act):
+ print('show toolbar ' + act['toolbar'] + ' from workbenches ' + repr(act['workbenches']))
+def subToolAction(act):
+ toolPath = act['toolbar'] + '.' + act['tool']
+ if 'subTool' in act:
+ toolPath = toolPath + '.' + act['subTool']
+ def runTool():
+ mw = Gui.getMainWindow()
+ 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)
+ FreeCADGui.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'])
+ FreeCADGui.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 = {
+ 'refreshTools': refreshToolsAction,
+ 'toolbar': toolbarAction,
+ 'tool': subToolAction,
+ 'subTool': subToolAction,
+ 'documentObject': documentObjectAction,
+ 'document': documentAction
+}
+
+# For some reason, the viewer always works except when used for two consecutive items in the search results: it then disappears after a short zoom-in+zoom-out animation.
+# I'm giving up on getting this viewer to work in a clean way, and will try swapping two instances so that the same one is never used twice in a row.
+safeViewerInstanceA = None
+safeViewerInstanceB = None
+
+import pivy
+class DocumentObjectToolTipWidget(QtGui.QWidget):
+ def __init__(self, nfo):
+ super(DocumentObjectToolTipWidget, self).__init__()
+ html = '
' + nfo['toolTip']['label'] + '
App.getDocument(' + repr(str(nfo['toolTip']['docName'])) + ').getObject(' + repr(str(nfo['toolTip']['name'])) + ')
'
+ description = QtGui.QTextEdit()
+ description.setReadOnly(True)
+ description.setAlignment(QtCore.Qt.AlignTop)
+ description.setText(html)
+
+ global safeViewerInstanceA, safeViewerInstanceB
+ if safeViewerInstanceA is None:
+ oldFocus = QtGui.QApplication.focusWidget()
+ safeViewerInstanceA = SafeViewer()
+ safeViewerInstanceB = SafeViewer()
+ oldFocus.setFocus()
+ # Tried setting the preview to a fixed size to prevent it from disappearing when changing its contents, this sets it to a fixed size but doesn't actually pick the size, .resize does that but isn't enough to fix the bug.
+ #safeViewerInstance.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed))
+ self.preview = safeViewerInstanceA
+ safeViewerInstanceA, safeViewerInstanceB = safeViewerInstanceB, safeViewerInstanceA
+
+ obj = App.getDocument(str(nfo['toolTip']['docName'])).getObject(str(nfo['toolTip']['name']))
+ ## dummy preview:
+ #col = pivy.coin.SoBaseColor()
+ #col.rgb = (1, 0, 0)
+ #trans = pivy.coin.SoTranslation()
+ #trans.translation.setValue([0, 0, 0])
+ #cub = pivy.coin.SoCube()
+ #myCustomNode = pivy.coin.SoSeparator()
+ #myCustomNode.addChild(col)
+ #myCustomNode.addChild(trans)
+ #myCustomNode.addChild(cub)
+ #self.preview.viewer.getViewer().setSceneGraph(myCustomNode)
+
+ # Tried hiding/detaching the preview to prevent it from disappearing when changing its contents
+ self.preview.viewer.stopAnimating()
+ self.preview.viewer.getViewer().setSceneGraph(obj.ViewObject.RootNode)
+ self.preview.viewer.fitAll()
+
+ lay = QtGui.QVBoxLayout()
+ self.setLayout(lay)
+ lay.addWidget(description)
+ lay.addWidget(self.preview)
+
+ def finalizer(self):
+ #self.preview.finalizer()
+ # Detach the widget so that it may be reused without getting deleted
+ self.preview.setParent(None)
+
+
+def easyToolTipWidget(html):
+ foo = QtGui.QTextEdit()
+ foo.setReadOnly(True)
+ foo.setAlignment(QtCore.Qt.AlignTop)
+ foo.setText(html)
+ return foo
+def refreshToolsToolTip(nfo):
+ return easyToolTipWidget(iconToHTML(genericToolIcon) + 'Load all workbenches to refresh this list of tools. This may take a minute, depending on the number of installed workbenches.
')
+def toolbarToolTip(nfo):
+ return easyToolTipWidget('Display toolbar ' + nfo['toolTip'] + '
This toolbar appears in the following workbenches:
' + ''.join(['- ' + iconToHTML(QtGui.QIcon(FreeCADGui.listWorkbenches()[wb].Icon)) + wb + '
' for wb in nfo['action']['workbenches']]) + '
')
+def subToolToolTip(nfo):
+ return easyToolTipWidget(iconToHTML(nfo['icon'], 32) + '' + nfo['toolTip'] + '
')
+def documentObjectToolTip(nfo):
+ return DocumentObjectToolTipWidget(nfo)
+def documentToolTip(nfo):
+ return easyToolTipWidget('' + nfo['toolTip']['label'] + '
App.getDocument(' + repr(str(nfo['toolTip']['name'])) + ')

')
+toolTipHandlers = {
+ 'refreshTools': refreshToolsToolTip,
+ 'toolbar': toolbarToolTip,
+ 'tool': subToolToolTip,
+ 'subTool': subToolToolTip,
+ 'documentObject': documentObjectToolTip,
+ 'document': documentToolTip
+}
+
+# Inspired by https://stackoverflow.com/a/5443220/324969
+# Inspired by https://forum.qt.io/topic/69807/qtreeview-indent-entire-row
+class IndentedItemDelegate(QtGui.QStyledItemDelegate):
+ def __init__(self):
+ super(IndentedItemDelegate, self).__init__()
+ def paint(self, painter, option, index):
+ depth = int(option.widget.model().itemData(index.siblingAtColumn(1))[0])
+ indent = 16 * depth
+ option.rect.adjust(indent, 0, 0, 0)
+ super(IndentedItemDelegate, self).paint(painter, option, index)
+#
+class SearchBox(QtGui.QLineEdit):
+ resultSelected = QtCore.Signal(int, str)
+ def __init__(self, itemGroups, itemDelegate = IndentedItemDelegate(), maxVisibleRows = 20, parent = None):
+ # Call parent cosntructor
+ super(SearchBox, self).__init__(parent)
+ # Save arguments
+ #self.model = model
+ self.itemGroups = itemGroups
+ self.maxVisibleRows = maxVisibleRows # TODO: use this to compute the correct height
+ # Create proxy model
+ self.proxyModel = QtCore.QIdentityProxyModel()
+ # Filtered model to which items are manually added. Store it as a property of the object instead of a local variable, to prevent grbage collection.
+ self.mdl = QtGui.QStandardItemModel()
+ #self.proxyModel.setModel(self.model)
+ # Create list view
+ 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.QWidget()
+ self.extraInfo.setWindowFlags(QtGui.Qt.ToolTip)
+ self.extraInfo.setWindowFlag(QtGui.Qt.FramelessWindowHint)
+ self.extraInfo.setLayout(QtGui.QVBoxLayout())
+ self.extraInfo.layout().setContentsMargins(0,0,0,0)
+ # Connect signals and slots
+ self.textChanged.connect(self.filterModel)
+ 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')
+ self.addAction(ico, QtGui.QLineEdit.LeadingPosition)
+ self.setClearButtonEnabled(True)
+ self.setPlaceholderText('Search tools, prefs & tree')
+ self.setFixedWidth(200) # needed to avoid a change of width when the clear button appears/disappears
+ # Initialize the model with the full list (assuming the text() is empty)
+ self.filterModel(self.text())
+ def focusInEvent(self, qFocusEvent):
+ self.showList()
+ super(SearchBox, self).focusInEvent(qFocusEvent)
+ def focusOutEvent(self, qFocusEvent):
+ 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: min(current + max(1, self.maxVisibleRows / 2), nbRows - 1),
+ QtCore.Qt.Key_PageUp: lambda current, nbRows: max(current - max(1, self.maxVisibleRows / 2), 0),
+ QtCore.Qt.Key_Home: lambda current, nbRows: 0,
+ QtCore.Qt.Key_End: lambda current, nbRows: nbRows - 1,
+ }
+ acceptKeys = {
+ QtCore.Qt.Key_Enter: 'select',
+ QtCore.Qt.Key_Return: 'select',
+ # space on a toolbar/category should toggle the entire category in the search results
+ QtCore.Qt.Key_Space: 'toggle',
+ }
+ cancelKeys = {
+ QtCore.Qt.Key_Escape: True,
+ }
+
+ currentIndex = self.listView.currentIndex()
+ if key in listMovementKeys:
self.showList()
- super(SearchBox, self).focusInEvent(qFocusEvent)
- def focusOutEvent(self, qFocusEvent):
- 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: 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',
- QtCore.Qt.Key_Return: 'select',
- # space on a toolbar/category should toggle the entire category in the search results
- QtCore.Qt.Key_Space: 'toggle',
- }
- cancelKeys = {
- QtCore.Qt.Key_Escape: True,
- }
-
- currentIndex = self.listView.currentIndex()
- if key in listMovementKeys:
- self.showList()
- if self.listView.isEnabled():
- currentRow = currentIndex.row()
- nbRows = self.listView.model().rowCount()
+ if self.listView.isEnabled():
+ currentRow = currentIndex.row()
+ nbRows = self.listView.model().rowCount()
+ if nbRows > 0:
newRow = listMovementKeys[key](currentRow, nbRows)
index = self.listView.model().index(newRow, 0)
self.listView.setCurrentIndex(index)
- elif key in acceptKeys:
- self.showList()
- if currentIndex.isValid():
- self.selectResult(acceptKeys[key], currentIndex)
- elif key in cancelKeys:
- 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])
+ elif key in acceptKeys:
+ self.showList()
+ if currentIndex.isValid():
+ self.selectResult(acceptKeys[key], currentIndex)
+ elif key in cancelKeys:
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)
+ 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:
- self.clearExtraInfo()
- #self.showList()
- def setFloatingWidgetsGeometry(self):
- def getScreenPosition(widget):
- geo = widget.geometry()
- parent = widget.parent()
- parentPos = getScreenPosition(parent) if parent is not None else QtCore.QPoint(0,0)
- return QtCore.QPoint(geo.x() + parentPos.x(), geo.y() + parentPos.y())
- pos = getScreenPosition(self)
- siz = self.size()
- screen = QtGui.QGuiApplication.screenAt(pos)
- x = pos.x()
- y = pos.y() + siz.height()
- hint_w = self.listView.sizeHint().width()
- # 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)
- 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)
+ subitems = filterGroups(group['subitems'])
+ if len(subitems) > 0 or matches(group['text']):
+ return { 'text': group['text'], 'icon': group['icon'], 'action': group['action'], 'toolTip':group['toolTip'], 'subitems': subitems }
else:
- 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()
+ return None
+ def filterGroups(groups):
+ groups = (filterGroup(group) for group in groups)
+ return [group for group in groups if group is not None]
+ self.mdl = QtGui.QStandardItemModel()
+ self.mdl.appendColumn([])
+ def addGroups(filteredGroups, depth=0):
+ for group in filteredGroups:
+ self.mdl.appendRow([QtGui.QStandardItem(group['icon'] or genericToolIcon, group['text']),
+ QtGui.QStandardItem(str(depth)),
+ QtGui.QStandardItem(group['action']),
+ QtGui.QStandardItem(json.dumps(serializeItemGroup(group)))])
+ addGroups(group['subitems'], depth+1)
+ addGroups(filterGroups(self.itemGroups))
+ self.proxyModel.setSourceModel(self.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()
+ parentPos = getScreenPosition(parent) if parent is not None else QtCore.QPoint(0,0)
+ return QtCore.QPoint(geo.x() + parentPos.x(), geo.y() + parentPos.y())
+ pos = getScreenPosition(self)
+ siz = self.size()
+ screen = QtGui.QGuiApplication.screenAt(pos)
+ x = pos.x()
+ y = pos.y() + siz.height()
+ hint_w = self.listView.sizeHint().width()
+ # TODO: this can still bump into the bottom of the screen, in that case we should flip
+ w = max(siz.width(), hint_w)
+ h = 200 # TODO: set height / size here according to desired number of items
+ 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)
+ 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:
+ 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):
+ nfo = str(index.model().itemData(index.siblingAtColumn(3))[0])
+ # TODO: move this outside of this class, probably use a single metadata
+ metadata = str(index.model().itemData(index.siblingAtColumn(2))[0])
+ nfo = deserializeItemGroup(json.loads(nfo))
+ nfo['action'] = json.loads(nfo['action'])
+ toolTipWidget = toolTipHandlers[nfo['action']['handler']](nfo)
+ #while len(self.extraInfo.children()) > 0:
+ # self.extraInfo.children()[0].setParent(None)
+ w = self.extraInfo.layout().takeAt(0)
+ while w:
+ if hasattr(w.widget(), 'finalizer'):
+ # The 3D viewer segfaults very easily if it is used after being destroyed, and some Python/C++ interop seems to overzealously destroys some widgets, including this one, too soon?
+ # Ensuring that we properly detacth the 3D viewer widget before discarding its parent seems to avoid these crashes.
+ w.widget().finalizer()
+ w.widget().setParent(None)
+ w = self.extraInfo.layout().takeAt(0)
+ self.extraInfo.layout().addWidget(toolTipWidget)
+ global toto
+ toto = self.extraInfo
+ #toolTipHTML = toolTipHandlers[nfo['action']['handler']](nfo)
+ #self.extraInfo.setText(toolTipHTML)
+ self.setFloatingWidgetsGeometry()
+ def clearExtraInfo(self):
+ self.extraInfo.setText('')
+ def showExtraInfo(self):
+ self.extraInfo.show()
- mw = Gui.getMainWindow()
- mdi = mw.findChild(QtGui.QMdiArea)
+def getAllToolbars():
+ all_tbs = dict()
+ for wbname, workbench in FreeCADGui.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
- wdg = QtGui.QWidget()
- lay = QtGui.QGridLayout(wdg)
- mwx = QtGui.QMainWindow()
- mbr = mw.findChildren(QtGui.QToolBar, 'File')[0]
+import json
+def serializeIcon(icon):
+ iconPixmaps = {}
+ for sz in icon.availableSizes():
+ strW = str(sz.width())
+ strH = str(sz.height())
+ iconPixmaps[strW] = {}
+ iconPixmaps[strW][strH] = {}
+ for strMode, mode in {'normal':QtGui.QIcon.Mode.Normal, 'disabled':QtGui.QIcon.Mode.Disabled, 'active':QtGui.QIcon.Mode.Active, 'selected':QtGui.QIcon.Mode.Selected}.items():
+ iconPixmaps[strW][strH][strMode] = {}
+ for strState, state in {'off':QtGui.QIcon.State.Off, 'on':QtGui.QIcon.State.On}.items():
+ iconPixmaps[strW][strH][strMode][strState] = iconToBase64(icon, sz, mode, state)
+ return iconPixmaps
+# workbenches is a list(str), toolbar is a str, text is a str, icon is a QtGui.QIcon
+def serializeTool(tool):
+ return {
+ 'workbenches': tool['workbenches'],
+ 'toolbar': tool['toolbar'],
+ 'text': tool['text'],
+ 'toolTip': tool['toolTip'],
+ 'icon': serializeIcon(tool['icon']),
+ }
- def getAllToolbars():
- all_tbs = dict()
- for wbname, workbench in Gui.listWorkbenches().items():
+def deserializeIcon(iconPixmaps):
+ ico = QtGui.QIcon()
+ for strW, wPixmaps in iconPixmaps.items():
+ for strH, hPixmaps in wPixmaps.items():
+ for strMode, modePixmaps in hPixmaps.items():
+ mode = {'normal':QtGui.QIcon.Mode.Normal, 'disabled':QtGui.QIcon.Mode.Disabled, 'active':QtGui.QIcon.Mode.Active, 'selected':QtGui.QIcon.Mode.Selected}[strMode]
+ for strState, statePixmap in modePixmaps.items():
+ state = {'off':QtGui.QIcon.State.Off, 'on':QtGui.QIcon.State.On}[strState]
+ pxm = QtGui.QPixmap()
+ pxm.loadFromData(QtCore.QByteArray.fromBase64(QtCore.QTextCodec.codecForName('UTF-8').fromUnicode(statePixmap)))
+ ico.addPixmap(pxm, mode, state)
+ return ico
+
+def deserializeTool(tool):
+ return {
+ 'workbenches': tool['workbenches'],
+ 'toolbar': tool['toolbar'],
+ 'text': tool['text'],
+ 'toolTip': tool['toolTip'],
+ 'icon': deserializeIcon(tool['icon']),
+ }
+
+def GatherTools():
+ itemGroups = []
+ itemGroups.append({
+ 'icon': genericToolIcon,
+ 'text': 'Refresh list of tools',
+ 'toolTip': '',
+ 'action': json.dumps({'handler': 'refreshTools'}),
+ 'subitems': []
+ })
+ all_tbs = getAllToolbars()
+ mw = FreeCADGui.getMainWindow()
+ 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()
+ men = tbt.menu()
+ subgroup = []
+ if men:
+ subgroup = []
+ for mac in men.actions():
+ if mac.text():
+ action = { 'handler': 'subTool', 'workbenches': toolbarIsInWorkbenches, 'toolbar': toolbarName, 'tool': text, 'subTool': mac.text() }
+ subgroup.append({'icon':mac.icon(), 'text':mac.text(), 'toolTip': 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': 'tool', 'workbenches': toolbarIsInWorkbenches, 'toolbar': toolbarName, 'tool': text, 'showMenu': bool(men) }
+ group.append({'icon':icon, 'text':text, 'toolTip': tbt.toolTip(), 'action': json.dumps(action), 'subitems': subgroup})
+ # TODO: move the 'workbenches' field to the itemgroup
+ action = { 'handler': 'toolbar', 'workbenches': toolbarIsInWorkbenches, 'toolbar': toolbarName }
+ itemGroups.append({
+ 'icon': QtGui.QIcon(':/icons/Group.svg'),
+ 'text': toolbarName,
+ 'toolTip': '',
+ 'action': json.dumps(action),
+ 'subitems': group
+ })
+ #
+ def document(doc):
+ group = []
+ for o in doc.Objects:
+ #all_actions.append(lambda: )
+ action = { 'handler': 'documentObject', '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
+ 'toolTip': { 'label': o.Label, 'name': o.Name, 'docName': o.Document.Name},
+ 'action': json.dumps(action),
+ 'subitems': []
+ }
+ group.append(item)
+
+ action = { 'handler': 'document', 'document': doc.Name }
+ itemGroups.append({
+ 'icon': QtGui.QIcon(':/icons/Document.svg'),
+ 'text': doc.Label + ' (' + doc.Name + ')',
+ # TODO: preview of the document
+ 'toolTip': { 'label': doc.Label, 'name': doc.Name},
+ 'action':json.dumps(action),
+ 'subitems': group })
+ if App.ActiveDocument:
+ document(App.ActiveDocument)
+ for docname, doc in App.listDocuments().items():
+ if not App.activeDocument or docname != App.ActiveDocument.Name:
+ document(doc)
+ return itemGroups
+
+def serializeItemGroup(itemGroup):
+ return {
+ 'icon': serializeIcon(itemGroup['icon']),
+ 'text': itemGroup['text'],
+ 'toolTip': itemGroup['toolTip'],
+ 'action': itemGroup['action'],
+ 'subitems': serializeItemGroups(itemGroup['subitems'])
+ }
+def serializeItemGroups(itemGroups):
+ return [serializeItemGroup(itemGroup) for itemGroup in itemGroups]
+def serialize(itemGroups):
+ return json.dumps(serializeItemGroups(itemGroups))
+def deserializeItemGroup(itemGroup):
+ return {
+ 'icon': deserializeIcon(itemGroup['icon']),
+ 'text': itemGroup['text'],
+ 'toolTip': itemGroup['toolTip'],
+ 'action': itemGroup['action'],
+ 'subitems': deserializeItemGroups(itemGroup['subitems'])
+ }
+def deserializeItemGroups(serializedItemGroups):
+ return [deserializeItemGroup(itemGroup) for itemGroup in serializedItemGroups]
+def deserialize(serializeItemGroups):
+ return deserializeItemGroups(json.loads(serializedItemGroups))
+
+itemGroups = None
+serializedItemGroups = None
+
+def refreshToolbars(loadAllWorkbenches = True):
+ global itemGroups, serializedItemGroups
+ if loadAllWorkbenches:
+ activeWorkbench = FreeCADGui.activeWorkbench().name()
+ lbl = QtGui.QLabel('Loading workbench … (…/…)')
+ lbl.show()
+ lst = FreeCADGui.listWorkbenches()
+ for i, wb in enumerate(lst):
+ msg = 'Loading workbench ' + wb + ' (' + str(i) + '/' + str(len(lst)) + ')'
+ print(msg)
+ lbl.setText(msg)
+ geo = lbl.geometry()
+ geo.setSize(lbl.sizeHint())
+ lbl.setGeometry(geo)
+ lbl.repaint()
try:
- tbs = workbench.listToolbars()
+ FreeCADGui.activateWorkbench(wb)
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 = {}
- for sz in icon.availableSizes():
- strW = str(sz.width())
- strH = str(sz.height())
- iconPixmaps[strW] = {}
- iconPixmaps[strW][strH] = {}
- for strMode, mode in {'normal':QtGui.QIcon.Mode.Normal, 'disabled':QtGui.QIcon.Mode.Disabled, 'active':QtGui.QIcon.Mode.Active, 'selected':QtGui.QIcon.Mode.Selected}.items():
- iconPixmaps[strW][strH][strMode] = {}
- for strState, state in {'off':QtGui.QIcon.State.Off, 'on':QtGui.QIcon.State.On}.items():
- buf = QtCore.QBuffer()
- buf.open(QtCore.QIODevice.WriteOnly)
- icon.pixmap(sz, mode, state).save(buf, 'PNG')
- iconPixmaps[strW][strH][strMode][strState] = QtCore.QTextCodec.codecForName('UTF-8').toUnicode(buf.data().toBase64())
- return iconPixmaps
- # workbenches is a list(str), toolbar is a str, text is a str, icon is a QtGui.QIcon
- def serializeTool(tool):
- return {
- 'workbenches': tool['workbenches'],
- 'toolbar': tool['toolbar'],
- 'text': tool['text'],
- 'icon': serializeIcon(tool['icon']),
- 'toolTipHTML': tool['toolTipHTML']
- }
-
- def deserializeIcon(iconPixmaps):
- ico = QtGui.QIcon()
- for strW, wPixmaps in iconPixmaps.items():
- for strH, hPixmaps in wPixmaps.items():
- for strMode, modePixmaps in hPixmaps.items():
- mode = {'normal':QtGui.QIcon.Mode.Normal, 'disabled':QtGui.QIcon.Mode.Disabled, 'active':QtGui.QIcon.Mode.Active, 'selected':QtGui.QIcon.Mode.Selected}[strMode]
- for strState, statePixmap in modePixmaps.items():
- state = {'off':QtGui.QIcon.State.Off, 'on':QtGui.QIcon.State.On}[strState]
- pxm = QtGui.QPixmap()
- pxm.loadFromData(QtCore.QByteArray.fromBase64(QtCore.QTextCodec.codecForName('UTF-8').fromUnicode(statePixmap)))
- ico.addPixmap(pxm, mode, state)
- return ico
-
- def deserializeTool(tool):
- return {
- 'workbenches': tool['workbenches'],
- 'toolbar': tool['toolbar'],
- 'text': tool['text'],
- 'icon': deserializeIcon(tool['icon']),
- 'toolTipHTML': tool['toolTipHTML']
- }
-
+ pass
+ lbl.hide()
+ FreeCADGui.activateWorkbench(activeWorkbench)
+ serializedItemGroups = serialize(GatherTools())
# TODO: save serialized tools in App.getUserAppDataDir() ################################################################################################################
# + never cache the document objects
-
- def GatherTools():
- itemGroups = []
- 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()
- men = tbt.menu()
- subgroup = []
- if men:
- subgroup = []
- 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': 'Display toolbar ' + toolbarName + '
This toolbar appears in the following workbenches:
' + ''.join(['- ' + wb + '
' for wb in toolbarIsInWorkbenches]) + '
',
- 'action': json.dumps(action),
- 'subitems': group
- })
- #
- def document(doc):
- group = []
- for o in doc.Objects:
- #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': '' + o.Label + '
App.getDocument(' + repr(o.Document.Name) + ').getObject(' + repr(o.Name) + ')
' + '

',
- 'action': json.dumps(action),
- 'subitems': []
- }
- group.append(item)
-
- 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': '' + doc.Label + '
App.getDocument(' + repr(doc.Name) + ')

',
- 'action':json.dumps(action),
- 'subitems': group })
- if App.ActiveDocument:
- document(App.ActiveDocument)
- for docname, doc in App.listDocuments().items():
- if not App.activeDocument or docname != App.ActiveDocument.Name:
- document(doc)
- return itemGroups
-
- def serializeItemGroup(itemGroup):
- return {
- 'icon': serializeIcon(itemGroup['icon']),
- 'text': itemGroup['text'],
- 'toolTipHTML': itemGroup['toolTipHTML'],
- 'action': itemGroup['action'],
- 'subitems': serializeItemGroups(itemGroup['subitems'])
- }
- def serializeItemGroups(itemGroups):
- return [serializeItemGroup(itemGroup) for itemGroup in itemGroups]
- def serialize(itemGroups):
- return json.dumps(serializeItemGroups(itemGroups))
- def deserializeItemGroup(itemGroup):
- return {
- 'icon': deserializeIcon(itemGroup['icon']),
- 'text': itemGroup['text'],
- 'toolTipHTML': itemGroup['toolTipHTML'],
- 'action': itemGroup['action'],
- 'subitems': deserializeItemGroups(itemGroup['subitems'])
- }
- def deserializeItemGroups(serializedItemGroups):
- return [deserializeItemGroup(itemGroup) for itemGroup in serializedItemGroups]
- def deserialize(serializeItemGroups):
- return deserializeItemGroups(json.loads(serializedItemGroups))
-
- serializedItemGroups = serialize(GatherTools())
itemGroups = deserialize(serializedItemGroups)
- #
+# Avoid garbage collection by storing the action in a global variable
+wax = None
+
+def addToolSearchBox():
+ global wax, itemGroups, serializedItemGroups
+ if itemGroups is None:
+ if serializedItemGroups is None:
+ refreshToolbars(False)
+ else:
+ itemGroups = deserialize(serializedItemGroups)
+
+ mw = FreeCADGui.getMainWindow()
+ mbr = mw.findChildren(QtGui.QToolBar, 'File')[0]
+ # Create search box widget
sea = SearchBox(itemGroups)
def onResultSelected(index, metadata):
action = json.loads(metadata)
@@ -434,4 +672,7 @@ if True:
wax = QtGui.QWidgetAction(None)
wax.setDefaultWidget(sea)
#mbr.addWidget(sea)
+ print("addAction" + repr(mbr) + ' add(' + repr(wax))
mbr.addAction(wax)
+
+addToolSearchBox()
\ No newline at end of file
diff --git a/Tango-Tools-spanner-hammer.svg b/Tango-Tools-spanner-hammer.svg
new file mode 100644
index 0000000..b54ec4b
--- /dev/null
+++ b/Tango-Tools-spanner-hammer.svg
@@ -0,0 +1,250 @@
+
+
+
\ No newline at end of file
diff --git a/Tango-Tools-spanner-hammer.svg.url b/Tango-Tools-spanner-hammer.svg.url
new file mode 100644
index 0000000..1e5c536
--- /dev/null
+++ b/Tango-Tools-spanner-hammer.svg.url
@@ -0,0 +1,2 @@
+https://commons.wikimedia.org/wiki/File:Tools-spanner-hammer.svg
+http://tango.freedesktop.org/Tango_Desktop_Project