diff --git a/aqt/addons.py b/aqt/addons.py
index 40457dc45..324e76fd5 100644
--- a/aqt/addons.py
+++ b/aqt/addons.py
@@ -5,6 +5,7 @@ import io
import json
import re
import zipfile
+import markdown
from send2trash import send2trash
from aqt.qt import *
@@ -26,8 +27,6 @@ class AddonManager:
f = self.mw.form
f.actionAdd_ons.triggered.connect(self.onAddonsDialog)
sys.path.insert(0, self.addonsFolder())
- if not self.mw.safeMode:
- self.loadAddons()
def allAddons(self):
l = []
@@ -105,7 +104,8 @@ When loading '%(name)s':
name = os.path.splitext(fname)[0]
- # remove old version first
+ # previously installed?
+ meta = self.addonMeta(sid)
base = self.addonsFolder(sid)
if os.path.exists(base):
self.deleteAddon(sid)
@@ -119,9 +119,9 @@ When loading '%(name)s':
# write
z.extract(n, base)
- # write metadata
- meta = dict(name=name,
- mod=intTime())
+ # update metadata
+ meta['name'] = name
+ meta['mod'] = intTime()
self.writeAddonMeta(sid, meta)
def deleteAddon(self, dir):
@@ -187,6 +187,43 @@ When loading '%(name)s':
updated.append(sid)
return updated
+ # Add-on Config
+ ######################################################################
+
+ _configButtonActions = {}
+
+ def addonConfigDefaults(self, dir):
+ path = os.path.join(self.addonsFolder(dir), "config.json")
+ try:
+ return json.load(open(path, encoding="utf8"))
+ except:
+ return None
+
+ def addonConfigHelp(self, dir):
+ path = os.path.join(self.addonsFolder(dir), "config.md")
+ if os.path.exists(path):
+ return markdown.markdown(open(path).read())
+ else:
+ return ""
+
+ def getConfig(self, module):
+ addon = module.split(".")[0]
+ meta = self.addonMeta(addon)
+ if meta.get("config"):
+ return meta["config"]
+ return self.addonConfigDefaults(addon)
+
+ def configAction(self, addon):
+ return self._configButtonActions.get(addon)
+
+ def setConfigAction(self, addon, fn):
+ self._configButtonActions[addon] = fn
+
+ def writeConfig(self, addon, conf):
+ meta = self.addonMeta(addon)
+ meta['config'] = conf
+ self.writeAddonMeta(addon, meta)
+
# Add-ons Dialog
######################################################################
@@ -206,6 +243,7 @@ class AddonsDialog(QDialog):
f.viewPage.clicked.connect(self.onViewPage)
f.viewFiles.clicked.connect(self.onViewFiles)
f.delete_2.clicked.connect(self.onDelete)
+ f.config.clicked.connect(self.onConfig)
self.redrawAddons()
self.show()
@@ -293,6 +331,25 @@ class AddonsDialog(QDialog):
self.redrawAddons()
+ def onConfig(self):
+ addon = self.onlyOneSelected()
+ if not addon:
+ return
+
+ # does add-on manage its own config?
+ act = self.mgr.configAction(addon)
+ if act:
+ act()
+ return
+
+ conf = self.mgr.getConfig(addon)
+ if conf is None:
+ showInfo(_("Add-on has no configuration."))
+ return
+
+ ConfigEditor(self, addon, conf)
+
+
# Fetching Add-ons
######################################################################
@@ -332,3 +389,47 @@ class GetAddons(QDialog):
self.addonsDlg.redrawAddons()
QDialog.accept(self)
+
+# Editing config
+######################################################################
+
+class ConfigEditor(QDialog):
+
+ def __init__(self, dlg, addon, conf):
+ super().__init__(dlg)
+ self.addon = addon
+ self.conf = conf
+ self.mgr = dlg.mgr
+ self.form = aqt.forms.addonconf.Ui_Dialog()
+ self.form.setupUi(self)
+ restore = self.form.buttonBox.button(QDialogButtonBox.RestoreDefaults)
+ restore.clicked.connect(self.onRestoreDefaults)
+ self.updateHelp()
+ self.updateText()
+ self.show()
+
+ def onRestoreDefaults(self):
+ self.conf = self.mgr.addonConfigDefaults(self.addon)
+ self.updateText()
+
+ def updateHelp(self):
+ txt = self.mgr.addonConfigHelp(self.addon)
+ if txt:
+ self.form.label.setText(txt)
+ else:
+ self.form.scrollArea.setVisible(False)
+
+ def updateText(self):
+ self.form.editor.setPlainText(
+ json.dumps(self.conf,sort_keys=True,indent=4, separators=(',', ': ')))
+
+ def accept(self):
+ txt = self.form.editor.toPlainText()
+ try:
+ self.conf = json.loads(txt)
+ except Exception as e:
+ showInfo(_("Invalid configuration: ") + repr(e))
+ return
+
+ self.mgr.writeConfig(self.addon, self.conf)
+ super().accept()
diff --git a/aqt/main.py b/aqt/main.py
index 1b1cd3eeb..26a814afe 100644
--- a/aqt/main.py
+++ b/aqt/main.py
@@ -597,6 +597,8 @@ title="%s" %s>%s''' % (
def setupAddons(self):
import aqt.addons
self.addonManager = aqt.addons.AddonManager(self)
+ if not self.safeMode:
+ self.addonManager.loadAddons()
def setupThreads(self):
self._mainThread = QThread.currentThread()
diff --git a/designer/addonconf.ui b/designer/addonconf.ui
new file mode 100644
index 000000000..e2a688d63
--- /dev/null
+++ b/designer/addonconf.ui
@@ -0,0 +1,127 @@
+
+
+ Dialog
+
+
+ Qt::ApplicationModal
+
+
+
+ 0
+ 0
+ 631
+ 521
+
+
+
+ Configuration
+
+
+ -
+
+
+
+ 0
+ 1
+
+
+
+ QFrame::NoFrame
+
+
+ true
+
+
+
+
+ 0
+ 0
+ 607
+ 112
+
+
+
+
+ 0
+
+
-
+
+
+
+
+
+ Qt::RichText
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
+
+
+ true
+
+
+ true
+
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 3
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ Dialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ Dialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/designer/addons.ui b/designer/addons.ui
index b962c7bb9..eb7144608 100644
--- a/designer/addons.ui
+++ b/designer/addons.ui
@@ -74,6 +74,13 @@
+ -
+
+
+ Config
+
+
+
-
diff --git a/requirements.txt b/requirements.txt
index ba519212a..dd6379d2c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,3 +4,4 @@ httplib2
pyaudio
requests
decorator
+markdown