From ab65597e762ed1afb860363a949cdcab716c0e91 Mon Sep 17 00:00:00 2001 From: Tim Green Date: Thu, 10 Oct 2024 23:13:13 +1100 Subject: [PATCH] feat: roles/3d_printing --- local.yml | 3 +- .../FreeCAD/Macro/CommandPalette.FCMacro | 188 ++++++++++++++++++ roles/3d_printing/tasks/main.yml | 9 + 3 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 roles/3d_printing/files/.local/share/FreeCAD/Macro/CommandPalette.FCMacro create mode 100644 roles/3d_printing/tasks/main.yml diff --git a/local.yml b/local.yml index a1c8cfc..d5cc898 100644 --- a/local.yml +++ b/local.yml @@ -92,10 +92,11 @@ - dev - never roles: - # - vscode + - vscode - nvm - rustup - conda + - 3d_printing - name: Chrome os hosts: localhost diff --git a/roles/3d_printing/files/.local/share/FreeCAD/Macro/CommandPalette.FCMacro b/roles/3d_printing/files/.local/share/FreeCAD/Macro/CommandPalette.FCMacro new file mode 100644 index 0000000..dbafa5d --- /dev/null +++ b/roles/3d_printing/files/.local/share/FreeCAD/Macro/CommandPalette.FCMacro @@ -0,0 +1,188 @@ +# TODO: show disabled actions at the bottom of search +# TODO: better fuzzy search? +# TODO: better visuals: +# see: https://stackoverflow.com/questions/41107202/pyqt-coloring-part-of-text-in-qlistwidget +# - better path: right-aligned + grey +# - show the keyboard shortcut for the item? +# - highlight matched parts of search +# - larger text +# TODO: toggle visibility on keyboard shortcut if already displayed + + +# Useful methods on QAction menu items +# text(): The text of the menu item with '&' before the shortcut key. +# toolTip(): on hover tooltip as html. +# shortcut(): shortcut keys for that action as a QKeySequence. +# trigger(): invoke that action. + +from PySide import QtGui, QtCore + +def enumerateActions(menu, path=[]): + actionList = [] + for action in menu.actions(): + if action.isSeparator(): + pass + elif action.menu(): + actionList.extend(enumerateActions(action.menu(), path + [action.text()])) + else: + actionList.append((action, path)) + return actionList + +def getMenuItems(): + def convertAction(action, path): + # For an unknown reason, there are a bunch of blank menu items -- filter them out. + if not action.text(): + return None + + # For now, don't include disabled actions. + if not action.isEnabled(): + return None + + pathText = "->".join(path) + fullText = f"{action.text()} ({pathText})" + # fixedText = action.text().replace('&', '') + fixedText = fullText.replace('&', '') + + item = PaletteListWidgetItem() + item.setText(fixedText) + item.setSearchText(fixedText.casefold()) + item.setToolTip(action.toolTip()) + item.setIcon(action.icon()) + item.setTrigger(action.trigger) + return item + + mw = Gui.getMainWindow() + menuBar = mw.menuBar() + return [convertAction(action, path) for action, path in enumerateActions(menuBar) if action is not None] + + +class PaletteDialog(QtGui.QDialog): + def __init__(self, parent): + super().__init__(parent) + self.installEventFilter(self) + + def eventFilter(self, obj, event): + # Hide the palette if it ever loses focus. + if event.type() == QtCore.QEvent.WindowDeactivate: + self.hide() + return True + return False + + +class PaletteLineEdit(QtGui.QLineEdit): + keyMappings = {} + + def keyPressEvent(self, event): + keyFn = self.keyMappings.get(event.key()) + if keyFn is not None: + keyFn() + return + + super().keyPressEvent(event) + + def setMappings(self, keyMappings): + self.keyMappings = keyMappings + +class PaletteListWidgetItem(QtGui.QListWidgetItem): + _searchText = None + _trigger = None + + def searchText(self): + return self._searchText + + def setSearchText(self, text): + self._searchText = text + + def matches(self, text): + return False + + def runTrigger(self): + self._trigger() + + def setTrigger(self, trigger): + self._trigger = trigger + +class CommandPalette: + MIN_DIALOG_WIDTH = 600 + MIN_DIALOG_HEIGHT = 400 + def activate(self): + items = getMenuItems() + dialog = PaletteDialog(Gui.getMainWindow()) + dialog.setObjectName("CommandPalette") + dialog.setMinimumWidth(self.MIN_DIALOG_WIDTH) + dialog.setMinimumHeight(self.MIN_DIALOG_HEIGHT) + dialog.setWindowFlags(dialog.windowFlags() | QtCore.Qt.FramelessWindowHint) + + + vbox = QtGui.QVBoxLayout(dialog) + + searchBar = PaletteLineEdit() + searchBar.textChanged.connect(self.textChanged) + searchBar.returnPressed.connect(self.returnPressed) + searchBar.setMappings({ + QtCore.Qt.Key.Key_Down: self.downPressed, + QtCore.Qt.Key.Key_Up: self.upPressed, + }) + vbox.addWidget(searchBar) + + commandList = QtGui.QListWidget() + vbox.addWidget(commandList) + + for item in items: + commandList.addItem(item) + + commandList.itemClicked.connect(self.itemClicked) + + commandList.show() + dialog.show() + + + self.commandList = commandList + self.dialog = dialog # prevent dialog from being garbage collected + + + def textChanged(self, newText): + foldedText = newText.casefold() + foldedTerms = foldedText.split() + currentItemSet = False + for i in range(0, self.commandList.count()): + item = self.commandList.item(i) + + if all(term in item.searchText() for term in foldedTerms): + item.setHidden(False) + if not currentItemSet: + self.commandList.setCurrentItem(item) + currentItemSet = True + else: + item.setHidden(True) + + def returnPressed(self): + # Run the selected action. + currentItem = self.commandList.currentItem() + if currentItem is not None: + self.dialog.hide() + currentItem.runTrigger() + + def downPressed(self): + # Select the next unhidden action, if any. + currentRow = self.commandList.currentRow() + for i in range(currentRow + 1, self.commandList.count()): + if not self.commandList.item(i).isHidden(): + self.commandList.setCurrentRow(i) + return + + def upPressed(self): + # Select the previous unhidden action, if any. + currentRow = self.commandList.currentRow() + for i in range(currentRow - 1, -1, -1): + if not self.commandList.item(i).isHidden(): + self.commandList.setCurrentRow(i) + return + + def itemClicked(self, item): + self.dialog.hide() + item.runTrigger() + + +palette = CommandPalette() +palette.activate() diff --git a/roles/3d_printing/tasks/main.yml b/roles/3d_printing/tasks/main.yml new file mode 100644 index 0000000..fce5940 --- /dev/null +++ b/roles/3d_printing/tasks/main.yml @@ -0,0 +1,9 @@ +--- +- block: + - name: Stow 3d printing + command: stow -v -t ~ -d {{ role_path }}/files -S . --no-folding + register: output + changed_when: '"LINK" in output.stderr' + + tags: + - 3d_printing