Index: html/gui.html
==================================================================
--- html/gui.html
+++ html/gui.html
@@ -30,89 +30,100 @@
-def option_entry(o, config)
+def option_entry(opt, config)
-
-
+
widgets for single config option
-def plugin_entry(e, plugin_states)
+def plugin_entry(pmd, plugin_states)
-
-
+
-def plugin_layout(ls, config, plugin_states, opt_label=False)
+def plugin_layout(pmd_list, config, plugin_states, opt_label=False)
-
-
+
craft list of widgets for each read plugin
def read_options(files)
-
-
+
read files, return dict of {id:pmd} for all plugins
-def window(config={}, plugin_states={}, files=['*/*.py'], plugins={}, opt_label=False, theme='DefaultNoMoreNagging', **kwargs)
+def window(config, plugin_states, files=['*/*.py'], **kwargs)
-
Reads *.py files and crafts a settings dialog from meta data.
+
Where plugin_states{}
is usually an entry in config{}
itself. Depending on plugin
+and option names, it might even be a flat/shared namespace for both. Per default you'd
+set files=["plugins/*.py", __file__]
to be read. But with files=[]
it's possible to
+provide a plugins=pluginconf.get_plugin_meta()
or prepared plugin/options dict instead.
Parameters
-config
: dict
+config
: dict 🔁
- Config settings, updated after dialog completion
-plugin_states
: dict
+plugin_states
: dict 🔁
- Plugin activation states, also input/output
files
: list
- Glob list of *.py files to extract meta definitions from
plugins
: dict
- Alternatively to files=[] list, a preparsed list of pluginmeta+config dicts can be injected
opt_label
: bool
- Show config name= as label
+theme
: str
+- Set PSG window theme.
**kwargs
: dict
- Other options are passed on to PySimpleGUI
+
+
Returns
+
+True
: if changed config{} values are to be saved (the dict will be updated in any case)
+-
-def wrap(s, w=50)
+def wrap(text, width=50)
-
-
+
textwrap for description
and help
option fields
-
-class cast
+
+class Cast
-
-
+
map option types (from strings)
Static methods
-
-def bool(v)
-
--
-
-
-
-def fromtype(v, opt)
-
--
-
-
-
-def int(v)
-
--
-
+
+def bool(val)
+
+-
+
+
+
+def fromtype(val, opt)
+
+-
+
cast according to option type
+
+
+def int(val)
+
+-
+
@@ -139,15 +150,15 @@
Index: pluginconf/gui.py
==================================================================
--- pluginconf/gui.py
+++ pluginconf/gui.py
@@ -1,15 +1,16 @@
# encoding: UTF-8
# api: python
-# type: ui
+##type: gui
# category: io
# title: Config GUI
# description: Display plugins + options in setup window
# version: 0.8
# depends: python:pysimplegui (>= 4.0)
# priority: optional
# config: -
+# pylint: disable=line-too-long
#
# Creates a PySimpleGUI options list. Scans a given list of *.py files
# for meta data, then populates a config{} dict and (optionally) a state
# map for plugins themselves.
#
@@ -21,184 +22,209 @@
#
""" PySimpleGUI window to populate config dict via plugin options """
+#import os
+import re
+#import json
+import glob
+import textwrap
import PySimpleGUI as sg
import pluginconf
-import glob, json, os, re, textwrap
# temporarily store collected plugin config: dicts
-options = {}
+OPTIONS = {}
#-- show configuation window
-def window(config={}, plugin_states={}, files=["*/*.py"], plugins={}, opt_label=False, theme="DefaultNoMoreNagging", **kwargs):
+def window(config, plugin_states, files=["*/*.py"], **kwargs):
"""
Reads *.py files and crafts a settings dialog from meta data.
-
+
+ Where `plugin_states{}` is usually an entry in `config{}` itself. Depending on plugin
+ and option names, it might even be a flat/shared namespace for both. Per default you'd
+ set `files=["plugins/*.py", __file__]` to be read. But with `files=[]` it's possible to
+ provide a `plugins=pluginconf.get_plugin_meta()` or prepared plugin/options dict instead.
+
Parameters
----------
- config : dict
+ config : dict 🔁
Config settings, updated after dialog completion
- plugin_states : dict
+ plugin_states : dict 🔁
Plugin activation states, also input/output
files : list
Glob list of *.py files to extract meta definitions from
plugins : dict
Alternatively to files=[] list, a preparsed list of pluginmeta+config dicts can be injected
opt_label : bool
Show config name= as label
+ theme : str
+ Set PSG window theme.
**kwargs : dict
Other options are passed on to PySimpleGUI
+
+ Returns
+ -------
+ True : if changed config{} values are to be saved (the dict will be updated in any case)
"""
-
+ plugins = kwargs.get("plugins", {})
+ opt_label = kwargs.get("opt_label", False)
+ theme = kwargs.get("theme", "DefaultNoMoreNagging")
if theme:
sg.theme(theme)
if files:
plugins = read_options(files)
layout = plugin_layout(plugins.values(), config, plugin_states, opt_label=opt_label)
layout.append([sg.T(" ")])
#print(repr(layout))
-
+
# pack window
layout = [
- [sg.Column(layout, expand_x=1, expand_y=0, size=(575,680), scrollable="vertically", element_justification='left')],
+ [sg.Column(layout, expand_x=1, expand_y=0, size=(575, 680), scrollable="vertically", element_justification='left')],
[sg.Column([[sg.Button("Cancel"), sg.Button("Save")]], element_justification='right')]
]
- if not "title" in kwargs:
+ if "title" not in kwargs:
kwargs["title"] = "Options"
- if not "font" in kwargs:
+ if "font" not in kwargs:
kwargs["font"] = "Sans 11"
win = sg.Window(layout=layout, resizable=1, **kwargs)
- # wait for save/exit
- event,data = win.read()
+ # wait for save/exit
+ event, data = win.read()
win.close()
- if event=="Save":
- for k,v in data.items():
- if options.get(k):
+ if event == "Save":
+ for key, val in data.items():
+ if OPTIONS.get(key):
#@ToDo: handle array[key] names
- config[k] = cast.fromtype(data[k], options[k])
- elif type(k) is str and k.startswith('p:'):
- k = k.replace('p:', '')
- if plugins.get(k):
- plugin_states[k] = v
+ config[key] = Cast.fromtype(data[key], OPTIONS[key])
+ elif isinstance(key, str) and key.startswith('p:'):
+ key = key.replace('p:', '')
+ if plugins.get(key):
+ plugin_states[key] = val
return True
#print(config, plugin_states)
-
-
-# craft list of widgets for each read plugin
-def plugin_layout(ls, config, plugin_states, opt_label=False):
+
+
+def plugin_layout(pmd_list, config, plugin_states, opt_label=False):
+ """ craft list of widgets for each read plugin """
layout = []
- for plg in ls:
+ for plg in pmd_list:
#print(plg.get("id"))
layout = layout + plugin_entry(plg, plugin_states)
for opt in plg["config"]:
if opt.get("name"):
if opt_label:
- layout.append([sg.T(opt["name"], font=("Sans",11,"bold"), pad=((50,0),(7,0)))])
- layout.append(option_entry(opt, config))
- return layout
-
-# checkbox for plugin name
-def plugin_entry(e, plugin_states):
- id = e["id"]
- return [
- [
- sg.Checkbox(
- e.get("title", id), key='p:'+id, default=plugin_states.get(id, 0), tooltip=e.get("doc"), metadata="plugin",
- font="bold", pad=(0,(8,0))
- ),
- sg.Text("({}/{})".format(e.get("type"), e.get("category")), text_color="#005", pad=(0,(8,0))),
- sg.Text(e.get("version"), text_color="#a72", pad=(0,(8,0)))
- ],
- [
- sg.Text(e.get("description", ""), tooltip=e.get("doc"), font=("sans", 10), pad=(26,(0,10)))
- ]
- ]
-
-# widgets for single config option
-def option_entry(o, config):
- #print(o)
- name = o.get("name", "")
- desc = wrap(o.get("description", name), 60)
- type = o.get("type", "str")
- help = o.get("help", None)
- if help:
- help = wrap(help, 60)
- options[name] = o
- val = config.get(name, o.get("value", ""))
- if o.get("hidden"):
- pass
- elif type == "str":
- return [
- sg.InputText(key=name, default_text=str(val), size=(20,1), pad=((50,0),3)),
- sg.Text(wrap(desc, 50), pad=(5,2), tooltip=help or name, justification='left', auto_size_text=1)
- ]
- elif type == "text":
- return [
- sg.Multiline(key=name, default_text=str(val), size=(45,4), pad=((40,0),3)),
- sg.Text(wrap(desc, 20), pad=(5,2), tooltip=help or name, justification='left', auto_size_text=1)
- ]
- elif type == "bool":
- return [
- sg.Checkbox(wrap(desc, 70), key=name, default=cast.bool(val), tooltip=help or name, pad=((40,0),2), auto_size_text=1)
- ]
- elif type == "int":
- return [
- sg.InputText(key=name, default_text=str(val), size=(6,1), pad=((50,0),3)),
- sg.Text(wrap(desc, 60), pad=(5,2), tooltip=help or name, auto_size_text=1)
- ]
- elif type == "select":
- #o["select"] = parse_select(o.get("select", ""))
- values = [v for v in o["select"].values()]
- return [
- sg.Combo(key=name, default_value=o["select"].get(val, val), values=values, size=(15,1), pad=((50,0),0), font="Sans 11"),
- sg.Text(wrap(desc, 47), pad=(5,2), tooltip=help or name, auto_size_text=1)
- ]
- elif type == "dict": # or "table" rather ?
- return [
- sg.Table(values=config.get(name, ["", ""]), headings=o.get("columns", "Key,Value").split(","),
- num_rows=5, col_widths=30, def_col_width=30, auto_size_columns=False, max_col_width=150, key=name, tooltip=help or desc)
- ]
- return []
-
-
-#-- read files, return dict of {id:pmd} for all plugins
-def read_options(files):
- ls = [pluginconf.plugin_meta(fn=fn) for pattern in files for fn in glob.glob(pattern)]
- return dict(
- (meta["id"], meta) for meta in ls
- )
-
-
-#-- map option types (from strings)
-class cast:
- @staticmethod
- def bool(v):
- if v in ("1", 1, True, "true", "TRUE", "yes", "YES", "on", "ON"):
- return True
- return False
- @staticmethod
- def int(v):
- return int(v) if re.match("-?\d+", v) else 0
- @staticmethod
- def fromtype(v, opt):
- if not opt.get("type"):
- return str(v)
- elif opt["type"] == "int":
- return cast.int(v)
- elif opt["type"] == "bool":
- return cast.bool(v)
- elif opt["type"] == "select":
- inverse = dict((v,k) for k,v in opt["select"].items())
- return inverse.get(v, v)
- elif opt["type"] == "text":
- return str(v).rstrip()
- else:
- return v
-
-#-- textwrap for `description` and `help` option fields
-def wrap(s, w=50):
- return "\n".join(textwrap.wrap(s, w)) if s else ""
+ layout.append([sg.T(opt["name"], font=("Sans", 11, "bold"), pad=((50, 0), (7, 0)))])
+ layout.append(option_entry(opt, config))
+ return layout
+
+def plugin_entry(pmd, plugin_states):
+ """ checkbox for plugin name """
+ name = pmd["id"]
+ return [
+ [
+ sg.Checkbox(
+ pmd.get("title", name), key='p:'+name, default=plugin_states.get(name, 0),
+ tooltip=pmd.get("doc"), metadata="plugin", font="bold", pad=(0, (8, 0))
+ ),
+ sg.Text("({}/{})".format(pmd.get("type"), pmd.get("category")), text_color="#005", pad=(0, (8, 0))),
+ sg.Text(pmd.get("version"), text_color="#a72", pad=(0, (8, 0)))
+ ],
+ [
+ sg.Text(pmd.get("description", ""), tooltip=pmd.get("doc"), font=("sans", 10), pad=(26, (0, 10)))
+ ]
+ ]
+
+def option_entry(opt, config):
+ """ widgets for single config option """
+ #print(o)
+ name = opt.get("name", "")
+ desc = wrap(opt.get("description", name), 60)
+ typedef = opt.get("type", "str")
+ tooltip = wrap(opt.get("help", name), 60)
+ OPTIONS[name] = opt
+ val = config.get(name, opt.get("value", ""))
+
+ widget = []
+ if opt.get("hidden"):
+ pass
+ elif typedef == "str":
+ widget = [
+ sg.InputText(key=name, default_text=str(val), size=(20, 1), pad=((50, 0), 3)),
+ sg.Text(wrap(desc, 50), pad=(5, 2), tooltip=tooltip, justification='left', auto_size_text=1)
+ ]
+ elif typedef == "text":
+ widget = [
+ sg.Multiline(key=name, default_text=str(val), size=(45, 4), pad=((40, 0), 3)),
+ sg.Text(wrap(desc, 20), pad=(5, 2), tooltip=tooltip, justification='left', auto_size_text=1)
+ ]
+ elif typedef == "bool":
+ widget = [
+ sg.Checkbox(wrap(desc, 70), key=name, default=Cast.bool(val), tooltip=tooltip, pad=((40, 0), 2), auto_size_text=1)
+ ]
+ elif typedef == "int":
+ widget = [
+ sg.InputText(key=name, default_text=str(val), size=(6, 1), pad=((50, 0), 3)),
+ sg.Text(wrap(desc, 60), pad=(5, 2), tooltip=tooltip, auto_size_text=1)
+ ]
+ elif typedef == "select":
+ values = opt["select"].values()
+ widget = [
+ sg.Combo(key=name, default_value=opt["select"].get(val, val), values=values, size=(15, 1), pad=((50, 0), 0), font="Sans 11"),
+ sg.Text(wrap(desc, 47), pad=(5, 2), tooltip=tooltip, auto_size_text=1)
+ ]
+ elif typedef == "dict": # or "table" rather ?
+ widget = [
+ sg.Table(
+ values=config.get(name, ["", ""]), headings=opt.get("columns", "Key,Value").split(","),
+ num_rows=5, col_widths=30, def_col_width=30, auto_size_columns=False, max_col_width=150,
+ key=name, tooltip=wrap(opt.get("help", desc))
+ )
+ ]
+ return widget
+
+
+def read_options(files):
+ """ read files, return dict of {id:pmd} for all plugins """
+ return {
+ meta["id"]: meta for meta in
+ [pluginconf.plugin_meta(fn=fn) for pattern in files for fn in glob.glob(pattern)]
+ }
+
+
+class Cast:
+ """ map option types (from strings) """
+
+ @staticmethod
+ def bool(val):
+ """ map boolean literals """
+ if val in ("1", 1, True, "true", "TRUE", "yes", "YES", "on", "ON"):
+ return True
+ return False
+
+ @staticmethod
+ def int(val):
+ """ verify integer """
+ return int(val) if re.match(r"-?\d+", val) else 0
+
+ @staticmethod
+ def fromtype(val, opt):
+ """ cast according to option type """
+ if not opt.get("type"):
+ return str(val)
+ if opt["type"] == "int":
+ return Cast.int(val)
+ if opt["type"] == "bool":
+ return Cast.bool(val)
+ if opt["type"] == "select":
+ inverse = dict((val, key) for key, val in opt["select"].items())
+ return inverse.get(val, val)
+ if opt["type"] == "text":
+ return str(val).rstrip()
+ # else:
+ return val
+
+def wrap(text, width=50):
+ """ textwrap for `description` and `help` option fields """
+ return "\n".join(textwrap.wrap(text, width)) if text else ""