persistent add-on configuration
- add-ons can ship default config in a config.json file - users can edit the config in the add-ons dialog, easily syntax-check the json, and restore it to the defaults - an optional config.md contains instructions to the user in markdown format - config will be preserved when add-on is updated, instead of being overwritten as is the case when users are required to edit the source files A simple example: in config.json: {"myvar": 5} In your add-on's code: from aqt import mw config = mw.addonManager.getConfig(__name__) print("var is", config['myvar']) Add-ons that manage options in their own GUI can have that GUI displayed when the config button is clicked: mw.addonManager.setConfigAction(__name__, myOptionsFunc)
This commit is contained in:
parent
b0a62838b5
commit
737a8d934e
113
aqt/addons.py
113
aqt/addons.py
@ -5,6 +5,7 @@ import io
|
|||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import zipfile
|
import zipfile
|
||||||
|
import markdown
|
||||||
from send2trash import send2trash
|
from send2trash import send2trash
|
||||||
|
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
@ -26,8 +27,6 @@ class AddonManager:
|
|||||||
f = self.mw.form
|
f = self.mw.form
|
||||||
f.actionAdd_ons.triggered.connect(self.onAddonsDialog)
|
f.actionAdd_ons.triggered.connect(self.onAddonsDialog)
|
||||||
sys.path.insert(0, self.addonsFolder())
|
sys.path.insert(0, self.addonsFolder())
|
||||||
if not self.mw.safeMode:
|
|
||||||
self.loadAddons()
|
|
||||||
|
|
||||||
def allAddons(self):
|
def allAddons(self):
|
||||||
l = []
|
l = []
|
||||||
@ -105,7 +104,8 @@ When loading '%(name)s':
|
|||||||
|
|
||||||
name = os.path.splitext(fname)[0]
|
name = os.path.splitext(fname)[0]
|
||||||
|
|
||||||
# remove old version first
|
# previously installed?
|
||||||
|
meta = self.addonMeta(sid)
|
||||||
base = self.addonsFolder(sid)
|
base = self.addonsFolder(sid)
|
||||||
if os.path.exists(base):
|
if os.path.exists(base):
|
||||||
self.deleteAddon(sid)
|
self.deleteAddon(sid)
|
||||||
@ -119,9 +119,9 @@ When loading '%(name)s':
|
|||||||
# write
|
# write
|
||||||
z.extract(n, base)
|
z.extract(n, base)
|
||||||
|
|
||||||
# write metadata
|
# update metadata
|
||||||
meta = dict(name=name,
|
meta['name'] = name
|
||||||
mod=intTime())
|
meta['mod'] = intTime()
|
||||||
self.writeAddonMeta(sid, meta)
|
self.writeAddonMeta(sid, meta)
|
||||||
|
|
||||||
def deleteAddon(self, dir):
|
def deleteAddon(self, dir):
|
||||||
@ -187,6 +187,43 @@ When loading '%(name)s':
|
|||||||
updated.append(sid)
|
updated.append(sid)
|
||||||
return updated
|
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
|
# Add-ons Dialog
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
@ -206,6 +243,7 @@ class AddonsDialog(QDialog):
|
|||||||
f.viewPage.clicked.connect(self.onViewPage)
|
f.viewPage.clicked.connect(self.onViewPage)
|
||||||
f.viewFiles.clicked.connect(self.onViewFiles)
|
f.viewFiles.clicked.connect(self.onViewFiles)
|
||||||
f.delete_2.clicked.connect(self.onDelete)
|
f.delete_2.clicked.connect(self.onDelete)
|
||||||
|
f.config.clicked.connect(self.onConfig)
|
||||||
self.redrawAddons()
|
self.redrawAddons()
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
@ -293,6 +331,25 @@ class AddonsDialog(QDialog):
|
|||||||
|
|
||||||
self.redrawAddons()
|
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
|
# Fetching Add-ons
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
@ -332,3 +389,47 @@ class GetAddons(QDialog):
|
|||||||
|
|
||||||
self.addonsDlg.redrawAddons()
|
self.addonsDlg.redrawAddons()
|
||||||
QDialog.accept(self)
|
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()
|
||||||
|
@ -597,6 +597,8 @@ title="%s" %s>%s</button>''' % (
|
|||||||
def setupAddons(self):
|
def setupAddons(self):
|
||||||
import aqt.addons
|
import aqt.addons
|
||||||
self.addonManager = aqt.addons.AddonManager(self)
|
self.addonManager = aqt.addons.AddonManager(self)
|
||||||
|
if not self.safeMode:
|
||||||
|
self.addonManager.loadAddons()
|
||||||
|
|
||||||
def setupThreads(self):
|
def setupThreads(self):
|
||||||
self._mainThread = QThread.currentThread()
|
self._mainThread = QThread.currentThread()
|
||||||
|
127
designer/addonconf.ui
Normal file
127
designer/addonconf.ui
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>Dialog</class>
|
||||||
|
<widget class="QDialog" name="Dialog">
|
||||||
|
<property name="windowModality">
|
||||||
|
<enum>Qt::ApplicationModal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>631</width>
|
||||||
|
<height>521</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Configuration</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QScrollArea" name="scrollArea">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>1</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::NoFrame</enum>
|
||||||
|
</property>
|
||||||
|
<property name="widgetResizable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>607</width>
|
||||||
|
<height>112</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<property name="margin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="textFormat">
|
||||||
|
<enum>Qt::RichText</enum>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="openExternalLinks">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPlainTextEdit" name="editor">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>3</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>Dialog</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>248</x>
|
||||||
|
<y>254</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>157</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>Dialog</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>316</x>
|
||||||
|
<y>260</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
@ -74,6 +74,13 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="config">
|
||||||
|
<property name="text">
|
||||||
|
<string>Config</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="viewFiles">
|
<widget class="QPushButton" name="viewFiles">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -4,3 +4,4 @@ httplib2
|
|||||||
pyaudio
|
pyaudio
|
||||||
requests
|
requests
|
||||||
decorator
|
decorator
|
||||||
|
markdown
|
||||||
|
Loading…
Reference in New Issue
Block a user