From 216aeb5e72bd5996e09b98d57a82e12d6046a857 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Thu, 5 Mar 2020 06:06:32 +0100 Subject: [PATCH 01/29] test text --- test.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test.py b/test.py index d3c6f21..a4f052b 100644 --- a/test.py +++ b/test.py @@ -18,6 +18,10 @@ "title": "Schema path", "type": "string" }, + "text": { + "type": "string", + "maxLength": 20 + }, "integerRangeSteps": { "title": "Integer range (by 10)", "type": "integer", From 5f5b32e3936ec0a5a0b55d1fa62646f2259e2113 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Thu, 5 Mar 2020 06:06:51 +0100 Subject: [PATCH 02/29] test enum --- test.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test.py b/test.py index a4f052b..b2cec6d 100644 --- a/test.py +++ b/test.py @@ -32,6 +32,11 @@ "sky_colour": { "type": "string" }, + "enum": { + "type": "boolean", + "enum": [True, False] + + }, "names": { "type": "array", "items": [ @@ -63,6 +68,9 @@ }, "sky_colour": { "ui:widget": "colour" + }, + "enum": { + "ui:widget": "enum", } } From 08080a3dc060bb5f7b66f17255f15e0ce3a81efc Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Thu, 5 Mar 2020 06:07:02 +0100 Subject: [PATCH 03/29] test boolean --- test.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test.py b/test.py index b2cec6d..492caae 100644 --- a/test.py +++ b/test.py @@ -32,6 +32,10 @@ "sky_colour": { "type": "string" }, + "boolean": { + "type": "boolean", + + }, "enum": { "type": "boolean", "enum": [True, False] From 07bea043ca71ca12c9170f7a941e3d3079bfe431 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Thu, 5 Mar 2020 03:25:16 +0100 Subject: [PATCH 04/29] spin respect min/max and steps The problem of course is that if multiple of is not satisfied at initialization, then it can't be satisfied using only up and down. In this case the user must type the number by hand. Once it's satisfied however, the number will always keep satisfying the constraint --- qt_jsonschema_form/widgets.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/qt_jsonschema_form/widgets.py b/qt_jsonschema_form/widgets.py index 3de33cd..aae5323 100644 --- a/qt_jsonschema_form/widgets.py +++ b/qt_jsonschema_form/widgets.py @@ -115,6 +115,23 @@ def state(self, state: float): def configure(self): self.valueChanged.connect(self.on_changed.emit) + if "maximum" in self.schema: + if "exclusiveMaximum" in self.schema: + self.setMaximum(min(self.schema["maximum"], self.schema["exclusiveMaximum"])) + else: + self.setMaximum(self.schema["maximum"]) + elif "exclusiveMaximum" in self.schema: + self.setMaximum(self.schema["exclusiveMaximum"]) + if "minimum" in self.schema: + if "exclusiveMinimum" in self.schema: + self.setMinimum(min(self.schema["minimum"], self.schema["exclusiveMinimum"])) + else: + self.setMinimum(self.schema["minimum"]) + elif "exclusiveMinimum" in self.schema: + self.setMinimum(self.schema["exclusiveMinimum"]) + if "multipleOf" in self.schema: + self.setSingleStep(self.schema["multipleOf"]) + class SpinSchemaWidget(SchemaWidgetMixin, QtWidgets.QSpinBox): @@ -129,6 +146,22 @@ def state(self, state: int): def configure(self): self.valueChanged.connect(self.on_changed.emit) + if "maximum" in self.schema: + if "exclusiveMaximum" in self.schema: + self.setMaximum(min(self.schema["maximum"], self.schema["exclusiveMaximum"]-1)) + else: + self.setMaximum(self.schema["maximum"]) + elif "exclusiveMaximum" in self.schema: + self.setMaximum(self.schema["exclusiveMaximum"]-1) + if "minimum" in self.schema: + if "exclusiveMinimum" in self.schema: + self.setMinimum(min(self.schema["minimum"], self.schema["exclusiveMinimum"]+1)) + else: + self.setMinimum(self.schema["minimum"]) + elif "exclusiveMinimum" in self.schema: + self.setMinimum(self.schema["exclusiveMinimum"]+1) + if "multipleOf" in self.schema: + self.setSingleStep(self.schema["multipleOf"]) class IntegerRangeSchemaWidget(SchemaWidgetMixin, QtWidgets.QSlider): From 5d85aeed635de88f7bfcf0b3c4f7c11ae570cc0b Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Thu, 5 Mar 2020 03:27:51 +0100 Subject: [PATCH 05/29] use a dict to send type to their default method --- qt_jsonschema_form/defaults.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/qt_jsonschema_form/defaults.py b/qt_jsonschema_form/defaults.py index ebe5cd2..7d5e5fb 100644 --- a/qt_jsonschema_form/defaults.py +++ b/qt_jsonschema_form/defaults.py @@ -12,10 +12,16 @@ def object_defaults(schema): def array_defaults(schema): items_schema = schema['items'] if isinstance(items_schema, dict): + # in this case, it should be a list of items_schema return [] + # in this case, it's a tuple. return [compute_defaults(s) for s in schema["items"]] +defaults = { + "array": array_defaults, + "object": object_defaults, +} def compute_defaults(schema): if "default" in schema: @@ -27,10 +33,7 @@ def compute_defaults(schema): schema_type = schema["type"] - if schema_type == "object": - return object_defaults(schema) - - elif schema_type == "array": - return array_defaults(schema) + if schema_type in defaults: + return defaults[schema_type](schema) return None From ea9fd8219dd3f35082ef6b2b83d39d142b4f9f1a Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Thu, 5 Mar 2020 04:32:56 +0100 Subject: [PATCH 06/29] ignore tmp files --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b25c15b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*~ From b7482db284766a9e52343f1be29263de0e88e14d Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Thu, 5 Mar 2020 04:33:11 +0100 Subject: [PATCH 07/29] ignore pycache --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b25c15b..9b5e2e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *~ +__pycache__ From 9351cc69e92e80ecd512d781cfb6cb62c10b6490 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Thu, 5 Mar 2020 04:33:30 +0100 Subject: [PATCH 08/29] pep8 and sort import --- qt_jsonschema_form/__init__.py | 2 +- qt_jsonschema_form/defaults.py | 2 ++ qt_jsonschema_form/form.py | 7 ++-- qt_jsonschema_form/widgets.py | 58 ++++++++++++++++++++-------------- test.py | 1 - 5 files changed, 43 insertions(+), 27 deletions(-) diff --git a/qt_jsonschema_form/__init__.py b/qt_jsonschema_form/__init__.py index a2c3980..b9284ea 100644 --- a/qt_jsonschema_form/__init__.py +++ b/qt_jsonschema_form/__init__.py @@ -1 +1 @@ -from .form import WidgetBuilder \ No newline at end of file +from .form import WidgetBuilder diff --git a/qt_jsonschema_form/defaults.py b/qt_jsonschema_form/defaults.py index 7d5e5fb..23ab5c5 100644 --- a/qt_jsonschema_form/defaults.py +++ b/qt_jsonschema_form/defaults.py @@ -18,11 +18,13 @@ def array_defaults(schema): # in this case, it's a tuple. return [compute_defaults(s) for s in schema["items"]] + defaults = { "array": array_defaults, "object": object_defaults, } + def compute_defaults(schema): if "default" in schema: return schema["default"] diff --git a/qt_jsonschema_form/form.py b/qt_jsonschema_form/form.py index 004a219..f6e3fab 100644 --- a/qt_jsonschema_form/form.py +++ b/qt_jsonschema_form/form.py @@ -1,10 +1,12 @@ from copy import deepcopy from jsonschema.validators import validator_for -\ + from . import widgets from .defaults import compute_defaults +\ + def get_widget_state(schema, state=None): if state is None: @@ -73,7 +75,8 @@ def create_widget(self, schema: dict, ui_schema: dict, state=None) -> widgets.Sc schema_type = get_schema_type(schema) try: - default_variant = self.widget_variant_modifiers[schema_type](schema) + default_variant = self.widget_variant_modifiers[schema_type]( + schema) except KeyError: default_variant = self.default_widget_variants[schema_type] diff --git a/qt_jsonschema_form/widgets.py b/qt_jsonschema_form/widgets.py index aae5323..bd1b197 100644 --- a/qt_jsonschema_form/widgets.py +++ b/qt_jsonschema_form/widgets.py @@ -1,11 +1,10 @@ from functools import partial -from typing import List -from typing import Tuple, Optional, Dict +from typing import Dict, List, Optional, Tuple -from PyQt5 import QtWidgets, QtCore, QtGui +from PyQt5 import QtCore, QtGui, QtWidgets from .signal import Signal -from .utils import iter_layout_widgets, state_property, is_concrete_schema +from .utils import is_concrete_schema, iter_layout_widgets, state_property class SchemaWidgetMixin: @@ -46,7 +45,8 @@ def clear_error(self): def _set_valid_state(self, error: Exception = None): palette = self.palette() colour = QtGui.QColor() - colour.setNamedColor(self.VALID_COLOUR if error is None else self.INVALID_COLOUR) + colour.setNamedColor( + self.VALID_COLOUR if error is None else self.INVALID_COLOUR) palette.setColor(self.backgroundRole(), colour) self.setPalette(palette) @@ -117,23 +117,24 @@ def configure(self): self.valueChanged.connect(self.on_changed.emit) if "maximum" in self.schema: if "exclusiveMaximum" in self.schema: - self.setMaximum(min(self.schema["maximum"], self.schema["exclusiveMaximum"])) + self.setMaximum( + min(self.schema["maximum"], self.schema["exclusiveMaximum"])) else: self.setMaximum(self.schema["maximum"]) elif "exclusiveMaximum" in self.schema: - self.setMaximum(self.schema["exclusiveMaximum"]) + self.setMaximum(self.schema["exclusiveMaximum"]) if "minimum" in self.schema: if "exclusiveMinimum" in self.schema: - self.setMinimum(min(self.schema["minimum"], self.schema["exclusiveMinimum"])) + self.setMinimum( + min(self.schema["minimum"], self.schema["exclusiveMinimum"])) else: self.setMinimum(self.schema["minimum"]) elif "exclusiveMinimum" in self.schema: - self.setMinimum(self.schema["exclusiveMinimum"]) + self.setMinimum(self.schema["exclusiveMinimum"]) if "multipleOf" in self.schema: self.setSingleStep(self.schema["multipleOf"]) - class SpinSchemaWidget(SchemaWidgetMixin, QtWidgets.QSpinBox): @state_property @@ -148,18 +149,20 @@ def configure(self): self.valueChanged.connect(self.on_changed.emit) if "maximum" in self.schema: if "exclusiveMaximum" in self.schema: - self.setMaximum(min(self.schema["maximum"], self.schema["exclusiveMaximum"]-1)) + self.setMaximum( + min(self.schema["maximum"], self.schema["exclusiveMaximum"]-1)) else: self.setMaximum(self.schema["maximum"]) elif "exclusiveMaximum" in self.schema: - self.setMaximum(self.schema["exclusiveMaximum"]-1) + self.setMaximum(self.schema["exclusiveMaximum"]-1) if "minimum" in self.schema: if "exclusiveMinimum" in self.schema: - self.setMinimum(min(self.schema["minimum"], self.schema["exclusiveMinimum"]+1)) + self.setMinimum( + min(self.schema["minimum"], self.schema["exclusiveMinimum"]+1)) else: self.setMinimum(self.schema["minimum"]) elif "exclusiveMinimum" in self.schema: - self.setMinimum(self.schema["exclusiveMinimum"]+1) + self.setMinimum(self.schema["exclusiveMinimum"]+1) if "multipleOf" in self.schema: self.setSingleStep(self.schema["multipleOf"]) @@ -301,11 +304,13 @@ def __init__(self): self.up_button.clicked.connect(lambda _: self.on_move_up.emit()) self.delete_button = QtWidgets.QPushButton() - self.delete_button.setIcon(style.standardIcon(QtWidgets.QStyle.SP_DialogCancelButton)) + self.delete_button.setIcon(style.standardIcon( + QtWidgets.QStyle.SP_DialogCancelButton)) self.delete_button.clicked.connect(lambda _: self.on_delete.emit()) self.down_button = QtWidgets.QPushButton() - self.down_button.setIcon(style.standardIcon(QtWidgets.QStyle.SP_ArrowDown)) + self.down_button.setIcon( + style.standardIcon(QtWidgets.QStyle.SP_ArrowDown)) self.down_button.clicked.connect(lambda _: self.on_move_down.emit()) group_layout = QtWidgets.QHBoxLayout() @@ -360,7 +365,8 @@ def configure(self): style = self.style() self.add_button = QtWidgets.QPushButton() - self.add_button.setIcon(style.standardIcon(QtWidgets.QStyle.SP_FileIcon)) + self.add_button.setIcon( + style.standardIcon(QtWidgets.QStyle.SP_FileIcon)) self.add_button.clicked.connect(lambda _: self.add_item()) self.array_layout = QtWidgets.QVBoxLayout() @@ -383,7 +389,8 @@ def _on_updated(self, state): if previous_row: can_exchange_previous = previous_row.widget.schema == row.widget.schema row.controls.up_button.setEnabled(can_exchange_previous) - previous_row.controls.down_button.setEnabled(can_exchange_previous) + previous_row.controls.down_button.setEnabled( + can_exchange_previous) else: row.controls.up_button.setEnabled(False) row.controls.delete_button.setEnabled(not self.is_fixed_schema(i)) @@ -443,7 +450,8 @@ def _add_item(self, item_state=None): # Create widget item_ui_schema = self.ui_schema.get("items", {}) - widget = self.widget_builder.create_widget(item_schema, item_ui_schema, item_state) + widget = self.widget_builder.create_widget( + item_schema, item_ui_schema, item_state) controls = ArrayControlsWidget() # Create row @@ -472,7 +480,8 @@ class ObjectSchemaWidget(SchemaWidgetMixin, QtWidgets.QGroupBox): def __init__(self, schema: dict, ui_schema: dict, widget_builder: 'WidgetBuilder'): super().__init__(schema, ui_schema, widget_builder) - self.widgets = self.populate_from_schema(schema, ui_schema, widget_builder) + self.widgets = self.populate_from_schema( + schema, ui_schema, widget_builder) @state_property def state(self) -> dict: @@ -509,7 +518,8 @@ def populate_from_schema(self, schema: dict, ui_schema: dict, widget_builder: 'W for name, sub_schema in schema['properties'].items(): sub_ui_schema = ui_schema.get(name, {}) - widget = widget_builder.create_widget(sub_schema, sub_ui_schema) # TODO onchanged + widget = widget_builder.create_widget( + sub_schema, sub_ui_schema) # TODO onchanged widget.on_changed.connect(partial(self.widget_on_changed, name)) label = sub_schema.get("title", name) layout.addRow(label, widget) @@ -537,7 +547,8 @@ def configure(self): self.addItem(str(opt)) self.setItemData(i, opt) - self.currentIndexChanged.connect(lambda _: self.on_changed.emit(self.state)) + self.currentIndexChanged.connect( + lambda _: self.on_changed.emit(self.state)) def _index_changed(self, index: int): self.on_changed.emit(self.state) @@ -572,7 +583,8 @@ def display_errors(self, errors: List[Exception]): item.widget().deleteLater() for err in errors: - widget = QtWidgets.QLabel(f".{'.'.join(err.path)} {err.message}") + widget = QtWidgets.QLabel( + f".{'.'.join(err.path)} {err.message}") layout.addWidget(widget) def clear_errors(self): diff --git a/test.py b/test.py index 492caae..3bd948c 100644 --- a/test.py +++ b/test.py @@ -92,4 +92,3 @@ form.widget.on_changed.connect(lambda d: print(dumps(d, indent=4))) app.exec_() - From 44b3b56fdd2b4f6728bd93f6634913602458b78a Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Thu, 5 Mar 2020 04:35:09 +0100 Subject: [PATCH 09/29] add default value for numeric --- qt_jsonschema_form/defaults.py | 4 + qt_jsonschema_form/numeric_defaults.py | 105 +++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 qt_jsonschema_form/numeric_defaults.py diff --git a/qt_jsonschema_form/defaults.py b/qt_jsonschema_form/defaults.py index 23ab5c5..38e54b9 100644 --- a/qt_jsonschema_form/defaults.py +++ b/qt_jsonschema_form/defaults.py @@ -1,3 +1,5 @@ +from .numeric_defaults import numeric_defaults + def enum_defaults(schema): try: return schema["enum"][0] @@ -22,6 +24,8 @@ def array_defaults(schema): defaults = { "array": array_defaults, "object": object_defaults, + "numeric": numeric_defaults, + "integer": numeric_defaults, } diff --git a/qt_jsonschema_form/numeric_defaults.py b/qt_jsonschema_form/numeric_defaults.py new file mode 100644 index 0000000..63ca3f8 --- /dev/null +++ b/qt_jsonschema_form/numeric_defaults.py @@ -0,0 +1,105 @@ +import math +from copy import deepcopy + +import jsonschema + + +"""function to compute a number default value. If min and max are set, +a number near their average is chosen. Otherwise, a number as close as +possible from the only bound. Otherwise 0. """ + + +def numeric_defaults(schema): + value = _numeric_defaults(schema) + try: + jsonschema.validate(value, schema) + return value + except jsonschema.ValidationError: + return None + + +def _numeric_defaults(schema): + schema = deepcopy(schema) + # Setting min and max according to exclusive min/max + if "exclusiveMinimum" in schema: + if "minimum" in schema: + schema["minimum"] = max( + schema["minimum"], schema["exclusiveMinimum"]) + else: + schema["minimum"] = schema["exclusiveMinimum"] + if "exclusiveMaximum" in schema: + if "maximum" in schema: + schema["maximum"] = max( + schema["maximum"], schema["exclusiveMaximum"]) + else: + schema["maximum"] = schema["exclusiveMaximum"] + + if "multipleOf" in schema: + return _numeric_defaults_multiple_of(schema) + else: + return _numeric_defaults_not_multiple_of(schema) + + +def _numeric_defaults_not_multiple_of(schema): + if "minimum" in schema and "maximum" in schema: + middle = (schema["minimum"] + schema["maximum"]) / 2 + if schema["type"] == "integer": + middle_ = math.floor(middle) + try: + jsonschema.validate(middle_, schema) + return middle_ + except jsonschema.ValidationError: + return math.ceil(middle) + else: + return middle + elif "minimum" in schema: + # no maximum + m = schema["minimum"] + if schema["type"] == "integer": + m = math.ceil(m) + if schema["exclusiveMinimum"] == m: + m += 1 + return m + elif "maximum" in schema: + # no minimum + M = schema["maximum"] + if schema["type"] == "integer": + M = math.ceil(M) + if schema["exclusiveMaximum"] == M: + M -= 1 + return M + else: + # neither min nor max + return 0 + + +def _numeric_defaults_multiple_of(schema): + multipleOf = schema["multipleOf"] + if schema["type"] == "integer" and multipleOf != math.floor(multipleOf): + # todo: find an integral multiple of a float distinct from 0 + return 0 + if "minimum" in schema and "maximum" in schema: + middle = (schema["minimum"] + schema["maximum"]) / 2 + middle_ = math.floor(middle//multipleOf) * multipleOf + try: + jsonschema.validate(middle_, schema) + return middle_ + except jsonschema.ValidationError: + return middle_ + multipleOf + elif "minimum" in schema: + # no maximum + m = schema["minimum"] + m = (math.ceil(m / multipleOf)) * multipleOf + if schema["exclusiveMinimum"] == m: + m += multipleOf + return m + elif "maximum" in schema: + # no minimum + M = schema["maximum"] + M = (math.floor(M / multipleOf)) * multipleOf + if schema["exclusiveMinimum"] == M: + M -= multipleOf + return M + else: + # neither min nor max + return 0 From dbb03d07db2fe3db5eb196f68b82854c04c627c6 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Thu, 5 Mar 2020 04:43:44 +0100 Subject: [PATCH 10/29] return default for const --- qt_jsonschema_form/defaults.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qt_jsonschema_form/defaults.py b/qt_jsonschema_form/defaults.py index 38e54b9..5159254 100644 --- a/qt_jsonschema_form/defaults.py +++ b/qt_jsonschema_form/defaults.py @@ -37,6 +37,10 @@ def compute_defaults(schema): if "enum" in schema: return enum_defaults(schema) + # Const + if "const" in schema: + return schema["const"] + schema_type = schema["type"] if schema_type in defaults: From 126b63af3e60b53ece199bb54ee850a03dbfed2c Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Thu, 5 Mar 2020 04:45:02 +0100 Subject: [PATCH 11/29] default value when no type, enum nor const --- qt_jsonschema_form/defaults.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qt_jsonschema_form/defaults.py b/qt_jsonschema_form/defaults.py index 5159254..1096dec 100644 --- a/qt_jsonschema_form/defaults.py +++ b/qt_jsonschema_form/defaults.py @@ -41,6 +41,10 @@ def compute_defaults(schema): if "const" in schema: return schema["const"] + if "type" not in schema: + # any value is valid. + return {} + schema_type = schema["type"] if schema_type in defaults: From cbfb0d6e38100617133cba214fdb61a5572eec5a Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Thu, 5 Mar 2020 04:47:02 +0100 Subject: [PATCH 12/29] deal with values of multiple possible types --- qt_jsonschema_form/defaults.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/qt_jsonschema_form/defaults.py b/qt_jsonschema_form/defaults.py index 1096dec..dfb6bb7 100644 --- a/qt_jsonschema_form/defaults.py +++ b/qt_jsonschema_form/defaults.py @@ -45,9 +45,12 @@ def compute_defaults(schema): # any value is valid. return {} - schema_type = schema["type"] + schema_types = schema["type"] + if not isinstance(schema_types, list): + schema_types = [schema_types] - if schema_type in defaults: - return defaults[schema_type](schema) + for schema_type in schema_types: + if schema_type in defaults: + return defaults[schema_type](schema) return None From 54c53a0f29058d7a3eb3e28f7f8d6f36a3a0e621 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Thu, 5 Mar 2020 04:49:29 +0100 Subject: [PATCH 13/29] separate list and tuple defaults --- qt_jsonschema_form/defaults.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/qt_jsonschema_form/defaults.py b/qt_jsonschema_form/defaults.py index dfb6bb7..7ba7e67 100644 --- a/qt_jsonschema_form/defaults.py +++ b/qt_jsonschema_form/defaults.py @@ -1,5 +1,6 @@ from .numeric_defaults import numeric_defaults + def enum_defaults(schema): try: return schema["enum"][0] @@ -11,16 +12,21 @@ def object_defaults(schema): return {k: compute_defaults(s) for k, s in schema["properties"].items()} -def array_defaults(schema): - items_schema = schema['items'] - if isinstance(items_schema, dict): - # in this case, it should be a list of items_schema - return [] +def list_defaults(schema): + return [] + - # in this case, it's a tuple. +def tuple_defaults(schema): return [compute_defaults(s) for s in schema["items"]] +def array_defaults(schema): + if isinstance(schema['items'], dict): + return list_defaults(schema) + else: + return tuple_defaults(schema) + + defaults = { "array": array_defaults, "object": object_defaults, From d596babe6907495cd9842fd45d8f963d2d67aafa Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Thu, 5 Mar 2020 04:51:31 +0100 Subject: [PATCH 14/29] list_default return min_items elements --- qt_jsonschema_form/defaults.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/qt_jsonschema_form/defaults.py b/qt_jsonschema_form/defaults.py index 7ba7e67..e877555 100644 --- a/qt_jsonschema_form/defaults.py +++ b/qt_jsonschema_form/defaults.py @@ -13,7 +13,12 @@ def object_defaults(schema): def list_defaults(schema): - return [] + # todo: respect unicity constraint. + minItems = schema.get("minItems", 0) + if minItems <= 0: + return [] + default = compute_defaults(schema["items"]) + return [default] * minItems def tuple_defaults(schema): From c1f79006ef901ed9fc91cd4fdda054ff3171e688 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Thu, 5 Mar 2020 04:59:03 +0100 Subject: [PATCH 15/29] deal with "contains" in list --- qt_jsonschema_form/defaults.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/qt_jsonschema_form/defaults.py b/qt_jsonschema_form/defaults.py index e877555..17c2470 100644 --- a/qt_jsonschema_form/defaults.py +++ b/qt_jsonschema_form/defaults.py @@ -14,6 +14,23 @@ def object_defaults(schema): def list_defaults(schema): # todo: respect unicity constraint. + # todo: deal with intersection of schema, in case there is contains and items + # e.g. add elements at end of tuple + if "contains" in schema: + return list_defaults_contains(schema) + else: + return list_defaults_no_contains(schema) + + +def list_defaults_contains(schema): + minContains = schema.get("minContains", 1) + if minContains <= 0: + return [] + default = compute_defaults(schema["contains"]) + return [default] * minContains + + +def list_defaults_no_contains(schema): minItems = schema.get("minItems", 0) if minItems <= 0: return [] From a7529be21c004f13a8d527b8157cb9c503416a99 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Thu, 5 Mar 2020 05:01:13 +0100 Subject: [PATCH 16/29] default boolean --- qt_jsonschema_form/defaults.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/qt_jsonschema_form/defaults.py b/qt_jsonschema_form/defaults.py index 17c2470..6fcbe34 100644 --- a/qt_jsonschema_form/defaults.py +++ b/qt_jsonschema_form/defaults.py @@ -49,11 +49,16 @@ def array_defaults(schema): return tuple_defaults(schema) +def boolean_defaults(schema): + return True + + defaults = { "array": array_defaults, "object": object_defaults, "numeric": numeric_defaults, "integer": numeric_defaults, + "boolean": boolean_defaults, } From 5c38f34a90b325ae615f810a37cdc234ca65218c Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Thu, 5 Mar 2020 05:07:08 +0100 Subject: [PATCH 17/29] string default --- qt_jsonschema_form/defaults.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/qt_jsonschema_form/defaults.py b/qt_jsonschema_form/defaults.py index 6fcbe34..8e92bde 100644 --- a/qt_jsonschema_form/defaults.py +++ b/qt_jsonschema_form/defaults.py @@ -53,12 +53,19 @@ def boolean_defaults(schema): return True +def string_defaults(schema): + # todo: deal with pattern + minLength = schema.get("minLength", 0) + return " " * minLength + + defaults = { "array": array_defaults, "object": object_defaults, "numeric": numeric_defaults, "integer": numeric_defaults, "boolean": boolean_defaults, + "string": string_defaults, } From 6d104ed4cfcfee00ba1b3b4ba56f8316ec7bdc40 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Thu, 5 Mar 2020 06:23:41 +0100 Subject: [PATCH 18/29] apply max length to string --- qt_jsonschema_form/widgets.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/qt_jsonschema_form/widgets.py b/qt_jsonschema_form/widgets.py index bd1b197..8a9aa20 100644 --- a/qt_jsonschema_form/widgets.py +++ b/qt_jsonschema_form/widgets.py @@ -54,13 +54,21 @@ def _set_valid_state(self, error: Exception = None): class TextSchemaWidget(SchemaWidgetMixin, QtWidgets.QLineEdit): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.maxLength = self.schema.get("maxLength") def configure(self): self.textChanged.connect(self.on_changed.emit) @state_property def state(self) -> str: - return str(self.text()) + state = str(self.text()) + if self.maxLength is not None and len(state) > self.maxLength: + state = state[:self.maxLength] + self.setText(state) + # Stripping the text to limit to the admitted length + return state @state.setter def state(self, state: str): @@ -76,10 +84,18 @@ def configure(self): class TextAreaSchemaWidget(SchemaWidgetMixin, QtWidgets.QTextEdit): + def __init__(self, *args, **kwargs): + super(*args, **kwargs) + self.maxLength = self.schema.get("maxLength") @state_property def state(self) -> str: - return str(self.toPlainText()) + state = str(self.toPlainText()) + if self.maxLength is not None and len(state) > self.maxLength: + state = state[:self.maxLength] + self.setPlainText(state) + # Stripping the text to limit to the admitted length + return state @state.setter def state(self, state: str): From 6b5f3f2514e2098a85bcf0cedc91957efc9af523 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Thu, 5 Mar 2020 06:24:05 +0100 Subject: [PATCH 19/29] comments --- qt_jsonschema_form/form.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/qt_jsonschema_form/form.py b/qt_jsonschema_form/form.py index f6e3fab..eee4837 100644 --- a/qt_jsonschema_form/form.py +++ b/qt_jsonschema_form/form.py @@ -5,10 +5,12 @@ from . import widgets from .defaults import compute_defaults -\ - def get_widget_state(schema, state=None): + """A JSON object. Either the state given in input if any, otherwise + the default value satisfying the current type. + + """ if state is None: return compute_defaults(schema) return state @@ -44,6 +46,8 @@ class WidgetBuilder: } def __init__(self, validator_cls=None): + """validator_cls -- A validator, as in jsonschema library. Schemas are + supposed to be valid for it.""" self.widget_map = deepcopy(self.default_widget_map) self.validator_cls = validator_cls @@ -58,6 +62,8 @@ def create_form(self, schema: dict, ui_schema: dict, state=None) -> widgets.Sche form = widgets.FormWidget(schema_widget) def validate(data): + """Show the error widget iff there are errors, and the error messages + in it.""" form.clear_errors() errors = [*validator.iter_errors(data)] From d3c97025a103f8c03b0a8a92b1ad27529ca00e25 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Thu, 5 Mar 2020 07:07:32 +0100 Subject: [PATCH 20/29] USAGE.md --- README.md | 3 ++ USAGE.md | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 USAGE.md diff --git a/README.md b/README.md index 1fcad09..c8fb83f 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,9 @@ Currently this tool does not support `anyOf` or `oneOf` directives. The reason f Additionally, the `$ref` keyword is not supported. This will be fixed, but is waiting on some proposed upstream changes in `jsonschema` +## Detailed explanation +For more details about each options, see [](USAGE.md) + ## Example ```python3 import sys diff --git a/USAGE.md b/USAGE.md new file mode 100644 index 0000000..35a6442 --- /dev/null +++ b/USAGE.md @@ -0,0 +1,98 @@ +# Main usage +As shown in the example, the main use of this library is as follows: + +```python3 +from qt_jsonschema_form import WidgetBuilder +builder = WidgetBuilder() +form = builder.create_form(schema, ui_schema) +form.show() +form.widget.state = default_value +``` +You can then apply a method `fn(json_value)` to the JSON value each time a change +is done by doing: +```python3 +form.widget.on_changed.connect(fn) +``` +and you can access to the current json value using +```python3 +form.widget.state +```. + +# Variants +JSON's type is extremely vague. For example decimal (floating point) +number and integral numbers have the same type. In order to decide +which widget the user see, this library uses "variant". A variant is a +name stating which kind of widget should be used to display a value to +the user and let them change it. A variant can only be assigned to an +element of an object, it is determined by the property name and the +parameter ui_schema. TODO: why? + +Each type has the variant "enum". If this variant is used for a +schema, this schema must have an "enum" property. Each element of this +property will be displayed in a `QComboBox`. We now list the other +variants. + +## Boolean +The only other variant of "boolean" is "checkbox", which is +implemented using a `QCheckBox` + +## Object +The only other variant of "object" is "object", which is implemented +using a `QGroupBox`. That is: its content is displayed in the same +window, with elements grouped together. + +## Number + +Number has two variants: +* "spin", which uses a `QDoubleSpinBox` +* "text", which uses a `QLineEdit` + +## Integer + +It admits the same variants as Number, plus: +* "range", which uses a `QSlider` + +## String + +Strings has the following variant: +* "textarea", which uses a `QTextEdit` +* "text", which uses a `QLineEdit` +* "password", which uses a `TextSchemaWidget` +* "filepath", which adds a button which use the open file name in the computer +* "colour", which uses a `QColorButton` + +# Defaults +When the UI is created, default values are inserted. Those values may +be changed by the user for a specific json value. Those values are of +the correct types; it's not guaranteed however that they satisfy all +schema constraints. (Actually, since there can be conjunction, +disjunction, negation of schema, and even if-then-else schema, finding +such value may be actually impossible). + +If a schema contains a "default" value, this value is used. + +If a schema is an enum, its first value is used as default value. + +If the type of the schema is an object, then its default value is an object +containing the values in "properties", and its associated default +values are computed as explained in this section. + +If the type of the schema is a list (array whose "items" is a schema) +then the default value is the empty list. + +If the type of the schema is a tuple (array whose "items" is an array +of schema) then the default value is a tuple, whose value contains as +many element as the items. Each element's default value is computed as +explained in this section. + +The default value of Boolean is True. + +The default value of a string is a string containing only spaces, as +short as possible, accordin to the minimal size constraint. + +The default value of a number is: +* as close as possible to the average between the maximum and the + minimum if they are set. +* as close as possible to the maximum or to the minimum if only one is + set +* 0 otherwise. From c2553e9607eafbfdc6ae3ff2a7ee60c83408fe63 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Sat, 7 Mar 2020 00:16:08 +0100 Subject: [PATCH 21/29] allow to give a parent window --- qt_jsonschema_form/form.py | 4 ++-- qt_jsonschema_form/widgets.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qt_jsonschema_form/form.py b/qt_jsonschema_form/form.py index eee4837..c5e589c 100644 --- a/qt_jsonschema_form/form.py +++ b/qt_jsonschema_form/form.py @@ -51,7 +51,7 @@ def __init__(self, validator_cls=None): self.widget_map = deepcopy(self.default_widget_map) self.validator_cls = validator_cls - def create_form(self, schema: dict, ui_schema: dict, state=None) -> widgets.SchemaWidgetMixin: + def create_form(self, schema: dict, ui_schema: dict, state=None, parent=None) -> widgets.SchemaWidgetMixin: validator_cls = self.validator_cls if validator_cls is None: validator_cls = validator_for(schema) @@ -59,7 +59,7 @@ def create_form(self, schema: dict, ui_schema: dict, state=None) -> widgets.Sche validator_cls.check_schema(schema) validator = validator_cls(schema) schema_widget = self.create_widget(schema, ui_schema, state) - form = widgets.FormWidget(schema_widget) + form = widgets.FormWidget(schema_widget, parent) def validate(data): """Show the error widget iff there are errors, and the error messages diff --git a/qt_jsonschema_form/widgets.py b/qt_jsonschema_form/widgets.py index 8a9aa20..e7c1576 100644 --- a/qt_jsonschema_form/widgets.py +++ b/qt_jsonschema_form/widgets.py @@ -572,8 +572,8 @@ def _index_changed(self, index: int): class FormWidget(QtWidgets.QWidget): - def __init__(self, widget: SchemaWidgetMixin): - super().__init__() + def __init__(self, widget: SchemaWidgetMixin, parent=None): + super().__init__(parent=parent) layout = QtWidgets.QVBoxLayout() self.setLayout(layout) From 16625af1ebf831bc8c72b6bb55eee1d37fcb2ba8 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Fri, 6 Mar 2020 20:16:55 +0100 Subject: [PATCH 22/29] default empty ui_schema --- qt_jsonschema_form/form.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt_jsonschema_form/form.py b/qt_jsonschema_form/form.py index c5e589c..095332a 100644 --- a/qt_jsonschema_form/form.py +++ b/qt_jsonschema_form/form.py @@ -51,7 +51,7 @@ def __init__(self, validator_cls=None): self.widget_map = deepcopy(self.default_widget_map) self.validator_cls = validator_cls - def create_form(self, schema: dict, ui_schema: dict, state=None, parent=None) -> widgets.SchemaWidgetMixin: + def create_form(self, schema: dict, ui_schema: dict = {}, state=None, parent=None) -> widgets.SchemaWidgetMixin: validator_cls = self.validator_cls if validator_cls is None: validator_cls = validator_for(schema) From ea2a3376406d26625cc4d74e7667d308f0e0c3a0 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Sat, 7 Mar 2020 03:01:33 +0100 Subject: [PATCH 23/29] save layout as FormWidget parameter So that caller may add elements to layout --- qt_jsonschema_form/widgets.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qt_jsonschema_form/widgets.py b/qt_jsonschema_form/widgets.py index e7c1576..aa6e121 100644 --- a/qt_jsonschema_form/widgets.py +++ b/qt_jsonschema_form/widgets.py @@ -570,12 +570,12 @@ def _index_changed(self, index: int): self.on_changed.emit(self.state) -class FormWidget(QtWidgets.QWidget): +class FormWidget(QtWidgets.QDialog): def __init__(self, widget: SchemaWidgetMixin, parent=None): super().__init__(parent=parent) - layout = QtWidgets.QVBoxLayout() - self.setLayout(layout) + self.layout = QtWidgets.QVBoxLayout() + self.setLayout(self.layout) self.error_widget = QtWidgets.QGroupBox() self.error_widget.setTitle("Errors") @@ -583,8 +583,8 @@ def __init__(self, widget: SchemaWidgetMixin, parent=None): self.error_widget.setLayout(self.error_layout) self.error_widget.hide() - layout.addWidget(self.error_widget) - layout.addWidget(widget) + self.layout.addWidget(self.error_widget) + self.layout.addWidget(widget) self.widget = widget From 74a57e7532e8b0f939cb91a59d12ab40ba62d6c5 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Sat, 7 Mar 2020 04:05:52 +0100 Subject: [PATCH 24/29] directory for file --- USAGE.md | 1 + qt_jsonschema_form/form.py | 2 +- qt_jsonschema_form/widgets.py | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/USAGE.md b/USAGE.md index 35a6442..0feaf0d 100644 --- a/USAGE.md +++ b/USAGE.md @@ -59,6 +59,7 @@ Strings has the following variant: * "text", which uses a `QLineEdit` * "password", which uses a `TextSchemaWidget` * "filepath", which adds a button which use the open file name in the computer +* "dirpath", which adds a button which use the open directory name in the computer * "colour", which uses a `QColorButton` # Defaults diff --git a/qt_jsonschema_form/form.py b/qt_jsonschema_form/form.py index 095332a..421752d 100644 --- a/qt_jsonschema_form/form.py +++ b/qt_jsonschema_form/form.py @@ -26,7 +26,7 @@ class WidgetBuilder: "object": {"object": widgets.ObjectSchemaWidget, "enum": widgets.EnumSchemaWidget}, "number": {"spin": widgets.SpinDoubleSchemaWidget, "text": widgets.TextSchemaWidget, "enum": widgets.EnumSchemaWidget}, "string": {"textarea": widgets.TextAreaSchemaWidget, "text": widgets.TextSchemaWidget, "password": widgets.PasswordWidget, - "filepath": widgets.FilepathSchemaWidget, "colour": widgets.ColorSchemaWidget, "enum": widgets.EnumSchemaWidget}, + "filepath": widgets.FilepathSchemaWidget, "dirpath": widgets.DirectorypathSchemaWidget, "colour": widgets.ColorSchemaWidget, "enum": widgets.EnumSchemaWidget}, "integer": {"spin": widgets.SpinSchemaWidget, "text": widgets.TextSchemaWidget, "range": widgets.IntegerRangeSchemaWidget, "enum": widgets.EnumSchemaWidget}, "array": {"array": widgets.ArraySchemaWidget, "enum": widgets.EnumSchemaWidget} diff --git a/qt_jsonschema_form/widgets.py b/qt_jsonschema_form/widgets.py index aa6e121..72936fe 100644 --- a/qt_jsonschema_form/widgets.py +++ b/qt_jsonschema_form/widgets.py @@ -305,6 +305,12 @@ def state(self, state: str): self.path_widget.setText(state) +class DirectorypathSchemaWidget(FilepathSchemaWidget): + def _on_clicked(self, flag): + path = QtWidgets.QFileDialog.getExistingDirectory() + self.path_widget.setText(path) + + class ArrayControlsWidget(QtWidgets.QWidget): on_delete = QtCore.pyqtSignal() on_move_up = QtCore.pyqtSignal() From 54c558e3c9fad2712fa816d86a912a13359676a2 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Sat, 7 Mar 2020 04:32:37 +0100 Subject: [PATCH 25/29] tool tip over labels in objects --- qt_jsonschema_form/widgets.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qt_jsonschema_form/widgets.py b/qt_jsonschema_form/widgets.py index 72936fe..ec1b02d 100644 --- a/qt_jsonschema_form/widgets.py +++ b/qt_jsonschema_form/widgets.py @@ -544,6 +544,9 @@ def populate_from_schema(self, schema: dict, ui_schema: dict, widget_builder: 'W sub_schema, sub_ui_schema) # TODO onchanged widget.on_changed.connect(partial(self.widget_on_changed, name)) label = sub_schema.get("title", name) + label = QtWidgets.QLabel(label) + if "description" in sub_schema: + label.setToolTip(sub_schema["description"]) layout.addRow(label, widget) widgets[name] = widget From 37017b75d97b0cc0badbb67a65c38baf5196b4b3 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Wed, 11 Mar 2020 17:55:46 +0100 Subject: [PATCH 26/29] error message for some wrong value --- qt_jsonschema_form/widgets.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qt_jsonschema_form/widgets.py b/qt_jsonschema_form/widgets.py index ec1b02d..730ac2a 100644 --- a/qt_jsonschema_form/widgets.py +++ b/qt_jsonschema_form/widgets.py @@ -113,6 +113,8 @@ def state(self) -> bool: @state.setter def state(self, checked: bool): + if not isinstance(checked, bool): + print(f"«{checked}» should be a bool and is a {type(checked)}") self.setChecked(checked) def configure(self): @@ -563,6 +565,7 @@ def state(self): def state(self, value): index = self.findData(value) if index == -1: + print(f"Value {value} not found in the combo box.") raise ValueError(value) self.setCurrentIndex(index) From b71508091c5fbe898474cf72dd26a75d6d9fcdf4 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Wed, 11 Mar 2020 17:55:50 +0100 Subject: [PATCH 27/29] remove example and redirects to test Example was wrong because of date --- README.md | 87 +------------------------------------------------------ 1 file changed, 1 insertion(+), 86 deletions(-) diff --git a/README.md b/README.md index c8fb83f..0fcacc7 100644 --- a/README.md +++ b/README.md @@ -15,89 +15,4 @@ Additionally, the `$ref` keyword is not supported. This will be fixed, but is wa For more details about each options, see [](USAGE.md) ## Example -```python3 -import sys -from json import dumps - -from PyQt5 import QtWidgets - -from qt_jsonschema_form import WidgetBuilder - -if __name__ == "__main__": - app = QtWidgets.QApplication(sys.argv) - - builder = WidgetBuilder() - - schema = { - "type": "object", - "title": "Number fields and widgets", - "properties": { - "schema_path": { - "title": "Schema path", - "type": "string" - }, - "integerRangeSteps": { - "title": "Integer range (by 10)", - "type": "integer", - "minimum": 55, - "maximum": 100, - "multipleOf": 10 - }, - "event": { - "type": "string", - "format": "date" - }, - "sky_colour": { - "type": "string" - }, - "names": { - "type": "array", - "items": [ - { - "type": "string", - "pattern": "[a-zA-Z\-'\s]+", - "enum": [ - "Jack", "Jill" - ] - }, - { - "type": "string", - "pattern": "[a-zA-Z\-'\s]+", - "enum": [ - "Alice", "Bob" - ] - }, - ], - "additionalItems": { - "type": "number" - }, - } - } - } - - ui_schema = { - "schema_path": { - "ui:widget": "filepath" - }, - "sky_colour": { - "ui:widget": "colour" - } - - } - form = builder.create_form(schema, ui_schema) - form.widget.state = { - "schema_path": "some_file.py", - "integerRangeSteps": 60, - "sky_colour": "#8f5902", - "names": [ - "Jack", - "Bob" - ] - } - form.show() - form.widget.on_changed.connect(lambda d: print(dumps(d, indent=4))) - - app.exec_() - - -``` +See (the test file)[test.py] From d803b112d23453c9027d1d7bcfcbd79855b63753 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Wed, 11 Mar 2020 18:02:05 +0100 Subject: [PATCH 28/29] create get_widget_variant --- qt_jsonschema_form/form.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/qt_jsonschema_form/form.py b/qt_jsonschema_form/form.py index 421752d..ee8cb4d 100644 --- a/qt_jsonschema_form/form.py +++ b/qt_jsonschema_form/form.py @@ -4,7 +4,7 @@ from . import widgets from .defaults import compute_defaults - +from typing import Dict, Any def get_widget_state(schema, state=None): """A JSON object. Either the state given in input if any, otherwise @@ -79,17 +79,7 @@ def validate(data): def create_widget(self, schema: dict, ui_schema: dict, state=None) -> widgets.SchemaWidgetMixin: schema_type = get_schema_type(schema) - - try: - default_variant = self.widget_variant_modifiers[schema_type]( - schema) - except KeyError: - default_variant = self.default_widget_variants[schema_type] - - if "enum" in schema: - default_variant = "enum" - - widget_variant = ui_schema.get('ui:widget', default_variant) + widget_variant = self.get_widget_variant(schema_type, schema, ui_schema) widget_cls = self.widget_map[schema_type][widget_variant] widget = widget_cls(schema, ui_schema, self) @@ -98,3 +88,14 @@ def create_widget(self, schema: dict, ui_schema: dict, state=None) -> widgets.Sc if default_state is not None: widget.state = default_state return widget + + def get_widget_variant(self, schema_type: str, schema: Dict[str, Any], ui_schema: Dict[str, Any]) -> str: + try: + default_variant = self.widget_variant_modifiers[schema_type](schema) + except KeyError: + default_variant = self.default_widget_variants[schema_type] + + if "enum" in schema: + default_variant = "enum" + + return ui_schema.get('ui:widget', default_variant) From ae1b255cb290a21733a77561a39290e26147a3d9 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Thu, 12 Mar 2020 03:41:49 +0100 Subject: [PATCH 29/29] all __init__ have args ans kwargs --- qt_jsonschema_form/widgets.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/qt_jsonschema_form/widgets.py b/qt_jsonschema_form/widgets.py index 730ac2a..f5e64db 100644 --- a/qt_jsonschema_form/widgets.py +++ b/qt_jsonschema_form/widgets.py @@ -85,7 +85,7 @@ def configure(self): class TextAreaSchemaWidget(SchemaWidgetMixin, QtWidgets.QTextEdit): def __init__(self, *args, **kwargs): - super(*args, **kwargs) + super().__init__(*args, **kwargs) self.maxLength = self.schema.get("maxLength") @state_property @@ -187,8 +187,8 @@ def configure(self): class IntegerRangeSchemaWidget(SchemaWidgetMixin, QtWidgets.QSlider): - def __init__(self, schema: dict, ui_schema: dict, widget_builder: 'WidgetBuilder'): - super().__init__(schema, ui_schema, widget_builder, orientation=QtCore.Qt.Horizontal) + def __init__(self, schema: dict, ui_schema: dict, widget_builder: 'WidgetBuilder', *args, **kwargs): + super().__init__(schema, ui_schema, widget_builder, orientation=QtCore.Qt.Horizontal, *args, **kwargs) @state_property def state(self) -> int: @@ -280,8 +280,8 @@ def state(self, data: str): class FilepathSchemaWidget(SchemaWidgetMixin, QtWidgets.QWidget): - def __init__(self, schema: dict, ui_schema: dict, widget_builder: 'WidgetBuilder'): - super().__init__(schema, ui_schema, widget_builder) + def __init__(self, schema: dict, ui_schema: dict, widget_builder: 'WidgetBuilder', *args, **kwargs): + super().__init__(schema, ui_schema, widget_builder, *args, **kwargs) layout = QtWidgets.QHBoxLayout() self.setLayout(layout) @@ -318,8 +318,8 @@ class ArrayControlsWidget(QtWidgets.QWidget): on_move_up = QtCore.pyqtSignal() on_move_down = QtCore.pyqtSignal() - def __init__(self): - super().__init__() + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) style = self.style() @@ -348,8 +348,8 @@ def __init__(self): class ArrayRowWidget(QtWidgets.QWidget): - def __init__(self, widget: QtWidgets.QWidget, controls: ArrayControlsWidget): - super().__init__() + def __init__(self, widget: QtWidgets.QWidget, controls: ArrayControlsWidget, *args, **kwargs): + super().__init__(*args, **kwargs) layout = QtWidgets.QHBoxLayout() layout.addWidget(widget) @@ -501,8 +501,8 @@ def widget_on_changed(self, row: ArrayRowWidget, value): class ObjectSchemaWidget(SchemaWidgetMixin, QtWidgets.QGroupBox): - def __init__(self, schema: dict, ui_schema: dict, widget_builder: 'WidgetBuilder'): - super().__init__(schema, ui_schema, widget_builder) + def __init__(self, schema: dict, ui_schema: dict, widget_builder: 'WidgetBuilder', *args, **kwargs): + super().__init__(schema, ui_schema, widget_builder, *args, **kwargs) self.widgets = self.populate_from_schema( schema, ui_schema, widget_builder) @@ -584,8 +584,8 @@ def _index_changed(self, index: int): class FormWidget(QtWidgets.QDialog): - def __init__(self, widget: SchemaWidgetMixin, parent=None): - super().__init__(parent=parent) + def __init__(self, widget: SchemaWidgetMixin, parent=None, *args, **kwargs): + super().__init__(*args, parent=parent, **kwargs) self.layout = QtWidgets.QVBoxLayout() self.setLayout(self.layout)