py10x-universe 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- core_10x/__init__.py +42 -0
- core_10x/backbone/__init__.py +0 -0
- core_10x/backbone/backbone_store.py +59 -0
- core_10x/backbone/backbone_traitable.py +30 -0
- core_10x/backbone/backbone_user.py +66 -0
- core_10x/backbone/bound_data_domain.py +49 -0
- core_10x/backbone/namespace.py +101 -0
- core_10x/backbone/vault.py +38 -0
- core_10x/code_samples/__init__.py +0 -0
- core_10x/code_samples/_package_manifest.py +3 -0
- core_10x/code_samples/directories.py +181 -0
- core_10x/code_samples/person.py +76 -0
- core_10x/concrete_traits.py +356 -0
- core_10x/conftest.py +12 -0
- core_10x/curve.py +321 -0
- core_10x/data_domain.py +48 -0
- core_10x/data_domain_binder.py +45 -0
- core_10x/directory.py +250 -0
- core_10x/entity.py +8 -0
- core_10x/entity_filter.py +5 -0
- core_10x/environment_variables.py +147 -0
- core_10x/exec_control.py +84 -0
- core_10x/experimental/__init__.py +0 -0
- core_10x/experimental/data_protocol_ex.py +34 -0
- core_10x/global_cache.py +121 -0
- core_10x/manual_tests/__init__.py +0 -0
- core_10x/manual_tests/calendar_test.py +35 -0
- core_10x/manual_tests/ctor_update_bug.py +58 -0
- core_10x/manual_tests/debug_graph_on.py +17 -0
- core_10x/manual_tests/debug_graphoff_inside_graph_on.py +28 -0
- core_10x/manual_tests/enum_bits_test.py +17 -0
- core_10x/manual_tests/env_vars_trivial_test.py +12 -0
- core_10x/manual_tests/existing_traitable.py +33 -0
- core_10x/manual_tests/k10x_test1.py +13 -0
- core_10x/manual_tests/named_constant_test.py +121 -0
- core_10x/manual_tests/nucleus_trivial_test.py +42 -0
- core_10x/manual_tests/polars_test.py +14 -0
- core_10x/manual_tests/py_class_test.py +4 -0
- core_10x/manual_tests/rc_test.py +42 -0
- core_10x/manual_tests/rdate_test.py +12 -0
- core_10x/manual_tests/reference_serialization_bug.py +19 -0
- core_10x/manual_tests/resource_trivial_test.py +10 -0
- core_10x/manual_tests/store_uri_test.py +6 -0
- core_10x/manual_tests/trait_definition_test.py +19 -0
- core_10x/manual_tests/trait_filter_test.py +15 -0
- core_10x/manual_tests/trait_flag_modification_test.py +42 -0
- core_10x/manual_tests/trait_modification_bug.py +26 -0
- core_10x/manual_tests/traitable_as_of_test.py +82 -0
- core_10x/manual_tests/traitable_heir_test.py +39 -0
- core_10x/manual_tests/traitable_history_test.py +41 -0
- core_10x/manual_tests/traitable_serialization_test.py +54 -0
- core_10x/manual_tests/traitable_trivial_test.py +71 -0
- core_10x/manual_tests/trivial_graph_test.py +16 -0
- core_10x/manual_tests/ts_class_association_test.py +64 -0
- core_10x/manual_tests/ts_trivial_test.py +35 -0
- core_10x/named_constant.py +425 -0
- core_10x/nucleus.py +81 -0
- core_10x/package_manifest.py +85 -0
- core_10x/package_refactoring.py +153 -0
- core_10x/py_class.py +431 -0
- core_10x/rc.py +155 -0
- core_10x/rdate.py +339 -0
- core_10x/resource.py +189 -0
- core_10x/roman_number.py +67 -0
- core_10x/testlib/__init__.py +0 -0
- core_10x/testlib/test_store.py +240 -0
- core_10x/testlib/traitable_history_tests.py +787 -0
- core_10x/testlib/ts_tests.py +280 -0
- core_10x/trait.py +377 -0
- core_10x/trait_definition.py +176 -0
- core_10x/trait_filter.py +205 -0
- core_10x/trait_method_error.py +36 -0
- core_10x/traitable.py +1082 -0
- core_10x/traitable_cli.py +153 -0
- core_10x/traitable_heir.py +33 -0
- core_10x/traitable_id.py +31 -0
- core_10x/ts_store.py +172 -0
- core_10x/ts_store_type.py +26 -0
- core_10x/ts_union.py +147 -0
- core_10x/ui_hint.py +153 -0
- core_10x/unit_tests/test_concrete_traits.py +156 -0
- core_10x/unit_tests/test_converters.py +51 -0
- core_10x/unit_tests/test_curve.py +157 -0
- core_10x/unit_tests/test_directory.py +54 -0
- core_10x/unit_tests/test_documentation.py +172 -0
- core_10x/unit_tests/test_environment_variables.py +15 -0
- core_10x/unit_tests/test_filters.py +239 -0
- core_10x/unit_tests/test_graph.py +348 -0
- core_10x/unit_tests/test_named_constant.py +98 -0
- core_10x/unit_tests/test_rc.py +11 -0
- core_10x/unit_tests/test_rdate.py +484 -0
- core_10x/unit_tests/test_trait_method_error.py +80 -0
- core_10x/unit_tests/test_trait_modification.py +19 -0
- core_10x/unit_tests/test_traitable.py +959 -0
- core_10x/unit_tests/test_traitable_history.py +1 -0
- core_10x/unit_tests/test_ts_store.py +1 -0
- core_10x/unit_tests/test_ts_union.py +369 -0
- core_10x/unit_tests/test_ui_nodes.py +81 -0
- core_10x/unit_tests/test_xxcalendar.py +471 -0
- core_10x/vault/__init__.py +0 -0
- core_10x/vault/sec_keys.py +133 -0
- core_10x/vault/security_keys_old.py +168 -0
- core_10x/vault/vault.py +56 -0
- core_10x/vault/vault_traitable.py +56 -0
- core_10x/vault/vault_user.py +70 -0
- core_10x/xdate_time.py +136 -0
- core_10x/xnone.py +71 -0
- core_10x/xxcalendar.py +228 -0
- infra_10x/__init__.py +0 -0
- infra_10x/manual_tests/__init__.py +0 -0
- infra_10x/manual_tests/test_misc.py +16 -0
- infra_10x/manual_tests/test_prepare_filter_and_pipeline.py +25 -0
- infra_10x/mongodb_admin.py +111 -0
- infra_10x/mongodb_store.py +346 -0
- infra_10x/mongodb_utils.py +129 -0
- infra_10x/unit_tests/conftest.py +13 -0
- infra_10x/unit_tests/test_mongo_db.py +36 -0
- infra_10x/unit_tests/test_mongo_history.py +1 -0
- py10x_universe-0.1.3.dist-info/METADATA +406 -0
- py10x_universe-0.1.3.dist-info/RECORD +214 -0
- py10x_universe-0.1.3.dist-info/WHEEL +4 -0
- py10x_universe-0.1.3.dist-info/licenses/LICENSE +21 -0
- ui_10x/__init__.py +0 -0
- ui_10x/apps/__init__.py +0 -0
- ui_10x/apps/collection_editor_app.py +100 -0
- ui_10x/choice.py +212 -0
- ui_10x/collection_editor.py +135 -0
- ui_10x/concrete_trait_widgets.py +220 -0
- ui_10x/conftest.py +8 -0
- ui_10x/entity_stocker.py +173 -0
- ui_10x/examples/__init__.py +0 -0
- ui_10x/examples/_guess_word_data.py +14076 -0
- ui_10x/examples/collection_editor.py +17 -0
- ui_10x/examples/date_selector.py +14 -0
- ui_10x/examples/entity_stocker.py +18 -0
- ui_10x/examples/guess_word.py +392 -0
- ui_10x/examples/message_box.py +20 -0
- ui_10x/examples/multi_choice.py +17 -0
- ui_10x/examples/py_data_browser.py +66 -0
- ui_10x/examples/radiobox.py +29 -0
- ui_10x/examples/single_choice.py +31 -0
- ui_10x/examples/style_sheet.py +47 -0
- ui_10x/examples/trivial_entity_editor.py +18 -0
- ui_10x/platform.py +20 -0
- ui_10x/platform_interface.py +517 -0
- ui_10x/py_data_browser.py +249 -0
- ui_10x/qt6/__init__.py +0 -0
- ui_10x/qt6/conftest.py +8 -0
- ui_10x/qt6/manual_tests/__init__.py +0 -0
- ui_10x/qt6/manual_tests/basic_test.py +35 -0
- ui_10x/qt6/platform_implementation.py +275 -0
- ui_10x/qt6/utils.py +665 -0
- ui_10x/rio/__init__.py +0 -0
- ui_10x/rio/apps/examples/examples/__init__.py +22 -0
- ui_10x/rio/apps/examples/examples/components/__init__.py +3 -0
- ui_10x/rio/apps/examples/examples/components/collection_editor.py +15 -0
- ui_10x/rio/apps/examples/examples/pages/collection_editor.py +21 -0
- ui_10x/rio/apps/examples/examples/pages/login_page.py +88 -0
- ui_10x/rio/apps/examples/examples/pages/style_sheet.py +21 -0
- ui_10x/rio/apps/examples/rio.toml +14 -0
- ui_10x/rio/component_builder.py +497 -0
- ui_10x/rio/components/__init__.py +9 -0
- ui_10x/rio/components/group_box.py +31 -0
- ui_10x/rio/components/labeled_checkbox.py +18 -0
- ui_10x/rio/components/line_edit.py +37 -0
- ui_10x/rio/components/radio_button.py +32 -0
- ui_10x/rio/components/separator.py +24 -0
- ui_10x/rio/components/splitter.py +121 -0
- ui_10x/rio/components/tree_view.py +75 -0
- ui_10x/rio/conftest.py +35 -0
- ui_10x/rio/internals/__init__.py +0 -0
- ui_10x/rio/internals/app.py +192 -0
- ui_10x/rio/manual_tests/__init__.py +0 -0
- ui_10x/rio/manual_tests/basic_test.py +24 -0
- ui_10x/rio/manual_tests/splitter.py +27 -0
- ui_10x/rio/platform_implementation.py +91 -0
- ui_10x/rio/style_sheet.py +53 -0
- ui_10x/rio/unit_tests/test_collection_editor.py +68 -0
- ui_10x/rio/unit_tests/test_internals.py +630 -0
- ui_10x/rio/unit_tests/test_style_sheet.py +37 -0
- ui_10x/rio/widgets/__init__.py +46 -0
- ui_10x/rio/widgets/application.py +109 -0
- ui_10x/rio/widgets/button.py +48 -0
- ui_10x/rio/widgets/button_group.py +60 -0
- ui_10x/rio/widgets/calendar.py +23 -0
- ui_10x/rio/widgets/checkbox.py +24 -0
- ui_10x/rio/widgets/dialog.py +137 -0
- ui_10x/rio/widgets/group_box.py +27 -0
- ui_10x/rio/widgets/layout.py +34 -0
- ui_10x/rio/widgets/line_edit.py +37 -0
- ui_10x/rio/widgets/list.py +105 -0
- ui_10x/rio/widgets/message_box.py +70 -0
- ui_10x/rio/widgets/scroll_area.py +31 -0
- ui_10x/rio/widgets/spacer.py +6 -0
- ui_10x/rio/widgets/splitter.py +45 -0
- ui_10x/rio/widgets/text_edit.py +28 -0
- ui_10x/rio/widgets/tree.py +89 -0
- ui_10x/rio/widgets/unit_tests/test_button.py +101 -0
- ui_10x/rio/widgets/unit_tests/test_button_group.py +33 -0
- ui_10x/rio/widgets/unit_tests/test_calendar.py +114 -0
- ui_10x/rio/widgets/unit_tests/test_checkbox.py +109 -0
- ui_10x/rio/widgets/unit_tests/test_group_box.py +158 -0
- ui_10x/rio/widgets/unit_tests/test_label.py +43 -0
- ui_10x/rio/widgets/unit_tests/test_line_edit.py +140 -0
- ui_10x/rio/widgets/unit_tests/test_list.py +146 -0
- ui_10x/table_header_view.py +305 -0
- ui_10x/table_view.py +174 -0
- ui_10x/trait_editor.py +189 -0
- ui_10x/trait_widget.py +131 -0
- ui_10x/traitable_editor.py +200 -0
- ui_10x/traitable_view.py +131 -0
- ui_10x/unit_tests/conftest.py +8 -0
- ui_10x/unit_tests/test_platform.py +9 -0
- ui_10x/utils.py +661 -0
ui_10x/trait_widget.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from core_10x.rc import RC, RC_TRUE
|
|
6
|
+
from core_10x.trait import T, Ui
|
|
7
|
+
|
|
8
|
+
from ui_10x.utils import UxStyleSheet, ux
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from core_10x.trait import Trait
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TraitWidget:
|
|
15
|
+
# __slots__ = ('program_edit', 'refresh_context', 'style_sheet', 'trait', 'trait_editor')
|
|
16
|
+
s_hinted_widgets = {}
|
|
17
|
+
|
|
18
|
+
def __init_subclass__(cls, widget_type: Ui.WIDGET_TYPE = None, **kwargs):
|
|
19
|
+
if widget_type:
|
|
20
|
+
TraitWidget.s_hinted_widgets[widget_type] = cls
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def widget_class(widget_type: Ui.WIDGET_TYPE):
|
|
24
|
+
w_cls = TraitWidget.s_hinted_widgets.get(widget_type)
|
|
25
|
+
assert w_cls, f'Unknown trait widget type: {widget_type}'
|
|
26
|
+
return w_cls
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def instance(trait_editor) -> TraitWidget | None:
|
|
30
|
+
w_type = trait_editor.ui_hint.widget_type
|
|
31
|
+
if w_type is not Ui.WIDGET_TYPE.NONE:
|
|
32
|
+
w_cls = TraitWidget.widget_class(w_type)
|
|
33
|
+
return w_cls(trait_editor)
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
def __init__(self, trait_editor):
|
|
37
|
+
self.trait_editor = trait_editor
|
|
38
|
+
self.trait: Trait = trait_editor.trait
|
|
39
|
+
self.program_edit = False
|
|
40
|
+
|
|
41
|
+
self._create()
|
|
42
|
+
|
|
43
|
+
if self.trait_editor.is_read_only():
|
|
44
|
+
self._set_read_only(True)
|
|
45
|
+
|
|
46
|
+
self.set_tool_tip(self.trait.ui_hint.tip)
|
|
47
|
+
|
|
48
|
+
entity = self.trait_editor.entity
|
|
49
|
+
|
|
50
|
+
self.style_sheet = sheet = UxStyleSheet(self)
|
|
51
|
+
sh = entity.get_style_sheet(self.trait)
|
|
52
|
+
sheet.update(sh)
|
|
53
|
+
|
|
54
|
+
value = entity.get_value(self.trait)
|
|
55
|
+
self.set_widget_value(value)
|
|
56
|
+
|
|
57
|
+
self.refresh_context = ctx = RefreshContext(self)
|
|
58
|
+
entity.create_ui_node(self.trait, ctx.emit_signal)
|
|
59
|
+
|
|
60
|
+
def refresh(self):
|
|
61
|
+
entity = self.trait_editor.entity
|
|
62
|
+
trait = self.trait
|
|
63
|
+
entity.update_ui_node(self.trait)
|
|
64
|
+
|
|
65
|
+
if not trait.flags_on(T.EXPENSIVE):
|
|
66
|
+
value = entity.get_value(trait)
|
|
67
|
+
self.set_widget_value(value)
|
|
68
|
+
else:
|
|
69
|
+
sh = entity.get_style_sheet(trait)
|
|
70
|
+
self.style_sheet.update(sh)
|
|
71
|
+
|
|
72
|
+
if not entity.is_valid(trait):
|
|
73
|
+
self.style_sheet.update({Ui.BG_COLOR: 'lightblue'}, _system=True)
|
|
74
|
+
|
|
75
|
+
def widget_value(self):
|
|
76
|
+
return self._value()
|
|
77
|
+
|
|
78
|
+
def set_widget_value(self, value) -> bool:
|
|
79
|
+
self.program_edit = True
|
|
80
|
+
try:
|
|
81
|
+
self._set_value(value)
|
|
82
|
+
except Exception: # -- the real widget is gone (so far, has encountered this only in Qt on Mac OS)
|
|
83
|
+
# self.clean() #-- TODO: fix!
|
|
84
|
+
self.program_edit = False
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
sh = self.trait_editor.entity.get_style_sheet(self.trait)
|
|
88
|
+
self.style_sheet.update(sh)
|
|
89
|
+
self.program_edit = False
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
def update_trait_value(self, value: Any = None, invalidate: bool = False):
|
|
93
|
+
if not self.program_edit:
|
|
94
|
+
entity = self.trait_editor.entity
|
|
95
|
+
if invalidate:
|
|
96
|
+
entity.invalidate_value(self.trait)
|
|
97
|
+
rc = RC_TRUE
|
|
98
|
+
else:
|
|
99
|
+
if value is None:
|
|
100
|
+
value = self.widget_value()
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
rc = entity.set_value(self.trait, value)
|
|
104
|
+
except Exception as e:
|
|
105
|
+
rc = RC(False, str(e))
|
|
106
|
+
|
|
107
|
+
if not rc:
|
|
108
|
+
self.set_tool_tip(rc.error())
|
|
109
|
+
self.style_sheet.update({Ui.BG_COLOR: 'orange'}, _system=True)
|
|
110
|
+
else:
|
|
111
|
+
self.set_tool_tip(self.trait.ui_hint.tip)
|
|
112
|
+
self.style_sheet.restore()
|
|
113
|
+
|
|
114
|
+
# fmt: off
|
|
115
|
+
def _create(self): raise NotImplementedError
|
|
116
|
+
def _set_read_only(self, flag): raise NotImplementedError
|
|
117
|
+
def _value(self): raise NotImplementedError
|
|
118
|
+
def _set_value(self, value): raise NotImplementedError
|
|
119
|
+
# fmt: on
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class RefreshContext(ux.Object):
|
|
123
|
+
REFRESH = ux.signal_decl(object)
|
|
124
|
+
|
|
125
|
+
def __init__(self, trait_widget: TraitWidget):
|
|
126
|
+
super().__init__()
|
|
127
|
+
self.trait_widget = trait_widget
|
|
128
|
+
self.REFRESH.connect(TraitWidget.refresh, type=ux.QueuedConnection)
|
|
129
|
+
|
|
130
|
+
def emit_signal(self):
|
|
131
|
+
self.REFRESH.emit(self.trait_widget)
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from core_10x.exec_control import INTERACTIVE
|
|
6
|
+
from core_10x.global_cache import cache
|
|
7
|
+
from core_10x.py_class import PyClass
|
|
8
|
+
from core_10x.rc import RC_TRUE
|
|
9
|
+
from core_10x.trait import Ui
|
|
10
|
+
from core_10x.traitable import Traitable
|
|
11
|
+
|
|
12
|
+
from ui_10x.trait_editor import TraitEditor
|
|
13
|
+
from ui_10x.traitable_view import TraitableView
|
|
14
|
+
from ui_10x.utils import UxDialog, ux, ux_warning
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from collections.abc import Callable
|
|
18
|
+
|
|
19
|
+
from core_10x.rc import RC
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TraitableEditor:
|
|
23
|
+
"""
|
|
24
|
+
For many Traitables, a default Editor is sufficient.
|
|
25
|
+
A custom Editor, if needed, should be in a particular class, module and package.
|
|
26
|
+
Suppose your traitable class is abc.zyz.messages.TextMessage
|
|
27
|
+
- the custom editor class must be TextMessageEditor
|
|
28
|
+
- its module name must be messages_ui
|
|
29
|
+
- the module may be in either abc.xyz, abc.xyz.ui or alternative packages
|
|
30
|
+
- an editor class is searched first in alternative packages, if any; then in abc.xyz and abc.xyz.ui
|
|
31
|
+
- if a custom class is not found, the default TraitableEditor is used.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
@cache
|
|
36
|
+
def find_editor_class(
|
|
37
|
+
traitable_class: type[Traitable], *alternative_packages, traitable_class_parent: type[Traitable] = None, verify_custom_class: bool = True
|
|
38
|
+
):
|
|
39
|
+
assert issubclass(traitable_class, Traitable), f'{traitable_class} is not a subclass of Traitable'
|
|
40
|
+
assert not traitable_class_parent or issubclass(traitable_class, traitable_class_parent), (
|
|
41
|
+
f'{traitable_class} is not a subclass of {traitable_class_parent}'
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
found = PyClass.find_related_class(traitable_class, 'ui', 'Editor', *alternative_packages, alternative_parent_class=traitable_class_parent)
|
|
45
|
+
if found:
|
|
46
|
+
if verify_custom_class:
|
|
47
|
+
assert issubclass(found, TraitableEditor), (
|
|
48
|
+
f'{traitable_class} has a custom editor class {found} which is not a subclass of TraitableEditor'
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return found
|
|
52
|
+
|
|
53
|
+
return TraitableEditor
|
|
54
|
+
|
|
55
|
+
# fmt: off
|
|
56
|
+
@staticmethod
|
|
57
|
+
def editor(
|
|
58
|
+
entity: Traitable,
|
|
59
|
+
*alternative_packages, #-- if custom editor class should be looked up somewhere else
|
|
60
|
+
traitable_class_parent = None, #-- if a custom editor of entity's parent class could be used if it's missing for the entity.__class__
|
|
61
|
+
verify_custom_class: bool = True, #-- if a custom editor class should be verified if exists
|
|
62
|
+
view: TraitableView = None, #-- if a specific view for the entity should be used
|
|
63
|
+
read_only: bool = False #-- browser if True
|
|
64
|
+
) -> TraitableEditor:
|
|
65
|
+
# fmt: on
|
|
66
|
+
editor_class = TraitableEditor.find_editor_class(
|
|
67
|
+
entity.__class__,
|
|
68
|
+
*alternative_packages,
|
|
69
|
+
traitable_class_parent = traitable_class_parent,
|
|
70
|
+
verify_custom_class = verify_custom_class
|
|
71
|
+
)
|
|
72
|
+
return editor_class(entity, view = view, read_only = read_only, _confirm = True)
|
|
73
|
+
|
|
74
|
+
def __init__(self, entity: Traitable, view: TraitableView = None, read_only = False, _confirm = False):
|
|
75
|
+
assert _confirm, 'Do not call TraitableEditor() directly, use TraitableEditor.editor() instead'
|
|
76
|
+
assert isinstance(entity, Traitable), 'entity must be an instance of Traitable'
|
|
77
|
+
|
|
78
|
+
entity_class = entity.__class__
|
|
79
|
+
if view is None:
|
|
80
|
+
view = TraitableView.default(entity_class, read_only = read_only)
|
|
81
|
+
else:
|
|
82
|
+
assert issubclass(entity_class, view.cls), f'Given view is not for {entity_class}'
|
|
83
|
+
|
|
84
|
+
self.entity = entity
|
|
85
|
+
self.traitable_processor = None
|
|
86
|
+
|
|
87
|
+
trait_dir = entity_class.s_dir
|
|
88
|
+
self.trait_hints = trait_hints = {trait_dir[trait_name]: ui_hint for trait_name, ui_hint in view.ui_hints.items() if not ui_hint.flags_on(Ui.HIDDEN)}
|
|
89
|
+
|
|
90
|
+
self.main_w: ux.Widget|None = None
|
|
91
|
+
self.callbacks_for_traits = {}
|
|
92
|
+
self.init()
|
|
93
|
+
|
|
94
|
+
callbacks = self.callbacks_for_traits
|
|
95
|
+
self.trait_editors = {
|
|
96
|
+
trait.name: TraitEditor(entity, trait, ui_hint, custom_callback = callbacks.get(trait.name), traitable_processor = lambda : self.traitable_processor)
|
|
97
|
+
for trait, ui_hint in trait_hints.items()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
def init(self):
|
|
101
|
+
...
|
|
102
|
+
|
|
103
|
+
def set_callback_for_trait(self, trait_name: str, bound_method):
|
|
104
|
+
self.callbacks_for_traits[trait_name] = bound_method
|
|
105
|
+
|
|
106
|
+
def callback_for_trait(self, trait_name: str):
|
|
107
|
+
return self.callbacks_for_traits.get(trait_name)
|
|
108
|
+
|
|
109
|
+
def main_layout(self) -> ux.Layout:
|
|
110
|
+
lay = ux.FormLayout()
|
|
111
|
+
|
|
112
|
+
te: TraitEditor
|
|
113
|
+
for te in self.trait_editors.values():
|
|
114
|
+
label = te.new_label()
|
|
115
|
+
w = te.new_widget()
|
|
116
|
+
lay.add_row(label, w)
|
|
117
|
+
if te.ui_hint.flags_on(Ui.SEPARATOR):
|
|
118
|
+
lay.add_row(ux.separator())
|
|
119
|
+
|
|
120
|
+
return lay
|
|
121
|
+
|
|
122
|
+
def row_layout(self) -> ux.Layout:
|
|
123
|
+
row = ux.HBoxLayout()
|
|
124
|
+
|
|
125
|
+
stretched_trait_found = False
|
|
126
|
+
for te in self.trait_editors.values():
|
|
127
|
+
stretch = te.ui_hint.param('stretch', 0)
|
|
128
|
+
if stretch:
|
|
129
|
+
stretched_trait_found = True
|
|
130
|
+
|
|
131
|
+
row.add_widget(te.new_label())
|
|
132
|
+
row.add_widget(te.new_widget(), stretch=stretch)
|
|
133
|
+
|
|
134
|
+
if te.ui_hint.flags_on(Ui.SEPARATOR):
|
|
135
|
+
row.add_widget(ux.separator(horizontal = False))
|
|
136
|
+
|
|
137
|
+
if stretched_trait_found:
|
|
138
|
+
row.add_widget(ux.Label(), stretch = 1)
|
|
139
|
+
|
|
140
|
+
return row
|
|
141
|
+
|
|
142
|
+
def main_widget(self) -> ux.Widget:
|
|
143
|
+
self.main_w = w = ux.Widget()
|
|
144
|
+
lay = self.main_layout()
|
|
145
|
+
w.set_layout(lay)
|
|
146
|
+
return w
|
|
147
|
+
|
|
148
|
+
def _cleanup_tp(self, apply: bool):
|
|
149
|
+
self.main_w = None
|
|
150
|
+
if self.traitable_processor:
|
|
151
|
+
if apply:
|
|
152
|
+
self.traitable_processor.export_nodes()
|
|
153
|
+
self.traitable_processor=None
|
|
154
|
+
|
|
155
|
+
def _dialog(self, layout: ux.Layout, title: str, ok: str, min_width: int, on_accept: Callable[[], RC]) -> UxDialog:
|
|
156
|
+
ux.init()
|
|
157
|
+
if layout is not None:
|
|
158
|
+
w = self.main_w = ux.Widget()
|
|
159
|
+
w.set_layout(layout)
|
|
160
|
+
else:
|
|
161
|
+
w = self.main_widget()
|
|
162
|
+
|
|
163
|
+
def accept_callback():
|
|
164
|
+
self._cleanup_tp(True)
|
|
165
|
+
rc = self.entity.verify()
|
|
166
|
+
if rc:
|
|
167
|
+
return on_accept()
|
|
168
|
+
return rc
|
|
169
|
+
|
|
170
|
+
def cancel_callback():
|
|
171
|
+
self._cleanup_tp(False)
|
|
172
|
+
|
|
173
|
+
return UxDialog(w, title = title, accept_callback = accept_callback, cancel_callback = cancel_callback, ok = ok, min_width = min_width)
|
|
174
|
+
|
|
175
|
+
def dialog(self, layout: ux.Layout = None, copy_entity: bool = True, title: str = '', save: bool = False, accept_hook: Callable[[RC],None] = None, min_width: int = 0) -> UxDialog:
|
|
176
|
+
if title is None:
|
|
177
|
+
title = ''
|
|
178
|
+
elif not title:
|
|
179
|
+
title = self.entity.__class__.__name__
|
|
180
|
+
|
|
181
|
+
ok = 'Save' if save else 'Ok'
|
|
182
|
+
|
|
183
|
+
def on_accept():
|
|
184
|
+
rc = self.entity.save() if save else RC_TRUE
|
|
185
|
+
if not rc:
|
|
186
|
+
self.warning(rc.error())
|
|
187
|
+
if accept_hook:
|
|
188
|
+
accept_hook(rc)
|
|
189
|
+
return rc
|
|
190
|
+
|
|
191
|
+
if copy_entity:
|
|
192
|
+
self.traitable_processor = INTERACTIVE()
|
|
193
|
+
|
|
194
|
+
return self._dialog(layout, title, ok, min_width, on_accept=on_accept)
|
|
195
|
+
|
|
196
|
+
def popup(self, layout: ux.Layout = None, copy_entity = True, title: str = '', save: bool = False, accept_hook: Callable[[RC],None] = None, min_width: int = 0) -> None:
|
|
197
|
+
self.dialog(layout = layout, copy_entity = copy_entity, title = title, save = save, accept_hook = accept_hook, min_width = min_width).exec()
|
|
198
|
+
|
|
199
|
+
def warning(self, msg: str, title: str = ''):
|
|
200
|
+
ux_warning(msg, parent = self.main_w, title = title, on_close=lambda ctx: None)
|
ui_10x/traitable_view.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
import inspect
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from core_10x.global_cache import cache
|
|
8
|
+
from core_10x.trait import T
|
|
9
|
+
from core_10x.traitable import Traitable
|
|
10
|
+
from core_10x.ui_hint import Ui, UiMod
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from core_10x.trait import Trait
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TraitableView:
|
|
17
|
+
"""
|
|
18
|
+
To create an instance of TraitableView you need to pass the following args:
|
|
19
|
+
1) entity_or_class: a subclass of Entity or an instance of it
|
|
20
|
+
2) _header_data: a dictionary with each item as follows:
|
|
21
|
+
either
|
|
22
|
+
<trait-name>: <label> (if label is empty, the trait's label will be used)
|
|
23
|
+
or
|
|
24
|
+
<group-label>: <header> (_header like dict)
|
|
25
|
+
3) named_traits_to_use: (kwargs) with each item as follows:
|
|
26
|
+
<trait-name>: Ui(...)
|
|
27
|
+
|
|
28
|
+
Such named_traits specify either traits to use (slice) or the ones to modify (modify)
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def _check(cls, hint_mod, trait_name: str) -> bool:
|
|
33
|
+
assert isinstance(hint_mod, UiMod), f'{trait_name} = {hint_mod} - UiMod is expected'
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def suitable_record(cls, trait: Trait = None, trait_dir: dict = None, trait_name: str = None, _skip_reserved: bool = True) -> Trait | None:
|
|
38
|
+
if not trait:
|
|
39
|
+
assert trait_dir and trait_name, 'trait is None, so both trait_dir and trait_name must be provided'
|
|
40
|
+
trait = trait_dir.get(trait_name)
|
|
41
|
+
if not trait:
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
flags = T.HIDDEN | T.RESERVED if _skip_reserved else T.HIDDEN
|
|
45
|
+
return None if trait.flags_on(flags) or trait.getter_params or trait.s_ui_hint.widget_type == Ui.WIDGET_TYPE.NONE else trait
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def slice(cls, traitable_class, _header_data: dict = None, **named_ui_hint_changes) -> TraitableView:
|
|
49
|
+
assert inspect.isclass(traitable_class) and issubclass(traitable_class, Traitable), f'{traitable_class} is not a Traitable'
|
|
50
|
+
|
|
51
|
+
trait_dir = traitable_class.s_dir
|
|
52
|
+
trait: Trait
|
|
53
|
+
ui_hints = {
|
|
54
|
+
name: hint_change.apply(trait.ui_hint) if cls._check(hint_change, trait.name) else None
|
|
55
|
+
for name, hint_change in named_ui_hint_changes.items()
|
|
56
|
+
if (trait := cls.suitable_record(trait_dir=trait_dir, trait_name=name))
|
|
57
|
+
}
|
|
58
|
+
return TraitableView(traitable_class, ui_hints, _header_data)
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def modify(
|
|
62
|
+
cls, traitable_class: type[Traitable], _header_data: dict = None, _skip_reserved: bool = True, **named_ui_hint_changes
|
|
63
|
+
) -> TraitableView:
|
|
64
|
+
assert inspect.isclass(traitable_class) and issubclass(traitable_class, Traitable), f'{traitable_class} is not a Traitable'
|
|
65
|
+
|
|
66
|
+
ui_hints = {
|
|
67
|
+
trait_name: trait.ui_hint
|
|
68
|
+
for trait_name, trait in traitable_class.s_dir.items()
|
|
69
|
+
if TraitableView.suitable_record(trait=trait, _skip_reserved=_skip_reserved)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
for name, hint_change in named_ui_hint_changes.items():
|
|
73
|
+
cls._check(hint_change, name)
|
|
74
|
+
hint = ui_hints.get(name)
|
|
75
|
+
if hint is not None:
|
|
76
|
+
ui_hints[name] = hint_change.apply(hint)
|
|
77
|
+
|
|
78
|
+
return TraitableView(traitable_class, ui_hints, _header_data)
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
@cache
|
|
82
|
+
def default(traitable_class: type[Traitable], read_only: bool = False) -> TraitableView:
|
|
83
|
+
return TraitableView.modify(traitable_class).make_read_only(read_only, clone=True)
|
|
84
|
+
|
|
85
|
+
def make_read_only(self, flag: bool, clone=False) -> TraitableView:
|
|
86
|
+
view = self if not clone else copy.deepcopy(self)
|
|
87
|
+
|
|
88
|
+
to_set = Ui.READ_ONLY if flag else 0x0
|
|
89
|
+
to_reset = 0x0 if flag else Ui.READ_ONLY
|
|
90
|
+
for ui_hint in view.ui_hints.values():
|
|
91
|
+
ui_hint.set_reset_flags(to_set, to_reset)
|
|
92
|
+
return view
|
|
93
|
+
|
|
94
|
+
def __init__(self, traitable_class, ui_hints: dict, header_data: dict):
|
|
95
|
+
self.cls = traitable_class
|
|
96
|
+
self.ui_hints = ui_hints
|
|
97
|
+
self.header = self.create_header(ui_hints, header_data)
|
|
98
|
+
|
|
99
|
+
def all_hints(self) -> dict:
|
|
100
|
+
return self.ui_hints
|
|
101
|
+
|
|
102
|
+
def ui_hint(self, trait_name: str) -> Ui:
|
|
103
|
+
return self.ui_hints[trait_name]
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def _process_tree(ui_hints: dict, tree: dict) -> dict:
|
|
107
|
+
header = {}
|
|
108
|
+
for subtree_name, subtree in tree.items():
|
|
109
|
+
if isinstance(subtree, str): # -- must be a trait name
|
|
110
|
+
label = subtree
|
|
111
|
+
hint = ui_hints.get(subtree_name)
|
|
112
|
+
if not hint: # -- ignore an unknown or unused trait
|
|
113
|
+
continue
|
|
114
|
+
if not label: # -- no label overwrite
|
|
115
|
+
label = hint.label
|
|
116
|
+
header[subtree_name] = label
|
|
117
|
+
|
|
118
|
+
elif isinstance(subtree, dict): # -- must be a real subtree
|
|
119
|
+
header[subtree_name] = TraitableView._process_tree(ui_hints, subtree)
|
|
120
|
+
|
|
121
|
+
else:
|
|
122
|
+
continue # -- ignore anything else
|
|
123
|
+
|
|
124
|
+
return header
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def create_header(ui_hints: dict, header_data: dict) -> dict:
|
|
128
|
+
if not header_data:
|
|
129
|
+
return {trait_name: ui.label for trait_name, ui in ui_hints.items()}
|
|
130
|
+
|
|
131
|
+
return TraitableView._process_tree(ui_hints, header_data)
|