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
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from core_10x.directory import Directory
|
|
2
|
+
from core_10x.environment_variables import EnvVars
|
|
3
|
+
from core_10x.exec_control import INTERACTIVE
|
|
4
|
+
from core_10x.py_class import PyClass
|
|
5
|
+
from core_10x.traitable import RC, RC_TRUE, RT, AnonymousTraitable, Traitable, XNone
|
|
6
|
+
|
|
7
|
+
from ui_10x.collection_editor import Collection, CollectionEditor
|
|
8
|
+
from ui_10x.traitable_editor import TraitableEditor
|
|
9
|
+
from ui_10x.traitable_view import Ui
|
|
10
|
+
from ui_10x.utils import UxDialog, ux
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CEApp(Traitable):
|
|
14
|
+
s_exclude_packages = ('manual_tests', 'unit_tests')
|
|
15
|
+
|
|
16
|
+
# fmt: off
|
|
17
|
+
main_store_uri: str = RT()
|
|
18
|
+
top_package: str = RT()
|
|
19
|
+
exclude_packages: list = RT(Ui(flags = Ui.HIDDEN))
|
|
20
|
+
current_class: type = RT(Ui.choice(stretch = 1))
|
|
21
|
+
|
|
22
|
+
my_editor: TraitableEditor = RT()
|
|
23
|
+
col_editor: CollectionEditor = RT()
|
|
24
|
+
main_w: ux.Splitter = RT()
|
|
25
|
+
# fmt: on
|
|
26
|
+
|
|
27
|
+
def main_store_uri_get(self) -> str:
|
|
28
|
+
return EnvVars.main_ts_store_uri
|
|
29
|
+
|
|
30
|
+
def main_store_uri_set(self, trait, value) -> RC:
|
|
31
|
+
self.raw_set_value(trait, value)
|
|
32
|
+
EnvVars.main_ts_store_uri = value
|
|
33
|
+
return RC_TRUE
|
|
34
|
+
|
|
35
|
+
def exclude_packages_get(self) -> list:
|
|
36
|
+
root_name = self.top_package
|
|
37
|
+
return [f'{root_name}.{name}' for name in self.s_exclude_packages]
|
|
38
|
+
|
|
39
|
+
def current_class_set(self, trait, value) -> RC:
|
|
40
|
+
self.raw_set_value(trait, value)
|
|
41
|
+
coll = Collection(cls=value)
|
|
42
|
+
ce = CollectionEditor(coll=coll)
|
|
43
|
+
self.col_editor = ce
|
|
44
|
+
w = ce.main_widget()
|
|
45
|
+
self.main_w.replace_widget(0, w)
|
|
46
|
+
|
|
47
|
+
return RC_TRUE
|
|
48
|
+
|
|
49
|
+
def current_class_choices(self, trait) -> Directory:
|
|
50
|
+
top_package = self.top_package
|
|
51
|
+
if not top_package:
|
|
52
|
+
return XNone
|
|
53
|
+
|
|
54
|
+
all_traitables = PyClass.all_classes(self.top_package, exclude_packages=self.exclude_packages, parent_classes=(Traitable,))
|
|
55
|
+
all_storables = tuple(cls for cls in all_traitables if cls.is_storable() and not issubclass(cls, AnonymousTraitable))
|
|
56
|
+
dir = Directory(name='Classes')
|
|
57
|
+
for cls in all_storables:
|
|
58
|
+
path = cls.__module__.split('.')
|
|
59
|
+
dir.insert(PyClass.name(cls), *path)
|
|
60
|
+
|
|
61
|
+
return dir
|
|
62
|
+
|
|
63
|
+
def my_editor_get(self) -> TraitableEditor:
|
|
64
|
+
return TraitableEditor(self, _confirm=True)
|
|
65
|
+
|
|
66
|
+
def main_widget(self) -> ux.Widget:
|
|
67
|
+
w = ux.Widget()
|
|
68
|
+
lay = ux.VBoxLayout()
|
|
69
|
+
w.set_layout(lay)
|
|
70
|
+
|
|
71
|
+
lay.add_layout(self.my_editor.row_layout())
|
|
72
|
+
lay.add_widget(ux.separator())
|
|
73
|
+
|
|
74
|
+
sp = ux.Splitter(ux.Horizontal)
|
|
75
|
+
lay.add_widget(sp)
|
|
76
|
+
|
|
77
|
+
sp.set_style_sheet('QSplitter::handle { background-color: darkgrey; }')
|
|
78
|
+
sp.set_handle_width(1)
|
|
79
|
+
|
|
80
|
+
sp.add_widget(ux.Widget())
|
|
81
|
+
sp.set_stretch_factor(0, 1)
|
|
82
|
+
sp.set_stretch_factor(1, 2) # -- TODO: set it for extra panes as needed
|
|
83
|
+
|
|
84
|
+
self.main_w = sp
|
|
85
|
+
|
|
86
|
+
return w
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if __name__ == '__main__':
|
|
90
|
+
from ui_10x.apps.collection_editor_app import CEApp
|
|
91
|
+
|
|
92
|
+
ux.init()
|
|
93
|
+
|
|
94
|
+
top_package = input('Top level package:')
|
|
95
|
+
ce_app = CEApp(top_package=top_package)
|
|
96
|
+
|
|
97
|
+
with INTERACTIVE():
|
|
98
|
+
w = ce_app.main_widget()
|
|
99
|
+
d = UxDialog(w)
|
|
100
|
+
d.exec()
|
ui_10x/choice.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
from core_10x.directory import Directory
|
|
2
|
+
from core_10x.named_constant import Enum
|
|
3
|
+
from core_10x.xnone import XNone
|
|
4
|
+
|
|
5
|
+
from ui_10x.utils import UxDialog, UxRadioBox, UxSearchableList, UxTreeViewer, ux
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Choice:
|
|
9
|
+
"""
|
|
10
|
+
choices - a dict of labels to tags values or Directory; if given, f_choices is ignored
|
|
11
|
+
f_choices - a fully bound function that must return choices (see above)
|
|
12
|
+
|
|
13
|
+
Q1: do we want to allow setting tags_selected with tags outside of all_choices? If so, what about updating all_choices
|
|
14
|
+
and potentially saving such Choice instance?
|
|
15
|
+
"""
|
|
16
|
+
def __init__(self, choices = XNone, f_choices = None, f_selection_callback = None, **kwargs):
|
|
17
|
+
self.choices: dict = XNone
|
|
18
|
+
self.inverted_choices: dict = XNone
|
|
19
|
+
self.f_choices = None
|
|
20
|
+
self.f_selection_cb = None
|
|
21
|
+
self.directory: Directory = None
|
|
22
|
+
self.kwargs = {}
|
|
23
|
+
|
|
24
|
+
self.values_selected = []
|
|
25
|
+
|
|
26
|
+
self.set(choices = choices, f_choices = f_choices, f_selection_callback = f_selection_callback, **kwargs)
|
|
27
|
+
|
|
28
|
+
def set(self, choices = XNone, f_choices = None, f_selection_callback = None, **kwargs):
|
|
29
|
+
assert choices or f_choices, 'Either choices or f_choices must be given'
|
|
30
|
+
|
|
31
|
+
self.f_selection_cb = f_selection_callback
|
|
32
|
+
|
|
33
|
+
if (choices):
|
|
34
|
+
self.f_choices = None
|
|
35
|
+
else:
|
|
36
|
+
self.f_choices = f_choices
|
|
37
|
+
choices = f_choices() #-- TODO: we may want to wrap it in try - except
|
|
38
|
+
|
|
39
|
+
self.kwargs.update(kwargs)
|
|
40
|
+
|
|
41
|
+
if isinstance(choices, Directory):
|
|
42
|
+
self.directory = choices
|
|
43
|
+
self.choices = choices.choices()
|
|
44
|
+
|
|
45
|
+
elif isinstance(choices, dict):
|
|
46
|
+
self.directory = None
|
|
47
|
+
self.choices = choices
|
|
48
|
+
|
|
49
|
+
else: #-- must be an iterable then!
|
|
50
|
+
self.directory = None
|
|
51
|
+
try:
|
|
52
|
+
self.choices = {hint: hint for hint in choices}
|
|
53
|
+
except Exception:
|
|
54
|
+
self.choices = {}
|
|
55
|
+
|
|
56
|
+
self.inverted_choices = {value: label for label, value in self.choices.items()}
|
|
57
|
+
|
|
58
|
+
def widget(self) -> ux.Widget:
|
|
59
|
+
if self.directory:
|
|
60
|
+
return UxTreeViewer(
|
|
61
|
+
self.directory,
|
|
62
|
+
select_hook = lambda dir: self.on_item_selected(dir, dir.value, False),
|
|
63
|
+
**self.kwargs
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return UxSearchableList(
|
|
67
|
+
choices = list(self.choices.keys()),
|
|
68
|
+
select_hook = lambda item_value: self.on_item_selected(None, item_value, True),
|
|
69
|
+
**self.kwargs
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def on_item_selected(self, dir: Directory, item, convert_choice: bool):
|
|
73
|
+
item_value = self.choices.get(item) if convert_choice else item
|
|
74
|
+
if item_value is not None:
|
|
75
|
+
self.on_value_selected(dir, item_value, convert_value = convert_choice)
|
|
76
|
+
if self.f_selection_cb:
|
|
77
|
+
self.f_selection_cb(item)
|
|
78
|
+
|
|
79
|
+
def on_value_selected(self, dir: Directory, item_value, convert_value: bool = False):
|
|
80
|
+
self.values_selected = [item_value]
|
|
81
|
+
|
|
82
|
+
def _on_selection_in_dialog(self, d: UxDialog, current_selection_cb, item_value):
|
|
83
|
+
if current_selection_cb:
|
|
84
|
+
current_selection_cb(item_value)
|
|
85
|
+
d.done(1)
|
|
86
|
+
self.f_selection_cb = current_selection_cb
|
|
87
|
+
|
|
88
|
+
def popup(self, parent: ux.Widget = None, _open = True) -> UxDialog:
|
|
89
|
+
_d = UxDialog(
|
|
90
|
+
self.widget(),
|
|
91
|
+
parent = parent,
|
|
92
|
+
title = 'Pick your choice',
|
|
93
|
+
ok = '', cancel = '',
|
|
94
|
+
#window_flags = Qt.FramelessWindowHint | Qt.Window | Qt.CustomizeWindowHint
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
current_selection_cb = self.f_selection_cb
|
|
98
|
+
self.f_selection_cb = lambda item_value: self._on_selection_in_dialog(_d, current_selection_cb, item_value)
|
|
99
|
+
|
|
100
|
+
if _open:
|
|
101
|
+
_d.show()
|
|
102
|
+
|
|
103
|
+
return _d
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class MultiChoice(Choice):
|
|
107
|
+
class SELECT_MODE(Enum):
|
|
108
|
+
ANY = ()
|
|
109
|
+
LEAF = ()
|
|
110
|
+
SUBDIR = ()
|
|
111
|
+
|
|
112
|
+
def __init__(self, *choices_selected, choices = XNone, f_choices = None, **kwargs):
|
|
113
|
+
super().__init__(choices = choices, f_choices = f_choices, **kwargs)
|
|
114
|
+
self._set_choices_selected(choices_selected)
|
|
115
|
+
|
|
116
|
+
def set(self, *choices_selected, choices = XNone, f_choices = None, **kwargs):
|
|
117
|
+
super().set(choices = choices, f_choices = f_choices, **kwargs)
|
|
118
|
+
self._set_choices_selected(choices_selected)
|
|
119
|
+
|
|
120
|
+
def _set_choices_selected(self, choices_selected: tuple):
|
|
121
|
+
all_choices = self.choices
|
|
122
|
+
self.values_selected.extend(tuple(value for choice in choices_selected if (value := all_choices.get(choice) )))
|
|
123
|
+
|
|
124
|
+
def select_mode(self):
|
|
125
|
+
return self.rb.choice() if self.rb else self.SELECT_MODE.ANY
|
|
126
|
+
|
|
127
|
+
def widget(self) -> ux.Widget:
|
|
128
|
+
self.sw = sw = super().widget()
|
|
129
|
+
if not sw:
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
w = ux.Splitter(ux.Horizontal)
|
|
133
|
+
|
|
134
|
+
if isinstance(sw, UxTreeViewer):
|
|
135
|
+
left = ux.Widget()
|
|
136
|
+
left_lay = ux.VBoxLayout()
|
|
137
|
+
left.set_layout(left_lay)
|
|
138
|
+
|
|
139
|
+
self.rb = rb = UxRadioBox(self.SELECT_MODE, horizontal = True, title = 'Selection Mode')
|
|
140
|
+
left_lay.add_widget(rb)
|
|
141
|
+
left_lay.add_widget(sw)
|
|
142
|
+
else:
|
|
143
|
+
left = sw
|
|
144
|
+
self.rb = None
|
|
145
|
+
|
|
146
|
+
w.add_widget(left)
|
|
147
|
+
|
|
148
|
+
self.selection_list = selection_list = ux.ListWidget()
|
|
149
|
+
labels = self.values_selected
|
|
150
|
+
if not self.directory:
|
|
151
|
+
map = self.inverted_choices
|
|
152
|
+
labels = [map.get(v) for v in labels if map.get(v)]
|
|
153
|
+
|
|
154
|
+
selection_list.add_items(labels)
|
|
155
|
+
selection_list.clicked_connect(self.on_selection_list_selection)
|
|
156
|
+
w.add_widget(selection_list)
|
|
157
|
+
|
|
158
|
+
return w
|
|
159
|
+
|
|
160
|
+
def on_selection_list_selection(self, item: ux.ListItem):
|
|
161
|
+
label = item.text()
|
|
162
|
+
if not self.directory:
|
|
163
|
+
value = self.choices.get(label)
|
|
164
|
+
else:
|
|
165
|
+
value = label
|
|
166
|
+
|
|
167
|
+
assert value, f"Value for '{label}' must exist"
|
|
168
|
+
|
|
169
|
+
self.values_selected.remove(value)
|
|
170
|
+
row = self.selection_list.row(item)
|
|
171
|
+
self.selection_list.take_item(row)
|
|
172
|
+
|
|
173
|
+
def on_value_selected(self, subdir: Directory, item_value, convert_value = False):
|
|
174
|
+
values_selected = self.values_selected
|
|
175
|
+
if item_value not in values_selected:
|
|
176
|
+
mode = self.select_mode()
|
|
177
|
+
handler = self.s_select_table.get(mode)
|
|
178
|
+
if handler(self, subdir, values_selected, item_value):
|
|
179
|
+
values_selected.append(item_value)
|
|
180
|
+
if convert_value:
|
|
181
|
+
item_value = self.inverted_choices.get( item_value )
|
|
182
|
+
self.selection_list.add_item(item_value)
|
|
183
|
+
|
|
184
|
+
def _select_leaf(self, subdir: Directory, values_selected: list, item_value) -> bool:
|
|
185
|
+
return subdir.is_leaf()
|
|
186
|
+
|
|
187
|
+
def _select_subdir(self, subdir: Directory, values_selected: list, item_value) -> bool:
|
|
188
|
+
dir = self.directory
|
|
189
|
+
assert dir, 'This is not a Directory'
|
|
190
|
+
|
|
191
|
+
n = len(values_selected)
|
|
192
|
+
for value in list(values_selected):
|
|
193
|
+
if dir.is_value_contained(item_value, value):
|
|
194
|
+
return False #-- it's a subdir of something already selected
|
|
195
|
+
|
|
196
|
+
if subdir.contains(value):
|
|
197
|
+
values_selected.remove(value)
|
|
198
|
+
|
|
199
|
+
if n > len(values_selected): #-- one or more subdirs contained by the subdir got removed
|
|
200
|
+
self.selection_list.clear()
|
|
201
|
+
self.selection_list.add_items(values_selected)
|
|
202
|
+
|
|
203
|
+
return True
|
|
204
|
+
|
|
205
|
+
# fmt: off
|
|
206
|
+
s_select_table = {
|
|
207
|
+
SELECT_MODE.ANY: lambda self, x,y,z: True,
|
|
208
|
+
SELECT_MODE.LEAF: _select_leaf,
|
|
209
|
+
SELECT_MODE.SUBDIR: _select_subdir
|
|
210
|
+
}
|
|
211
|
+
# fmt: on
|
|
212
|
+
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from core_10x.trait_filter import f
|
|
4
|
+
from core_10x.traitable import RC, RT, T, Traitable
|
|
5
|
+
from core_10x.traitable_id import ID
|
|
6
|
+
|
|
7
|
+
from ui_10x.entity_stocker import EntityStocker, StockerPlug
|
|
8
|
+
from ui_10x.traitable_editor import TraitableEditor
|
|
9
|
+
from ui_10x.utils import (
|
|
10
|
+
UxSearchableList,
|
|
11
|
+
ux,
|
|
12
|
+
ux_make_scrollable,
|
|
13
|
+
ux_push_button,
|
|
14
|
+
ux_warning,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Collection(Traitable):
|
|
19
|
+
cls: type[Traitable]
|
|
20
|
+
filter: f = RT(T.HIDDEN)
|
|
21
|
+
|
|
22
|
+
entity_ids: list[str]
|
|
23
|
+
|
|
24
|
+
def cls_set(self, t, cls) -> RC:
|
|
25
|
+
assert issubclass(cls, Traitable), f'{cls} is not a Traitable class'
|
|
26
|
+
return self.raw_set_value(t, cls)
|
|
27
|
+
|
|
28
|
+
def entity_ids_get(self) -> list[str]:
|
|
29
|
+
return [entity_id.value for entity_id in self.cls.load_ids()]
|
|
30
|
+
|
|
31
|
+
def refresh(self):
|
|
32
|
+
self.invalidate_value('entity_ids')
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class CollectionEditor(Traitable):
|
|
36
|
+
coll: Collection
|
|
37
|
+
current_class: Any
|
|
38
|
+
coll_title: str
|
|
39
|
+
num_panes: int = RT(1)
|
|
40
|
+
|
|
41
|
+
main_w: ux.Splitter
|
|
42
|
+
searchable_list: UxSearchableList
|
|
43
|
+
stocker: EntityStocker
|
|
44
|
+
|
|
45
|
+
current_editor: Any
|
|
46
|
+
current_entity: Traitable
|
|
47
|
+
|
|
48
|
+
def current_class_get(self):
|
|
49
|
+
return self.coll.cls
|
|
50
|
+
|
|
51
|
+
def stocker_get(self):
|
|
52
|
+
return EntityStocker(plug=StockerPlug(master=self))
|
|
53
|
+
|
|
54
|
+
def main_widget(self) -> ux.Widget:
|
|
55
|
+
sp = ux.Splitter(ux.Horizontal)
|
|
56
|
+
|
|
57
|
+
left_w = ux.Widget()
|
|
58
|
+
left_lay = ux.VBoxLayout()
|
|
59
|
+
left_w.set_layout(left_lay)
|
|
60
|
+
|
|
61
|
+
left_top_lay = ux.HBoxLayout()
|
|
62
|
+
line_w = ux.LineEdit()
|
|
63
|
+
line_w.set_minimum_width(100)
|
|
64
|
+
button_w = ux_push_button('New', callback=self.on_new_entity, style_icon='FileIcon')
|
|
65
|
+
left_top_lay.add_widget(line_w)
|
|
66
|
+
left_top_lay.add_widget(button_w)
|
|
67
|
+
|
|
68
|
+
left_lay.add_layout(left_top_lay)
|
|
69
|
+
|
|
70
|
+
# fmt: off
|
|
71
|
+
self.searchable_list = list_w = UxSearchableList(
|
|
72
|
+
text_widget = line_w,
|
|
73
|
+
title = f'Instances of {self.coll.cls.__name__}',
|
|
74
|
+
choices = self.coll.entity_ids,
|
|
75
|
+
select_hook = self.on_entity_id_selection,
|
|
76
|
+
sort = True,
|
|
77
|
+
)
|
|
78
|
+
# fmt: on
|
|
79
|
+
|
|
80
|
+
slw = ux_make_scrollable(list_w, h=ux.SCROLL.OFF)
|
|
81
|
+
|
|
82
|
+
left_lay.add_widget(slw)
|
|
83
|
+
|
|
84
|
+
sp.set_style_sheet('QSplitter::handle { background-color: darkgrey; }')
|
|
85
|
+
sp.set_handle_width(1)
|
|
86
|
+
sp.add_widget(left_w)
|
|
87
|
+
sp.add_widget(ux.Widget())
|
|
88
|
+
sp.set_stretch_factor(0, 1)
|
|
89
|
+
sp.set_stretch_factor(1, 2) # -- TODO: set it for extra panes as needed
|
|
90
|
+
|
|
91
|
+
self.main_w = sp
|
|
92
|
+
return sp
|
|
93
|
+
|
|
94
|
+
def set_pane(self, index: int, w: ux.Widget):
|
|
95
|
+
main_w = self.main_w
|
|
96
|
+
assert main_w, f'{self.__class__} - no widget has been created yet'
|
|
97
|
+
|
|
98
|
+
num_panes = self.num_panes
|
|
99
|
+
if num_panes and 0 <= index < num_panes:
|
|
100
|
+
if index < num_panes:
|
|
101
|
+
main_w.replace_widget(index + 1, w)
|
|
102
|
+
else:
|
|
103
|
+
main_w.add_widget(w)
|
|
104
|
+
self.num_panes = num_panes + 1
|
|
105
|
+
|
|
106
|
+
def on_entity_id_selection(self, id_value: str):
|
|
107
|
+
if self.num_panes:
|
|
108
|
+
obj: Traitable = self.coll.cls(_id=ID(id_value))
|
|
109
|
+
se = self.stocker
|
|
110
|
+
ed = se or TraitableEditor.editor(obj)
|
|
111
|
+
self.current_entity = obj
|
|
112
|
+
self.current_editor = ed
|
|
113
|
+
w = ed.main_widget()
|
|
114
|
+
self.set_pane(0, w)
|
|
115
|
+
|
|
116
|
+
def on_new_entity(self, flag):
|
|
117
|
+
cls = self.current_class
|
|
118
|
+
if cls:
|
|
119
|
+
new_entity = cls()
|
|
120
|
+
ed = TraitableEditor.editor(new_entity)
|
|
121
|
+
|
|
122
|
+
def accept_hook(rc: RC):
|
|
123
|
+
if not rc:
|
|
124
|
+
ux_warning(rc.error(), parent=self.main_w, on_close=lambda ctx: None)
|
|
125
|
+
# -- TODO: should we "merge" values from the existing instance?
|
|
126
|
+
|
|
127
|
+
else:
|
|
128
|
+
self.searchable_list.add_choice(new_entity.id().value)
|
|
129
|
+
|
|
130
|
+
ed.popup(copy_entity=False, title=f'New Entity of {cls.__name__}', save=True, accept_hook=accept_hook)
|
|
131
|
+
|
|
132
|
+
def on_deleted_entity(self, deleted_entity: Traitable):
|
|
133
|
+
self.searchable_list.remove_choice(deleted_entity.id().value)
|
|
134
|
+
if self.current_entity is deleted_entity:
|
|
135
|
+
self.set_pane(0, ux.Widget())
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from core_10x.concrete_traits import list_trait
|
|
6
|
+
from core_10x.trait import Ui
|
|
7
|
+
|
|
8
|
+
from ui_10x.choice import Choice
|
|
9
|
+
from ui_10x.trait_widget import TraitWidget
|
|
10
|
+
from ui_10x.utils import UxClipBoard, ux, ux_push_button
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from core_10x.trait import Trait
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class LineEditWidget(TraitWidget, ux.LineEdit, widget_type=Ui.WIDGET_TYPE.LINE):
|
|
17
|
+
def _create(self):
|
|
18
|
+
ux.LineEdit.__init__(self)
|
|
19
|
+
|
|
20
|
+
self.text_edited_connect(self.on_editing)
|
|
21
|
+
self.editing_finished_connect(self.on_editing_finished)
|
|
22
|
+
self.was_edited = False
|
|
23
|
+
|
|
24
|
+
def on_editing(self, text):
|
|
25
|
+
self.was_edited = True
|
|
26
|
+
|
|
27
|
+
def on_editing_finished(self):
|
|
28
|
+
if self.was_edited:
|
|
29
|
+
self.was_edited = False
|
|
30
|
+
str_value = self.text()
|
|
31
|
+
if not str_value: # -- user cleared the field, let's get the value back!
|
|
32
|
+
self.update_trait_value(invalidate=True)
|
|
33
|
+
else:
|
|
34
|
+
##value = self.trait.from_str(str_value)
|
|
35
|
+
self.update_trait_value(value=str_value, invalidate=False)
|
|
36
|
+
|
|
37
|
+
def mouse_press_event(self, event: ux.MouseEvent):
|
|
38
|
+
if event.is_right_button():
|
|
39
|
+
if not self.trait_editor.is_read_only():
|
|
40
|
+
cb = UxClipBoard()
|
|
41
|
+
cb.popup(self, self.entity, self.trait.name)
|
|
42
|
+
else:
|
|
43
|
+
ux.LineEdit.mouse_press_event(self, event)
|
|
44
|
+
|
|
45
|
+
def _set_read_only(self, flag):
|
|
46
|
+
self.set_read_only(flag)
|
|
47
|
+
|
|
48
|
+
def _value(self):
|
|
49
|
+
str_value = self.text()
|
|
50
|
+
return self.trait.from_str(str_value)
|
|
51
|
+
|
|
52
|
+
def _set_value(self, value):
|
|
53
|
+
str_value = self.trait.to_str(value)
|
|
54
|
+
self.set_text(str_value)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def create_text_widget(editor, trait: Trait):
|
|
58
|
+
if isinstance(trait, list_trait):
|
|
59
|
+
return TextEditForListWidget(editor, trait)
|
|
60
|
+
|
|
61
|
+
return TextEditWidget(editor, trait)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
TraitWidget.s_hinted_widgets[Ui.WIDGET_TYPE.TEXT] = create_text_widget
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class TextEditWidget(TraitWidget, ux.TextEdit):
|
|
68
|
+
def _create(self):
|
|
69
|
+
ux.TextEdit.__init__(self)
|
|
70
|
+
|
|
71
|
+
def focus_out_event(self, event):
|
|
72
|
+
value = self._value()
|
|
73
|
+
self.update_trait_value(value=value) if value else self.update_trait_value(invalidate=True)
|
|
74
|
+
|
|
75
|
+
def _set_read_only(self, flag):
|
|
76
|
+
self.set_read_only(flag)
|
|
77
|
+
|
|
78
|
+
def _value(self):
|
|
79
|
+
return self.to_plain_text()
|
|
80
|
+
|
|
81
|
+
def _set_value(self, value):
|
|
82
|
+
if not isinstance(value, str):
|
|
83
|
+
value = ''
|
|
84
|
+
self.set_plain_text(value)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class TextEditForListWidget(TextEditWidget):
|
|
88
|
+
def _value(self) -> list:
|
|
89
|
+
text = self.to_plain_text()
|
|
90
|
+
return text.split('\n')
|
|
91
|
+
|
|
92
|
+
def _set_value(self, value):
|
|
93
|
+
if value:
|
|
94
|
+
if not isinstance(value, list):
|
|
95
|
+
raise AssertionError('list is expected')
|
|
96
|
+
|
|
97
|
+
value = '\n'.join(value)
|
|
98
|
+
self.set_plain_text(value)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class PasswordWidget(LineEditWidget, widget_type=Ui.WIDGET_TYPE.PASSWORD):
|
|
102
|
+
def _create(self):
|
|
103
|
+
super()._create()
|
|
104
|
+
self.set_password_mode()
|
|
105
|
+
self.set_text('')
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class CheckBoxWidget(TraitWidget, ux.CheckBox, widget_type=Ui.WIDGET_TYPE.CHECK):
|
|
109
|
+
def _create(self):
|
|
110
|
+
ux.CheckBox.__init__(self)
|
|
111
|
+
if self.trait.ui_hint.param('right_label', False):
|
|
112
|
+
self.set_text(self.trait.ui_hint.label)
|
|
113
|
+
|
|
114
|
+
self.state_changed_connect(self.on_state_changed)
|
|
115
|
+
|
|
116
|
+
def on_state_changed(self, flag):
|
|
117
|
+
self.update_trait_value(value=bool(flag))
|
|
118
|
+
|
|
119
|
+
def _set_read_only(self, flag):
|
|
120
|
+
self.set_enabled(flag)
|
|
121
|
+
|
|
122
|
+
def _value(self):
|
|
123
|
+
return bool(self.is_checked())
|
|
124
|
+
|
|
125
|
+
def _set_value(self, value):
|
|
126
|
+
self.set_checked(bool(value))
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class ChoiceWidget(TraitWidget, ux.Widget, widget_type=Ui.WIDGET_TYPE.CHOICE):
|
|
130
|
+
def _create(self):
|
|
131
|
+
ux.Widget.__init__(self)
|
|
132
|
+
|
|
133
|
+
lay = ux.HBoxLayout()
|
|
134
|
+
lay.set_spacing(0)
|
|
135
|
+
lay.set_contents_margins(0, 0, 0, 0)
|
|
136
|
+
self.set_layout(lay)
|
|
137
|
+
|
|
138
|
+
# -- Line edit
|
|
139
|
+
self.line_edit = le = ux.LineEdit()
|
|
140
|
+
if self.trait_editor.is_read_only():
|
|
141
|
+
le.set_read_only(True)
|
|
142
|
+
|
|
143
|
+
le.editing_finished_connect(self.on_editing_finished)
|
|
144
|
+
lay.add_widget(le)
|
|
145
|
+
|
|
146
|
+
# -- Arrow down button
|
|
147
|
+
self.button = ux_push_button('', callback=self.show_popup, style_icon='ArrowDown')
|
|
148
|
+
lay.add_widget(self.button)
|
|
149
|
+
|
|
150
|
+
entity = self.trait_editor.entity
|
|
151
|
+
trait = self.trait
|
|
152
|
+
self.choice = Choice(f_choices=lambda: entity.get_choices(trait), f_selection_callback=lambda item: self.on_selection(item))
|
|
153
|
+
|
|
154
|
+
def _value(self):
|
|
155
|
+
selection = self.choice.values_selected
|
|
156
|
+
return selection[0] if selection else None
|
|
157
|
+
|
|
158
|
+
def _set_value(self, value):
|
|
159
|
+
if not self.choice.directory:
|
|
160
|
+
value = self.choice.inverted_choices.get(value)
|
|
161
|
+
|
|
162
|
+
if not value:
|
|
163
|
+
value = ''
|
|
164
|
+
self.line_edit.set_text(value)
|
|
165
|
+
|
|
166
|
+
def _set_read_only(self, flag):
|
|
167
|
+
self.line_edit.set_read_only(flag)
|
|
168
|
+
self.button.set_enabled(not flag)
|
|
169
|
+
|
|
170
|
+
# def on_current_index_changed(self, index):
|
|
171
|
+
# self.update_trait_value()
|
|
172
|
+
|
|
173
|
+
def show_popup(self):
|
|
174
|
+
self.popup = d = self.choice.popup(parent=self, _open=False)
|
|
175
|
+
w = self.width()
|
|
176
|
+
h = self.height()
|
|
177
|
+
xy = self.map_to_global(ux.Point(0, 0))
|
|
178
|
+
|
|
179
|
+
d.set_modal(True)
|
|
180
|
+
d.show()
|
|
181
|
+
d.set_geometry(xy.x(), xy.y(), max(w, d.width()), h)
|
|
182
|
+
|
|
183
|
+
def on_selection(self, item):
|
|
184
|
+
self.line_edit.set_text(item)
|
|
185
|
+
self.update_trait_value(value=self.choice.values_selected[0], invalidate=False)
|
|
186
|
+
|
|
187
|
+
def on_editing_finished(self):
|
|
188
|
+
str_value = self.line_edit.text()
|
|
189
|
+
if not str_value:
|
|
190
|
+
self.update_trait_value(invalidate=True)
|
|
191
|
+
else:
|
|
192
|
+
value = self.choice.choices.get(str_value, str_value)
|
|
193
|
+
self.update_trait_value(value=value, invalidate=False)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class PixmapLabelWidget(TraitWidget, ux.Label, widget_type=Ui.WIDGET_TYPE.PIXMAP):
|
|
197
|
+
def _create(self):
|
|
198
|
+
ux.Label.__init__(self)
|
|
199
|
+
# self.pixmap = UxPixmap()
|
|
200
|
+
|
|
201
|
+
...
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class PushButtonWidget(TraitWidget, ux.PushButton, widget_type=Ui.WIDGET_TYPE.PUSH):
|
|
205
|
+
def _create(self):
|
|
206
|
+
ux.PushButton.__init__(self)
|
|
207
|
+
self.set_text(self.trait.ui_hint.label)
|
|
208
|
+
|
|
209
|
+
...
|
|
210
|
+
|
|
211
|
+
def _set_read_only(self, flag):
|
|
212
|
+
pass
|
|
213
|
+
|
|
214
|
+
def _value(self):
|
|
215
|
+
pass
|
|
216
|
+
|
|
217
|
+
def _set_value(self, value):
|
|
218
|
+
if value is None:
|
|
219
|
+
value = True
|
|
220
|
+
self.set_enabled(value)
|