From 2c072e2252b5b52fb33fcd4dadb43a3706dd0b8e Mon Sep 17 00:00:00 2001 From: foefl Date: Mon, 11 May 2026 16:28:20 +0200 Subject: [PATCH] prepare recursive UI build --- pdm.lock | 120 ++++----- prototypes/t_qt_2.py | 570 ++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 2 +- 3 files changed, 620 insertions(+), 72 deletions(-) diff --git a/pdm.lock b/pdm.lock index 385bed5..5626db6 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "dev", "lint", "nb", "tests"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:eb4aaeb1bc7efec0c69be5e4cde53b2d92fdacfcc170db0204ef694828f9e99d" +content_hash = "sha256:09d959f153789f965fe2d45e1eb65842eb8d0b446b67cdec76891c6048192b0c" [[metadata.targets]] requires_python = ">=3.11,<3.14" @@ -2684,24 +2684,24 @@ files = [ [[package]] name = "pydantic" -version = "2.13.3" +version = "2.13.4" requires_python = ">=3.9" summary = "Data validation using Python type hints" groups = ["default", "dev"] dependencies = [ "annotated-types>=0.6.0", - "pydantic-core==2.46.3", + "pydantic-core==2.46.4", "typing-extensions>=4.14.1", "typing-inspection>=0.4.2", ] files = [ - {file = "pydantic-2.13.3-py3-none-any.whl", hash = "sha256:6db14ac8dfc9a1e57f87ea2c0de670c251240f43cb0c30a5130e9720dc612927"}, - {file = "pydantic-2.13.3.tar.gz", hash = "sha256:af09e9d1d09f4e7fe37145c1f577e1d61ceb9a41924bf0094a36506285d0a84d"}, + {file = "pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba"}, + {file = "pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6"}, ] [[package]] name = "pydantic-core" -version = "2.46.3" +version = "2.46.4" requires_python = ">=3.9" summary = "Core functionality for Pydantic validation and serialization" groups = ["default", "dev"] @@ -2709,60 +2709,60 @@ dependencies = [ "typing-extensions>=4.14.1", ] files = [ - {file = "pydantic_core-2.46.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ab124d49d0459b2373ecf54118a45c28a1e6d4192a533fbc915e70f556feb8e5"}, - {file = "pydantic_core-2.46.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cca67d52a5c7a16aed2b3999e719c4bcf644074eac304a5d3d62dd70ae7d4b2c"}, - {file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c024e08c0ba23e6fd68c771a521e9d6a792f2ebb0fa734296b36394dc30390e"}, - {file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6645ce7eec4928e29a1e3b3d5c946621d105d3e79f0c9cddf07c2a9770949287"}, - {file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a712c7118e6c5ea96562f7b488435172abb94a3c53c22c9efc1412264a45cbbe"}, - {file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69a868ef3ff206343579021c40faf3b1edc64b1cc508ff243a28b0a514ccb050"}, - {file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc7e8c32db809aa0f6ea1d6869ebc8518a65d5150fdfad8bcae6a49ae32a22e2"}, - {file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:3481bd1341dc85779ee506bc8e1196a277ace359d89d28588a9468c3ecbe63fa"}, - {file = "pydantic_core-2.46.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8690eba565c6d68ffd3a8655525cbdd5246510b44a637ee2c6c03a7ebfe64d3c"}, - {file = "pydantic_core-2.46.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4de88889d7e88d50d40ee5b39d5dac0bcaef9ba91f7e536ac064e6b2834ecccf"}, - {file = "pydantic_core-2.46.3-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:e480080975c1ef7f780b8f99ed72337e7cc5efea2e518a20a692e8e7b278eb8b"}, - {file = "pydantic_core-2.46.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:de3a5c376f8cd94da9a1b8fd3dd1c16c7a7b216ed31dc8ce9fd7a22bf13b836e"}, - {file = "pydantic_core-2.46.3-cp311-cp311-win32.whl", hash = "sha256:fc331a5314ffddd5385b9ee9d0d2fee0b13c27e0e02dad71b1ae5d6561f51eeb"}, - {file = "pydantic_core-2.46.3-cp311-cp311-win_amd64.whl", hash = "sha256:b5b9c6cf08a8a5e502698f5e153056d12c34b8fb30317e0c5fd06f45162a6346"}, - {file = "pydantic_core-2.46.3-cp311-cp311-win_arm64.whl", hash = "sha256:5dfd51cf457482f04ec49491811a2b8fd5b843b64b11eecd2d7a1ee596ea78a6"}, - {file = "pydantic_core-2.46.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b11b59b3eee90a80a36701ddb4576d9ae31f93f05cb9e277ceaa09e6bf074a67"}, - {file = "pydantic_core-2.46.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af8653713055ea18a3abc1537fe2ebc42f5b0bbb768d1eb79fd74eb47c0ac089"}, - {file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75a519dab6d63c514f3a81053e5266c549679e4aa88f6ec57f2b7b854aceb1b0"}, - {file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6cd87cb1575b1ad05ba98894c5b5c96411ef678fa2f6ed2576607095b8d9789"}, - {file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f80a55484b8d843c8ada81ebf70a682f3f00a3d40e378c06cf17ecb44d280d7d"}, - {file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3861f1731b90c50a3266316b9044f5c9b405eecb8e299b0a7120596334e4fe9c"}, - {file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb528e295ed31570ac3dcc9bfdd6e0150bc11ce6168ac87a8082055cf1a67395"}, - {file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:367508faa4973b992b271ba1494acaab36eb7e8739d1e47be5035fb1ea225396"}, - {file = "pydantic_core-2.46.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ad3c826fe523e4becf4fe39baa44286cff85ef137c729a2c5e269afbfd0905d"}, - {file = "pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ec638c5d194ef8af27db69f16c954a09797c0dc25015ad6123eb2c73a4d271ca"}, - {file = "pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:28ed528c45446062ee66edb1d33df5d88828ae167de76e773a3c7f64bd14e976"}, - {file = "pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aed19d0c783886d5bd86d80ae5030006b45e28464218747dcf83dabfdd092c7b"}, - {file = "pydantic_core-2.46.3-cp312-cp312-win32.whl", hash = "sha256:06d5d8820cbbdb4147578c1fe7ffcd5b83f34508cb9f9ab76e807be7db6ff0a4"}, - {file = "pydantic_core-2.46.3-cp312-cp312-win_amd64.whl", hash = "sha256:c3212fda0ee959c1dd04c60b601ec31097aaa893573a3a1abd0a47bcac2968c1"}, - {file = "pydantic_core-2.46.3-cp312-cp312-win_arm64.whl", hash = "sha256:f1f8338dd7a7f31761f1f1a3c47503a9a3b34eea3c8b01fa6ee96408affb5e72"}, - {file = "pydantic_core-2.46.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:12bc98de041458b80c86c56b24df1d23832f3e166cbaff011f25d187f5c62c37"}, - {file = "pydantic_core-2.46.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:85348b8f89d2c3508b65b16c3c33a4da22b8215138d8b996912bb1532868885f"}, - {file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1105677a6df914b1fb71a81b96c8cce7726857e1717d86001f29be06a25ee6f8"}, - {file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87082cd65669a33adeba5470769e9704c7cf026cc30afb9cc77fd865578ebaad"}, - {file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e5f66e12c4f5212d08522963380eaaeac5ebd795826cfd19b2dfb0c7a52b9c"}, - {file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6cdf19bf84128d5e7c37e8a73a0c5c10d51103a650ac585d42dd6ae233f2b7f"}, - {file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031bb17f4885a43773c8c763089499f242aee2ea85cf17154168775dccdecf35"}, - {file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:bcf2a8b2982a6673693eae7348ef3d8cf3979c1d63b54fca7c397a635cc68687"}, - {file = "pydantic_core-2.46.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28e8cf2f52d72ced402a137145923a762cbb5081e48b34312f7a0c8f55928ec3"}, - {file = "pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:17eaface65d9fc5abb940003020309c1bf7a211f5f608d7870297c367e6f9022"}, - {file = "pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:93fd339f23408a07e98950a89644f92c54d8729719a40b30c0a30bb9ebc55d23"}, - {file = "pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:23cbdb3aaa74dfe0837975dbf69b469753bbde8eacace524519ffdb6b6e89eb7"}, - {file = "pydantic_core-2.46.3-cp313-cp313-win32.whl", hash = "sha256:610eda2e3838f401105e6326ca304f5da1e15393ae25dacae5c5c63f2c275b13"}, - {file = "pydantic_core-2.46.3-cp313-cp313-win_amd64.whl", hash = "sha256:68cc7866ed863db34351294187f9b729964c371ba33e31c26f478471c52e1ed0"}, - {file = "pydantic_core-2.46.3-cp313-cp313-win_arm64.whl", hash = "sha256:f64b5537ac62b231572879cd08ec05600308636a5d63bcbdb15063a466977bec"}, - {file = "pydantic_core-2.46.3-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:13afdd885f3d71280cf286b13b310ee0f7ccfefd1dbbb661514a474b726e2f25"}, - {file = "pydantic_core-2.46.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f91c0aff3e3ee0928edd1232c57f643a7a003e6edf1860bc3afcdc749cb513f3"}, - {file = "pydantic_core-2.46.3-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6529d1d128321a58d30afcc97b49e98836542f68dd41b33c2e972bb9e5290536"}, - {file = "pydantic_core-2.46.3-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:975c267cff4f7e7272eacbe50f6cc03ca9a3da4c4fbd66fffd89c94c1e311aa1"}, - {file = "pydantic_core-2.46.3-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2b8e4f2bbdf71415c544b4b1138b8060db7b6611bc927e8064c769f64bed651c"}, - {file = "pydantic_core-2.46.3-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e61ea8e9fff9606d09178f577ff8ccdd7206ff73d6552bcec18e1033c4254b85"}, - {file = "pydantic_core-2.46.3-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b504bda01bafc69b6d3c7a0c7f039dcf60f47fab70e06fe23f57b5c75bdc82b8"}, - {file = "pydantic_core-2.46.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b00b76f7142fc60c762ce579bd29c8fa44aaa56592dd3c54fab3928d0d4ca6ff"}, - {file = "pydantic_core-2.46.3.tar.gz", hash = "sha256:41c178f65b8c29807239d47e6050262eb6bf84eb695e41101e62e38df4a5bc2c"}, + {file = "pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594"}, + {file = "pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3"}, + {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848"}, + {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3"}, + {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109"}, + {file = "pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda"}, + {file = "pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33"}, + {file = "pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d"}, + {file = "pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2"}, + {file = "pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b"}, + {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458"}, + {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b"}, + {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c"}, + {file = "pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894"}, + {file = "pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89"}, + {file = "pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a"}, + {file = "pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008"}, + {file = "pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e"}, + {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd"}, + {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be"}, + {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d"}, + {file = "pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb"}, + {file = "pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292"}, + {file = "pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983"}, + {file = "pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1"}, ] [[package]] diff --git a/prototypes/t_qt_2.py b/prototypes/t_qt_2.py index a070c0a..c9d9840 100644 --- a/prototypes/t_qt_2.py +++ b/prototypes/t_qt_2.py @@ -428,10 +428,16 @@ class FormField: raise ValueError("Invalid field definition: Dropdown requires options") +@dc.dataclass(slots=True) +class FormFieldDynList: + label: str + fields: Sequence[FormField] + + @dc.dataclass(slots=True) class FormFieldGroup: label: str | None - fields: Sequence[FormField] + fields: Sequence[FormField] | FormFieldDynList FORM_FIELD_DEF = [ @@ -594,6 +600,400 @@ FORM_FIELDS_CONTACT_PERSON = [ ), ] +FORM_FIELDS_MASTER_DATA = [ + FormField( + "Titel", + FormFieldType.TEXT, + required=False, + tooltip=( + "* nur wenn anrufende Person oder kontaktaufnehmende Person " + "nicht die zu beratende Person ist" + ), + ), + FormField( + "Anrede", + FormFieldType.TEXT, + required=True, + ), + FormField( + "Name", + FormFieldType.TEXT, + required=True, + ), + FormField( + "Vorname", + FormFieldType.TEXT, + required=False, + ), + FormField( + "Geburtsdatum", + FormFieldType.DATE, + required=False, + tooltip=( + "* Wichtig zu erfragen, da u.a. Mindestgehaltsschwelle davon abhängt " + "(Regelung bei Ü45 Jahre)" + ), + ), + FormField( + "Herkunftsland", + FormFieldType.DROPDOWN, + required=True, + options=["LÄNDERLISTE NOCH ZU ERGÄNZEN"], + tooltip=("* Wichtig zu erfragen aufgrund eventueller EU-Freizügigkeitsregelung"), + ), + FormField( + "Staatsangehörigkeit", + FormFieldType.DROPDOWN, + required=False, + options=["LÄNDERLISTE NOCH ZU ERGÄNZEN"], + tooltip=("* Wichtig zu erfragen aufgrund eventueller EU-Freizügigkeitsregelung"), + ), + FormField( + "Rückkehrer", + FormFieldType.DROPDOWN, + required=False, + options=["ja", "nein"], + tooltip=("* Wichtig zu erfragen aufgrund eventueller EU-Freizügigkeitsregelung"), + ), + FormField( + "Wo befindet sich die Person?", + FormFieldType.DROPDOWN, + required=True, + options=["Inland", "Ausland EU/EWR", "Ausland Drittstaat"], + ), + FormField( + "Straße", + FormFieldType.TEXT, + required=False, + ), + FormField( + "Hausnummer", + FormFieldType.TEXT, + required=False, + ), + FormField( + "PLZ", + FormFieldType.TEXT, + required=False, + ), + FormField( + "Ort", + FormFieldType.TEXT, + required=False, + ), + FormField( + "Bundesland", + FormFieldType.DROPDOWN, + required=False, + options=["BUNDESLÄNDER NOCH ZU ERGÄNZEN"], + tooltip=( + "nur wenn Inland angegeben und die Angabe zieht es in keine Dokumente " + "rüber! Liste Bundesländer verwenden" + ), + ), + FormField( + "Land", + FormFieldType.TEXT, + required=False, + ), + FormField( + "Festnetznummer", + FormFieldType.TEXT, + required=False, + ), + FormField( + "Mobilfunknummer", + FormFieldType.TEXT, + required=False, + ), + FormField( + "E-Mail", + FormFieldType.TEXT, + required=False, + ), + FormField( + "Familienstand", + FormFieldType.TEXT, + required=False, + tooltip="* Wichtig zu erfragen aufgrund Lebensunterhaltssicherung", + ), + FormField( + "Anzahl Kinder", + FormFieldType.DROPDOWN, + required=False, + options=[str(x) for x in range(11)], + tooltip="* Wichtig zu erfragen aufgrund Lebensunterhaltssicherung", + ), +] + +FORM_FIELDS_ADDITIONAL_DATA = [ + FormField( + "Deutsch als Kommunikationssprache", + FormFieldType.DROPDOWN, + required=False, + options=["nein", "ja, als Muttersprache", "ja, als Fremdsprache"], + ), + FormField( + "Aufenthaltstitel", + FormFieldType.DROPDOWN, + required=False, + options=[ + "anerkannter Flüchtling §§ 22 - 26 AufenthG", + "Aufenthaltsgestattung §55 AufenthG", + "Blaue Karte EU § 18g AufenthG", + "BüMA (Bescheinigung über Meldung als Asylsuchender)", + "Duldung § 60 AufenthG", + "bisher kein Aufenthaltstitel", + "Deutscher", + "familiäre Gründe §§ 27 - 36 AufenthG", + "Niederlassungserlaubnis §9 AufenthG", + "Staatsbürger EUR/EWR/CH", + "Aufenthalt für Ausbildung §§ 16 - 17 AufenthG", + "Aufenthalt für Erwerbstätigkeit §§ 18- 21 AufenthG", + "Chancenaufenthaltsrecht §104c AufenthG", + "Sonstiges", + ], + tooltip="sofern nicht bekannt, unbedingt einfordern", + ), + FormField( + "Gültigkeit Aufenthaltsstatus", + FormFieldType.DATE, + required=False, + ), + FormField( + "Arbeitsstatus aktuell", + FormFieldType.DROPDOWN, + required=False, + options=[ + "Arbeitslos", + "Ausbildung/Qualifizierung Inland", + "geringfügig beschäftigt", + "in Anstellung Inland", + "selbstständig Inland", + "Ausbildung/Qualifizierung Ausland", + "in Anstellung Ausland", + "selbstständig Ausland", + ], + ), + FormField( + "Gemeldet bei Institutionen ", + FormFieldType.DROPDOWN, + required=False, + options=[ + "bei keiner", + "Jobcenter mit Leistungsbezug", + "Jobcenter ohne Leistungsbezug", + "Sozialamt mit Leistungsbezug", + "Sozialamt ohne Leistungsbezug", + "Agentur für Arbeit mit Leistungsbezug", + "Agentur für Arbeit ohne Leistungsbezug", + ], + ), +] + +FORM_FIELDS_SCHOOL = [ + FormField( + "Abschluss", + FormFieldType.TEXT, + required=False, + ), + FormField( + "Abschlussgrad laut Dokument", + FormFieldType.TEXT, + required=False, + ), + FormField( + "Schule", + FormFieldType.TEXT, + required=False, + ), + FormField( + "Ort", + FormFieldType.TEXT, + required=False, + ), + FormField( + "Land", + FormFieldType.DROPDOWN, + required=False, + options=["LÄNDERLISTE ERGÄNZEN"], + ), + FormField( + "Abschlussjahr", + FormFieldType.TEXT, + required=False, + ), + FormField( + "Bemerkungsfeld", + FormFieldType.TEXT, + required=False, + ), +] + +FORM_FIELDS_HIGHER_EDUCATION = [ + FormField( + "Anerkennung", + FormFieldType.TEXT, + required=False, + ), + FormField( + "Abschlussgrad", + FormFieldType.TEXT, + required=False, + tooltip=( + "bitte den Titel eingeben z.B. Doktor, Diplom oder " + "Betriebswirt (Fachschulabschluss)" + ), + ), + FormField( + "Abschlussgrad laut Dokument", + FormFieldType.TEXT, + required=False, + ), + FormField( + "Hochschule / Ausbildungsbetrieb / Berufsschule", + FormFieldType.TEXT, + required=False, + ), + FormField( + "Beruf / Fachrichtung", + FormFieldType.TEXT, + required=False, + tooltip=( + "bitte spezifizieren z.B. Allgemeinmedizin, Ingenieur Maschinenbau, " + "technischer Betriebswirt Datenverarbeitung" + ), + ), + FormField( + "Land", + FormFieldType.DROPDOWN, + required=False, + options=["LÄNDERLISTE ERGÄNZEN"], + ), + FormField( + "Ort", + FormFieldType.TEXT, + required=False, + ), + FormField( + "Abschlussjahr", + FormFieldType.TEXT, + required=False, + ), + FormField( + "Bemerkungsfeld", + FormFieldType.TEXT, + required=False, + tooltip="z.B. Promotionen oder den Studiengang angeben", + ), +] + +FORM_FIELDS_WORK_EXPERIENCE = [ + FormField( + "Branche", + FormFieldType.DROPDOWN, + required=False, + options=["DROPDOWN-LISTE AN ANDERER STELLE DEFINIERT"], + ), + FormField( + "Berufsbezeichnung/Tätigkeit", + FormFieldType.TEXT, + required=False, + ), + FormField( + "Funktion", + FormFieldType.DROPDOWN, + required=False, + options=[ + "Auszubildender", + "Fachkraft", + "Hilfskraft", + "Akademiker", + "Führungskraft", + "Praktikant", + "FSJ/BFD", + "Elternzeit", + "Sabbatical", + "Sonstiges", + ], + ), + FormField( + "Unternehmen", + FormFieldType.TEXT, + required=False, + ), + FormField( + "Land", + FormFieldType.DROPDOWN, + required=False, + options=["LÄNDERLISTE ERGÄNZEN"], + ), + FormField( + "Zeitspanne (von ... bis ...)", + FormFieldType.TEXT, + required=False, + ), + FormField( + "Beschäftsigungsart", + FormFieldType.DROPDOWN, + required=False, + options=[ + "Vollzeit", + "Teilzeit", + "Sonstiges", + ], + tooltip="Minijob, Praktikum, Wehrdienst, soziale Dienste", + ), + FormField( + "Bemerkungsfeld", + FormFieldType.TEXT, + required=False, + ), +] + +FORM_FIELDS_LANGUAGES = [ + FormField( + "Sprache", + FormFieldType.TEXT, + required=False, + ), + FormField( + "Niveau", + FormFieldType.DROPDOWN, + required=False, + options=[ + "A1", + "A2", + "B1", + "B2", + "C1", + "C2", + ], + ), + FormField( + "Nachweis", + FormFieldType.DROPDOWN, + required=False, + options=[ + "vorhanden", + "nicht vorhanden", + ], + ), + FormField( + "Art des Nachweises (NUR WENN VORHANDEN)", + FormFieldType.TEXT, + required=False, + ), + FormField( + "Datum des Nachweises (NUR WENN VORHANDEN)", + FormFieldType.DATE, + required=False, + ), +] + +FORM_DYN_LIST = FormFieldDynList("Dynamische Liste", FORM_FIELDS_SCHOOL) + + FORM_FIELD_GROUPS = [ FormFieldGroup( "Status && Projektrelevanz", @@ -608,6 +1008,13 @@ FORM_FIELD_GROUPS = [ ], ), FormFieldGroup("Daten Kontaktperson", FORM_FIELDS_CONTACT_PERSON), + FormFieldGroup("Stammdaten (ERGÄNZUNG KINDERALTER DYNAMISCH)", FORM_FIELDS_MASTER_DATA), + FormFieldGroup("weitere Informationen", FORM_FIELDS_ADDITIONAL_DATA), + FormFieldGroup("Schule (MIT PLUS-ERWEITERUNG)", FORM_FIELDS_SCHOOL), + FormFieldGroup("Studium/Ausbildung (MIT PLUS-ERWEITERUNG)", FORM_FIELDS_HIGHER_EDUCATION), + FormFieldGroup("Arbeitserfahrung (MIT PLUS-ERWEITERUNG)", FORM_FIELDS_WORK_EXPERIENCE), + FormFieldGroup("Sprachkenntnisse (MIT PLUS-ERWEITERUNG)", FORM_FIELDS_LANGUAGES), + FormFieldGroup("Dynamische Liste", FORM_DYN_LIST), # FormFieldGroup("Test-2", FORM_FIELD_DEF2), ] @@ -645,9 +1052,14 @@ class AutoForm(QWidget): self.widgets: dict[str, QWidget] = {} for fg in form_field_groups: - form_group = FormGroupWidget(fg.fields, fg.label) - self.main_layout.addWidget(form_group) - self._add_widgets(form_group.widgets) + _sub = fg.fields + if isinstance(_sub, FormFieldDynList): + widget = DynamicListWidget(_sub.fields, _sub.label) + else: + widget = FormGroupWidget(_sub, fg.label) + self.main_layout.addWidget(widget) + # TODO widget list is not updated with dynamic lists + # self._add_widgets(widget.widgets) # self.widgets.update(form_group.widgets) # buttons @@ -707,7 +1119,10 @@ class AutoForm(QWidget): # Wir gehen unsere Feld-Definitionen durch for fg in self.form_field_groups: - for field in fg.fields: + if isinstance(fg.fields, DynamicListWidget): + continue + + for field in fg.fields: # type: ignore widget = self.widgets[field.key] # 1. Zuerst setzen wir das Design des Feldes wieder auf "Normal" zurück. @@ -747,8 +1162,9 @@ class AutoForm(QWidget): # --- ERGEBNIS AUSWERTEN --- if errors: # Es gibt Fehler! Speichern abbrechen und Pop-up anzeigen. - error_text = "Bitte fülle die folgenden Pflichtfelder aus:\n\n- " + "\n- ".join( - errors + error_text = ( + "Bitte füllen Sie die folgenden Pflichtfelder aus:\n\n- " + + "\n- ".join(errors) ) QMessageBox.warning(self, "Fehlende Angaben", error_text) self._enable_save() @@ -762,7 +1178,10 @@ class AutoForm(QWidget): def reset_form(self) -> None: for fg in self.form_field_groups: - for field in fg.fields: + if isinstance(fg.fields, DynamicListWidget): + continue + + for field in fg.fields: # type: ignore widget = self.widgets[field.key] if field.readonly: @@ -803,6 +1222,109 @@ class AutoForm(QWidget): return data +class DynamicListWidget(QWidget): + """ + Ein Widget, das dynamisch beliebig viele Sub-Formulare + (z.B. Adressen) erzeugen und verwalten kann. + """ + + def __init__(self, fields: Sequence[FormField], title="Eintrag"): + super().__init__() + self.fields = fields + self.title = title + self.widgets: dict[str, QWidget] = {} + + # Das Hauptlayout für diese Liste + self.group_box = QGroupBox(title) + self.main_layout = QVBoxLayout(self) + self.main_layout.setContentsMargins(0, 0, 0, 0) + self.inner_layout = QVBoxLayout() + self.main_layout.addWidget(self.group_box) + self.group_box.setLayout(self.inner_layout) + + # Liste, in der wir uns die generierten Sub-Formulare merken + self.sub_forms = [] + + # Der "+ Hinzufügen" Button + self.add_btn = QPushButton("+ Weitere hinzufügen") + self.add_btn.setStyleSheet( + "color: #0369a1; font-weight: bold; border: 1px dashed #0369a1; padding: 5px;" + ) + self.add_btn.clicked.connect(self.add_entry) + + self.inner_layout.addWidget(self.add_btn) + + # Direkt beim Start ein leeres Formular anzeigen (optional) + self.add_entry() + + def add_entry(self): + """Erzeugt ein neues Sub-Formular und fügt es VOR dem Plus-Button ein.""" + + # 1. Den Rahmen für das einzelne Sub-Formular bauen + entry_box = QGroupBox(f"{self.title} {len(self.sub_forms) + 1}") + entry_layout = QHBoxLayout(entry_box) + + # TEST auto form generator + # TODO: keys may not be fixed + form_widget = FormGroupWidget(self.fields, None) + self.widgets.update(form_widget.widgets) + + # 2. Das eigentliche Formular (hier könntest du auch deinen automatisierten + # Form-Generator rekursiv aufrufen!) + # target_layout = QFormLayout() + # street_input = QLineEdit() + # city_input = QLineEdit() + # target_layout.addRow("Straße:", street_input) + # target_layout.addRow("Stadt:", city_input) + + # 3. Den "Löschen" (-) Button bauen + del_btn = QPushButton("🗑️") + del_btn.setFixedSize(30, 30) + # Lambda mit default-Parameter, um genau DIESE entry_box zu löschen + del_btn.clicked.connect(lambda checked=False, box=entry_box: self.remove_entry(box)) + + # 4. Alles zusammensetzen + # entry_layout.addLayout(target_layout) + entry_layout.addWidget(form_widget) + entry_layout.addWidget(del_btn) + + # 5. Daten merken, um sie später auszulesen + self.sub_forms.append({"box": entry_box, "form_widget": form_widget}) + + # 6. In das Hauptlayout einfügen (Index - 1 bedeutet: VOR dem "+" Button) + self.inner_layout.insertWidget(self.main_layout.count() - 1, entry_box) + self.update_titles() + + def remove_entry(self, box_to_remove: QGroupBox): + """Löscht ein Sub-Formular wieder.""" + # Das Widget aus dem Layout entfernen und zerstören + self.main_layout.removeWidget(box_to_remove) + box_to_remove.deleteLater() + + # Aus unserer internen Liste filtern + self.sub_forms = [f for f in self.sub_forms if f["box"] != box_to_remove] + self.update_titles() + + def update_titles(self): + """Nummeriert die Boxen nach dem Löschen neu durch.""" + for index, form in enumerate(self.sub_forms): + form["box"].setTitle(f"{self.title} {index + 1}") + + def get_data(self): + """Liest die Daten aller dynamisch erzeugten Formulare aus!""" + raise NotImplementedError() + + results = [] + for form in self.sub_forms: + # Hier bauen wir ein Dictionary pro Sub-Formular + entry_data = { + "strasse": form["widgets"]["strasse"].text(), + "ort": form["widgets"]["ort"].text(), + } + results.append(entry_data) + return results + + class FormGroupWidget(QWidget): def __init__( self, @@ -844,16 +1366,21 @@ class FormGroupWidget(QWidget): # --- LAYOUT --- self.main_layout = QVBoxLayout(self) self.main_layout.setContentsMargins(0, 0, 0, 0) + self.inner_layout = QVBoxLayout() + # self.inner_layout.setContentsMargins(0, 0, 0, 0) self.form_layout = QFormLayout() + self.inner_layout.addLayout(self.form_layout) self.group_box: QGroupBox | None = None self.group_name = group_name if self.group_name: self.group_box = QGroupBox(group_name) self.main_layout.addWidget(self.group_box) - self.group_box.setLayout(self.form_layout) + self.group_box.setLayout(self.inner_layout) + # self.group_box.setLayout(self.form_layout) else: - self.main_layout.addLayout(self.form_layout) + # self.main_layout.addLayout(self.form_layout) + self.main_layout.addLayout(self.inner_layout) self.form_layout.setSpacing(10) # Abstand zwischen den Zeilen @@ -919,20 +1446,30 @@ class FormGroupWidget(QWidget): case FormFieldType.DROPDOWN: widget = QComboBox() assert field.options + widget.addItem("--- Bitte wählen ---") widget.addItems(field.options) if field.placeholder: widget.setPlaceholderText(field.placeholder) if field.fill_value: widget.setCurrentText(field.fill_value) + else: + widget.setCurrentIndex(0) if field.readonly: widget.setEnabled(False) widget.setProperty("styleClass", "stempel") + # case FormFieldType.DYNAMIC_LIST: + # widget = DynamicListWidget(field.label) + case _: raise NotImplementedError(f"Not supported field type: {field.type.value}") self.widgets[field.key] = widget + # if field.type is FormFieldType.DYNAMIC_LIST: + # self.inner_layout.addWidget(widget) + # continue + if not field.tooltip: self.form_layout.addRow(field.label, widget) continue @@ -952,6 +1489,17 @@ class FormGroupWidget(QWidget): self.form_layout.addRow(field.label, field_layout) +# from PySide6.QtWidgets import ( +# QFormLayout, +# QGroupBox, +# QHBoxLayout, +# QLineEdit, +# QPushButton, +# QVBoxLayout, +# QWidget, +# ) + + class ClickableCell(QFrame): # Wir definieren ein Signal, das ein Dictionary (die Daten) mitschickt clicked = Signal(dict) @@ -1273,7 +1821,7 @@ class SearchFormPage(QWidget): ] ) combo.setPlaceholderText("Bitte auswählen") - combo.model().item(0).setEnabled(False) + combo.model().item(0).setEnabled(False) # type: ignore hor_layout.addWidget(label) hor_layout.addWidget(combo, stretch=1) container_layout.addLayout(hor_layout) diff --git a/pyproject.toml b/pyproject.toml index 2ca2ea4..326c772 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ description = "GUI for CRM of NAFKA project with WCE" authors = [ {name = "d-opt GmbH, resp. Florian Förster", email = "f.foerster@d-opt.com"}, ] -dependencies = ["nicegui>=3.10.0", "pyside6>=6.11.0", "sqlalchemy>=2.0.49", "polars>=1.40.1", "dopt-basics>=0.2.4"] +dependencies = ["nicegui>=3.10.0", "pyside6>=6.11.0", "sqlalchemy>=2.0.49", "polars>=1.40.1", "dopt-basics>=0.2.4", "pydantic>=2.13.4"] requires-python = "<3.14,>=3.11" readme = "README.md" license = {text = "LicenseRef-Proprietary"}