Skip to content

Commit

Permalink
support user plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
eliranwong committed Oct 30, 2024
1 parent f5b9603 commit 91afa9f
Show file tree
Hide file tree
Showing 14 changed files with 116 additions and 73 deletions.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
# https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/
setup(
name=package,
version="0.1.36",
version="0.1.37",
python_requires=">=3.8, <3.13",
description=f"UniqueBible App is a cross-platform & offline bible application, integrated with high-quality resources and unique features. Developers: Eliran Wong and Oliver Tseng",
long_description=long_description,
Expand Down
8 changes: 8 additions & 0 deletions uniquebible/gui/AlephMainWindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,14 @@ def create_menu(self):
else:
addMenuItem(menu, plugin, self, lambda plugin=plugin: self.runPlugin(plugin), translation=False)
menu.addSeparator()
for plugin in FileUtil.fileNamesWithoutExtension(os.path.join(config.ubaUserDir, "plugins", "menu"), "py"):
if not plugin in config.excludeMenuPlugins:
if "_" in plugin:
feature, shortcut = plugin.split("_", 1)
addMenuItem(menu, feature, self, lambda plugin=plugin: self.runPlugin(plugin), shortcut=shortcut, translation=False)
else:
addMenuItem(menu, plugin, self, lambda plugin=plugin: self.runPlugin(plugin), translation=False)
menu.addSeparator()
addMenuItem(menu, "enableIndividualPlugins", self, self.enableIndividualPluginsWindow)

about_menu = self.menuBar().addMenu("{0}{1}".format(config.menuUnderline, config.thisTranslation["menu_about"]))
Expand Down
52 changes: 28 additions & 24 deletions uniquebible/gui/EnableIndividualPlugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,13 @@ def setupUI(self):
dataView1.setEditTriggers(QAbstractItemView.NoEditTriggers)
dataViewModel1 = QStandardItemModel(dataView1)
dataView1.setModel(dataViewModel1)
for plugin in FileUtil.fileNamesWithoutExtension(os.path.join(config.packageDir, "plugins", "startup"), "py"):
item = QStandardItem(plugin)
item.setToolTip(plugin)
item.setCheckable(True)
item.setCheckState(Qt.CheckState.Unchecked if plugin in config.excludeStartupPlugins else Qt.CheckState.Checked)
dataViewModel1.appendRow(item)
for ff in (config.packageDir, config.ubaUserDir):
for plugin in FileUtil.fileNamesWithoutExtension(os.path.join(ff, "plugins", "startup"), "py"):
item = QStandardItem(plugin)
item.setToolTip(plugin)
item.setCheckable(True)
item.setCheckState(Qt.CheckState.Unchecked if plugin in config.excludeStartupPlugins else Qt.CheckState.Checked)
dataViewModel1.appendRow(item)
dataViewModel1.itemChanged.connect(self.itemChanged1)
layout.addWidget(dataView1)
subLayout.addLayout(layout)
Expand All @@ -68,12 +69,13 @@ def setupUI(self):
dataView2.setEditTriggers(QAbstractItemView.NoEditTriggers)
dataViewModel2 = QStandardItemModel(dataView2)
dataView2.setModel(dataViewModel2)
for plugin in FileUtil.fileNamesWithoutExtension(os.path.join(config.packageDir, "plugins", "menu"), "py"):
item = QStandardItem(plugin)
item.setToolTip(plugin)
item.setCheckable(True)
item.setCheckState(Qt.CheckState.Unchecked if plugin in config.excludeMenuPlugins else Qt.CheckState.Checked)
dataViewModel2.appendRow(item)
for ff in (config.packageDir, config.ubaUserDir):
for plugin in FileUtil.fileNamesWithoutExtension(os.path.join(ff, "plugins", "menu"), "py"):
item = QStandardItem(plugin)
item.setToolTip(plugin)
item.setCheckable(True)
item.setCheckState(Qt.CheckState.Unchecked if plugin in config.excludeMenuPlugins else Qt.CheckState.Checked)
dataViewModel2.appendRow(item)
dataViewModel2.itemChanged.connect(self.itemChanged2)
layout.addWidget(dataView2)
subLayout.addLayout(layout)
Expand All @@ -84,12 +86,13 @@ def setupUI(self):
dataView3.setEditTriggers(QAbstractItemView.NoEditTriggers)
dataViewModel3 = QStandardItemModel(dataView3)
dataView3.setModel(dataViewModel3)
for plugin in FileUtil.fileNamesWithoutExtension(os.path.join(config.packageDir, "plugins", "context"), "py"):
item = QStandardItem(plugin)
item.setToolTip(plugin)
item.setCheckable(True)
item.setCheckState(Qt.CheckState.Unchecked if plugin in config.excludeContextPlugins else Qt.CheckState.Checked)
dataViewModel3.appendRow(item)
for ff in (config.packageDir, config.ubaUserDir):
for plugin in FileUtil.fileNamesWithoutExtension(os.path.join(ff, "plugins", "context"), "py"):
item = QStandardItem(plugin)
item.setToolTip(plugin)
item.setCheckable(True)
item.setCheckState(Qt.CheckState.Unchecked if plugin in config.excludeContextPlugins else Qt.CheckState.Checked)
dataViewModel3.appendRow(item)
dataViewModel3.itemChanged.connect(self.itemChanged3)
layout.addWidget(dataView3)
subLayout.addLayout(layout)
Expand All @@ -100,12 +103,13 @@ def setupUI(self):
dataView4.setEditTriggers(QAbstractItemView.NoEditTriggers)
dataViewModel4 = QStandardItemModel(dataView4)
dataView4.setModel(dataViewModel4)
for plugin in FileUtil.fileNamesWithoutExtension(os.path.join(config.packageDir, "plugins", "shutdown"), "py"):
item = QStandardItem(plugin)
item.setToolTip(plugin)
item.setCheckable(True)
item.setCheckState(Qt.CheckState.Unchecked if plugin in config.excludeShutdownPlugins else Qt.CheckState.Checked)
dataViewModel4.appendRow(item)
for ff in (config.packageDir, config.ubaUserDir):
for plugin in FileUtil.fileNamesWithoutExtension(os.path.join(ff, "plugins", "shutdown"), "py"):
item = QStandardItem(plugin)
item.setToolTip(plugin)
item.setCheckable(True)
item.setCheckState(Qt.CheckState.Unchecked if plugin in config.excludeShutdownPlugins else Qt.CheckState.Checked)
dataViewModel4.appendRow(item)
dataViewModel4.itemChanged.connect(self.itemChanged4)
layout.addWidget(dataView4)
subLayout.addLayout(layout)
Expand Down
2 changes: 1 addition & 1 deletion uniquebible/gui/MainWindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -5372,7 +5372,7 @@ def addMenuPluginButton(self, plugin, feature, icon, toolbar, translation=True):
self.addMaterialIconButton(feature, icon, partial(self.runPlugin, plugin), toolbar, translation=translation)

def isMenuPlugin(self, plugin):
return os.path.isfile(os.path.join(config.packageDir, "plugins", "menu", "{0}.py".format(plugin)))
return os.path.isfile(os.path.join(config.packageDir, "plugins", "menu", "{0}.py".format(plugin))) or os.path.isfile(os.path.join(config.ubaUserDir, "plugins", "menu", "{0}.py".format(plugin)))

def runPlugin(self, fileName, _=None):
self.crossPlatform.runPlugin(fileName)
Expand Down
9 changes: 9 additions & 0 deletions uniquebible/gui/MaterialMainWindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,15 @@ def setSubMenuVlcSpeed():
# For both PySide2 and PyQt5
addMenuItem(menu, plugin, self, partial(self.runPlugin, plugin), translation=False)
menu.addSeparator()
for plugin in FileUtil.fileNamesWithoutExtension(os.path.join(config.ubaUserDir, "plugins", "menu"), "py"):
if not plugin in config.excludeMenuPlugins:
if "_" in plugin:
feature, shortcut = plugin.split("_", 1)
feature = "{0} | {1}".format(feature, shortcut)
addMenuItem(menu, feature, self, partial(self.runPlugin, plugin), shortcut=shortcut, translation=False)
else:
addMenuItem(menu, plugin, self, partial(self.runPlugin, plugin), translation=False)
menu.addSeparator()
addMenuItem(menu, "enableIndividualPlugins", self, self.enableIndividualPluginsWindow)

# information
Expand Down
30 changes: 16 additions & 14 deletions uniquebible/gui/SystemTrayMenu.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@
# Menu plugins
if config.enablePlugins:
subMenu = QMenu()
for index, plugin in enumerate(FileUtil.fileNamesWithoutExtension(os.path.join(config.packageDir, "plugins", "menu"), "py")):
if not plugin in config.excludeMenuPlugins:
feature, *_ = plugin.split("_", 1)
exec("menuPlugin{0} = QAction(feature)".format(index))
#exec("menuPlugin{0}.triggered.connect(config.mainWindow.showFromTray)".format(index))
exec("menuPlugin{0}.triggered.connect(partial(config.mainWindow.runPlugin, plugin))".format(index))
exec("subMenu.addAction(menuPlugin{0})".format(index))
for ff in (config.packageDir, config.ubaUserDir):
for index, plugin in enumerate(FileUtil.fileNamesWithoutExtension(os.path.join(ff, "plugins", "menu"), "py")):
if not plugin in config.excludeMenuPlugins:
feature, *_ = plugin.split("_", 1)
exec("menuPlugin{0} = QAction(feature)".format(index))
#exec("menuPlugin{0}.triggered.connect(config.mainWindow.showFromTray)".format(index))
exec("menuPlugin{0}.triggered.connect(partial(config.mainWindow.runPlugin, plugin))".format(index))
exec("subMenu.addAction(menuPlugin{0})".format(index))
menuPlugins = QAction(config.thisTranslation["menu_plugins"])
menuPlugins.setMenu(subMenu)
trayMenu.addAction(menuPlugins)
Expand Down Expand Up @@ -154,13 +155,14 @@
# Context plugins
if config.enablePlugins:
subMenu = QMenu()
for index, plugin in enumerate(FileUtil.fileNamesWithoutExtension(os.path.join(config.packageDir, "plugins", "context"), "py")):
if not plugin in config.excludeContextPlugins:
feature, *_ = plugin.split("_", 1)
exec("contextPlugin{0} = QAction(feature)".format(index))
#exec("contextPlugin{0}.triggered.connect(config.mainWindow.showFromTray)".format(index))
exec("contextPlugin{0}.triggered.connect(partial(config.mainWindow.runContextPluginOnClipboardContent, plugin))".format(index))
exec("subMenu.addAction(contextPlugin{0})".format(index))
for ff in (config.packageDir, config.ubaUserDir):
for index, plugin in enumerate(FileUtil.fileNamesWithoutExtension(os.path.join(ff, "plugins", "context"), "py")):
if not plugin in config.excludeContextPlugins:
feature, *_ = plugin.split("_", 1)
exec("contextPlugin{0} = QAction(feature)".format(index))
#exec("contextPlugin{0}.triggered.connect(config.mainWindow.showFromTray)".format(index))
exec("contextPlugin{0}.triggered.connect(partial(config.mainWindow.runContextPluginOnClipboardContent, plugin))".format(index))
exec("subMenu.addAction(contextPlugin{0})".format(index))
contextPlugins = QAction(config.thisTranslation["runContextPluginOnClipboardContent"])
contextPlugins.setMenu(subMenu)
trayMenu.addAction(contextPlugins)
Expand Down
34 changes: 19 additions & 15 deletions uniquebible/gui/WebEngineView.py
Original file line number Diff line number Diff line change
Expand Up @@ -940,20 +940,20 @@ def addMenuActions(self):
self.addAction(separator)

subMenu = QMenu()

for plugin in FileUtil.fileNamesWithoutExtension(os.path.join(config.packageDir, "plugins", "context"), "py"):
if not plugin in config.excludeContextPlugins:
action = QAction(self)
if "_" in plugin:
feature, shortcut = plugin.split("_", 1)
action.setText("{0} | {1}".format(feature, shortcut) if shortcut else feature)
# The following line does not work
#action.setShortcut(QKeySequence(shortcut))
self.parent.parent.addContextPluginShortcut(plugin, shortcut)
else:
action.setText(plugin)
action.triggered.connect(partial(self.runPlugin, plugin))
subMenu.addAction(action)
for ff in (config.packageDir, config.ubaUserDir):
for plugin in FileUtil.fileNamesWithoutExtension(os.path.join(ff, "plugins", "context"), "py"):
if not plugin in config.excludeContextPlugins:
action = QAction(self)
if "_" in plugin:
feature, shortcut = plugin.split("_", 1)
action.setText("{0} | {1}".format(feature, shortcut) if shortcut else feature)
# The following line does not work
#action.setShortcut(QKeySequence(shortcut))
self.parent.parent.addContextPluginShortcut(plugin, shortcut)
else:
action.setText(plugin)
action.triggered.connect(partial(self.runPlugin, plugin))
subMenu.addAction(action)

separator = QAction(self)
separator.setSeparator(True)
Expand Down Expand Up @@ -1008,7 +1008,11 @@ def runPlugin(self, fileName, selectedText=None, activeSelection=False):
config.contextSource = self
config.pluginContext = selectedText
script = os.path.join(config.packageDir, "plugins", "context", "{0}.py".format(fileName))
self.parent.parent.execPythonFile(script)
if os.path.isfile(script):
self.parent.parent.execPythonFile(script)
script = os.path.join(config.ubaUserDir, "plugins", "context", "{0}.py".format(fileName))
if os.path.isfile(script):
self.parent.parent.execPythonFile(script)
config.pluginContext = ""
config.contextSource = None

Expand Down
4 changes: 4 additions & 0 deletions uniquebible/latest_changes.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
PIP package:

0.1.37

* support user plugins, stored in ~/UniqueBible/plugins

0.1.36

* improved commentary output for terminal and stream mode
Expand Down
9 changes: 5 additions & 4 deletions uniquebible/startup/guiQt.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,11 @@ def exitApplication():
config.mainWindow.closeMediaPlayer()
# Run shutdown plugins
if config.enablePlugins:
for plugin in FileUtil.fileNamesWithoutExtension(os.path.join(config.packageDir, "plugins", "shutdown"), "py"):
if not plugin in config.excludeShutdownPlugins:
script = os.path.join(config.packageDir, "plugins", "shutdown", "{0}.py".format(plugin))
config.mainWindow.execPythonFile(script)
for ff in (config.packageDir, config.ubaUserDir):
for plugin in FileUtil.fileNamesWithoutExtension(os.path.join(ff, "plugins", "shutdown"), "py"):
if not plugin in config.excludeShutdownPlugins:
script = os.path.join(ff, "plugins", "shutdown", "{0}.py".format(plugin))
config.mainWindow.execPythonFile(script)
ConfigUtil.save()
NoteService.close()
if (config.runMode == "docker") and config.restartUBA:
Expand Down
9 changes: 5 additions & 4 deletions uniquebible/startup/share.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ def cleanupTempFiles():
# Run startup plugins
def runStartupPlugins():
if config.enablePlugins:
for plugin in FileUtil.fileNamesWithoutExtension(os.path.join(config.packageDir, "plugins", "startup"), "py"):
if not plugin in config.excludeStartupPlugins:
script = os.path.join(config.packageDir, "plugins", "startup", "{0}.py".format(plugin))
config.mainWindow.execPythonFile(script)
for ff in (config.packageDir, config.ubaUserDir):
for plugin in FileUtil.fileNamesWithoutExtension(os.path.join(ff, "plugins", "startup"), "py"):
if not plugin in config.excludeStartupPlugins:
script = os.path.join(ff, "plugins", "startup", "{0}.py".format(plugin))
config.mainWindow.execPythonFile(script)

def printContentOnConsole(text):
if not "html-text" in sys.modules:
Expand Down
6 changes: 5 additions & 1 deletion uniquebible/util/CrossPlatform.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,11 @@ def getHistory(self, view):

def runPlugin(self, fileName):
script = os.path.join(config.packageDir, "plugins", "menu", "{0}.py".format(fileName))
self.execPythonFile(script)
if os.path.isfile(script):
self.execPythonFile(script)
script = os.path.join(config.ubaUserDir, "plugins", "menu", "{0}.py".format(fileName))
if os.path.isfile(script):
self.execPythonFile(script)

def execPythonFile(self, script):
if config.developer:
Expand Down
13 changes: 9 additions & 4 deletions uniquebible/util/LocalCliHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -777,17 +777,22 @@ def execFile(self):
return ""

def plugins(self, default="", accept_default=False):
availablePlugins = FileUtil.fileNamesWithoutExtension(os.path.join(config.packageDir, "plugins", "terminal"), "py")
availablePlugins = FileUtil.fileNamesWithoutExtension(os.path.join(config.packageDir, "plugins", "terminal"), "py") + FileUtil.fileNamesWithoutExtension(os.path.join(config.ubaUserDir, "plugins", "terminal"), "py")
if default and accept_default:
userInput = self.simplePrompt(default=default, accept_default=accept_default)
else:
userInput = self.dialogs.getValidOptions(options=availablePlugins, title="Plugins", default=default)
if not userInput or userInput.lower() == config.terminal_cancel_action:
return self.cancelAction()
try:
#filepath = os.path.join(config.packageDir, "plugins", "terminal", f"{availablePlugins[int(userInput)]}.py")
filepath = os.path.join(config.packageDir, "plugins", "terminal", f"{userInput}.py")
self.execPythonFile(filepath)
filepath1 = os.path.join(config.packageDir, "plugins", "terminal", f"{userInput}.py")
filepath2 = os.path.join(config.ubaUserDir, "plugins", "terminal", f"{userInput}.py")
if os.path.isfile(filepath1):
self.execPythonFile(filepath1)
elif os.path.isfile(filepath2):
self.execPythonFile(filepath2)
else:
return self.printInvalidOptionEntered()
return ""
except:
return self.printInvalidOptionEntered()
Expand Down
9 changes: 5 additions & 4 deletions uniquebible/util/RemoteHttpHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,10 +236,11 @@ def runStartupPlugins(self):
config.bibleWindowContentTransformers = []
config.customCommandShortcuts = {}
if config.enablePlugins:
for plugin in FileUtil.fileNamesWithoutExtension(os.path.join(config.packageDir, "plugins", "startup"), "py"):
if not plugin in config.excludeStartupPlugins:
script = os.path.join(config.packageDir, "plugins", "startup", "{0}.py".format(plugin))
config.mainWindow.execPythonFile(script)
for ff in (config.packageDir, config.ubaUserDir):
for plugin in FileUtil.fileNamesWithoutExtension(os.path.join(ff, "plugins", "startup"), "py"):
if not plugin in config.excludeStartupPlugins:
script = os.path.join(ff, "plugins", "startup", "{0}.py".format(plugin))
config.mainWindow.execPythonFile(script)

def execPythonFile(self, script):
self.textCommandParser.parent.execPythonFile(script)
Expand Down
2 changes: 1 addition & 1 deletion uniquebible/util/terminal_text_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,7 @@ def plugins(self):
if not userInput or userInput.lower() == config.terminal_cancel_action:
return self.parent.cancelAction()
try:
filepath = os.path.join(config.packageDir, "plugins", "text_editor", f"{userInput}.py")
filepath = os.path.join(pluginDir, f"{userInput}.py")
self.parent.execPythonFile(filepath)
return ""
except:
Expand Down

0 comments on commit 91afa9f

Please sign in to comment.