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.
Files changed (214) hide show
  1. core_10x/__init__.py +42 -0
  2. core_10x/backbone/__init__.py +0 -0
  3. core_10x/backbone/backbone_store.py +59 -0
  4. core_10x/backbone/backbone_traitable.py +30 -0
  5. core_10x/backbone/backbone_user.py +66 -0
  6. core_10x/backbone/bound_data_domain.py +49 -0
  7. core_10x/backbone/namespace.py +101 -0
  8. core_10x/backbone/vault.py +38 -0
  9. core_10x/code_samples/__init__.py +0 -0
  10. core_10x/code_samples/_package_manifest.py +3 -0
  11. core_10x/code_samples/directories.py +181 -0
  12. core_10x/code_samples/person.py +76 -0
  13. core_10x/concrete_traits.py +356 -0
  14. core_10x/conftest.py +12 -0
  15. core_10x/curve.py +321 -0
  16. core_10x/data_domain.py +48 -0
  17. core_10x/data_domain_binder.py +45 -0
  18. core_10x/directory.py +250 -0
  19. core_10x/entity.py +8 -0
  20. core_10x/entity_filter.py +5 -0
  21. core_10x/environment_variables.py +147 -0
  22. core_10x/exec_control.py +84 -0
  23. core_10x/experimental/__init__.py +0 -0
  24. core_10x/experimental/data_protocol_ex.py +34 -0
  25. core_10x/global_cache.py +121 -0
  26. core_10x/manual_tests/__init__.py +0 -0
  27. core_10x/manual_tests/calendar_test.py +35 -0
  28. core_10x/manual_tests/ctor_update_bug.py +58 -0
  29. core_10x/manual_tests/debug_graph_on.py +17 -0
  30. core_10x/manual_tests/debug_graphoff_inside_graph_on.py +28 -0
  31. core_10x/manual_tests/enum_bits_test.py +17 -0
  32. core_10x/manual_tests/env_vars_trivial_test.py +12 -0
  33. core_10x/manual_tests/existing_traitable.py +33 -0
  34. core_10x/manual_tests/k10x_test1.py +13 -0
  35. core_10x/manual_tests/named_constant_test.py +121 -0
  36. core_10x/manual_tests/nucleus_trivial_test.py +42 -0
  37. core_10x/manual_tests/polars_test.py +14 -0
  38. core_10x/manual_tests/py_class_test.py +4 -0
  39. core_10x/manual_tests/rc_test.py +42 -0
  40. core_10x/manual_tests/rdate_test.py +12 -0
  41. core_10x/manual_tests/reference_serialization_bug.py +19 -0
  42. core_10x/manual_tests/resource_trivial_test.py +10 -0
  43. core_10x/manual_tests/store_uri_test.py +6 -0
  44. core_10x/manual_tests/trait_definition_test.py +19 -0
  45. core_10x/manual_tests/trait_filter_test.py +15 -0
  46. core_10x/manual_tests/trait_flag_modification_test.py +42 -0
  47. core_10x/manual_tests/trait_modification_bug.py +26 -0
  48. core_10x/manual_tests/traitable_as_of_test.py +82 -0
  49. core_10x/manual_tests/traitable_heir_test.py +39 -0
  50. core_10x/manual_tests/traitable_history_test.py +41 -0
  51. core_10x/manual_tests/traitable_serialization_test.py +54 -0
  52. core_10x/manual_tests/traitable_trivial_test.py +71 -0
  53. core_10x/manual_tests/trivial_graph_test.py +16 -0
  54. core_10x/manual_tests/ts_class_association_test.py +64 -0
  55. core_10x/manual_tests/ts_trivial_test.py +35 -0
  56. core_10x/named_constant.py +425 -0
  57. core_10x/nucleus.py +81 -0
  58. core_10x/package_manifest.py +85 -0
  59. core_10x/package_refactoring.py +153 -0
  60. core_10x/py_class.py +431 -0
  61. core_10x/rc.py +155 -0
  62. core_10x/rdate.py +339 -0
  63. core_10x/resource.py +189 -0
  64. core_10x/roman_number.py +67 -0
  65. core_10x/testlib/__init__.py +0 -0
  66. core_10x/testlib/test_store.py +240 -0
  67. core_10x/testlib/traitable_history_tests.py +787 -0
  68. core_10x/testlib/ts_tests.py +280 -0
  69. core_10x/trait.py +377 -0
  70. core_10x/trait_definition.py +176 -0
  71. core_10x/trait_filter.py +205 -0
  72. core_10x/trait_method_error.py +36 -0
  73. core_10x/traitable.py +1082 -0
  74. core_10x/traitable_cli.py +153 -0
  75. core_10x/traitable_heir.py +33 -0
  76. core_10x/traitable_id.py +31 -0
  77. core_10x/ts_store.py +172 -0
  78. core_10x/ts_store_type.py +26 -0
  79. core_10x/ts_union.py +147 -0
  80. core_10x/ui_hint.py +153 -0
  81. core_10x/unit_tests/test_concrete_traits.py +156 -0
  82. core_10x/unit_tests/test_converters.py +51 -0
  83. core_10x/unit_tests/test_curve.py +157 -0
  84. core_10x/unit_tests/test_directory.py +54 -0
  85. core_10x/unit_tests/test_documentation.py +172 -0
  86. core_10x/unit_tests/test_environment_variables.py +15 -0
  87. core_10x/unit_tests/test_filters.py +239 -0
  88. core_10x/unit_tests/test_graph.py +348 -0
  89. core_10x/unit_tests/test_named_constant.py +98 -0
  90. core_10x/unit_tests/test_rc.py +11 -0
  91. core_10x/unit_tests/test_rdate.py +484 -0
  92. core_10x/unit_tests/test_trait_method_error.py +80 -0
  93. core_10x/unit_tests/test_trait_modification.py +19 -0
  94. core_10x/unit_tests/test_traitable.py +959 -0
  95. core_10x/unit_tests/test_traitable_history.py +1 -0
  96. core_10x/unit_tests/test_ts_store.py +1 -0
  97. core_10x/unit_tests/test_ts_union.py +369 -0
  98. core_10x/unit_tests/test_ui_nodes.py +81 -0
  99. core_10x/unit_tests/test_xxcalendar.py +471 -0
  100. core_10x/vault/__init__.py +0 -0
  101. core_10x/vault/sec_keys.py +133 -0
  102. core_10x/vault/security_keys_old.py +168 -0
  103. core_10x/vault/vault.py +56 -0
  104. core_10x/vault/vault_traitable.py +56 -0
  105. core_10x/vault/vault_user.py +70 -0
  106. core_10x/xdate_time.py +136 -0
  107. core_10x/xnone.py +71 -0
  108. core_10x/xxcalendar.py +228 -0
  109. infra_10x/__init__.py +0 -0
  110. infra_10x/manual_tests/__init__.py +0 -0
  111. infra_10x/manual_tests/test_misc.py +16 -0
  112. infra_10x/manual_tests/test_prepare_filter_and_pipeline.py +25 -0
  113. infra_10x/mongodb_admin.py +111 -0
  114. infra_10x/mongodb_store.py +346 -0
  115. infra_10x/mongodb_utils.py +129 -0
  116. infra_10x/unit_tests/conftest.py +13 -0
  117. infra_10x/unit_tests/test_mongo_db.py +36 -0
  118. infra_10x/unit_tests/test_mongo_history.py +1 -0
  119. py10x_universe-0.1.3.dist-info/METADATA +406 -0
  120. py10x_universe-0.1.3.dist-info/RECORD +214 -0
  121. py10x_universe-0.1.3.dist-info/WHEEL +4 -0
  122. py10x_universe-0.1.3.dist-info/licenses/LICENSE +21 -0
  123. ui_10x/__init__.py +0 -0
  124. ui_10x/apps/__init__.py +0 -0
  125. ui_10x/apps/collection_editor_app.py +100 -0
  126. ui_10x/choice.py +212 -0
  127. ui_10x/collection_editor.py +135 -0
  128. ui_10x/concrete_trait_widgets.py +220 -0
  129. ui_10x/conftest.py +8 -0
  130. ui_10x/entity_stocker.py +173 -0
  131. ui_10x/examples/__init__.py +0 -0
  132. ui_10x/examples/_guess_word_data.py +14076 -0
  133. ui_10x/examples/collection_editor.py +17 -0
  134. ui_10x/examples/date_selector.py +14 -0
  135. ui_10x/examples/entity_stocker.py +18 -0
  136. ui_10x/examples/guess_word.py +392 -0
  137. ui_10x/examples/message_box.py +20 -0
  138. ui_10x/examples/multi_choice.py +17 -0
  139. ui_10x/examples/py_data_browser.py +66 -0
  140. ui_10x/examples/radiobox.py +29 -0
  141. ui_10x/examples/single_choice.py +31 -0
  142. ui_10x/examples/style_sheet.py +47 -0
  143. ui_10x/examples/trivial_entity_editor.py +18 -0
  144. ui_10x/platform.py +20 -0
  145. ui_10x/platform_interface.py +517 -0
  146. ui_10x/py_data_browser.py +249 -0
  147. ui_10x/qt6/__init__.py +0 -0
  148. ui_10x/qt6/conftest.py +8 -0
  149. ui_10x/qt6/manual_tests/__init__.py +0 -0
  150. ui_10x/qt6/manual_tests/basic_test.py +35 -0
  151. ui_10x/qt6/platform_implementation.py +275 -0
  152. ui_10x/qt6/utils.py +665 -0
  153. ui_10x/rio/__init__.py +0 -0
  154. ui_10x/rio/apps/examples/examples/__init__.py +22 -0
  155. ui_10x/rio/apps/examples/examples/components/__init__.py +3 -0
  156. ui_10x/rio/apps/examples/examples/components/collection_editor.py +15 -0
  157. ui_10x/rio/apps/examples/examples/pages/collection_editor.py +21 -0
  158. ui_10x/rio/apps/examples/examples/pages/login_page.py +88 -0
  159. ui_10x/rio/apps/examples/examples/pages/style_sheet.py +21 -0
  160. ui_10x/rio/apps/examples/rio.toml +14 -0
  161. ui_10x/rio/component_builder.py +497 -0
  162. ui_10x/rio/components/__init__.py +9 -0
  163. ui_10x/rio/components/group_box.py +31 -0
  164. ui_10x/rio/components/labeled_checkbox.py +18 -0
  165. ui_10x/rio/components/line_edit.py +37 -0
  166. ui_10x/rio/components/radio_button.py +32 -0
  167. ui_10x/rio/components/separator.py +24 -0
  168. ui_10x/rio/components/splitter.py +121 -0
  169. ui_10x/rio/components/tree_view.py +75 -0
  170. ui_10x/rio/conftest.py +35 -0
  171. ui_10x/rio/internals/__init__.py +0 -0
  172. ui_10x/rio/internals/app.py +192 -0
  173. ui_10x/rio/manual_tests/__init__.py +0 -0
  174. ui_10x/rio/manual_tests/basic_test.py +24 -0
  175. ui_10x/rio/manual_tests/splitter.py +27 -0
  176. ui_10x/rio/platform_implementation.py +91 -0
  177. ui_10x/rio/style_sheet.py +53 -0
  178. ui_10x/rio/unit_tests/test_collection_editor.py +68 -0
  179. ui_10x/rio/unit_tests/test_internals.py +630 -0
  180. ui_10x/rio/unit_tests/test_style_sheet.py +37 -0
  181. ui_10x/rio/widgets/__init__.py +46 -0
  182. ui_10x/rio/widgets/application.py +109 -0
  183. ui_10x/rio/widgets/button.py +48 -0
  184. ui_10x/rio/widgets/button_group.py +60 -0
  185. ui_10x/rio/widgets/calendar.py +23 -0
  186. ui_10x/rio/widgets/checkbox.py +24 -0
  187. ui_10x/rio/widgets/dialog.py +137 -0
  188. ui_10x/rio/widgets/group_box.py +27 -0
  189. ui_10x/rio/widgets/layout.py +34 -0
  190. ui_10x/rio/widgets/line_edit.py +37 -0
  191. ui_10x/rio/widgets/list.py +105 -0
  192. ui_10x/rio/widgets/message_box.py +70 -0
  193. ui_10x/rio/widgets/scroll_area.py +31 -0
  194. ui_10x/rio/widgets/spacer.py +6 -0
  195. ui_10x/rio/widgets/splitter.py +45 -0
  196. ui_10x/rio/widgets/text_edit.py +28 -0
  197. ui_10x/rio/widgets/tree.py +89 -0
  198. ui_10x/rio/widgets/unit_tests/test_button.py +101 -0
  199. ui_10x/rio/widgets/unit_tests/test_button_group.py +33 -0
  200. ui_10x/rio/widgets/unit_tests/test_calendar.py +114 -0
  201. ui_10x/rio/widgets/unit_tests/test_checkbox.py +109 -0
  202. ui_10x/rio/widgets/unit_tests/test_group_box.py +158 -0
  203. ui_10x/rio/widgets/unit_tests/test_label.py +43 -0
  204. ui_10x/rio/widgets/unit_tests/test_line_edit.py +140 -0
  205. ui_10x/rio/widgets/unit_tests/test_list.py +146 -0
  206. ui_10x/table_header_view.py +305 -0
  207. ui_10x/table_view.py +174 -0
  208. ui_10x/trait_editor.py +189 -0
  209. ui_10x/trait_widget.py +131 -0
  210. ui_10x/traitable_editor.py +200 -0
  211. ui_10x/traitable_view.py +131 -0
  212. ui_10x/unit_tests/conftest.py +8 -0
  213. ui_10x/unit_tests/test_platform.py +9 -0
  214. 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)
ui_10x/conftest.py ADDED
@@ -0,0 +1,8 @@
1
+ import pytest
2
+
3
+
4
+ @pytest.fixture(autouse=True)
5
+ def setup_ui_platform(monkeypatch):
6
+ monkeypatch.setenv('UI_PLATFORM', 'Unknown')
7
+ yield
8
+ monkeypatch.undo()