diff --git a/pyminer/__latest.json b/pyminer/__latest.json
index cddd011d1c5f1a93026e36ad89566128aaf2e50f..5d56a4f8f70e25a5b851c9194fe6ae88f61da581 100644
--- a/pyminer/__latest.json
+++ b/pyminer/__latest.json
@@ -1 +1 @@
-{"files": {"app2.py": "c7e061a1c028e828cb2bd76c46a7a858", "check_dependency.py": "35b3112cf9cb50d31be5f45c8ae3d179", "config.ini": "7f83e4b78df1a293343baeaaf5881203", "dist.py": "e3b17ce9bea81da8f9b4582ee78fe6cd", "LICENSE": "b5b6bed06dd8ed68f00c26d0b4cede89", "load_modules.py": "6bfcd0281e7ab654b27851cc25c2a31f", "pmgui.py": "54472e38004442ee8209f732e013e09a", "pyminer.pro": "148c684e721b44a4a29ec4c700e95c49", "pyminer.py": "8676961ecd2c4d293db44b2e8e962315", "pytest.ini": "7e1ac696b90974c21fcaed50d881ebf1", "README.md": "618338896f514c4f446bd0142edce65a", "requirements.txt": "bdc7ed15fe5b96b65882b02a3ce91877", "requirements_dev.txt": "6e23539b6bc3cd698e6f9713e598e2a9", "requirements_linux.txt": "32193efcf05f692095c24da7263ea8de", "requirements_mac.txt": "a1fdae24ed40656c9879626a8130c59c", "run_before_commit.py": "067f08926721cb4679e63604fa3528c4", "update_translation.py": "afcbf3f7c930b65e44c1bed2430d4284", "\u5f00\u53d1\u6307\u5357.md": "791025c5ef81f9b6a334e4f6d39e9cb6", "\u5f00\u53d1\u8fdb\u5ea6.url": "e47baa8f175a5766ae709b9e8d951b53", "configuration/config.ini": "56859fc0e799ae26d49fd7a41cfe0532", "configuration/default_settings.json": "7bdc7e60ac0fe14d849afb297fb9db2f", "configuration/extensions.json": "cb8f6f2bf8cdccae988f3cb589282f7c", "configuration/settings.json": "81f0b5a2f4f719f23e247b92ee72fff7", "core/__init__.py": "f0d32e8fca7a2398789eb9b0537057ce", "core/algorithms/index.rst": "493e85a5e6744431a7755a7d33f9979d", "core/algorithms/__init__.py": "489b6a395b7e79f2cab1f8c46eec73e1", "core/algorithms/calculation/digits.py": "b14e19d1913de492b116201b5f5c6c27", "core/algorithms/calculation/__init__.py": "6fb7087712399aff01a8a820cd77a474", "core/algorithms/linear_algebra/array.py": "f1e96ef9af8aaf15aaef29c863b3663c", "core/algorithms/linear_algebra/exceptions.py": "cc1a682728cd86a67aaf4b03a27c3e8a", "core/algorithms/linear_algebra/linear_space.py": "0d12ac5250106fc5fd0270b502ceb6e1", "core/algorithms/linear_algebra/matrix_cross.py": "ba1e06a57db80bcaa9509d6e733c1df9", "core/algorithms/linear_algebra/matrix_determinant.py": "23a47100204fd2f73f43cc201113f5e5", "core/algorithms/linear_algebra/matrix_diagonal.py": "ab7c7e101f46ebe7ed4bc73f542f111a", "core/algorithms/linear_algebra/matrix_divide.py": "01ec01fbb97bca37f22f719f3dc3d089", "core/algorithms/linear_algebra/matrix_dot.py": "67d721785420cd3f3344ac69bdb6ac32", "core/algorithms/linear_algebra/matrix_eigenvalue.py": "43b83077241aa32acdff04c8489363f5", "core/algorithms/linear_algebra/matrix_inverse.py": "f4c6ec6129a07ea821aff5b6b84f2aa6", "core/algorithms/linear_algebra/matrix_multiply.py": "067a30b30ff9be6d9b9123a75a30abc9", "core/algorithms/linear_algebra/matrix_transpose.py": "d63b39c00f437fab1540f331876444ab", "core/algorithms/linear_algebra/ones.py": "f7c3f42cde513ec5e91bc24dbc068d62", "core/algorithms/linear_algebra/reshape.py": "54f0d073af724f1c3c474e59940c6f72", "core/algorithms/linear_algebra/shape.py": "863cf7a043ab1d82f02a3a1279632b57", "core/algorithms/linear_algebra/zeros.py": "161e8f970d39d7bb2135852051a18005", "core/algorithms/linear_algebra/_utils.py": "8864ac424626bc7804d31b3e0ef3905f", "core/algorithms/linear_algebra/__init__.py": "a1ff4909241d1462727c33e5a8038cbd", "core/algorithms/linear_algebra/\u5f00\u53d1\u6d41\u7a0b.md": "54daed08712378a9ad4208884b65088b", "core/algorithms/linear_algebra/assets/code_hint.png": "013aecfff792430d0d3fac227bfcaaf4", "core/algorithms/linear_algebra/assets/configure_test_utils.png": "952e07a65e50b2fbd9666f8544ac20fa", "core/algorithms/linear_algebra/assets/define_function_framework.png": "4c921fffe0c7b10e5fb01e02d8f7a353", "core/algorithms/linear_algebra/assets/demand_change_file_change.png": "537d6591e1833d43c9fd6fc30cd1b2f6", "core/algorithms/linear_algebra/assets/finish.png": "d4c5fb995d723b15072691707b0ed72e", "core/algorithms/linear_algebra/assets/fix_testcase.png": "b1f4c682efcfb3f23e32ab085cc85b50", "core/algorithms/linear_algebra/assets/function_explanation.png": "c7bc75487b2abe6a536fc91a880d5f44", "core/algorithms/linear_algebra/assets/function_explanation_file_change": "15ade2607376365bb31c38a79d46ea89", "core/algorithms/linear_algebra/assets/function_file_change.png": "5b961672e09adba22805a5098e1bbbd1", "core/algorithms/linear_algebra/assets/function_workspace.png": "2bf5321cda6e0e1ac939895233e7a833", "core/algorithms/linear_algebra/assets/help_doc.png": "0e2f87b116e1fcb517cc63d5aa8e3d21", "core/algorithms/linear_algebra/assets/import_in_global.png": "259b30531b0ab509d4b93acec5cf3db1", "core/algorithms/linear_algebra/assets/import_in_sub_pkg.png": "622f44c6ec4738254d8585ae341b945e", "core/algorithms/linear_algebra/assets/run_in_pm.png": "4a64482cdd2125276501a4190848352c", "core/algorithms/linear_algebra/assets/run_test.png": "0d9abff3ca8a85fa0c3f58aef6ffd2d3", "core/algorithms/linear_algebra/assets/testcase_file_change.png": "caeda394883222a82b6dabd224fe0f0c", "core/algorithms/linear_algebra/assets/test_error.png": "a6b5a1a95cdc1fc21ce522f35da111aa", "core/algorithms/linear_algebra/assets/test_pass.png": "7792d7ee583bdb629ea41cec3a5cbf07", "core/algorithms/linear_algebra/assets/write_doc.png": "5349a5f4df8ed7a982ac5c86298ce715", "core/algorithms/linear_algebra/assets/write_doc_file_change.png": "34753c6a77cc470710669f518cfb3055", "core/algorithms/linear_algebra/assets/write_function_file_change.png": "2f5d964072df62b5a915fc14a41597b9", "core/algorithms/plotting/graph.py": "1728aea4907b92b67fe91ad742a3083f", "core/algorithms/plotting/graph_configs.py": "614cbeae8b696da2e8fe983e3c87a5f9", "core/algorithms/plotting/__init__.py": "9f90fe07592b24c4a09ed9230b6c093d", "core/algorithms/pyminer_util/communication.py": "216e0f4b1c8b00d535f0585c7fa1add3", "core/algorithms/pyminer_util/__init__.py": "de143e26c42af15d96c77f52383561a0", "core/algorithms/statistics/__init__.py": "4bba02c1df562ce45ccf5dfc988ab576", "core/evaluation/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "core/evaluation/woe/config.py": "9ac12638758e61c547224a0b4cc5e5f8", "core/evaluation/woe/eval.py": "62ab9836e64b475b07e0ea5cccf3aeb0", "core/evaluation/woe/feature_process.py": "e12d77b446b6a75746499d909ca22ae8", "core/evaluation/woe/ftrl.py": "1e6dad2dfc172413104ef5b3fb25e88d", "core/evaluation/woe/GridSearch.py": "8b92ecf1226fa81972510b17aa4cc2c4", "core/evaluation/woe/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "core/evaluation/woe/tests/config.csv": "66f330fdc285911a52c4a65166eab04f", "core/evaluation/woe/tests/HereWeGo.py": "94d48377d8292b80d8f4929cf9162004", "core/evaluation/woe/tests/README.rst": "8f72b661abc5eaf059d05e603b1c705c", "core/evaluation/woe/tests/UCI_Credit_Card.csv": "afd3af2602d66d6ceb36cb04216f0ed2", "core/io/postgresql/psycopg2/compat.py": "7abef6e4534c7b625057e37289bc8c27", "core/io/postgresql/psycopg2/errorcodes.py": "b2346a81ec49de54caa4e32a5360076e", "core/io/postgresql/psycopg2/errors.py": "316dfc64e89c95715e974a31f96e18fe", "core/io/postgresql/psycopg2/extensions.py": "d7fc21cf847f2d12a60f56097e3eca2f", "core/io/postgresql/psycopg2/extras.py": "4d79c51ba46dc3285d9faf8789a37369", "core/io/postgresql/psycopg2/pool.py": "1b0403b2597557108c4c35a9bc568834", "core/io/postgresql/psycopg2/sql.py": "1b8ad7b5746ad2f514cbdd678954fee2", "core/io/postgresql/psycopg2/tz.py": "b70b2abdc56dc05e9c28993934bed8f4", "core/io/postgresql/psycopg2/_ipaddress.py": "e0ff64e2ae604224c8cd11d1bdb27003", "core/io/postgresql/psycopg2/_json.py": "38e03cf8ae626f8fa028ceb48dcdb960", "core/io/postgresql/psycopg2/_lru_cache.py": "5e2bc12517950812ccac51af1d61d8a5", "core/io/postgresql/psycopg2/_range.py": "46ff7ab96f96d7db81eee160f666cfa4", "core/io/postgresql/psycopg2/__init__.py": "36fa5f3355c7d7ea36b87d1cdda6afe4", "core/modelling/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "core/modelling/regression/base_regression.py": "461cde16f7769d88c386f6f142ec233a", "core/modelling/regression/knn.py": "9f0c137ed701a2d11325ae1c9da23caa", "core/modelling/regression/linear.py": "a7936db33b1faa26cdbdc53a4bb81dfc", "core/modelling/regression/linear_bfgs.py": "031491ee5d158dcc8d506458fd10dbdf", "core/modelling/regression/mean.py": "c494d914a7be950f81b073dcad1e8bdf", "core/modelling/regression/neural_network.py": "571fa4e8bdc730c503cda08b0afd1d41", "core/modelling/regression/random_forest.py": "33c3aa9f1300b805ef13dd96866ea114", "core/modelling/regression/simple_random_forest.py": "e8c180090ce051d2f57e94b98e30b3c3", "core/modelling/regression/svm.py": "f5547c9da4c71d06a1df162be1e51885", "core/modelling/regression/tree.py": "0bd5b05a252b233597b6ff7453881ebd", "core/modelling/regression/__init__.py": "5b1908d95bc0ac027b22c337ea12f21b", "core/preprocess/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "docs/make.bat": "0d5c4de56de1ea8fa10468d561de4d77", "docs/Makefile": "45ecc4dd568420521f285e291939ecf1", "docs/make_doc.py": "3710c825012f1dc2e8d4f5854622e186", "docs/PyMiner\u4ecb\u7ecd.md": "56012d9e37961de92ea165d227e8707b", "docs/THANKS": "ad97f1302bcf00e26a219a0801a0b34d", "docs/upload.py": "10534ab4011a82607ef2b0c2f6c1b599", "docs/source/conf.py": "86928f87e5b0a03cbdd58f6f8a87f55b", "docs/source/contents.rst": "cd9756a400d0aa68ef85e92394c58b25", "docs/source/contribute/index.md": "260363f4717c8db812dc5d9c2c5723f8", "docs/source/_static/css/pyminer.css": "4a98b74c6e7893114cabc72ef5c6b81d", "docs/source/_static/css/pyminer.css.map": "e82da77c9ca623f0acbf4cbc62d78cbd", "docs/source/_static/css/pyminer.sass": "e5940574b2a7af7e3b7c01c2fc600354", "docs/source/_static/css/README.txt": "6e9ef1827e9eaea7ca3831685d830c7d", "docs/source/_static/index_files/all.css": "242611f34a440c48c2e6405e91e01a71", "docs/source/_static/index_files/all.js(1).download": "d7b3138b22aac6df42d86c92e36763ba", "docs/source/_static/index_files/all.js(2).download": "83006561af55b7a96dd7e17d34ebfe8a", "docs/source/_static/index_files/all.js.download": "39bebcf34d45ccab4d08bfb31b684294", "docs/source/_static/index_files/check_data.png": "e44471c2514f8a048df2a89a9df2e795", "docs/source/_static/index_files/code.png": "814df667bb94b7d2ae844d938ec916b7", "docs/source/_static/index_files/font-awesome.min.css": "ea6cc550de5339fc787f1e041363e544", "docs/source/_static/index_files/ico_mailme_01.png": "c40acf63e04064714b98a30a92984868", "docs/source/_static/index_files/main.png": "1b6f8a5331f0c5954a86b9024f070568", "docs/source/_static/index_files/modernizr.js.download": "4fae2a90728c528aa148c31466b7ed39", "docs/source/_static/index_files/normalize.css": "ed3146b9b1ec5eecb132a21916d0afe5", "docs/source/_static/index_files/robot.svg": "ba2b8a892fc5457a02829ba0b2caf3da", "docs/source/_templates/index.html": "66315ce66e29f04ce356a9b8b0d41ac8", "examples/HereWeGo.py": "d94883e4b2545ccfec29c1e76aebb982", "examples/README.md": "d49b56a1fe9a7ef2530943a5848fac88", "examples/README_ZH.md": "ac49a6e35c2518cac510ee6920c840da", "examples/UCI_Credit_Card.csv": "afd3af2602d66d6ceb36cb04216f0ed2", "examples/woe_rule.pkl": "cc2695e5f41ac2679a2040e15ff9463e", "examples/datasets/air_quality_no2.csv": "c27e6437bf25787c881d0d5f57e10233", "examples/datasets/bankloan.sav": "b5e78f9be79d7ba44c4fe5ac69cddc02", "examples/datasets/boston_house_prices.csv": "6454dba73c425c66b50d5206ed45f1a0", "examples/datasets/brown-selected.tab": "9e43112b53c6f6c78fa0b2645ace506d", "examples/datasets/brown-selected.tab.metadata": "337569fc2132e37108b428b9b082cfe9", "examples/datasets/cars.sas7bdat": "073898a0487cde27abd7155461c564be", "examples/datasets/car_sales.sav": "7b2487624eb4ffb893812706d30b0542", "examples/datasets/ccpp.csv": "20372480cb1e06f4cb99e46a0bd759a4", "examples/datasets/class.sas7bdat": "6e69aeb1f12de8d3fc46a64cbd4dc786", "examples/datasets/conferences.dst": "78dc681ceec57ee82dbf1951f11fa758", "examples/datasets/datasets.info": "19934e0d167a3128a09c493b9aeb031e", "examples/datasets/diabetes.csv": "8523f75b2f041c08542910b7ea3a8e84", "examples/datasets/heart_disease.tab": "859a303f42efd8b2a1bd0f2e33fedf71", "examples/datasets/heart_disease.tab.metadata": "ed45f5da717ba279094a29f33a4b263d", "examples/datasets/housing.tab": "e0eda02af8c85dd28b21620ee5c74552", "examples/datasets/housing.tab.metadata": "9d88fcbc612a9928d951ceb5f22b04c9", "examples/datasets/iris.csv": "ffd137d9c66a717d061b9fa5831000b1", "examples/datasets/iris.tab": "bbf17c5ae7e81aeb2ad7694648a6b2e1", "examples/datasets/iris.tab.metadata": "e1d89c7e7561bcc716dafd546058196e", "examples/datasets/list_update.py": "737978a6df832d796431df3b048b051a", "examples/datasets/mushrooms.csv": "2007f683881ecd4febc1b7674c5751a8", "examples/datasets/slovenian-towns.dst": "e6c7acf1cb81c6d71523d0e7b3ea6f41", "examples/datasets/titanic.tab": "4291853e1bf953ef2c0c1e39653a0f80", "examples/datasets/titanic.tab.metadata": "5074c723c0342fa64396739eb0480d33", "examples/datasets/TitanicData.xls": "7afb9b02b50c902fd364364f7d2e49be", "examples/datasets/UCI_Credit_Card.csv": "afd3af2602d66d6ceb36cb04216f0ed2", "examples/datasets/zoo.tab": "90a455135a6a8822f1200630270bdcb0", "examples/datasets/zoo.tab.metadata": "1b824e81f9e8969228e01e460d6b05e6", "features/feedback.py": "2fed1192b08f982e19b6cb75f40c7f10", "features/index.rst": "caf9be56f3fc9b08f569abee024f6cf2", "features/openprocess.py": "539c43bc37743144d9216715b8fad57f", "features/README.md": "eed382b7fe1775f4b6b875d4b564ae50", "features/settings.py": "a7c36fc21234c3a23c7663e1b248e60b", "features/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/auth/authlocalserver.py": "1a7bd0455dc04627bdb5367c7ebc9c2a", "features/auth/authmanager.py": "06a45267063200c67104786db5cff4e1", "features/auth/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/auth/templates/login.html": "446aac7443cc70f43e46753092445aef", "features/auth/templates/register.html": "3a424e8c3bdf1eb6043e0ab9ca34acff", "features/extensions/index.rst": "01d20711a991f116e372db6fb72c96df", "features/extensions/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/extensions/extensionlib/baseext.py": "677942d6d94defa454ec27b173d582db", "features/extensions/extensionlib/extensionlib.md": "aacae615d8f781c275ac79c22fae5678", "features/extensions/extensionlib/extension_lib.py": "e48b16cc626c5001355438e28f00d219", "features/extensions/extensionlib/readme.md": "00629cf6b22e95200c530ea6d512057f", "features/extensions/extensionlib/__init__.py": "2dc050a0d3da896f4b39681a6e057f63", "features/extensions/extensions_manager/ExtensionLoader.py": "aee9c0b155f0bade604e41d3a38efa36", "features/extensions/extensions_manager/log.py": "6e8bd61219f2ff606b58c1399d322ddf", "features/extensions/extensions_manager/manager.py": "a7fa22dd5ba1fc531f31b0741f12c61e", "features/extensions/extensions_manager/readme.drawio": "4bafe50e30ca4be5cdef34e1e657f7a6", "features/extensions/extensions_manager/README.md": "97b9464ed10af4ad3015ac7f38e6cf99", "features/extensions/extensions_manager/UIInserter.py": "e66518eebc812a4f5b94997351fe1b0b", "features/extensions/extensions_manager/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/extensions/extensions_manager/vermanager/vermanager.py": "01c76efe94e29a53138a670f909f2d52", "features/extensions/extensions_manager/vermanager/__init__.py": "03f044152b579a37841af43eae7f2389", "features/interpretermanager/interpretermanager.py": "f444a2a4cebc7ae225cf46f61ae7950b", "features/interpretermanager/packagemanager.py": "573cf34280aff9d22775f692e63cbb8c", "features/interpretermanager/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/io/database.py": "567325cf72a6f4564a4a0f27326f1b25", "features/io/dbConn.py": "50780c5805a79cb1934b2d7e0d8c0ab6", "features/io/dbConnectAccount.pkl": "5d9291410939747f2c8c07dd81222283", "features/io/encoding.py": "3d17abe6b8042115f6e15b869aff7c44", "features/io/exceptions.py": "72dcca0f3371e119d43e9827e73528b0", "features/io/settings.py": "a3234ca4ddc2f8b0e0e62450756c1e56", "features/io/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/main_window/base.py": "aab682344cfc58317a1413203ebd6261", "features/pluginsmanager/pluginsmanager.py": "f7dc55bb96470571a9a6558cb3962591", "features/pluginsmanager/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/project/template/Basic-Template.py": "4204e7ce0f6309fd93535659c3c466c8", "features/project/template/Empty-Template.py": "18dc747535104b7c6ef67b21c97069f1", "features/project/template/Plot-Template.py": "b688a56819d86c6eb44ff9e72b809091", "features/project/template/PyQt-Template.py": "8a64552bb7be154721ceeaf50f4e8673", "features/project/template/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/project/template/PySide2Template/main.py": "ed912fbf0fc0c20fcacb81bd19800c14", "features/project/template/PySide2Template/PySide2_Template.py": "ac93a3b7fc512f9c0afa7f00a2643674", "features/project/template/PySide2Template/PySide2_Template.ui": "c8cd27a3e0b5cb1c9936603328d7b732", "features/project/template/PySide2Template/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/ui/main.py": "2d7d5d9496b9208ddb27e60f708a1a4d", "features/ui/ui_aboutme.py": "c79955815de4013a7a64d690f94febbc", "features/ui/ui_aboutme.ui": "160718ccbd19dc35a00aaeee6faffaa2", "features/ui/ui_appstore.py": "256f2fd75ad7ffdf667c13bf7a3b5128", "features/ui/ui_appstore.ui": "a7e7317eca4ee6311e41968445e1fa81", "features/ui/ui_check_update.py": "8226a48eb517b9da8cf425b809ff3c8a", "features/ui/ui_check_update.ui": "0766a087aa5b8e4a5e46f4211055aa9b", "features/ui/ui_data_normal.py": "d9971d97184ade3f9c405f061dd22231", "features/ui/ui_data_normal.ui": "c8a3e10c4487ce5a2774ca8ea5a5905a", "features/ui/ui_first_form.py": "cbbf26db8887e60df0dd7ce0713a5a54", "features/ui/ui_first_form.ui": "1b5d9231837a421c9b77382b3f6f1719", "features/ui/ui_login.py": "2fe8d95e4257473abea1549e127f7ede", "features/ui/ui_login.ui": "58916dfb5dd1dfab103fa4f15882a282", "features/ui/ui_logined.py": "668bf0e57ec258d1ee0b7bfac2abe1b5", "features/ui/ui_logined.ui": "e6d6f9d467a6b8a24336713481e9a6f6", "features/ui/ui_option.py": "12491a3f32f99d3bb7833291c5402cfa", "features/ui/ui_option.ui": "9cf7b928ccf449e096586e949cd7dd95", "features/ui/ui_preferences.py": "d925283bad5dc99d85a90798cb86c6ce", "features/ui/ui_preferences.ui": "4336a5c78eef921b047e1c86c49650f5", "features/ui/ui_project_wizard.py": "65f0c2afe19b349e8b63b57a941c4db1", "features/ui/ui_project_wizard.ui": "9d21051c8df87eb832bdf8a5470f1c8c", "features/ui/ui_workspace_launcher.py": "594f60adb389355ef83a5bca20eafb5a", "features/ui/ui_workspace_launcher.ui": "fd4e48c43a6d35a209c2696942f0927f", "features/ui/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/ui/common/debug_process_with_pyqt.py": "2971e07b890f1872c7f7009c4d2b44fe", "features/ui/common/openprocess.py": "3d1aeb10bde66329d7126840466ba6df", "features/ui/common/open_process_with_pyqt.py": "851480f8440b2f81033a319b5f32f465", "features/ui/common/platformutil.py": "070ffc4eaea4cb0c4b6dcca39fe4035f", "features/ui/common/pmlocale.py": "f7ca747cc3dedc2b6cf03c7b5f9ec121", "features/ui/common/test_open_app.py": "5f56a2266c07dd652659c949b297f498", "features/ui/common/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/ui/pmwidgets/dockwidget.py": "cb0c4fe5642dda21f5692ecce49eed95", "features/ui/pmwidgets/pmmainwindow.py": "5f3887a06e377de92251c6b49853cddb", "features/ui/pmwidgets/toplevel.py": "8700605abc51c422a5a1588dc80a8bfd", "features/ui/pmwidgets/__init__.py": "dbd5c209ff332a55a7e483fcb686421f", "features/ui/pm_marketplace/install.py": "54117accb9d426c89b751355b7e73f0b", "features/ui/pm_marketplace/install.ui": "22c1cb1d2e58f0e6e63d3c199af96edd", "features/ui/pm_marketplace/main.py": "1fea599f650efa60951f3029d940d73e", "features/ui/pm_marketplace/main.ui": "76b3d012f1742eeb8cb85a0ee9ec5680", "features/ui/pm_marketplace/package_manager_main.py": "0ab9760f90d31f8f0eb3287bdf530891", "features/ui/pm_marketplace/package_manager_main.ui": "8dba9def55b1ea06d6c953f33e875cb9", "features/ui/pm_marketplace/uninstall.py": "292fa58f6d05fc584029fb0650702aa3", "features/ui/pm_marketplace/uninstall.ui": "c1160fc571c23573e4bfb4ada3807edc", "features/ui/pm_marketplace/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/ui/widgets/controlpanel.py": "d5fceeb26a8878ff80d6339895dbb5cd", "features/ui/widgets/notificationwidget.py": "3c6d71d757c8a0e3a11b9e48ca7108eb", "features/ui/widgets/README.md": "9f7d88859d291d2f64cba1b6ba49a59e", "features/ui/widgets/reportwidget.py": "1f00f377fe6cfc610cf58e68462307e6", "features/ui/widgets/resources.py": "14a758e941b459f8b23704065f1e8639", "features/ui/widgets/__init__.py": "77b4a6a8852c7103e7525d3955c40358", "features/util/check_update_ui.py": "5bfc0584c45856ace75efedafd677436", "features/util/check_update_ui.ui": "0766a087aa5b8e4a5e46f4211055aa9b", "features/util/make_update.py": "17f81b28bdc7a353dac1d7b45ed762cf", "features/util/openprocess.py": "539c43bc37743144d9216715b8fad57f", "features/util/update.py": "896e76ccbae9ae36c17d14515b6fafa5", "features/util/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/workspace/data_manager.py": "d7b5e29419b744fad86e6245018de1e8", "features/workspace/index.rst": "3ce5d474607ba614ba9f6afc772e7bc2", "features/workspace/signals.py": "d950f3f33fd821323d95ece2b8d90e84", "features/workspace/signals.rst": "3618e25aa7f77f05142dc84a96eaf45c", "features/workspace/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/workspace/blinker/base.py": "83733f47cb03be3713ce7a5860612e96", "features/workspace/blinker/_saferef.py": "ced62e1fda983045da2076064a7db34d", "features/workspace/blinker/_utilities.py": "1aeb68e85d8ad0aa80295e5f29f42d18", "features/workspace/blinker/__init__.py": "464a73bb96572aaf6c2ed191676d40a4", "features/workspace/data/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/workspace/data_adapter/array.py": "2891617cd8d0eb1cd3780258212ce847", "features/workspace/data_adapter/base.py": "8d176a5909da92fbbfdade7ecd040127", "features/workspace/data_adapter/data_frame.py": "d71dc9d36194663b154bbb13eb1bf8a9", "features/workspace/data_adapter/detector.py": "af748d211285fe07fa56bf2b21e4d60e", "features/workspace/data_adapter/index.rst": "1d16f21a65dadb51dc1ecc68d9cad119", "features/workspace/data_adapter/universal.py": "49603cc94bc554bdb8456a1cdbdd39b1", "features/workspace/data_adapter/__init__.py": "b9da2c81695eae92f9748a4a59cd0c29", "features/workspace_old/history.md": "ab52fc7f26dff2cecf2f2f704844db56", "features/workspace_old/index.rst": "961579c17854f0d0d7b9f4e0bb6b1621", "features/workspace_old/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/workspace_old/datamanager/converter.py": "0cddb9d818f283a125bf534c3633f2b7", "features/workspace_old/datamanager/datamanager.py": "a2025c72d71aaf79728c54bfaf9dc3d3", "features/workspace_old/datamanager/dataset.py": "5d0b8678f8a53db8d8c9a483b90c79aa", "features/workspace_old/datamanager/exceptions.py": "6ed4da7a9003f12c756366cf67d87027", "features/workspace_old/datamanager/historyset.py": "85f91eac3f692ce44ed011647d319bac", "features/workspace_old/datamanager/metadataset.py": "9047c690af46c4db5b0501fdc25f975b", "features/workspace_old/datamanager/recyclebin.py": "c9effef9419e3be130a027f116761881", "features/workspace_old/datamanager/variable.py": "d35eb9e5fea49eaf5899d7315caa582a", "features/workspace_old/datamanager/varset.py": "54e77b59c43212ef22b4e271b8893953", "features/workspace_old/datamanager/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "languages/en.ts": "64307a7a6a8a2bc627ecbfaf2fc0e805", "languages/en/en.ts": "a426bc922ab99d1920e790b890d1b386", "languages/zh_CN/pmgwidgets_qt_zh_CN.qm": "cbaaec13dd22418095f6c7a9122926cc", "languages/zh_CN/pmgwidgets_qt_zh_CN.ts": "d81ecd8ec48ae725c76eba4f09ee9ecb", "languages/zh_CN/pmtoolbox_qt_zh_CN.ts": "95510ea97a650faeba03d155b1c81cbf", "languages/zh_CN/qt_zh_CN.ts": "bf9e10698c9cef267c344f73e30789e6", "languages/zh_CN/zh_CN.qm": "941aa53373af233466a16d682d7e625a", "languages/zh_CN/zh_CN.ts": "1be0a3147fea71527d4e17065a66d482", "languages/zh_CN/zh_CN.ts.bak": "c916a039d8a676ad891150786e9b1185", "languages/zh_TW/zh_TW.ts": "a426bc922ab99d1920e790b890d1b386", "packages/index.rst": "60e962032576844618f7a1448880b8fd", "packages/__init__.py": "a248423783ba8e03c638b996a91de109", "packages/advanced_drawings_toolbar/group_chart.py": "846a72c9e0303f8ef4b5c6b809f07db7", "packages/advanced_drawings_toolbar/ipython_console.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/advanced_drawings_toolbar/main.py": "78b220f88ba07d759c9f0722f6fc0306", "packages/advanced_drawings_toolbar/map_var.json": "91324fe1e6b292ebc2d548f3ae7d49b7", "packages/advanced_drawings_toolbar/map_var.py": "46e4e76e96cb5691754e4b63a38b67d9", "packages/advanced_drawings_toolbar/package.json": "3483b83687dfaf5bb4410107b04fdfd0", "packages/advanced_drawings_toolbar/radar_chart.py": "67ea13759ea77d67006df488c9fde4e0", "packages/advanced_drawings_toolbar/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/advanced_drawings_toolbar/pmmap/china/all/01-beijing.npy": "05fc6e35605340ef6e4d62af9770ef7a", "packages/advanced_drawings_toolbar/pmmap/china/all/02-shanghai.npy": "dbd37714da88edf44df7a2642ce2c14f", "packages/advanced_drawings_toolbar/pmmap/china/all/03-tianjin.npy": "e767b1d0810acfd174d4b52586441de1", "packages/advanced_drawings_toolbar/pmmap/china/all/04-chongqing.npy": "1980444de2496af7ae7b11cae9372d2c", "packages/advanced_drawings_toolbar/pmmap/china/all/05-heilongjiang.npy": "f369a349db31ac0636e625737802922e", "packages/advanced_drawings_toolbar/pmmap/china/all/06-neimeng.npy": "0366d9f95bfe2e0768a6491f8f5e27cc", "packages/advanced_drawings_toolbar/pmmap/china/all/07-xinjiang.npy": "201b3ff97375ae9ba6e9fc9c7b0eed1e", "packages/advanced_drawings_toolbar/pmmap/china/all/08-jilin.npy": "0ed85b6d84b877cf07154e1b54db0bdd", "packages/advanced_drawings_toolbar/pmmap/china/all/09-gansu.npy": "08c6e4437a0fc5d19d6c655a14a7df2e", "packages/advanced_drawings_toolbar/pmmap/china/all/10-liaoning.npy": "542d598f5c39b363738b0b753d8318d1", "packages/advanced_drawings_toolbar/pmmap/china/all/11-hebei.npy": "5a7ce525859caabaffad0fe1c11cb26f", "packages/advanced_drawings_toolbar/pmmap/china/all/12-shanxi.npy": "5ebb5347431393d599f5f0d36a31dd07", "packages/advanced_drawings_toolbar/pmmap/china/all/13-shan3xi.npy": "71944b8eea6caa0b7e4138063d6e8945", "packages/advanced_drawings_toolbar/pmmap/china/all/14-ningxia.npy": "dc0660ac4216cb4e928fb08dba5e36fb", "packages/advanced_drawings_toolbar/pmmap/china/all/15-qinghai.npy": "46f4e9bf9a47734d0cfbef0916e6cec1", "packages/advanced_drawings_toolbar/pmmap/china/all/16-shandong.npy": "b3eee97751e252f1c729b20cde81220c", "packages/advanced_drawings_toolbar/pmmap/china/all/17-henan.npy": "530d73e54901bf816e61c86bafb97219", "packages/advanced_drawings_toolbar/pmmap/china/all/18-xizang.npy": "83c873032100d735d2a3c1becb4e4ee5", "packages/advanced_drawings_toolbar/pmmap/china/all/19-jiangsu.npy": "33051765300de0a9d003ea91c3846cc6", "packages/advanced_drawings_toolbar/pmmap/china/all/20-anhui.npy": "d80f40e27c3a60188547d058c827eae0", "packages/advanced_drawings_toolbar/pmmap/china/all/21-sichuan.npy": "115e4b1abbc6df1f862095e94cacb8b5", "packages/advanced_drawings_toolbar/pmmap/china/all/22-hubei.npy": "065b61ce8a2728be70d0e726990e42c2", "packages/advanced_drawings_toolbar/pmmap/china/all/23-zhejiang.npy": "6910df476893b4e5a84689094203b724", "packages/advanced_drawings_toolbar/pmmap/china/all/24-jiangxi.npy": "2d4e166cd8d9371a1c6d01848ece3301", "packages/advanced_drawings_toolbar/pmmap/china/all/25-hunan.npy": "ddbce0334a1f79bd77f7b0ae582dd28e", "packages/advanced_drawings_toolbar/pmmap/china/all/26-guizhou.npy": "4d23f4f330203b61c6f8cca0ac764819", "packages/advanced_drawings_toolbar/pmmap/china/all/27-yunnan.npy": "1d1c9378c8427667dbf0b8948c36b484", "packages/advanced_drawings_toolbar/pmmap/china/all/28-fujian.npy": "77270ff92f0d67f0123672527b70772a", "packages/advanced_drawings_toolbar/pmmap/china/all/29-guangxi.npy": "3862d0becc054fe754ae71c111c583ad", "packages/advanced_drawings_toolbar/pmmap/china/all/30-guangdong.npy": "7d7f1392cc01a22d4503354ce7c729b1", "packages/advanced_drawings_toolbar/pmmap/china/all/31-taiwan.npy": "2f398da02605db2be30751dedb92ea90", "packages/advanced_drawings_toolbar/pmmap/china/all/32-xianggang.npy": "b3cf9d637c7d87e82a86174f99be74da", "packages/advanced_drawings_toolbar/pmmap/china/all/33-aomen.npy": "68e3c682aef3e25cf873878a19df5361", "packages/advanced_drawings_toolbar/pmmap/china/all/34-hainan.npy": "a67e27cc4f68d566f6977580d95b006c", "packages/advanced_drawings_toolbar/pmmap/china/all/39-jiuduan.npy": "35fbdcbb72d2c22caab9b1bfd2eba401", "packages/advanced_drawings_toolbar/pmmap/china/small/30-guangdong.npy": "7d7f1392cc01a22d4503354ce7c729b1", "packages/advanced_drawings_toolbar/pmmap/china/small/31-taiwan.npy": "2f398da02605db2be30751dedb92ea90", "packages/advanced_drawings_toolbar/pmmap/china/small/32-xianggang.npy": "b3cf9d637c7d87e82a86174f99be74da", "packages/advanced_drawings_toolbar/pmmap/china/small/33-aomen.npy": "68e3c682aef3e25cf873878a19df5361", "packages/advanced_drawings_toolbar/pmmap/china/small/34-hainan.npy": "a67e27cc4f68d566f6977580d95b006c", "packages/advanced_drawings_toolbar/pmmap/china/small/39-jiuduan.npy": "35fbdcbb72d2c22caab9b1bfd2eba401", "packages/advanced_drawings_toolbar/source/down.svg": "fe6b296aa4020dda5c5a870bb7596235", "packages/advanced_drawings_toolbar/source/plot.svg": "05fd271af6c134f6872c80c489064d60", "packages/advanced_drawings_toolbar/source/\u5730\u56fe.png": "eca50b3207a973738145d1cf12ca0c34", "packages/advanced_drawings_toolbar/source/\u6298\u7ebf\u56fe.png": "1bde0eca8cfa880452accf56ca925866", "packages/advanced_drawings_toolbar/source/\u6563\u70b9\u56fe.png": "78c432bb278d8ea5c9587f23ffdd5f82", "packages/advanced_drawings_toolbar/source/\u6761\u5f62\u56fe.png": "b07f4db5b1785376f97636fc6963234e", "packages/advanced_drawings_toolbar/source/\u67f1\u5f62\u56fe.png": "d6cba37e00bf9bbeb65235897a7b90b9", "packages/advanced_drawings_toolbar/source/\u6c14\u6ce1\u56fe.png": "d6784d4c53145cce1fde17f673b76984", "packages/advanced_drawings_toolbar/source/\u70ed\u529b\u56fe.png": "566d7c6ad396084ca6b7d490868fdc2c", "packages/advanced_drawings_toolbar/source/\u76f4\u65b9\u56fe.png": "a9b01bca53610b2c03f7ea2331c91cc3", "packages/advanced_drawings_toolbar/source/\u7bb1\u7ebf\u56fe.png": "ba733c4c1a0979ab5bc838149490e915", "packages/advanced_drawings_toolbar/source/\u7ec4\u5408\u56fe.png": "c5bea73cff095ad47bd07e1e81921ab2", "packages/advanced_drawings_toolbar/source/\u96f7\u8fbe\u56fe.png": "5aa1a48424b30a157a2bca181c9be2d0", "packages/advanced_drawings_toolbar/source/\u9762\u79ef\u56fe.png": "69682cc944d7afdd5c1f2019bf6ca21c", "packages/advanced_drawings_toolbar/source/\u997c\u56fe.png": "862b6c81a5752ee7d53500ce9064c10d", "packages/advanced_drawings_toolbar/translations/qt_zh_CN.qm": "cd23a7e488d169790b204929188d78e7", "packages/advanced_drawings_toolbar/translations/qt_zh_CN.ts": "ce76ad850f047e2cea0440e4dab7c7d4", "packages/applications_toolbar/applications_toolbar.py": "1c7db3beb99333a43fcbc29f2b628117", "packages/applications_toolbar/dev_tools.py": "32396eb367e2f40a2ca41bbf1c122380", "packages/applications_toolbar/ipyinterface.py": "b52686fd36bd819d65916f6c70a135e9", "packages/applications_toolbar/ipython_console.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/applications_toolbar/main.py": "1d6fa534839be7a73ed71881a9fb166e", "packages/applications_toolbar/manage_apps.py": "88d58bae208874c4973e22165f112675", "packages/applications_toolbar/package.json": "b6e6fae7a7650f3a94327d27f4fd4f9c", "packages/applications_toolbar/process_monitor.py": "86f69fb10c7ef7ba19504b418e8851f5", "packages/applications_toolbar/README.md": "500119ec92d71af8ac97e7246e30342e", "packages/applications_toolbar/settings_apps.json": "4182a1dc3f6489bca92a7eb123a85b6c", "packages/applications_toolbar/apps/cftool/algorithm.py": "253d3d8c4d7fcebe0018d65511e03ae9", "packages/applications_toolbar/apps/cftool/GUI_QT.py": "81a8a4dbbb41799c196129cb7b98247c", "packages/applications_toolbar/apps/cftool/main.py": "386bb5ebf2b5d8ad609ecd2be95bb607", "packages/applications_toolbar/apps/cftool/package.json": "ef90cc084c17151e6db5f1a4bb170897", "packages/applications_toolbar/apps/cftool/python.jpg": "81740ef62fda1acb2905ad8dedb96385", "packages/applications_toolbar/apps/cftool/README.md": "82a32691c1f8296f6fe73faebc1ddf89", "packages/applications_toolbar/apps/cftool/regexvalidifyer.py": "f7b28de6b75b89f19078a3e2e10d9818", "packages/applications_toolbar/apps/cftool/test1.py": "d8b7b73613a84a9044721403a1e8042a", "packages/applications_toolbar/apps/cftool/test_file.py": "dc6b5c9b40404d62209e36daf7b4819e", "packages/applications_toolbar/apps/cftool/src/cftool.png": "04975fa7979a56ac4dabea313cc7c004", "packages/applications_toolbar/apps/cftool/src/\u66f2\u7ebf\u62df\u5408.png": "d7ccf65e6d59dfd63507f1432454a943", "packages/applications_toolbar/apps/demo_app/default.png": "b58b2daf9a2a44726b678f235aed41e6", "packages/applications_toolbar/apps/demo_app/demo_app.py": "931bb2658ec5554a4f1cbb7424736f5a", "packages/applications_toolbar/apps/demo_app/main.py": "63ffc303fd56ba7d882fe108cd44efeb", "packages/applications_toolbar/apps/demo_app/package.json": "24bd9031b9afde16470332c19333c0bb", "packages/applications_toolbar/apps/demo_app/README.md": "ddb8b30770b3972dee69051c99026d1f", "packages/applications_toolbar/apps/demo_app/settings.json": "e4385e0a6554dfe40b3696f328f5edd1", "packages/applications_toolbar/apps/flowchart/flowchart.png": "e419f05974065c85c4c7b56604ce920a", "packages/applications_toolbar/apps/flowchart/main.py": "3288fef7caa83b16745351634f1c5e13", "packages/applications_toolbar/apps/flowchart/package.json": "bfe315ff8f31d2c4699a560608a5b973", "packages/applications_toolbar/apps/flowchart/python.jpg": "81740ef62fda1acb2905ad8dedb96385", "packages/applications_toolbar/apps/flowchart/README.md": "9b53d15d3e33ddba31de6b5f4e684c1b", "packages/applications_toolbar/apps/flowchart/examples/drop_duplicated.pmfc": "be5c44289f838b15d93c1b227b657091", "packages/applications_toolbar/apps/flowchart/examples/read_all_csv_files.pmfc": "4b1e9884b118c9dfa1e855f55178fad5", "packages/applications_toolbar/apps/flowchart/plugin_nodes/nodes.py": "92702a3afc444e8ce17b53314722eff6", "packages/applications_toolbar/apps/flowchart/plugin_nodes/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/applications_toolbar/apps/flowchart/resources/flowchartwindow.jpg": "d91093a77c5746982f7575081674e05b", "packages/applications_toolbar/apps/flowchart/resources/nodeports.jpg": "fddd899d2618662bb2b61d7e09680ad2", "packages/applications_toolbar/apps/flowchart/test_files/test_drop_duplicated.csv": "0574869eff0b86a260260b72cf53bb4a", "packages/applications_toolbar/apps/ligralpy/chart_server.py": "b3dc9a23925b30a73ac974f7a8123689", "packages/applications_toolbar/apps/ligralpy/Data.csv": "5fdba74629c5a0b1a5a6be20a3a19688", "packages/applications_toolbar/apps/ligralpy/flowchart.png": "e419f05974065c85c4c7b56604ce920a", "packages/applications_toolbar/apps/ligralpy/img.png": "8408ad879fe242ed82c30adbc3694d46", "packages/applications_toolbar/apps/ligralpy/main.py": "59a370745781b14b673f52ef392a5108", "packages/applications_toolbar/apps/ligralpy/package.json": "9e7eeab6e86f652142ae125bec65e71d", "packages/applications_toolbar/apps/ligralpy/python.jpg": "81740ef62fda1acb2905ad8dedb96385", "packages/applications_toolbar/apps/ligralpy/README.md": "9b53d15d3e33ddba31de6b5f4e684c1b", "packages/applications_toolbar/apps/ligralpy/examples/flowchart_stat_demo.json": "2d91e5c4fff5494f14dbb88a8b5f5031", "packages/applications_toolbar/apps/ligralpy/examples/second_ordered.lig.json": "86a67bc8b6402059ff6f15ef83722705", "packages/applications_toolbar/apps/ligralpy/nodes/nodes.py": "a08108573e2ace0e04dc0551da7653a6", "packages/applications_toolbar/apps/ligralpy/nodes/simulation.py": "043e5c86cf3c147d23cda2a2cf589395", "packages/applications_toolbar/apps/ligralpy/nodes/__init__.py": "06133052eded73b0d4be7b4719d5a3fc", "packages/applications_toolbar/apps/ligralpy/nodes/tools/gen_classes.py": "976b6416996c3bd2ce0c811b68175d60", "packages/applications_toolbar/apps/ligralpy/resources/flowchartwindow.jpg": "d91093a77c5746982f7575081674e05b", "packages/applications_toolbar/apps/ligralpy/resources/nodeports.jpg": "fddd899d2618662bb2b61d7e09680ad2", "packages/applications_toolbar/source/appstore.svg": "e2a48d1643f244e957ce9dba9e55a72f", "packages/applications_toolbar/source/app_main.py": "d8797ef1c2c1506b14b24a3602d4f929", "packages/applications_toolbar/source/background.png": "2a3f6737ddbb2a0314cdf2f640b0e929", "packages/applications_toolbar/source/default.png": "b58b2daf9a2a44726b678f235aed41e6", "packages/applications_toolbar/source/down.svg": "fe6b296aa4020dda5c5a870bb7596235", "packages/applications_toolbar/source/install.svg": "699200cfd59967ff740f1e3076795f0f", "packages/applications_toolbar/source/lightening.png": "844cc073cae34c193f3f39335146c955", "packages/applications_toolbar/source/main.py": "f7c06af5e03add541ee260a6224af05a", "packages/applications_toolbar/source/package.svg": "db8542fb863117d5d74f7a6676ab419e", "packages/applications_toolbar/source/qt-logo.png": "9a2446007bbc869ee1ef00479fc73872", "packages/applications_toolbar/source/run.png": "fcf7e23f264801b79df841dabdab9417", "packages/applications_toolbar/source/run.py": "931bb2658ec5554a4f1cbb7424736f5a", "packages/applications_toolbar/source/settings.png": "b9f8edfca0a36df8f3d445f61683511d", "packages/applications_toolbar/translations/qt_zh_CN.qm": "e613b5296764ff24e04450243de3e383", "packages/applications_toolbar/translations/qt_zh_CN.ts": "242fdca10f7405da10eae48f2244e13f", "packages/applications_toolbar/ui/app_designer.py": "b90e5fb6a0430ceb7bb74533ca4d198a", "packages/applications_toolbar/ui/app_designer.ui": "363e995f6861e7817a27e577d236233c", "packages/code_editor/debugger.py": "a19f564dddb9df36cfd14779dfea943c", "packages/code_editor/main.py": "0b3fc0dfb3c49a9dbe74a02f881b36a7", "packages/code_editor/package.json": "f529dee72a413f5d0d92b62ffeaad59b", "packages/code_editor/python.jpg": "81740ef62fda1acb2905ad8dedb96385", "packages/code_editor/README.md": "2918b5e59be436e278047961adb8afeb", "packages/code_editor/toolbar.py": "26ea5eb990d08893ee848dd86768aaba", "packages/code_editor/codeeditor/abstracteditor.py": "b1556e7c0b64c122e8dc6b295b9d9cc9", "packages/code_editor/codeeditor/autocomplete.py": "85443f0a05636004d73019442ca2d628", "packages/code_editor/codeeditor/flake8_trans.json": "745c056024e394cd874a60a0569de1b3", "packages/code_editor/codeeditor/infer.py": "7a9c311fd78ce3afe093bced75ff8fb0", "packages/code_editor/codeeditor/markdowneditor.py": "710b73d04dbf40dbda4c8a9516f1cdb9", "packages/code_editor/codeeditor/pythoneditor.py": "6eb09191f31b0d9a3752e13e3cef6098", "packages/code_editor/codeeditor/syntaxana.py": "b41485288a49c5e9dff3babfdbaa3a3b", "packages/code_editor/codeeditor/tabwidget.py": "e8c6177923b12a377116f65e2045fac3", "packages/code_editor/codeeditor/__init__.py": "de948222f3aba39724a6583a7c1d818f", "packages/code_editor/codeeditor/config/.flake8": "5706c8ed878411a63a6dd9827224e010", "packages/code_editor/codeeditor/config/.style.yapf": "0d898fa79efa8db79c72c151658d26aa", "packages/code_editor/codeeditor/config/.style.yapf.zh": "3f98dd47e54c916665839ab5fe9c5b8c", "packages/code_editor/codeeditor/errors_translation/translate.py": "750d35adf1c925d4e54c5afc6bd4471b", "packages/code_editor/codeeditor/errors_translation/translations.txt": "ec6b11469cf13cc49f53f144a3048952", "packages/code_editor/codeeditor/qtpyeditor/find_gotoline.py": "98a1a7fd1e0b447a55ce0941984ef809", "packages/code_editor/codeeditor/qtpyeditor/linenumber.py": "0dbc3249db663a0d338ec4630a8b857a", "packages/code_editor/codeeditor/qtpyeditor/syntaxana.py": "ccfed05306fc8e31513e4138fc6cb197", "packages/code_editor/codeeditor/qtpyeditor/__init__.py": "750c4f8fd3d0708b965ba5b6349201df", "packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py": "1f47f04069d87c1e49eadcac64e0c546", "packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py": "1bd34b84907bd2cb9602b28ff87aae23", "packages/code_editor/codeeditor/qtpyeditor/codeedit/__init__.py": "49be4addcaeff7654aa0d9770649c359", "packages/code_editor/codeeditor/qtpyeditor/codeeditor/abstracteditor.py": "ae34418002a69d7872496eb5007842c0", "packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py": "81adc6aff2de98db56eb88c22fbea69f", "packages/code_editor/codeeditor/qtpyeditor/codeeditor/pythoneditor.py": "5458fc8ab2d05929fa3a9b6790125554", "packages/code_editor/codeeditor/qtpyeditor/codeeditor/__init__.py": "2d1f567a03ae3b14864c5807c2ecfd90", "packages/code_editor/codeeditor/qtpyeditor/highlighters/python.py": "30f29dea3c0261d114038d049dcb8b9b", "packages/code_editor/codeeditor/qtpyeditor/highlighters/__init__.py": "274aab5f8155ea49df8cc33689fe427a", "packages/code_editor/codeeditor/qtpyeditor/icons/breakpoint.svg": "5225dadbedc9f7bda4bb94fecc75ad53", "packages/code_editor/codeeditor/qtpyeditor/icons/copy.svg": "1c800d6346207003b4d7dc70a11e433f", "packages/code_editor/codeeditor/qtpyeditor/icons/debug.svg": "b931caeb19e4cc175cec44fd8af719f5", "packages/code_editor/codeeditor/qtpyeditor/icons/format.svg": "dccd4600d9450bfe4deb9b244c41e85f", "packages/code_editor/codeeditor/qtpyeditor/icons/help.svg": "edfdb212fdcae3ece7e7c58950a35645", "packages/code_editor/codeeditor/qtpyeditor/icons/python.svg": "d17b75c2256afc9b2beb64afeafbc3e0", "packages/code_editor/codeeditor/qtpyeditor/icons/run.svg": "e00087c6bd323d47d4f70a32a25b5667", "packages/code_editor/codeeditor/qtpyeditor/icons/save.svg": "750627b83e976365fb2d80529cb84a2d", "packages/code_editor/codeeditor/qtpyeditor/icons/spate.svg": "13ab9e2e9d07edf5f7c9cd73560141a7", "packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/class.png": "c64837c614c385630f360831492d8eb9", "packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/function.png": "7ee29ba9761700d9c9312f95ab6c799e", "packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/instance.png": "7ccdeda9723de6cbf234c333abe1d244", "packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/keyword.png": "babe443bc1400af18df2d5a6cc11cec4", "packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/module.png": "86eaece57974e3d10eb6e175d650abf6", "packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/param.png": "74e736a865157eb31e015effb34df17b", "packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/path.png": "7e01e98822f89b5e7bb64c2ead585aa0", "packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/property.png": "3f6aebb5a4862a8adca6d82dc724ab5e", "packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/statement.png": "d16ab05b46ff0f6ed3b6421a2a66dab0", "packages/code_editor/codeeditor/qtpyeditor/translations/qt_zh_CN.qm": "2c896338c3b1b75508a842cf534d7cbf", "packages/code_editor/codeeditor/qtpyeditor/translations/qt_zh_CN.ts": "5382de30cc1e09a23252d66ca93b27db", "packages/code_editor/codeeditor/qtpyeditor/ui/findinpath.py": "14fa54fbcf8cb34804f6ef075c4267e6", "packages/code_editor/codeeditor/qtpyeditor/ui/formeditor.py": "1da95795442e26ab7bb522dc811aae50", "packages/code_editor/codeeditor/qtpyeditor/ui/gotoline.py": "609b6fb7d45a04b898878cab26938277", "packages/code_editor/codeeditor/qtpyeditor/ui/ui_formeditor.py": "2d559bafc1b0d8819e62eea2f875de8d", "packages/code_editor/codeeditor/qtpyeditor/ui/ui_gotoline.py": "1827eee50b32b313d397027c46dc020b", "packages/code_editor/codeeditor/qtpyeditor/ui/__init__.py": "de948222f3aba39724a6583a7c1d818f", "packages/code_editor/codeeditor/qtpyeditor/Utilities/autocomp.py": "7adde398458535177d586db7da6eb151", "packages/code_editor/codeeditor/qtpyeditor/Utilities/__init__.py": "d7a23951afba6034e252ea600119c242", "packages/code_editor/codeeditor/simpleeditor/lexer": "d41d8cd98f00b204e9800998ecf8427e", "packages/code_editor/codeeditor/simpleeditor/__init__.py": "fbc8e0505acbb7662554aa0ebf010dd8", "packages/code_editor/codeeditor/tests/get_flake8_output.py": "d6a5545ac0b49483aed41b5059276d1e", "packages/code_editor/codeeditor/tests/get_yapf_output.py": "f6a13da53a21f87d7f5d9fbd0e9a22fa", "packages/code_editor/codeeditor/tests/test_file.py": "4ed78c29dfa08ff34bff564bd39545c4", "packages/code_editor/codeeditor/tests/theme_xml_json.py": "86f8b53c91b4d974acafc78c7a9e399c", "packages/code_editor/codeeditor/themes/Material-Dark.xml": "32f09404512dea8a892ef0095e659ae3", "packages/code_editor/codeeditor/themes/Obsidian PyCs.xml": "934cfd0236acebfaa37bc76516e4cc12", "packages/code_editor/codeeditor/themes/tomorrow.xml": "4f226a7b073ea417d6eca69bdf0e999a", "packages/code_editor/codeeditor/themes/tomorrow_night.xml": "5b7770abad7ab117727e91351140724f", "packages/code_editor/codeeditor/themes/tomorrow_night_bright.xml": "ba3f13a083f3b0aa8f8332e90b4fbb86", "packages/code_editor/codeeditor/tools/eric6_api.py": "72d2e6ee453be1f17e6c0382eb99d344", "packages/code_editor/codeeditor/tools/__init__.py": "9aa13d467aa7ca6ca94b283dea7e5c22", "packages/code_editor/codeeditor/tools/DocumentationTools/APIGenerator.py": "a725a1b2ffcbc8fc98f9e69b252f73ae", "packages/code_editor/codeeditor/tools/DocumentationTools/__init__.py": "f5dba5378fa551d11f098318de65a4a1", "packages/code_editor/codeeditor/tools/QScintilla/Editor.py": "3e5f9b7d8c00734ed27c85bd1de63906", "packages/code_editor/codeeditor/tools/QScintilla/__init__.py": "e58d1155053d6b3763c2ed9f92ca9e57", "packages/code_editor/codeeditor/tools/Utilities/ModuleParser.py": "be326e07e15841196ec7d752417cc6e2", "packages/code_editor/codeeditor/tools/Utilities/__init__.py": "65ffafe4bc9cd6a17832f441036cdb63", "packages/code_editor/codeeditor/ui/formeditor.py": "1da95795442e26ab7bb522dc811aae50", "packages/code_editor/codeeditor/ui/gotoline.py": "2ea843566488cd151da2f6cab335625f", "packages/code_editor/icons/breakpoint.svg": "5225dadbedc9f7bda4bb94fecc75ad53", "packages/code_editor/icons/copy.svg": "1c800d6346207003b4d7dc70a11e433f", "packages/code_editor/icons/debug.svg": "b931caeb19e4cc175cec44fd8af719f5", "packages/code_editor/icons/format.svg": "dccd4600d9450bfe4deb9b244c41e85f", "packages/code_editor/icons/help.svg": "edfdb212fdcae3ece7e7c58950a35645", "packages/code_editor/icons/python.svg": "d17b75c2256afc9b2beb64afeafbc3e0", "packages/code_editor/icons/run.svg": "e00087c6bd323d47d4f70a32a25b5667", "packages/code_editor/icons/save.svg": "750627b83e976365fb2d80529cb84a2d", "packages/code_editor/icons/spate.svg": "13ab9e2e9d07edf5f7c9cd73560141a7", "packages/code_editor/source/lightening.png": "844cc073cae34c193f3f39335146c955", "packages/code_editor/translations/qt_zh_CN.qm": "8e43fef9f53d43e8b693fc52984742d6", "packages/code_editor/translations/qt_zh_CN.ts": "20d10e3f79c729e1c90cbc88858cfd36", "packages/dataio/accountutil.py": "98a4758a6a1b11f7ee17f08e8b6c03d8", "packages/dataio/database_sample.py": "40b1c25397f6ddea9fb38029ee97eaea", "packages/dataio/dataImportModel.py": "88792dc540a80f0a5b22a7de6f3f1b12", "packages/dataio/dbimport.py": "6e4e64e8277a17c0fd21748ee7612b9a", "packages/dataio/dbindexing.py": "8d8d88e3a62cd935519aa4716218fac9", "packages/dataio/export.py": "ba01392157f6058a0d3a2a6f65a82b46", "packages/dataio/exportutils.py": "71a15223db0b6f6ef4bbe7e5ad31a4b6", "packages/dataio/importutils.py": "138d86b798b92d45639791ef57285981", "packages/dataio/main.py": "194549d322dbcea58329cff2cd5d7104", "packages/dataio/package.json": "a857dcfa80f7bbe16b29d447684fede6", "packages/dataio/python.jpg": "81740ef62fda1acb2905ad8dedb96385", "packages/dataio/README.md": "2918b5e59be436e278047961adb8afeb", "packages/dataio/sample.py": "346de2707ffbf2cd036b19b6911be5ff", "packages/dataio/settings.json": "d41d8cd98f00b204e9800998ecf8427e", "packages/dataio/dataUI/data_import_csv.py": "6b0ffb99fdb1d3bc68a61ca6a9169063", "packages/dataio/dataUI/data_import_csv.ui": "47c21370141a61960861b65836e7287b", "packages/dataio/dataUI/data_import_excel.py": "17b37971c44f89cdc5cef09c4ae742ef", "packages/dataio/dataUI/data_import_excel.ui": "2038dabc9e1caea8e1328b64fb10dc89", "packages/dataio/dataUI/data_import_matlab.py": "eccad7dc8726499702e75adcb6eb64d4", "packages/dataio/dataUI/data_import_matlab.ui": "4ae19c223a9d60139a73f2b1c553c64a", "packages/dataio/dataUI/data_import_model.py": "6732462f2b28d1228e6b6429d8f820cb", "packages/dataio/dataUI/data_import_model.ui": "6c61ace6854928adfb4c6ea8eff19fb5", "packages/dataio/dataUI/data_import_mysql.py": "10024040b895e1f7a0761cf41ed569aa", "packages/dataio/dataUI/data_import_mysql.ui": "bab2eaf2057d9175638198f14fb5f599", "packages/dataio/dataUI/data_import_oracle.py": "8068996f5d6395e95e878c341273ceb2", "packages/dataio/dataUI/data_import_oracle.ui": "c7d90bb98b8d4c0d5401eca7bcabe7ec", "packages/dataio/dataUI/data_import_postgresql.py": "c5404dc63e987cd2ed07d2b2424d5dac", "packages/dataio/dataUI/data_import_postgresql.ui": "b9a515989b036b7466bc2edd325b6eb1", "packages/dataio/dataUI/data_import_sas.py": "428fd62424d9e21c3d5bdc67ad2474a2", "packages/dataio/dataUI/data_import_sas.ui": "1f8667b3667d2baba53533b938b1a6a9", "packages/dataio/dataUI/data_import_spss.py": "8a1154c9315758ad9fa8bc96bfa1a367", "packages/dataio/dataUI/data_import_spss.ui": "35e2eb820c7e69bef4f06bbce6344c19", "packages/dataio/dataUI/data_import_stata.py": "102a879258cfbb70afc5815684e53b93", "packages/dataio/dataUI/data_import_stata.ui": "fd01ec0806b50f0dbbe7df3ae5578bcb", "packages/dataio/dataUI/data_import_text.py": "347f0183bb4055c43993f5ed84ae3d6c", "packages/dataio/dataUI/data_import_text.ui": "c8cc49a3ee1b2c4209c5c9ffe92fcfd8", "packages/dataio/dataUI/display.png": "38a696b77d8e89b1f24e3077184f3588", "packages/dataio/dataUI/hide.png": "80009586668a32b1a0bcdc5fb1854ac4", "packages/dataio/dataUI/password.py": "c174715c922460e682382cc1a4c9076f", "packages/dataio/dataUI/__init__.py": "cddad8a43ae85331e7fc60b6c4783a14", "packages/document_server/extension_demo.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/document_server/index.rst": "de08e1d943a610a1d4b61de487fc9606", "packages/document_server/main.py": "8e52c195bd0de7649b208fe9431fac90", "packages/document_server/package.json": "d552267ae466f74d360aa2dde9707999", "packages/document_server/settings.json": "ac084b924952c4c88e397f5c1f64c893", "packages/document_server/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/document_server/docserver/renderer.py": "0d2b8fa90287c98d2b2ea47893d902fd", "packages/document_server/docserver/server.py": "b525026a556b339079d30c4294e7fd60", "packages/document_server/docserver/__init__.py": "f64b6b6963038b02a0fafec97cb826da", "packages/document_server/docserver/static/css/main.css": "514ba05b769e4a20a328f77267aeb3a0", "packages/document_server/docserver/static/css/main.css.map": "db360aa1e642da89de3240fed50e4432", "packages/document_server/docserver/static/css/main.sass": "7b0d06ed553c19887a7dcad5bb97e954", "packages/document_server/docserver/static/js/main.js": "52a8a678ac589478f5b97e88066109c6", "packages/document_server/docserver/static/mathjax/tex-mml-svg.js": "2ac6956d3f16edb119f3b5686ef933d5", "packages/document_server/docserver/templates/content.html": "7420679eb81eea0ca7b9e98972b49160", "packages/document_server/translations/qt_zh_CN.ts": "023045a080a9a26547219f054c272d80", "packages/document_server/translations/zh_CN.ts": "023045a080a9a26547219f054c272d80", "packages/drawings_toolbar/group_chart.py": "846a72c9e0303f8ef4b5c6b809f07db7", "packages/drawings_toolbar/ipython_console.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/drawings_toolbar/main.py": "6d8e3f8e511fa3ec1186e59c68487b69", "packages/drawings_toolbar/map_var.json": "91324fe1e6b292ebc2d548f3ae7d49b7", "packages/drawings_toolbar/map_var.py": "46e4e76e96cb5691754e4b63a38b67d9", "packages/drawings_toolbar/package.json": "95e01099cdd1f73595c179112383e45a", "packages/drawings_toolbar/radar_chart.py": "67ea13759ea77d67006df488c9fde4e0", "packages/drawings_toolbar/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/drawings_toolbar/fastui/base.py": "5aabafefad5433391c67bf5f5749141f", "packages/drawings_toolbar/fastui/draw_boxplot.py": "54cc901679b29dc27fbaa8c12a458daf", "packages/drawings_toolbar/fastui/draw_hist.py": "97f5fd773217c53c26c9d44c6905f8cf", "packages/drawings_toolbar/fastui/functions.py": "2756f0633851f7605644c78de2f9e7b6", "packages/drawings_toolbar/fastui/plot.py": "c52ba2a8e3dc27b6397480b316f3f745", "packages/drawings_toolbar/fastui/__init__.py": "b3f689f89235e0fd970023fb1910ebaa", "packages/drawings_toolbar/fastui/helps/boxplot.md": "c28b070128b33660fd657c82d7bd2d54", "packages/drawings_toolbar/fastui/helps/markers.md": "35a158b4cb160219a0d5f4c89025f50c", "packages/drawings_toolbar/fastui/helps/plot.md": "9a819ab6bdf462d779ff742d77e49526", "packages/drawings_toolbar/pmmap/china/all/01-beijing.npy": "05fc6e35605340ef6e4d62af9770ef7a", "packages/drawings_toolbar/pmmap/china/all/02-shanghai.npy": "dbd37714da88edf44df7a2642ce2c14f", "packages/drawings_toolbar/pmmap/china/all/03-tianjin.npy": "e767b1d0810acfd174d4b52586441de1", "packages/drawings_toolbar/pmmap/china/all/04-chongqing.npy": "1980444de2496af7ae7b11cae9372d2c", "packages/drawings_toolbar/pmmap/china/all/05-heilongjiang.npy": "f369a349db31ac0636e625737802922e", "packages/drawings_toolbar/pmmap/china/all/06-neimeng.npy": "0366d9f95bfe2e0768a6491f8f5e27cc", "packages/drawings_toolbar/pmmap/china/all/07-xinjiang.npy": "201b3ff97375ae9ba6e9fc9c7b0eed1e", "packages/drawings_toolbar/pmmap/china/all/08-jilin.npy": "0ed85b6d84b877cf07154e1b54db0bdd", "packages/drawings_toolbar/pmmap/china/all/09-gansu.npy": "08c6e4437a0fc5d19d6c655a14a7df2e", "packages/drawings_toolbar/pmmap/china/all/10-liaoning.npy": "542d598f5c39b363738b0b753d8318d1", "packages/drawings_toolbar/pmmap/china/all/11-hebei.npy": "5a7ce525859caabaffad0fe1c11cb26f", "packages/drawings_toolbar/pmmap/china/all/12-shanxi.npy": "5ebb5347431393d599f5f0d36a31dd07", "packages/drawings_toolbar/pmmap/china/all/13-shan3xi.npy": "71944b8eea6caa0b7e4138063d6e8945", "packages/drawings_toolbar/pmmap/china/all/14-ningxia.npy": "dc0660ac4216cb4e928fb08dba5e36fb", "packages/drawings_toolbar/pmmap/china/all/15-qinghai.npy": "46f4e9bf9a47734d0cfbef0916e6cec1", "packages/drawings_toolbar/pmmap/china/all/16-shandong.npy": "b3eee97751e252f1c729b20cde81220c", "packages/drawings_toolbar/pmmap/china/all/17-henan.npy": "530d73e54901bf816e61c86bafb97219", "packages/drawings_toolbar/pmmap/china/all/18-xizang.npy": "83c873032100d735d2a3c1becb4e4ee5", "packages/drawings_toolbar/pmmap/china/all/19-jiangsu.npy": "33051765300de0a9d003ea91c3846cc6", "packages/drawings_toolbar/pmmap/china/all/20-anhui.npy": "d80f40e27c3a60188547d058c827eae0", "packages/drawings_toolbar/pmmap/china/all/21-sichuan.npy": "115e4b1abbc6df1f862095e94cacb8b5", "packages/drawings_toolbar/pmmap/china/all/22-hubei.npy": "065b61ce8a2728be70d0e726990e42c2", "packages/drawings_toolbar/pmmap/china/all/23-zhejiang.npy": "6910df476893b4e5a84689094203b724", "packages/drawings_toolbar/pmmap/china/all/24-jiangxi.npy": "2d4e166cd8d9371a1c6d01848ece3301", "packages/drawings_toolbar/pmmap/china/all/25-hunan.npy": "ddbce0334a1f79bd77f7b0ae582dd28e", "packages/drawings_toolbar/pmmap/china/all/26-guizhou.npy": "4d23f4f330203b61c6f8cca0ac764819", "packages/drawings_toolbar/pmmap/china/all/27-yunnan.npy": "1d1c9378c8427667dbf0b8948c36b484", "packages/drawings_toolbar/pmmap/china/all/28-fujian.npy": "77270ff92f0d67f0123672527b70772a", "packages/drawings_toolbar/pmmap/china/all/29-guangxi.npy": "3862d0becc054fe754ae71c111c583ad", "packages/drawings_toolbar/pmmap/china/all/30-guangdong.npy": "7d7f1392cc01a22d4503354ce7c729b1", "packages/drawings_toolbar/pmmap/china/all/31-taiwan.npy": "2f398da02605db2be30751dedb92ea90", "packages/drawings_toolbar/pmmap/china/all/32-xianggang.npy": "b3cf9d637c7d87e82a86174f99be74da", "packages/drawings_toolbar/pmmap/china/all/33-aomen.npy": "68e3c682aef3e25cf873878a19df5361", "packages/drawings_toolbar/pmmap/china/all/34-hainan.npy": "a67e27cc4f68d566f6977580d95b006c", "packages/drawings_toolbar/pmmap/china/all/39-jiuduan.npy": "35fbdcbb72d2c22caab9b1bfd2eba401", "packages/drawings_toolbar/pmmap/china/small/30-guangdong.npy": "7d7f1392cc01a22d4503354ce7c729b1", "packages/drawings_toolbar/pmmap/china/small/31-taiwan.npy": "2f398da02605db2be30751dedb92ea90", "packages/drawings_toolbar/pmmap/china/small/32-xianggang.npy": "b3cf9d637c7d87e82a86174f99be74da", "packages/drawings_toolbar/pmmap/china/small/33-aomen.npy": "68e3c682aef3e25cf873878a19df5361", "packages/drawings_toolbar/pmmap/china/small/34-hainan.npy": "a67e27cc4f68d566f6977580d95b006c", "packages/drawings_toolbar/pmmap/china/small/39-jiuduan.npy": "35fbdcbb72d2c22caab9b1bfd2eba401", "packages/drawings_toolbar/source/down.svg": "fe6b296aa4020dda5c5a870bb7596235", "packages/drawings_toolbar/source/erase.png": "68beac11764738b4fd52d367e5ad8223", "packages/drawings_toolbar/source/grid.png": "a39688f7d385bf86ba28eaf9d63a286d", "packages/drawings_toolbar/source/label.png": "2289b06fad4b39ea868450cdfbdc5eea", "packages/drawings_toolbar/source/monitor.png": "7272e1f62681d4200a2d778ec3df27e5", "packages/drawings_toolbar/source/plot.svg": "05fd271af6c134f6872c80c489064d60", "packages/drawings_toolbar/source/split.png": "d3cdd812358fd82f19500d8ac1bbc3be", "packages/drawings_toolbar/source/ticks.png": "2165ab858f8de60f5132e861740f7699", "packages/drawings_toolbar/source/\u5730\u56fe.png": "eca50b3207a973738145d1cf12ca0c34", "packages/drawings_toolbar/source/\u6298\u7ebf\u56fe.png": "1bde0eca8cfa880452accf56ca925866", "packages/drawings_toolbar/source/\u6563\u70b9\u56fe.png": "78c432bb278d8ea5c9587f23ffdd5f82", "packages/drawings_toolbar/source/\u6761\u5f62\u56fe.png": "b07f4db5b1785376f97636fc6963234e", "packages/drawings_toolbar/source/\u67f1\u5f62\u56fe.png": "d6cba37e00bf9bbeb65235897a7b90b9", "packages/drawings_toolbar/source/\u6c14\u6ce1\u56fe.png": "d6784d4c53145cce1fde17f673b76984", "packages/drawings_toolbar/source/\u70ed\u529b\u56fe.png": "566d7c6ad396084ca6b7d490868fdc2c", "packages/drawings_toolbar/source/\u76f4\u65b9\u56fe.png": "87113fa7353e0342248a7c953949079e", "packages/drawings_toolbar/source/\u7bb1\u7ebf\u56fe.png": "ba733c4c1a0979ab5bc838149490e915", "packages/drawings_toolbar/source/\u7ec4\u5408\u56fe.png": "c5bea73cff095ad47bd07e1e81921ab2", "packages/drawings_toolbar/source/\u96f7\u8fbe\u56fe.png": "5aa1a48424b30a157a2bca181c9be2d0", "packages/drawings_toolbar/source/\u9762\u79ef\u56fe.png": "69682cc944d7afdd5c1f2019bf6ca21c", "packages/drawings_toolbar/source/\u997c\u56fe.png": "862b6c81a5752ee7d53500ce9064c10d", "packages/drawings_toolbar/translations/qt_zh_CN.qm": "cd23a7e488d169790b204929188d78e7", "packages/drawings_toolbar/translations/qt_zh_CN.ts": "ce76ad850f047e2cea0440e4dab7c7d4", "packages/embedded_browser/extension_demo.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/embedded_browser/index.rst": "f1825ba1705fc05e593342e0eb4d2800", "packages/embedded_browser/main.py": "23ad2b340856b4ee181f560c6234655c", "packages/embedded_browser/package.json": "662baeb4c74d3c20f8a4604a7a9922df", "packages/embedded_browser/settings.json": "ac084b924952c4c88e397f5c1f64c893", "packages/embedded_browser/webbrowser.py": "d9038ba9457b984e37a4ae1e19ab636a", "packages/embedded_browser/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/embedded_browser/translations/qt_zh_CN.ts": "023045a080a9a26547219f054c272d80", "packages/embedded_browser/translations/zh_CN.ts": "023045a080a9a26547219f054c272d80", "packages/extension_demo/extension_demo.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/extension_demo/main.py": "b776c52a5c366b88629c1c12a0c4da57", "packages/extension_demo/package.json": "fe0c68ec838785d4f8ae971ce415f6db", "packages/extension_demo/settings.json": "ac084b924952c4c88e397f5c1f64c893", "packages/extension_demo/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/extension_demo/translations/qt_zh_CN.ts": "023045a080a9a26547219f054c272d80", "packages/extension_demo/translations/zh_CN.ts": "023045a080a9a26547219f054c272d80", "packages/file_tree/extension_demo.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/file_tree/file_tree.py": "b65b3a68d28d55db5bfc3bfb1adecee2", "packages/file_tree/main.py": "210493015e96aecac98bba55bd57674b", "packages/file_tree/package.json": "0e08e253fce09755a4d9758cb55b3900", "packages/file_tree/settings.json": "5502d6d53ce7276a7cdaa1ef54315efb", "packages/file_tree/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/file_tree/src/up.svg": "cef12db3d43301347f3d556eb9499a6c", "packages/file_tree/translations/qt_zh_CN.qm": "76f4ace00b4ef9e66503cb39d5c9ab5d", "packages/file_tree/translations/qt_zh_CN.ts": "eefaa8ed0d5717aab5253c74ea7d6a3a", "packages/graph_agg/extension_demo.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/graph_agg/graph_agg.py": "7ebd645f566d7412c3af7de2865d2896", "packages/graph_agg/graph_agg_ui.py": "61edf67aebb1d89f92d604a8d22c1200", "packages/graph_agg/graph_agg_ui.ui": "45c54e740ed679ebcdc66594709eaf9f", "packages/graph_agg/main.py": "3399d8c6899a36a92b1910d55d05ea96", "packages/graph_agg/package.json": "b2bfcc74063422e491d0c3df279bbc38", "packages/graph_agg/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/ipython_console/commandparser.py": "8df507247c3112a5a164a961f8b154b5", "packages/ipython_console/index.rst": "de4df58d8cb7a27038ea01f08d3e74c9", "packages/ipython_console/initialize.py": "f75a73ebb2b6af4e198d47cb326ac8c6", "packages/ipython_console/ipythonqtconsole.py": "715c9c195c300400bd24375a2c0f2cac", "packages/ipython_console/ipython_console.jpg": "e2c98aceacdcf9370be0234c1ae56660", "packages/ipython_console/main.py": "764879652c234804c28091c167e8506f", "packages/ipython_console/package.json": "330d1b64fd0fa236f68e4e9148d7f2ff", "packages/ipython_console/README.md": "d91f325d8113d754239f36ebf6211e2d", "packages/ipython_console/requirements_ipython_node.txt": "7e2992fb7c705a5cc8a817af37b0b890", "packages/ipython_console/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/ipython_console/translations/qt_zh_CN.qm": "d32941bf701e20d30c70b509e3421f26", "packages/ipython_console/translations/qt_zh_CN.ts": "ae95465c0df4254a53d4144fd5a90148", "packages/jupyter_notebook_support/client.py": "64707c4c5af928668dfe5624020ab381", "packages/jupyter_notebook_support/extension_demo.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/jupyter_notebook_support/ipython_data_show.py": "ba5fafcca30e9db07a1330fb143b539d", "packages/jupyter_notebook_support/main.py": "1961046decf8e842648695f64f4a951a", "packages/jupyter_notebook_support/package.json": "8f1bbf103ab237adf5e932d77304bab6", "packages/jupyter_notebook_support/route.py": "d63fb2946b16cecd4c03b63b96b417fa", "packages/jupyter_notebook_support/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/jupyter_notebook_support/scripts/pyminer_ipython_node.py": "5afb85f04995a313be86875d55896b8f", "packages/jupyter_notebook_support/tests/exec_api_test.py": "db3498a3620bc730a093106024c3d895", "packages/jupyter_notebook_support/tests/find_free_port.py": "7519a17439dd072a5f5609dccf71acff", "packages/jupyter_notebook_support/tests/find_kernel_spec.py": "5e494c2592bc747a166f3e5eb1fdb0d2", "packages/jupyter_notebook_support/tests/jupyter_client_test.py": "cd2193f167111e6f74a51ce27671b6e0", "packages/jupyter_notebook_support/tests/list_running_jupyter_servers.py": "decfc524233da897d5ae4e0896ad42bf", "packages/jupyter_notebook_support/tests/test.json": "fd0693bed89aa001426c855cf60719da", "packages/jupyter_notebook_support/tests/Untitled.ipynb": "0eca040919ec50b82d4197e13564f3f8", "packages/jupyter_notebook_support/tests/.ipynb_checkpoints/Untitled-checkpoint.ipynb": "2f6cceae1d861df553a6ebb64b6862ef", "packages/jupyter_notebook_support/translations/qt_zh_CN.qm": "910b1a4eece778fe19eb8c37d856219b", "packages/jupyter_notebook_support/translations/qt_zh_CN.ts": "a52f5f65e0ebc5ef1c4c205d8b977e5b", "packages/pmagg/extension_demo.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/pmagg/LICENSE": "e49f4652534af377a713df3d9dec60cb", "packages/pmagg/main.py": "26f2abd09259651ad17e35b24de5a097", "packages/pmagg/package.json": "3f5b1d4110de43c171ec51876448d516", "packages/pmagg/PMAgg.py": "51be8eab2ca29167621904193a7d94e0", "packages/pmagg/Readme_CN.md": "b120eff0eec50462b099a5afc44e8135", "packages/pmagg/setup.py": "c819982245570068aeff72f2c4925d3c", "packages/pmagg/unit_test.py": "6cc0c2b57eebac72cb159bdb73dae6dd", "packages/pmagg/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/pmagg/icons/annotation.png": "41abc31827d980673cb4b774d9cfc589", "packages/pmagg/icons/arrow.png": "faea79dbbe13b73b2b6b2d381d3f0b04", "packages/pmagg/icons/axis.png": "8aad7c4a25caea1eab5e00416383fe87", "packages/pmagg/icons/back.png": "7e3a69ff1b93c0d922903ca9753d9413", "packages/pmagg/icons/colorbar.png": "51edcf418f2920bcc55e703956345d22", "packages/pmagg/icons/figure.png": "6c84c49ce26f638a352654df89ee3d35", "packages/pmagg/icons/front.png": "b3675ce85ff0c87602fcee821606b376", "packages/pmagg/icons/grid.png": "f495112a5ee666819bccc792ac9ab480", "packages/pmagg/icons/home.png": "e4a7f4019b4ea9cb1f375da78a2e5adf", "packages/pmagg/icons/Icon.ico": "3fe019a16833a4b89743e829706a22a6", "packages/pmagg/icons/image.png": "9a685d94a08e6dab95ab996ac36c3019", "packages/pmagg/icons/layout.png": "113fcb65d470b3c44eca07a69883b067", "packages/pmagg/icons/legend.png": "4554646775b8caab3af239051e231530", "packages/pmagg/icons/line.png": "0ad86f368735816bb25dae9a6037d042", "packages/pmagg/icons/oval.png": "bf5e995685d6bd1758ddc56db9a706c1", "packages/pmagg/icons/pan.png": "d31d48619f6187a992f8bc51f4bbbf31", "packages/pmagg/icons/point.png": "6ea950ae1d8f16fc9d364dea468e1bf6", "packages/pmagg/icons/polygon.png": "a7d892e0a3ca8e00aefc73211cc211aa", "packages/pmagg/icons/rect.png": "9088ab4129c08c3370675a27c5af7f76", "packages/pmagg/icons/rotate.png": "b058a0e469a679d752ea393eb8b94204", "packages/pmagg/icons/save.png": "1f019d1efc880a99aa0d6c413d4b1672", "packages/pmagg/icons/setting.png": "2ca8a4d585232a124bc8f8bbaf913eec", "packages/pmagg/icons/space.png": "a63438f63e5c9a61ae377f92e4638427", "packages/pmagg/icons/style.png": "5479b633bfd95b75aca566d283f90f2d", "packages/pmagg/icons/text.png": "2d058b7289a520eb1087ea7957e85a18", "packages/pmagg/icons/X_axis.png": "81b2fbfd328ea56e03bbac9d98f54bd0", "packages/pmagg/icons/Y_axis.png": "dec07bca2db81b7a13d05cf63147042c", "packages/pmagg/icons/zoom.png": "624d6036e5af3901a672926eed0cfd74", "packages/pmagg/icons/Z_axis.png": "4ef63246be7447c74de3fc6352f56782", "packages/pmagg/langs/en_axes_control.qm": "3e31bb846d2075752f2083796bbab081", "packages/pmagg/langs/en_axes_control.ts": "e3f2a95ffc1ee822600420487fc64497", "packages/pmagg/langs/en_pmagg_ui.qm": "0d72b1e6a3868f6a9d7f4353dc49da57", "packages/pmagg/langs/en_pmagg_ui.ts": "c40ed0942c1fc822cbfdac7d51728e15", "packages/pmagg/langs/zh_CN_axes_control.qm": "43c4b723bf698383102abf57ae7ff375", "packages/pmagg/langs/zh_CN_axes_control.ts": "b69e3a2583aab4d47e9042151ddd4431", "packages/pmagg/langs/zh_CN_pmagg_ui.qm": "bcebcf42735c6849bdecbb77451021dd", "packages/pmagg/langs/zh_CN_pmagg_ui.ts": "a924984618a9687fe9bed9cd1ab3e8bd", "packages/pmagg/pictures/pmagg_api.mmd": "3c46f4d87fb3706b4b1bd70b7e442a5d", "packages/pmagg/pictures/pmagg_api.png": "822f49430932514591eda54acfc10a1f", "packages/pmagg/pictures/pmagg_show.png": "5dc944f02aa22177c5c24ab1f71a391d", "packages/pmagg/translations/qt_zh_CN.ts": "023045a080a9a26547219f054c272d80", "packages/pmagg/translations/zh_CN.ts": "023045a080a9a26547219f054c272d80", "packages/pmagg/ui/arrow_setting.py": "d8e8f2f7101729c6ced2cd4f4dd0ee6d", "packages/pmagg/ui/axes_control.py": "b232825bef4aa017e9bbf92b57640f49", "packages/pmagg/ui/axis_edit.py": "4875d36d29871c4d87c05108117d9f9f", "packages/pmagg/ui/axis_edit.ui": "f7b92a8a5d9b4ca769bf42ebdb34577a", "packages/pmagg/ui/axis_edit_manager.py": "e2245787fc389cb116ca56c37131addd", "packages/pmagg/ui/colorbar_setting.py": "46a557021eeeefe8c56bf5f9e26629cd", "packages/pmagg/ui/color_table.py": "e209c8c3d24f582978ca33100de623a5", "packages/pmagg/ui/default_setting.py": "5846b7322727ee4b19dc7b6c336d1947", "packages/pmagg/ui/default_setting.ui": "b93e791244d7912a51bbaf75a2deff38", "packages/pmagg/ui/default_setting_manager.py": "c06f05023a1bbd6eb754b0e68209fca6", "packages/pmagg/ui/ellipse_setting.py": "4d24440e44796ada3a73a387600899fc", "packages/pmagg/ui/image_setting.py": "6e909b7852eb02a52455a9aa36da7bed", "packages/pmagg/ui/legend_setting.py": "6e3a19772353cdf39944df1e4faa17dd", "packages/pmagg/ui/line2d_setting.py": "470ef26ff8d454e8247f37277d5a785a", "packages/pmagg/ui/linestyles.py": "648f0a673e71e4d08c98e0cc6864202e", "packages/pmagg/ui/pmagg_ui.py": "0ac0c186e7177a3a9c6aee2d9d409177", "packages/pmagg/ui/pmagg_ui.ui": "f1a0f6ff01e632a4994f00258356911f", "packages/pmagg/ui/rectangle_setting.py": "3080ff255cfe2378777f68025e61f2c4", "packages/pmagg/ui/save_image_setting.py": "16036a37904ffa3f20fecc7d004b009f", "packages/pmagg/ui/text_setting.py": "34de8bed7cf453d59fe938be0fbfe92c", "packages/pmagg/ui/title_setting.py": "ebdf8cbd5684278ec81c9cdde176b684", "packages/pmagg/ui/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/pm_calc/ipython_console.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/pm_calc/main.py": "83b0f01515be0f4f76da807e9cb61125", "packages/pm_calc/package.json": "9fdd3a4c9b4a20d39944e5f390642e1e", "packages/pm_calc/preprocess.py": "a8c8d1cb131393e4bf0a6e43503f5c7b", "packages/pm_calc/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/pm_calc/fastui/base.py": "76ac4b617688dfeb97d75c73803dcd44", "packages/pm_calc/fastui/create_random_variable.py": "fa6d77ce65a13e4974da9f5a33516d2a", "packages/pm_calc/fastui/create_tensor.py": "bde0d9952e2ab26e9fb5f47cf8fff98a", "packages/pm_calc/fastui/create_vector.py": "cb707dcfefc1f308d25c17d6a034cdfc", "packages/pm_calc/fastui/dblquad.py": "5e4c5b60231b67cbe5233de0b555f308", "packages/pm_calc/fastui/equation_solve.py": "6fdb3b720ad9547553938a3b735bf970", "packages/pm_calc/fastui/matrix_calc.py": "fc4dbc1ce3af2ae26125d1adb7f8f690", "packages/pm_calc/fastui/matrix_inv.py": "bd826082ba2b3c8775563da8c3ee6321", "packages/pm_calc/fastui/matrix_numbers.py": "b9bc2f888b2c98743072fc686e07751f", "packages/pm_calc/fastui/numerical_integration.py": "57edb1443625bdc5d990aa4d99f6272e", "packages/pm_calc/fastui/reshape_tensor.py": "0647e734b05f39cef659c82c315fb023", "packages/pm_calc/fastui/__init__.py": "b3f689f89235e0fd970023fb1910ebaa", "packages/pm_calc/fastui/helps/numerical_integration.md": "df25b4e7795b58e927c62368d814f0ab", "packages/pm_calc/fastui/helps/reshape_tensor.md": "96a600a163541231dc71d43438de0ab4", "packages/pm_calc/icons/create.png": "d94320bc7e7a4dded74f4eda5b6d1c3e", "packages/pm_calc/icons/eigen.png": "97048cae872a927137f5b7295828c5c2", "packages/pm_calc/icons/equation_solve.png": "af3de472d63ef0e8e6005c153d29ad0b", "packages/pm_calc/icons/flip.png": "e769eeaa1f423de72f34dda86f088540", "packages/pm_calc/icons/integrate.png": "0d3a7f301fa771ee8ef56b10541551d9", "packages/pm_calc/icons/matrix.png": "5b97eee2dae1233c831daae04c5dd98f", "packages/pm_calc/icons/matrix_calc.png": "151e56c9e82870f7cf9783971ee37782", "packages/pm_calc/icons/reshape.png": "3ee90fbdcf09e1bcf3f24e13be041530", "packages/pm_calc/icons/rvs.png": "da077db4d3d834bb311f445e6a9a07c7", "packages/pm_calc/translations/qt_zh_CN.qm": "38862557de320f2ff74455ab1c059a5a", "packages/pm_calc/translations/qt_zh_CN.ts": "c17e3ce1dd88a0cfc45d42cd1f706361", "packages/pm_helpLinkEngine/helpLinkEngine.py": "8e712a1e13bd4d0dd0a5f3cbb59212e0", "packages/pm_helpLinkEngine/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/pm_marketplace/env_manager.py": "b3d1a95732a2b9c9af7ebb3f2d919f08", "packages/pm_marketplace/env_manager.ui": "c910e7c1fdb7d4931ae6395f32fdb06f", "packages/pm_marketplace/Icon.ico": "3fe019a16833a4b89743e829706a22a6", "packages/pm_marketplace/package_install.py": "3b2122f43e471ed12022254a633e93ea", "packages/pm_marketplace/package_install.ui": "58c036d918d493749750442a6dd6e56c", "packages/pm_marketplace/package_manager.py": "9bc3ebc8d0896b8c90661b5e3e0bab5b", "packages/pm_marketplace/package_remove.py": "2882477a6acf357e401983e64a9e5b3d", "packages/pm_marketplace/package_remove.ui": "cd82652c1c18ce309b7b39f64eb1eb93", "packages/pm_marketplace/package_setting.py": "0900c75df4d09edb2df07df0d40b3893", "packages/pm_marketplace/package_setting.ui": "7294066d39daafe32bfb6e14dc9bd4b2", "packages/pm_marketplace/package_update.py": "7bb12094c0e57a557e2e1d104300e910", "packages/pm_marketplace/package_update.ui": "73dd8b7e55bd1dc9730991b5b9a06fd4", "packages/pm_marketplace/pm_marketplace.py": "c00744c2f27e852e4ad0d2d37df043f7", "packages/pm_marketplace/pm_marketplace.ui": "72b055181d7bd41dbfdd06feac750245", "packages/pm_marketplace/pth_modifier.py": "330f9dce62a058e767c9654020ff2012", "packages/pm_marketplace/__init__.py": "e4e5f41117ada3d729f55649c34a3932", "packages/pm_modelling/default.png": "b58b2daf9a2a44726b678f235aed41e6", "packages/pm_modelling/main.py": "936de10bd651db7f14815477894bed86", "packages/pm_modelling/package.json": "ce1142cf81e5d40919fcce5ebe2ae4f9", "packages/pm_modelling/settings.json": "e4385e0a6554dfe40b3696f328f5edd1", "packages/pm_modelling/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/pm_modelling/source/down.svg": "fe6b296aa4020dda5c5a870bb7596235", "packages/pm_modelling/source/plot.svg": "05fd271af6c134f6872c80c489064d60", "packages/pm_modelling/source/\u5730\u56fe.png": "eca50b3207a973738145d1cf12ca0c34", "packages/pm_modelling/source/\u6298\u7ebf\u56fe.png": "1bde0eca8cfa880452accf56ca925866", "packages/pm_modelling/source/\u6563\u70b9\u56fe.png": "78c432bb278d8ea5c9587f23ffdd5f82", "packages/pm_modelling/source/\u6761\u5f62\u56fe.png": "b07f4db5b1785376f97636fc6963234e", "packages/pm_modelling/source/\u67f1\u5f62\u56fe.png": "d6cba37e00bf9bbeb65235897a7b90b9", "packages/pm_modelling/source/\u6c14\u6ce1\u56fe.png": "d6784d4c53145cce1fde17f673b76984", "packages/pm_modelling/source/\u70ed\u529b\u56fe.png": "566d7c6ad396084ca6b7d490868fdc2c", "packages/pm_modelling/source/\u76f4\u65b9\u56fe.png": "a9b01bca53610b2c03f7ea2331c91cc3", "packages/pm_modelling/source/\u7bb1\u7ebf\u56fe.png": "ba733c4c1a0979ab5bc838149490e915", "packages/pm_modelling/source/\u7ec4\u5408\u56fe.png": "c5bea73cff095ad47bd07e1e81921ab2", "packages/pm_modelling/source/\u96f7\u8fbe\u56fe.png": "5aa1a48424b30a157a2bca181c9be2d0", "packages/pm_modelling/source/\u9762\u79ef\u56fe.png": "69682cc944d7afdd5c1f2019bf6ca21c", "packages/pm_modelling/source/\u997c\u56fe.png": "862b6c81a5752ee7d53500ce9064c10d", "packages/pm_modelling/translations/qt_zh_CN.qm": "8a5de3a24facfac59b9b7a95ab7937d7", "packages/pm_modelling/translations/qt_zh_CN.ts": "611800964b03dfef8c1cbc1dc4a0bd9c", "packages/pm_preprocess/base.py": "731a2f17cc570b8b92e5da9e323e07f9", "packages/pm_preprocess/datafilter.py": "ac09562b0e7a6b9094225e01bc70e08c", "packages/pm_preprocess/datamissingvalue.py": "51a2e07966da2b720141bd8cb4721678", "packages/pm_preprocess/datareplace.py": "3cce7391493a104a2fc59a7f792a1883", "packages/pm_preprocess/data_filter.py": "102b1b8071747d6d8ed25530e206c5e7", "packages/pm_preprocess/ipython_console.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/pm_preprocess/main.py": "dd006452670a44799031989f21730175", "packages/pm_preprocess/package.json": "c27f88add11891797e8564867f94abfe", "packages/pm_preprocess/preprocess.py": "a8c8d1cb131393e4bf0a6e43503f5c7b", "packages/pm_preprocess/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/pm_preprocess/fastui/base.py": "97be57a24cb88f7de7aee11e768364ab", "packages/pm_preprocess/fastui/datamerge.py": "c11ecb39655996d441747c93d3a82a0a", "packages/pm_preprocess/fastui/dropna.py": "44a433355c6732b5c9a2ceebd2fed587", "packages/pm_preprocess/fastui/fillna.py": "fe5d6a6f14f21db63116e5ece2dcb9c6", "packages/pm_preprocess/fastui/pivot.py": "dc302f4e2a9a0a03f035cb04452dc978", "packages/pm_preprocess/fastui/transpose.py": "b9873caa7d3d698221fcd590eb848e27", "packages/pm_preprocess/fastui/__init__.py": "5774d567a5ade4656321f056692ecf7c", "packages/pm_preprocess/fastui/templates/dropna.json": "2135651085e530edc3555862c807e8a2", "packages/pm_preprocess/fastui/templates/dropna.py": "818d27872b51cabc824f36545e848dc2", "packages/pm_preprocess/fastui/templates/template.py": "ab8ca29cb2d8a0116de63f411ad08729", "packages/pm_preprocess/translations/qt_zh_CN.qm": "38862557de320f2ff74455ab1c059a5a", "packages/pm_preprocess/translations/qt_zh_CN.ts": "c17e3ce1dd88a0cfc45d42cd1f706361", "packages/pm_preprocess/ui/data_column_desc.py": "50e1de93cb2b5af92ef9f70ec61b28ae", "packages/pm_preprocess/ui/data_column_desc.ui": "2e4ff14a2934d2e461967f4adf57d096", "packages/pm_preprocess/ui/data_column_encode.py": "addb53a419ba7aad0c822918bd164584", "packages/pm_preprocess/ui/data_column_encode.ui": "83585c68a1b5f10c8cb5c8f979fc069a", "packages/pm_preprocess/ui/data_column_name.py": "ab1343d7eb25ba61ff00653dc727dcd2", "packages/pm_preprocess/ui/data_column_name.ui": "290d0f3b71254001a41ed5419cfc2aa7", "packages/pm_preprocess/ui/data_delete_column.py": "96cc3bb3a839bf327be48c5b8bcf7b4f", "packages/pm_preprocess/ui/data_delete_column.ui": "eb9f4daa76ea45a80897c7d699f0223a", "packages/pm_preprocess/ui/data_delete_row.py": "302569fa225a51a1f63cc8fd740532f2", "packages/pm_preprocess/ui/data_delete_row.ui": "a2bef2e740f12897748cb3ab8942c994", "packages/pm_preprocess/ui/data_filter.py": "18e9c3b6ec4a59ee18b9304234354fee", "packages/pm_preprocess/ui/data_filter.ui": "6b68ea20099eb2657a83277c2b35c5e2", "packages/pm_preprocess/ui/data_import_database.py": "aa8f2c175707416490b15c5b8a5f91b1", "packages/pm_preprocess/ui/data_import_database.ui": "70081a8159869c52d03f42ff96198bcd", "packages/pm_preprocess/ui/data_import_excel.py": "50eab4e5f295ac1e36b415bc1fc4ce17", "packages/pm_preprocess/ui/data_import_excel.ui": "8e4fad97836b5ade1bcf623df4019354", "packages/pm_preprocess/ui/data_import_sas.py": "b46b3a739487b88044585b81d8e1e6c3", "packages/pm_preprocess/ui/data_import_sas.ui": "3a238589200dae2ca6d95b9f76658bd4", "packages/pm_preprocess/ui/data_import_spss.py": "f2429d58b23aca160b2652423d57c491", "packages/pm_preprocess/ui/data_import_spss.ui": "02e6076ba3dba639a81488f57a779acd", "packages/pm_preprocess/ui/data_import_text.py": "72839d8f4905a64c72e3e1ef25f0e084", "packages/pm_preprocess/ui/data_import_text.ui": "42ff2c69c472636f51e11b1d26162306", "packages/pm_preprocess/ui/data_info.py": "9be5f4ff591e53d622773a0323d00195", "packages/pm_preprocess/ui/data_info.ui": "d4fcd989ceba0f39012093152207d598", "packages/pm_preprocess/ui/data_merge.py": "22e207fe8f408b7c558e4f9231c2812b", "packages/pm_preprocess/ui/data_merge.ui": "9cacfe18fc7720477acc7a53b46dd35f", "packages/pm_preprocess/ui/data_merge_horizontal.py": "af3de8607ed351d77daa6213fb2828bc", "packages/pm_preprocess/ui/data_merge_horizontal.ui": "d61611fdb4e9cde0fbc2e2c332f48bf5", "packages/pm_preprocess/ui/data_merge_vertical.py": "ef03b6681fd55cf8eb42138024ad1a4e", "packages/pm_preprocess/ui/data_merge_vertical.ui": "59f79c7fa7b08e5ae1e1aeb83eb05987", "packages/pm_preprocess/ui/data_missing_value.py": "81f362bf96738d9138b85a3d1a2dd9e2", "packages/pm_preprocess/ui/data_missing_value.ui": "14e0e959934e191ca4ecc2b65342864f", "packages/pm_preprocess/ui/data_new_column.py": "e079cabae617aeb3ba5fab8656bc79d6", "packages/pm_preprocess/ui/data_new_column.ui": "8d83bde6b1701635d9266ff9fe8047d6", "packages/pm_preprocess/ui/data_partition.py": "6f723fbf5b83bc18194b0e9242d08177", "packages/pm_preprocess/ui/data_partition.ui": "bb426c3df5bd8e2300a6fce26f0acb45", "packages/pm_preprocess/ui/data_repace.py": "76d5e5f3168158c6527adbc1c1b1d309", "packages/pm_preprocess/ui/data_repace.ui": "853b6f43d364faaae3d6f2ed484681ea", "packages/pm_preprocess/ui/data_role.py": "698c50eb2a907f5e517454a28d610daf", "packages/pm_preprocess/ui/data_role.ui": "9b659e8e2accf1bf76fe39efa74152ff", "packages/pm_preprocess/ui/data_role_edit.py": "f93e00c6adfd65e436630e4c5c097d3c", "packages/pm_preprocess/ui/data_role_edit.ui": "18a531771deee0c25777880c0f0894e6", "packages/pm_preprocess/ui/data_row_filter.py": "32baeee9b80430448f0e1f8af96a60e2", "packages/pm_preprocess/ui/data_row_filter.ui": "7a9b909a49af56c8f2916bf4c3f186f5", "packages/pm_preprocess/ui/data_sample.py": "a4919fe81ffbd64838ed0b84b924d36b", "packages/pm_preprocess/ui/data_sample.ui": "f43301f891fd0450fe7800cb81ff4af0", "packages/pm_preprocess/ui/data_sort.py": "fc051898a3fd9a70ff38fe8ff1d3106c", "packages/pm_preprocess/ui/data_sort.ui": "674a1b2eb62111b5c5134b01ed6d684e", "packages/pm_preprocess/ui/data_standard.py": "822ec20008baf2b82b8ee00ea1711933", "packages/pm_preprocess/ui/data_standard.ui": "65a2eca4b0f6ea65a6edf0c9b6f1628f", "packages/pm_preprocess/ui/data_transpose.py": "97b688d1ca7fb74f5bd72a615eac397e", "packages/pm_preprocess/ui/data_transpose.ui": "d5b50013e984a0f56af8b45aa213fd20", "packages/pm_statistics/default.png": "b58b2daf9a2a44726b678f235aed41e6", "packages/pm_statistics/describe.py": "d48a7e2d4573eec5ea2d6b78f6d518cf", "packages/pm_statistics/main.py": "35a51268c3d516d91f75fd155981327e", "packages/pm_statistics/package.json": "b0cfa0a43b9b4ceffef50955bc142708", "packages/pm_statistics/settings.json": "e4385e0a6554dfe40b3696f328f5edd1", "packages/pm_statistics/stat_desc.py": "1d1af20f21d51a2c4c937b583fa1c1e0", "packages/pm_statistics/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/pm_statistics/source/add.svg": "abddfead93cdf1feeff13e0fb361b2bc", "packages/pm_statistics/source/collect.svg": "df376dc82733ce771785e80417b5730c", "packages/pm_statistics/source/comment.svg": "17b32e5429d0af97cb4b0cb0b878301a", "packages/pm_statistics/source/descending.svg": "4c0294dcdbb972f6f7e7daae9ca3695e", "packages/pm_statistics/source/down.svg": "cf38a40a26932f5d960da15d8bbcb9f5", "packages/pm_statistics/source/history.svg": "f5e2890d446cc530e8505c2a8bcbeb4c", "packages/pm_statistics/source/left(1).svg": "195de59f0ae8946bbdad79b1e6211c13", "packages/pm_statistics/source/left.svg": "5e2fab4fbb3002791406ffb7d4f24552", "packages/pm_statistics/source/like(1).svg": "be280f55711fe37711710d66cc6de07f", "packages/pm_statistics/source/like.svg": "cd5a555d5aeb29ea51c2d4c411a8e2f7", "packages/pm_statistics/source/offline.svg": "864ab749688d77a17dd418ddd12f613f", "packages/pm_statistics/source/print.svg": "58e74f093ab0f76d7a5f4827139d8202", "packages/pm_statistics/source/reduce.svg": "d3a4d2cb5e65462b6e488f9008101095", "packages/pm_statistics/source/replace.svg": "2c9371c33c45026339f8f0a0fad242bb", "packages/pm_statistics/source/right(1).svg": "6ab0334feba1ebc9fe424a408dbeb053", "packages/pm_statistics/source/right.svg": "0d34209038963d8520605c2945a4aea5", "packages/pm_statistics/source/up.svg": "bfc6c9a4e8afe014a9ef2a3a2eaac829", "packages/pm_statistics/source/user.svg": "03789a31cc8d77164ee5e38758be9fed", "packages/pm_statistics/source/view.svg": "404b787576ea5ee3fb77e219a4020739", "packages/pm_statistics/translations/qt_zh_CN.qm": "27c309c51dd6a9b4453805a9d57314d8", "packages/pm_statistics/translations/qt_zh_CN.ts": "6f69f9df98fc80ba305d1ce1bc850e9a", "packages/pm_statistics/ui/stats.qrc": "9e88108821db568452feff5c52cd51bf", "packages/pm_statistics/ui/stats_rc.py": "aed3f3d5bd2699a234c5b87a8c90c27a", "packages/pm_statistics/ui/stat_base.py": "16ff0fac4ad347a51cb36503ddabc76b", "packages/pm_statistics/ui/stat_base.ui": "5b3601a91453d85b8ba247fac1772a85", "packages/qt_vditor/client.py": "b5f987d7265282647636753bb1992169", "packages/qt_vditor/extension_demo.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/qt_vditor/main.py": "43f5ca83b9b8de08a4b08f98c8d5b12c", "packages/qt_vditor/package.json": "a6ea48177df90bd33e6d9ef571715434", "packages/qt_vditor/route.py": "81e4d783b1ee5662a42175044860ac97", "packages/qt_vditor/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/qt_vditor/examples/sample.md": "6d463f26da3f758e8f8cd73d2f07a013", "packages/qt_vditor/templates/index.html": "ed4bff205de492bd9a445b878941ab98", "packages/setting_manager/main.py": "5e25055f2748fd8f2e16ef6cd438cbfe", "packages/setting_manager/package.json": "7b1091e765c316eb4602acbff21a7b5b", "packages/setting_manager/python.jpg": "81740ef62fda1acb2905ad8dedb96385", "packages/setting_manager/settings.json": "d41d8cd98f00b204e9800998ecf8427e", "packages/setting_manager/settings.py": "f60620c0bbfe1bc061bfc00759107674", "packages/setting_manager/ui_inputs.py": "16c170ac829957e8bd4f3b02f4375472", "packages/setting_manager/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/setting_manager/translations/qt_zh_CN.ts": "023045a080a9a26547219f054c272d80", "packages/setting_manager/translations/zh_CN.ts": "023045a080a9a26547219f054c272d80", "packages/socket_server/extension_demo.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/socket_server/index.rst": "e1840535bf3051014eab7bb2c76fbc10", "packages/socket_server/main.py": "a032c7a064b344e47dd998c545feb38f", "packages/socket_server/package.json": "12d6c14b45eb86427e311e31d005fa20", "packages/socket_server/server_by_socket.py": "77e46bf007fd51f41471b7901bc4b7e1", "packages/socket_server/settings.json": "ac084b924952c4c88e397f5c1f64c893", "packages/socket_server/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/socket_server/translations/qt_zh_CN.ts": "023045a080a9a26547219f054c272d80", "packages/socket_server/translations/zh_CN.ts": "023045a080a9a26547219f054c272d80", "packages/workspace_inspector/data_viewer.py": "96d8e120289b60f4fe6a4877c807a12e", "packages/workspace_inspector/inspectortable.py": "7e88412960946a64afcdaee6b3bf1902", "packages/workspace_inspector/main.py": "4d6088b006fe4b09815eb34cfad86dcb", "packages/workspace_inspector/package.json": "9ed3843023e8b054420bd126866d6962", "packages/workspace_inspector/python.jpg": "81740ef62fda1acb2905ad8dedb96385", "packages/workspace_inspector/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/workspace_inspector/translations/qt_zh_CN.qm": "4c1aa1fdb602f504983cfc1f303fdb4b", "packages/workspace_inspector/translations/qt_zh_CN.ts": "0176607ae0f96037d5b756be88615f76", "pmgwidgets/get_time_consuming_classes.py": "b1cc4fe83988288288bb4d46a55297e8", "pmgwidgets/__init__.py": "d4a51fe0c972d4f917c7c2029f6d83f5", "pmgwidgets/display/examples.py": "ed6581d11a7624a45e76a0c214abb6e4", "pmgwidgets/display/__init__.py": "de9a0f70e421564574c3c849d8c9cbcd", "pmgwidgets/display/browser/browser.py": "6471eef6706c84e33e915d2539d7ad74", "pmgwidgets/display/browser/get_ipy.py": "fdcef50d9c3d562c0c1a01a07fffac29", "pmgwidgets/display/browser/handler.py": "8360907d55237220c8409d02896e1630", "pmgwidgets/display/browser/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "pmgwidgets/display/dynamicgraph/pgexample.py": "3fd8ccf52c92ebeb164ca3b048b657bc", "pmgwidgets/display/dynamicgraph/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "pmgwidgets/display/dynamicgraph/base/basetimeseries.py": "5c6cef4a05d653d276681d9281ea7eb3", "pmgwidgets/display/dynamicgraph/base/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "pmgwidgets/display/dynamicgraph/mplplots/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "pmgwidgets/display/dynamicgraph/pgplots/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "pmgwidgets/display/matplotlib/pmagg.py": "bbee7bf2140f5b9e7d0f2bd3f9f1dd53", "pmgwidgets/display/matplotlib/qt5agg.py": "33f10a259f5fa8973c6cb863ef117285", "pmgwidgets/display/matplotlib/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "pmgwidgets/display/matplotlib/pyqtgraph/pyqtgraphwidget.py": "c260badf3dd0ad2f2e3bcd0ae107dcd3", "pmgwidgets/display/matplotlib/pyqtgraph/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "pmgwidgets/display/vtk/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "pmgwidgets/docs/threading_and_tasking.md": "803fb7aad9cbd712229fa089727d2aae", "pmgwidgets/docs/doc_figures/pmflowarea_2.png": "4c5b6d091ae5fdc685e94c64a3561218", "pmgwidgets/doc_figures/nested_lists_to_place_widgets.png": "3b3dfe1437591299762a3307ba9880a0", "pmgwidgets/doc_figures/pmflowarea_1.png": "aeef39398e583540e55a87c04fcb19fc", "pmgwidgets/doc_figures/pmflowarea_2.png": "4c5b6d091ae5fdc685e94c64a3561218", "pmgwidgets/doc_figures/settings_panel.png": "d1e20e63c914275463ed75740228e7b6", "pmgwidgets/elements/dockobject.py": "bbad65e7b1f0304112a70c28917c4fe4", "pmgwidgets/elements/toolbar.py": "adb36bf834bbb12c5baabfb323dc5297", "pmgwidgets/elements/__init__.py": "5d3ff4be23732caafec3725aacfd2bb1", "pmgwidgets/examples/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "pmgwidgets/examples/utilities/examples.py": "4b738678a2cec6ca81f038d2aad1ebc4", "pmgwidgets/examples/utilities/long_conn.py": "a7fb1b9aac35414235ef80dbd1715812", "pmgwidgets/examples/utilities/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "pmgwidgets/flowchart/create_node_content_class.md": "e15251c3574ee6f3472cb32984007f1c", "pmgwidgets/flowchart/dataprocesswidget.py": "5830d35615c1b8e05555d4005089f1dd", "pmgwidgets/flowchart/readme.md": "13c783b72c01bbef6fd2462deb54d1e1", "pmgwidgets/flowchart/readme_arch.md": "15760931b08a70d8257b8e3695f06105", "pmgwidgets/flowchart/simulationwidget.py": "17b7dadaf18e84f2d359f2e24571cbe4", "pmgwidgets/flowchart/__init__.py": "1ec9907600bd333efbac93fb205ba8d7", "pmgwidgets/flowchart/\u521b\u5efa\u65b0\u8282\u70b9(deprecated).md": "c9a0c64e31e69a6c2b226590bf174049", "pmgwidgets/flowchart/core/flowchart_scene.py": "8e6b923663fb49519f6555d181c6bc2b", "pmgwidgets/flowchart/core/flowchart_widget.py": "6e61a893445b0c08e6bb4fc04f5c9663", "pmgwidgets/flowchart/core/flow_content.py": "9f7f15c45ea181fa9390cac06823cca6", "pmgwidgets/flowchart/core/flow_items.py": "45810b572b64dfb46b1dbafc4c8391bb", "pmgwidgets/flowchart/core/flow_node.py": "426bd423a15ea028aae608489bf27c2f", "pmgwidgets/flowchart/core/nodemanager.py": "22f10c693caabc1a588658af292a8238", "pmgwidgets/flowchart/core/utils.py": "5e2a38303e8fd30ed7ce3131d99ee4ae", "pmgwidgets/flowchart/core/__init__.py": "33d597a87ce723d432ee101bfea5933c", "pmgwidgets/flowchart/doc_figures/before_run.png": "56d3c6e2d043d5be501d27a016bf2eb0", "pmgwidgets/flowchart/doc_figures/check_json.png": "98dcb1514c6a63b37820704c3ff76ce3", "pmgwidgets/flowchart/doc_figures/click_edit_button.png": "1a6625861a3fbd71cf14aa948b16eced", "pmgwidgets/flowchart/doc_figures/click_right_top_add_button.png": "116cc66ea7f7a01621afa2bceeefde15", "pmgwidgets/flowchart/doc_figures/composition_structure.png": "8a898076c0aa77e7cfe627a933d29c73", "pmgwidgets/flowchart/doc_figures/configure_panel.png": "6fc3b5d60c9df7dc4b7ae1ca73162e0a", "pmgwidgets/flowchart/doc_figures/create.png": "77d2839be9232d6923e99ac372f1bdd5", "pmgwidgets/flowchart/doc_figures/create_new_content_Mul.png": "7d8073ceafe5de7b52cc99b7b0f83b70", "pmgwidgets/flowchart/doc_figures/create_node.png": "5defabfa57656965e0ce99fe418d9559", "pmgwidgets/flowchart/doc_figures/custom_node.png": "98076ecba04484ffb0f68bc1d3ebe506", "pmgwidgets/flowchart/doc_figures/edit_node.png": "5bab19f7eb2b5794e85edaaac6ac8f9f", "pmgwidgets/flowchart/doc_figures/edit_panel_meaning.png": "fc03b61991304dbc4b9757420d4853d3", "pmgwidgets/flowchart/doc_figures/popup_edit_panel.png": "a069ec207a2060649079a5876ef785e1", "pmgwidgets/flowchart/doc_figures/sketch_after_edit.png": "562a4f15b3d808e34ed205ba4859c66d", "pmgwidgets/flowchart/icons/down.png": "fe8105e197d1a8dbb7c3f4d04605cc94", "pmgwidgets/flowchart/icons/logo.png": "3ba1fce2f5b57a2ab16287e9d0784702", "pmgwidgets/flowchart/nodes/dfoperation.py": "3ea5a1b70fc51b363cd8d6ea029aa64f", "pmgwidgets/flowchart/nodes/docparser.py": "97e1297436001fc6d447a957799d56d6", "pmgwidgets/flowchart/nodes/plots.py": "1423de8473b088b0cf15fada6cebffdc", "pmgwidgets/flowchart/nodes/random.py": "bbab2dc862413421cfec6c250f5f04cf", "pmgwidgets/flowchart/nodes/reliabilities.py": "834359f014c3618af22987679829df79", "pmgwidgets/flowchart/nodes/simplecalc.py": "75c69d36c3813ee5e6b7f98c8c1893e2", "pmgwidgets/flowchart/nodes/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "pmgwidgets/flowchart/nodes/dataframeoperation/dropduplicated.py": "5db7eb9383b7e18a6e6b185b657c2f4e", "pmgwidgets/flowchart/nodes/dataframeoperation/randomrowsample.py": "594931f937571051aaa08115303f9b9d", "pmgwidgets/flowchart/nodes/dataframeoperation/__init__.py": "590a3c48436d17b168ab491e3d5ef71d", "pmgwidgets/flowchart/nodes/io/iterator.py": "d41401063ca9a942416f0f057de62c29", "pmgwidgets/flowchart/nodes/io/listdir.py": "8c5903f20f49659f9af2a7ec201d96b3", "pmgwidgets/flowchart/nodes/io/pdimport.py": "c32294f54f65a3f25ba6fd1cdabef0ea", "pmgwidgets/flowchart/nodes/io/__init__.py": "e6a014f94141e383286a37068a446b4d", "pmgwidgets/flowchart/tests/continously_data_process.py": "8f7cbef680438882a373a1a98b6f763c", "pmgwidgets/flowchart/tests/database_import.py": "ac1bb7f644465845aac15203b459813a", "pmgwidgets/flowchart/tests/fault_tree.py": "4e7a094f1bf83835627c17ecf51eec0a", "pmgwidgets/flowchart/tests/node_test.py": "49436316b4bcffa573c07cf714f61d29", "pmgwidgets/flowchart/tests/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "pmgwidgets/utilities/__init__.py": "846122683610ac9a3a02228d40c893d8", "pmgwidgets/utilities/network/baseclient.py": "025302351202482cdb67961418a21dda", "pmgwidgets/utilities/network/generalclient.py": "57523e2dfe953db9e48e0b2e887f9aea", "pmgwidgets/utilities/network/qtclient.py": "c136486e5d7ce14a760e68b5cef5eac9", "pmgwidgets/utilities/network/server.py": "36c357391ae6077674c686159e9adc0d", "pmgwidgets/utilities/network/util.py": "6d82debc837372e82a42af901dbd8d18", "pmgwidgets/utilities/network/__init__.py": "8dd261a5fd464d78930e554a3c71ca0e", "pmgwidgets/utilities/platform/commandutils.py": "c534c45682925de3a9ef097bc5e9738f", "pmgwidgets/utilities/platform/filemanager.py": "a7ed6bb8a48dc0a631c676b70618b727", "pmgwidgets/utilities/platform/filesyswatchdog.py": "c0a2bf1c91a21a5dcf23e31bfeecb70a", "pmgwidgets/utilities/platform/fileutils.py": "3ca5537cb60e43a1357e9681b7ac7792", "pmgwidgets/utilities/platform/openprocess.py": "1d61b9d4e3ad65e71005b378f25c556c", "pmgwidgets/utilities/platform/pmdebug.py": "d0ec593f50f535adc1d9c48c79ea00d7", "pmgwidgets/utilities/platform/translation.py": "c4309549c4c11558fdbc3b7491ed64f6", "pmgwidgets/utilities/platform/__init__.py": "7fd20799d245559b3722b2688b40aada", "pmgwidgets/utilities/platform/test/python_file_test.py": "0c4bff9ef948dbcc88ea0532ae0beb44", "pmgwidgets/utilities/platform/test/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "pmgwidgets/utilities/source/colorutils.py": "fde6914451c075a3e0428f2cce1d0882", "pmgwidgets/utilities/source/graphicsitemutils.py": "30395a5a9829eee910c69eca95aa649b", "pmgwidgets/utilities/source/iconutils.py": "e83075c233b7ce3290f0e37ded6fba94", "pmgwidgets/utilities/source/translation.py": "bb3c8fbd3051b640ed77d30ab718dc32", "pmgwidgets/utilities/source/__init__.py": "5b8008b3309c862942604991569b19f0", "pmgwidgets/utilities/uilogics/codechecking.py": "48560fbdbd7cf04f3461587b90a12872", "pmgwidgets/utilities/uilogics/drags.py": "857f251d9e621005afd8be2c3bf62708", "pmgwidgets/utilities/uilogics/uidisplay.py": "f4ee6b2b8695f77551bb4d7e5ff7ad39", "pmgwidgets/utilities/uilogics/undomanager.py": "df9dc910b24d09c49e9726420fdeee10", "pmgwidgets/utilities/uilogics/windowutils.py": "8372420c42f626d39d9eb193296cb900", "pmgwidgets/utilities/uilogics/__init__.py": "645e7a6c9bf8432d1c1ba5e333c362c2", "pmgwidgets/utilities/uilogics/tasks/loop_background.py": "4a363d514a49b3d142dd111f957b4a0a", "pmgwidgets/utilities/uilogics/tasks/minimal_thread.py": "94f8326dbba36d11f6e45fb79bb2697c", "pmgwidgets/utilities/uilogics/tasks/one_shot_background.py": "0075eafe0b95ef10ec9e382917867f7b", "pmgwidgets/utilities/uilogics/tasks/threads.py": "380066964983e4398279b18f1cb4c2a5", "pmgwidgets/utilities/uilogics/tasks/__init__.py": "2a83611ec96538dd91f878617b4cc882", "pmgwidgets/widgets/__init__.py": "dace0c2123dda2b5cf61bfbe954f76e4", "pmgwidgets/widgets/basic/__init__.py": "fd2926ad171e35c84c8e34ddc341a15a", "pmgwidgets/widgets/basic/browsers/browser.py": "db4472da3e421edae53084e2f60f8531", "pmgwidgets/widgets/basic/browsers/__init__.py": "f2ca69c55dd6dd321eb50715d281d85f", "pmgwidgets/widgets/basic/browsers/translations/qt_zh_CN.ts": "023045a080a9a26547219f054c272d80", "pmgwidgets/widgets/basic/buttons/__init__.py": "900cfebb524cf4f7d2a311ba7376744c", "pmgwidgets/widgets/basic/buttons/button/toolbutton.py": "078234cbde02a9528c683296768c716a", "pmgwidgets/widgets/basic/buttons/button/__init__.py": "0c609746f7319525ec3b9a95478be7e1", "pmgwidgets/widgets/basic/buttons/buttonpane/pushbuttonpane.py": "b4b83976fd0b594799dc95bc3c577433", "pmgwidgets/widgets/basic/buttons/buttonpane/__init__.py": "1e6ab09b63582d591d9c9fbb95d77d25", "pmgwidgets/widgets/basic/buttons/translations/qt_zh_CN.ts": "023045a080a9a26547219f054c272d80", "pmgwidgets/widgets/basic/containers/flowarea.py": "3465e9353838239b7a85503bdb348eb6", "pmgwidgets/widgets/basic/containers/flowlayout.py": "e7722b3b344fb1966513920657c3ab9e", "pmgwidgets/widgets/basic/containers/pmdockwidget.py": "264fd97a9b774531ee722a2c561c9eb1", "pmgwidgets/widgets/basic/containers/pmscrollarea.py": "d4474ecaf4ebe7ef1bc33646f9776c74", "pmgwidgets/widgets/basic/containers/PMTab.py": "69bc55a23c66f5aeefd57c00c54daf9f", "pmgwidgets/widgets/basic/containers/pmtoolbox.py": "aaa8b73936ef32a4c48d3c6dbb4a1edc", "pmgwidgets/widgets/basic/containers/__init__.py": "edcf475260eaebb0aababd31515f66a6", "pmgwidgets/widgets/basic/containers/translations/qt_zh_CN.ts": "023045a080a9a26547219f054c272d80", "pmgwidgets/widgets/basic/dialogs/textdialog.py": "02c8803b009663537eadcf6d1a1708c0", "pmgwidgets/widgets/basic/dialogs/__init__.py": "b05f556cffa689fce8ecf11590d5073d", "pmgwidgets/widgets/basic/images/imageview.py": "677c40cd96057c84dbb37592daef0af2", "pmgwidgets/widgets/basic/images/imageviewitem.py": "7f4c1d86ca85188970310d26c26d30a8", "pmgwidgets/widgets/basic/images/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "pmgwidgets/widgets/basic/labels/scrolllabel.py": "f6bd03763bb1f04990b20b3a1a9b1c90", "pmgwidgets/widgets/basic/labels/__init__.py": "37e333e93bd172534e76cd5e850f58f1", "pmgwidgets/widgets/basic/labels/translations/qt_zh_CN.ts": "023045a080a9a26547219f054c272d80", "pmgwidgets/widgets/basic/lists/combobasic.py": "0e7fbf8724164af3e7b8b9933df4729c", "pmgwidgets/widgets/basic/lists/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "pmgwidgets/widgets/basic/lists/translations/qt_zh_CN.ts": "023045a080a9a26547219f054c272d80", "pmgwidgets/widgets/basic/others/console.py": "5905c89de68775fd568577969e593f3a", "pmgwidgets/widgets/basic/others/ConsoleHistoryDialog.py": "2d4f0c82d296813c5a578cc906ae9424", "pmgwidgets/widgets/basic/others/ConsoleHistoryDialog.ui": "3ae43f1f52ed4f7dc414a4e522c76122", "pmgwidgets/widgets/basic/others/gauge.py": "16159230eebfc9f1be3ff0c4c5d555e1", "pmgwidgets/widgets/basic/others/instantbootconsole.py": "5633bce9595e8b8e509431e3d3ce5f09", "pmgwidgets/widgets/basic/others/processconsole.py": "adf620b42500885f8252657aefcc701e", "pmgwidgets/widgets/basic/others/Ui_ConsoleHistoryDialog.py": "22769bd93d86bf8129ca0fa03b9af12b", "pmgwidgets/widgets/basic/others/__init__.py": "fbcfcf98fbfcfb8625281748544e5194", "pmgwidgets/widgets/basic/others/source/clear.png": "d00b30caeba4e133a503c28d29484cd6", "pmgwidgets/widgets/basic/others/source/run.png": "a7ad927c1b15a7fd5a3a228991d6b778", "pmgwidgets/widgets/basic/others/source/stop.png": "db32ac51e103396c0758faa7ca39df7d", "pmgwidgets/widgets/basic/others/translations/qt_zh_CN.ts": "efbc0c2de628506b1cd2528e85110912", "pmgwidgets/widgets/basic/plots/__init__.py": "29b430ab74236a99e749c91f4a7a4d8d", "pmgwidgets/widgets/basic/plots/bars/histogram.py": "57fe8015a7abe33f76965ffb405a1ba1", "pmgwidgets/widgets/basic/plots/bars/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "pmgwidgets/widgets/basic/plots/lines/timeseries.py": "7a1877123a6d38adb06b3b9f43a6bb73", "pmgwidgets/widgets/basic/plots/lines/__init__.py": "d8f241f084bf5d4a8f4efd5b51d7e94f", "pmgwidgets/widgets/basic/plots/matplotlib/__init__.py": "2ac44857cafb53b120d44169c3c34f04", "pmgwidgets/widgets/basic/plots/matplotlib/base/pmaggplot.py": "c1882247f0027b2b962ba7a5ea3ca7c7", "pmgwidgets/widgets/basic/plots/matplotlib/base/qt5aggplot.py": "33f10a259f5fa8973c6cb863ef117285", "pmgwidgets/widgets/basic/plots/matplotlib/base/__init__.py": "f0e054fea02ba6ad51bf06d2a311e39b", "pmgwidgets/widgets/basic/plots/pyqtgraph/__init__.py": "2be8e98cb7ca94374b5168f14d00b359", "pmgwidgets/widgets/basic/plots/pyqtgraph/base/pgplot.py": "8c38a37cb08c0b54300704a0d1b0f10b", "pmgwidgets/widgets/basic/plots/pyqtgraph/base/__init__.py": "81087fce1382395cd3c04360a691bd99", "pmgwidgets/widgets/basic/plots/scatters/scatters.py": "450136b97d71bbca2ac809b4b7541a7c", "pmgwidgets/widgets/basic/plots/scatters/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "pmgwidgets/widgets/basic/plots/translations/qt_zh_CN.ts": "023045a080a9a26547219f054c272d80", "pmgwidgets/widgets/basic/quick/demo1.py": "353264297eedb94ef52af4c181c79e4e", "pmgwidgets/widgets/basic/quick/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "pmgwidgets/widgets/basic/tables/tableviews.py": "a2111346680c2af8ab00f8588191e07a", "pmgwidgets/widgets/basic/tables/tablewidgets.py": "890007b4bf1e6e2a38673614f324168d", "pmgwidgets/widgets/basic/tables/__init__.py": "84da0cb7f378bbf1ea095d6b8ccaf66d", "pmgwidgets/widgets/basic/tables/help/help.md": "803db01c2a1acf0f80d479042ecaff99", "pmgwidgets/widgets/basic/tables/translations/qt_zh_CN.ts": "147dcbf56740cccf27dc99dcec3b5b6a", "pmgwidgets/widgets/basic/texts/__init__.py": "e1c06d85ae7b8b032bef47e42e4c08f9", "pmgwidgets/widgets/basic/texts/statusreport/errroreport.py": "a7f14a40ca0be66bd12ac1084f1976f1", "pmgwidgets/widgets/basic/texts/statusreport/__init__.py": "b2b898748d695f952a3838cd8289bfbe", "pmgwidgets/widgets/basic/texts/webeditors/editor.py": "34336ae67d3efda5060edffa2982d941", "pmgwidgets/widgets/basic/texts/webeditors/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "pmgwidgets/widgets/basic/trees/filetree.py": "f2f05c9657978a587295f5a4f09ed1fb", "pmgwidgets/widgets/basic/trees/jsontree.py": "b91dc6b98e5561f0f9988051371101c6", "pmgwidgets/widgets/basic/trees/treecheck.py": "f29b4c69b3f30b99da28eb79bcf1c068", "pmgwidgets/widgets/basic/trees/varattrtree.py": "3f6d8812f23ab42247ee5185e8c8c57c", "pmgwidgets/widgets/basic/trees/__init__.py": "2dc6f31ca28c563654578e8b821f06bd", "pmgwidgets/widgets/basic/trees/translations/qt_zh_CN.ts": "14da9ce6253397bf6c0fcc1c4c004a01", "pmgwidgets/widgets/composited/buttonpanel.py": "a7323ff16ef179e89c39c3253667aafc", "pmgwidgets/widgets/composited/fastui.py": "8b604fc24975c5c7ba2de067065a363f", "pmgwidgets/widgets/composited/generalpanel.py": "b6be00c44a6e382aae0453e133e84d01", "pmgwidgets/widgets/composited/__init__.py": "d13563610fa6f243f2d973dd42a6f6df", "pmgwidgets/widgets/extended/__init__.py": "cc16561fe162128cca4c778f11263487", "pmgwidgets/widgets/extended/base/baseextendedwidget.py": "378fdef54ba10e68ed5cb276a5624c31", "pmgwidgets/widgets/extended/base/__init__.py": "d7d1b8939cf2c630235925abcb5a2ee2", "pmgwidgets/widgets/extended/checkbuttons/check.py": "d8b6cbe579ad2161bce135f2925313c4", "pmgwidgets/widgets/extended/checkbuttons/__init__.py": "a5c9c5ecdc69c4139ecd15de439b2e0b", "pmgwidgets/widgets/extended/comboboxes/combo.py": "ebf75f80630cac67f9642f5372097df4", "pmgwidgets/widgets/extended/comboboxes/variables_combo.py": "371fd0d84768be8a624dac87979e1f1b", "pmgwidgets/widgets/extended/comboboxes/__init__.py": "58edc6def91691d42263e0d79deec2c0", "pmgwidgets/widgets/extended/entries/baseentryctrl.py": "e752e2c6a708b1a435a84473bba154d8", "pmgwidgets/widgets/extended/entries/colorctrl.py": "16f70e0d829af6c89fcab387dd6f1442", "pmgwidgets/widgets/extended/entries/evalctrl.py": "de708611514272173a02f047b7e5fc68", "pmgwidgets/widgets/extended/entries/filectrl.py": "d11a9711cb319e2131464baa2a6a8d84", "pmgwidgets/widgets/extended/entries/folderctrl.py": "ff904108b13cddf3b6c7837cb60aa847", "pmgwidgets/widgets/extended/entries/funcctrl.py": "f308b1b347c7d44a4efe36b4e8a73ec3", "pmgwidgets/widgets/extended/entries/keymappingctrl.py": "2a50527522468cd00f3d7ccb99a02a7d", "pmgwidgets/widgets/extended/entries/linectrl.py": "626be1e4169342de4027f902779d9e19", "pmgwidgets/widgets/extended/entries/numctrl.py": "3739731735cc0520c4f45d1b0429297a", "pmgwidgets/widgets/extended/entries/passwordctrl.py": "85a3604a10ba51e5aaddfd269a8f1317", "pmgwidgets/widgets/extended/entries/__init__.py": "a87b64affd6a8b87f14caa030b1d7196", "pmgwidgets/widgets/extended/labels/label.py": "e12d3eaafac1b29d87b6b83cb5ca46c9", "pmgwidgets/widgets/extended/labels/__init__.py": "5d5fadd87777a0ecdf64ec91c7af5fb1", "pmgwidgets/widgets/extended/lists/listwgt.py": "52cf01737cab419a302a61a2fc22dd7d", "pmgwidgets/widgets/extended/lists/__init__.py": "fe79c30d2106faff8c630bec7e79ce37", "pmgwidgets/widgets/extended/others/multitypeparaminput.py": "f22495e2e29d29843c3888a21abd43a0", "pmgwidgets/widgets/extended/others/__init__.py": "372dfdcc25b8f846d64b77bea41535b8", "pmgwidgets/widgets/extended/others/monitors/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "pmgwidgets/widgets/extended/plots/__init__.py": "56a6f2c973e69ed27c3021de7b28e035", "pmgwidgets/widgets/extended/plots/lines/timeseries.py": "d0b20cb0ea7e4a6ccafc47d9b16e6fc9", "pmgwidgets/widgets/extended/plots/lines/__init__.py": "bc396f8083302203baaf5d23065c33ca", "pmgwidgets/widgets/extended/radiobuttons/radiobuttonctrl.py": "113268756d9f5bb930230e4f8824ec67", "pmgwidgets/widgets/extended/radiobuttons/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "pmgwidgets/widgets/extended/spins/datetime.py": "e6a555a3bf8d0a57b98d8926ec6684c7", "pmgwidgets/widgets/extended/spins/numberspin.py": "74160a8031c42d2f5ea87cc4a5be3272", "pmgwidgets/widgets/extended/spins/__init__.py": "cfa6752de3dc824468eaaec6166fb3d4", "pmgwidgets/widgets/extended/tables/rulesctrl.py": "6d2fda81132490482c0fdc5263cb5190", "pmgwidgets/widgets/extended/tables/tableshow.py": "100223049815e42adb4fd3c74753dda7", "pmgwidgets/widgets/extended/tables/__init__.py": "2945023ce1794015105e1aba6213136d", "pmgwidgets/widgets/extended/texts/htmlshow.py": "d41d8cd98f00b204e9800998ecf8427e", "pmgwidgets/widgets/extended/texts/markdownshow.py": "d41d8cd98f00b204e9800998ecf8427e", "pmgwidgets/widgets/extended/texts/__init__.py": "4ab23c644c97003b968443d8a2f4a584", "pmgwidgets/widgets/extended/trees/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "pmlocalserver/readme.md": "6e10586e731f6fdc871441974934b006", "pmlocalserver/server.py": "d29fca0da053832fcbaec4788889674c", "pyminer_comm/readme.md": "d41d8cd98f00b204e9800998ecf8427e", "pyminer_comm/__init__.py": "f6b46f7aedc9dd22d9cdaf15d1be08c2", "pyminer_comm/base/datadesc.py": "1f068e02ef48771fb3499b5b27ff058b", "pyminer_comm/base/encode_decode.py": "42188f6322e17da29d8d1cfb54ebc75a", "pyminer_comm/base/network.py": "1b0fbd15a289297b65698b2139724741", "pyminer_comm/base/sys_utils.py": "4b9c387c0926aa2bec713ba5560c68f1", "pyminer_comm/base/__init__.py": "036632e2718a53fdcd02a87492de0963", "pyminer_comm/data_client/data_client.py": "613aeac2716a0ec38b3f5db2c38080b4", "pyminer_comm/data_client/unittest_data_client.py": "cd4366d90aeca0639432273292da8efe", "pyminer_comm/data_client/__init__.py": "8159a5a2b762aa7581a92517146bd002", "pyminer_comm/pyminer_client/pm_client.py": "5e7ff88e50112aac1f639a7301404218", "pyminer_comm/pyminer_client/__init__.py": "fa7dd1f47a04d6515681315870a12899", "pyminer_comm/tests/test_communication.py": "95fa83a3686c8ea7bfc4736220e3bbdb", "resources/pyqtsource.qrc": "8a3a6b55a64fa60f8c58f49d2d7eb949", "resources/pyqtsource_rc.py": "1cdc5870d3e5107926e57d15c7fa63ea", "resources/fonts/Cascadia.ttf": "717e365c4a4c1478f8208a5ab33ee26b", "resources/fonts/CascadiaMono.ttf": "66c917f89d707ba0b41d5a1619c1e03e", "resources/fonts/Deng.ttf": "15c8b490227909f31d456ee9d11521e5", "resources/fonts/Dengb.ttf": "2d690e1656db754bc4ce63531223d007", "resources/fonts/Dengl.ttf": "1ddcd772ff1d04a2d545434b47ae4782", "resources/fonts/SourceCodePro-Bold.ttf": "458f0d7c492182d4c1b08621518689c0", "resources/fonts/SourceCodePro-BoldIt.ttf": "0cddef66936155d98aaa0d2a71d609c1", "resources/fonts/SourceCodePro-It.ttf": "df1343e44ce0fd5eb8c09ce8995966c8", "resources/fonts/SourceCodePro-Regular.ttf": "fedb9984186419a66cf725a38b6703ca", "resources/icons/logo.ico": "f1eb0f12aa600bd5638cd6fbca4b978f", "resources/icons/logo.png": "30e040dea91eb5edc95a2ab16c895324", "resources/images/bg.png": "931fe255dbcec81b94c2cd692fa1ff01", "resources/images/left.bmp": "d9daee9f12d8d45832a5f85fb79733cd", "resources/images/PyMiner\u6846\u67b6\u8bf4\u660e.jpg": "02eecc71bfee918d5a559501aa8b1334", "resources/images/splash.jpg": "d093033402635cffde8ae0d4840e46c4", "resources/images/splash_v2.png": "8de3454d0a7ea72b2ddbe2b242ebc535", "resources/images/weixin.png": "9fd2526c405766beb714515a07571c44", "resources/images/xmind.png": "9f0ef2b1b83dee369f38600bbdbf44bb", "resources/images/zhifubao.png": "2b957fd623e55d473c9c1eb81ce7ecd8", "resources/qss/Fusion.qss": "e3b35eb107311192c3d6a1b6512da0a0", "resources/qss/index.rst": "6000f5eae1ed6f0e434e0001675bf8a2", "resources/qss/Qdarkstyle.qss": "1d5438b0c29ab46529c7d38a976dbf62", "resources/qss/standard.ini": "ee661fb8f5a4a0852389e31a11c71001", "resources/qss/standard.qss": "1c1672f58d85af0e6211b6c144987974", "resources/qss/Windows.qss": "70481f2ea4c380995271f4b338da0968", "resources/qss/windowsvista.qss": "80e75b0e7ec8b7dd7a23b4bf75ab8ef4", "resources/screenshot/check_data.png": "e44471c2514f8a048df2a89a9df2e795", "resources/screenshot/code.png": "814df667bb94b7d2ae844d938ec916b7", "resources/screenshot/group.jpg": "0b3959e958742a35baeaec5044a95584", "resources/screenshot/main.png": "1b6f8a5331f0c5954a86b9024f070568", "resources/theme/default/icons/3d.svg": "61157c75fbfde955215c93e2daf93551", "resources/theme/default/icons/action.svg": "61ef2e8adb0b2bfe5928aa14b376fec3", "resources/theme/default/icons/addannotation.svg": "503b6bffffae70dfe67068cd24f5d88e", "resources/theme/default/icons/added.png": "2acf12376249833027eadb29c9e8a58f", "resources/theme/default/icons/addFeature.svg": "bcc4056d0d52f3cb44acbc81858b6099", "resources/theme/default/icons/addnode.svg": "763e098d49dec789969c25b7e69ab629", "resources/theme/default/icons/addpath.svg": "e700804c812dfe2bdc54798b11814924", "resources/theme/default/icons/add_col.svg": "eee7249c440e048cb1796d3e8d1c949b", "resources/theme/default/icons/add_row.svg": "620b902ccebc31be4d270e7124fb2e48", "resources/theme/default/icons/align_bottom.svg": "f4fb3a0c1bb022440e3489f75e69c390", "resources/theme/default/icons/align_horCenter.svg": "cef02a64b8f66064fc237ea34b41536f", "resources/theme/default/icons/align_left.svg": "2275c71a5bd808f0379098e0663c1aee", "resources/theme/default/icons/align_right.svg": "aeb304bf474f3402804c6c113d197101", "resources/theme/default/icons/align_top.svg": "621e60f54d5dcd50a28aa7aa3be3ffe4", "resources/theme/default/icons/align_verCenter.svg": "a8ea8b762c89efcbeeb78f3c3f72b4dc", "resources/theme/default/icons/allowedit.svg": "3c2b136910b2403dc0da5cd1a54fba0c", "resources/theme/default/icons/allowedit_layer.svg": "fe3079b3a271ac07bea92c298fe75452", "resources/theme/default/icons/allowsearch.svg": "1504eb4f1da9532cbb86754b39c0f410", "resources/theme/default/icons/allowsearch_layer.svg": "e126ff953c35229e30444c8d6e4ae2e8", "resources/theme/default/icons/allowselect.svg": "5b3dbfe62ddbf003af7efb4f429cb0fe", "resources/theme/default/icons/allowselect_layer.svg": "62cbf0796e8e284f4df30d1575d4c16c", "resources/theme/default/icons/allrecords.svg": "75b5c30ce3932f21ae7b16013ffc4c8c", "resources/theme/default/icons/annotation.svg": "1ae382f88b44847332e9826ba46c5397", "resources/theme/default/icons/annotation_no.svg": "c3e5a2d3179e075c49b13015eb5b7e52", "resources/theme/default/icons/anova.svg": "14b71522f19c12ffde5e46202fcf6bc3", "resources/theme/default/icons/appstore.svg": "e2a48d1643f244e957ce9dba9e55a72f", "resources/theme/default/icons/app_down.svg": "5af2c894a37156084df6218bf562c626", "resources/theme/default/icons/attributerender.svg": "216c03c70731a3d0936649486d1ac943", "resources/theme/default/icons/attributes.png": "5a9ddb1cb0d0b3e35bb8f5bec3c7c511", "resources/theme/default/icons/basicStatistic.svg": "e84a179215a06d27e8547e52620a4859", "resources/theme/default/icons/beginfly.svg": "260f78da60b0d46b64fdd9b942167066", "resources/theme/default/icons/bottom.svg": "6fbc8f02bc979600df8a6a8d24bd3ae8", "resources/theme/default/icons/camera.svg": "27ae116857da0dc4bc1bf2a45a899dd9", "resources/theme/default/icons/canshu.svg": "3dd688f4c48e9c5e32ea9ef81ac8ac86", "resources/theme/default/icons/catalogAttributeTablePageFirst.svg": "0872ecfef2107aeaafbcdf0959d1a21f", "resources/theme/default/icons/catalogAttributeTablePageLast.svg": "18d9cf57a8fa52b6fabb08565ba0177f", "resources/theme/default/icons/catalogAttributeTablePageNext.svg": "ceeedf9b8523a724ea5cd524fa733211", "resources/theme/default/icons/catalogAttributeTablePagePrevious.svg": "164f4dc04bc3e9e8c508505027d408fd", "resources/theme/default/icons/catchline.svg": "6b4ee518bf6ba27bdd6cf44188ca864c", "resources/theme/default/icons/catchpoint.svg": "8191852167c41f1408891d74f233110c", "resources/theme/default/icons/centerpointlinkage.svg": "8fe7f934cfdca995811c3170017607d1", "resources/theme/default/icons/changeAttribute.svg": "a1b9c1ff1e2f6d7dad0a3873a52ce13f", "resources/theme/default/icons/changeFeature.svg": "556de2ee6e10e39b6e369c6c7a271be3", "resources/theme/default/icons/changeGeometry.svg": "1628a5992db6891c26741e3356817473", "resources/theme/default/icons/chartStatistic.svg": "30047525eb626a274f074399334f4b30", "resources/theme/default/icons/check_update.svg": "d6e33e022ada06b9b3abf33de637a573", "resources/theme/default/icons/Classification.svg": "658e28143d9a717f0e2b951cc67111ae", "resources/theme/default/icons/clear.svg": "1d841dcc502211799d9055a87cf3c55d", "resources/theme/default/icons/close.png": "20780f1ae86a0f942f448f69e16dbd3c", "resources/theme/default/icons/close_white.png": "7b567f8cd3293ae156df417e5a21dc6b", "resources/theme/default/icons/Clustering.svg": "012392d9351d8983b29e99b42145c77b", "resources/theme/default/icons/cmd.svg": "0ea2aa79bb81ae297c97d356072745bc", "resources/theme/default/icons/column.svg": "006f3f2cbba5c9fc0fcc223883b00fc7", "resources/theme/default/icons/column_2.svg": "4a2981704a879a016c4a8dc88710b5f2", "resources/theme/default/icons/community.svg": "3cdd0bf3826dfc1ad073d7ad449bc93e", "resources/theme/default/icons/compare.svg": "ddc98ac27fe119cb0d4acbf6a04affdc", "resources/theme/default/icons/conflict.svg": "ffe7f2d515ba91b5920547c0193b6474", "resources/theme/default/icons/conflicted.png": "1f83c2acd291df5d1d10ea67930145fe", "resources/theme/default/icons/continuefly.svg": "4346811bddecadf1ff6db3d87c1447d6", "resources/theme/default/icons/copy.svg": "9ad66acb6dcb55bf923628b188ff5f64", "resources/theme/default/icons/copyElement.svg": "cbe746ec2f6abd3a4b5a020943acf0a1", "resources/theme/default/icons/csv.svg": "9867d63fe22cc88b9c47159b0fd04791", "resources/theme/default/icons/csv_gray.svg": "16c6562ded6d0f6e0ee26005b9bc4c7f", "resources/theme/default/icons/currentindex.png": "dc987f99c44a68ca57c15dd784babc24", "resources/theme/default/icons/database_config.svg": "fa799903a0c570e3a09fe7efa6d3a276", "resources/theme/default/icons/dataSourceConfig.svg": "6a3bd375f33fe348498e053f3bcae8f5", "resources/theme/default/icons/dataspecification.svg": "1e6d1fcac3f8dc14a0daffc947c4d14a", "resources/theme/default/icons/data_desc.svg": "5913822d76a61455714c15e6132eb3e6", "resources/theme/default/icons/data_desc_2.svg": "1f51941660ac732dda207d022c26ee04", "resources/theme/default/icons/data_info.svg": "9ead14ad7a1cdf63b5702c48e05e78d3", "resources/theme/default/icons/data_join.svg": "406e169ebb9b8af93d286581f59f0245", "resources/theme/default/icons/data_missing.svg": "c7f2e5a1dc938edae131a30c6b56b711", "resources/theme/default/icons/data_partition.svg": "d8cbc4400259d84850ec97154dacd688", "resources/theme/default/icons/data_role.svg": "5002bd4ed773126cf2d4c34c4bb4541d", "resources/theme/default/icons/debug.svg": "e2ca8b334c69f413637945ebe871d696", "resources/theme/default/icons/debug_red.svg": "ab77c95b9f382e8f6fdb09da83795dfc", "resources/theme/default/icons/deleted.png": "99073a344eb6184343073b14eb3b4338", "resources/theme/default/icons/deletedata.svg": "0809bd41dd7489df3a1dc2a8e976f888", "resources/theme/default/icons/deleteFeature.svg": "5acc1452971d56857d219e9906341904", "resources/theme/default/icons/delete_col.svg": "c64463979c0dabb232a424810a41fad0", "resources/theme/default/icons/delete_row.svg": "28c35f8dae78bb25a1d8b4981996ec5d", "resources/theme/default/icons/dependencies.svg": "150af39be2e05f96916e16c20f7eacbc", "resources/theme/default/icons/diagram.svg": "93a8d692b994662c68e9374ca42fbfe0", "resources/theme/default/icons/disallowedit.svg": "1d2ea40eb876a1b57edfab5937150a23", "resources/theme/default/icons/disallowsearch.svg": "48d814b63bb64b53e1bbe5a745ad2efa", "resources/theme/default/icons/disallowselect.svg": "780b169700eec6b08dc575e3a0d01275", "resources/theme/default/icons/display.svg": "a370d879c046105fa360f2bee6809a10", "resources/theme/default/icons/distribution.svg": "53e6bcf151c4cb9f89f80093c256ea62", "resources/theme/default/icons/donate.svg": "f72f24a7e8db14c7c98394d6c8dfc860", "resources/theme/default/icons/down.png": "303cd2548ab3be7109c7fbab16730725", "resources/theme/default/icons/downWard.svg": "0bf14903bcbdef20890f936ac7855c61", "resources/theme/default/icons/drawline3d.svg": "64fc6cdf5c361bb7a6f2b5c7dadc3934", "resources/theme/default/icons/drawpoint3d.svg": "452946e331372a97bdd09df9f5f86a71", "resources/theme/default/icons/drawpolygon.svg": "e54865783b8e639d6b8aef7f0eb0597f", "resources/theme/default/icons/drawpolygon3d.svg": "4dd900f92c31b8254c18371b72532011", "resources/theme/default/icons/duoyuan.svg": "5d84f2bcfc84ae85a6fb1a7a5ee08b28", "resources/theme/default/icons/E-matlab.svg": "11fb165a87912c326ba2a5f27d4971b8", "resources/theme/default/icons/earth3d.svg": "2bbc786eaadf43a74a592ec82b14ba03", "resources/theme/default/icons/editannotation.svg": "e73609dbea16a474b62e07344a34264d", "resources/theme/default/icons/editConfig.svg": "2e65b8ba1573593af29ee2fcf1a54288", "resources/theme/default/icons/editmetadata.svg": "c89258a047ac04855464e0a2cb883f8e", "resources/theme/default/icons/endadding.svg": "ba25cd175a3a98a0d2c7b81f125975df", "resources/theme/default/icons/endfly.svg": "8aa793c8cc77046cdfd7d70307c83731", "resources/theme/default/icons/errorInfo.svg": "b8d8db18986b89bf3b1b980fa613462b", "resources/theme/default/icons/excel(1).svg": "9eb4ba787a3e62e477772f0c4a93f366", "resources/theme/default/icons/excel.svg": "ea00b0b78bb93119c7eafe98a79a8988", "resources/theme/default/icons/ExcelFile.png": "c488f98763fb577be56bc955c928c947", "resources/theme/default/icons/excel_gray.svg": "a1135950f1896aa177e00eb10f44f910", "resources/theme/default/icons/expendDown.svg": "fe6b296aa4020dda5c5a870bb7596235", "resources/theme/default/icons/expire.svg": "b7055d451d7bf23ddffcd974ceb39225", "resources/theme/default/icons/fastCollect.svg": "b929bc85fd17ba612275008238d70e53", "resources/theme/default/icons/favorites.svg": "589f0e80dde61b86c2470b3799f8f860", "resources/theme/default/icons/feedback.svg": "f695df9b7313ab60be2c3c46b4fc7468", "resources/theme/default/icons/field.svg": "d168f3a212323820267956d6de9ca9d7", "resources/theme/default/icons/file.svg": "58f428cb524544f4f3993bb37f2ef1ba", "resources/theme/default/icons/file_gray.svg": "fe90e95c15020d95c8f5690f3cb5fae3", "resources/theme/default/icons/filter.svg": "e87a00609c494f030812f3c00a2e6e35", "resources/theme/default/icons/final.png": "87457d3035ab89d82a6b57976e9b0245", "resources/theme/default/icons/find_replace.svg": "665177ccc10ce22b469a118beec97dd6", "resources/theme/default/icons/first.png": "f0edce38b01be51e2dfea42df74d8f23", "resources/theme/default/icons/flight.svg": "8857e62ab11e2cd3ae039db12d61b0d4", "resources/theme/default/icons/float.png": "5f31043d787d15eab504b59bef221e6d", "resources/theme/default/icons/float_white.png": "89609a354d4d717f4e578528dfd5547c", "resources/theme/default/icons/fly.svg": "9761c5cef3b2a4a59ef4ed762c62ad7a", "resources/theme/default/icons/flyaround.svg": "c8a2ba65a3a3ea28c710727327b062b6", "resources/theme/default/icons/folder.svg": "b7e11fde0a4f073c8255d911214aecd3", "resources/theme/default/icons/folder_yellow.svg": "9d3d6d38fa617dfeedec734a5c5c13e1", "resources/theme/default/icons/foundrecords.svg": "aeb3afe2862d00fdeaf810cc479a9704", "resources/theme/default/icons/general.svg": "8d0ffe48967c6985486949410f399c74", "resources/theme/default/icons/generalConfig.svg": "ce54c065114cc47016cd6a382457c1b8", "resources/theme/default/icons/GeneratingAdministrativeRegion.svg": "444cf5e4f7daa640706014ae3f55e307", "resources/theme/default/icons/geodbms.ico": "fef8bfe0713f757762dd9ce837b48c1c", "resources/theme/default/icons/geomap.ico": "dd4b17c0ec69f51b039c51b6e0887c31", "resources/theme/default/icons/gotoview.svg": "da5e78b70c58d112189832fd5cdad9d9", "resources/theme/default/icons/help.svg": "6c9e88b86a30ea8df99ecb75891990d2", "resources/theme/default/icons/help_doc.svg": "1491066f6ba737fb319d4b3aeec96402", "resources/theme/default/icons/histogram.png": "334c464f233edaf5d54c0e1a78289559", "resources/theme/default/icons/home_site.svg": "a9fdb97167551132fe16dd05e7668643", "resources/theme/default/icons/html.ico": "ef2d86df09c9fc6d671e12b5deaf49e6", "resources/theme/default/icons/html.svg": "8158b710d8854547cfe6e2ece1ab3e22", "resources/theme/default/icons/import.svg": "e38ca75a6ffcf334ea461b245f523444", "resources/theme/default/icons/importConfig.svg": "f60db217ccfce27088257eeb9b39d236", "resources/theme/default/icons/import_database.svg": "42ae4560410eb3b900a99360d0019fba", "resources/theme/default/icons/indent_left.svg": "13d4c1ee641fc364bf20be31e9c277a7", "resources/theme/default/icons/indent_right.svg": "a9bc26d41494a2760b186614cc5a177d", "resources/theme/default/icons/index.svg": "63797de724460b5527354bcc9df5d79d", "resources/theme/default/icons/info.svg": "59366b0c10bbce5c3dd9dfec1860d32c", "resources/theme/default/icons/install.svg": "699200cfd59967ff740f1e3076795f0f", "resources/theme/default/icons/invisibleMap.svg": "f5e5e045de80b10006eca992d6f156c1", "resources/theme/default/icons/javascript.ico": "237b59ab21d39cbf9d56481abd6ea724", "resources/theme/default/icons/jiashe.svg": "6195487f581d0b17bf8bd5e440ac3152", "resources/theme/default/icons/join.png": "ae8569cf1fc1762af272861ffbf1a16f", "resources/theme/default/icons/JoinMapTable.svg": "c7277790649b9dafcba2531c8ec43630", "resources/theme/default/icons/jump_line.svg": "36d10e5f905da4134c668d1baa7b0eff", "resources/theme/default/icons/jupyter.ico": "fcd12a895e826c9bf68ec69a3801a6b5", "resources/theme/default/icons/Jupyter.svg": "5e31f82bc08dff94a0f7a19492ae3e57", "resources/theme/default/icons/lab.svg": "bb81cefd8b4592063bf604536517bbe4", "resources/theme/default/icons/labelingMultiple.svg": "60b47ac95c42db86f0a1996619aa5710", "resources/theme/default/icons/labelingNone.svg": "aec873e7c3a156d25d90ba14332250fe", "resources/theme/default/icons/labelingSingle.svg": "4eb6031628115e94af14c75ed7743277", "resources/theme/default/icons/labels.svg": "2f07179dee5125cdc7720364cc83fa4d", "resources/theme/default/icons/LandSurveyAutoNumber.svg": "d4fa015fe319301146a20d569319d197", "resources/theme/default/icons/layerBrush.svg": "30f0e853f0588bb65f58b6e407f26e73", "resources/theme/default/icons/layerConfig.svg": "ca61ff1339a5aab82167c3b3834f6fc8", "resources/theme/default/icons/legend.svg": "d34ddf6094becbbd0c65321a1f154bfd", "resources/theme/default/icons/lockorthoview.svg": "39417a4ce7986b55c908a36268185ca3", "resources/theme/default/icons/lockview.svg": "89f5b906db5c12867af42e06ccb1d18e", "resources/theme/default/icons/lost.png": "e5f28a07326ba62314e4964833d853c8", "resources/theme/default/icons/mActionAbout.png": "1b50df16c376e6e78454236403627aeb", "resources/theme/default/icons/mActionAbout.svg": "1e1360e32f95eee26b8f41e69339cab8", "resources/theme/default/icons/mActionActiveStyle.svg": "302386637bb5cd7bf856ad1ce6ea42fe", "resources/theme/default/icons/mActionAdd.svg": "340f3bb6a7682527aae76150aabff11b", "resources/theme/default/icons/mActionAddArrow.png": "95e75cb331f8ff2ae412b474db6f55bb", "resources/theme/default/icons/mActionAddArrow.svg": "06246d25e27308ad26aac55af7eb9713", "resources/theme/default/icons/mActionAddBasicCircle.svg": "f8a5e280035bbfaa025028e4eed1c703", "resources/theme/default/icons/mActionAddBasicRectangle.svg": "85605dbd86923fbed31379f828d995bc", "resources/theme/default/icons/mActionAddClassification.svg": "5d91e7c199fdfc94b71882b743e084f8", "resources/theme/default/icons/mActionAddClassificationCode.svg": "71811be70f15711a0890b9f6a407dab5", "resources/theme/default/icons/mActionAddCustom.svg": "cc118673a1e3b2e74fc57da54a9aa35e", "resources/theme/default/icons/mActionAddDataSet.svg": "e15df2fe98dfcddd91f6edc44b17f719", "resources/theme/default/icons/mActionAddDataTable.svg": "3944027676b922267a24159091390090", "resources/theme/default/icons/mActionAddDicItem.svg": "d71bb80327c14ba5e06e965b15864b48", "resources/theme/default/icons/mActionAddDirectory.svg": "f4a64c10d63eb102b6088281748dedf5", "resources/theme/default/icons/mActionAddEnumRange.svg": "9969caf289186d9cd546400aa5f16079", "resources/theme/default/icons/mActionAddGroup.svg": "f308932c912ab0483324f89d5ecce6c0", "resources/theme/default/icons/mActionAddImage.png": "94e43a1d6b956c2b35625a5ea760261e", "resources/theme/default/icons/mActionAddImage.svg": "28bb04fd672efe9f1a52f2901f54de6f", "resources/theme/default/icons/mActionAddLayer.svg": "840395598b646deb2c747f78d305e324", "resources/theme/default/icons/mActionAddLegend.png": "8349d32174a82160f1c812641855fbb9", "resources/theme/default/icons/mActionAddLegend.svg": "d756c3c84d9a1e613341676f8c3e8965", "resources/theme/default/icons/mActionAddMultiPoint.svg": "f9ade46e1458d5942ae557420592f836", "resources/theme/default/icons/mactionaddordergroup.svg": "a7e40299ca8607fd8ec9573f66cb95a5", "resources/theme/default/icons/mActionAddPoint.svg": "c85722428ece40150082f3d81bf170f3", "resources/theme/default/icons/mActionAddPolygon.svg": "83662dc01e024450dce5ecfaf1514edc", "resources/theme/default/icons/mActionAddPolyline.svg": "5d61a265d4c1e899cd171721d3b006ce", "resources/theme/default/icons/mActionAddRangeRange.svg": "99b93618e2906303244bc6a2ed20d3e0", "resources/theme/default/icons/mActionAddResourceWizard.svg": "9318a164ee340e04da837175aeea8820", "resources/theme/default/icons/mActionAddScaleBar.svg": "43afed23afb8200c15a543df1a450f3c", "resources/theme/default/icons/mActionAddSchemeData.svg": "c86e2f3e2356fd1ad82cdda941a9ad7b", "resources/theme/default/icons/mActionAddStyle.svg": "c60df25cbed4067a5ac0bc320463b816", "resources/theme/default/icons/mActionAddText.svg": "b6e185e764c5da36528cd7a83654d17d", "resources/theme/default/icons/mActionAddToCanvas.svg": "4f222a6648efa565f051aa2617caca5e", "resources/theme/default/icons/mActionAddVertexTool.svg": "73d0e0aa645810d8737db7604fb48d98", "resources/theme/default/icons/mActionAdjustLayers.svg": "42d197117a2af9818fcbefc935e03fa2", "resources/theme/default/icons/mActionAggregateNode.svg": "0e8e850ce17baa5e7c7aa3b238279790", "resources/theme/default/icons/mActionAligningToLine.svg": "6844c8fc2e7d023ebd31bfb23c907a9e", "resources/theme/default/icons/mActionAllEdits.svg": "1a13a6fe0c38adcdbbc315b963805233", "resources/theme/default/icons/mActionAnnotationImport.svg": "ed08c23f1bb2ea1dc03aee1496693ec6", "resources/theme/default/icons/mActionAnyDLAnalyze.svg": "cbcf45db82108f1e0ec97691d2b021ed", "resources/theme/default/icons/mActionAttributeBatchTool.svg": "f8853792345634f2ccbf3d5b3cc2ae9f", "resources/theme/default/icons/mActionAttributeBrush.svg": "bc8e8e7de8a8b5c4d75891072f58a9c9", "resources/theme/default/icons/mActionAttributeIndexManager.svg": "de8c1f6a81ef7a0dc047131f2b3bccca", "resources/theme/default/icons/mActionAttributeSelect.svg": "0a0e325e465cfcd8eb9a502e4fc9aac2", "resources/theme/default/icons/mActionAutoChange.svg": "2add0a98ec45eb1f992348a61b77107c", "resources/theme/default/icons/mActionAutoCutPolygon.svg": "5142adcd7b3d1702cc05eb4732bda7a5", "resources/theme/default/icons/mActionAutomaticClosure.svg": "100464d989b5520597726da001d2e6b7", "resources/theme/default/icons/mActionAutoParallelPolygon.svg": "d301512aec2983ee3fa88426f2c0583d", "resources/theme/default/icons/mActionAutoParallelPolyline.svg": "419b2e6153752735a075f87b2c6976af", "resources/theme/default/icons/mActionAutoProjection.svg": "1cfe66813280c607e1afc8715fa98983", "resources/theme/default/icons/mActionBackLastLevel.png": "0e06243d91122f7a759af354689e786c", "resources/theme/default/icons/mActionBackupDatabase.svg": "ab331f009a206badb5b6eb214ad0e555", "resources/theme/default/icons/mActionbatcgSetUniqueCode.svg": "3d5f12cd66429d20dc6b16d2e4af6a27", "resources/theme/default/icons/mActionBorderPolygon.svg": "70dd6b1cafe2eb371c897c2509ec0406", "resources/theme/default/icons/mActionBreakBySinglePoint.svg": "a4f86b74ab5d94b4bf90c8678155c8de", "resources/theme/default/icons/mActionBreakByTwoPoints.svg": "9faa1aeaf46c1a8a5f1a39a8f077293d", "resources/theme/default/icons/mActionBreakIntersectantPolyline.svg": "5891ffbba64c08cf4db8760d782465b1", "resources/theme/default/icons/mActionBreakWeld.svg": "f7f43dad61d9c4e3322eca488da3ab92", "resources/theme/default/icons/mActionBrush.svg": "e66b98adb06f1d8d702edb7f9ec5546f", "resources/theme/default/icons/mActionBufferAnalysis.svg": "d80ab987bd4a1fc371e2776e66425406", "resources/theme/default/icons/mActionCalculateField.svg": "f32837750c5840f154a2a749c8aade44", "resources/theme/default/icons/mActionCatalogManager.svg": "99e161a7b7367a9fa2929009134a653a", "resources/theme/default/icons/mActionChangePolylineByExitline.svg": "e08d8787b31f2c90f7378b382311f23a", "resources/theme/default/icons/mActionCheckAll.svg": "438f359d854c7ebedf6be6b88f7d0f6c", "resources/theme/default/icons/mActionCheckAndMaintain.svg": "a7e9601b46669c45f9096f011ab41bd1", "resources/theme/default/icons/mActionCheckNode.svg": "b14495714a6c980374bb8482d3211444", "resources/theme/default/icons/mActionCheckResult.svg": "2cf1b01c206622535c1e1825eb2b69cf", "resources/theme/default/icons/mActionCircularStringCurvePoint.svg": "310cea9398a663a4d4b13e528fa7ce5d", "resources/theme/default/icons/mActionClassification.svg": "995a538a5a6002821d6c50dc9a81fe63", "resources/theme/default/icons/mActionClearEdit.svg": "1c5a1c68aac83d68129868577ea30f7e", "resources/theme/default/icons/mActionClearLayer.svg": "32eba1e6735860eb142fff43a48eed54", "resources/theme/default/icons/mActionClearSelect.svg": "acc58e9be78e0153b7d7d1ed921b6230", "resources/theme/default/icons/mActionClose.svg": "e85dae4f037b08290dd34d668bcf06b3", "resources/theme/default/icons/mActionCollapse.svg": "3d2eba0c9ca35b25ec706a3e970a8b44", "resources/theme/default/icons/mActionCollapseTree.svg": "b39d593ea4774c1238ed6cd69f12b11f", "resources/theme/default/icons/mActionCommit.svg": "f354d7a233e4a5923580acbe25264d1f", "resources/theme/default/icons/mActionCommonNode.svg": "f2ab046d5257ee17f76363de01472e36", "resources/theme/default/icons/mActionComposeExport.svg": "53d0f2eaa80a97421ea812d32f5d9670", "resources/theme/default/icons/mActionConfigProperties.svg": "aa6aa2c381d8636a19e815a347831ed2", "resources/theme/default/icons/mActionConnectToFolder.png": "84a044edac968fdf06ecc30455f9b57d", "resources/theme/default/icons/mActionCopyMapImage.svg": "18260fca2fda975633899e07772ec225", "resources/theme/default/icons/mActionCreateDbConnection.png": "1194f60c3604a75a234b7c5829a9f623", "resources/theme/default/icons/mActionCreateInterNode.svg": "7d8cbee95202587a4593044f752438d2", "resources/theme/default/icons/mActionCreatePolygonBySnap.svg": "afd2ee4841eb4162f6d40825768359ee", "resources/theme/default/icons/mActionCreatePolylineBySnap.svg": "e80bb5083740e9bbf248b9ba3b28c988", "resources/theme/default/icons/mActionCreateProject.svg": "0d1f6ed64f39f7f3e3cacaeb376ac952", "resources/theme/default/icons/mActionCreateSpatialIndex.svg": "19063ef92e585e6b89139b95d8bc4ebc", "resources/theme/default/icons/mActionCurrentTask.svg": "37591573decfe551c7cb7418e2e8acad", "resources/theme/default/icons/mActionCustom.svg": "4f20d0450747420cbd2e9c9269193bee", "resources/theme/default/icons/mActionDatabase.svg": "5356e364576af2c38e4216283342db4f", "resources/theme/default/icons/mActionDataComparisons.svg": "05fd271af6c134f6872c80c489064d60", "resources/theme/default/icons/mActionDataExport.svg": "a4adf859430ab231122e24d6d6140459", "resources/theme/default/icons/mActionDataset.svg": "a06eea07e31055fe638120092fd03fed", "resources/theme/default/icons/mActionDataSource.svg": "899c06c28703ea8b15798c799a97cb02", "resources/theme/default/icons/mActionDataSourceManager.svg": "e265e51dc79a614afbba2d9a03eca71c", "resources/theme/default/icons/mActionDeleteAttribute.svg": "f487f09dcfad1b19da3cd46ff020c8aa", "resources/theme/default/icons/mActionDeleteBookmark.svg": "63a5159656718a85300368e95a87c965", "resources/theme/default/icons/mActionDeleteClassification.svg": "328e502956af43b1506928b8e03fc496", "resources/theme/default/icons/mActionDeleteClassificationCode.svg": "7cb7a62ac5e8be4028316c1c3e8836bc", "resources/theme/default/icons/mActionDeleteCustom.svg": "2c3e544e2be400c9f1fc224eb19baa6c", "resources/theme/default/icons/mActionDeleteDataSet.svg": "cf05da90c76989fe5796f0bfa9c69fd6", "resources/theme/default/icons/mActionDeleteDataSpecification.svg": "f4e03936c7564d14d752c7b9570726df", "resources/theme/default/icons/mActionDeleteDataTable.svg": "79ff705674f36ee519327ae6c51ae3d8", "resources/theme/default/icons/mActionDeleteDicItem.svg": "40f4b24ebc5ee91d2a05b715dc5ef94e", "resources/theme/default/icons/mActionDeleteLayer.svg": "10c565c23727bc79f0a3b3653c539e27", "resources/theme/default/icons/mActionDeleteLink.svg": "2d21c91ca791aed38b8135cf405fada5", "resources/theme/default/icons/mActionDeleteRange.svg": "f26af8d0a9917bb3d33bdbdd7665f2b9", "resources/theme/default/icons/mActionDeleteRevision.svg": "7f32ea7fb09ffa5851f57a64b548cf76", "resources/theme/default/icons/mActionDeleteSelected.svg": "27cc93f001805b310dd17a6cd761417f", "resources/theme/default/icons/mActionDeleteStyle.svg": "e8529586cd6c6c35725ae9ddf42678f8", "resources/theme/default/icons/mActionDeselectAll.svg": "acc58e9be78e0153b7d7d1ed921b6230", "resources/theme/default/icons/mActionDiagramStatistic.svg": "ba795b1a3c88b9ecc48bf3533793084b", "resources/theme/default/icons/mActionDicManage.svg": "5711827d34bf8c7f45bb9473339c9d5d", "resources/theme/default/icons/mActionDictionary.svg": "9f3227fd9fd4856b21ed0203f57d462e", "resources/theme/default/icons/mActionDictionaryManager.svg": "31af9da87c0ef66edf88279dc0979691", "resources/theme/default/icons/mActionDirectImport.svg": "2705f7d2b595f44bf7d41ba5469f8ecf", "resources/theme/default/icons/mActionDirectory.svg": "543010d3f4891a87337ea4a0553b6cc4", "resources/theme/default/icons/mActionDisperseLegends.svg": "44e0028546a1d892193bf85c1fc9ee68", "resources/theme/default/icons/mActionDownloadData.svg": "73cf487f43c6c67708755c77d1031b16", "resources/theme/default/icons/mActionDraw.svg": "79d6fde375c1d3a7c8be1e8ec5fc02d2", "resources/theme/default/icons/mActionDrawAnnotation.svg": "1ead0a1082e9fbae833bcfdd520f1f6f", "resources/theme/default/icons/mActionDropSpatialIndex.svg": "b22f1baf81a17fdc104b1bf05fd5c5ca", "resources/theme/default/icons/mActionEdgeTool.svg": "e51484e30111c77e3f34a0e367340041", "resources/theme/default/icons/mActionEditConnection.svg": "b0f644dd694a0c6554d1e1dcac139845", "resources/theme/default/icons/mActionEditCopy.svg": "83c8c2506e3d53b1b0b7865d1873a633", "resources/theme/default/icons/mActionEditCut.svg": "23ce1b2d297515aefe2dea9533184fab", "resources/theme/default/icons/mActionEditPaste.svg": "e59391b2630274e267ad10bb0a40a30d", "resources/theme/default/icons/mActionEditPolyline.svg": "4a3413a2c400b402733577b00c4e4a57", "resources/theme/default/icons/mActionEditSelect.svg": "20ed9f0380e99e271ccc04bf8590d084", "resources/theme/default/icons/mActionElementAlignment.svg": "da6e0b52d636403003a8bf06d81426bd", "resources/theme/default/icons/mActionElementOrder.svg": "9a4846b8d8c64c5a6b839fb5f5f2acab", "resources/theme/default/icons/mActionEmpty.svg": "7d03c7a62ba3b69a498063a52e7a0480", "resources/theme/default/icons/mActionExpandAll.svg": "5ce3b9872728fe1ab2db08881b6696e0", "resources/theme/default/icons/mActionExpandTree.svg": "8df56726928510b542d479f9d65bf2a4", "resources/theme/default/icons/mActionExport.svg": "9262fbccd6dce910b05542130d5b5d3d", "resources/theme/default/icons/mActionExportFont.svg": "fc19d534b16e7e87d74304dc9206913d", "resources/theme/default/icons/mActionExportGeometry.svg": "490f60da490788568dfcc1fb3ab8d57a", "resources/theme/default/icons/mActionExportPDF.svg": "7079bf9f8c4d4ef7ebf3fc26895c651a", "resources/theme/default/icons/mActionExportStyle.svg": "c3ebc4b4d9eef274c460e40db01734e1", "resources/theme/default/icons/mActionExtensionIntersect.svg": "b2b45155b3775a6082f0432db8667476", "resources/theme/default/icons/mActionExtensionPolyline.svg": "e5fdd4473d3a496bd3a0e67dfafc9902", "resources/theme/default/icons/mActionExtractFeatures.svg": "d756e45f408817a284947e1dc2015564", "resources/theme/default/icons/mActionFastScan.svg": "4a055d8089b712ba73b61939c3248f14", "resources/theme/default/icons/mActionFeatureClass.svg": "12960e7e5a7abafae89f6c75fe250172", "resources/theme/default/icons/mActionFeatureImport.svg": "bc1bf048cb86d53ee263523e8d49a33e", "resources/theme/default/icons/mActionFieldAssignment.svg": "55face7af26423b494df7490d9c9a1ef", "resources/theme/default/icons/mActionFileExit.svg": "014da3686c1d21b0df59f2b6d70e5a69", "resources/theme/default/icons/mActionFileNew.svg": "359e36e8c6e7793d550a4050765a169d", "resources/theme/default/icons/mActionFileOpen.svg": "d80181aaa8aa890cb0b7e48319a8d085", "resources/theme/default/icons/mActionFileOpen_small.svg": "e2471acb5d431a1203fe3c3956a4db79", "resources/theme/default/icons/mActionFileSave.svg": "68f08f30bdd326df72b092be223a0d70", "resources/theme/default/icons/mActionFileSaveAs.svg": "9836c3f4771b653b1ee967cae793b742", "resources/theme/default/icons/mActionFillHoll.svg": "7ef3bcc1919ef6f9f132ece12e007827", "resources/theme/default/icons/mActionFilter2.svg": "3ec913bd01c74a188cb4c055fbc03c14", "resources/theme/default/icons/mActionFlash.svg": "9bfe496e74b6e9a43be5908d499af2be", "resources/theme/default/icons/mActionFoldAll.svg": "740369ae0104d790e9d5fd18edd3f159", "resources/theme/default/icons/mActionFolder.svg": "1d4ea7410800af4f5b979135c4d14529", "resources/theme/default/icons/mActionFormView.svg": "f7e61b110fffd58a3803cba75c95aa40", "resources/theme/default/icons/mActionFull.svg": "de410eb887d59537eb5a97991fc1eebd", "resources/theme/default/icons/mActionGJB50000FFT.svg": "26fd67a57ea8dbbf034212047cd73c2e", "resources/theme/default/icons/mActionGoto.svg": "4cc2023f7b3f7813af02c1bcaf11ef9d", "resources/theme/default/icons/mActionGrid.svg": "305cb21216f9e8638e5521ba83f54ff2", "resources/theme/default/icons/mActionGridCheck.svg": "5cd471cd29ff479f87c6aac170c2c117", "resources/theme/default/icons/mActionGroup.svg": "30e60e1533289e2052cf4928583757c8", "resources/theme/default/icons/mActionGroupLayer.svg": "71a8905d793790d9303f2d32815c9079", "resources/theme/default/icons/mActionGroupLine.svg": "a3f83d7e3afba41c961550d99ef97136", "resources/theme/default/icons/mActionHelpContents.svg": "dfcc6f3e7a95c9fa377a24306877dda6", "resources/theme/default/icons/mActionHideAllLayers.svg": "16f373cad1b7b644477951a7ed66351b", "resources/theme/default/icons/mActionHidenFromBrowser.svg": "d38d6063e070d2d61dd6f55713896ab9", "resources/theme/default/icons/mActionHideResults.svg": "3eaa5c01dc7b1bacd0a6e5db58d1fdeb", "resources/theme/default/icons/mActionHideSelectedLayers.svg": "9d5f022d1375227b18a4fe01c6bcdb30", "resources/theme/default/icons/mActionIdentify.svg": "752e8589c7c43e261247a04eebfd130d", "resources/theme/default/icons/mActionImport.svg": "eccb61c61f90c7dd5be6f82059c499c1", "resources/theme/default/icons/mActionImportGeometry.svg": "18642deca79e43f652442d195b59fca3", "resources/theme/default/icons/mActionInspect.svg": "4f20d0450747420cbd2e9c9269193bee", "resources/theme/default/icons/mActionInverseCheck.svg": "9115eb0e5c06d728cf83af898f4a3f79", "resources/theme/default/icons/mActionInvertSelect.svg": "8f2be589133713a5d3ff36e50c1f01c0", "resources/theme/default/icons/mActionInvertSelectedLayers.svg": "9752a3091fe12e8fa6c6c07a53eb96b4", "resources/theme/default/icons/mActionInvertSelection.svg": "6149896f7ea1dbdade1677b31fcf5016", "resources/theme/default/icons/mActionLabelManager.svg": "d5d5d38222a6f64eca412317a25f962f", "resources/theme/default/icons/mActionLayer.svg": "f156bfe69e1e2508e5f0bf15db9ba44e", "resources/theme/default/icons/mActionLayerClassification.svg": "6aec85cd21332ccce4de83dcbc78a0c0", "resources/theme/default/icons/mActionLayerManager.svg": "add1fed077998d1e15ea21b0476024a9", "resources/theme/default/icons/mActionLayerSaveAs.svg": "baf726688146979e6d52548de7214a3d", "resources/theme/default/icons/mActionLayerSaveAsFile.svg": "b17ac7d94a0994f542a783cf8fb0d33b", "resources/theme/default/icons/mActionLayersOverview.svg": "4813c1d18f037f63dae432d4ef253cfe", "resources/theme/default/icons/mActionLayerTreeView.svg": "be643367e7ffd5de10d0dcb108e4c3a1", "resources/theme/default/icons/mActionLayout.svg": "28f9190ac68a00b0d7f6e04170d31cee", "resources/theme/default/icons/mActionLayoutManager.svg": "31be4d584dfc1ccafd8d01cdcd164fc8", "resources/theme/default/icons/mActionlinkage.svg": "b3ce4171038f9d82c8d04ea9cb3ad2d0", "resources/theme/default/icons/mActionLoadData.svg": "684e514f1cff1d2c9e95dd55a858df9f", "resources/theme/default/icons/mActionLoadLayerFile.svg": "f305a0ff3392689c805ce7984044289c", "resources/theme/default/icons/mActionLoadProjects.svg": "65d677b31784876d2af6e7c1fbf57b8a", "resources/theme/default/icons/mActionLoadRevision.svg": "874fed75579a074e530d87b91ad2ff07", "resources/theme/default/icons/mActionLocate.svg": "52f9968f5e0a6392c5f0a58db02b0825", "resources/theme/default/icons/mActionLock.svg": "26131931765f2aec7cc4bd0443f8f2ac", "resources/theme/default/icons/mActionLog.svg": "155acdfdf0f50cc1488483bb2eb844c1", "resources/theme/default/icons/mActionMaintain.svg": "ba47b24e4af73b927909cc5348f7bec8", "resources/theme/default/icons/mActionMaintainBSM.svg": "7acf0cf2e793fb29050771698c22b2e3", "resources/theme/default/icons/mActionMapsheetManage.svg": "1fd72e8c7b4d1b68cdfc45b6a1587ffc", "resources/theme/default/icons/mActionMapsheetNode.svg": "45219d3849e361f574ca86fcda80b3a1", "resources/theme/default/icons/mActionMapStyleManage.svg": "cf9354021a4a5b26ad660525ece1edff", "resources/theme/default/icons/mActionMeasure.svg": "a62f37b1ec481f1c2bab829f83849396", "resources/theme/default/icons/mActionMeasureAngle.svg": "5edcf784a0e8807d14944cce68a1a014", "resources/theme/default/icons/mActionMeasureArea.svg": "3ccde2a2802aac9ed7677d5c92bb3a0c", "resources/theme/default/icons/mActionMergeFeatures.svg": "58fca120fbe1d8ed6fd3a2dee1f90422", "resources/theme/default/icons/mActionMiddleLine.svg": "30f0034bc7c043e397e3f2ddc0ffc345", "resources/theme/default/icons/mActionMirrorTool.svg": "786dda30a600b3326b3ae42402dc443e", "resources/theme/default/icons/mActionModifyElements.svg": "59d8bbab970228ddbf2537c23da41ed3", "resources/theme/default/icons/mActionMosaic.svg": "34c034a1e13abe0d007225fbded76d7a", "resources/theme/default/icons/mActionMoveDown.svg": "e69d25c8b849110e43023f18f68c0bcd", "resources/theme/default/icons/mActionMoveElementBottom.svg": "5c963ec211036b7012aaab490bcb3741", "resources/theme/default/icons/mActionMoveElementDown.svg": "ed84988b86b920d732c1cc2c922e52a8", "resources/theme/default/icons/mActionMoveElementTop.svg": "8d5251c95c7b8b51c06719aed5991511", "resources/theme/default/icons/mActionMoveElementUp.svg": "0f2bae29c5c6a245ef7a1ef79d01d970", "resources/theme/default/icons/mActionMoveFeature.svg": "46a3b71b7e3282a07bcc8fe11a20916f", "resources/theme/default/icons/mActionMoveUp.svg": "c09793cc8b982dfc7160ca61174aa3af", "resources/theme/default/icons/mActionMoveVertexTool.svg": "fd8cdb5ff4b54efded668628786520da", "resources/theme/default/icons/mActionMultiEdit.svg": "1b4e305d58dbf3793eab33d6b474c28a", "resources/theme/default/icons/mActionNetworkStorage.svg": "e62bba269bce3c8ea8c4cf96e11bdd7c", "resources/theme/default/icons/mActionNew.svg": "c04a77f3fadc8f682577a4a1ffa31ea6", "resources/theme/default/icons/mActionNewAttribute.svg": "286fff33a3eb371889d740ae643cf9c4", "resources/theme/default/icons/mActionNewBookmark.svg": "5b5885cbdcefd11ac0e9d54461cfd914", "resources/theme/default/icons/mActionNewConn.svg": "4113d50441e7461c420e93a0b59d9bbf", "resources/theme/default/icons/mActionNewDataSpecification.svg": "c9c120156d64453162f017b2a4abbee7", "resources/theme/default/icons/mActionNewFileGdb.svg": "f5721d9a6558d415f3a67c63b78a95cc", "resources/theme/default/icons/mActionNewFolder.svg": "d7afc17f864be5b81aae706f5bb9141e", "resources/theme/default/icons/mActionNewMapElement.svg": "6566f4e1671d310ab3ed30117c44e5ef", "resources/theme/default/icons/mActionNewPKG.svg": "4156b6fbf9e9a09e8889e1ae0b0330e4", "resources/theme/default/icons/mActionNewSchemeData.svg": "7807bb77c398afb16afd46463672f893", "resources/theme/default/icons/mActionNewTableRow.svg": "f2fd0ead19625b325f33359e6f12a55f", "resources/theme/default/icons/mActionNewTask.svg": "7618baf3704550ed1ae0505eb74d1037", "resources/theme/default/icons/mActionNewTileClass.svg": "0166b686d417d0a81b5ae5b1791b4169", "resources/theme/default/icons/mActionNodeDiluting.svg": "6a864c4a9ce1fdd07ba68f132c980643", "resources/theme/default/icons/mActionOpenData.svg": "8cdca77f195e267228b878d1d7d82fe4", "resources/theme/default/icons/mActionOpenDirectory.svg": "d37744c14f87326d779ce634086a4a9b", "resources/theme/default/icons/mActionOpenJS.svg": "de439993a9ab1770b8e2fb17f5a8d0e1", "resources/theme/default/icons/mActionOpenLayout.svg": "da607b272ab28493f7b354a684388bae", "resources/theme/default/icons/mActionOpenScheme.svg": "781accf4794f648dcf2f8c4097ff8fa7", "resources/theme/default/icons/mActionOpenTable.svg": "b1bdd4c59286ff406b8262f1b61d54e2", "resources/theme/default/icons/mActionOptions.svg": "50f9ee8b59e5e364ba52222d2c55a8fb", "resources/theme/default/icons/mActionOraganizationManager.svg": "d386d4dc75ac892a620c5f726295214e", "resources/theme/default/icons/mActionOverView.svg": "c8df9418bb2d3606aa53e30ab4daa93f", "resources/theme/default/icons/mActionPan.svg": "98a5dbf4184531ba2a2c68db92806456", "resources/theme/default/icons/mActionPanToSelected.svg": "974a9cf2f5750b418b474942b5dcb2a1", "resources/theme/default/icons/mActionParamSetting.svg": "10cb60a011e7788aac961c212e1c8608", "resources/theme/default/icons/mActionPolygonIncise.svg": "2d82c0b0f5739c6bf787c4eab8064a6a", "resources/theme/default/icons/mActionPolygonInterattraction.svg": "622f8a50bc5ceb068e484420b9241020", "resources/theme/default/icons/mActionPolygonOverlay.svg": "95411d192cad3e679a01556a44ba29d5", "resources/theme/default/icons/mActionPolygonToPolyline.svg": "22b2360fe102a0c1d93c7c387aa86026", "resources/theme/default/icons/mActionPolylineToPolygon.svg": "c08885aa366dc2ec78104616d564ec31", "resources/theme/default/icons/mActionPreprocessingScheme.svg": "2cce25bc39ad3c0e64e4ede8a7dbf790", "resources/theme/default/icons/mActionPreprovessingTB.svg": "f8c7613e852b0c9af5f8e3038fd4b73f", "resources/theme/default/icons/mActionPrint.svg": "31c995fa5d814d9746b94d0e3780260b", "resources/theme/default/icons/mActionPrivilegeManager.svg": "d406a3ea8e70ac6188f7ff6cda412fdf", "resources/theme/default/icons/mActionProperties.svg": "643c2dbf159641c13a90dd282c750b66", "resources/theme/default/icons/mActionPropertiesWidget.svg": "7b8bdc3ad81fe89c339754370467ef05", "resources/theme/default/icons/mActionProperty.svg": "0f5fd9de9f38273462de2ef945b9a3ca", "resources/theme/default/icons/mActionQgsAddView.svg": "77154a2bd7a278ec5417f54bea869039", "resources/theme/default/icons/mActionQueryByLine.svg": "100a77b190342c4f4d612d2f4db8964c", "resources/theme/default/icons/mActionQueryByPoint.svg": "15630a334591356d90a90216319a3d48", "resources/theme/default/icons/mActionQueryByPolygon.svg": "6a70cc390d0f99105123a68aac36ee17", "resources/theme/default/icons/mActionQueryRoot.svg": "f442d02fb5ef3741fdf1624a3f02f5d7", "resources/theme/default/icons/mActionRasterImport.svg": "2417938f794ce78543633dc544bca4d0", "resources/theme/default/icons/mActionRedo.svg": "8c028f9ef70e474d7387f9b460a65eb6", "resources/theme/default/icons/mActionRefresh.svg": "52fa4b4278264c367eda284c53d15f89", "resources/theme/default/icons/mActionRegionExport.svg": "845dd15fd40ad4f743623469b5b3fdb4", "resources/theme/default/icons/mActionRegionImport.svg": "8dbd184a3b428ce6e102f7056e116f8b", "resources/theme/default/icons/mActionReload.svg": "43784ed0de3112c14d5921b6303d63a3", "resources/theme/default/icons/mActionRemove.svg": "e6754b6e1d730a5c882c2db92eea2950", "resources/theme/default/icons/mActionRemoveAllLayer.svg": "a6da6d6063b7329140356336a91090cf", "resources/theme/default/icons/mActionRemoveLayer.svg": "1c49ecbc2147eba97ec8aad9633f7ccd", "resources/theme/default/icons/mActionRemoveRepeatData.svg": "95b636aa514362fb03154a017f8eadc9", "resources/theme/default/icons/mActionRemoveRepeatedPoints.svg": "f3559b1f0ead7a72c40d87131c92acd8", "resources/theme/default/icons/mActionRemoveSchemeData.svg": "9194bf7bea36f62dc7dc2f3b30b90cbc", "resources/theme/default/icons/mActionRemoveVertexTool.svg": "8ae82b51d34a411968de1a9d97307367", "resources/theme/default/icons/mActionReName.svg": "2f4f8cbcf71196f22f219881f3e971d4", "resources/theme/default/icons/mActionReset.svg": "741f7fb42d42849d612fc20b12b7cd35", "resources/theme/default/icons/mActionResetDirPath.svg": "c344596ea4ddfc767152bdeb7c02ed27", "resources/theme/default/icons/mActionResolveSharePointError.svg": "f59d218ed27b3d3e93b66853590a8e94", "resources/theme/default/icons/mActionResultExport.svg": "e05fe9e6f120fff6f9fe567427d73eae", "resources/theme/default/icons/mActionResultPreprocessing.svg": "d915ed03dcd61c0d516c76a4b87d0aa4", "resources/theme/default/icons/mActionReversePolyline.svg": "d55a0018bb397763cc96e462198fdbe2", "resources/theme/default/icons/mActionRevertToRevision.svg": "0392f2d180aee9d9365959f9245e265b", "resources/theme/default/icons/mActionRightAngle.svg": "4c55097541c57f3764ca125d0920ef85", "resources/theme/default/icons/mActionRotateFeature.svg": "3e22ce01e2c0c965d96fc17ccf57f686", "resources/theme/default/icons/mActionRuleManage.svg": "15482f473e3fe2874ab18f9f2e8fbadb", "resources/theme/default/icons/mActionSaveAllEdits.svg": "94a89bc9b11cb90a59fc8f044c0a5936", "resources/theme/default/icons/mActionSaveAsScheme.svg": "13d249919c23815a608a9ff5d28846d6", "resources/theme/default/icons/mActionSaveEdits.svg": "777486ea8c29a2a365dfe80977fa17c1", "resources/theme/default/icons/mActionSaveLayout.svg": "8b3789e6e0dd8223130b84000d3ed79f", "resources/theme/default/icons/mActionSaveScheme.svg": "4f281e045c96432fd1cd7a82ca6f606b", "resources/theme/default/icons/mActionScaleBar.svg": "537d6a364f6c093e6b585b7e049b29af", "resources/theme/default/icons/mActionScaleNode.svg": "50f99f8accdc8751b270032cb556b39f", "resources/theme/default/icons/mActionSchemeBatch.svg": "dc5b9b397f9281a476733882693b6c47", "resources/theme/default/icons/mActionSchemeFilter.svg": "717cb0048b15305a8cadd452718d340c", "resources/theme/default/icons/mActionSchemeFit.svg": "43ce16a7914f0ed136fb52182040b068", "resources/theme/default/icons/mActionSchemeManage.svg": "c3514670076c47f378bf24f59d007e43", "resources/theme/default/icons/mActionSchemeNoFit.svg": "ddb52d6845b46bb2da8f44d6515c48ad", "resources/theme/default/icons/mActionSchemeShow.svg": "bd93bfc5885d5307f38203892ee44ef2", "resources/theme/default/icons/mActionSchemeSourceManage.svg": "49f8d190098edf63f5afa2a4fcb1b343", "resources/theme/default/icons/mActionSchemeTargetRoot.svg": "4c51b15a288a6857cb595ca87badc50d", "resources/theme/default/icons/mActionSearch.svg": "c53fee0f34c42a7a9d8e898532a3b86d", "resources/theme/default/icons/mActionSelect.svg": "af097cfbf7068555e0316d56705f153a", "resources/theme/default/icons/mActionSelectAll.svg": "05fe2f51d3089764d072f9ace468d8b4", "resources/theme/default/icons/mActionSelectAllLayers.svg": "fc3274b291accf2f3c94e7b6d21b496d", "resources/theme/default/icons/mActionSelectPolygon.svg": "de8682bdb5e3b611224a6ee8b52515ab", "resources/theme/default/icons/mActionSelectRadius.svg": "06c743aeea2546796c169625cdbfeb0c", "resources/theme/default/icons/mActionSeparateFeatures.svg": "47a255fe5cc8c8cd23a512e458ccf01a", "resources/theme/default/icons/mActionSeparateLayer.svg": "dca40eff33ddc48a7c0a1221c0aae739", "resources/theme/default/icons/mActionServer.svg": "1ab2380a5b5e7915e80d6b7f232d3bd2", "resources/theme/default/icons/mActionSetBottom.svg": "efce405a42a008cf74a9663e46362ab0", "resources/theme/default/icons/mActionSetClipEnv.svg": "cfc446c1a8ce1801fb3682f4caf2185b", "resources/theme/default/icons/mActionSetClipPolygon.svg": "0fc055217ef4255c7e588f08e3edf780", "resources/theme/default/icons/mActionSetDataSource.svg": "92fea7f7b80c74bb4f22cbedbd160e81", "resources/theme/default/icons/mActionSetNoClip.svg": "729fd670d5e947741b64c6ed7ee385b7", "resources/theme/default/icons/mActionSetNull.svg": "33deea05fa20968c10a0c3123e47c0d6", "resources/theme/default/icons/mActionSetSpatialReference.svg": "64f9c175f0e21d1128c10aa08dbcad85", "resources/theme/default/icons/mActionSetting.svg": "2c6d37e1466939c8e16f1cd04ff48d24", "resources/theme/default/icons/mActionSettings.svg": "98160adbf1b7bc7dfa0064d5da680278", "resources/theme/default/icons/mActionSetTop.svg": "57517e5161ba671327848c4c2495ce56", "resources/theme/default/icons/mActionSharing.svg": "8f5e6dffd6231a6858fc5f56ce1a60b2", "resources/theme/default/icons/mActionSharingExport.svg": "10de78f4fe80a0cef1a3609dd40a67f0", "resources/theme/default/icons/mActionSharingImport.svg": "9bed0a42714abdd71b65742d1ca1b26b", "resources/theme/default/icons/mActionShowAllHide.svg": "f6dea326e1c06484a7dbaa3a1c2313fe", "resources/theme/default/icons/mActionShowAllLayers.svg": "1069904cfba0dbc27c02e6659bacbc49", "resources/theme/default/icons/mActionShowBookmarks.svg": "18aa642ffd432316af2e4410f4b29d7d", "resources/theme/default/icons/mActionShowFilter.svg": "ace4d7974edfb60aca730fd5095c3259", "resources/theme/default/icons/mActionShowGridTool.svg": "682e24e73864e87cfdb33e9bd3e9809f", "resources/theme/default/icons/mActionShowLayersSet.svg": "2ac82d593bce15962b546a4aebe8fe48", "resources/theme/default/icons/mActionShowPluginManager.svg": "99436035696bb2cb8f10f5c098c4b44f", "resources/theme/default/icons/mActionShowResults.svg": "f56c5cd78219b41cd930774bb121024b", "resources/theme/default/icons/mActionShowSelectedLayers.svg": "a838ae1987783d19767e54228de05aa1", "resources/theme/default/icons/mActionSimplify.svg": "9c03c4c49fcd354e04832dd253c880a1", "resources/theme/default/icons/mActionSmoothTool.svg": "707f96d7e602170da613f80b679c8905", "resources/theme/default/icons/mActionSpatial.svg": "bef0b6a230ff89396636be69e8b40ef7", "resources/theme/default/icons/mActionSpecialAttributeBrush.svg": "24fc0d1c64c25fe84b8b2cfe896141cd", "resources/theme/default/icons/mActionSplitByPolygon.svg": "538583d5099e9abc25d5b1a9268e10e7", "resources/theme/default/icons/mActionSplitByPolyLine.svg": "8701b4efde0bae3ed46376ef173e4787", "resources/theme/default/icons/mActionSplitBySelect.svg": "26580003f206a37877bd8d4fb7c7e83a", "resources/theme/default/icons/mActionSplitFeatures.svg": "58f0d4717c6590d5116d6c4e3cf6661a", "resources/theme/default/icons/mActionSql.svg": "19852025b86712876a6f8722151414af", "resources/theme/default/icons/mActionStartCheck.svg": "36f7176cf331fccd0b34d63d0ea1330a", "resources/theme/default/icons/mActionStartImport.svg": "c6016f45b0af8a4852c51b94390c8226", "resources/theme/default/icons/mActionStreamline.svg": "e5cbf8db4c102b2afb6c8231f59b5c17", "resources/theme/default/icons/mActionStyleView.svg": "730e0af4ddac5a0fb1f5f539fd30622b", "resources/theme/default/icons/mActionSum.svg": "d017046d48ce90ed5bceab7d3e6d6f1f", "resources/theme/default/icons/mActionSwipe.svg": "12847b35a09965af255f83f9b227e3f0", "resources/theme/default/icons/mActionSysSwitch.svg": "34367f856ddc6239e12d778c5a19b816", "resources/theme/default/icons/mActionTableImport.svg": "8deaa47f6625c021e0fe3236c1d7b94d", "resources/theme/default/icons/mActionTaskManage.svg": "970769a0f3cd8be7dd294a765ab2187e", "resources/theme/default/icons/mActionTeamConfig.svg": "e84b0043b2cb13f560f94a0d6721e07c", "resources/theme/default/icons/mActionTeamEdit.svg": "b0114a6fc46120440e19c53730a0581c", "resources/theme/default/icons/mActionTeamProjectInfoStatistic.svg": "3129a2940c9803c5ca6ba12bef78cb00", "resources/theme/default/icons/mActionTeamRevisionSlider.svg": "5f1ab9a8ba1f48f0fcb3cfd5984862cc", "resources/theme/default/icons/mActionTeamServer.svg": "fecca577325287d69541a40ded17fadb", "resources/theme/default/icons/mActionTeamTimingAcquireLog.svg": "48487ce93bfd8cd3d0aa77a2c26ce9cd", "resources/theme/default/icons/mActionTeamTool.svg": "fc55777995fd4de917463da7c9f5424b", "resources/theme/default/icons/mActionTemplate.svg": "01927217e00be004e12f16b651440359", "resources/theme/default/icons/mActionTemplateCompose.svg": "af1adc1a48861d24dc688b9778d3d80e", "resources/theme/default/icons/mActionTemporaryLayer.svg": "68e4b5c61088a15f89d939c81ff86b87", "resources/theme/default/icons/mActionThematicAttributes.svg": "6b6ee135eedc395c6eb8e04b92a5cab0", "resources/theme/default/icons/mActionTileImport.svg": "903e13e6c35e1ee083a61b23918688de", "resources/theme/default/icons/mActionTiling.svg": "c8e25a3eda0bc19b9778168ecf753375", "resources/theme/default/icons/mActionTimeSlider.svg": "30df8efe796c12114471323077bca30c", "resources/theme/default/icons/mActionTimingAcquire.svg": "489dd9a97dffb720b3162dc14bc86a9e", "resources/theme/default/icons/mActionTimingAcquireSetting.svg": "7ecabd8416d95df2f92da8ff60d44da9", "resources/theme/default/icons/mActionToggleEditing.svg": "29723b6d47441f7bb494714ea429edde", "resources/theme/default/icons/mActionToolBox.svg": "2c9169fc1c7e50f15b84f71bafd17a6d", "resources/theme/default/icons/mActionTrim.svg": "225c5b9cc91e4f5e8ca4aaf8787780c6", "resources/theme/default/icons/mActionUndo.svg": "ec0a88d3076c323ae59118eca56b8163", "resources/theme/default/icons/mActionUngroup.svg": "7bc078804d80699dc8cc91e2082f84d4", "resources/theme/default/icons/mActionUp.svg": "7999c9a02eda90ac2f42b6c3641e904b", "resources/theme/default/icons/mActionUpdate.svg": "40ba9725c8cdd5e1fe69108bb6f8525d", "resources/theme/default/icons/mActionUpdateRecords.svg": "5a46d098c69fbf39923ffe3178018278", "resources/theme/default/icons/mActionUpdateToRevision.svg": "80e6f808eeb64d71829e44a827b9b51f", "resources/theme/default/icons/mActionUploadData.svg": "4817a531daa8606bc9531d50957971c6", "resources/theme/default/icons/mActionUserRoleManager.svg": "e3e59465ff0ccfc907414df8628b5723", "resources/theme/default/icons/mActionVertexTool.svg": "daa8e981408b835ddbe36af3bed62e19", "resources/theme/default/icons/mActionVTSPreview.svg": "f9c9643fd1b174e56abae7e8a5ffe7ba", "resources/theme/default/icons/mActionWeldPolyline.svg": "35c012816b7f000837e465109dcb1f95", "resources/theme/default/icons/mActionYearChangeNavigation.svg": "943edf66d680b0ee7fc4a2d68504b6ab", "resources/theme/default/icons/mActionYearChangeRegression.svg": "b4f5be92104d693da3a88a817166c20e", "resources/theme/default/icons/mActionZoomFullExtent.svg": "6f21d610891d2924bcfe88c73fb764f5", "resources/theme/default/icons/mActionZoomIn.svg": "1042c451e38bbbdea2736329514618a0", "resources/theme/default/icons/mActionZoomInCenter.svg": "f02ed86aaccae6e9a97ecbb852a0b224", "resources/theme/default/icons/mActionZoomLast.svg": "5c42ac5098f8e0f648dbe3de9bb4bad8", "resources/theme/default/icons/mActionZoomNext.svg": "a9fa6a5c006dd0c0840aa4e1774de19d", "resources/theme/default/icons/mActionZoomOut.svg": "7a84e09a885a48b381d69d0b5d3ff69a", "resources/theme/default/icons/mActionZoomOutCenter.svg": "c84df5bb54c1fc4da3656025dd3bd6bd", "resources/theme/default/icons/mActionZoomToBookmark.svg": "9073facd9ee913217bed1c2093a4e098", "resources/theme/default/icons/mActionZoomToLayer.svg": "82c760fa362ccf923603a9ab657d737f", "resources/theme/default/icons/mActionZoomToSelected.svg": "e934636f2b504da8ab0e4e6d3e859bac", "resources/theme/default/icons/MaintainRegionDic.svg": "e220cc3e4c97e63296684f3d9e38d5f8", "resources/theme/default/icons/mapConfig.png": "f7b863779cc89c8be7605bc56a65703c", "resources/theme/default/icons/mapConfig.svg": "c7a0b74014cc0599c1e82c0c8b8be1f1", "resources/theme/default/icons/markdown.svg": "b1318f7383ab486afa82e4568aa58185", "resources/theme/default/icons/matlab.svg": "8d5f10b9fb25f9df14e71d6e4eda19a7", "resources/theme/default/icons/mAutoChangePolygon.svg": "4aed8b896926a1d832f64933d1257f52", "resources/theme/default/icons/mAutoCompletePolygon.svg": "3c11c43f10997872241c257e9987d46c", "resources/theme/default/icons/mComposeSchemeManage.svg": "d352afeceb179d2d645026aa873c5b92", "resources/theme/default/icons/mDataExportSchemeManage.svg": "a7e3c9f7ad828fb76c0cf4cfb13c097b", "resources/theme/default/icons/mDataImportSchemeManage.svg": "fc35fcdf359f9f81fa2ae510ab87c4c1", "resources/theme/default/icons/mDataTransSchemeManage.svg": "c9bf6fa291e4976bf0983bd5288934ab", "resources/theme/default/icons/measureConfig.svg": "903161e927f84e5613a6dd4c9922a1bd", "resources/theme/default/icons/merge_h.svg": "e18a2b6bbdb4a1e6d8a95644122e60d9", "resources/theme/default/icons/merge_v.svg": "a8527f6026fe7118c129f5cba6fb1016", "resources/theme/default/icons/metadata.svg": "b2e273799dbfa6dbc52e8228f5bdbdd9", "resources/theme/default/icons/mGeneratingDLJX.svg": "4c69197baf2338313faf3993d6b115e8", "resources/theme/default/icons/mGeoPackage.svg": "7c73ffb090a38af7feab29aa12152423", "resources/theme/default/icons/mIconAddDBServer.svg": "8c825e00c2816ab43183b541733d71f8", "resources/theme/default/icons/mIconAddServer.svg": "7112be64f8be6720026bc2a8bf9c625d", "resources/theme/default/icons/mIconAfs.svg": "26bc632ff2d7c289e202f6c8f61e1c03", "resources/theme/default/icons/mIconAms.svg": "630bac3c1a415087351fe6492e4bb83d", "resources/theme/default/icons/mIconAnalyseFlow.svg": "ea21512744b1a11c266e5b9d1520389a", "resources/theme/default/icons/mIconAnnotationLayer.svg": "fbe179641e24ec8c54a302265cca4f2f", "resources/theme/default/icons/mIconAnnotationMLayer.svg": "68d1aa7332c4c52ba3e969a14417ae19", "resources/theme/default/icons/mIconApplication.svg": "94bea3681edb4be10e23727c67923918", "resources/theme/default/icons/mIconApply.svg": "51876eb236913705ce2756c72b7fb49a", "resources/theme/default/icons/mIconAttirbuteAssign.svg": "b93e85f9184b920d402f862abf94357b", "resources/theme/default/icons/mIconAttributeTable.svg": "e7db893217b5b1baac3961b549108479", "resources/theme/default/icons/mIconAuxiliaryStorage.svg": "424505094deccde793bb07080494a9b1", "resources/theme/default/icons/mIconCad.svg": "fef13370469b1722e4dcc26d46a89f57", "resources/theme/default/icons/mIconCalEllipsoid.svg": "823474172c2ccec64f5adaee114fd82a", "resources/theme/default/icons/mIconCatalogResource.svg": "6c8e7c6dfd9c958328f27092fb00c3c3", "resources/theme/default/icons/mIconCatalogRoot.svg": "01afcfbe57ea1a4a90b6daf78af9a2a9", "resources/theme/default/icons/mIconChange.svg": "7cc1b16efb9fcf0c8144b9c1e86d258e", "resources/theme/default/icons/mIconCheckLayer.svg": "841eb956c21eb7f7557937b56648eea2", "resources/theme/default/icons/mIconClearText.svg": "f20b46fc4f835d7d636f97f1d7770c63", "resources/theme/default/icons/mIconClearTextHover.svg": "6bf93a9f31af24cc804503c71aadbfa7", "resources/theme/default/icons/mIconClose.svg": "7787829dfdddf0c0ff3306e96b5998fe", "resources/theme/default/icons/mIconCode.svg": "e6351f72e8d1f68207c574af6f6557ad", "resources/theme/default/icons/mIconCodeSpecifiation.svg": "e0a81b82fa77022b5850eb16a0382690", "resources/theme/default/icons/mIconCodingScheme.svg": "654f59de125796f54e270e6278e60d34", "resources/theme/default/icons/mIconCodingSchemeRoot.svg": "06c8ad7cf27e496674ed6bdc31db1ba7", "resources/theme/default/icons/mIconCompoundLayer.svg": "d1026c32abaa5a0591f7a5c7b693eac8", "resources/theme/default/icons/mIconConnect.png": "c4d29a2a304cc265c34def6e2a1cc7ed", "resources/theme/default/icons/mIconCritical.svg": "479fc5b611396c2c535a2bc18a808ef6", "resources/theme/default/icons/mIconDaMeng.svg": "74e85fd3c915ccc760b40a05f961bae5", "resources/theme/default/icons/mIconDataSet.svg": "cb718d909534dadd72d4b72dbddb1a4a", "resources/theme/default/icons/mIconDataStructure.svg": "fd89f1f6fae2b928a841aea919995f34", "resources/theme/default/icons/mIconDbSchema.png": "1b79c7357dd1f0e5eb1f4d47f759b4f6", "resources/theme/default/icons/mIconDelete.svg": "b7645486da851a6b3272d304f2a6dd86", "resources/theme/default/icons/mIconDeselected.svg": "75733116db9e219b0f4382385342b511", "resources/theme/default/icons/mIconDicItem.svg": "68e57fc565b36ae2e1d060ce6dc8c964", "resources/theme/default/icons/mIconEditableEdits.svg": "455990cd70b279a43e6fe1483ef47b3b", "resources/theme/default/icons/mIconError.svg": "affb8053194df39dda4f105433c209d7", "resources/theme/default/icons/mIconExportCatalogDataNode.svg": "66c846fd3b5f7a771e834b12555d546e", "resources/theme/default/icons/mIconExportCatalogFileNode.svg": "719a5a83513ddb084966a21c3909092a", "resources/theme/default/icons/mIconExportCatalogRootNode.svg": "51d5c34391a171eed24ebfc860fc710b", "resources/theme/default/icons/mIconExportSchemeNode.svg": "810c27933bac0c415bba59ad0e28619a", "resources/theme/default/icons/mIconExportSchemeRootNode.svg": "cd1a0f9d1adc73d30ad78f03e26e3125", "resources/theme/default/icons/mIconExpression.svg": "4a7c9010d4faef5cb7dd50d4b063bf1d", "resources/theme/default/icons/mIconExpressionSelect.svg": "d5a11b917f78b8963933e25463ed3f13", "resources/theme/default/icons/mIconExternApplication.svg": "76860ff954accd3d41779f1b13614a03", "resources/theme/default/icons/mIconFcs.svg": "842114c237df2e6488115f32bf6179a0", "resources/theme/default/icons/mIconFifthLevel.svg": "64fd05b7bc8fcc3d5613bff14477230b", "resources/theme/default/icons/mIconFile.svg": "3047ab35641e5516a3eb281bada2613a", "resources/theme/default/icons/mIconFirstLevel.svg": "411ddf0a99c68e8adf1c08389bd2ea10", "resources/theme/default/icons/mIconFolder.svg": "cf1ca6b8396b31dcca5b8f33eb43ceb4", "resources/theme/default/icons/mIconFolderCatalog.svg": "4eb1ba9a379ada6a5d6bdb205259bb04", "resources/theme/default/icons/mIconFourthLevel.svg": "638de27d6466f21ec317cb203840ae5d", "resources/theme/default/icons/mIconFtp.svg": "a199167e87d48e535a28a0f1bba7b62b", "resources/theme/default/icons/mIconFtpConnection.svg": "8a392e40f69a0f418fdbdd881165da08", "resources/theme/default/icons/mIconFtpFile.svg": "dd9f4404c69b29266260a9a9d8df269b", "resources/theme/default/icons/mIconGBase.svg": "fb1586a853c2904b60aaae867f76c85a", "resources/theme/default/icons/mIconGdb.svg": "faf3ba5fe031bfdce3aed9a3f46f078f", "resources/theme/default/icons/mIconGeoMap.svg": "5fddaa3bf5d340522608d62df060290f", "resources/theme/default/icons/mIconGeoModel.svg": "1062170310062340d8e756bf956bc804", "resources/theme/default/icons/mIconImage.svg": "d7d21b7fb0e283fcf8411c03f3e31845", "resources/theme/default/icons/mIconInfo.svg": "3c0cf72d3f29ee6108e7d408a9103694", "resources/theme/default/icons/mIconInnerLayer.svg": "8191e0d1b9834eec58a8ee696da1ceeb", "resources/theme/default/icons/mIconKingBase.svg": "8ad55c2113ceee058166bdd7da6fd468", "resources/theme/default/icons/mIconLayer.png": "21cf151b551b35eed6e034ed7112dbc0", "resources/theme/default/icons/mIconLayer.svg": "9540c68b59d83e5f407a81cb364fe39e", "resources/theme/default/icons/mIconLayoutTemplate.svg": "215df0affb2b3b0d8895161fcc9bdafa", "resources/theme/default/icons/mIconLayoutTemplateRoot.svg": "99d5dce6929dd587527835636b774346", "resources/theme/default/icons/mIconLineLayer.svg": "4c69197baf2338313faf3993d6b115e8", "resources/theme/default/icons/mIconLineMLayer.svg": "fe6b4178b48f8999d83c10155c1a0b0e", "resources/theme/default/icons/mIconLoading.gif": "8fa9058348a86d759eb315dd66de2714", "resources/theme/default/icons/mIconLocalServer.svg": "f890500950ee4e3e5468082e57eabcf9", "resources/theme/default/icons/mIconMapFile.svg": "627ca8d2c2c1796f646e9216d9349bea", "resources/theme/default/icons/mIconMapStyle.svg": "b6356008ed553c02b7a31e7be2b3289f", "resources/theme/default/icons/mIconMixCatalog.svg": "4eb1ba9a379ada6a5d6bdb205259bb04", "resources/theme/default/icons/mIconModelLayer.svg": "72f4b78bf3b06435aef0e06e5ca151e2", "resources/theme/default/icons/mIconModifyServer.svg": "c7d9ee62d4aa9720f2f15a3f22e86b3e", "resources/theme/default/icons/mIconMySQL.svg": "a99effe63b8a0afe08ca87c291451c6e", "resources/theme/default/icons/mIconNewGroup.svg": "e27402c3f3b6866ea996eff280c1329d", "resources/theme/default/icons/mIconNow.svg": "6a8ae624d3a553b4db8d03e60170fee0", "resources/theme/default/icons/mIconOffice.png": "6303c5abbdcdd3a24281007c3f75910e", "resources/theme/default/icons/mIconOracleSpatial.svg": "96b27c5bb382407c8fbb3f1d5cdfbb45", "resources/theme/default/icons/mIconOutsideLayer.svg": "2e84bf82fd9ba6d6d3b170ff227e43b7", "resources/theme/default/icons/mIconOws.svg": "861d35b91c66a25907dd731a522795fd", "resources/theme/default/icons/mIconPg.svg": "870e241bd22d89b83aa26e69b79e44d9", "resources/theme/default/icons/mIconPhysicalTable.svg": "62556fe79abc5c1aa1b139a4caab4f97", "resources/theme/default/icons/mIconPhysicalTenseTable.svg": "8495bd64272f9519ba3639bb108ce6a1", "resources/theme/default/icons/mIconPointLayer.svg": "95251fc052d089796b41a46072661ad2", "resources/theme/default/icons/mIconPointMLayer.svg": "3ace9ed797c1cd91848f6e75df4ce5cf", "resources/theme/default/icons/mIconPolygonLayer.svg": "da646fa3fef5ce81f9f0e9ace1d0be1e", "resources/theme/default/icons/mIconPolygonMLayer.svg": "b76b1c64744e78f711a6ed163b6fbf29", "resources/theme/default/icons/mIconPostgis.svg": "d95101a8f8b9eaa80083d646e5383659", "resources/theme/default/icons/mIconProjectionEnabled.svg": "820493124a75fe953400f9f2ceba9d34", "resources/theme/default/icons/mIconProperties.svg": "b57f75b3eccce67243c8143fc010f052", "resources/theme/default/icons/mIconRaster.svg": "a65c7579f3849569b88caa69d46d6d1c", "resources/theme/default/icons/mIconRasterGroup.svg": "b22c2b30b5b48777566a041b02ca20d7", "resources/theme/default/icons/mIconRasterLayer.svg": "37358bd2779dd20a7580a1b4b354913b", "resources/theme/default/icons/mIconRegionItemNode.svg": "0c892403655a142a739da4a445f10d69", "resources/theme/default/icons/mIconRegionNode.svg": "2b8d8fcdb3054ca0b50d165d76a0e529", "resources/theme/default/icons/mIconRegionRoot.svg": "a05b73abd59d65752a387c0887afeaa1", "resources/theme/default/icons/mIconRegionRootNode.svg": "b7b0001fe203a7c6e9bfaf88d2f2db4c", "resources/theme/default/icons/mIconRegionTree.svg": "981c3314204c6c2ddb53e50da0181732", "resources/theme/default/icons/mIconReload.svg": "4ee18f3e56c0a3ce09a1cc261ca9d12d", "resources/theme/default/icons/mIconRemoveServer.svg": "1644b9d1c6aa137862d1ae8788374b3e", "resources/theme/default/icons/mIconReportWizard.svg": "fc1234e9224f00f23323c50723568edb", "resources/theme/default/icons/mIconReserveSelection.svg": "8ab255dc5906d1cf88675446c3e09240", "resources/theme/default/icons/mIconResourceCatalog.svg": "4eb1ba9a379ada6a5d6bdb205259bb04", "resources/theme/default/icons/mIconRule.svg": "5115e07bbedf98741550b7db9dae61a4", "resources/theme/default/icons/mIconRuleGroup.svg": "99610d48d732d9f76ad9dd28fba1bb1d", "resources/theme/default/icons/mIconRuleRoot.svg": "013cdba55f00d20c4d9d1429a33d3981", "resources/theme/default/icons/mIconSave.svg": "5e0bcceceac29567885531806f0df5eb", "resources/theme/default/icons/mIconSaveAs.svg": "92eae31b2324f5a7b3fc959de7858a54", "resources/theme/default/icons/mIconSecondLevel.svg": "f830a82f170fdc417e8243e99f6463ef", "resources/theme/default/icons/mIconSelectAll.svg": "2c19a865fc8951dc6b0d76e03192fa57", "resources/theme/default/icons/mIconSelectNone.svg": "728e2fbb4f50cd340e739df3efa98505", "resources/theme/default/icons/mIconSelectServer.svg": "7a40c874356f04dfcf9dc16631b01e36", "resources/theme/default/icons/mIconServer.svg": "b037d88ce726e5e2e9022bcfccf1ab1d", "resources/theme/default/icons/mIconServerManager.svg": "db323ee83983521b1fe55d718137118b", "resources/theme/default/icons/mIconSetColor.svg": "04cd244f3efa35e3e49c3d15a41dfd8b", "resources/theme/default/icons/mIconShowStyle.svg": "82e6f287175dfa2492b04316a1001113", "resources/theme/default/icons/mIconShp.svg": "d4b18ad9e5808b63b022799c9b9ad7fe", "resources/theme/default/icons/mIconSixthLevel.svg": "e7553e75a3071065b15b6bcf2bdc8e96", "resources/theme/default/icons/mIconSnapping.svg": "63f355964cf029d5dcaf7181384b8762", "resources/theme/default/icons/mIconSqlite.svg": "aaa168559302aad794f38c3eba3bddd4", "resources/theme/default/icons/mIconSuccess.svg": "2d9a7e0389e29ef07f2f881f478acc57", "resources/theme/default/icons/mIconSystem.svg": "fa75b6702dfc9a38d955c4db45bce8de", "resources/theme/default/icons/mIconTable.svg": "21615bcc2dab3f7d81b36b7267d5c003", "resources/theme/default/icons/mIconTableLayer.svg": "f097c9a2709c4bb46a5c69edcaeb877d", "resources/theme/default/icons/mIconTableMLayer.svg": "3777b46cbf789491572e2ab6c3841384", "resources/theme/default/icons/mIconTense.svg": "a9506e8a7e783d03a27c777d50f639c5", "resources/theme/default/icons/mIconThirdLevel.svg": "1b82d2a307fc30d45b11ea616c7b5c10", "resources/theme/default/icons/mIconTile.svg": "b73d838ec69991e754f3cb8eb1f47366", "resources/theme/default/icons/mIconTileLayer.svg": "e65e45cc49d05e235d10096ad817fc82", "resources/theme/default/icons/mIconTimerContinue.svg": "cfd5c0fdbd6fdcd6a414dac9c092a633", "resources/theme/default/icons/mIconTimerPause.svg": "56f9fb6e1a8b29efb238c60c7abb2682", "resources/theme/default/icons/mIconTxt.svg": "2a5d39dfd28028ea033bbe28e5e5bfad", "resources/theme/default/icons/mIconType.svg": "652794ba38eacdf6364786d77e138b92", "resources/theme/default/icons/mIconValueRangDic.svg": "b5fbdf278321d22243dcc2a0dfa25e72", "resources/theme/default/icons/mIconValueRange.svg": "3656fff54e067f46659582350822c3bd", "resources/theme/default/icons/mIconValueRangeRange.svg": "2b8d8fcdb3054ca0b50d165d76a0e529", "resources/theme/default/icons/mIconVCT.svg": "9de1e1bdb8620f90226157e87ff08f35", "resources/theme/default/icons/mIconWarning.svg": "469d5f397cb89f1768473b9fc065cb1f", "resources/theme/default/icons/mIconWcs.svg": "d50746b54aaf55fe0f070cd61f42b589", "resources/theme/default/icons/mIconWfs.svg": "4bb63bff96c7076f1372a1a21fc746a3", "resources/theme/default/icons/mIconWms.svg": "ff83723069a766205aaeedbd335906dc", "resources/theme/default/icons/mIconWmts.svg": "ad3fe79ff517b26bf49f6ee5d6a31b17", "resources/theme/default/icons/mIconXYZTile.svg": "d0c0b73adfc969f20bf96165e9581276", "resources/theme/default/icons/mIconYearChangeDataInittail.svg": "53d895ee3af75bc504e52de69b648768", "resources/theme/default/icons/missing_value.svg": "864863c0054f6b72766d2f8a3e030f30", "resources/theme/default/icons/mLayerSaveAs.svg": "42e4a4ffc0342a7cc626cc2df0f4748e", "resources/theme/default/icons/model_selection.svg": "222c717feb5de2c675cfb7c85c41b9b4", "resources/theme/default/icons/modified.png": "430719999ef1aad4e546d47662f64a82", "resources/theme/default/icons/Mouse.svg": "87a75d7d8243dad6fd65d9815db3e7c0", "resources/theme/default/icons/mPolygonDifference.svg": "c7fcfd8353e8661aade6824f2f946878", "resources/theme/default/icons/mPolygonIntersection.svg": "8a8cef3083c30309df6946d6ccd660b7", "resources/theme/default/icons/mPubLayerRoot.svg": "5073514803a9dc01c5c70c3c18305ffc", "resources/theme/default/icons/mPubLayerSet.svg": "a64220d6ecb203571ef6f1d8b0bbe5c2", "resources/theme/default/icons/mPubLayerSimple.svg": "40a8f32e0ec29323c176effbab6a1199", "resources/theme/default/icons/mPyramidManage.svg": "f50928a886510ab2489f3875930c252a", "resources/theme/default/icons/mRefreshErrorStatus.svg": "07bf753129c84e7d476e939ee906c4cd", "resources/theme/default/icons/mReportExport.svg": "e4c0687252e51c27e8967b033352b09e", "resources/theme/default/icons/mReportSetting.svg": "9f12bcd4a448ceeed1ddfa07a289d712", "resources/theme/default/icons/mSourceFields.svg": "c29a122376138fa67964299e1cd554dc", "resources/theme/default/icons/mStartCheckProcess.svg": "1a43b916dc8effdee18fbc2126c77714", "resources/theme/default/icons/multieditChangedValues.svg": "fa2114de837cf105940390a270ca0fbb", "resources/theme/default/icons/multieditMixedValues.svg": "41e09b3a0af251bf17d9283b8f81a577", "resources/theme/default/icons/multieditSameValues.svg": "f280c0cd500fd7679b7bc4cc7abec436", "resources/theme/default/icons/mvectorlayercache.svg": "52656796a777dcd29c43e584de7e40ca", "resources/theme/default/icons/MySQL.svg": "b4b89c2f14fe912b9acc90c32f1b3dc8", "resources/theme/default/icons/nActionBasicStatistic.svg": "f58a64edfbcc2a499d017d021caa854f", "resources/theme/default/icons/net.svg": "01609ed2d769c8d7ad4c97460cac8f69", "resources/theme/default/icons/New post.svg": "0faf63c8a6a31411cfedca48447870cd", "resources/theme/default/icons/new.svg": "f39a9d36090c7cad6757b3621aea0dbf", "resources/theme/default/icons/newvectorfile.svg": "56abc8aa55c3e2143c78b52af8a364d9", "resources/theme/default/icons/new_project.svg": "3cd72c019015b461870f3ad6e871aca1", "resources/theme/default/icons/next.png": "ff36945b6427eb8226bec7fe2743af70", "resources/theme/default/icons/nextConflict.svg": "a086d4b6331680eeb568a4df98937305", "resources/theme/default/icons/non_versioned.png": "25ba46f98f7aee7477ffb7d7fc1a7d62", "resources/theme/default/icons/normal.png": "795d679579e414fd37489f552b604b94", "resources/theme/default/icons/numpy.svg": "f36ed6c85013eb6f6bb3e49a8a9ee713", "resources/theme/default/icons/open.svg": "200a281b71b9104332781e9eb2fcc6de", "resources/theme/default/icons/open_folder.svg": "4aac2ccabd5e0ee6afe3286e61b8c85a", "resources/theme/default/icons/oracle.svg": "62030f9604e445ac5ef6b9edc94da181", "resources/theme/default/icons/orderlayergroup.svg": "2dd8c13c7243f17d69087176d29ddbb3", "resources/theme/default/icons/overlay.png": "17f28aabcf682bbf6a22d0a80070b1ce", "resources/theme/default/icons/package.svg": "db8542fb863117d5d74f7a6676ab419e", "resources/theme/default/icons/pagesetup.svg": "b25656114dc0af77191a6d7b206c7e91", "resources/theme/default/icons/paste.svg": "02b0ecdb9e40d6e9a3a589be40ea0790", "resources/theme/default/icons/pasteElement.svg": "deede2c394febaf7818a5a74220adb58", "resources/theme/default/icons/pausefly.svg": "aec4d2aee5c1a96174b7d0efbb654d35", "resources/theme/default/icons/plugin-installed.svg": "715e5c20df916d7d0a959ef6a6efbc40", "resources/theme/default/icons/plugin.svg": "c5c2d3092b1c2958aae669b0ce891492", "resources/theme/default/icons/plugins.svg": "e04bad7db3b8435bff70fd5ebbebc369", "resources/theme/default/icons/postgresql.svg": "5c2c9989c1a23c934678119d55c088b8", "resources/theme/default/icons/previous.png": "af4d639c2ccefd5b57d4772e3cc3bb62", "resources/theme/default/icons/previousConflict.svg": "099b009a440d54790c0cda1d60842134", "resources/theme/default/icons/print.svg": "3ba6cb43288c30503450361636fae5aa", "resources/theme/default/icons/processingAlgorithm.svg": "5acd3a3ea3ac4302ebed7d35fc1f16ae", "resources/theme/default/icons/project.svg": "72a57d9485a1a0a78e977a2f472e2213", "resources/theme/default/icons/projectDataTree.svg": "23c638ad38fe4f0dc6a4661730f24081", "resources/theme/default/icons/providerQGeomap.svg": "2c9169fc1c7e50f15b84f71bafd17a6d", "resources/theme/default/icons/pypi.svg": "6b9ea3945cf090b33076ea91ce2e9612", "resources/theme/default/icons/pypi_color.svg": "aefe42c944201ae3a898d71d5ec62075", "resources/theme/default/icons/pyramids.png": "051b61faa344c78af9a059e9cfc78dae", "resources/theme/default/icons/python.svg": "cdf88def02be5b2ce0e4c40b89d2c6ce", "resources/theme/default/icons/python_gray.svg": "b0b260fbbaa0b24915e46e80a5d7af9d", "resources/theme/default/icons/qgeodataspecificationmanagertool.ico": "c86f314e1777d0e7541709d50f9d726c", "resources/theme/default/icons/R.svg": "c27cc0791576e0ca3616b76731907b9e", "resources/theme/default/icons/reduce.svg": "1e88d456b31261ed99c18414a5c47671", "resources/theme/default/icons/regression.svg": "ea2efc714a40dfba39ecbd5218268fa6", "resources/theme/default/icons/remove.svg": "a6328dbd354bd5965aae818d26b273da", "resources/theme/default/icons/removeElement.svg": "7f477ed033b711e6e3b96f9bf22c79e1", "resources/theme/default/icons/rendering.svg": "55bdf00e277bc65c5639e48f8d9f42d2", "resources/theme/default/icons/replace.svg": "fbba9c3ecb7acb6dc76d5fc36cb03302", "resources/theme/default/icons/resolved.svg": "4a3f747ccc1459e54666994682080e38", "resources/theme/default/icons/ribbonMaximize.png": "e5dc4278826e32c25a4723fc87b943b2", "resources/theme/default/icons/ribbonMinimize.png": "42094d61d30e8a72a1a2cbff7a0df598", "resources/theme/default/icons/right.svg": "5dca8df9c3b66d4d1449494b34f936fe", "resources/theme/default/icons/roaming.svg": "4db76c4b080e95322cde6b52d36cfcfb", "resources/theme/default/icons/rubberBandConfig.svg": "9dd7c66dd566eaabd42ab2edd443654c", "resources/theme/default/icons/run.svg": "ec156027329d2c13877625ff3fcbee01", "resources/theme/default/icons/sample.svg": "59c88431eeb5015338430a9b7d1be8c5", "resources/theme/default/icons/sas.ico": "c4c83c2060701e0389262d9d07362896", "resources/theme/default/icons/save.svg": "89eafd6a566e859840d717e73872df13", "resources/theme/default/icons/save_layout.svg": "de4fd322d23ce467c2d30c2f2cb4fec0", "resources/theme/default/icons/scale.svg": "6b074bd9946d667c2f3e6502e634ecd2", "resources/theme/default/icons/scaleAdd.svg": "1724ab174f6709f9485d555a2d372639", "resources/theme/default/icons/scalebartext.svg": "1c6d5b3e2d85f1cbfb7305ebc7c2ee94", "resources/theme/default/icons/scaleClear.svg": "c62dd67d91bfb849c20eb5a1bd37e215", "resources/theme/default/icons/scaleRemove.svg": "0fb15d6df30ef9a5248804f9d082ad04", "resources/theme/default/icons/scaleReset.svg": "c69773f8695e60c85c82f65bfb44a29e", "resources/theme/default/icons/scorecard.svg": "9919e029f7566ce49fbe7338493e0473", "resources/theme/default/icons/script.svg": "cb1493bb56cd8d924de2516d7dbb5187", "resources/theme/default/icons/search.svg": "b116a8aa818d1cd7ec41752a0c29816b", "resources/theme/default/icons/section.svg": "aea2b044eb14c55452bc8172df030f92", "resources/theme/default/icons/selectedrecords.svg": "171b3d8de98149a3675b853745105c22", "resources/theme/default/icons/setExtent.svg": "7080d56468833b423897465aeca93138", "resources/theme/default/icons/setting.svg": "41cf928d70d4e8ed012cc2d15d224ed2", "resources/theme/default/icons/shengcunfenxi.svg": "5ae32cbb038032ed1443b8005baff03c", "resources/theme/default/icons/shortcutsConfig.svg": "4cb9c5a2687e14ba17d7ad9f4dcbfea9", "resources/theme/default/icons/simplearrow.svg": "157c76b4552215d148febb98baf92b1d", "resources/theme/default/icons/situationpoint.svg": "af69811bf86271d54800957689c28aec", "resources/theme/default/icons/skip_line.svg": "eedc935dd2773c44ff7e54a50811030a", "resources/theme/default/icons/slider_close.svg": "3402865a23ff073e953448e3d2480c24", "resources/theme/default/icons/slider_lasttime.svg": "da6ead6853a3db08a482f28ceaf933e3", "resources/theme/default/icons/slider_lastversion.svg": "3d10f7b5692a9ea4b8929e5a65528bfb", "resources/theme/default/icons/slider_nexttime.svg": "b54ce11ec64888d09640861fcc2ed0a5", "resources/theme/default/icons/slider_nextversion.svg": "54213dc4e51bccdd8348bcbdc0102d7b", "resources/theme/default/icons/slider_zoomin.svg": "abeda64aa9f3fa4e6d3bb2eefe65a89e", "resources/theme/default/icons/slider_zoomout.svg": "b9a24b973053cf1d836872b788180f00", "resources/theme/default/icons/sloperuler.svg": "16d462cb766b85c0ec907ce5c529bb02", "resources/theme/default/icons/spss.svg": "4d8cb6044137301a4101755e43b83fc1", "resources/theme/default/icons/sql.svg": "a0997f68ee9c263ffecd0efbaa524236", "resources/theme/default/icons/stata.svg": "a62e991cde9e8389dea6bf46b0bccb43", "resources/theme/default/icons/sun.svg": "e98cbd692ec62d35d27c78c31d91f86f", "resources/theme/default/icons/symbology.svg": "062e0ba664ee2320d21cda33ddaf3525", "resources/theme/default/icons/symbolreorder.svg": "062e0ba664ee2320d21cda33ddaf3525", "resources/theme/default/icons/system.svg": "c76354112390bc2f9151b4baf729ccfc", "resources/theme/default/icons/table.svg": "21615bcc2dab3f7d81b36b7267d5c003", "resources/theme/default/icons/tablegroup.svg": "b9a57bb1b7a98b1ea2220552ae77f576", "resources/theme/default/icons/task.svg": "1223b13e13ffccea138013fd33b71775", "resources/theme/default/icons/teamEditProject.svg": "35828465cb83319c8579c9cb9a904481", "resources/theme/default/icons/terrain.svg": "5e2ccfec44e7d6a40320912b4137615e", "resources/theme/default/icons/threenorth.svg": "96eada7b3eeebdca632533988e617717", "resources/theme/default/icons/time_series.svg": "94fd406769655b721b17f4cb17bc673f", "resources/theme/default/icons/top.svg": "22d2fefe180a699944a6a5ab545ab5a9", "resources/theme/default/icons/topologyConfig.svg": "2af77dff1d80bcccdefd9079ed4d68b6", "resources/theme/default/icons/transformed.svg": "5b98c431c64bf1f5697a2c63cab6ca11", "resources/theme/default/icons/transparency.png": "17c24a4daf81d8d7f8b9c7642e2fa7e7", "resources/theme/default/icons/transposition.svg": "07d926b580577fcc30c3a709cbde68bc", "resources/theme/default/icons/tree.svg": "14ae56a092c0268672520520ffc4443a", "resources/theme/default/icons/txt.svg": "867501b0b384f46cf6d4f225605995c1", "resources/theme/default/icons/undo.svg": "5484c93bf4a89654d20a2ba6bb66d912", "resources/theme/default/icons/uploaddata.svg": "e2a9a1ccdb820364d99b96d1a3ab39b6", "resources/theme/default/icons/upWard.svg": "b1c72b33367343316cc888d977ebb43d", "resources/theme/default/icons/useLeftAll.svg": "fddc9b7c0d601a881ab55af95659d3cd", "resources/theme/default/icons/useLeftSelected.svg": "1764c036105882a4743fc4e34e384404", "resources/theme/default/icons/useRightAll.svg": "c7020f993a82c847054dadf2b5709a5a", "resources/theme/default/icons/useRightSelected.svg": "18d35ec89cbb795bb88449c8859b3d81", "resources/theme/default/icons/var.svg": "c3d9e7461e40e74c17f3ce5873905f51", "resources/theme/default/icons/var_open.svg": "b023934e733448d74563926e0d5f9a1f", "resources/theme/default/icons/vectorTileStyle.svg": "670fc3779ea1e4b103aa75efd3832baf", "resources/theme/default/icons/view_var.svg": "a495e2e336064e1549622b87c69dd649", "resources/theme/default/icons/visibleMap.svg": "7ec32396ae7a2da699cf55929aed4f98", "resources/theme/default/icons/walking.svg": "790a25d6b2f642d0e2c04e5f5e5efd01", "resources/theme/default/icons/website.svg": "c789f48d1f500ad317ef3cde4818b240", "resources/theme/default/icons/windowicon.ico": "dd4b17c0ec69f51b039c51b6e0887c31", "resources/theme/default/images/addNode.svg": "d11d5a3ce54b3a864281b2f5f3311e36", "resources/theme/default/images/attributeBrush.svg": "ddeee5f9e2e1e63792eff8cbbb840164", "resources/theme/default/images/background.png": "ddf7168c491dac078b9b0c7c6771d602", "resources/theme/default/images/breakByOnePoint.svg": "ed5c5d11f74011fcb4c8a027eeb31599", "resources/theme/default/images/breakByTwoPoints.svg": "ce13a12a1afde966f284661dfdb1471f", "resources/theme/default/images/copyFeature.svg": "0293921cdf1de395e600349621565051", "resources/theme/default/images/cursor_leftbottom.svg": "3e9b60706a3f0bf7d729c48bd440cdc7", "resources/theme/default/images/cursor_leftright.svg": "418f31c70c594b53922604cdddb036e5", "resources/theme/default/images/cursor_lefttop.svg": "c364223abf37f51dd7def7162d9be9af", "resources/theme/default/images/cursor_move.svg": "8a82e82a355f18c8f410bd9bbf0c5f8e", "resources/theme/default/images/cursor_topbottom.svg": "ec0ad72e180c458e3155a88a2db6c023", "resources/theme/default/images/deleteNode.svg": "2aae237e59216f92c6b87dd7757c1a95", "resources/theme/default/images/editDraw.svg": "23e810e15d33b68a03427f8a40a408a1", "resources/theme/default/images/editSelect.svg": "8859783cdb6f54f380faadc8ad60c3c0", "resources/theme/default/images/error.png": "450eb59729737d72ceb03818c43ffcc9", "resources/theme/default/images/extensionPolyline.svg": "91c86deac1a658ed6b928487fb076723", "resources/theme/default/images/identify.svg": "ac8cb0d555021521270e9e6cc0d8ce59", "resources/theme/default/images/information.png": "423578ade54524e34a95580788d7729d", "resources/theme/default/images/mCapturePoint.svg": "5caf3405335284af6d880495b01d5776", "resources/theme/default/images/measure.svg": "23e810e15d33b68a03427f8a40a408a1", "resources/theme/default/images/mIconDeselected.svg": "75733116db9e219b0f4382385342b511", "resources/theme/default/images/mIconSelected.svg": "cc341d78f94f446e0e6ca672482edd15", "resources/theme/default/images/move.svg": "8a82e82a355f18c8f410bd9bbf0c5f8e", "resources/theme/default/images/moveFeature.svg": "8a82e82a355f18c8f410bd9bbf0c5f8e", "resources/theme/default/images/moveNode.svg": "84a9570e8f6d04cd64c450c1523aad4f", "resources/theme/default/images/mPageLayoutPan.svg": "67a143805264211f1031ebb79eb356a4", "resources/theme/default/images/mPageLayoutZoomIn.svg": "0d4e308d01775da24950c7c7869cadc2", "resources/theme/default/images/mPageLayoutZoomOut.svg": "ffa286230cff03f5bf4003a7f2e77a94", "resources/theme/default/images/mPanClose.svg": "a0603e76de7727c6a3a524adec772f49", "resources/theme/default/images/overlayUpdates.png": "75a5188a1a36b3e55a0c26bf0692ec3c", "resources/theme/default/images/pan.svg": "8fa8827e723ea97094655ce0ab60bcbb", "resources/theme/default/images/pyramidfirst.png": "6336d1c87d3a53c59f42ab682a7ef68d", "resources/theme/default/images/pyramidfourth.png": "3ca3b9cdbb6b88a50f6a270c24504df2", "resources/theme/default/images/pyramidsecond.png": "d8eb0fae632533ce16178629a61e77d6", "resources/theme/default/images/pyramidthird.png": "2dba3fd37994c231b6fe468f5922144a", "resources/theme/default/images/rotateFeature.svg": "d47a6e3f34dd3d3484440c7e4a96b58c", "resources/theme/default/images/select.svg": "128d8d763c91a71599ad86fcc069ad63", "resources/theme/default/images/selectbypolygon.svg": "5efdd8dc872ba52a262b939cfcd70515", "resources/theme/default/images/selectbyradius.svg": "c2d9e4efd0649941dfee9a843284ccca", "resources/theme/default/images/splitBySelect.svg": "021d0ae70cb8157d9d6f18feef0db270", "resources/theme/default/images/SwipeDown.svg": "21f3775a767d4cfb81d748276baf522a", "resources/theme/default/images/SwipeLeft.svg": "457aac45411c29aa4d6f1b63e6d999ba", "resources/theme/default/images/SwipeLeftRight.svg": "21193a1f1446c5c392d7a3807427e25a", "resources/theme/default/images/SwipeRight.svg": "cc700b8a09d58417f17496738db67404", "resources/theme/default/images/SwipeUp.svg": "b440961cee34d1649815aa594a896318", "resources/theme/default/images/SwipeUpDown.svg": "e21665a6f5d712ab4b25022ddb738ca5", "resources/theme/default/images/systemabout.png": "067610e0d804b16c75496b4ed77005dd", "resources/theme/default/images/thematicAttributes.svg": "ac8cb0d555021521270e9e6cc0d8ce59", "resources/theme/default/images/warning.png": "9323d69c25b6f79fc974023fee921af0", "resources/theme/default/images/zoomin.svg": "6bf9cf3ff08b719f4f872977e7ee201f", "resources/theme/default/images/zoomout.svg": "debcf4106754577838008ca423288371", "static/README.md": "2b883d8ed7e177b421e29625caf11a10", "static/tutorials_page.html": "7e6350719caf6a92de7a495441cd65ed", "static/css/iview.min.css": "b56ab90b84c3ac9f460f50906cbf5ae8", "static/js/echarts.min.js": "40874546a400f6e1b358c0495998a43f", "static/js/form-create.min.js": "5b2e899b103ec5bade920ac909268db4", "static/js/iview.min.js": "cb94a058fc714808d440d2b9835fed0d", "static/js/jquery-3.5.1.min.js": "dc5e7f18c8d36ac1d3d4753a87c98d0a", "static/js/vue.min.js": "b0473a59bd7e655c4da3d26f50dbba1e", "static/js/element-ui/CHANGELOG.en-US.md": "f90a50248e8a935d4bf94939b2b83bc1", "static/js/element-ui/CHANGELOG.es.md": "cd352a41d428cfe359e2dd25b3e69216", "static/js/element-ui/CHANGELOG.fr-FR.md": "2a5cb94c1cc391f95705a822c50906d2", "static/js/element-ui/CHANGELOG.zh-CN.md": "fae21798d847ea9bd4827f78766d634e", "static/js/element-ui/package.json": "ed15655a88db7bf4e42f2879000f0241", "static/js/element-ui/README.md": "29e235e7c8cba854610d2faa0424c5a0", "static/js/element-ui/lib/alert.js": "44bd2ce7e47fc65112066ebe354ceaca", "static/js/element-ui/lib/aside.js": "6af3243f56f6c0bda3eba64e2cd8078a", "static/js/element-ui/lib/autocomplete.js": "8cfc24a3a0f63f57b0bbefd1d43150b1", "static/js/element-ui/lib/avatar.js": "0c3f24bdffc3f4c3016149e5b589bf58", "static/js/element-ui/lib/backtop.js": "4ba1079d79f186a4b73395ab8048ce30", "static/js/element-ui/lib/badge.js": "d124ddea127b3b87fb7fc0e3d28f976e", "static/js/element-ui/lib/breadcrumb-item.js": "3ee84610ae6e927194916dc1850be078", "static/js/element-ui/lib/breadcrumb.js": "3b696a8b4d753bce1c9a42f1f2f2e559", "static/js/element-ui/lib/button-group.js": "c13e97a9b747982a6fe3475ce0963202", "static/js/element-ui/lib/button.js": "db479a095696d3b41e4e8e5ca573b2d9", "static/js/element-ui/lib/calendar.js": "78b60123511490a4957bb7ecc90ca562", "static/js/element-ui/lib/card.js": "a63bc5d2a60ecffc3fe581d2b9a86b19", "static/js/element-ui/lib/carousel-item.js": "24398b44ec0a885f9f7dcf7b4511b140", "static/js/element-ui/lib/carousel.js": "dc86769400de82ac847dfe71d278331a", "static/js/element-ui/lib/cascader-panel.js": "400c0c6ad5bb41a6d0aa489f027a849c", "static/js/element-ui/lib/cascader.js": "a97cc547f182cb959ff0af6d31f0769c", "static/js/element-ui/lib/checkbox-button.js": "e867a0fcfa56c197d8cbbcd8000ae723", "static/js/element-ui/lib/checkbox-group.js": "81593c2eeeee70e69fff71af4dfe212c", "static/js/element-ui/lib/checkbox.js": "09780b33e4add1d32b2b870c8ccc5a4b", "static/js/element-ui/lib/col.js": "227c1189111f2dfbada9a8f26e1433ed", "static/js/element-ui/lib/collapse-item.js": "0ef8b7bea75fa2d185730fa809d86148", "static/js/element-ui/lib/collapse.js": "8cc69b5cbaefb354e3fa19e748a54118", "static/js/element-ui/lib/color-picker.js": "25752f575ad81607f8b3b4652e1e1790", "static/js/element-ui/lib/container.js": "f2118022ef7e900503a8028e72a47bf2", "static/js/element-ui/lib/date-picker.js": "160c4f0f12cc50d77eeff03eb3e2abce", "static/js/element-ui/lib/dialog.js": "3446278e381ae6162bfc309cd883b84a", "static/js/element-ui/lib/divider.js": "ce49dbf8658355e06eb9ca852acd98e0", "static/js/element-ui/lib/drawer.js": "2a4f53c5e574a8fbe04eb528ccdb4e6b", "static/js/element-ui/lib/dropdown-item.js": "1c440ab125200ed27bb4402f26ae6edf", "static/js/element-ui/lib/dropdown-menu.js": "51e13aaf4f6dea2a6ec4eb6a06f49c1a", "static/js/element-ui/lib/dropdown.js": "02786a92e4ed20d950ad7d09e6dd03e7", "static/js/element-ui/lib/element-ui.common.js": "b6b435a52f5cdc204fb8a023bc856a16", "static/js/element-ui/lib/footer.js": "4d392ecf90fa43ebf25caa31e1783354", "static/js/element-ui/lib/form-item.js": "98d4dd1fa2c81e68ff40dc3344620df9", "static/js/element-ui/lib/form.js": "88eeb8b813539099fec26e9993789af8", "static/js/element-ui/lib/header.js": "9c9ab6bc2af99e67eee8367ab3ed855b", "static/js/element-ui/lib/icon.js": "658fcece90b39788b763c25f17d60207", "static/js/element-ui/lib/image.js": "650a56522f7a590b8fca25fdfb09690a", "static/js/element-ui/lib/index.js": "28fb829b428bc14fe9d26f852dbc11a9", "static/js/element-ui/lib/infinite-scroll.js": "3c09c5248512b2f0281e8e8bf4ecee8c", "static/js/element-ui/lib/input-number.js": "3dd23cc075c49822fe68366cef6f09a3", "static/js/element-ui/lib/input.js": "bb52be1067cfd9b1dd8ded0b664f5605", "static/js/element-ui/lib/link.js": "312480ee5f2292f314346434faa3518f", "static/js/element-ui/lib/loading.js": "9bb5753b57ee274668c5748d38eac2ab", "static/js/element-ui/lib/main.js": "92a26937eaac62367d2b81ebec36d9f0", "static/js/element-ui/lib/menu-item-group.js": "2098666a7bbd0c271cc084c3924d4ec3", "static/js/element-ui/lib/menu-item.js": "bf8db518c9b73c9cd3771dabf91bcaca", "static/js/element-ui/lib/menu.js": "0188cd906e1159936cbee28980fbec39", "static/js/element-ui/lib/message-box.js": "e275b026ca058dff799673d5740b6e22", "static/js/element-ui/lib/message.js": "0f64248045730556999011fa779d1dfa", "static/js/element-ui/lib/notification.js": "6190c53c839faa5b01a815de6769c899", "static/js/element-ui/lib/option-group.js": "f2759330e59d0518f60a6c5547d3c827", "static/js/element-ui/lib/option.js": "1c418a74feafdc73cfa5360764f1b90a", "static/js/element-ui/lib/page-header.js": "6a9f088700adf20d389cc542e6a0deaf", "static/js/element-ui/lib/pagination.js": "17b696cc294ca8bbc37af7b1c5968f7a", "static/js/element-ui/lib/popconfirm.js": "6bcceddbd44e456d716b66a018beaf7c", "static/js/element-ui/lib/popover.js": "a5cf39911205f08d5e5691a158bd81c6", "static/js/element-ui/lib/progress.js": "6f206f6d72b0622ea6a6c2fccd062bb5", "static/js/element-ui/lib/radio-button.js": "efdf34b9f8c565b99aeae587e11eb87a", "static/js/element-ui/lib/radio-group.js": "93003dbff84a768dbb1a12e7d6e7796a", "static/js/element-ui/lib/radio.js": "66201ad4319bcf5330d5711537cb85c6", "static/js/element-ui/lib/rate.js": "d57f0eede17b4ff2c80fbc3a5899d030", "static/js/element-ui/lib/row.js": "4d92917609e4db63a0acc64bca5d9ab4", "static/js/element-ui/lib/scrollbar.js": "76120f65f535b535d19fc1e820e959d5", "static/js/element-ui/lib/select.js": "5e900a932de2f3be38ef8f436fa201e4", "static/js/element-ui/lib/slider.js": "faf0a48d331a5c869c7bf7c8a07abe58", "static/js/element-ui/lib/spinner.js": "83f0cb9cc66bba831681c58c230890fd", "static/js/element-ui/lib/step.js": "cd744e288e5eeb55dde52cbe0909016d", "static/js/element-ui/lib/steps.js": "7cb3835a98f56194a2ca85cbeb197bf2", "static/js/element-ui/lib/submenu.js": "07584a153decdad6f82fe2de4e565f15", "static/js/element-ui/lib/switch.js": "aa60bca689f5db14f3bad0fcd8cb3ece", "static/js/element-ui/lib/tab-pane.js": "9cba2ce5089e06c7a6f9560687e0e150", "static/js/element-ui/lib/table-column.js": "bcf79a6f1de840db943cdee7c4e41921", "static/js/element-ui/lib/table.js": "f017fc250b36fbfebeef1b462b6984f8", "static/js/element-ui/lib/tabs.js": "f19f934d0ee0cb9b4234c9d81301f0b7", "static/js/element-ui/lib/tag.js": "66492bd336c57f37bbd9da3cac97b51c", "static/js/element-ui/lib/time-picker.js": "d2561b513c3dfdbb57f34f3f6855bd5b", "static/js/element-ui/lib/time-select.js": "c562bd018cb94d0a5f993a23bdb2e687", "static/js/element-ui/lib/timeline-item.js": "84ce5c070a064909355b56610bd8ff2f", "static/js/element-ui/lib/timeline.js": "e66393cdd8e7a22413acad9ab1f6f8af", "static/js/element-ui/lib/tooltip.js": "7f6a6999519c70a0485d2ae7d03ea6ab", "static/js/element-ui/lib/transfer.js": "b5825b45e447c403e3b0833e7975fa96", "static/js/element-ui/lib/tree.js": "16346c71a54ffbaddc37b635296704ed", "static/js/element-ui/lib/upload.js": "1682818d886774e15dd8267e40d81ecb", "static/js/element-ui/lib/directives/mousewheel.js": "d57acb00ba5ec4322dd7b4e37dedbb69", "static/js/element-ui/lib/directives/repeat-click.js": "ee7cb0bb5822588e21af009528f04490", "static/js/element-ui/lib/locale/format.js": "471399a5f5d8d53d46cb7e2fcad68d0d", "static/js/element-ui/lib/locale/index.js": "7f48cd4280e7b5be536b37a33af7a441", "static/js/element-ui/lib/locale/lang/af-ZA.js": "1cab2e3d954eaf982bff6464654f162a", "static/js/element-ui/lib/locale/lang/ar.js": "a973ecdc856fa1ea02fc3a1f1d436ddb", "static/js/element-ui/lib/locale/lang/bg.js": "100d5a70d89307668e0a512d953bbbd3", "static/js/element-ui/lib/locale/lang/ca.js": "f85b7ef8722b22ab636c540dc90c9aae", "static/js/element-ui/lib/locale/lang/cs-CZ.js": "5b0aeb0ab3f5177a7d2ae975db11ff20", "static/js/element-ui/lib/locale/lang/da.js": "7b04b9b382da5785b105084c0b1895fc", "static/js/element-ui/lib/locale/lang/de.js": "165678f7ff2d5c05c1b196c1fe36919f", "static/js/element-ui/lib/locale/lang/ee.js": "77cab864f3b5c0d02700606da528e0c9", "static/js/element-ui/lib/locale/lang/el.js": "1ab144c6f49d289c320e8672fdd78d05", "static/js/element-ui/lib/locale/lang/en.js": "fbea06cf549fdf1bc8107e258676825d", "static/js/element-ui/lib/locale/lang/eo.js": "91b4bd398fd27e80d331675e0567eae8", "static/js/element-ui/lib/locale/lang/es.js": "e1845d63e50ee4f117aa16c8b7756cc7", "static/js/element-ui/lib/locale/lang/eu.js": "6851cf67a17a2988f1e905557ebba981", "static/js/element-ui/lib/locale/lang/fa.js": "67763039da687c77702e59237ce66e04", "static/js/element-ui/lib/locale/lang/fi.js": "0b35a1d0fb672b74e243a5f41defdd70", "static/js/element-ui/lib/locale/lang/fr.js": "64c0cbb50596da4841223e8c85c34c1b", "static/js/element-ui/lib/locale/lang/he.js": "2a3719ffb84a33ed3dd1e9799c03a3dd", "static/js/element-ui/lib/locale/lang/hr.js": "81661a420afc076d1792c21f0432a50e", "static/js/element-ui/lib/locale/lang/hu.js": "0fa9fc603c3f5ad1bbbab426db5fa3cf", "static/js/element-ui/lib/locale/lang/hy-AM.js": "a6d88e26e14fd663a5aebe98623b1e5d", "static/js/element-ui/lib/locale/lang/id.js": "ace3933e6fba75bf17787c34580d1430", "static/js/element-ui/lib/locale/lang/it.js": "ddbaa0c9de79f04c13510abf9e9fe347", "static/js/element-ui/lib/locale/lang/ja.js": "927e2dbb268276b854f4ab58486c029d", "static/js/element-ui/lib/locale/lang/kg.js": "321e5d0e5cd78eb71baeea679f9866ad", "static/js/element-ui/lib/locale/lang/km.js": "712afcf5ce5bd04235c9da5cabd3211c", "static/js/element-ui/lib/locale/lang/ko.js": "a1462b4c5f54be3312a11745525baf79", "static/js/element-ui/lib/locale/lang/ku.js": "0843793ace9f2be0d749f90ae499fd25", "static/js/element-ui/lib/locale/lang/kz.js": "7da2b07e5b87618e3a7176ddfe04887a", "static/js/element-ui/lib/locale/lang/lt.js": "9525aa7bd194094651d15525bd32d060", "static/js/element-ui/lib/locale/lang/lv.js": "6a911e229e2501001d303a8fdf425d32", "static/js/element-ui/lib/locale/lang/mn.js": "2b91da6fd41d16584339b227082dd47f", "static/js/element-ui/lib/locale/lang/nb-NO.js": "91463e2c92f200c50e1dccd73e03695f", "static/js/element-ui/lib/locale/lang/nl.js": "feab63aa44ae350f99f1d484a91d942c", "static/js/element-ui/lib/locale/lang/pl.js": "a27556391421cdbf5d2424d38955b645", "static/js/element-ui/lib/locale/lang/pt-br.js": "1a7d47d3cca171739de21a0cc99387f1", "static/js/element-ui/lib/locale/lang/pt.js": "87ef9e7ed1f27c582e7faabc406cf483", "static/js/element-ui/lib/locale/lang/ro.js": "b608c43b34a6975d98e4b7b2b7295402", "static/js/element-ui/lib/locale/lang/ru-RU.js": "0504c7d75043cb31bcb692a689743cef", "static/js/element-ui/lib/locale/lang/sk.js": "3dd18f7fd8a017f994feba8b6d9a0890", "static/js/element-ui/lib/locale/lang/sl.js": "9ce57a4806c0740551d7dc51e786c86e", "static/js/element-ui/lib/locale/lang/sr.js": "d2ff6141f3ecd471a8cf846a24d7a8ed", "static/js/element-ui/lib/locale/lang/sv-SE.js": "3ca29384088f1f47398278654fa3011c", "static/js/element-ui/lib/locale/lang/ta.js": "b4de5e6d6863d34dfa6e98ccf495d504", "static/js/element-ui/lib/locale/lang/th.js": "cf6e83d1c133279705c8296ef90e775b", "static/js/element-ui/lib/locale/lang/tk.js": "7c799830a4ba20120bc2e7bccf3419d8", "static/js/element-ui/lib/locale/lang/tr-TR.js": "061e2668858c3654f3bf6750276546be", "static/js/element-ui/lib/locale/lang/ua.js": "399ff5fe5470ec3e4a57f88d1ae6fe6b", "static/js/element-ui/lib/locale/lang/ug-CN.js": "fa8d94b219647056165f1baaa843b5e6", "static/js/element-ui/lib/locale/lang/uz-UZ.js": "3607e6bf57b1d37e917736c460d7dd63", "static/js/element-ui/lib/locale/lang/vi.js": "3c54c8dec1d0c8afccd03efb53a0b53c", "static/js/element-ui/lib/locale/lang/zh-CN.js": "0b60542152156fa18a045d408bed61ea", "static/js/element-ui/lib/locale/lang/zh-TW.js": "d886891267165c838c345606c972856e", "static/js/element-ui/lib/mixins/emitter.js": "f38340cf5d69582efda171286efafa65", "static/js/element-ui/lib/mixins/focus.js": "b113513900bcf212968edd814c163760", "static/js/element-ui/lib/mixins/locale.js": "9b538e9a7f8d56c2c1f85984a9d4b2d2", "static/js/element-ui/lib/mixins/migrating.js": "7774a8a036683cd8b2b3340e0a63b132", "static/js/element-ui/lib/theme-chalk/alert.css": "4a233d163ac6641263075f6e7c4821db", "static/js/element-ui/lib/theme-chalk/aside.css": "edcc490fb0053f1cce4a71a5c047bdf9", "static/js/element-ui/lib/theme-chalk/autocomplete.css": "47ce2bcdfb1b1b08f5c623f9387391ff", "static/js/element-ui/lib/theme-chalk/avatar.css": "58a5fedd9b307c75a19c3b0533d813b8", "static/js/element-ui/lib/theme-chalk/backtop.css": "8b2ca61408dad479b881960763d3db98", "static/js/element-ui/lib/theme-chalk/badge.css": "ea8bfd89345e13ad428bdfb5574237b4", "static/js/element-ui/lib/theme-chalk/base.css": "f25cc484087487b1e1ef6ce8bf62ca44", "static/js/element-ui/lib/theme-chalk/breadcrumb-item.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/breadcrumb.css": "16d9a27c1cd682756f3173c95a83d0ed", "static/js/element-ui/lib/theme-chalk/button-group.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/button.css": "41b0f4e08fdd9eedc5a38b9713ddd904", "static/js/element-ui/lib/theme-chalk/calendar.css": "982528766ad2393b8617dbbf05dfaa7f", "static/js/element-ui/lib/theme-chalk/card.css": "48a5b07b8b0a6fb0c5eb6eafacdd0cd5", "static/js/element-ui/lib/theme-chalk/carousel-item.css": "6c97d1a467ad4b215ee69ed21754a728", "static/js/element-ui/lib/theme-chalk/carousel.css": "53761931f0070d80e77e2073f18b1d23", "static/js/element-ui/lib/theme-chalk/cascader-panel.css": "d897ade7fa060cfb4a7fc14503e61a44", "static/js/element-ui/lib/theme-chalk/cascader.css": "bdded6ec78f1471b771f1c4e4e07b596", "static/js/element-ui/lib/theme-chalk/checkbox-button.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/checkbox-group.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/checkbox.css": "515b3e4a1c684ea0538292380b38ca5f", "static/js/element-ui/lib/theme-chalk/col.css": "cd14b27ef81a2fc60079ecf869bca3cd", "static/js/element-ui/lib/theme-chalk/collapse-item.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/collapse.css": "9b8b3424302f453728fe80719c97de40", "static/js/element-ui/lib/theme-chalk/color-picker.css": "083fba820bd7440f18cf93af1d6a31dd", "static/js/element-ui/lib/theme-chalk/container.css": "e79009df433ab4403029e519ddc3340d", "static/js/element-ui/lib/theme-chalk/date-picker.css": "bd299472f79e6132d0dc6845176cadbe", "static/js/element-ui/lib/theme-chalk/dialog.css": "d30bf0818a97168906dc243c0e04a1bf", "static/js/element-ui/lib/theme-chalk/display.css": "c110a2385504d5ee6adb4377365270d7", "static/js/element-ui/lib/theme-chalk/divider.css": "6e52365004d46117ecfb085bc38479b0", "static/js/element-ui/lib/theme-chalk/drawer.css": "6ba86d8fe04df4d3549e4b3fc950bcb4", "static/js/element-ui/lib/theme-chalk/dropdown-item.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/dropdown-menu.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/dropdown.css": "246cd4c0d0f8d4493c5e987d0e330925", "static/js/element-ui/lib/theme-chalk/footer.css": "0a75eee620a1eec96cf1718047bde916", "static/js/element-ui/lib/theme-chalk/form-item.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/form.css": "8521897ad3a147046fd049b37f6775c8", "static/js/element-ui/lib/theme-chalk/header.css": "4bf1a1be0a4af7778c092b276458787a", "static/js/element-ui/lib/theme-chalk/icon.css": "83dc46b9fc11c99b5c0511d0a9a9987e", "static/js/element-ui/lib/theme-chalk/image.css": "32f4b078377ca3c364767e55bba62ede", "static/js/element-ui/lib/theme-chalk/index.css": "2414fd307c22e07b681e50e0720cbc23", "static/js/element-ui/lib/theme-chalk/infinite-scroll.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/infiniteScroll.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/input-number.css": "a30d3d47860db05882de8597cbb35a3f", "static/js/element-ui/lib/theme-chalk/input.css": "88d9749aad3cfd64720e1090474a8573", "static/js/element-ui/lib/theme-chalk/link.css": "b29ddaa7be960e0dabeee5b23fed982c", "static/js/element-ui/lib/theme-chalk/loading.css": "ee99b8ad8874ed9e7df64b7dab4159a6", "static/js/element-ui/lib/theme-chalk/main.css": "9923eb608c01cf001501708e1c8df0bc", "static/js/element-ui/lib/theme-chalk/menu-item-group.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/menu-item.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/menu.css": "30fa354841214de1919e49d8e831a5bf", "static/js/element-ui/lib/theme-chalk/message-box.css": "6fe773d1292f0acbc9ddad9153548c58", "static/js/element-ui/lib/theme-chalk/message.css": "6ab057c82772aa70e8ecc9902d4284ec", "static/js/element-ui/lib/theme-chalk/notification.css": "875a37cf12d647c1dee9e7e467ea01fb", "static/js/element-ui/lib/theme-chalk/option-group.css": "b3a13247682f590bb17b55517326ec39", "static/js/element-ui/lib/theme-chalk/option.css": "1698a68c50ff2618face813daa89982e", "static/js/element-ui/lib/theme-chalk/page-header.css": "3508c570c80476d68b49a13d4c14e27c", "static/js/element-ui/lib/theme-chalk/pagination.css": "93a1866f64772a9d877796cc27f59e2b", "static/js/element-ui/lib/theme-chalk/popconfirm.css": "24183a243d4c79db8c383f296300a5be", "static/js/element-ui/lib/theme-chalk/popover.css": "4d19d78df7b0a3157772b86018648ee9", "static/js/element-ui/lib/theme-chalk/popper.css": "36fcdf5712174c8177fe2fcb73977ed5", "static/js/element-ui/lib/theme-chalk/progress.css": "a3d92e7241d275e11982945ad7c7e284", "static/js/element-ui/lib/theme-chalk/radio-button.css": "d126d35d880b72736a6d24b94170b9eb", "static/js/element-ui/lib/theme-chalk/radio-group.css": "86af00fe6b72f9005bf39e3481691586", "static/js/element-ui/lib/theme-chalk/radio.css": "a3110894bf315e7eedf9111a951840f6", "static/js/element-ui/lib/theme-chalk/rate.css": "0f33a694a0e389f05cd24aeec4b1765e", "static/js/element-ui/lib/theme-chalk/reset.css": "3db1afd65a0400d0d643f705d44250f5", "static/js/element-ui/lib/theme-chalk/row.css": "b1ac86fc178549496bb90eec4ba5da5e", "static/js/element-ui/lib/theme-chalk/scrollbar.css": "23449b1c81727518ce5d914579ee6174", "static/js/element-ui/lib/theme-chalk/select-dropdown.css": "f95dc3aef5e200e495d51f00a81b4e20", "static/js/element-ui/lib/theme-chalk/select.css": "d16e8c09f8fa26ec7d03b5652cdbb797", "static/js/element-ui/lib/theme-chalk/slider.css": "c7db7b54589f0ba57dd2b3a1057bfb90", "static/js/element-ui/lib/theme-chalk/spinner.css": "5b5d1e5f3e4422c063adc569a8346fca", "static/js/element-ui/lib/theme-chalk/step.css": "cf252484e262941d6d6c081042e38074", "static/js/element-ui/lib/theme-chalk/steps.css": "ac6f8637955a659b97ea3548a20fcfba", "static/js/element-ui/lib/theme-chalk/submenu.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/switch.css": "dbe3a9344c8c3594fce88715c118edf9", "static/js/element-ui/lib/theme-chalk/tab-pane.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/table-column.css": "e11267cebea4826211fbfbc1f88c6ec8", "static/js/element-ui/lib/theme-chalk/table.css": "facf1d994f3fda680e22163b2e4148c2", "static/js/element-ui/lib/theme-chalk/tabs.css": "ec342796738d9d1a87137c0451c6b784", "static/js/element-ui/lib/theme-chalk/tag.css": "52178a68247a9fe015ccb5e965ce3fd3", "static/js/element-ui/lib/theme-chalk/time-picker.css": "7a25549a612c52a82dcc265f20566f0c", "static/js/element-ui/lib/theme-chalk/time-select.css": "a230a27e1eae140dc38e608de2651ba6", "static/js/element-ui/lib/theme-chalk/timeline-item.css": "743e68350200b578c75506697a7f5789", "static/js/element-ui/lib/theme-chalk/timeline.css": "b5d2b9a92b38005cff9a5752246992e1", "static/js/element-ui/lib/theme-chalk/tooltip.css": "4722109edb63d5c732ca61ecb3d47175", "static/js/element-ui/lib/theme-chalk/transfer.css": "14928e12a3985c3d0e516e6799601d65", "static/js/element-ui/lib/theme-chalk/tree.css": "5f5d85942e52967f08d4f1698cee2683", "static/js/element-ui/lib/theme-chalk/upload.css": "077d8dc834e6d88194879db89ff54bc5", "static/js/element-ui/lib/theme-chalk/fonts/element-icons.ttf": "4b1a4d348209ad29243b0a042de1b557", "static/js/element-ui/lib/theme-chalk/fonts/element-icons.woff": "f717deee44e7fcc757c6c1ade5cce443", "static/js/element-ui/lib/transitions/collapse-transition.js": "354e780b8a86771cd737928485f7d798", "static/js/element-ui/lib/umd/locale/af-ZA.js": "0fc2ac6b8936f2cd76256d2672b8b426", "static/js/element-ui/lib/umd/locale/ar.js": "18bbfc189ca1a3da137e560c112e0aba", "static/js/element-ui/lib/umd/locale/bg.js": "2c6245f699c62437fa7ee0d163cdd65c", "static/js/element-ui/lib/umd/locale/ca.js": "f494da9c984efa37bb7aff8be66d026e", "static/js/element-ui/lib/umd/locale/cs-CZ.js": "6554aa484f5ec7b4d1e3ad176cc69b71", "static/js/element-ui/lib/umd/locale/da.js": "0fb807a6d9d65b4d438c1e8563e369c4", "static/js/element-ui/lib/umd/locale/de.js": "835ff2b17f107ff50be524805b201f3c", "static/js/element-ui/lib/umd/locale/ee.js": "a363d6f90a0acfea8f31f1a6be49aeee", "static/js/element-ui/lib/umd/locale/el.js": "e9b50aee66128071682125d1e9d98c23", "static/js/element-ui/lib/umd/locale/en.js": "eccc9db8251b0ef6f69337f39234d423", "static/js/element-ui/lib/umd/locale/eo.js": "373c79e4396edec41165343f5a889f99", "static/js/element-ui/lib/umd/locale/es.js": "f33d213c3974b2acdc167886b61f2e47", "static/js/element-ui/lib/umd/locale/eu.js": "62bcc16524b72c7094a20162832b36b5", "static/js/element-ui/lib/umd/locale/fa.js": "273a1e35316564355fed5eb3bfeb2ab8", "static/js/element-ui/lib/umd/locale/fi.js": "9f9889caf4adcffc6cc78c229c457a7a", "static/js/element-ui/lib/umd/locale/fr.js": "ecd5286ed9b8faf83a56bbe449df9e00", "static/js/element-ui/lib/umd/locale/he.js": "db9119fe9dc05c872421bf4758de2eca", "static/js/element-ui/lib/umd/locale/hr.js": "9396ffd6cc82eeb3a09ce0d8ed1dd483", "static/js/element-ui/lib/umd/locale/hu.js": "1e6962cb413392e68dae16680bcd6370", "static/js/element-ui/lib/umd/locale/hy-AM.js": "85701cecfaf21f5813aeebe91b2b7560", "static/js/element-ui/lib/umd/locale/id.js": "1b99f644e81cf63fc7cecd34746dc66e", "static/js/element-ui/lib/umd/locale/it.js": "4fd6e8ba9c2d5fe7c9e74f753b95379c", "static/js/element-ui/lib/umd/locale/ja.js": "567701dd5feb9804153acefabfaa44b9", "static/js/element-ui/lib/umd/locale/kg.js": "4b4067ad2fb93f84a6c9e8c35de483e9", "static/js/element-ui/lib/umd/locale/km.js": "c69fc83984c03a7a4f04cdc6967cca58", "static/js/element-ui/lib/umd/locale/ko.js": "9bdf5bf54251017cb60636c8b4fe0ecf", "static/js/element-ui/lib/umd/locale/ku.js": "581e50d5471c9ec4c535ebcdb5ba68c8", "static/js/element-ui/lib/umd/locale/kz.js": "3113fafaec1e5f4ae75275df004a438f", "static/js/element-ui/lib/umd/locale/lt.js": "1f9ab75d239d73dd6450d20719d14e4c", "static/js/element-ui/lib/umd/locale/lv.js": "06ed0fdc7d15aaaa254ddafd5cc2d984", "static/js/element-ui/lib/umd/locale/mn.js": "a30050261567e96fcae3eb8184077738", "static/js/element-ui/lib/umd/locale/nb-NO.js": "8f696776c06ee3e12e43a4cdd6cce736", "static/js/element-ui/lib/umd/locale/nl.js": "99258a51fa6d3bb2eaf24687b08681b6", "static/js/element-ui/lib/umd/locale/pl.js": "727bf2dd0d8c2917c7d7b67651fa4e2c", "static/js/element-ui/lib/umd/locale/pt-br.js": "9b175360cb8d7199055506a7f57e7d4b", "static/js/element-ui/lib/umd/locale/pt.js": "1626947603ba9812628e5388526e02e7", "static/js/element-ui/lib/umd/locale/ro.js": "fb9eef3d28ce9ecc7ad83b41caf17864", "static/js/element-ui/lib/umd/locale/ru-RU.js": "02776c80fbb8f65298ef9156d79d11ee", "static/js/element-ui/lib/umd/locale/sk.js": "34457b2d2b6c75a8025a0eba36931523", "static/js/element-ui/lib/umd/locale/sl.js": "2d186805c45ce5292f9dfa2f2255677f", "static/js/element-ui/lib/umd/locale/sr.js": "57009e2dc4bc4de61794b29c659a132d", "static/js/element-ui/lib/umd/locale/sv-SE.js": "315c4a945b2b89e206ade38990a7e9f9", "static/js/element-ui/lib/umd/locale/ta.js": "da94b97bf773e49867bdd4c91b738cdf", "static/js/element-ui/lib/umd/locale/th.js": "76dde843e7f874f2e45cc1ea1dadc638", "static/js/element-ui/lib/umd/locale/tk.js": "710dfdc6ba97ebc5b6ed5e5c6ba40c79", "static/js/element-ui/lib/umd/locale/tr-TR.js": "2af8060b6746a342827a94ee1d45972c", "static/js/element-ui/lib/umd/locale/ua.js": "c2240b43e3d70e4b2c25db2580d5b9ad", "static/js/element-ui/lib/umd/locale/ug-CN.js": "6ddaea800aed2638e2d02b06322ceb07", "static/js/element-ui/lib/umd/locale/uz-UZ.js": "ddb5d919cc3756722a2980f995086225", "static/js/element-ui/lib/umd/locale/vi.js": "764578d2497752bec40d8cf3982501d5", "static/js/element-ui/lib/umd/locale/zh-CN.js": "af0b3424682dedebc3064a17c8c8f047", "static/js/element-ui/lib/umd/locale/zh-TW.js": "ee9c9d202116bd367a54b6542b41e9ac", "static/js/element-ui/lib/utils/after-leave.js": "5b6eab2e19137f22b7f155c64c127b5a", "static/js/element-ui/lib/utils/aria-dialog.js": "584e07ba3bcd14fe02d84d456461a381", "static/js/element-ui/lib/utils/aria-utils.js": "bccd0c0998fa688607a28aef5d3954be", "static/js/element-ui/lib/utils/clickoutside.js": "1d2c86f338286924e65ba0beb1e9275b", "static/js/element-ui/lib/utils/date-util.js": "f7bc86e6068c4c4f23c86f7d6fafce77", "static/js/element-ui/lib/utils/date.js": "63bca9e7033fcf66fa9b93ccaf4dad0f", "static/js/element-ui/lib/utils/dom.js": "7a5bf584e8d01360f759c1a78305219c", "static/js/element-ui/lib/utils/merge.js": "48d430909e3583f3010bf4e844bd1163", "static/js/element-ui/lib/utils/popper.js": "f90f278e90fdff8f4afd382145b2db9b", "static/js/element-ui/lib/utils/resize-event.js": "96628f5f98b18fee3c4972e8f39b6481", "static/js/element-ui/lib/utils/scroll-into-view.js": "a2847b7f5d7ae6860f66669f645ce861", "static/js/element-ui/lib/utils/scrollbar-width.js": "6dd9aafa50bf2028e1fa542feb995f89", "static/js/element-ui/lib/utils/shared.js": "e9becb8ca1ad71a5d64d989e28778b11", "static/js/element-ui/lib/utils/types.js": "b76cda09d6b97c9598681b372f3efd6e", "static/js/element-ui/lib/utils/util.js": "c4013004a996dd7d030f779c721c7c2d", "static/js/element-ui/lib/utils/vdom.js": "a72f60b0134bd47f49c537ad28982bad", "static/js/element-ui/lib/utils/vue-popper.js": "ea42e858259fa582acfadc15265b1d30", "static/js/element-ui/lib/utils/menu/aria-menubar.js": "172afd68add2537d32f299e5369c8ad8", "static/js/element-ui/lib/utils/menu/aria-menuitem.js": "2c51a053252827845cce692634443771", "static/js/element-ui/lib/utils/menu/aria-submenu.js": "2d5f39c9bc644388bc57563ee8ecae87", "static/js/element-ui/lib/utils/popup/index.js": "8df7b5c153e9187359c20ab602ad6740", "static/js/element-ui/lib/utils/popup/popup-manager.js": "68856d04bb10de1e999fc10693a73999", "tests/README.md": "bad9b5c1d18429a006532bb7faebd117", "tests/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/assets/\u5168\u90e8\u6d4b\u8bd5.png": "dbed30a78bcec42c7523471fcf514700", "tests/assets/\u6253\u5f00pycharm\u7684pytest\u529f\u80fd.png": "b849c96411e8d82fc8a50f0ff816c1f7", "tests/assets/\u6267\u884c\u6d4b\u8bd5\u7528\u4f8b.png": "39116cca185c73ae3d4517ee0f84133a", "tests/assets/\u6d4b\u8bd5\u5931\u8d25.png": "d04228741f3da0b6e977211fe41c6438", "tests/assets/\u6d4b\u8bd5\u6210\u529f.png": "6669c0c45442db35ab92b06bf9993c06", "tests/test_algorithms/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_algorithms/test_linear_algebra/test_linear_space.py": "6df796d85cb787fd7f045a0ab5c63a1c", "tests/test_algorithms/test_linear_algebra/test_matrix_cross.py": "4b574f0a6131847d802761bd83a6d6ae", "tests/test_algorithms/test_linear_algebra/test_matrix_determinant.py": "041bf9f620c2dcb093bf20cf4ed20c72", "tests/test_algorithms/test_linear_algebra/test_matrix_diagonal.py": "b8afb32ca59752683d9c605a9885e2f5", "tests/test_algorithms/test_linear_algebra/test_matrix_divide.py": "2b63a05c372c145b05920fe5c8e7ee1d", "tests/test_algorithms/test_linear_algebra/test_matrix_dot.py": "3f80ff34c4b639fc33344ce8c23ab153", "tests/test_algorithms/test_linear_algebra/test_matrix_eigenvalue.py": "0edcf7066a5e3143d089a0222d8659be", "tests/test_algorithms/test_linear_algebra/test_matrix_inverse.py": "8f502aabb41e4faaae06663c45fe52a9", "tests/test_algorithms/test_linear_algebra/test_matrix_multiply.py": "45423c811a4db180b269945908595383", "tests/test_algorithms/test_linear_algebra/test_matrix_transpose.py": "d92dc7600a77f090f5f897dff9d099e4", "tests/test_algorithms/test_linear_algebra/test_ones.py": "faa005c5fce0def31dcc4aa83861f4de", "tests/test_algorithms/test_linear_algebra/test_reshape.py": "ff0f15e2ba03d7bd3ee086deab31f9d0", "tests/test_algorithms/test_linear_algebra/test_shape.py": "29a411d526e34d9070155e1a198195db", "tests/test_algorithms/test_linear_algebra/test_zeros.py": "5c8802a5d2bd87dc00e67e7f2f892c9e", "tests/test_algorithms/test_linear_algebra/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_algorithms/test_statistics/run_distribution.py": "fd81a148edd8cb40ed4390dde9c9af9d", "tests/test_algorithms/test_statistics/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_data_adapter/run_dump_load.py": "4b7572b7ce9b3c6ce0ed6d6811b64e6b", "tests/test_data_adapter/test_array.py": "717162f0f2da526e3a18456b12dfbd10", "tests/test_data_adapter/test_data_frame.py": "e56b02672c2d979b495c24fdc113eafc", "tests/test_data_adapter/test_detector.py": "92495e21a9b05068d73833d4d6a8f3f9", "tests/test_data_adapter/test_universal.py": "f69c1055e69f2e844596a3b80ab20c88", "tests/test_data_adapter/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_dev/__init__.py": "4277383d96a0b390d7299069af45c987", "tests/test_dev/test_doc/test_file_tree_node.py": "362ef3dedb9be7eca57248be44f93e3f", "tests/test_dev/test_doc/__init__.py": "d2b42ea8d4d60b51c772335633e18078", "tests/test_dev/test_doc/test_case_for_file_tree_node/main.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_dev/test_doc/test_case_for_file_tree_node/README.md": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_dev/test_doc/test_case_for_file_tree_node/_exclude.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_dev/test_doc/test_case_for_file_tree_node/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_dev/test_doc/test_case_for_file_tree_node/sub_dir/core1.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_dev/test_doc/test_case_for_file_tree_node/sub_dir/test1.txt": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_dev/test_doc/test_case_for_file_tree_node/sub_dir/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_dev/test_doc/test_case_for_file_tree_node/sub_dir2/test2.txt": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_dev/test_doc/test_case_for_file_tree_node/sub_dir2/__init__.pyw": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_dev/test_doc/test_case_for_file_tree_node/sub_dir3/core3.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_dev/test_doc/test_case_for_file_tree_node/sub_dir3/test3.txt": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_pmgwidgets/test_maccabe/mccaberun.py": "17cdb5bf5d1157aeba779172c294ad95", "tests/test_pmgwidgets/test_maccabe/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_pmgwidgets/test_normal/test_display.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_pmgwidgets/test_util/test_util.py": "8db14adf951ddecf73952d6841b2aa93", "tests/test_pmtoolbox/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_ui/test_ipython_support.py": "4e109800eedb8113b7da674b05e08bac", "tests/test_ui/test_pmgpanel.py": "a8d7ede78b58ae7be4f738f322f9be98", "tests/test_workspace2/test_data_manager.py": "bcf209b27357532443d75b3c368e68c7", "tests/test_workspace2/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "utils/environ.py": "b8bfeb3f35922c0337c197abea87829f", "utils/http_client.py": "3a5ced47c6f09803ad68b4da5b59d7f1", "utils/path.py": "93a5404fabe530d18791aaafecfd1b40", "utils/platform.py": "2da872dbe9abece68ed95e204c2ff655", "utils/__init__.py": "7e6452c9cf8ce5c4c856dcbdd3cf6b2f", "utils/debug/debuggerprocess.py": "0fe81aa6f61d81df42ef9afcc2d25bd5", "utils/debug/pdbtest.py": "b79fc6b32634f06788247e8d58402d41", "utils/debug/pmdebug.py": "d384421931a8e48e26cab0fba3c4241c", "utils/debug/test2.py": "b53f37a4994356be41f962afcb0a0b10", "utils/debug/__init__.py": "350446184463764465845852bd3ef50e", "utils/doc/file_tree.py": "36bd540c3b4c32b184dee9f2182f0819", "utils/doc/index.rst": "2488cd21dbd21544665f802ab3f0045e", "utils/doc/rst_generator.py": "fca7802a37b44b4a62924aa7521d6a2a", "utils/doc/__init__.py": "892fd1aca5b1cae0f580cd809094dad2", "utils/doc/doc_guide/choose_position.md": "482843264c94f2bd50c448fec43a3cc3", "utils/doc/doc_guide/compile.md": "eeee41c8eefce5234f176c76a0b15ee9", "utils/doc/doc_guide/index.rst": "0f1e7af0e52ee3073edf77f0eb9b0de6", "utils/doc/doc_guide/md_guide.rst": "809611fdb43d6b05edfadf5cc3a8c7c7", "utils/doc/doc_guide/rst_guide.rst": "8fa7462d9a5b56d6c5a67646e2ba4acc", "utils/doc/doc_guide/use_chevereto.rst": "3cb77997069a4c98b34360c9490f5733", "utils/doc/template/module.rst_t": "3af408a56d3d4adb3acfe09f653128ac", "utils/doc/template/package.rst_t": "801c621e304bc973e95f2e2519de04f8", "utils/doc/template/toc.rst_t": "a7cf368655e8fd91249cee24406aaff8", "utils/doc_figures/\u5e15\u7d2f\u6258\u56fe.jpg": "17c5690d782d1554a0eee747b496dcac", "utils/doc_figures/\u5e94\u7528\u5de5\u5177\u680f.png": "52d6b3bbabc43c2d368ce7748e8a9c7d", "utils/doc_figures/\u5f02\u5e38\u503c\u68c0\u6d4b": "49f442e7d72e05764e8353e36a824637", "utils/doc_figures/\u6807\u51c6\u79d1\u5b66\u8ba1\u7b97app\u754c\u9762\u539f\u578b.png": "5a6aa5719ae6d2ab0a3e2deeb4d9f841", "utils/doc_figures/\u9879\u76ee\u7ec4\u6210\u7ed3\u6784.png": "7d7896300a5e62d2fad7062bcba88aee", "utils/io/file_import.py": "d41d8cd98f00b204e9800998ecf8427e", "utils/io/piputil.py": "d45b0ab10e658c8bd1a260e7a7b0daca", "utils/io/__init__.py": "e9846ca05e2cf2393e2d36e130f7e84f", "utils/io/dbconnect/dbBaseTool.py": "964e1d0151fac429242dfdd163d6693b", "utils/io/dbconnect/dbConnectAccount.pkl": "410722595449e489f364425f6cd8c240", "utils/io/dbconnect/dbutils.py": "a83eac2b48d43e4bbb144b6ee30ac5a6", "utils/io/dbconnect/test_dbBaseTool_add_connection.py": "9777701d02960adae45e4ebc5afdabbc", "utils/io/dbconnect/test_dbBaseTool_query.py": "637037577573d38544ed3a801e4a8238", "utils/io/dbconnect/__init__.py": "5111d7ba85f86397e7318a88362fd502", "utils/io/fileutil/compressutils.py": "d79d280d2f6ddc66de6687a7b8201338", "utils/io/fileutil/encoding.py": "f70ddeb46e587c343134428e3a1d9b6c", "utils/io/fileutil/search_in_path.py": "3a369957683bec55df7e6e63cb3ce499", "utils/io/fileutil/variableutils.py": "e980e969d1f33da86e71e1207f282fe5", "utils/io/fileutil/__init__.py": "86e37c6a62dd940b6add84e2f3464697", "utils/io/fileutil/source/encoding/test_ascii.csv": "930b9d198c8fe4cf62cb40bf86deb059", "utils/io/fileutil/source/encoding/test_gb2312.csv": "b78fff031a5e71e40eaa348a628dd45e", "utils/io/fileutil/source/encoding/test_gbk.csv": "b78fff031a5e71e40eaa348a628dd45e", "utils/io/fileutil/source/encoding/test_utf8.csv": "b3c2e6ec634b81b2ebd348a3983fc165", "utils/io/fileutil/test/test_word_in_line.py": "0e2699f99428b73b86fdeb3fca2ee3b3", "utils/io/pmserial/pmqtreadserial.py": "7f31cf4aa590690a8c9e53b4d3599ce8", "utils/io/pmserial/readserial.py": "5cafed26a707eecdac54883dca6d2c33", "utils/io/pmserial/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "utils/settings/settings.py": "a65d80cc4a777b526b057b8efebd6989", "utils/settings/__init__.py": "43d16a290ee34cb378a6c6244e8c0a32", "utils/ui/translation.py": "a75ca908fa328d348adc9106554e35d7", "utils/ui/variableselect.py": "6623e3057f2291f6ad61ee1e55ac58c4", "utils/ui/__init__.py": "05099d0c6cd09966cb7621c9e57c2f13", "utils/ui/app/pmbasicapp.py": "a8cf494179389639ee3a68e39167cdc1", "utils/ui/app/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "utils/ui/app/test/basicapp_abnormal.py": "74d0fab0335ac9847dd589275e304c64", "utils/ui/app/test/basicapp_pareto.py": "522a51d208d3d32c68e9de0267a9f1fb", "utils/ui/src/balance.png": "0e13e9c8fd6853eedef19ac2ca0d9f01", "utils/ui/src/chemestry.png": "be3234787dcd5ad6b8c03cce7d142dfd", "utils/ui/src/electricity.png": "390b497d0687540630694599edc71dac", "utils/ui/src/engineering machinery.png": "099cdd52b97926617032ff66487cc311", "utils/ui/src/fracture.png": "e2dcccc7d7751d10b0e439a9a8b8f7d3", "utils/ui/src/function.png": "0a6dd73740c6800c913a5dc8556539cb", "utils/ui/src/gauge.png": "167e1ea4af8c9e504c30e734e7761488", "utils/ui/src/histogram.png": "231deddcfe9222512173e3d0b7829728", "utils/ui/src/ic_common_\u751f\u7269&\u81ea\u7136.png": "351343f4046d6d2c7a3405065d3a66a2", "utils/ui/src/loadwave.png": "95280a0b2dbc61a6c817d40e705f0379", "utils/ui/src/log.png": "117ef9da5ed765f2cc31f65bd2a49db9", "utils/ui/src/math_1.png": "a62c3b980b0f9332c56452c2dfac5a8c", "utils/ui/src/math_2.png": "d98240ad143bbc7cc739cac16b18e60a", "utils/ui/src/math_3.png": "0dbbea9ccc5e7cfa409a65e7ab0d16c4", "utils/ui/src/mechanics.png": "e730af4dc4083d52675826590d05dfa8", "utils/ui/src/money.png": "078a6fec99543566f7abd7f9d235cfc1", "utils/ui/src/motor.png": "cb1071e863f9d4108d509a1f0a29ce8e", "utils/ui/src/motor_2.png": "b71bdb729289a54a9330af929f477b39", "utils/ui/src/normal_distribution.png": "18785959972c6076e3201e41ba68d971", "utils/ui/src/physics.png": "67a6b64cee517d8678e2001ebd2af37d", "utils/ui/src/physics_2.png": "75796555027a4edc1bf441a69800de78", "utils/ui/src/plot_1.png": "484e1553dbd4d3b51dc986af7da9126d", "utils/ui/src/plot_2.png": "fa13d212836a556060df8fbee4dd8593", "utils/ui/src/plot_3.png": "bf548bcb5eba63b655fcaafa9af1e50e", "utils/ui/src/plot_4.png": "63658bb0aa5e10850d4cbcefb31bc5ec", "utils/ui/src/roboarm.png": "adc612a5fbe26cbc1ef9964078be30d0", "utils/ui/src/run.png": "fcf7e23f264801b79df841dabdab9417", "utils/ui/src/run_cell.png": "e40eb239205653b61fa2ba5a57f8d378", "utils/ui/src/settings_1.png": "4dc1f5c80740868b1cf0a4f50909dc20", "utils/ui/src/settings_2.png": "e8cd30a0264a6ee00fbe461e72a9fb6e", "utils/ui/src/settings_3.png": "2917d7c1b222d2e235a85f7e677935ca", "utils/ui/src/sinsidual.png": "371e0fdb35b52764d7d9c17774c36a07", "utils/ui/src/statistics.png": "3e1dd67456f93098bc0276c3b37e1b45", "utils/ui/src/statistics_2.png": "524756b6420d9a60cec2212e23112dbc", "utils/ui/src/statistics_3.png": "737c1756516487220aafa992210374bb", "utils/ui/src/viberation.png": "49ccb24861c08a92834e814bf8ea4767", "utils/ui/src/wave.png": "402b0e16aaf792596c734ee454ba25dd", "utils/ui/src/wave_2.png": "ce59ead6a036cca68b02950c9bdcb91a", "utils/ui/src/wave_history.png": "566e2a0ac92bf12f6291e0dd4d272091", "utils/ui/src/\u5206\u5b50\u751f\u7269\u5b66\u5e73\u53f0-\u7070.png": "6a5127ef0f94e54506455e0dd5d8d712", "utils/ui/src/\u751f\u547d\u5065\u5eb7\u4ea7\u4e1a_2\u751f\u7269\u533b\u836f.png": "c6f652bcc942e7c3c244d06726fcf063", "utils/ui/src/\u751f\u7269 (1).png": "9f4b502be694761d44c106a09a61be19", "utils/ui/src/\u751f\u7269 (2).png": "58da036ee4e5ead077590ebb65b80460", "utils/ui/src/\u751f\u7269 (3).png": "9cf816bfb76439a5f366e9beca9bea92", "utils/ui/src/\u751f\u7269 (4).png": "3b699f6ca234d635a8bc3274a7b44020", "utils/ui/src/\u751f\u7269.png": "4bcc3068ba68359848f293c23ac0b670", "utils/ui/src/\u751f\u7269\u8bc6\u522b (1).png": "021f770979cb264bc827ac041bab972b", "utils/ui/src/\u751f\u7269\u8bc6\u522b.png": "d73c19821dbdc9b46facd404afda33f4", "utils/ui/uiutil/datashowutil.py": "a88e7ef43145082c8278f6273e1bfb05", "utils/ui/uiutil/workspaceutil.py": "4a74ba5a7a176257d0e659d4be892301", "utils/ui/uiutil/__init__.py": "d611c6031ffff2a4570872c525a95660", "utils/ui/uiutil/formatting/textformat.py": "dee7f3523b301ceb8bdb0b35da44e456", "utils/ui/uiutil/formatting/__init__.py": "73a717186f47f07e16e0dcb8ed4ed76d", "widgets/frame_less_window.py": "e2a0208d327013c21bd75c71535d05af"}}
\ No newline at end of file
+{"files": {"app2.py": "c7e061a1c028e828cb2bd76c46a7a858", "check_dependency.py": "35b3112cf9cb50d31be5f45c8ae3d179", "config.ini": "7f83e4b78df1a293343baeaaf5881203", "dist.py": "e3b17ce9bea81da8f9b4582ee78fe6cd", "LICENSE": "b5b6bed06dd8ed68f00c26d0b4cede89", "load_modules.py": "6bfcd0281e7ab654b27851cc25c2a31f", "pmgui.py": "54472e38004442ee8209f732e013e09a", "pyminer.pro": "148c684e721b44a4a29ec4c700e95c49", "pyminer.py": "8676961ecd2c4d293db44b2e8e962315", "pytest.ini": "7e1ac696b90974c21fcaed50d881ebf1", "README.md": "618338896f514c4f446bd0142edce65a", "requirements.txt": "bdc7ed15fe5b96b65882b02a3ce91877", "requirements_dev.txt": "6e23539b6bc3cd698e6f9713e598e2a9", "requirements_linux.txt": "32193efcf05f692095c24da7263ea8de", "requirements_mac.txt": "a1fdae24ed40656c9879626a8130c59c", "run_before_commit.py": "067f08926721cb4679e63604fa3528c4", "update_translation.py": "afcbf3f7c930b65e44c1bed2430d4284", "\u5f00\u53d1\u6307\u5357.md": "791025c5ef81f9b6a334e4f6d39e9cb6", "\u5f00\u53d1\u8fdb\u5ea6.url": "e47baa8f175a5766ae709b9e8d951b53", "configuration/config.ini": "56859fc0e799ae26d49fd7a41cfe0532", "configuration/default_settings.json": "7bdc7e60ac0fe14d849afb297fb9db2f", "configuration/extensions.json": "cb8f6f2bf8cdccae988f3cb589282f7c", "configuration/settings.json": "81f0b5a2f4f719f23e247b92ee72fff7", "core/__init__.py": "f0d32e8fca7a2398789eb9b0537057ce", "core/algorithms/index.rst": "493e85a5e6744431a7755a7d33f9979d", "core/algorithms/__init__.py": "489b6a395b7e79f2cab1f8c46eec73e1", "core/algorithms/calculation/digits.py": "b14e19d1913de492b116201b5f5c6c27", "core/algorithms/calculation/__init__.py": "6fb7087712399aff01a8a820cd77a474", "core/algorithms/linear_algebra/array.py": "f1e96ef9af8aaf15aaef29c863b3663c", "core/algorithms/linear_algebra/exceptions.py": "cc1a682728cd86a67aaf4b03a27c3e8a", "core/algorithms/linear_algebra/linear_space.py": "0d12ac5250106fc5fd0270b502ceb6e1", "core/algorithms/linear_algebra/matrix_cross.py": "ba1e06a57db80bcaa9509d6e733c1df9", "core/algorithms/linear_algebra/matrix_determinant.py": "23a47100204fd2f73f43cc201113f5e5", "core/algorithms/linear_algebra/matrix_diagonal.py": "ab7c7e101f46ebe7ed4bc73f542f111a", "core/algorithms/linear_algebra/matrix_divide.py": "01ec01fbb97bca37f22f719f3dc3d089", "core/algorithms/linear_algebra/matrix_dot.py": "67d721785420cd3f3344ac69bdb6ac32", "core/algorithms/linear_algebra/matrix_eigenvalue.py": "43b83077241aa32acdff04c8489363f5", "core/algorithms/linear_algebra/matrix_inverse.py": "f4c6ec6129a07ea821aff5b6b84f2aa6", "core/algorithms/linear_algebra/matrix_multiply.py": "067a30b30ff9be6d9b9123a75a30abc9", "core/algorithms/linear_algebra/matrix_transpose.py": "d63b39c00f437fab1540f331876444ab", "core/algorithms/linear_algebra/ones.py": "f7c3f42cde513ec5e91bc24dbc068d62", "core/algorithms/linear_algebra/reshape.py": "54f0d073af724f1c3c474e59940c6f72", "core/algorithms/linear_algebra/shape.py": "863cf7a043ab1d82f02a3a1279632b57", "core/algorithms/linear_algebra/zeros.py": "161e8f970d39d7bb2135852051a18005", "core/algorithms/linear_algebra/_utils.py": "8864ac424626bc7804d31b3e0ef3905f", "core/algorithms/linear_algebra/__init__.py": "a1ff4909241d1462727c33e5a8038cbd", "core/algorithms/linear_algebra/\u5f00\u53d1\u6d41\u7a0b.md": "54daed08712378a9ad4208884b65088b", "core/algorithms/linear_algebra/assets/code_hint.png": "013aecfff792430d0d3fac227bfcaaf4", "core/algorithms/linear_algebra/assets/configure_test_utils.png": "952e07a65e50b2fbd9666f8544ac20fa", "core/algorithms/linear_algebra/assets/define_function_framework.png": "4c921fffe0c7b10e5fb01e02d8f7a353", "core/algorithms/linear_algebra/assets/demand_change_file_change.png": "537d6591e1833d43c9fd6fc30cd1b2f6", "core/algorithms/linear_algebra/assets/finish.png": "d4c5fb995d723b15072691707b0ed72e", "core/algorithms/linear_algebra/assets/fix_testcase.png": "b1f4c682efcfb3f23e32ab085cc85b50", "core/algorithms/linear_algebra/assets/function_explanation.png": "c7bc75487b2abe6a536fc91a880d5f44", "core/algorithms/linear_algebra/assets/function_explanation_file_change": "15ade2607376365bb31c38a79d46ea89", "core/algorithms/linear_algebra/assets/function_file_change.png": "5b961672e09adba22805a5098e1bbbd1", "core/algorithms/linear_algebra/assets/function_workspace.png": "2bf5321cda6e0e1ac939895233e7a833", "core/algorithms/linear_algebra/assets/help_doc.png": "0e2f87b116e1fcb517cc63d5aa8e3d21", "core/algorithms/linear_algebra/assets/import_in_global.png": "259b30531b0ab509d4b93acec5cf3db1", "core/algorithms/linear_algebra/assets/import_in_sub_pkg.png": "622f44c6ec4738254d8585ae341b945e", "core/algorithms/linear_algebra/assets/run_in_pm.png": "4a64482cdd2125276501a4190848352c", "core/algorithms/linear_algebra/assets/run_test.png": "0d9abff3ca8a85fa0c3f58aef6ffd2d3", "core/algorithms/linear_algebra/assets/testcase_file_change.png": "caeda394883222a82b6dabd224fe0f0c", "core/algorithms/linear_algebra/assets/test_error.png": "a6b5a1a95cdc1fc21ce522f35da111aa", "core/algorithms/linear_algebra/assets/test_pass.png": "7792d7ee583bdb629ea41cec3a5cbf07", "core/algorithms/linear_algebra/assets/write_doc.png": "5349a5f4df8ed7a982ac5c86298ce715", "core/algorithms/linear_algebra/assets/write_doc_file_change.png": "34753c6a77cc470710669f518cfb3055", "core/algorithms/linear_algebra/assets/write_function_file_change.png": "2f5d964072df62b5a915fc14a41597b9", "core/algorithms/plotting/graph.py": "1728aea4907b92b67fe91ad742a3083f", "core/algorithms/plotting/graph_configs.py": "614cbeae8b696da2e8fe983e3c87a5f9", "core/algorithms/plotting/__init__.py": "9f90fe07592b24c4a09ed9230b6c093d", "core/algorithms/pyminer_util/communication.py": "216e0f4b1c8b00d535f0585c7fa1add3", "core/algorithms/pyminer_util/__init__.py": "de143e26c42af15d96c77f52383561a0", "core/algorithms/statistics/__init__.py": "4bba02c1df562ce45ccf5dfc988ab576", "core/evaluation/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "core/evaluation/woe/config.py": "9ac12638758e61c547224a0b4cc5e5f8", "core/evaluation/woe/eval.py": "62ab9836e64b475b07e0ea5cccf3aeb0", "core/evaluation/woe/feature_process.py": "e12d77b446b6a75746499d909ca22ae8", "core/evaluation/woe/ftrl.py": "1e6dad2dfc172413104ef5b3fb25e88d", "core/evaluation/woe/GridSearch.py": "8b92ecf1226fa81972510b17aa4cc2c4", "core/evaluation/woe/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "core/evaluation/woe/tests/config.csv": "66f330fdc285911a52c4a65166eab04f", "core/evaluation/woe/tests/HereWeGo.py": "94d48377d8292b80d8f4929cf9162004", "core/evaluation/woe/tests/README.rst": "8f72b661abc5eaf059d05e603b1c705c", "core/evaluation/woe/tests/UCI_Credit_Card.csv": "afd3af2602d66d6ceb36cb04216f0ed2", "core/io/postgresql/psycopg2/compat.py": "7abef6e4534c7b625057e37289bc8c27", "core/io/postgresql/psycopg2/errorcodes.py": "b2346a81ec49de54caa4e32a5360076e", "core/io/postgresql/psycopg2/errors.py": "316dfc64e89c95715e974a31f96e18fe", "core/io/postgresql/psycopg2/extensions.py": "d7fc21cf847f2d12a60f56097e3eca2f", "core/io/postgresql/psycopg2/extras.py": "4d79c51ba46dc3285d9faf8789a37369", "core/io/postgresql/psycopg2/pool.py": "1b0403b2597557108c4c35a9bc568834", "core/io/postgresql/psycopg2/sql.py": "1b8ad7b5746ad2f514cbdd678954fee2", "core/io/postgresql/psycopg2/tz.py": "b70b2abdc56dc05e9c28993934bed8f4", "core/io/postgresql/psycopg2/_ipaddress.py": "e0ff64e2ae604224c8cd11d1bdb27003", "core/io/postgresql/psycopg2/_json.py": "38e03cf8ae626f8fa028ceb48dcdb960", "core/io/postgresql/psycopg2/_lru_cache.py": "5e2bc12517950812ccac51af1d61d8a5", "core/io/postgresql/psycopg2/_range.py": "46ff7ab96f96d7db81eee160f666cfa4", "core/io/postgresql/psycopg2/__init__.py": "36fa5f3355c7d7ea36b87d1cdda6afe4", "core/modelling/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "core/modelling/regression/base_regression.py": "461cde16f7769d88c386f6f142ec233a", "core/modelling/regression/knn.py": "9f0c137ed701a2d11325ae1c9da23caa", "core/modelling/regression/linear.py": "a7936db33b1faa26cdbdc53a4bb81dfc", "core/modelling/regression/linear_bfgs.py": "031491ee5d158dcc8d506458fd10dbdf", "core/modelling/regression/mean.py": "c494d914a7be950f81b073dcad1e8bdf", "core/modelling/regression/neural_network.py": "571fa4e8bdc730c503cda08b0afd1d41", "core/modelling/regression/random_forest.py": "33c3aa9f1300b805ef13dd96866ea114", "core/modelling/regression/simple_random_forest.py": "e8c180090ce051d2f57e94b98e30b3c3", "core/modelling/regression/svm.py": "f5547c9da4c71d06a1df162be1e51885", "core/modelling/regression/tree.py": "0bd5b05a252b233597b6ff7453881ebd", "core/modelling/regression/__init__.py": "5b1908d95bc0ac027b22c337ea12f21b", "core/preprocess/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "docs/make.bat": "0d5c4de56de1ea8fa10468d561de4d77", "docs/Makefile": "45ecc4dd568420521f285e291939ecf1", "docs/make_doc.py": "3710c825012f1dc2e8d4f5854622e186", "docs/PyMiner\u4ecb\u7ecd.md": "56012d9e37961de92ea165d227e8707b", "docs/THANKS": "ad97f1302bcf00e26a219a0801a0b34d", "docs/upload.py": "10534ab4011a82607ef2b0c2f6c1b599", "docs/source/conf.py": "86928f87e5b0a03cbdd58f6f8a87f55b", "docs/source/contents.rst": "cd9756a400d0aa68ef85e92394c58b25", "docs/source/contribute/index.md": "260363f4717c8db812dc5d9c2c5723f8", "docs/source/_static/css/pyminer.css": "4a98b74c6e7893114cabc72ef5c6b81d", "docs/source/_static/css/pyminer.css.map": "e82da77c9ca623f0acbf4cbc62d78cbd", "docs/source/_static/css/pyminer.sass": "e5940574b2a7af7e3b7c01c2fc600354", "docs/source/_static/css/README.txt": "6e9ef1827e9eaea7ca3831685d830c7d", "docs/source/_static/index_files/all.css": "242611f34a440c48c2e6405e91e01a71", "docs/source/_static/index_files/all.js(1).download": "d7b3138b22aac6df42d86c92e36763ba", "docs/source/_static/index_files/all.js(2).download": "83006561af55b7a96dd7e17d34ebfe8a", "docs/source/_static/index_files/all.js.download": "39bebcf34d45ccab4d08bfb31b684294", "docs/source/_static/index_files/check_data.png": "e44471c2514f8a048df2a89a9df2e795", "docs/source/_static/index_files/code.png": "814df667bb94b7d2ae844d938ec916b7", "docs/source/_static/index_files/font-awesome.min.css": "ea6cc550de5339fc787f1e041363e544", "docs/source/_static/index_files/ico_mailme_01.png": "c40acf63e04064714b98a30a92984868", "docs/source/_static/index_files/main.png": "1b6f8a5331f0c5954a86b9024f070568", "docs/source/_static/index_files/modernizr.js.download": "4fae2a90728c528aa148c31466b7ed39", "docs/source/_static/index_files/normalize.css": "ed3146b9b1ec5eecb132a21916d0afe5", "docs/source/_static/index_files/robot.svg": "ba2b8a892fc5457a02829ba0b2caf3da", "docs/source/_templates/index.html": "66315ce66e29f04ce356a9b8b0d41ac8", "examples/HereWeGo.py": "d94883e4b2545ccfec29c1e76aebb982", "examples/README.md": "d49b56a1fe9a7ef2530943a5848fac88", "examples/README_ZH.md": "ac49a6e35c2518cac510ee6920c840da", "examples/UCI_Credit_Card.csv": "afd3af2602d66d6ceb36cb04216f0ed2", "examples/woe_rule.pkl": "cc2695e5f41ac2679a2040e15ff9463e", "examples/datasets/air_quality_no2.csv": "c27e6437bf25787c881d0d5f57e10233", "examples/datasets/bankloan.sav": "b5e78f9be79d7ba44c4fe5ac69cddc02", "examples/datasets/boston_house_prices.csv": "6454dba73c425c66b50d5206ed45f1a0", "examples/datasets/brown-selected.tab": "9e43112b53c6f6c78fa0b2645ace506d", "examples/datasets/brown-selected.tab.metadata": "337569fc2132e37108b428b9b082cfe9", "examples/datasets/cars.sas7bdat": "073898a0487cde27abd7155461c564be", "examples/datasets/car_sales.sav": "7b2487624eb4ffb893812706d30b0542", "examples/datasets/ccpp.csv": "20372480cb1e06f4cb99e46a0bd759a4", "examples/datasets/class.sas7bdat": "6e69aeb1f12de8d3fc46a64cbd4dc786", "examples/datasets/conferences.dst": "78dc681ceec57ee82dbf1951f11fa758", "examples/datasets/datasets.info": "19934e0d167a3128a09c493b9aeb031e", "examples/datasets/diabetes.csv": "8523f75b2f041c08542910b7ea3a8e84", "examples/datasets/heart_disease.tab": "859a303f42efd8b2a1bd0f2e33fedf71", "examples/datasets/heart_disease.tab.metadata": "ed45f5da717ba279094a29f33a4b263d", "examples/datasets/housing.tab": "e0eda02af8c85dd28b21620ee5c74552", "examples/datasets/housing.tab.metadata": "9d88fcbc612a9928d951ceb5f22b04c9", "examples/datasets/iris.csv": "ffd137d9c66a717d061b9fa5831000b1", "examples/datasets/iris.tab": "bbf17c5ae7e81aeb2ad7694648a6b2e1", "examples/datasets/iris.tab.metadata": "e1d89c7e7561bcc716dafd546058196e", "examples/datasets/list_update.py": "737978a6df832d796431df3b048b051a", "examples/datasets/mushrooms.csv": "2007f683881ecd4febc1b7674c5751a8", "examples/datasets/slovenian-towns.dst": "e6c7acf1cb81c6d71523d0e7b3ea6f41", "examples/datasets/titanic.tab": "4291853e1bf953ef2c0c1e39653a0f80", "examples/datasets/titanic.tab.metadata": "5074c723c0342fa64396739eb0480d33", "examples/datasets/TitanicData.xls": "7afb9b02b50c902fd364364f7d2e49be", "examples/datasets/UCI_Credit_Card.csv": "afd3af2602d66d6ceb36cb04216f0ed2", "examples/datasets/zoo.tab": "90a455135a6a8822f1200630270bdcb0", "examples/datasets/zoo.tab.metadata": "1b824e81f9e8969228e01e460d6b05e6", "features/feedback.py": "2fed1192b08f982e19b6cb75f40c7f10", "features/index.rst": "caf9be56f3fc9b08f569abee024f6cf2", "features/openprocess.py": "539c43bc37743144d9216715b8fad57f", "features/README.md": "eed382b7fe1775f4b6b875d4b564ae50", "features/settings.py": "a7c36fc21234c3a23c7663e1b248e60b", "features/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/auth/authlocalserver.py": "1a7bd0455dc04627bdb5367c7ebc9c2a", "features/auth/authmanager.py": "06a45267063200c67104786db5cff4e1", "features/auth/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/auth/templates/login.html": "446aac7443cc70f43e46753092445aef", "features/auth/templates/register.html": "3a424e8c3bdf1eb6043e0ab9ca34acff", "features/extensions/index.rst": "01d20711a991f116e372db6fb72c96df", "features/extensions/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/extensions/extensionlib/baseext.py": "677942d6d94defa454ec27b173d582db", "features/extensions/extensionlib/extensionlib.md": "aacae615d8f781c275ac79c22fae5678", "features/extensions/extensionlib/extension_lib.py": "e48b16cc626c5001355438e28f00d219", "features/extensions/extensionlib/readme.md": "00629cf6b22e95200c530ea6d512057f", "features/extensions/extensionlib/__init__.py": "2dc050a0d3da896f4b39681a6e057f63", "features/extensions/extensions_manager/ExtensionLoader.py": "aee9c0b155f0bade604e41d3a38efa36", "features/extensions/extensions_manager/log.py": "6e8bd61219f2ff606b58c1399d322ddf", "features/extensions/extensions_manager/manager.py": "a7fa22dd5ba1fc531f31b0741f12c61e", "features/extensions/extensions_manager/readme.drawio": "4bafe50e30ca4be5cdef34e1e657f7a6", "features/extensions/extensions_manager/README.md": "97b9464ed10af4ad3015ac7f38e6cf99", "features/extensions/extensions_manager/UIInserter.py": "e66518eebc812a4f5b94997351fe1b0b", "features/extensions/extensions_manager/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/extensions/extensions_manager/vermanager/vermanager.py": "01c76efe94e29a53138a670f909f2d52", "features/extensions/extensions_manager/vermanager/__init__.py": "03f044152b579a37841af43eae7f2389", "features/interpretermanager/interpretermanager.py": "f444a2a4cebc7ae225cf46f61ae7950b", "features/interpretermanager/packagemanager.py": "573cf34280aff9d22775f692e63cbb8c", "features/interpretermanager/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/io/database.py": "567325cf72a6f4564a4a0f27326f1b25", "features/io/dbConn.py": "50780c5805a79cb1934b2d7e0d8c0ab6", "features/io/dbConnectAccount.pkl": "5d9291410939747f2c8c07dd81222283", "features/io/encoding.py": "3d17abe6b8042115f6e15b869aff7c44", "features/io/exceptions.py": "72dcca0f3371e119d43e9827e73528b0", "features/io/settings.py": "a3234ca4ddc2f8b0e0e62450756c1e56", "features/io/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/main_window/base.py": "aab682344cfc58317a1413203ebd6261", "features/pluginsmanager/pluginsmanager.py": "f7dc55bb96470571a9a6558cb3962591", "features/pluginsmanager/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/project/template/Basic-Template.py": "4204e7ce0f6309fd93535659c3c466c8", "features/project/template/Empty-Template.py": "18dc747535104b7c6ef67b21c97069f1", "features/project/template/Plot-Template.py": "b688a56819d86c6eb44ff9e72b809091", "features/project/template/PyQt-Template.py": "8a64552bb7be154721ceeaf50f4e8673", "features/project/template/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/project/template/PySide2Template/main.py": "ed912fbf0fc0c20fcacb81bd19800c14", "features/project/template/PySide2Template/PySide2_Template.py": "ac93a3b7fc512f9c0afa7f00a2643674", "features/project/template/PySide2Template/PySide2_Template.ui": "c8cd27a3e0b5cb1c9936603328d7b732", "features/project/template/PySide2Template/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/ui/main.py": "2d7d5d9496b9208ddb27e60f708a1a4d", "features/ui/ui_aboutme.py": "c79955815de4013a7a64d690f94febbc", "features/ui/ui_aboutme.ui": "160718ccbd19dc35a00aaeee6faffaa2", "features/ui/ui_appstore.py": "256f2fd75ad7ffdf667c13bf7a3b5128", "features/ui/ui_appstore.ui": "a7e7317eca4ee6311e41968445e1fa81", "features/ui/ui_check_update.py": "8226a48eb517b9da8cf425b809ff3c8a", "features/ui/ui_check_update.ui": "0766a087aa5b8e4a5e46f4211055aa9b", "features/ui/ui_data_normal.py": "d9971d97184ade3f9c405f061dd22231", "features/ui/ui_data_normal.ui": "c8a3e10c4487ce5a2774ca8ea5a5905a", "features/ui/ui_first_form.py": "cbbf26db8887e60df0dd7ce0713a5a54", "features/ui/ui_first_form.ui": "1b5d9231837a421c9b77382b3f6f1719", "features/ui/ui_login.py": "2fe8d95e4257473abea1549e127f7ede", "features/ui/ui_login.ui": "58916dfb5dd1dfab103fa4f15882a282", "features/ui/ui_logined.py": "668bf0e57ec258d1ee0b7bfac2abe1b5", "features/ui/ui_logined.ui": "e6d6f9d467a6b8a24336713481e9a6f6", "features/ui/ui_option.py": "12491a3f32f99d3bb7833291c5402cfa", "features/ui/ui_option.ui": "9cf7b928ccf449e096586e949cd7dd95", "features/ui/ui_preferences.py": "d925283bad5dc99d85a90798cb86c6ce", "features/ui/ui_preferences.ui": "4336a5c78eef921b047e1c86c49650f5", "features/ui/ui_project_wizard.py": "65f0c2afe19b349e8b63b57a941c4db1", "features/ui/ui_project_wizard.ui": "9d21051c8df87eb832bdf8a5470f1c8c", "features/ui/ui_workspace_launcher.py": "594f60adb389355ef83a5bca20eafb5a", "features/ui/ui_workspace_launcher.ui": "fd4e48c43a6d35a209c2696942f0927f", "features/ui/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/ui/common/debug_process_with_pyqt.py": "2971e07b890f1872c7f7009c4d2b44fe", "features/ui/common/openprocess.py": "3d1aeb10bde66329d7126840466ba6df", "features/ui/common/open_process_with_pyqt.py": "851480f8440b2f81033a319b5f32f465", "features/ui/common/platformutil.py": "070ffc4eaea4cb0c4b6dcca39fe4035f", "features/ui/common/pmlocale.py": "f7ca747cc3dedc2b6cf03c7b5f9ec121", "features/ui/common/test_open_app.py": "5f56a2266c07dd652659c949b297f498", "features/ui/common/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/ui/pmwidgets/dockwidget.py": "cb0c4fe5642dda21f5692ecce49eed95", "features/ui/pmwidgets/pmmainwindow.py": "5f3887a06e377de92251c6b49853cddb", "features/ui/pmwidgets/toplevel.py": "8700605abc51c422a5a1588dc80a8bfd", "features/ui/pmwidgets/__init__.py": "dbd5c209ff332a55a7e483fcb686421f", "features/ui/pm_marketplace/install.py": "54117accb9d426c89b751355b7e73f0b", "features/ui/pm_marketplace/install.ui": "22c1cb1d2e58f0e6e63d3c199af96edd", "features/ui/pm_marketplace/main.py": "1fea599f650efa60951f3029d940d73e", "features/ui/pm_marketplace/main.ui": "76b3d012f1742eeb8cb85a0ee9ec5680", "features/ui/pm_marketplace/package_manager_main.py": "0ab9760f90d31f8f0eb3287bdf530891", "features/ui/pm_marketplace/package_manager_main.ui": "8dba9def55b1ea06d6c953f33e875cb9", "features/ui/pm_marketplace/uninstall.py": "292fa58f6d05fc584029fb0650702aa3", "features/ui/pm_marketplace/uninstall.ui": "c1160fc571c23573e4bfb4ada3807edc", "features/ui/pm_marketplace/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/ui/widgets/controlpanel.py": "d5fceeb26a8878ff80d6339895dbb5cd", "features/ui/widgets/notificationwidget.py": "3c6d71d757c8a0e3a11b9e48ca7108eb", "features/ui/widgets/README.md": "9f7d88859d291d2f64cba1b6ba49a59e", "features/ui/widgets/reportwidget.py": "1f00f377fe6cfc610cf58e68462307e6", "features/ui/widgets/resources.py": "14a758e941b459f8b23704065f1e8639", "features/ui/widgets/__init__.py": "77b4a6a8852c7103e7525d3955c40358", "features/util/check_update_ui.py": "5bfc0584c45856ace75efedafd677436", "features/util/check_update_ui.ui": "0766a087aa5b8e4a5e46f4211055aa9b", "features/util/make_update.py": "17f81b28bdc7a353dac1d7b45ed762cf", "features/util/openprocess.py": "539c43bc37743144d9216715b8fad57f", "features/util/update.py": "896e76ccbae9ae36c17d14515b6fafa5", "features/util/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/workspace/data_manager.py": "d7b5e29419b744fad86e6245018de1e8", "features/workspace/index.rst": "3ce5d474607ba614ba9f6afc772e7bc2", "features/workspace/signals.py": "d950f3f33fd821323d95ece2b8d90e84", "features/workspace/signals.rst": "3618e25aa7f77f05142dc84a96eaf45c", "features/workspace/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/workspace/blinker/base.py": "83733f47cb03be3713ce7a5860612e96", "features/workspace/blinker/_saferef.py": "ced62e1fda983045da2076064a7db34d", "features/workspace/blinker/_utilities.py": "1aeb68e85d8ad0aa80295e5f29f42d18", "features/workspace/blinker/__init__.py": "464a73bb96572aaf6c2ed191676d40a4", "features/workspace/data/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/workspace/data_adapter/array.py": "2891617cd8d0eb1cd3780258212ce847", "features/workspace/data_adapter/base.py": "8d176a5909da92fbbfdade7ecd040127", "features/workspace/data_adapter/data_frame.py": "d71dc9d36194663b154bbb13eb1bf8a9", "features/workspace/data_adapter/detector.py": "af748d211285fe07fa56bf2b21e4d60e", "features/workspace/data_adapter/index.rst": "1d16f21a65dadb51dc1ecc68d9cad119", "features/workspace/data_adapter/universal.py": "49603cc94bc554bdb8456a1cdbdd39b1", "features/workspace/data_adapter/__init__.py": "b9da2c81695eae92f9748a4a59cd0c29", "features/workspace_old/history.md": "ab52fc7f26dff2cecf2f2f704844db56", "features/workspace_old/index.rst": "961579c17854f0d0d7b9f4e0bb6b1621", "features/workspace_old/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "features/workspace_old/datamanager/converter.py": "0cddb9d818f283a125bf534c3633f2b7", "features/workspace_old/datamanager/datamanager.py": "a2025c72d71aaf79728c54bfaf9dc3d3", "features/workspace_old/datamanager/dataset.py": "5d0b8678f8a53db8d8c9a483b90c79aa", "features/workspace_old/datamanager/exceptions.py": "6ed4da7a9003f12c756366cf67d87027", "features/workspace_old/datamanager/historyset.py": "85f91eac3f692ce44ed011647d319bac", "features/workspace_old/datamanager/metadataset.py": "9047c690af46c4db5b0501fdc25f975b", "features/workspace_old/datamanager/recyclebin.py": "c9effef9419e3be130a027f116761881", "features/workspace_old/datamanager/variable.py": "d35eb9e5fea49eaf5899d7315caa582a", "features/workspace_old/datamanager/varset.py": "54e77b59c43212ef22b4e271b8893953", "features/workspace_old/datamanager/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "languages/en.ts": "64307a7a6a8a2bc627ecbfaf2fc0e805", "languages/en/en.ts": "a426bc922ab99d1920e790b890d1b386", "languages/zh_CN/widgets_qt_zh_CN.qm": "cbaaec13dd22418095f6c7a9122926cc", "languages/zh_CN/widgets_qt_zh_CN.ts": "d81ecd8ec48ae725c76eba4f09ee9ecb", "languages/zh_CN/pmtoolbox_qt_zh_CN.ts": "95510ea97a650faeba03d155b1c81cbf", "languages/zh_CN/qt_zh_CN.ts": "bf9e10698c9cef267c344f73e30789e6", "languages/zh_CN/zh_CN.qm": "941aa53373af233466a16d682d7e625a", "languages/zh_CN/zh_CN.ts": "1be0a3147fea71527d4e17065a66d482", "languages/zh_CN/zh_CN.ts.bak": "c916a039d8a676ad891150786e9b1185", "languages/zh_TW/zh_TW.ts": "a426bc922ab99d1920e790b890d1b386", "packages/index.rst": "60e962032576844618f7a1448880b8fd", "packages/__init__.py": "a248423783ba8e03c638b996a91de109", "packages/advanced_drawings_toolbar/group_chart.py": "846a72c9e0303f8ef4b5c6b809f07db7", "packages/advanced_drawings_toolbar/ipython_console.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/advanced_drawings_toolbar/main.py": "78b220f88ba07d759c9f0722f6fc0306", "packages/advanced_drawings_toolbar/map_var.json": "91324fe1e6b292ebc2d548f3ae7d49b7", "packages/advanced_drawings_toolbar/map_var.py": "46e4e76e96cb5691754e4b63a38b67d9", "packages/advanced_drawings_toolbar/package.json": "3483b83687dfaf5bb4410107b04fdfd0", "packages/advanced_drawings_toolbar/radar_chart.py": "67ea13759ea77d67006df488c9fde4e0", "packages/advanced_drawings_toolbar/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/advanced_drawings_toolbar/pmmap/china/all/01-beijing.npy": "05fc6e35605340ef6e4d62af9770ef7a", "packages/advanced_drawings_toolbar/pmmap/china/all/02-shanghai.npy": "dbd37714da88edf44df7a2642ce2c14f", "packages/advanced_drawings_toolbar/pmmap/china/all/03-tianjin.npy": "e767b1d0810acfd174d4b52586441de1", "packages/advanced_drawings_toolbar/pmmap/china/all/04-chongqing.npy": "1980444de2496af7ae7b11cae9372d2c", "packages/advanced_drawings_toolbar/pmmap/china/all/05-heilongjiang.npy": "f369a349db31ac0636e625737802922e", "packages/advanced_drawings_toolbar/pmmap/china/all/06-neimeng.npy": "0366d9f95bfe2e0768a6491f8f5e27cc", "packages/advanced_drawings_toolbar/pmmap/china/all/07-xinjiang.npy": "201b3ff97375ae9ba6e9fc9c7b0eed1e", "packages/advanced_drawings_toolbar/pmmap/china/all/08-jilin.npy": "0ed85b6d84b877cf07154e1b54db0bdd", "packages/advanced_drawings_toolbar/pmmap/china/all/09-gansu.npy": "08c6e4437a0fc5d19d6c655a14a7df2e", "packages/advanced_drawings_toolbar/pmmap/china/all/10-liaoning.npy": "542d598f5c39b363738b0b753d8318d1", "packages/advanced_drawings_toolbar/pmmap/china/all/11-hebei.npy": "5a7ce525859caabaffad0fe1c11cb26f", "packages/advanced_drawings_toolbar/pmmap/china/all/12-shanxi.npy": "5ebb5347431393d599f5f0d36a31dd07", "packages/advanced_drawings_toolbar/pmmap/china/all/13-shan3xi.npy": "71944b8eea6caa0b7e4138063d6e8945", "packages/advanced_drawings_toolbar/pmmap/china/all/14-ningxia.npy": "dc0660ac4216cb4e928fb08dba5e36fb", "packages/advanced_drawings_toolbar/pmmap/china/all/15-qinghai.npy": "46f4e9bf9a47734d0cfbef0916e6cec1", "packages/advanced_drawings_toolbar/pmmap/china/all/16-shandong.npy": "b3eee97751e252f1c729b20cde81220c", "packages/advanced_drawings_toolbar/pmmap/china/all/17-henan.npy": "530d73e54901bf816e61c86bafb97219", "packages/advanced_drawings_toolbar/pmmap/china/all/18-xizang.npy": "83c873032100d735d2a3c1becb4e4ee5", "packages/advanced_drawings_toolbar/pmmap/china/all/19-jiangsu.npy": "33051765300de0a9d003ea91c3846cc6", "packages/advanced_drawings_toolbar/pmmap/china/all/20-anhui.npy": "d80f40e27c3a60188547d058c827eae0", "packages/advanced_drawings_toolbar/pmmap/china/all/21-sichuan.npy": "115e4b1abbc6df1f862095e94cacb8b5", "packages/advanced_drawings_toolbar/pmmap/china/all/22-hubei.npy": "065b61ce8a2728be70d0e726990e42c2", "packages/advanced_drawings_toolbar/pmmap/china/all/23-zhejiang.npy": "6910df476893b4e5a84689094203b724", "packages/advanced_drawings_toolbar/pmmap/china/all/24-jiangxi.npy": "2d4e166cd8d9371a1c6d01848ece3301", "packages/advanced_drawings_toolbar/pmmap/china/all/25-hunan.npy": "ddbce0334a1f79bd77f7b0ae582dd28e", "packages/advanced_drawings_toolbar/pmmap/china/all/26-guizhou.npy": "4d23f4f330203b61c6f8cca0ac764819", "packages/advanced_drawings_toolbar/pmmap/china/all/27-yunnan.npy": "1d1c9378c8427667dbf0b8948c36b484", "packages/advanced_drawings_toolbar/pmmap/china/all/28-fujian.npy": "77270ff92f0d67f0123672527b70772a", "packages/advanced_drawings_toolbar/pmmap/china/all/29-guangxi.npy": "3862d0becc054fe754ae71c111c583ad", "packages/advanced_drawings_toolbar/pmmap/china/all/30-guangdong.npy": "7d7f1392cc01a22d4503354ce7c729b1", "packages/advanced_drawings_toolbar/pmmap/china/all/31-taiwan.npy": "2f398da02605db2be30751dedb92ea90", "packages/advanced_drawings_toolbar/pmmap/china/all/32-xianggang.npy": "b3cf9d637c7d87e82a86174f99be74da", "packages/advanced_drawings_toolbar/pmmap/china/all/33-aomen.npy": "68e3c682aef3e25cf873878a19df5361", "packages/advanced_drawings_toolbar/pmmap/china/all/34-hainan.npy": "a67e27cc4f68d566f6977580d95b006c", "packages/advanced_drawings_toolbar/pmmap/china/all/39-jiuduan.npy": "35fbdcbb72d2c22caab9b1bfd2eba401", "packages/advanced_drawings_toolbar/pmmap/china/small/30-guangdong.npy": "7d7f1392cc01a22d4503354ce7c729b1", "packages/advanced_drawings_toolbar/pmmap/china/small/31-taiwan.npy": "2f398da02605db2be30751dedb92ea90", "packages/advanced_drawings_toolbar/pmmap/china/small/32-xianggang.npy": "b3cf9d637c7d87e82a86174f99be74da", "packages/advanced_drawings_toolbar/pmmap/china/small/33-aomen.npy": "68e3c682aef3e25cf873878a19df5361", "packages/advanced_drawings_toolbar/pmmap/china/small/34-hainan.npy": "a67e27cc4f68d566f6977580d95b006c", "packages/advanced_drawings_toolbar/pmmap/china/small/39-jiuduan.npy": "35fbdcbb72d2c22caab9b1bfd2eba401", "packages/advanced_drawings_toolbar/source/down.svg": "fe6b296aa4020dda5c5a870bb7596235", "packages/advanced_drawings_toolbar/source/plot.svg": "05fd271af6c134f6872c80c489064d60", "packages/advanced_drawings_toolbar/source/\u5730\u56fe.png": "eca50b3207a973738145d1cf12ca0c34", "packages/advanced_drawings_toolbar/source/\u6298\u7ebf\u56fe.png": "1bde0eca8cfa880452accf56ca925866", "packages/advanced_drawings_toolbar/source/\u6563\u70b9\u56fe.png": "78c432bb278d8ea5c9587f23ffdd5f82", "packages/advanced_drawings_toolbar/source/\u6761\u5f62\u56fe.png": "b07f4db5b1785376f97636fc6963234e", "packages/advanced_drawings_toolbar/source/\u67f1\u5f62\u56fe.png": "d6cba37e00bf9bbeb65235897a7b90b9", "packages/advanced_drawings_toolbar/source/\u6c14\u6ce1\u56fe.png": "d6784d4c53145cce1fde17f673b76984", "packages/advanced_drawings_toolbar/source/\u70ed\u529b\u56fe.png": "566d7c6ad396084ca6b7d490868fdc2c", "packages/advanced_drawings_toolbar/source/\u76f4\u65b9\u56fe.png": "a9b01bca53610b2c03f7ea2331c91cc3", "packages/advanced_drawings_toolbar/source/\u7bb1\u7ebf\u56fe.png": "ba733c4c1a0979ab5bc838149490e915", "packages/advanced_drawings_toolbar/source/\u7ec4\u5408\u56fe.png": "c5bea73cff095ad47bd07e1e81921ab2", "packages/advanced_drawings_toolbar/source/\u96f7\u8fbe\u56fe.png": "5aa1a48424b30a157a2bca181c9be2d0", "packages/advanced_drawings_toolbar/source/\u9762\u79ef\u56fe.png": "69682cc944d7afdd5c1f2019bf6ca21c", "packages/advanced_drawings_toolbar/source/\u997c\u56fe.png": "862b6c81a5752ee7d53500ce9064c10d", "packages/advanced_drawings_toolbar/translations/qt_zh_CN.qm": "cd23a7e488d169790b204929188d78e7", "packages/advanced_drawings_toolbar/translations/qt_zh_CN.ts": "ce76ad850f047e2cea0440e4dab7c7d4", "packages/applications_toolbar/applications_toolbar.py": "1c7db3beb99333a43fcbc29f2b628117", "packages/applications_toolbar/dev_tools.py": "32396eb367e2f40a2ca41bbf1c122380", "packages/applications_toolbar/ipyinterface.py": "b52686fd36bd819d65916f6c70a135e9", "packages/applications_toolbar/ipython_console.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/applications_toolbar/main.py": "1d6fa534839be7a73ed71881a9fb166e", "packages/applications_toolbar/manage_apps.py": "88d58bae208874c4973e22165f112675", "packages/applications_toolbar/package.json": "b6e6fae7a7650f3a94327d27f4fd4f9c", "packages/applications_toolbar/process_monitor.py": "86f69fb10c7ef7ba19504b418e8851f5", "packages/applications_toolbar/README.md": "500119ec92d71af8ac97e7246e30342e", "packages/applications_toolbar/settings_apps.json": "4182a1dc3f6489bca92a7eb123a85b6c", "packages/applications_toolbar/apps/cftool/algorithm.py": "253d3d8c4d7fcebe0018d65511e03ae9", "packages/applications_toolbar/apps/cftool/GUI_QT.py": "81a8a4dbbb41799c196129cb7b98247c", "packages/applications_toolbar/apps/cftool/main.py": "386bb5ebf2b5d8ad609ecd2be95bb607", "packages/applications_toolbar/apps/cftool/package.json": "ef90cc084c17151e6db5f1a4bb170897", "packages/applications_toolbar/apps/cftool/python.jpg": "81740ef62fda1acb2905ad8dedb96385", "packages/applications_toolbar/apps/cftool/README.md": "82a32691c1f8296f6fe73faebc1ddf89", "packages/applications_toolbar/apps/cftool/regexvalidifyer.py": "f7b28de6b75b89f19078a3e2e10d9818", "packages/applications_toolbar/apps/cftool/test1.py": "d8b7b73613a84a9044721403a1e8042a", "packages/applications_toolbar/apps/cftool/test_file.py": "dc6b5c9b40404d62209e36daf7b4819e", "packages/applications_toolbar/apps/cftool/src/cftool.png": "04975fa7979a56ac4dabea313cc7c004", "packages/applications_toolbar/apps/cftool/src/\u66f2\u7ebf\u62df\u5408.png": "d7ccf65e6d59dfd63507f1432454a943", "packages/applications_toolbar/apps/demo_app/default.png": "b58b2daf9a2a44726b678f235aed41e6", "packages/applications_toolbar/apps/demo_app/demo_app.py": "931bb2658ec5554a4f1cbb7424736f5a", "packages/applications_toolbar/apps/demo_app/main.py": "63ffc303fd56ba7d882fe108cd44efeb", "packages/applications_toolbar/apps/demo_app/package.json": "24bd9031b9afde16470332c19333c0bb", "packages/applications_toolbar/apps/demo_app/README.md": "ddb8b30770b3972dee69051c99026d1f", "packages/applications_toolbar/apps/demo_app/settings.json": "e4385e0a6554dfe40b3696f328f5edd1", "packages/applications_toolbar/apps/flowchart/flowchart.png": "e419f05974065c85c4c7b56604ce920a", "packages/applications_toolbar/apps/flowchart/main.py": "3288fef7caa83b16745351634f1c5e13", "packages/applications_toolbar/apps/flowchart/package.json": "bfe315ff8f31d2c4699a560608a5b973", "packages/applications_toolbar/apps/flowchart/python.jpg": "81740ef62fda1acb2905ad8dedb96385", "packages/applications_toolbar/apps/flowchart/README.md": "9b53d15d3e33ddba31de6b5f4e684c1b", "packages/applications_toolbar/apps/flowchart/examples/drop_duplicated.pmfc": "be5c44289f838b15d93c1b227b657091", "packages/applications_toolbar/apps/flowchart/examples/read_all_csv_files.pmfc": "4b1e9884b118c9dfa1e855f55178fad5", "packages/applications_toolbar/apps/flowchart/plugin_nodes/nodes.py": "92702a3afc444e8ce17b53314722eff6", "packages/applications_toolbar/apps/flowchart/plugin_nodes/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/applications_toolbar/apps/flowchart/resources/flowchartwindow.jpg": "d91093a77c5746982f7575081674e05b", "packages/applications_toolbar/apps/flowchart/resources/nodeports.jpg": "fddd899d2618662bb2b61d7e09680ad2", "packages/applications_toolbar/apps/flowchart/test_files/test_drop_duplicated.csv": "0574869eff0b86a260260b72cf53bb4a", "packages/applications_toolbar/apps/ligralpy/chart_server.py": "b3dc9a23925b30a73ac974f7a8123689", "packages/applications_toolbar/apps/ligralpy/Data.csv": "5fdba74629c5a0b1a5a6be20a3a19688", "packages/applications_toolbar/apps/ligralpy/flowchart.png": "e419f05974065c85c4c7b56604ce920a", "packages/applications_toolbar/apps/ligralpy/img.png": "8408ad879fe242ed82c30adbc3694d46", "packages/applications_toolbar/apps/ligralpy/main.py": "59a370745781b14b673f52ef392a5108", "packages/applications_toolbar/apps/ligralpy/package.json": "9e7eeab6e86f652142ae125bec65e71d", "packages/applications_toolbar/apps/ligralpy/python.jpg": "81740ef62fda1acb2905ad8dedb96385", "packages/applications_toolbar/apps/ligralpy/README.md": "9b53d15d3e33ddba31de6b5f4e684c1b", "packages/applications_toolbar/apps/ligralpy/examples/flowchart_stat_demo.json": "2d91e5c4fff5494f14dbb88a8b5f5031", "packages/applications_toolbar/apps/ligralpy/examples/second_ordered.lig.json": "86a67bc8b6402059ff6f15ef83722705", "packages/applications_toolbar/apps/ligralpy/nodes/nodes.py": "a08108573e2ace0e04dc0551da7653a6", "packages/applications_toolbar/apps/ligralpy/nodes/simulation.py": "043e5c86cf3c147d23cda2a2cf589395", "packages/applications_toolbar/apps/ligralpy/nodes/__init__.py": "06133052eded73b0d4be7b4719d5a3fc", "packages/applications_toolbar/apps/ligralpy/nodes/tools/gen_classes.py": "976b6416996c3bd2ce0c811b68175d60", "packages/applications_toolbar/apps/ligralpy/resources/flowchartwindow.jpg": "d91093a77c5746982f7575081674e05b", "packages/applications_toolbar/apps/ligralpy/resources/nodeports.jpg": "fddd899d2618662bb2b61d7e09680ad2", "packages/applications_toolbar/source/appstore.svg": "e2a48d1643f244e957ce9dba9e55a72f", "packages/applications_toolbar/source/app_main.py": "d8797ef1c2c1506b14b24a3602d4f929", "packages/applications_toolbar/source/background.png": "2a3f6737ddbb2a0314cdf2f640b0e929", "packages/applications_toolbar/source/default.png": "b58b2daf9a2a44726b678f235aed41e6", "packages/applications_toolbar/source/down.svg": "fe6b296aa4020dda5c5a870bb7596235", "packages/applications_toolbar/source/install.svg": "699200cfd59967ff740f1e3076795f0f", "packages/applications_toolbar/source/lightening.png": "844cc073cae34c193f3f39335146c955", "packages/applications_toolbar/source/main.py": "f7c06af5e03add541ee260a6224af05a", "packages/applications_toolbar/source/package.svg": "db8542fb863117d5d74f7a6676ab419e", "packages/applications_toolbar/source/qt-logo.png": "9a2446007bbc869ee1ef00479fc73872", "packages/applications_toolbar/source/run.png": "fcf7e23f264801b79df841dabdab9417", "packages/applications_toolbar/source/run.py": "931bb2658ec5554a4f1cbb7424736f5a", "packages/applications_toolbar/source/settings.png": "b9f8edfca0a36df8f3d445f61683511d", "packages/applications_toolbar/translations/qt_zh_CN.qm": "e613b5296764ff24e04450243de3e383", "packages/applications_toolbar/translations/qt_zh_CN.ts": "242fdca10f7405da10eae48f2244e13f", "packages/applications_toolbar/ui/app_designer.py": "b90e5fb6a0430ceb7bb74533ca4d198a", "packages/applications_toolbar/ui/app_designer.ui": "363e995f6861e7817a27e577d236233c", "packages/code_editor/debugger.py": "a19f564dddb9df36cfd14779dfea943c", "packages/code_editor/main.py": "0b3fc0dfb3c49a9dbe74a02f881b36a7", "packages/code_editor/package.json": "f529dee72a413f5d0d92b62ffeaad59b", "packages/code_editor/python.jpg": "81740ef62fda1acb2905ad8dedb96385", "packages/code_editor/README.md": "2918b5e59be436e278047961adb8afeb", "packages/code_editor/toolbar.py": "26ea5eb990d08893ee848dd86768aaba", "packages/code_editor/codeeditor/abstracteditor.py": "b1556e7c0b64c122e8dc6b295b9d9cc9", "packages/code_editor/codeeditor/autocomplete.py": "85443f0a05636004d73019442ca2d628", "packages/code_editor/codeeditor/flake8_trans.json": "745c056024e394cd874a60a0569de1b3", "packages/code_editor/codeeditor/infer.py": "7a9c311fd78ce3afe093bced75ff8fb0", "packages/code_editor/codeeditor/markdowneditor.py": "710b73d04dbf40dbda4c8a9516f1cdb9", "packages/code_editor/codeeditor/pythoneditor.py": "6eb09191f31b0d9a3752e13e3cef6098", "packages/code_editor/codeeditor/syntaxana.py": "b41485288a49c5e9dff3babfdbaa3a3b", "packages/code_editor/codeeditor/tabwidget.py": "e8c6177923b12a377116f65e2045fac3", "packages/code_editor/codeeditor/__init__.py": "de948222f3aba39724a6583a7c1d818f", "packages/code_editor/codeeditor/config/.flake8": "5706c8ed878411a63a6dd9827224e010", "packages/code_editor/codeeditor/config/.style.yapf": "0d898fa79efa8db79c72c151658d26aa", "packages/code_editor/codeeditor/config/.style.yapf.zh": "3f98dd47e54c916665839ab5fe9c5b8c", "packages/code_editor/codeeditor/errors_translation/translate.py": "750d35adf1c925d4e54c5afc6bd4471b", "packages/code_editor/codeeditor/errors_translation/translations.txt": "ec6b11469cf13cc49f53f144a3048952", "packages/code_editor/codeeditor/qtpyeditor/find_gotoline.py": "98a1a7fd1e0b447a55ce0941984ef809", "packages/code_editor/codeeditor/qtpyeditor/linenumber.py": "0dbc3249db663a0d338ec4630a8b857a", "packages/code_editor/codeeditor/qtpyeditor/syntaxana.py": "ccfed05306fc8e31513e4138fc6cb197", "packages/code_editor/codeeditor/qtpyeditor/__init__.py": "750c4f8fd3d0708b965ba5b6349201df", "packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py": "1f47f04069d87c1e49eadcac64e0c546", "packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py": "1bd34b84907bd2cb9602b28ff87aae23", "packages/code_editor/codeeditor/qtpyeditor/codeedit/__init__.py": "49be4addcaeff7654aa0d9770649c359", "packages/code_editor/codeeditor/qtpyeditor/codeeditor/abstracteditor.py": "ae34418002a69d7872496eb5007842c0", "packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py": "81adc6aff2de98db56eb88c22fbea69f", "packages/code_editor/codeeditor/qtpyeditor/codeeditor/pythoneditor.py": "5458fc8ab2d05929fa3a9b6790125554", "packages/code_editor/codeeditor/qtpyeditor/codeeditor/__init__.py": "2d1f567a03ae3b14864c5807c2ecfd90", "packages/code_editor/codeeditor/qtpyeditor/highlighters/python.py": "30f29dea3c0261d114038d049dcb8b9b", "packages/code_editor/codeeditor/qtpyeditor/highlighters/__init__.py": "274aab5f8155ea49df8cc33689fe427a", "packages/code_editor/codeeditor/qtpyeditor/icons/breakpoint.svg": "5225dadbedc9f7bda4bb94fecc75ad53", "packages/code_editor/codeeditor/qtpyeditor/icons/copy.svg": "1c800d6346207003b4d7dc70a11e433f", "packages/code_editor/codeeditor/qtpyeditor/icons/debug.svg": "b931caeb19e4cc175cec44fd8af719f5", "packages/code_editor/codeeditor/qtpyeditor/icons/format.svg": "dccd4600d9450bfe4deb9b244c41e85f", "packages/code_editor/codeeditor/qtpyeditor/icons/help.svg": "edfdb212fdcae3ece7e7c58950a35645", "packages/code_editor/codeeditor/qtpyeditor/icons/python.svg": "d17b75c2256afc9b2beb64afeafbc3e0", "packages/code_editor/codeeditor/qtpyeditor/icons/run.svg": "e00087c6bd323d47d4f70a32a25b5667", "packages/code_editor/codeeditor/qtpyeditor/icons/save.svg": "750627b83e976365fb2d80529cb84a2d", "packages/code_editor/codeeditor/qtpyeditor/icons/spate.svg": "13ab9e2e9d07edf5f7c9cd73560141a7", "packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/class.png": "c64837c614c385630f360831492d8eb9", "packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/function.png": "7ee29ba9761700d9c9312f95ab6c799e", "packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/instance.png": "7ccdeda9723de6cbf234c333abe1d244", "packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/keyword.png": "babe443bc1400af18df2d5a6cc11cec4", "packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/module.png": "86eaece57974e3d10eb6e175d650abf6", "packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/param.png": "74e736a865157eb31e015effb34df17b", "packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/path.png": "7e01e98822f89b5e7bb64c2ead585aa0", "packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/property.png": "3f6aebb5a4862a8adca6d82dc724ab5e", "packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/statement.png": "d16ab05b46ff0f6ed3b6421a2a66dab0", "packages/code_editor/codeeditor/qtpyeditor/translations/qt_zh_CN.qm": "2c896338c3b1b75508a842cf534d7cbf", "packages/code_editor/codeeditor/qtpyeditor/translations/qt_zh_CN.ts": "5382de30cc1e09a23252d66ca93b27db", "packages/code_editor/codeeditor/qtpyeditor/ui/findinpath.py": "14fa54fbcf8cb34804f6ef075c4267e6", "packages/code_editor/codeeditor/qtpyeditor/ui/formeditor.py": "1da95795442e26ab7bb522dc811aae50", "packages/code_editor/codeeditor/qtpyeditor/ui/gotoline.py": "609b6fb7d45a04b898878cab26938277", "packages/code_editor/codeeditor/qtpyeditor/ui/ui_formeditor.py": "2d559bafc1b0d8819e62eea2f875de8d", "packages/code_editor/codeeditor/qtpyeditor/ui/ui_gotoline.py": "1827eee50b32b313d397027c46dc020b", "packages/code_editor/codeeditor/qtpyeditor/ui/__init__.py": "de948222f3aba39724a6583a7c1d818f", "packages/code_editor/codeeditor/qtpyeditor/Utilities/autocomp.py": "7adde398458535177d586db7da6eb151", "packages/code_editor/codeeditor/qtpyeditor/Utilities/__init__.py": "d7a23951afba6034e252ea600119c242", "packages/code_editor/codeeditor/simpleeditor/lexer": "d41d8cd98f00b204e9800998ecf8427e", "packages/code_editor/codeeditor/simpleeditor/__init__.py": "fbc8e0505acbb7662554aa0ebf010dd8", "packages/code_editor/codeeditor/tests/get_flake8_output.py": "d6a5545ac0b49483aed41b5059276d1e", "packages/code_editor/codeeditor/tests/get_yapf_output.py": "f6a13da53a21f87d7f5d9fbd0e9a22fa", "packages/code_editor/codeeditor/tests/test_file.py": "4ed78c29dfa08ff34bff564bd39545c4", "packages/code_editor/codeeditor/tests/theme_xml_json.py": "86f8b53c91b4d974acafc78c7a9e399c", "packages/code_editor/codeeditor/themes/Material-Dark.xml": "32f09404512dea8a892ef0095e659ae3", "packages/code_editor/codeeditor/themes/Obsidian PyCs.xml": "934cfd0236acebfaa37bc76516e4cc12", "packages/code_editor/codeeditor/themes/tomorrow.xml": "4f226a7b073ea417d6eca69bdf0e999a", "packages/code_editor/codeeditor/themes/tomorrow_night.xml": "5b7770abad7ab117727e91351140724f", "packages/code_editor/codeeditor/themes/tomorrow_night_bright.xml": "ba3f13a083f3b0aa8f8332e90b4fbb86", "packages/code_editor/codeeditor/tools/eric6_api.py": "72d2e6ee453be1f17e6c0382eb99d344", "packages/code_editor/codeeditor/tools/__init__.py": "9aa13d467aa7ca6ca94b283dea7e5c22", "packages/code_editor/codeeditor/tools/DocumentationTools/APIGenerator.py": "a725a1b2ffcbc8fc98f9e69b252f73ae", "packages/code_editor/codeeditor/tools/DocumentationTools/__init__.py": "f5dba5378fa551d11f098318de65a4a1", "packages/code_editor/codeeditor/tools/QScintilla/Editor.py": "3e5f9b7d8c00734ed27c85bd1de63906", "packages/code_editor/codeeditor/tools/QScintilla/__init__.py": "e58d1155053d6b3763c2ed9f92ca9e57", "packages/code_editor/codeeditor/tools/Utilities/ModuleParser.py": "be326e07e15841196ec7d752417cc6e2", "packages/code_editor/codeeditor/tools/Utilities/__init__.py": "65ffafe4bc9cd6a17832f441036cdb63", "packages/code_editor/codeeditor/ui/formeditor.py": "1da95795442e26ab7bb522dc811aae50", "packages/code_editor/codeeditor/ui/gotoline.py": "2ea843566488cd151da2f6cab335625f", "packages/code_editor/icons/breakpoint.svg": "5225dadbedc9f7bda4bb94fecc75ad53", "packages/code_editor/icons/copy.svg": "1c800d6346207003b4d7dc70a11e433f", "packages/code_editor/icons/debug.svg": "b931caeb19e4cc175cec44fd8af719f5", "packages/code_editor/icons/format.svg": "dccd4600d9450bfe4deb9b244c41e85f", "packages/code_editor/icons/help.svg": "edfdb212fdcae3ece7e7c58950a35645", "packages/code_editor/icons/python.svg": "d17b75c2256afc9b2beb64afeafbc3e0", "packages/code_editor/icons/run.svg": "e00087c6bd323d47d4f70a32a25b5667", "packages/code_editor/icons/save.svg": "750627b83e976365fb2d80529cb84a2d", "packages/code_editor/icons/spate.svg": "13ab9e2e9d07edf5f7c9cd73560141a7", "packages/code_editor/source/lightening.png": "844cc073cae34c193f3f39335146c955", "packages/code_editor/translations/qt_zh_CN.qm": "8e43fef9f53d43e8b693fc52984742d6", "packages/code_editor/translations/qt_zh_CN.ts": "20d10e3f79c729e1c90cbc88858cfd36", "packages/dataio/accountutil.py": "98a4758a6a1b11f7ee17f08e8b6c03d8", "packages/dataio/database_sample.py": "40b1c25397f6ddea9fb38029ee97eaea", "packages/dataio/dataImportModel.py": "88792dc540a80f0a5b22a7de6f3f1b12", "packages/dataio/dbimport.py": "6e4e64e8277a17c0fd21748ee7612b9a", "packages/dataio/dbindexing.py": "8d8d88e3a62cd935519aa4716218fac9", "packages/dataio/export.py": "ba01392157f6058a0d3a2a6f65a82b46", "packages/dataio/exportutils.py": "71a15223db0b6f6ef4bbe7e5ad31a4b6", "packages/dataio/importutils.py": "138d86b798b92d45639791ef57285981", "packages/dataio/main.py": "194549d322dbcea58329cff2cd5d7104", "packages/dataio/package.json": "a857dcfa80f7bbe16b29d447684fede6", "packages/dataio/python.jpg": "81740ef62fda1acb2905ad8dedb96385", "packages/dataio/README.md": "2918b5e59be436e278047961adb8afeb", "packages/dataio/sample.py": "346de2707ffbf2cd036b19b6911be5ff", "packages/dataio/settings.json": "d41d8cd98f00b204e9800998ecf8427e", "packages/dataio/dataUI/data_import_csv.py": "6b0ffb99fdb1d3bc68a61ca6a9169063", "packages/dataio/dataUI/data_import_csv.ui": "47c21370141a61960861b65836e7287b", "packages/dataio/dataUI/data_import_excel.py": "17b37971c44f89cdc5cef09c4ae742ef", "packages/dataio/dataUI/data_import_excel.ui": "2038dabc9e1caea8e1328b64fb10dc89", "packages/dataio/dataUI/data_import_matlab.py": "eccad7dc8726499702e75adcb6eb64d4", "packages/dataio/dataUI/data_import_matlab.ui": "4ae19c223a9d60139a73f2b1c553c64a", "packages/dataio/dataUI/data_import_model.py": "6732462f2b28d1228e6b6429d8f820cb", "packages/dataio/dataUI/data_import_model.ui": "6c61ace6854928adfb4c6ea8eff19fb5", "packages/dataio/dataUI/data_import_mysql.py": "10024040b895e1f7a0761cf41ed569aa", "packages/dataio/dataUI/data_import_mysql.ui": "bab2eaf2057d9175638198f14fb5f599", "packages/dataio/dataUI/data_import_oracle.py": "8068996f5d6395e95e878c341273ceb2", "packages/dataio/dataUI/data_import_oracle.ui": "c7d90bb98b8d4c0d5401eca7bcabe7ec", "packages/dataio/dataUI/data_import_postgresql.py": "c5404dc63e987cd2ed07d2b2424d5dac", "packages/dataio/dataUI/data_import_postgresql.ui": "b9a515989b036b7466bc2edd325b6eb1", "packages/dataio/dataUI/data_import_sas.py": "428fd62424d9e21c3d5bdc67ad2474a2", "packages/dataio/dataUI/data_import_sas.ui": "1f8667b3667d2baba53533b938b1a6a9", "packages/dataio/dataUI/data_import_spss.py": "8a1154c9315758ad9fa8bc96bfa1a367", "packages/dataio/dataUI/data_import_spss.ui": "35e2eb820c7e69bef4f06bbce6344c19", "packages/dataio/dataUI/data_import_stata.py": "102a879258cfbb70afc5815684e53b93", "packages/dataio/dataUI/data_import_stata.ui": "fd01ec0806b50f0dbbe7df3ae5578bcb", "packages/dataio/dataUI/data_import_text.py": "347f0183bb4055c43993f5ed84ae3d6c", "packages/dataio/dataUI/data_import_text.ui": "c8cc49a3ee1b2c4209c5c9ffe92fcfd8", "packages/dataio/dataUI/display.png": "38a696b77d8e89b1f24e3077184f3588", "packages/dataio/dataUI/hide.png": "80009586668a32b1a0bcdc5fb1854ac4", "packages/dataio/dataUI/password.py": "c174715c922460e682382cc1a4c9076f", "packages/dataio/dataUI/__init__.py": "cddad8a43ae85331e7fc60b6c4783a14", "packages/document_server/extension_demo.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/document_server/index.rst": "de08e1d943a610a1d4b61de487fc9606", "packages/document_server/main.py": "8e52c195bd0de7649b208fe9431fac90", "packages/document_server/package.json": "d552267ae466f74d360aa2dde9707999", "packages/document_server/settings.json": "ac084b924952c4c88e397f5c1f64c893", "packages/document_server/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/document_server/docserver/renderer.py": "0d2b8fa90287c98d2b2ea47893d902fd", "packages/document_server/docserver/server.py": "b525026a556b339079d30c4294e7fd60", "packages/document_server/docserver/__init__.py": "f64b6b6963038b02a0fafec97cb826da", "packages/document_server/docserver/static/css/main.css": "514ba05b769e4a20a328f77267aeb3a0", "packages/document_server/docserver/static/css/main.css.map": "db360aa1e642da89de3240fed50e4432", "packages/document_server/docserver/static/css/main.sass": "7b0d06ed553c19887a7dcad5bb97e954", "packages/document_server/docserver/static/js/main.js": "52a8a678ac589478f5b97e88066109c6", "packages/document_server/docserver/static/mathjax/tex-mml-svg.js": "2ac6956d3f16edb119f3b5686ef933d5", "packages/document_server/docserver/templates/content.html": "7420679eb81eea0ca7b9e98972b49160", "packages/document_server/translations/qt_zh_CN.ts": "023045a080a9a26547219f054c272d80", "packages/document_server/translations/zh_CN.ts": "023045a080a9a26547219f054c272d80", "packages/drawings_toolbar/group_chart.py": "846a72c9e0303f8ef4b5c6b809f07db7", "packages/drawings_toolbar/ipython_console.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/drawings_toolbar/main.py": "6d8e3f8e511fa3ec1186e59c68487b69", "packages/drawings_toolbar/map_var.json": "91324fe1e6b292ebc2d548f3ae7d49b7", "packages/drawings_toolbar/map_var.py": "46e4e76e96cb5691754e4b63a38b67d9", "packages/drawings_toolbar/package.json": "95e01099cdd1f73595c179112383e45a", "packages/drawings_toolbar/radar_chart.py": "67ea13759ea77d67006df488c9fde4e0", "packages/drawings_toolbar/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/drawings_toolbar/fastui/base.py": "5aabafefad5433391c67bf5f5749141f", "packages/drawings_toolbar/fastui/draw_boxplot.py": "54cc901679b29dc27fbaa8c12a458daf", "packages/drawings_toolbar/fastui/draw_hist.py": "97f5fd773217c53c26c9d44c6905f8cf", "packages/drawings_toolbar/fastui/functions.py": "2756f0633851f7605644c78de2f9e7b6", "packages/drawings_toolbar/fastui/plot.py": "c52ba2a8e3dc27b6397480b316f3f745", "packages/drawings_toolbar/fastui/__init__.py": "b3f689f89235e0fd970023fb1910ebaa", "packages/drawings_toolbar/fastui/helps/boxplot.md": "c28b070128b33660fd657c82d7bd2d54", "packages/drawings_toolbar/fastui/helps/markers.md": "35a158b4cb160219a0d5f4c89025f50c", "packages/drawings_toolbar/fastui/helps/plot.md": "9a819ab6bdf462d779ff742d77e49526", "packages/drawings_toolbar/pmmap/china/all/01-beijing.npy": "05fc6e35605340ef6e4d62af9770ef7a", "packages/drawings_toolbar/pmmap/china/all/02-shanghai.npy": "dbd37714da88edf44df7a2642ce2c14f", "packages/drawings_toolbar/pmmap/china/all/03-tianjin.npy": "e767b1d0810acfd174d4b52586441de1", "packages/drawings_toolbar/pmmap/china/all/04-chongqing.npy": "1980444de2496af7ae7b11cae9372d2c", "packages/drawings_toolbar/pmmap/china/all/05-heilongjiang.npy": "f369a349db31ac0636e625737802922e", "packages/drawings_toolbar/pmmap/china/all/06-neimeng.npy": "0366d9f95bfe2e0768a6491f8f5e27cc", "packages/drawings_toolbar/pmmap/china/all/07-xinjiang.npy": "201b3ff97375ae9ba6e9fc9c7b0eed1e", "packages/drawings_toolbar/pmmap/china/all/08-jilin.npy": "0ed85b6d84b877cf07154e1b54db0bdd", "packages/drawings_toolbar/pmmap/china/all/09-gansu.npy": "08c6e4437a0fc5d19d6c655a14a7df2e", "packages/drawings_toolbar/pmmap/china/all/10-liaoning.npy": "542d598f5c39b363738b0b753d8318d1", "packages/drawings_toolbar/pmmap/china/all/11-hebei.npy": "5a7ce525859caabaffad0fe1c11cb26f", "packages/drawings_toolbar/pmmap/china/all/12-shanxi.npy": "5ebb5347431393d599f5f0d36a31dd07", "packages/drawings_toolbar/pmmap/china/all/13-shan3xi.npy": "71944b8eea6caa0b7e4138063d6e8945", "packages/drawings_toolbar/pmmap/china/all/14-ningxia.npy": "dc0660ac4216cb4e928fb08dba5e36fb", "packages/drawings_toolbar/pmmap/china/all/15-qinghai.npy": "46f4e9bf9a47734d0cfbef0916e6cec1", "packages/drawings_toolbar/pmmap/china/all/16-shandong.npy": "b3eee97751e252f1c729b20cde81220c", "packages/drawings_toolbar/pmmap/china/all/17-henan.npy": "530d73e54901bf816e61c86bafb97219", "packages/drawings_toolbar/pmmap/china/all/18-xizang.npy": "83c873032100d735d2a3c1becb4e4ee5", "packages/drawings_toolbar/pmmap/china/all/19-jiangsu.npy": "33051765300de0a9d003ea91c3846cc6", "packages/drawings_toolbar/pmmap/china/all/20-anhui.npy": "d80f40e27c3a60188547d058c827eae0", "packages/drawings_toolbar/pmmap/china/all/21-sichuan.npy": "115e4b1abbc6df1f862095e94cacb8b5", "packages/drawings_toolbar/pmmap/china/all/22-hubei.npy": "065b61ce8a2728be70d0e726990e42c2", "packages/drawings_toolbar/pmmap/china/all/23-zhejiang.npy": "6910df476893b4e5a84689094203b724", "packages/drawings_toolbar/pmmap/china/all/24-jiangxi.npy": "2d4e166cd8d9371a1c6d01848ece3301", "packages/drawings_toolbar/pmmap/china/all/25-hunan.npy": "ddbce0334a1f79bd77f7b0ae582dd28e", "packages/drawings_toolbar/pmmap/china/all/26-guizhou.npy": "4d23f4f330203b61c6f8cca0ac764819", "packages/drawings_toolbar/pmmap/china/all/27-yunnan.npy": "1d1c9378c8427667dbf0b8948c36b484", "packages/drawings_toolbar/pmmap/china/all/28-fujian.npy": "77270ff92f0d67f0123672527b70772a", "packages/drawings_toolbar/pmmap/china/all/29-guangxi.npy": "3862d0becc054fe754ae71c111c583ad", "packages/drawings_toolbar/pmmap/china/all/30-guangdong.npy": "7d7f1392cc01a22d4503354ce7c729b1", "packages/drawings_toolbar/pmmap/china/all/31-taiwan.npy": "2f398da02605db2be30751dedb92ea90", "packages/drawings_toolbar/pmmap/china/all/32-xianggang.npy": "b3cf9d637c7d87e82a86174f99be74da", "packages/drawings_toolbar/pmmap/china/all/33-aomen.npy": "68e3c682aef3e25cf873878a19df5361", "packages/drawings_toolbar/pmmap/china/all/34-hainan.npy": "a67e27cc4f68d566f6977580d95b006c", "packages/drawings_toolbar/pmmap/china/all/39-jiuduan.npy": "35fbdcbb72d2c22caab9b1bfd2eba401", "packages/drawings_toolbar/pmmap/china/small/30-guangdong.npy": "7d7f1392cc01a22d4503354ce7c729b1", "packages/drawings_toolbar/pmmap/china/small/31-taiwan.npy": "2f398da02605db2be30751dedb92ea90", "packages/drawings_toolbar/pmmap/china/small/32-xianggang.npy": "b3cf9d637c7d87e82a86174f99be74da", "packages/drawings_toolbar/pmmap/china/small/33-aomen.npy": "68e3c682aef3e25cf873878a19df5361", "packages/drawings_toolbar/pmmap/china/small/34-hainan.npy": "a67e27cc4f68d566f6977580d95b006c", "packages/drawings_toolbar/pmmap/china/small/39-jiuduan.npy": "35fbdcbb72d2c22caab9b1bfd2eba401", "packages/drawings_toolbar/source/down.svg": "fe6b296aa4020dda5c5a870bb7596235", "packages/drawings_toolbar/source/erase.png": "68beac11764738b4fd52d367e5ad8223", "packages/drawings_toolbar/source/grid.png": "a39688f7d385bf86ba28eaf9d63a286d", "packages/drawings_toolbar/source/label.png": "2289b06fad4b39ea868450cdfbdc5eea", "packages/drawings_toolbar/source/monitor.png": "7272e1f62681d4200a2d778ec3df27e5", "packages/drawings_toolbar/source/plot.svg": "05fd271af6c134f6872c80c489064d60", "packages/drawings_toolbar/source/split.png": "d3cdd812358fd82f19500d8ac1bbc3be", "packages/drawings_toolbar/source/ticks.png": "2165ab858f8de60f5132e861740f7699", "packages/drawings_toolbar/source/\u5730\u56fe.png": "eca50b3207a973738145d1cf12ca0c34", "packages/drawings_toolbar/source/\u6298\u7ebf\u56fe.png": "1bde0eca8cfa880452accf56ca925866", "packages/drawings_toolbar/source/\u6563\u70b9\u56fe.png": "78c432bb278d8ea5c9587f23ffdd5f82", "packages/drawings_toolbar/source/\u6761\u5f62\u56fe.png": "b07f4db5b1785376f97636fc6963234e", "packages/drawings_toolbar/source/\u67f1\u5f62\u56fe.png": "d6cba37e00bf9bbeb65235897a7b90b9", "packages/drawings_toolbar/source/\u6c14\u6ce1\u56fe.png": "d6784d4c53145cce1fde17f673b76984", "packages/drawings_toolbar/source/\u70ed\u529b\u56fe.png": "566d7c6ad396084ca6b7d490868fdc2c", "packages/drawings_toolbar/source/\u76f4\u65b9\u56fe.png": "87113fa7353e0342248a7c953949079e", "packages/drawings_toolbar/source/\u7bb1\u7ebf\u56fe.png": "ba733c4c1a0979ab5bc838149490e915", "packages/drawings_toolbar/source/\u7ec4\u5408\u56fe.png": "c5bea73cff095ad47bd07e1e81921ab2", "packages/drawings_toolbar/source/\u96f7\u8fbe\u56fe.png": "5aa1a48424b30a157a2bca181c9be2d0", "packages/drawings_toolbar/source/\u9762\u79ef\u56fe.png": "69682cc944d7afdd5c1f2019bf6ca21c", "packages/drawings_toolbar/source/\u997c\u56fe.png": "862b6c81a5752ee7d53500ce9064c10d", "packages/drawings_toolbar/translations/qt_zh_CN.qm": "cd23a7e488d169790b204929188d78e7", "packages/drawings_toolbar/translations/qt_zh_CN.ts": "ce76ad850f047e2cea0440e4dab7c7d4", "packages/embedded_browser/extension_demo.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/embedded_browser/index.rst": "f1825ba1705fc05e593342e0eb4d2800", "packages/embedded_browser/main.py": "23ad2b340856b4ee181f560c6234655c", "packages/embedded_browser/package.json": "662baeb4c74d3c20f8a4604a7a9922df", "packages/embedded_browser/settings.json": "ac084b924952c4c88e397f5c1f64c893", "packages/embedded_browser/webbrowser.py": "d9038ba9457b984e37a4ae1e19ab636a", "packages/embedded_browser/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/embedded_browser/translations/qt_zh_CN.ts": "023045a080a9a26547219f054c272d80", "packages/embedded_browser/translations/zh_CN.ts": "023045a080a9a26547219f054c272d80", "packages/extension_demo/extension_demo.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/extension_demo/main.py": "b776c52a5c366b88629c1c12a0c4da57", "packages/extension_demo/package.json": "fe0c68ec838785d4f8ae971ce415f6db", "packages/extension_demo/settings.json": "ac084b924952c4c88e397f5c1f64c893", "packages/extension_demo/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/extension_demo/translations/qt_zh_CN.ts": "023045a080a9a26547219f054c272d80", "packages/extension_demo/translations/zh_CN.ts": "023045a080a9a26547219f054c272d80", "packages/file_tree/extension_demo.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/file_tree/file_tree.py": "b65b3a68d28d55db5bfc3bfb1adecee2", "packages/file_tree/main.py": "210493015e96aecac98bba55bd57674b", "packages/file_tree/package.json": "0e08e253fce09755a4d9758cb55b3900", "packages/file_tree/settings.json": "5502d6d53ce7276a7cdaa1ef54315efb", "packages/file_tree/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/file_tree/src/up.svg": "cef12db3d43301347f3d556eb9499a6c", "packages/file_tree/translations/qt_zh_CN.qm": "76f4ace00b4ef9e66503cb39d5c9ab5d", "packages/file_tree/translations/qt_zh_CN.ts": "eefaa8ed0d5717aab5253c74ea7d6a3a", "packages/graph_agg/extension_demo.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/graph_agg/graph_agg.py": "7ebd645f566d7412c3af7de2865d2896", "packages/graph_agg/graph_agg_ui.py": "61edf67aebb1d89f92d604a8d22c1200", "packages/graph_agg/graph_agg_ui.ui": "45c54e740ed679ebcdc66594709eaf9f", "packages/graph_agg/main.py": "3399d8c6899a36a92b1910d55d05ea96", "packages/graph_agg/package.json": "b2bfcc74063422e491d0c3df279bbc38", "packages/graph_agg/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/ipython_console/commandparser.py": "8df507247c3112a5a164a961f8b154b5", "packages/ipython_console/index.rst": "de4df58d8cb7a27038ea01f08d3e74c9", "packages/ipython_console/initialize.py": "f75a73ebb2b6af4e198d47cb326ac8c6", "packages/ipython_console/ipythonqtconsole.py": "715c9c195c300400bd24375a2c0f2cac", "packages/ipython_console/ipython_console.jpg": "e2c98aceacdcf9370be0234c1ae56660", "packages/ipython_console/main.py": "764879652c234804c28091c167e8506f", "packages/ipython_console/package.json": "330d1b64fd0fa236f68e4e9148d7f2ff", "packages/ipython_console/README.md": "d91f325d8113d754239f36ebf6211e2d", "packages/ipython_console/requirements_ipython_node.txt": "7e2992fb7c705a5cc8a817af37b0b890", "packages/ipython_console/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/ipython_console/translations/qt_zh_CN.qm": "d32941bf701e20d30c70b509e3421f26", "packages/ipython_console/translations/qt_zh_CN.ts": "ae95465c0df4254a53d4144fd5a90148", "packages/jupyter_notebook_support/client.py": "64707c4c5af928668dfe5624020ab381", "packages/jupyter_notebook_support/extension_demo.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/jupyter_notebook_support/ipython_data_show.py": "ba5fafcca30e9db07a1330fb143b539d", "packages/jupyter_notebook_support/main.py": "1961046decf8e842648695f64f4a951a", "packages/jupyter_notebook_support/package.json": "8f1bbf103ab237adf5e932d77304bab6", "packages/jupyter_notebook_support/route.py": "d63fb2946b16cecd4c03b63b96b417fa", "packages/jupyter_notebook_support/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/jupyter_notebook_support/scripts/pyminer_ipython_node.py": "5afb85f04995a313be86875d55896b8f", "packages/jupyter_notebook_support/tests/exec_api_test.py": "db3498a3620bc730a093106024c3d895", "packages/jupyter_notebook_support/tests/find_free_port.py": "7519a17439dd072a5f5609dccf71acff", "packages/jupyter_notebook_support/tests/find_kernel_spec.py": "5e494c2592bc747a166f3e5eb1fdb0d2", "packages/jupyter_notebook_support/tests/jupyter_client_test.py": "cd2193f167111e6f74a51ce27671b6e0", "packages/jupyter_notebook_support/tests/list_running_jupyter_servers.py": "decfc524233da897d5ae4e0896ad42bf", "packages/jupyter_notebook_support/tests/test.json": "fd0693bed89aa001426c855cf60719da", "packages/jupyter_notebook_support/tests/Untitled.ipynb": "0eca040919ec50b82d4197e13564f3f8", "packages/jupyter_notebook_support/tests/.ipynb_checkpoints/Untitled-checkpoint.ipynb": "2f6cceae1d861df553a6ebb64b6862ef", "packages/jupyter_notebook_support/translations/qt_zh_CN.qm": "910b1a4eece778fe19eb8c37d856219b", "packages/jupyter_notebook_support/translations/qt_zh_CN.ts": "a52f5f65e0ebc5ef1c4c205d8b977e5b", "packages/pmagg/extension_demo.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/pmagg/LICENSE": "e49f4652534af377a713df3d9dec60cb", "packages/pmagg/main.py": "26f2abd09259651ad17e35b24de5a097", "packages/pmagg/package.json": "3f5b1d4110de43c171ec51876448d516", "packages/pmagg/PMAgg.py": "51be8eab2ca29167621904193a7d94e0", "packages/pmagg/Readme_CN.md": "b120eff0eec50462b099a5afc44e8135", "packages/pmagg/setup.py": "c819982245570068aeff72f2c4925d3c", "packages/pmagg/unit_test.py": "6cc0c2b57eebac72cb159bdb73dae6dd", "packages/pmagg/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/pmagg/icons/annotation.png": "41abc31827d980673cb4b774d9cfc589", "packages/pmagg/icons/arrow.png": "faea79dbbe13b73b2b6b2d381d3f0b04", "packages/pmagg/icons/axis.png": "8aad7c4a25caea1eab5e00416383fe87", "packages/pmagg/icons/back.png": "7e3a69ff1b93c0d922903ca9753d9413", "packages/pmagg/icons/colorbar.png": "51edcf418f2920bcc55e703956345d22", "packages/pmagg/icons/figure.png": "6c84c49ce26f638a352654df89ee3d35", "packages/pmagg/icons/front.png": "b3675ce85ff0c87602fcee821606b376", "packages/pmagg/icons/grid.png": "f495112a5ee666819bccc792ac9ab480", "packages/pmagg/icons/home.png": "e4a7f4019b4ea9cb1f375da78a2e5adf", "packages/pmagg/icons/Icon.ico": "3fe019a16833a4b89743e829706a22a6", "packages/pmagg/icons/image.png": "9a685d94a08e6dab95ab996ac36c3019", "packages/pmagg/icons/layout.png": "113fcb65d470b3c44eca07a69883b067", "packages/pmagg/icons/legend.png": "4554646775b8caab3af239051e231530", "packages/pmagg/icons/line.png": "0ad86f368735816bb25dae9a6037d042", "packages/pmagg/icons/oval.png": "bf5e995685d6bd1758ddc56db9a706c1", "packages/pmagg/icons/pan.png": "d31d48619f6187a992f8bc51f4bbbf31", "packages/pmagg/icons/point.png": "6ea950ae1d8f16fc9d364dea468e1bf6", "packages/pmagg/icons/polygon.png": "a7d892e0a3ca8e00aefc73211cc211aa", "packages/pmagg/icons/rect.png": "9088ab4129c08c3370675a27c5af7f76", "packages/pmagg/icons/rotate.png": "b058a0e469a679d752ea393eb8b94204", "packages/pmagg/icons/save.png": "1f019d1efc880a99aa0d6c413d4b1672", "packages/pmagg/icons/setting.png": "2ca8a4d585232a124bc8f8bbaf913eec", "packages/pmagg/icons/space.png": "a63438f63e5c9a61ae377f92e4638427", "packages/pmagg/icons/style.png": "5479b633bfd95b75aca566d283f90f2d", "packages/pmagg/icons/text.png": "2d058b7289a520eb1087ea7957e85a18", "packages/pmagg/icons/X_axis.png": "81b2fbfd328ea56e03bbac9d98f54bd0", "packages/pmagg/icons/Y_axis.png": "dec07bca2db81b7a13d05cf63147042c", "packages/pmagg/icons/zoom.png": "624d6036e5af3901a672926eed0cfd74", "packages/pmagg/icons/Z_axis.png": "4ef63246be7447c74de3fc6352f56782", "packages/pmagg/langs/en_axes_control.qm": "3e31bb846d2075752f2083796bbab081", "packages/pmagg/langs/en_axes_control.ts": "e3f2a95ffc1ee822600420487fc64497", "packages/pmagg/langs/en_pmagg_ui.qm": "0d72b1e6a3868f6a9d7f4353dc49da57", "packages/pmagg/langs/en_pmagg_ui.ts": "c40ed0942c1fc822cbfdac7d51728e15", "packages/pmagg/langs/zh_CN_axes_control.qm": "43c4b723bf698383102abf57ae7ff375", "packages/pmagg/langs/zh_CN_axes_control.ts": "b69e3a2583aab4d47e9042151ddd4431", "packages/pmagg/langs/zh_CN_pmagg_ui.qm": "bcebcf42735c6849bdecbb77451021dd", "packages/pmagg/langs/zh_CN_pmagg_ui.ts": "a924984618a9687fe9bed9cd1ab3e8bd", "packages/pmagg/pictures/pmagg_api.mmd": "3c46f4d87fb3706b4b1bd70b7e442a5d", "packages/pmagg/pictures/pmagg_api.png": "822f49430932514591eda54acfc10a1f", "packages/pmagg/pictures/pmagg_show.png": "5dc944f02aa22177c5c24ab1f71a391d", "packages/pmagg/translations/qt_zh_CN.ts": "023045a080a9a26547219f054c272d80", "packages/pmagg/translations/zh_CN.ts": "023045a080a9a26547219f054c272d80", "packages/pmagg/ui/arrow_setting.py": "d8e8f2f7101729c6ced2cd4f4dd0ee6d", "packages/pmagg/ui/axes_control.py": "b232825bef4aa017e9bbf92b57640f49", "packages/pmagg/ui/axis_edit.py": "4875d36d29871c4d87c05108117d9f9f", "packages/pmagg/ui/axis_edit.ui": "f7b92a8a5d9b4ca769bf42ebdb34577a", "packages/pmagg/ui/axis_edit_manager.py": "e2245787fc389cb116ca56c37131addd", "packages/pmagg/ui/colorbar_setting.py": "46a557021eeeefe8c56bf5f9e26629cd", "packages/pmagg/ui/color_table.py": "e209c8c3d24f582978ca33100de623a5", "packages/pmagg/ui/default_setting.py": "5846b7322727ee4b19dc7b6c336d1947", "packages/pmagg/ui/default_setting.ui": "b93e791244d7912a51bbaf75a2deff38", "packages/pmagg/ui/default_setting_manager.py": "c06f05023a1bbd6eb754b0e68209fca6", "packages/pmagg/ui/ellipse_setting.py": "4d24440e44796ada3a73a387600899fc", "packages/pmagg/ui/image_setting.py": "6e909b7852eb02a52455a9aa36da7bed", "packages/pmagg/ui/legend_setting.py": "6e3a19772353cdf39944df1e4faa17dd", "packages/pmagg/ui/line2d_setting.py": "470ef26ff8d454e8247f37277d5a785a", "packages/pmagg/ui/linestyles.py": "648f0a673e71e4d08c98e0cc6864202e", "packages/pmagg/ui/pmagg_ui.py": "0ac0c186e7177a3a9c6aee2d9d409177", "packages/pmagg/ui/pmagg_ui.ui": "f1a0f6ff01e632a4994f00258356911f", "packages/pmagg/ui/rectangle_setting.py": "3080ff255cfe2378777f68025e61f2c4", "packages/pmagg/ui/save_image_setting.py": "16036a37904ffa3f20fecc7d004b009f", "packages/pmagg/ui/text_setting.py": "34de8bed7cf453d59fe938be0fbfe92c", "packages/pmagg/ui/title_setting.py": "ebdf8cbd5684278ec81c9cdde176b684", "packages/pmagg/ui/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/pm_calc/ipython_console.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/pm_calc/main.py": "83b0f01515be0f4f76da807e9cb61125", "packages/pm_calc/package.json": "9fdd3a4c9b4a20d39944e5f390642e1e", "packages/pm_calc/preprocess.py": "a8c8d1cb131393e4bf0a6e43503f5c7b", "packages/pm_calc/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/pm_calc/fastui/base.py": "76ac4b617688dfeb97d75c73803dcd44", "packages/pm_calc/fastui/create_random_variable.py": "fa6d77ce65a13e4974da9f5a33516d2a", "packages/pm_calc/fastui/create_tensor.py": "bde0d9952e2ab26e9fb5f47cf8fff98a", "packages/pm_calc/fastui/create_vector.py": "cb707dcfefc1f308d25c17d6a034cdfc", "packages/pm_calc/fastui/dblquad.py": "5e4c5b60231b67cbe5233de0b555f308", "packages/pm_calc/fastui/equation_solve.py": "6fdb3b720ad9547553938a3b735bf970", "packages/pm_calc/fastui/matrix_calc.py": "fc4dbc1ce3af2ae26125d1adb7f8f690", "packages/pm_calc/fastui/matrix_inv.py": "bd826082ba2b3c8775563da8c3ee6321", "packages/pm_calc/fastui/matrix_numbers.py": "b9bc2f888b2c98743072fc686e07751f", "packages/pm_calc/fastui/numerical_integration.py": "57edb1443625bdc5d990aa4d99f6272e", "packages/pm_calc/fastui/reshape_tensor.py": "0647e734b05f39cef659c82c315fb023", "packages/pm_calc/fastui/__init__.py": "b3f689f89235e0fd970023fb1910ebaa", "packages/pm_calc/fastui/helps/numerical_integration.md": "df25b4e7795b58e927c62368d814f0ab", "packages/pm_calc/fastui/helps/reshape_tensor.md": "96a600a163541231dc71d43438de0ab4", "packages/pm_calc/icons/create.png": "d94320bc7e7a4dded74f4eda5b6d1c3e", "packages/pm_calc/icons/eigen.png": "97048cae872a927137f5b7295828c5c2", "packages/pm_calc/icons/equation_solve.png": "af3de472d63ef0e8e6005c153d29ad0b", "packages/pm_calc/icons/flip.png": "e769eeaa1f423de72f34dda86f088540", "packages/pm_calc/icons/integrate.png": "0d3a7f301fa771ee8ef56b10541551d9", "packages/pm_calc/icons/matrix.png": "5b97eee2dae1233c831daae04c5dd98f", "packages/pm_calc/icons/matrix_calc.png": "151e56c9e82870f7cf9783971ee37782", "packages/pm_calc/icons/reshape.png": "3ee90fbdcf09e1bcf3f24e13be041530", "packages/pm_calc/icons/rvs.png": "da077db4d3d834bb311f445e6a9a07c7", "packages/pm_calc/translations/qt_zh_CN.qm": "38862557de320f2ff74455ab1c059a5a", "packages/pm_calc/translations/qt_zh_CN.ts": "c17e3ce1dd88a0cfc45d42cd1f706361", "packages/pm_helpLinkEngine/helpLinkEngine.py": "8e712a1e13bd4d0dd0a5f3cbb59212e0", "packages/pm_helpLinkEngine/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/pm_marketplace/env_manager.py": "b3d1a95732a2b9c9af7ebb3f2d919f08", "packages/pm_marketplace/env_manager.ui": "c910e7c1fdb7d4931ae6395f32fdb06f", "packages/pm_marketplace/Icon.ico": "3fe019a16833a4b89743e829706a22a6", "packages/pm_marketplace/package_install.py": "3b2122f43e471ed12022254a633e93ea", "packages/pm_marketplace/package_install.ui": "58c036d918d493749750442a6dd6e56c", "packages/pm_marketplace/package_manager.py": "9bc3ebc8d0896b8c90661b5e3e0bab5b", "packages/pm_marketplace/package_remove.py": "2882477a6acf357e401983e64a9e5b3d", "packages/pm_marketplace/package_remove.ui": "cd82652c1c18ce309b7b39f64eb1eb93", "packages/pm_marketplace/package_setting.py": "0900c75df4d09edb2df07df0d40b3893", "packages/pm_marketplace/package_setting.ui": "7294066d39daafe32bfb6e14dc9bd4b2", "packages/pm_marketplace/package_update.py": "7bb12094c0e57a557e2e1d104300e910", "packages/pm_marketplace/package_update.ui": "73dd8b7e55bd1dc9730991b5b9a06fd4", "packages/pm_marketplace/pm_marketplace.py": "c00744c2f27e852e4ad0d2d37df043f7", "packages/pm_marketplace/pm_marketplace.ui": "72b055181d7bd41dbfdd06feac750245", "packages/pm_marketplace/pth_modifier.py": "330f9dce62a058e767c9654020ff2012", "packages/pm_marketplace/__init__.py": "e4e5f41117ada3d729f55649c34a3932", "packages/pm_modelling/default.png": "b58b2daf9a2a44726b678f235aed41e6", "packages/pm_modelling/main.py": "936de10bd651db7f14815477894bed86", "packages/pm_modelling/package.json": "ce1142cf81e5d40919fcce5ebe2ae4f9", "packages/pm_modelling/settings.json": "e4385e0a6554dfe40b3696f328f5edd1", "packages/pm_modelling/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/pm_modelling/source/down.svg": "fe6b296aa4020dda5c5a870bb7596235", "packages/pm_modelling/source/plot.svg": "05fd271af6c134f6872c80c489064d60", "packages/pm_modelling/source/\u5730\u56fe.png": "eca50b3207a973738145d1cf12ca0c34", "packages/pm_modelling/source/\u6298\u7ebf\u56fe.png": "1bde0eca8cfa880452accf56ca925866", "packages/pm_modelling/source/\u6563\u70b9\u56fe.png": "78c432bb278d8ea5c9587f23ffdd5f82", "packages/pm_modelling/source/\u6761\u5f62\u56fe.png": "b07f4db5b1785376f97636fc6963234e", "packages/pm_modelling/source/\u67f1\u5f62\u56fe.png": "d6cba37e00bf9bbeb65235897a7b90b9", "packages/pm_modelling/source/\u6c14\u6ce1\u56fe.png": "d6784d4c53145cce1fde17f673b76984", "packages/pm_modelling/source/\u70ed\u529b\u56fe.png": "566d7c6ad396084ca6b7d490868fdc2c", "packages/pm_modelling/source/\u76f4\u65b9\u56fe.png": "a9b01bca53610b2c03f7ea2331c91cc3", "packages/pm_modelling/source/\u7bb1\u7ebf\u56fe.png": "ba733c4c1a0979ab5bc838149490e915", "packages/pm_modelling/source/\u7ec4\u5408\u56fe.png": "c5bea73cff095ad47bd07e1e81921ab2", "packages/pm_modelling/source/\u96f7\u8fbe\u56fe.png": "5aa1a48424b30a157a2bca181c9be2d0", "packages/pm_modelling/source/\u9762\u79ef\u56fe.png": "69682cc944d7afdd5c1f2019bf6ca21c", "packages/pm_modelling/source/\u997c\u56fe.png": "862b6c81a5752ee7d53500ce9064c10d", "packages/pm_modelling/translations/qt_zh_CN.qm": "8a5de3a24facfac59b9b7a95ab7937d7", "packages/pm_modelling/translations/qt_zh_CN.ts": "611800964b03dfef8c1cbc1dc4a0bd9c", "packages/pm_preprocess/base.py": "731a2f17cc570b8b92e5da9e323e07f9", "packages/pm_preprocess/datafilter.py": "ac09562b0e7a6b9094225e01bc70e08c", "packages/pm_preprocess/datamissingvalue.py": "51a2e07966da2b720141bd8cb4721678", "packages/pm_preprocess/datareplace.py": "3cce7391493a104a2fc59a7f792a1883", "packages/pm_preprocess/data_filter.py": "102b1b8071747d6d8ed25530e206c5e7", "packages/pm_preprocess/ipython_console.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/pm_preprocess/main.py": "dd006452670a44799031989f21730175", "packages/pm_preprocess/package.json": "c27f88add11891797e8564867f94abfe", "packages/pm_preprocess/preprocess.py": "a8c8d1cb131393e4bf0a6e43503f5c7b", "packages/pm_preprocess/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/pm_preprocess/fastui/base.py": "97be57a24cb88f7de7aee11e768364ab", "packages/pm_preprocess/fastui/datamerge.py": "c11ecb39655996d441747c93d3a82a0a", "packages/pm_preprocess/fastui/dropna.py": "44a433355c6732b5c9a2ceebd2fed587", "packages/pm_preprocess/fastui/fillna.py": "fe5d6a6f14f21db63116e5ece2dcb9c6", "packages/pm_preprocess/fastui/pivot.py": "dc302f4e2a9a0a03f035cb04452dc978", "packages/pm_preprocess/fastui/transpose.py": "b9873caa7d3d698221fcd590eb848e27", "packages/pm_preprocess/fastui/__init__.py": "5774d567a5ade4656321f056692ecf7c", "packages/pm_preprocess/fastui/templates/dropna.json": "2135651085e530edc3555862c807e8a2", "packages/pm_preprocess/fastui/templates/dropna.py": "818d27872b51cabc824f36545e848dc2", "packages/pm_preprocess/fastui/templates/template.py": "ab8ca29cb2d8a0116de63f411ad08729", "packages/pm_preprocess/translations/qt_zh_CN.qm": "38862557de320f2ff74455ab1c059a5a", "packages/pm_preprocess/translations/qt_zh_CN.ts": "c17e3ce1dd88a0cfc45d42cd1f706361", "packages/pm_preprocess/ui/data_column_desc.py": "50e1de93cb2b5af92ef9f70ec61b28ae", "packages/pm_preprocess/ui/data_column_desc.ui": "2e4ff14a2934d2e461967f4adf57d096", "packages/pm_preprocess/ui/data_column_encode.py": "addb53a419ba7aad0c822918bd164584", "packages/pm_preprocess/ui/data_column_encode.ui": "83585c68a1b5f10c8cb5c8f979fc069a", "packages/pm_preprocess/ui/data_column_name.py": "ab1343d7eb25ba61ff00653dc727dcd2", "packages/pm_preprocess/ui/data_column_name.ui": "290d0f3b71254001a41ed5419cfc2aa7", "packages/pm_preprocess/ui/data_delete_column.py": "96cc3bb3a839bf327be48c5b8bcf7b4f", "packages/pm_preprocess/ui/data_delete_column.ui": "eb9f4daa76ea45a80897c7d699f0223a", "packages/pm_preprocess/ui/data_delete_row.py": "302569fa225a51a1f63cc8fd740532f2", "packages/pm_preprocess/ui/data_delete_row.ui": "a2bef2e740f12897748cb3ab8942c994", "packages/pm_preprocess/ui/data_filter.py": "18e9c3b6ec4a59ee18b9304234354fee", "packages/pm_preprocess/ui/data_filter.ui": "6b68ea20099eb2657a83277c2b35c5e2", "packages/pm_preprocess/ui/data_import_database.py": "aa8f2c175707416490b15c5b8a5f91b1", "packages/pm_preprocess/ui/data_import_database.ui": "70081a8159869c52d03f42ff96198bcd", "packages/pm_preprocess/ui/data_import_excel.py": "50eab4e5f295ac1e36b415bc1fc4ce17", "packages/pm_preprocess/ui/data_import_excel.ui": "8e4fad97836b5ade1bcf623df4019354", "packages/pm_preprocess/ui/data_import_sas.py": "b46b3a739487b88044585b81d8e1e6c3", "packages/pm_preprocess/ui/data_import_sas.ui": "3a238589200dae2ca6d95b9f76658bd4", "packages/pm_preprocess/ui/data_import_spss.py": "f2429d58b23aca160b2652423d57c491", "packages/pm_preprocess/ui/data_import_spss.ui": "02e6076ba3dba639a81488f57a779acd", "packages/pm_preprocess/ui/data_import_text.py": "72839d8f4905a64c72e3e1ef25f0e084", "packages/pm_preprocess/ui/data_import_text.ui": "42ff2c69c472636f51e11b1d26162306", "packages/pm_preprocess/ui/data_info.py": "9be5f4ff591e53d622773a0323d00195", "packages/pm_preprocess/ui/data_info.ui": "d4fcd989ceba0f39012093152207d598", "packages/pm_preprocess/ui/data_merge.py": "22e207fe8f408b7c558e4f9231c2812b", "packages/pm_preprocess/ui/data_merge.ui": "9cacfe18fc7720477acc7a53b46dd35f", "packages/pm_preprocess/ui/data_merge_horizontal.py": "af3de8607ed351d77daa6213fb2828bc", "packages/pm_preprocess/ui/data_merge_horizontal.ui": "d61611fdb4e9cde0fbc2e2c332f48bf5", "packages/pm_preprocess/ui/data_merge_vertical.py": "ef03b6681fd55cf8eb42138024ad1a4e", "packages/pm_preprocess/ui/data_merge_vertical.ui": "59f79c7fa7b08e5ae1e1aeb83eb05987", "packages/pm_preprocess/ui/data_missing_value.py": "81f362bf96738d9138b85a3d1a2dd9e2", "packages/pm_preprocess/ui/data_missing_value.ui": "14e0e959934e191ca4ecc2b65342864f", "packages/pm_preprocess/ui/data_new_column.py": "e079cabae617aeb3ba5fab8656bc79d6", "packages/pm_preprocess/ui/data_new_column.ui": "8d83bde6b1701635d9266ff9fe8047d6", "packages/pm_preprocess/ui/data_partition.py": "6f723fbf5b83bc18194b0e9242d08177", "packages/pm_preprocess/ui/data_partition.ui": "bb426c3df5bd8e2300a6fce26f0acb45", "packages/pm_preprocess/ui/data_repace.py": "76d5e5f3168158c6527adbc1c1b1d309", "packages/pm_preprocess/ui/data_repace.ui": "853b6f43d364faaae3d6f2ed484681ea", "packages/pm_preprocess/ui/data_role.py": "698c50eb2a907f5e517454a28d610daf", "packages/pm_preprocess/ui/data_role.ui": "9b659e8e2accf1bf76fe39efa74152ff", "packages/pm_preprocess/ui/data_role_edit.py": "f93e00c6adfd65e436630e4c5c097d3c", "packages/pm_preprocess/ui/data_role_edit.ui": "18a531771deee0c25777880c0f0894e6", "packages/pm_preprocess/ui/data_row_filter.py": "32baeee9b80430448f0e1f8af96a60e2", "packages/pm_preprocess/ui/data_row_filter.ui": "7a9b909a49af56c8f2916bf4c3f186f5", "packages/pm_preprocess/ui/data_sample.py": "a4919fe81ffbd64838ed0b84b924d36b", "packages/pm_preprocess/ui/data_sample.ui": "f43301f891fd0450fe7800cb81ff4af0", "packages/pm_preprocess/ui/data_sort.py": "fc051898a3fd9a70ff38fe8ff1d3106c", "packages/pm_preprocess/ui/data_sort.ui": "674a1b2eb62111b5c5134b01ed6d684e", "packages/pm_preprocess/ui/data_standard.py": "822ec20008baf2b82b8ee00ea1711933", "packages/pm_preprocess/ui/data_standard.ui": "65a2eca4b0f6ea65a6edf0c9b6f1628f", "packages/pm_preprocess/ui/data_transpose.py": "97b688d1ca7fb74f5bd72a615eac397e", "packages/pm_preprocess/ui/data_transpose.ui": "d5b50013e984a0f56af8b45aa213fd20", "packages/pm_statistics/default.png": "b58b2daf9a2a44726b678f235aed41e6", "packages/pm_statistics/describe.py": "d48a7e2d4573eec5ea2d6b78f6d518cf", "packages/pm_statistics/main.py": "35a51268c3d516d91f75fd155981327e", "packages/pm_statistics/package.json": "b0cfa0a43b9b4ceffef50955bc142708", "packages/pm_statistics/settings.json": "e4385e0a6554dfe40b3696f328f5edd1", "packages/pm_statistics/stat_desc.py": "1d1af20f21d51a2c4c937b583fa1c1e0", "packages/pm_statistics/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/pm_statistics/source/add.svg": "abddfead93cdf1feeff13e0fb361b2bc", "packages/pm_statistics/source/collect.svg": "df376dc82733ce771785e80417b5730c", "packages/pm_statistics/source/comment.svg": "17b32e5429d0af97cb4b0cb0b878301a", "packages/pm_statistics/source/descending.svg": "4c0294dcdbb972f6f7e7daae9ca3695e", "packages/pm_statistics/source/down.svg": "cf38a40a26932f5d960da15d8bbcb9f5", "packages/pm_statistics/source/history.svg": "f5e2890d446cc530e8505c2a8bcbeb4c", "packages/pm_statistics/source/left(1).svg": "195de59f0ae8946bbdad79b1e6211c13", "packages/pm_statistics/source/left.svg": "5e2fab4fbb3002791406ffb7d4f24552", "packages/pm_statistics/source/like(1).svg": "be280f55711fe37711710d66cc6de07f", "packages/pm_statistics/source/like.svg": "cd5a555d5aeb29ea51c2d4c411a8e2f7", "packages/pm_statistics/source/offline.svg": "864ab749688d77a17dd418ddd12f613f", "packages/pm_statistics/source/print.svg": "58e74f093ab0f76d7a5f4827139d8202", "packages/pm_statistics/source/reduce.svg": "d3a4d2cb5e65462b6e488f9008101095", "packages/pm_statistics/source/replace.svg": "2c9371c33c45026339f8f0a0fad242bb", "packages/pm_statistics/source/right(1).svg": "6ab0334feba1ebc9fe424a408dbeb053", "packages/pm_statistics/source/right.svg": "0d34209038963d8520605c2945a4aea5", "packages/pm_statistics/source/up.svg": "bfc6c9a4e8afe014a9ef2a3a2eaac829", "packages/pm_statistics/source/user.svg": "03789a31cc8d77164ee5e38758be9fed", "packages/pm_statistics/source/view.svg": "404b787576ea5ee3fb77e219a4020739", "packages/pm_statistics/translations/qt_zh_CN.qm": "27c309c51dd6a9b4453805a9d57314d8", "packages/pm_statistics/translations/qt_zh_CN.ts": "6f69f9df98fc80ba305d1ce1bc850e9a", "packages/pm_statistics/ui/stats.qrc": "9e88108821db568452feff5c52cd51bf", "packages/pm_statistics/ui/stats_rc.py": "aed3f3d5bd2699a234c5b87a8c90c27a", "packages/pm_statistics/ui/stat_base.py": "16ff0fac4ad347a51cb36503ddabc76b", "packages/pm_statistics/ui/stat_base.ui": "5b3601a91453d85b8ba247fac1772a85", "packages/qt_vditor/client.py": "b5f987d7265282647636753bb1992169", "packages/qt_vditor/extension_demo.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/qt_vditor/main.py": "43f5ca83b9b8de08a4b08f98c8d5b12c", "packages/qt_vditor/package.json": "a6ea48177df90bd33e6d9ef571715434", "packages/qt_vditor/route.py": "81e4d783b1ee5662a42175044860ac97", "packages/qt_vditor/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/qt_vditor/examples/sample.md": "6d463f26da3f758e8f8cd73d2f07a013", "packages/qt_vditor/templates/index.html": "ed4bff205de492bd9a445b878941ab98", "packages/setting_manager/main.py": "5e25055f2748fd8f2e16ef6cd438cbfe", "packages/setting_manager/package.json": "7b1091e765c316eb4602acbff21a7b5b", "packages/setting_manager/python.jpg": "81740ef62fda1acb2905ad8dedb96385", "packages/setting_manager/settings.json": "d41d8cd98f00b204e9800998ecf8427e", "packages/setting_manager/settings.py": "f60620c0bbfe1bc061bfc00759107674", "packages/setting_manager/ui_inputs.py": "16c170ac829957e8bd4f3b02f4375472", "packages/setting_manager/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/setting_manager/translations/qt_zh_CN.ts": "023045a080a9a26547219f054c272d80", "packages/setting_manager/translations/zh_CN.ts": "023045a080a9a26547219f054c272d80", "packages/socket_server/extension_demo.jpg": "d138a0042696fac046fa3b8a05d5d674", "packages/socket_server/index.rst": "e1840535bf3051014eab7bb2c76fbc10", "packages/socket_server/main.py": "a032c7a064b344e47dd998c545feb38f", "packages/socket_server/package.json": "12d6c14b45eb86427e311e31d005fa20", "packages/socket_server/server_by_socket.py": "77e46bf007fd51f41471b7901bc4b7e1", "packages/socket_server/settings.json": "ac084b924952c4c88e397f5c1f64c893", "packages/socket_server/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/socket_server/translations/qt_zh_CN.ts": "023045a080a9a26547219f054c272d80", "packages/socket_server/translations/zh_CN.ts": "023045a080a9a26547219f054c272d80", "packages/workspace_inspector/data_viewer.py": "96d8e120289b60f4fe6a4877c807a12e", "packages/workspace_inspector/inspectortable.py": "7e88412960946a64afcdaee6b3bf1902", "packages/workspace_inspector/main.py": "4d6088b006fe4b09815eb34cfad86dcb", "packages/workspace_inspector/package.json": "9ed3843023e8b054420bd126866d6962", "packages/workspace_inspector/python.jpg": "81740ef62fda1acb2905ad8dedb96385", "packages/workspace_inspector/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "packages/workspace_inspector/translations/qt_zh_CN.qm": "4c1aa1fdb602f504983cfc1f303fdb4b", "packages/workspace_inspector/translations/qt_zh_CN.ts": "0176607ae0f96037d5b756be88615f76", "widgets/get_time_consuming_classes.py": "b1cc4fe83988288288bb4d46a55297e8", "widgets/__init__.py": "d4a51fe0c972d4f917c7c2029f6d83f5", "widgets/display/examples.py": "ed6581d11a7624a45e76a0c214abb6e4", "widgets/display/__init__.py": "de9a0f70e421564574c3c849d8c9cbcd", "widgets/display/browser/browser.py": "6471eef6706c84e33e915d2539d7ad74", "widgets/display/browser/get_ipy.py": "fdcef50d9c3d562c0c1a01a07fffac29", "widgets/display/browser/handler.py": "8360907d55237220c8409d02896e1630", "widgets/display/browser/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "widgets/display/dynamicgraph/pgexample.py": "3fd8ccf52c92ebeb164ca3b048b657bc", "widgets/display/dynamicgraph/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "widgets/display/dynamicgraph/base/basetimeseries.py": "5c6cef4a05d653d276681d9281ea7eb3", "widgets/display/dynamicgraph/base/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "widgets/display/dynamicgraph/mplplots/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "widgets/display/dynamicgraph/pgplots/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "widgets/display/matplotlib/pmagg.py": "bbee7bf2140f5b9e7d0f2bd3f9f1dd53", "widgets/display/matplotlib/qt5agg.py": "33f10a259f5fa8973c6cb863ef117285", "widgets/display/matplotlib/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "widgets/display/matplotlib/pyqtgraph/pyqtgraphwidget.py": "c260badf3dd0ad2f2e3bcd0ae107dcd3", "widgets/display/matplotlib/pyqtgraph/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "widgets/display/vtk/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "widgets/docs/threading_and_tasking.md": "803fb7aad9cbd712229fa089727d2aae", "widgets/docs/doc_figures/pmflowarea_2.png": "4c5b6d091ae5fdc685e94c64a3561218", "widgets/doc_figures/nested_lists_to_place_widgets.png": "3b3dfe1437591299762a3307ba9880a0", "widgets/doc_figures/pmflowarea_1.png": "aeef39398e583540e55a87c04fcb19fc", "widgets/doc_figures/pmflowarea_2.png": "4c5b6d091ae5fdc685e94c64a3561218", "widgets/doc_figures/settings_panel.png": "d1e20e63c914275463ed75740228e7b6", "widgets/elements/dockobject.py": "bbad65e7b1f0304112a70c28917c4fe4", "widgets/elements/toolbar.py": "adb36bf834bbb12c5baabfb323dc5297", "widgets/elements/__init__.py": "5d3ff4be23732caafec3725aacfd2bb1", "widgets/examples/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "widgets/examples/utilities/examples.py": "4b738678a2cec6ca81f038d2aad1ebc4", "widgets/examples/utilities/long_conn.py": "a7fb1b9aac35414235ef80dbd1715812", "widgets/examples/utilities/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "widgets/flowchart/create_node_content_class.md": "e15251c3574ee6f3472cb32984007f1c", "widgets/flowchart/dataprocesswidget.py": "5830d35615c1b8e05555d4005089f1dd", "widgets/flowchart/readme.md": "13c783b72c01bbef6fd2462deb54d1e1", "widgets/flowchart/readme_arch.md": "15760931b08a70d8257b8e3695f06105", "widgets/flowchart/simulationwidget.py": "17b7dadaf18e84f2d359f2e24571cbe4", "widgets/flowchart/__init__.py": "1ec9907600bd333efbac93fb205ba8d7", "widgets/flowchart/\u521b\u5efa\u65b0\u8282\u70b9(deprecated).md": "c9a0c64e31e69a6c2b226590bf174049", "widgets/flowchart/core/flowchart_scene.py": "8e6b923663fb49519f6555d181c6bc2b", "widgets/flowchart/core/flowchart_widget.py": "6e61a893445b0c08e6bb4fc04f5c9663", "widgets/flowchart/core/flow_content.py": "9f7f15c45ea181fa9390cac06823cca6", "widgets/flowchart/core/flow_items.py": "45810b572b64dfb46b1dbafc4c8391bb", "widgets/flowchart/core/flow_node.py": "426bd423a15ea028aae608489bf27c2f", "widgets/flowchart/core/nodemanager.py": "22f10c693caabc1a588658af292a8238", "widgets/flowchart/core/utils.py": "5e2a38303e8fd30ed7ce3131d99ee4ae", "widgets/flowchart/core/__init__.py": "33d597a87ce723d432ee101bfea5933c", "widgets/flowchart/doc_figures/before_run.png": "56d3c6e2d043d5be501d27a016bf2eb0", "widgets/flowchart/doc_figures/check_json.png": "98dcb1514c6a63b37820704c3ff76ce3", "widgets/flowchart/doc_figures/click_edit_button.png": "1a6625861a3fbd71cf14aa948b16eced", "widgets/flowchart/doc_figures/click_right_top_add_button.png": "116cc66ea7f7a01621afa2bceeefde15", "widgets/flowchart/doc_figures/composition_structure.png": "8a898076c0aa77e7cfe627a933d29c73", "widgets/flowchart/doc_figures/configure_panel.png": "6fc3b5d60c9df7dc4b7ae1ca73162e0a", "widgets/flowchart/doc_figures/create.png": "77d2839be9232d6923e99ac372f1bdd5", "widgets/flowchart/doc_figures/create_new_content_Mul.png": "7d8073ceafe5de7b52cc99b7b0f83b70", "widgets/flowchart/doc_figures/create_node.png": "5defabfa57656965e0ce99fe418d9559", "widgets/flowchart/doc_figures/custom_node.png": "98076ecba04484ffb0f68bc1d3ebe506", "widgets/flowchart/doc_figures/edit_node.png": "5bab19f7eb2b5794e85edaaac6ac8f9f", "widgets/flowchart/doc_figures/edit_panel_meaning.png": "fc03b61991304dbc4b9757420d4853d3", "widgets/flowchart/doc_figures/popup_edit_panel.png": "a069ec207a2060649079a5876ef785e1", "widgets/flowchart/doc_figures/sketch_after_edit.png": "562a4f15b3d808e34ed205ba4859c66d", "widgets/flowchart/icons/down.png": "fe8105e197d1a8dbb7c3f4d04605cc94", "widgets/flowchart/icons/logo.png": "3ba1fce2f5b57a2ab16287e9d0784702", "widgets/flowchart/nodes/dfoperation.py": "3ea5a1b70fc51b363cd8d6ea029aa64f", "widgets/flowchart/nodes/docparser.py": "97e1297436001fc6d447a957799d56d6", "widgets/flowchart/nodes/plots.py": "1423de8473b088b0cf15fada6cebffdc", "widgets/flowchart/nodes/random.py": "bbab2dc862413421cfec6c250f5f04cf", "widgets/flowchart/nodes/reliabilities.py": "834359f014c3618af22987679829df79", "widgets/flowchart/nodes/simplecalc.py": "75c69d36c3813ee5e6b7f98c8c1893e2", "widgets/flowchart/nodes/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "widgets/flowchart/nodes/dataframeoperation/dropduplicated.py": "5db7eb9383b7e18a6e6b185b657c2f4e", "widgets/flowchart/nodes/dataframeoperation/randomrowsample.py": "594931f937571051aaa08115303f9b9d", "widgets/flowchart/nodes/dataframeoperation/__init__.py": "590a3c48436d17b168ab491e3d5ef71d", "widgets/flowchart/nodes/io/iterator.py": "d41401063ca9a942416f0f057de62c29", "widgets/flowchart/nodes/io/listdir.py": "8c5903f20f49659f9af2a7ec201d96b3", "widgets/flowchart/nodes/io/pdimport.py": "c32294f54f65a3f25ba6fd1cdabef0ea", "widgets/flowchart/nodes/io/__init__.py": "e6a014f94141e383286a37068a446b4d", "widgets/flowchart/tests/continously_data_process.py": "8f7cbef680438882a373a1a98b6f763c", "widgets/flowchart/tests/database_import.py": "ac1bb7f644465845aac15203b459813a", "widgets/flowchart/tests/fault_tree.py": "4e7a094f1bf83835627c17ecf51eec0a", "widgets/flowchart/tests/node_test.py": "49436316b4bcffa573c07cf714f61d29", "widgets/flowchart/tests/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "widgets/utilities/__init__.py": "846122683610ac9a3a02228d40c893d8", "widgets/utilities/network/baseclient.py": "025302351202482cdb67961418a21dda", "widgets/utilities/network/generalclient.py": "57523e2dfe953db9e48e0b2e887f9aea", "widgets/utilities/network/qtclient.py": "c136486e5d7ce14a760e68b5cef5eac9", "widgets/utilities/network/server.py": "36c357391ae6077674c686159e9adc0d", "widgets/utilities/network/util.py": "6d82debc837372e82a42af901dbd8d18", "widgets/utilities/network/__init__.py": "8dd261a5fd464d78930e554a3c71ca0e", "widgets/utilities/platform/commandutils.py": "c534c45682925de3a9ef097bc5e9738f", "widgets/utilities/platform/filemanager.py": "a7ed6bb8a48dc0a631c676b70618b727", "widgets/utilities/platform/filesyswatchdog.py": "c0a2bf1c91a21a5dcf23e31bfeecb70a", "widgets/utilities/platform/fileutils.py": "3ca5537cb60e43a1357e9681b7ac7792", "widgets/utilities/platform/openprocess.py": "1d61b9d4e3ad65e71005b378f25c556c", "widgets/utilities/platform/pmdebug.py": "d0ec593f50f535adc1d9c48c79ea00d7", "widgets/utilities/platform/translation.py": "c4309549c4c11558fdbc3b7491ed64f6", "widgets/utilities/platform/__init__.py": "7fd20799d245559b3722b2688b40aada", "widgets/utilities/platform/test/python_file_test.py": "0c4bff9ef948dbcc88ea0532ae0beb44", "widgets/utilities/platform/test/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "widgets/utilities/source/colorutils.py": "fde6914451c075a3e0428f2cce1d0882", "widgets/utilities/source/graphicsitemutils.py": "30395a5a9829eee910c69eca95aa649b", "widgets/utilities/source/iconutils.py": "e83075c233b7ce3290f0e37ded6fba94", "widgets/utilities/source/translation.py": "bb3c8fbd3051b640ed77d30ab718dc32", "widgets/utilities/source/__init__.py": "5b8008b3309c862942604991569b19f0", "widgets/utilities/uilogics/codechecking.py": "48560fbdbd7cf04f3461587b90a12872", "widgets/utilities/uilogics/drags.py": "857f251d9e621005afd8be2c3bf62708", "widgets/utilities/uilogics/uidisplay.py": "f4ee6b2b8695f77551bb4d7e5ff7ad39", "widgets/utilities/uilogics/undomanager.py": "df9dc910b24d09c49e9726420fdeee10", "widgets/utilities/uilogics/windowutils.py": "8372420c42f626d39d9eb193296cb900", "widgets/utilities/uilogics/__init__.py": "645e7a6c9bf8432d1c1ba5e333c362c2", "widgets/utilities/uilogics/tasks/loop_background.py": "4a363d514a49b3d142dd111f957b4a0a", "widgets/utilities/uilogics/tasks/minimal_thread.py": "94f8326dbba36d11f6e45fb79bb2697c", "widgets/utilities/uilogics/tasks/one_shot_background.py": "0075eafe0b95ef10ec9e382917867f7b", "widgets/utilities/uilogics/tasks/threads.py": "380066964983e4398279b18f1cb4c2a5", "widgets/utilities/uilogics/tasks/__init__.py": "2a83611ec96538dd91f878617b4cc882", "widgets/widgets/__init__.py": "dace0c2123dda2b5cf61bfbe954f76e4", "widgets/widgets/basic/__init__.py": "fd2926ad171e35c84c8e34ddc341a15a", "widgets/widgets/basic/browsers/browser.py": "db4472da3e421edae53084e2f60f8531", "widgets/widgets/basic/browsers/__init__.py": "f2ca69c55dd6dd321eb50715d281d85f", "widgets/widgets/basic/browsers/translations/qt_zh_CN.ts": "023045a080a9a26547219f054c272d80", "widgets/widgets/basic/buttons/__init__.py": "900cfebb524cf4f7d2a311ba7376744c", "widgets/widgets/basic/buttons/button/toolbutton.py": "078234cbde02a9528c683296768c716a", "widgets/widgets/basic/buttons/button/__init__.py": "0c609746f7319525ec3b9a95478be7e1", "widgets/widgets/basic/buttons/buttonpane/pushbuttonpane.py": "b4b83976fd0b594799dc95bc3c577433", "widgets/widgets/basic/buttons/buttonpane/__init__.py": "1e6ab09b63582d591d9c9fbb95d77d25", "widgets/widgets/basic/buttons/translations/qt_zh_CN.ts": "023045a080a9a26547219f054c272d80", "widgets/widgets/basic/containers/flowarea.py": "3465e9353838239b7a85503bdb348eb6", "widgets/widgets/basic/containers/flowlayout.py": "e7722b3b344fb1966513920657c3ab9e", "widgets/widgets/basic/containers/pmdockwidget.py": "264fd97a9b774531ee722a2c561c9eb1", "widgets/widgets/basic/containers/pmscrollarea.py": "d4474ecaf4ebe7ef1bc33646f9776c74", "widgets/widgets/basic/containers/PMTab.py": "69bc55a23c66f5aeefd57c00c54daf9f", "widgets/widgets/basic/containers/pmtoolbox.py": "aaa8b73936ef32a4c48d3c6dbb4a1edc", "widgets/widgets/basic/containers/__init__.py": "edcf475260eaebb0aababd31515f66a6", "widgets/widgets/basic/containers/translations/qt_zh_CN.ts": "023045a080a9a26547219f054c272d80", "widgets/widgets/basic/dialogs/textdialog.py": "02c8803b009663537eadcf6d1a1708c0", "widgets/widgets/basic/dialogs/__init__.py": "b05f556cffa689fce8ecf11590d5073d", "widgets/widgets/basic/images/imageview.py": "677c40cd96057c84dbb37592daef0af2", "widgets/widgets/basic/images/imageviewitem.py": "7f4c1d86ca85188970310d26c26d30a8", "widgets/widgets/basic/images/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "widgets/widgets/basic/labels/scrolllabel.py": "f6bd03763bb1f04990b20b3a1a9b1c90", "widgets/widgets/basic/labels/__init__.py": "37e333e93bd172534e76cd5e850f58f1", "widgets/widgets/basic/labels/translations/qt_zh_CN.ts": "023045a080a9a26547219f054c272d80", "widgets/widgets/basic/lists/combobasic.py": "0e7fbf8724164af3e7b8b9933df4729c", "widgets/widgets/basic/lists/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "widgets/widgets/basic/lists/translations/qt_zh_CN.ts": "023045a080a9a26547219f054c272d80", "widgets/widgets/basic/others/console.py": "5905c89de68775fd568577969e593f3a", "widgets/widgets/basic/others/ConsoleHistoryDialog.py": "2d4f0c82d296813c5a578cc906ae9424", "widgets/widgets/basic/others/ConsoleHistoryDialog.ui": "3ae43f1f52ed4f7dc414a4e522c76122", "widgets/widgets/basic/others/gauge.py": "16159230eebfc9f1be3ff0c4c5d555e1", "widgets/widgets/basic/others/instantbootconsole.py": "5633bce9595e8b8e509431e3d3ce5f09", "widgets/widgets/basic/others/processconsole.py": "adf620b42500885f8252657aefcc701e", "widgets/widgets/basic/others/Ui_ConsoleHistoryDialog.py": "22769bd93d86bf8129ca0fa03b9af12b", "widgets/widgets/basic/others/__init__.py": "fbcfcf98fbfcfb8625281748544e5194", "widgets/widgets/basic/others/source/clear.png": "d00b30caeba4e133a503c28d29484cd6", "widgets/widgets/basic/others/source/run.png": "a7ad927c1b15a7fd5a3a228991d6b778", "widgets/widgets/basic/others/source/stop.png": "db32ac51e103396c0758faa7ca39df7d", "widgets/widgets/basic/others/translations/qt_zh_CN.ts": "efbc0c2de628506b1cd2528e85110912", "widgets/widgets/basic/plots/__init__.py": "29b430ab74236a99e749c91f4a7a4d8d", "widgets/widgets/basic/plots/bars/histogram.py": "57fe8015a7abe33f76965ffb405a1ba1", "widgets/widgets/basic/plots/bars/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "widgets/widgets/basic/plots/lines/timeseries.py": "7a1877123a6d38adb06b3b9f43a6bb73", "widgets/widgets/basic/plots/lines/__init__.py": "d8f241f084bf5d4a8f4efd5b51d7e94f", "widgets/widgets/basic/plots/matplotlib/__init__.py": "2ac44857cafb53b120d44169c3c34f04", "widgets/widgets/basic/plots/matplotlib/base/pmaggplot.py": "c1882247f0027b2b962ba7a5ea3ca7c7", "widgets/widgets/basic/plots/matplotlib/base/qt5aggplot.py": "33f10a259f5fa8973c6cb863ef117285", "widgets/widgets/basic/plots/matplotlib/base/__init__.py": "f0e054fea02ba6ad51bf06d2a311e39b", "widgets/widgets/basic/plots/pyqtgraph/__init__.py": "2be8e98cb7ca94374b5168f14d00b359", "widgets/widgets/basic/plots/pyqtgraph/base/pgplot.py": "8c38a37cb08c0b54300704a0d1b0f10b", "widgets/widgets/basic/plots/pyqtgraph/base/__init__.py": "81087fce1382395cd3c04360a691bd99", "widgets/widgets/basic/plots/scatters/scatters.py": "450136b97d71bbca2ac809b4b7541a7c", "widgets/widgets/basic/plots/scatters/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "widgets/widgets/basic/plots/translations/qt_zh_CN.ts": "023045a080a9a26547219f054c272d80", "widgets/widgets/basic/quick/demo1.py": "353264297eedb94ef52af4c181c79e4e", "widgets/widgets/basic/quick/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "widgets/widgets/basic/tables/tableviews.py": "a2111346680c2af8ab00f8588191e07a", "widgets/widgets/basic/tables/tablewidgets.py": "890007b4bf1e6e2a38673614f324168d", "widgets/widgets/basic/tables/__init__.py": "84da0cb7f378bbf1ea095d6b8ccaf66d", "widgets/widgets/basic/tables/help/help.md": "803db01c2a1acf0f80d479042ecaff99", "widgets/widgets/basic/tables/translations/qt_zh_CN.ts": "147dcbf56740cccf27dc99dcec3b5b6a", "widgets/widgets/basic/texts/__init__.py": "e1c06d85ae7b8b032bef47e42e4c08f9", "widgets/widgets/basic/texts/statusreport/errroreport.py": "a7f14a40ca0be66bd12ac1084f1976f1", "widgets/widgets/basic/texts/statusreport/__init__.py": "b2b898748d695f952a3838cd8289bfbe", "widgets/widgets/basic/texts/webeditors/editor.py": "34336ae67d3efda5060edffa2982d941", "widgets/widgets/basic/texts/webeditors/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "widgets/widgets/basic/trees/filetree.py": "f2f05c9657978a587295f5a4f09ed1fb", "widgets/widgets/basic/trees/jsontree.py": "b91dc6b98e5561f0f9988051371101c6", "widgets/widgets/basic/trees/treecheck.py": "f29b4c69b3f30b99da28eb79bcf1c068", "widgets/widgets/basic/trees/varattrtree.py": "3f6d8812f23ab42247ee5185e8c8c57c", "widgets/widgets/basic/trees/__init__.py": "2dc6f31ca28c563654578e8b821f06bd", "widgets/widgets/basic/trees/translations/qt_zh_CN.ts": "14da9ce6253397bf6c0fcc1c4c004a01", "widgets/widgets/composited/buttonpanel.py": "a7323ff16ef179e89c39c3253667aafc", "widgets/widgets/composited/fastui.py": "8b604fc24975c5c7ba2de067065a363f", "widgets/widgets/composited/generalpanel.py": "b6be00c44a6e382aae0453e133e84d01", "widgets/widgets/composited/__init__.py": "d13563610fa6f243f2d973dd42a6f6df", "widgets/widgets/extended/__init__.py": "cc16561fe162128cca4c778f11263487", "widgets/widgets/extended/base/baseextendedwidget.py": "378fdef54ba10e68ed5cb276a5624c31", "widgets/widgets/extended/base/__init__.py": "d7d1b8939cf2c630235925abcb5a2ee2", "widgets/widgets/extended/checkbuttons/check.py": "d8b6cbe579ad2161bce135f2925313c4", "widgets/widgets/extended/checkbuttons/__init__.py": "a5c9c5ecdc69c4139ecd15de439b2e0b", "widgets/widgets/extended/comboboxes/combo.py": "ebf75f80630cac67f9642f5372097df4", "widgets/widgets/extended/comboboxes/variables_combo.py": "371fd0d84768be8a624dac87979e1f1b", "widgets/widgets/extended/comboboxes/__init__.py": "58edc6def91691d42263e0d79deec2c0", "widgets/widgets/extended/entries/baseentryctrl.py": "e752e2c6a708b1a435a84473bba154d8", "widgets/widgets/extended/entries/colorctrl.py": "16f70e0d829af6c89fcab387dd6f1442", "widgets/widgets/extended/entries/evalctrl.py": "de708611514272173a02f047b7e5fc68", "widgets/widgets/extended/entries/filectrl.py": "d11a9711cb319e2131464baa2a6a8d84", "widgets/widgets/extended/entries/folderctrl.py": "ff904108b13cddf3b6c7837cb60aa847", "widgets/widgets/extended/entries/funcctrl.py": "f308b1b347c7d44a4efe36b4e8a73ec3", "widgets/widgets/extended/entries/keymappingctrl.py": "2a50527522468cd00f3d7ccb99a02a7d", "widgets/widgets/extended/entries/linectrl.py": "626be1e4169342de4027f902779d9e19", "widgets/widgets/extended/entries/numctrl.py": "3739731735cc0520c4f45d1b0429297a", "widgets/widgets/extended/entries/passwordctrl.py": "85a3604a10ba51e5aaddfd269a8f1317", "widgets/widgets/extended/entries/__init__.py": "a87b64affd6a8b87f14caa030b1d7196", "widgets/widgets/extended/labels/label.py": "e12d3eaafac1b29d87b6b83cb5ca46c9", "widgets/widgets/extended/labels/__init__.py": "5d5fadd87777a0ecdf64ec91c7af5fb1", "widgets/widgets/extended/lists/listwgt.py": "52cf01737cab419a302a61a2fc22dd7d", "widgets/widgets/extended/lists/__init__.py": "fe79c30d2106faff8c630bec7e79ce37", "widgets/widgets/extended/others/multitypeparaminput.py": "f22495e2e29d29843c3888a21abd43a0", "widgets/widgets/extended/others/__init__.py": "372dfdcc25b8f846d64b77bea41535b8", "widgets/widgets/extended/others/monitors/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "widgets/widgets/extended/plots/__init__.py": "56a6f2c973e69ed27c3021de7b28e035", "widgets/widgets/extended/plots/lines/timeseries.py": "d0b20cb0ea7e4a6ccafc47d9b16e6fc9", "widgets/widgets/extended/plots/lines/__init__.py": "bc396f8083302203baaf5d23065c33ca", "widgets/widgets/extended/radiobuttons/radiobuttonctrl.py": "113268756d9f5bb930230e4f8824ec67", "widgets/widgets/extended/radiobuttons/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "widgets/widgets/extended/spins/datetime.py": "e6a555a3bf8d0a57b98d8926ec6684c7", "widgets/widgets/extended/spins/numberspin.py": "74160a8031c42d2f5ea87cc4a5be3272", "widgets/widgets/extended/spins/__init__.py": "cfa6752de3dc824468eaaec6166fb3d4", "widgets/widgets/extended/tables/rulesctrl.py": "6d2fda81132490482c0fdc5263cb5190", "widgets/widgets/extended/tables/tableshow.py": "100223049815e42adb4fd3c74753dda7", "widgets/widgets/extended/tables/__init__.py": "2945023ce1794015105e1aba6213136d", "widgets/widgets/extended/texts/htmlshow.py": "d41d8cd98f00b204e9800998ecf8427e", "widgets/widgets/extended/texts/markdownshow.py": "d41d8cd98f00b204e9800998ecf8427e", "widgets/widgets/extended/texts/__init__.py": "4ab23c644c97003b968443d8a2f4a584", "widgets/widgets/extended/trees/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "pmlocalserver/readme.md": "6e10586e731f6fdc871441974934b006", "pmlocalserver/server.py": "d29fca0da053832fcbaec4788889674c", "pyminer_comm/readme.md": "d41d8cd98f00b204e9800998ecf8427e", "pyminer_comm/__init__.py": "f6b46f7aedc9dd22d9cdaf15d1be08c2", "pyminer_comm/base/datadesc.py": "1f068e02ef48771fb3499b5b27ff058b", "pyminer_comm/base/encode_decode.py": "42188f6322e17da29d8d1cfb54ebc75a", "pyminer_comm/base/network.py": "1b0fbd15a289297b65698b2139724741", "pyminer_comm/base/sys_utils.py": "4b9c387c0926aa2bec713ba5560c68f1", "pyminer_comm/base/__init__.py": "036632e2718a53fdcd02a87492de0963", "pyminer_comm/data_client/data_client.py": "613aeac2716a0ec38b3f5db2c38080b4", "pyminer_comm/data_client/unittest_data_client.py": "cd4366d90aeca0639432273292da8efe", "pyminer_comm/data_client/__init__.py": "8159a5a2b762aa7581a92517146bd002", "pyminer_comm/pyminer_client/pm_client.py": "5e7ff88e50112aac1f639a7301404218", "pyminer_comm/pyminer_client/__init__.py": "fa7dd1f47a04d6515681315870a12899", "pyminer_comm/tests/test_communication.py": "95fa83a3686c8ea7bfc4736220e3bbdb", "resources/pyqtsource.qrc": "8a3a6b55a64fa60f8c58f49d2d7eb949", "resources/pyqtsource_rc.py": "1cdc5870d3e5107926e57d15c7fa63ea", "resources/fonts/Cascadia.ttf": "717e365c4a4c1478f8208a5ab33ee26b", "resources/fonts/CascadiaMono.ttf": "66c917f89d707ba0b41d5a1619c1e03e", "resources/fonts/Deng.ttf": "15c8b490227909f31d456ee9d11521e5", "resources/fonts/Dengb.ttf": "2d690e1656db754bc4ce63531223d007", "resources/fonts/Dengl.ttf": "1ddcd772ff1d04a2d545434b47ae4782", "resources/fonts/SourceCodePro-Bold.ttf": "458f0d7c492182d4c1b08621518689c0", "resources/fonts/SourceCodePro-BoldIt.ttf": "0cddef66936155d98aaa0d2a71d609c1", "resources/fonts/SourceCodePro-It.ttf": "df1343e44ce0fd5eb8c09ce8995966c8", "resources/fonts/SourceCodePro-Regular.ttf": "fedb9984186419a66cf725a38b6703ca", "resources/icons/logo.ico": "f1eb0f12aa600bd5638cd6fbca4b978f", "resources/icons/logo.png": "30e040dea91eb5edc95a2ab16c895324", "resources/images/bg.png": "931fe255dbcec81b94c2cd692fa1ff01", "resources/images/left.bmp": "d9daee9f12d8d45832a5f85fb79733cd", "resources/images/PyMiner\u6846\u67b6\u8bf4\u660e.jpg": "02eecc71bfee918d5a559501aa8b1334", "resources/images/splash.jpg": "d093033402635cffde8ae0d4840e46c4", "resources/images/splash_v2.png": "8de3454d0a7ea72b2ddbe2b242ebc535", "resources/images/weixin.png": "9fd2526c405766beb714515a07571c44", "resources/images/xmind.png": "9f0ef2b1b83dee369f38600bbdbf44bb", "resources/images/zhifubao.png": "2b957fd623e55d473c9c1eb81ce7ecd8", "resources/qss/Fusion.qss": "e3b35eb107311192c3d6a1b6512da0a0", "resources/qss/index.rst": "6000f5eae1ed6f0e434e0001675bf8a2", "resources/qss/Qdarkstyle.qss": "1d5438b0c29ab46529c7d38a976dbf62", "resources/qss/standard.ini": "ee661fb8f5a4a0852389e31a11c71001", "resources/qss/standard.qss": "1c1672f58d85af0e6211b6c144987974", "resources/qss/Windows.qss": "70481f2ea4c380995271f4b338da0968", "resources/qss/windowsvista.qss": "80e75b0e7ec8b7dd7a23b4bf75ab8ef4", "resources/screenshot/check_data.png": "e44471c2514f8a048df2a89a9df2e795", "resources/screenshot/code.png": "814df667bb94b7d2ae844d938ec916b7", "resources/screenshot/group.jpg": "0b3959e958742a35baeaec5044a95584", "resources/screenshot/main.png": "1b6f8a5331f0c5954a86b9024f070568", "resources/theme/default/icons/3d.svg": "61157c75fbfde955215c93e2daf93551", "resources/theme/default/icons/action.svg": "61ef2e8adb0b2bfe5928aa14b376fec3", "resources/theme/default/icons/addannotation.svg": "503b6bffffae70dfe67068cd24f5d88e", "resources/theme/default/icons/added.png": "2acf12376249833027eadb29c9e8a58f", "resources/theme/default/icons/addFeature.svg": "bcc4056d0d52f3cb44acbc81858b6099", "resources/theme/default/icons/addnode.svg": "763e098d49dec789969c25b7e69ab629", "resources/theme/default/icons/addpath.svg": "e700804c812dfe2bdc54798b11814924", "resources/theme/default/icons/add_col.svg": "eee7249c440e048cb1796d3e8d1c949b", "resources/theme/default/icons/add_row.svg": "620b902ccebc31be4d270e7124fb2e48", "resources/theme/default/icons/align_bottom.svg": "f4fb3a0c1bb022440e3489f75e69c390", "resources/theme/default/icons/align_horCenter.svg": "cef02a64b8f66064fc237ea34b41536f", "resources/theme/default/icons/align_left.svg": "2275c71a5bd808f0379098e0663c1aee", "resources/theme/default/icons/align_right.svg": "aeb304bf474f3402804c6c113d197101", "resources/theme/default/icons/align_top.svg": "621e60f54d5dcd50a28aa7aa3be3ffe4", "resources/theme/default/icons/align_verCenter.svg": "a8ea8b762c89efcbeeb78f3c3f72b4dc", "resources/theme/default/icons/allowedit.svg": "3c2b136910b2403dc0da5cd1a54fba0c", "resources/theme/default/icons/allowedit_layer.svg": "fe3079b3a271ac07bea92c298fe75452", "resources/theme/default/icons/allowsearch.svg": "1504eb4f1da9532cbb86754b39c0f410", "resources/theme/default/icons/allowsearch_layer.svg": "e126ff953c35229e30444c8d6e4ae2e8", "resources/theme/default/icons/allowselect.svg": "5b3dbfe62ddbf003af7efb4f429cb0fe", "resources/theme/default/icons/allowselect_layer.svg": "62cbf0796e8e284f4df30d1575d4c16c", "resources/theme/default/icons/allrecords.svg": "75b5c30ce3932f21ae7b16013ffc4c8c", "resources/theme/default/icons/annotation.svg": "1ae382f88b44847332e9826ba46c5397", "resources/theme/default/icons/annotation_no.svg": "c3e5a2d3179e075c49b13015eb5b7e52", "resources/theme/default/icons/anova.svg": "14b71522f19c12ffde5e46202fcf6bc3", "resources/theme/default/icons/appstore.svg": "e2a48d1643f244e957ce9dba9e55a72f", "resources/theme/default/icons/app_down.svg": "5af2c894a37156084df6218bf562c626", "resources/theme/default/icons/attributerender.svg": "216c03c70731a3d0936649486d1ac943", "resources/theme/default/icons/attributes.png": "5a9ddb1cb0d0b3e35bb8f5bec3c7c511", "resources/theme/default/icons/basicStatistic.svg": "e84a179215a06d27e8547e52620a4859", "resources/theme/default/icons/beginfly.svg": "260f78da60b0d46b64fdd9b942167066", "resources/theme/default/icons/bottom.svg": "6fbc8f02bc979600df8a6a8d24bd3ae8", "resources/theme/default/icons/camera.svg": "27ae116857da0dc4bc1bf2a45a899dd9", "resources/theme/default/icons/canshu.svg": "3dd688f4c48e9c5e32ea9ef81ac8ac86", "resources/theme/default/icons/catalogAttributeTablePageFirst.svg": "0872ecfef2107aeaafbcdf0959d1a21f", "resources/theme/default/icons/catalogAttributeTablePageLast.svg": "18d9cf57a8fa52b6fabb08565ba0177f", "resources/theme/default/icons/catalogAttributeTablePageNext.svg": "ceeedf9b8523a724ea5cd524fa733211", "resources/theme/default/icons/catalogAttributeTablePagePrevious.svg": "164f4dc04bc3e9e8c508505027d408fd", "resources/theme/default/icons/catchline.svg": "6b4ee518bf6ba27bdd6cf44188ca864c", "resources/theme/default/icons/catchpoint.svg": "8191852167c41f1408891d74f233110c", "resources/theme/default/icons/centerpointlinkage.svg": "8fe7f934cfdca995811c3170017607d1", "resources/theme/default/icons/changeAttribute.svg": "a1b9c1ff1e2f6d7dad0a3873a52ce13f", "resources/theme/default/icons/changeFeature.svg": "556de2ee6e10e39b6e369c6c7a271be3", "resources/theme/default/icons/changeGeometry.svg": "1628a5992db6891c26741e3356817473", "resources/theme/default/icons/chartStatistic.svg": "30047525eb626a274f074399334f4b30", "resources/theme/default/icons/check_update.svg": "d6e33e022ada06b9b3abf33de637a573", "resources/theme/default/icons/Classification.svg": "658e28143d9a717f0e2b951cc67111ae", "resources/theme/default/icons/clear.svg": "1d841dcc502211799d9055a87cf3c55d", "resources/theme/default/icons/close.png": "20780f1ae86a0f942f448f69e16dbd3c", "resources/theme/default/icons/close_white.png": "7b567f8cd3293ae156df417e5a21dc6b", "resources/theme/default/icons/Clustering.svg": "012392d9351d8983b29e99b42145c77b", "resources/theme/default/icons/cmd.svg": "0ea2aa79bb81ae297c97d356072745bc", "resources/theme/default/icons/column.svg": "006f3f2cbba5c9fc0fcc223883b00fc7", "resources/theme/default/icons/column_2.svg": "4a2981704a879a016c4a8dc88710b5f2", "resources/theme/default/icons/community.svg": "3cdd0bf3826dfc1ad073d7ad449bc93e", "resources/theme/default/icons/compare.svg": "ddc98ac27fe119cb0d4acbf6a04affdc", "resources/theme/default/icons/conflict.svg": "ffe7f2d515ba91b5920547c0193b6474", "resources/theme/default/icons/conflicted.png": "1f83c2acd291df5d1d10ea67930145fe", "resources/theme/default/icons/continuefly.svg": "4346811bddecadf1ff6db3d87c1447d6", "resources/theme/default/icons/copy.svg": "9ad66acb6dcb55bf923628b188ff5f64", "resources/theme/default/icons/copyElement.svg": "cbe746ec2f6abd3a4b5a020943acf0a1", "resources/theme/default/icons/csv.svg": "9867d63fe22cc88b9c47159b0fd04791", "resources/theme/default/icons/csv_gray.svg": "16c6562ded6d0f6e0ee26005b9bc4c7f", "resources/theme/default/icons/currentindex.png": "dc987f99c44a68ca57c15dd784babc24", "resources/theme/default/icons/database_config.svg": "fa799903a0c570e3a09fe7efa6d3a276", "resources/theme/default/icons/dataSourceConfig.svg": "6a3bd375f33fe348498e053f3bcae8f5", "resources/theme/default/icons/dataspecification.svg": "1e6d1fcac3f8dc14a0daffc947c4d14a", "resources/theme/default/icons/data_desc.svg": "5913822d76a61455714c15e6132eb3e6", "resources/theme/default/icons/data_desc_2.svg": "1f51941660ac732dda207d022c26ee04", "resources/theme/default/icons/data_info.svg": "9ead14ad7a1cdf63b5702c48e05e78d3", "resources/theme/default/icons/data_join.svg": "406e169ebb9b8af93d286581f59f0245", "resources/theme/default/icons/data_missing.svg": "c7f2e5a1dc938edae131a30c6b56b711", "resources/theme/default/icons/data_partition.svg": "d8cbc4400259d84850ec97154dacd688", "resources/theme/default/icons/data_role.svg": "5002bd4ed773126cf2d4c34c4bb4541d", "resources/theme/default/icons/debug.svg": "e2ca8b334c69f413637945ebe871d696", "resources/theme/default/icons/debug_red.svg": "ab77c95b9f382e8f6fdb09da83795dfc", "resources/theme/default/icons/deleted.png": "99073a344eb6184343073b14eb3b4338", "resources/theme/default/icons/deletedata.svg": "0809bd41dd7489df3a1dc2a8e976f888", "resources/theme/default/icons/deleteFeature.svg": "5acc1452971d56857d219e9906341904", "resources/theme/default/icons/delete_col.svg": "c64463979c0dabb232a424810a41fad0", "resources/theme/default/icons/delete_row.svg": "28c35f8dae78bb25a1d8b4981996ec5d", "resources/theme/default/icons/dependencies.svg": "150af39be2e05f96916e16c20f7eacbc", "resources/theme/default/icons/diagram.svg": "93a8d692b994662c68e9374ca42fbfe0", "resources/theme/default/icons/disallowedit.svg": "1d2ea40eb876a1b57edfab5937150a23", "resources/theme/default/icons/disallowsearch.svg": "48d814b63bb64b53e1bbe5a745ad2efa", "resources/theme/default/icons/disallowselect.svg": "780b169700eec6b08dc575e3a0d01275", "resources/theme/default/icons/display.svg": "a370d879c046105fa360f2bee6809a10", "resources/theme/default/icons/distribution.svg": "53e6bcf151c4cb9f89f80093c256ea62", "resources/theme/default/icons/donate.svg": "f72f24a7e8db14c7c98394d6c8dfc860", "resources/theme/default/icons/down.png": "303cd2548ab3be7109c7fbab16730725", "resources/theme/default/icons/downWard.svg": "0bf14903bcbdef20890f936ac7855c61", "resources/theme/default/icons/drawline3d.svg": "64fc6cdf5c361bb7a6f2b5c7dadc3934", "resources/theme/default/icons/drawpoint3d.svg": "452946e331372a97bdd09df9f5f86a71", "resources/theme/default/icons/drawpolygon.svg": "e54865783b8e639d6b8aef7f0eb0597f", "resources/theme/default/icons/drawpolygon3d.svg": "4dd900f92c31b8254c18371b72532011", "resources/theme/default/icons/duoyuan.svg": "5d84f2bcfc84ae85a6fb1a7a5ee08b28", "resources/theme/default/icons/E-matlab.svg": "11fb165a87912c326ba2a5f27d4971b8", "resources/theme/default/icons/earth3d.svg": "2bbc786eaadf43a74a592ec82b14ba03", "resources/theme/default/icons/editannotation.svg": "e73609dbea16a474b62e07344a34264d", "resources/theme/default/icons/editConfig.svg": "2e65b8ba1573593af29ee2fcf1a54288", "resources/theme/default/icons/editmetadata.svg": "c89258a047ac04855464e0a2cb883f8e", "resources/theme/default/icons/endadding.svg": "ba25cd175a3a98a0d2c7b81f125975df", "resources/theme/default/icons/endfly.svg": "8aa793c8cc77046cdfd7d70307c83731", "resources/theme/default/icons/errorInfo.svg": "b8d8db18986b89bf3b1b980fa613462b", "resources/theme/default/icons/excel(1).svg": "9eb4ba787a3e62e477772f0c4a93f366", "resources/theme/default/icons/excel.svg": "ea00b0b78bb93119c7eafe98a79a8988", "resources/theme/default/icons/ExcelFile.png": "c488f98763fb577be56bc955c928c947", "resources/theme/default/icons/excel_gray.svg": "a1135950f1896aa177e00eb10f44f910", "resources/theme/default/icons/expendDown.svg": "fe6b296aa4020dda5c5a870bb7596235", "resources/theme/default/icons/expire.svg": "b7055d451d7bf23ddffcd974ceb39225", "resources/theme/default/icons/fastCollect.svg": "b929bc85fd17ba612275008238d70e53", "resources/theme/default/icons/favorites.svg": "589f0e80dde61b86c2470b3799f8f860", "resources/theme/default/icons/feedback.svg": "f695df9b7313ab60be2c3c46b4fc7468", "resources/theme/default/icons/field.svg": "d168f3a212323820267956d6de9ca9d7", "resources/theme/default/icons/file.svg": "58f428cb524544f4f3993bb37f2ef1ba", "resources/theme/default/icons/file_gray.svg": "fe90e95c15020d95c8f5690f3cb5fae3", "resources/theme/default/icons/filter.svg": "e87a00609c494f030812f3c00a2e6e35", "resources/theme/default/icons/final.png": "87457d3035ab89d82a6b57976e9b0245", "resources/theme/default/icons/find_replace.svg": "665177ccc10ce22b469a118beec97dd6", "resources/theme/default/icons/first.png": "f0edce38b01be51e2dfea42df74d8f23", "resources/theme/default/icons/flight.svg": "8857e62ab11e2cd3ae039db12d61b0d4", "resources/theme/default/icons/float.png": "5f31043d787d15eab504b59bef221e6d", "resources/theme/default/icons/float_white.png": "89609a354d4d717f4e578528dfd5547c", "resources/theme/default/icons/fly.svg": "9761c5cef3b2a4a59ef4ed762c62ad7a", "resources/theme/default/icons/flyaround.svg": "c8a2ba65a3a3ea28c710727327b062b6", "resources/theme/default/icons/folder.svg": "b7e11fde0a4f073c8255d911214aecd3", "resources/theme/default/icons/folder_yellow.svg": "9d3d6d38fa617dfeedec734a5c5c13e1", "resources/theme/default/icons/foundrecords.svg": "aeb3afe2862d00fdeaf810cc479a9704", "resources/theme/default/icons/general.svg": "8d0ffe48967c6985486949410f399c74", "resources/theme/default/icons/generalConfig.svg": "ce54c065114cc47016cd6a382457c1b8", "resources/theme/default/icons/GeneratingAdministrativeRegion.svg": "444cf5e4f7daa640706014ae3f55e307", "resources/theme/default/icons/geodbms.ico": "fef8bfe0713f757762dd9ce837b48c1c", "resources/theme/default/icons/geomap.ico": "dd4b17c0ec69f51b039c51b6e0887c31", "resources/theme/default/icons/gotoview.svg": "da5e78b70c58d112189832fd5cdad9d9", "resources/theme/default/icons/help.svg": "6c9e88b86a30ea8df99ecb75891990d2", "resources/theme/default/icons/help_doc.svg": "1491066f6ba737fb319d4b3aeec96402", "resources/theme/default/icons/histogram.png": "334c464f233edaf5d54c0e1a78289559", "resources/theme/default/icons/home_site.svg": "a9fdb97167551132fe16dd05e7668643", "resources/theme/default/icons/html.ico": "ef2d86df09c9fc6d671e12b5deaf49e6", "resources/theme/default/icons/html.svg": "8158b710d8854547cfe6e2ece1ab3e22", "resources/theme/default/icons/import.svg": "e38ca75a6ffcf334ea461b245f523444", "resources/theme/default/icons/importConfig.svg": "f60db217ccfce27088257eeb9b39d236", "resources/theme/default/icons/import_database.svg": "42ae4560410eb3b900a99360d0019fba", "resources/theme/default/icons/indent_left.svg": "13d4c1ee641fc364bf20be31e9c277a7", "resources/theme/default/icons/indent_right.svg": "a9bc26d41494a2760b186614cc5a177d", "resources/theme/default/icons/index.svg": "63797de724460b5527354bcc9df5d79d", "resources/theme/default/icons/info.svg": "59366b0c10bbce5c3dd9dfec1860d32c", "resources/theme/default/icons/install.svg": "699200cfd59967ff740f1e3076795f0f", "resources/theme/default/icons/invisibleMap.svg": "f5e5e045de80b10006eca992d6f156c1", "resources/theme/default/icons/javascript.ico": "237b59ab21d39cbf9d56481abd6ea724", "resources/theme/default/icons/jiashe.svg": "6195487f581d0b17bf8bd5e440ac3152", "resources/theme/default/icons/join.png": "ae8569cf1fc1762af272861ffbf1a16f", "resources/theme/default/icons/JoinMapTable.svg": "c7277790649b9dafcba2531c8ec43630", "resources/theme/default/icons/jump_line.svg": "36d10e5f905da4134c668d1baa7b0eff", "resources/theme/default/icons/jupyter.ico": "fcd12a895e826c9bf68ec69a3801a6b5", "resources/theme/default/icons/Jupyter.svg": "5e31f82bc08dff94a0f7a19492ae3e57", "resources/theme/default/icons/lab.svg": "bb81cefd8b4592063bf604536517bbe4", "resources/theme/default/icons/labelingMultiple.svg": "60b47ac95c42db86f0a1996619aa5710", "resources/theme/default/icons/labelingNone.svg": "aec873e7c3a156d25d90ba14332250fe", "resources/theme/default/icons/labelingSingle.svg": "4eb6031628115e94af14c75ed7743277", "resources/theme/default/icons/labels.svg": "2f07179dee5125cdc7720364cc83fa4d", "resources/theme/default/icons/LandSurveyAutoNumber.svg": "d4fa015fe319301146a20d569319d197", "resources/theme/default/icons/layerBrush.svg": "30f0e853f0588bb65f58b6e407f26e73", "resources/theme/default/icons/layerConfig.svg": "ca61ff1339a5aab82167c3b3834f6fc8", "resources/theme/default/icons/legend.svg": "d34ddf6094becbbd0c65321a1f154bfd", "resources/theme/default/icons/lockorthoview.svg": "39417a4ce7986b55c908a36268185ca3", "resources/theme/default/icons/lockview.svg": "89f5b906db5c12867af42e06ccb1d18e", "resources/theme/default/icons/lost.png": "e5f28a07326ba62314e4964833d853c8", "resources/theme/default/icons/mActionAbout.png": "1b50df16c376e6e78454236403627aeb", "resources/theme/default/icons/mActionAbout.svg": "1e1360e32f95eee26b8f41e69339cab8", "resources/theme/default/icons/mActionActiveStyle.svg": "302386637bb5cd7bf856ad1ce6ea42fe", "resources/theme/default/icons/mActionAdd.svg": "340f3bb6a7682527aae76150aabff11b", "resources/theme/default/icons/mActionAddArrow.png": "95e75cb331f8ff2ae412b474db6f55bb", "resources/theme/default/icons/mActionAddArrow.svg": "06246d25e27308ad26aac55af7eb9713", "resources/theme/default/icons/mActionAddBasicCircle.svg": "f8a5e280035bbfaa025028e4eed1c703", "resources/theme/default/icons/mActionAddBasicRectangle.svg": "85605dbd86923fbed31379f828d995bc", "resources/theme/default/icons/mActionAddClassification.svg": "5d91e7c199fdfc94b71882b743e084f8", "resources/theme/default/icons/mActionAddClassificationCode.svg": "71811be70f15711a0890b9f6a407dab5", "resources/theme/default/icons/mActionAddCustom.svg": "cc118673a1e3b2e74fc57da54a9aa35e", "resources/theme/default/icons/mActionAddDataSet.svg": "e15df2fe98dfcddd91f6edc44b17f719", "resources/theme/default/icons/mActionAddDataTable.svg": "3944027676b922267a24159091390090", "resources/theme/default/icons/mActionAddDicItem.svg": "d71bb80327c14ba5e06e965b15864b48", "resources/theme/default/icons/mActionAddDirectory.svg": "f4a64c10d63eb102b6088281748dedf5", "resources/theme/default/icons/mActionAddEnumRange.svg": "9969caf289186d9cd546400aa5f16079", "resources/theme/default/icons/mActionAddGroup.svg": "f308932c912ab0483324f89d5ecce6c0", "resources/theme/default/icons/mActionAddImage.png": "94e43a1d6b956c2b35625a5ea760261e", "resources/theme/default/icons/mActionAddImage.svg": "28bb04fd672efe9f1a52f2901f54de6f", "resources/theme/default/icons/mActionAddLayer.svg": "840395598b646deb2c747f78d305e324", "resources/theme/default/icons/mActionAddLegend.png": "8349d32174a82160f1c812641855fbb9", "resources/theme/default/icons/mActionAddLegend.svg": "d756c3c84d9a1e613341676f8c3e8965", "resources/theme/default/icons/mActionAddMultiPoint.svg": "f9ade46e1458d5942ae557420592f836", "resources/theme/default/icons/mactionaddordergroup.svg": "a7e40299ca8607fd8ec9573f66cb95a5", "resources/theme/default/icons/mActionAddPoint.svg": "c85722428ece40150082f3d81bf170f3", "resources/theme/default/icons/mActionAddPolygon.svg": "83662dc01e024450dce5ecfaf1514edc", "resources/theme/default/icons/mActionAddPolyline.svg": "5d61a265d4c1e899cd171721d3b006ce", "resources/theme/default/icons/mActionAddRangeRange.svg": "99b93618e2906303244bc6a2ed20d3e0", "resources/theme/default/icons/mActionAddResourceWizard.svg": "9318a164ee340e04da837175aeea8820", "resources/theme/default/icons/mActionAddScaleBar.svg": "43afed23afb8200c15a543df1a450f3c", "resources/theme/default/icons/mActionAddSchemeData.svg": "c86e2f3e2356fd1ad82cdda941a9ad7b", "resources/theme/default/icons/mActionAddStyle.svg": "c60df25cbed4067a5ac0bc320463b816", "resources/theme/default/icons/mActionAddText.svg": "b6e185e764c5da36528cd7a83654d17d", "resources/theme/default/icons/mActionAddToCanvas.svg": "4f222a6648efa565f051aa2617caca5e", "resources/theme/default/icons/mActionAddVertexTool.svg": "73d0e0aa645810d8737db7604fb48d98", "resources/theme/default/icons/mActionAdjustLayers.svg": "42d197117a2af9818fcbefc935e03fa2", "resources/theme/default/icons/mActionAggregateNode.svg": "0e8e850ce17baa5e7c7aa3b238279790", "resources/theme/default/icons/mActionAligningToLine.svg": "6844c8fc2e7d023ebd31bfb23c907a9e", "resources/theme/default/icons/mActionAllEdits.svg": "1a13a6fe0c38adcdbbc315b963805233", "resources/theme/default/icons/mActionAnnotationImport.svg": "ed08c23f1bb2ea1dc03aee1496693ec6", "resources/theme/default/icons/mActionAnyDLAnalyze.svg": "cbcf45db82108f1e0ec97691d2b021ed", "resources/theme/default/icons/mActionAttributeBatchTool.svg": "f8853792345634f2ccbf3d5b3cc2ae9f", "resources/theme/default/icons/mActionAttributeBrush.svg": "bc8e8e7de8a8b5c4d75891072f58a9c9", "resources/theme/default/icons/mActionAttributeIndexManager.svg": "de8c1f6a81ef7a0dc047131f2b3bccca", "resources/theme/default/icons/mActionAttributeSelect.svg": "0a0e325e465cfcd8eb9a502e4fc9aac2", "resources/theme/default/icons/mActionAutoChange.svg": "2add0a98ec45eb1f992348a61b77107c", "resources/theme/default/icons/mActionAutoCutPolygon.svg": "5142adcd7b3d1702cc05eb4732bda7a5", "resources/theme/default/icons/mActionAutomaticClosure.svg": "100464d989b5520597726da001d2e6b7", "resources/theme/default/icons/mActionAutoParallelPolygon.svg": "d301512aec2983ee3fa88426f2c0583d", "resources/theme/default/icons/mActionAutoParallelPolyline.svg": "419b2e6153752735a075f87b2c6976af", "resources/theme/default/icons/mActionAutoProjection.svg": "1cfe66813280c607e1afc8715fa98983", "resources/theme/default/icons/mActionBackLastLevel.png": "0e06243d91122f7a759af354689e786c", "resources/theme/default/icons/mActionBackupDatabase.svg": "ab331f009a206badb5b6eb214ad0e555", "resources/theme/default/icons/mActionbatcgSetUniqueCode.svg": "3d5f12cd66429d20dc6b16d2e4af6a27", "resources/theme/default/icons/mActionBorderPolygon.svg": "70dd6b1cafe2eb371c897c2509ec0406", "resources/theme/default/icons/mActionBreakBySinglePoint.svg": "a4f86b74ab5d94b4bf90c8678155c8de", "resources/theme/default/icons/mActionBreakByTwoPoints.svg": "9faa1aeaf46c1a8a5f1a39a8f077293d", "resources/theme/default/icons/mActionBreakIntersectantPolyline.svg": "5891ffbba64c08cf4db8760d782465b1", "resources/theme/default/icons/mActionBreakWeld.svg": "f7f43dad61d9c4e3322eca488da3ab92", "resources/theme/default/icons/mActionBrush.svg": "e66b98adb06f1d8d702edb7f9ec5546f", "resources/theme/default/icons/mActionBufferAnalysis.svg": "d80ab987bd4a1fc371e2776e66425406", "resources/theme/default/icons/mActionCalculateField.svg": "f32837750c5840f154a2a749c8aade44", "resources/theme/default/icons/mActionCatalogManager.svg": "99e161a7b7367a9fa2929009134a653a", "resources/theme/default/icons/mActionChangePolylineByExitline.svg": "e08d8787b31f2c90f7378b382311f23a", "resources/theme/default/icons/mActionCheckAll.svg": "438f359d854c7ebedf6be6b88f7d0f6c", "resources/theme/default/icons/mActionCheckAndMaintain.svg": "a7e9601b46669c45f9096f011ab41bd1", "resources/theme/default/icons/mActionCheckNode.svg": "b14495714a6c980374bb8482d3211444", "resources/theme/default/icons/mActionCheckResult.svg": "2cf1b01c206622535c1e1825eb2b69cf", "resources/theme/default/icons/mActionCircularStringCurvePoint.svg": "310cea9398a663a4d4b13e528fa7ce5d", "resources/theme/default/icons/mActionClassification.svg": "995a538a5a6002821d6c50dc9a81fe63", "resources/theme/default/icons/mActionClearEdit.svg": "1c5a1c68aac83d68129868577ea30f7e", "resources/theme/default/icons/mActionClearLayer.svg": "32eba1e6735860eb142fff43a48eed54", "resources/theme/default/icons/mActionClearSelect.svg": "acc58e9be78e0153b7d7d1ed921b6230", "resources/theme/default/icons/mActionClose.svg": "e85dae4f037b08290dd34d668bcf06b3", "resources/theme/default/icons/mActionCollapse.svg": "3d2eba0c9ca35b25ec706a3e970a8b44", "resources/theme/default/icons/mActionCollapseTree.svg": "b39d593ea4774c1238ed6cd69f12b11f", "resources/theme/default/icons/mActionCommit.svg": "f354d7a233e4a5923580acbe25264d1f", "resources/theme/default/icons/mActionCommonNode.svg": "f2ab046d5257ee17f76363de01472e36", "resources/theme/default/icons/mActionComposeExport.svg": "53d0f2eaa80a97421ea812d32f5d9670", "resources/theme/default/icons/mActionConfigProperties.svg": "aa6aa2c381d8636a19e815a347831ed2", "resources/theme/default/icons/mActionConnectToFolder.png": "84a044edac968fdf06ecc30455f9b57d", "resources/theme/default/icons/mActionCopyMapImage.svg": "18260fca2fda975633899e07772ec225", "resources/theme/default/icons/mActionCreateDbConnection.png": "1194f60c3604a75a234b7c5829a9f623", "resources/theme/default/icons/mActionCreateInterNode.svg": "7d8cbee95202587a4593044f752438d2", "resources/theme/default/icons/mActionCreatePolygonBySnap.svg": "afd2ee4841eb4162f6d40825768359ee", "resources/theme/default/icons/mActionCreatePolylineBySnap.svg": "e80bb5083740e9bbf248b9ba3b28c988", "resources/theme/default/icons/mActionCreateProject.svg": "0d1f6ed64f39f7f3e3cacaeb376ac952", "resources/theme/default/icons/mActionCreateSpatialIndex.svg": "19063ef92e585e6b89139b95d8bc4ebc", "resources/theme/default/icons/mActionCurrentTask.svg": "37591573decfe551c7cb7418e2e8acad", "resources/theme/default/icons/mActionCustom.svg": "4f20d0450747420cbd2e9c9269193bee", "resources/theme/default/icons/mActionDatabase.svg": "5356e364576af2c38e4216283342db4f", "resources/theme/default/icons/mActionDataComparisons.svg": "05fd271af6c134f6872c80c489064d60", "resources/theme/default/icons/mActionDataExport.svg": "a4adf859430ab231122e24d6d6140459", "resources/theme/default/icons/mActionDataset.svg": "a06eea07e31055fe638120092fd03fed", "resources/theme/default/icons/mActionDataSource.svg": "899c06c28703ea8b15798c799a97cb02", "resources/theme/default/icons/mActionDataSourceManager.svg": "e265e51dc79a614afbba2d9a03eca71c", "resources/theme/default/icons/mActionDeleteAttribute.svg": "f487f09dcfad1b19da3cd46ff020c8aa", "resources/theme/default/icons/mActionDeleteBookmark.svg": "63a5159656718a85300368e95a87c965", "resources/theme/default/icons/mActionDeleteClassification.svg": "328e502956af43b1506928b8e03fc496", "resources/theme/default/icons/mActionDeleteClassificationCode.svg": "7cb7a62ac5e8be4028316c1c3e8836bc", "resources/theme/default/icons/mActionDeleteCustom.svg": "2c3e544e2be400c9f1fc224eb19baa6c", "resources/theme/default/icons/mActionDeleteDataSet.svg": "cf05da90c76989fe5796f0bfa9c69fd6", "resources/theme/default/icons/mActionDeleteDataSpecification.svg": "f4e03936c7564d14d752c7b9570726df", "resources/theme/default/icons/mActionDeleteDataTable.svg": "79ff705674f36ee519327ae6c51ae3d8", "resources/theme/default/icons/mActionDeleteDicItem.svg": "40f4b24ebc5ee91d2a05b715dc5ef94e", "resources/theme/default/icons/mActionDeleteLayer.svg": "10c565c23727bc79f0a3b3653c539e27", "resources/theme/default/icons/mActionDeleteLink.svg": "2d21c91ca791aed38b8135cf405fada5", "resources/theme/default/icons/mActionDeleteRange.svg": "f26af8d0a9917bb3d33bdbdd7665f2b9", "resources/theme/default/icons/mActionDeleteRevision.svg": "7f32ea7fb09ffa5851f57a64b548cf76", "resources/theme/default/icons/mActionDeleteSelected.svg": "27cc93f001805b310dd17a6cd761417f", "resources/theme/default/icons/mActionDeleteStyle.svg": "e8529586cd6c6c35725ae9ddf42678f8", "resources/theme/default/icons/mActionDeselectAll.svg": "acc58e9be78e0153b7d7d1ed921b6230", "resources/theme/default/icons/mActionDiagramStatistic.svg": "ba795b1a3c88b9ecc48bf3533793084b", "resources/theme/default/icons/mActionDicManage.svg": "5711827d34bf8c7f45bb9473339c9d5d", "resources/theme/default/icons/mActionDictionary.svg": "9f3227fd9fd4856b21ed0203f57d462e", "resources/theme/default/icons/mActionDictionaryManager.svg": "31af9da87c0ef66edf88279dc0979691", "resources/theme/default/icons/mActionDirectImport.svg": "2705f7d2b595f44bf7d41ba5469f8ecf", "resources/theme/default/icons/mActionDirectory.svg": "543010d3f4891a87337ea4a0553b6cc4", "resources/theme/default/icons/mActionDisperseLegends.svg": "44e0028546a1d892193bf85c1fc9ee68", "resources/theme/default/icons/mActionDownloadData.svg": "73cf487f43c6c67708755c77d1031b16", "resources/theme/default/icons/mActionDraw.svg": "79d6fde375c1d3a7c8be1e8ec5fc02d2", "resources/theme/default/icons/mActionDrawAnnotation.svg": "1ead0a1082e9fbae833bcfdd520f1f6f", "resources/theme/default/icons/mActionDropSpatialIndex.svg": "b22f1baf81a17fdc104b1bf05fd5c5ca", "resources/theme/default/icons/mActionEdgeTool.svg": "e51484e30111c77e3f34a0e367340041", "resources/theme/default/icons/mActionEditConnection.svg": "b0f644dd694a0c6554d1e1dcac139845", "resources/theme/default/icons/mActionEditCopy.svg": "83c8c2506e3d53b1b0b7865d1873a633", "resources/theme/default/icons/mActionEditCut.svg": "23ce1b2d297515aefe2dea9533184fab", "resources/theme/default/icons/mActionEditPaste.svg": "e59391b2630274e267ad10bb0a40a30d", "resources/theme/default/icons/mActionEditPolyline.svg": "4a3413a2c400b402733577b00c4e4a57", "resources/theme/default/icons/mActionEditSelect.svg": "20ed9f0380e99e271ccc04bf8590d084", "resources/theme/default/icons/mActionElementAlignment.svg": "da6e0b52d636403003a8bf06d81426bd", "resources/theme/default/icons/mActionElementOrder.svg": "9a4846b8d8c64c5a6b839fb5f5f2acab", "resources/theme/default/icons/mActionEmpty.svg": "7d03c7a62ba3b69a498063a52e7a0480", "resources/theme/default/icons/mActionExpandAll.svg": "5ce3b9872728fe1ab2db08881b6696e0", "resources/theme/default/icons/mActionExpandTree.svg": "8df56726928510b542d479f9d65bf2a4", "resources/theme/default/icons/mActionExport.svg": "9262fbccd6dce910b05542130d5b5d3d", "resources/theme/default/icons/mActionExportFont.svg": "fc19d534b16e7e87d74304dc9206913d", "resources/theme/default/icons/mActionExportGeometry.svg": "490f60da490788568dfcc1fb3ab8d57a", "resources/theme/default/icons/mActionExportPDF.svg": "7079bf9f8c4d4ef7ebf3fc26895c651a", "resources/theme/default/icons/mActionExportStyle.svg": "c3ebc4b4d9eef274c460e40db01734e1", "resources/theme/default/icons/mActionExtensionIntersect.svg": "b2b45155b3775a6082f0432db8667476", "resources/theme/default/icons/mActionExtensionPolyline.svg": "e5fdd4473d3a496bd3a0e67dfafc9902", "resources/theme/default/icons/mActionExtractFeatures.svg": "d756e45f408817a284947e1dc2015564", "resources/theme/default/icons/mActionFastScan.svg": "4a055d8089b712ba73b61939c3248f14", "resources/theme/default/icons/mActionFeatureClass.svg": "12960e7e5a7abafae89f6c75fe250172", "resources/theme/default/icons/mActionFeatureImport.svg": "bc1bf048cb86d53ee263523e8d49a33e", "resources/theme/default/icons/mActionFieldAssignment.svg": "55face7af26423b494df7490d9c9a1ef", "resources/theme/default/icons/mActionFileExit.svg": "014da3686c1d21b0df59f2b6d70e5a69", "resources/theme/default/icons/mActionFileNew.svg": "359e36e8c6e7793d550a4050765a169d", "resources/theme/default/icons/mActionFileOpen.svg": "d80181aaa8aa890cb0b7e48319a8d085", "resources/theme/default/icons/mActionFileOpen_small.svg": "e2471acb5d431a1203fe3c3956a4db79", "resources/theme/default/icons/mActionFileSave.svg": "68f08f30bdd326df72b092be223a0d70", "resources/theme/default/icons/mActionFileSaveAs.svg": "9836c3f4771b653b1ee967cae793b742", "resources/theme/default/icons/mActionFillHoll.svg": "7ef3bcc1919ef6f9f132ece12e007827", "resources/theme/default/icons/mActionFilter2.svg": "3ec913bd01c74a188cb4c055fbc03c14", "resources/theme/default/icons/mActionFlash.svg": "9bfe496e74b6e9a43be5908d499af2be", "resources/theme/default/icons/mActionFoldAll.svg": "740369ae0104d790e9d5fd18edd3f159", "resources/theme/default/icons/mActionFolder.svg": "1d4ea7410800af4f5b979135c4d14529", "resources/theme/default/icons/mActionFormView.svg": "f7e61b110fffd58a3803cba75c95aa40", "resources/theme/default/icons/mActionFull.svg": "de410eb887d59537eb5a97991fc1eebd", "resources/theme/default/icons/mActionGJB50000FFT.svg": "26fd67a57ea8dbbf034212047cd73c2e", "resources/theme/default/icons/mActionGoto.svg": "4cc2023f7b3f7813af02c1bcaf11ef9d", "resources/theme/default/icons/mActionGrid.svg": "305cb21216f9e8638e5521ba83f54ff2", "resources/theme/default/icons/mActionGridCheck.svg": "5cd471cd29ff479f87c6aac170c2c117", "resources/theme/default/icons/mActionGroup.svg": "30e60e1533289e2052cf4928583757c8", "resources/theme/default/icons/mActionGroupLayer.svg": "71a8905d793790d9303f2d32815c9079", "resources/theme/default/icons/mActionGroupLine.svg": "a3f83d7e3afba41c961550d99ef97136", "resources/theme/default/icons/mActionHelpContents.svg": "dfcc6f3e7a95c9fa377a24306877dda6", "resources/theme/default/icons/mActionHideAllLayers.svg": "16f373cad1b7b644477951a7ed66351b", "resources/theme/default/icons/mActionHidenFromBrowser.svg": "d38d6063e070d2d61dd6f55713896ab9", "resources/theme/default/icons/mActionHideResults.svg": "3eaa5c01dc7b1bacd0a6e5db58d1fdeb", "resources/theme/default/icons/mActionHideSelectedLayers.svg": "9d5f022d1375227b18a4fe01c6bcdb30", "resources/theme/default/icons/mActionIdentify.svg": "752e8589c7c43e261247a04eebfd130d", "resources/theme/default/icons/mActionImport.svg": "eccb61c61f90c7dd5be6f82059c499c1", "resources/theme/default/icons/mActionImportGeometry.svg": "18642deca79e43f652442d195b59fca3", "resources/theme/default/icons/mActionInspect.svg": "4f20d0450747420cbd2e9c9269193bee", "resources/theme/default/icons/mActionInverseCheck.svg": "9115eb0e5c06d728cf83af898f4a3f79", "resources/theme/default/icons/mActionInvertSelect.svg": "8f2be589133713a5d3ff36e50c1f01c0", "resources/theme/default/icons/mActionInvertSelectedLayers.svg": "9752a3091fe12e8fa6c6c07a53eb96b4", "resources/theme/default/icons/mActionInvertSelection.svg": "6149896f7ea1dbdade1677b31fcf5016", "resources/theme/default/icons/mActionLabelManager.svg": "d5d5d38222a6f64eca412317a25f962f", "resources/theme/default/icons/mActionLayer.svg": "f156bfe69e1e2508e5f0bf15db9ba44e", "resources/theme/default/icons/mActionLayerClassification.svg": "6aec85cd21332ccce4de83dcbc78a0c0", "resources/theme/default/icons/mActionLayerManager.svg": "add1fed077998d1e15ea21b0476024a9", "resources/theme/default/icons/mActionLayerSaveAs.svg": "baf726688146979e6d52548de7214a3d", "resources/theme/default/icons/mActionLayerSaveAsFile.svg": "b17ac7d94a0994f542a783cf8fb0d33b", "resources/theme/default/icons/mActionLayersOverview.svg": "4813c1d18f037f63dae432d4ef253cfe", "resources/theme/default/icons/mActionLayerTreeView.svg": "be643367e7ffd5de10d0dcb108e4c3a1", "resources/theme/default/icons/mActionLayout.svg": "28f9190ac68a00b0d7f6e04170d31cee", "resources/theme/default/icons/mActionLayoutManager.svg": "31be4d584dfc1ccafd8d01cdcd164fc8", "resources/theme/default/icons/mActionlinkage.svg": "b3ce4171038f9d82c8d04ea9cb3ad2d0", "resources/theme/default/icons/mActionLoadData.svg": "684e514f1cff1d2c9e95dd55a858df9f", "resources/theme/default/icons/mActionLoadLayerFile.svg": "f305a0ff3392689c805ce7984044289c", "resources/theme/default/icons/mActionLoadProjects.svg": "65d677b31784876d2af6e7c1fbf57b8a", "resources/theme/default/icons/mActionLoadRevision.svg": "874fed75579a074e530d87b91ad2ff07", "resources/theme/default/icons/mActionLocate.svg": "52f9968f5e0a6392c5f0a58db02b0825", "resources/theme/default/icons/mActionLock.svg": "26131931765f2aec7cc4bd0443f8f2ac", "resources/theme/default/icons/mActionLog.svg": "155acdfdf0f50cc1488483bb2eb844c1", "resources/theme/default/icons/mActionMaintain.svg": "ba47b24e4af73b927909cc5348f7bec8", "resources/theme/default/icons/mActionMaintainBSM.svg": "7acf0cf2e793fb29050771698c22b2e3", "resources/theme/default/icons/mActionMapsheetManage.svg": "1fd72e8c7b4d1b68cdfc45b6a1587ffc", "resources/theme/default/icons/mActionMapsheetNode.svg": "45219d3849e361f574ca86fcda80b3a1", "resources/theme/default/icons/mActionMapStyleManage.svg": "cf9354021a4a5b26ad660525ece1edff", "resources/theme/default/icons/mActionMeasure.svg": "a62f37b1ec481f1c2bab829f83849396", "resources/theme/default/icons/mActionMeasureAngle.svg": "5edcf784a0e8807d14944cce68a1a014", "resources/theme/default/icons/mActionMeasureArea.svg": "3ccde2a2802aac9ed7677d5c92bb3a0c", "resources/theme/default/icons/mActionMergeFeatures.svg": "58fca120fbe1d8ed6fd3a2dee1f90422", "resources/theme/default/icons/mActionMiddleLine.svg": "30f0034bc7c043e397e3f2ddc0ffc345", "resources/theme/default/icons/mActionMirrorTool.svg": "786dda30a600b3326b3ae42402dc443e", "resources/theme/default/icons/mActionModifyElements.svg": "59d8bbab970228ddbf2537c23da41ed3", "resources/theme/default/icons/mActionMosaic.svg": "34c034a1e13abe0d007225fbded76d7a", "resources/theme/default/icons/mActionMoveDown.svg": "e69d25c8b849110e43023f18f68c0bcd", "resources/theme/default/icons/mActionMoveElementBottom.svg": "5c963ec211036b7012aaab490bcb3741", "resources/theme/default/icons/mActionMoveElementDown.svg": "ed84988b86b920d732c1cc2c922e52a8", "resources/theme/default/icons/mActionMoveElementTop.svg": "8d5251c95c7b8b51c06719aed5991511", "resources/theme/default/icons/mActionMoveElementUp.svg": "0f2bae29c5c6a245ef7a1ef79d01d970", "resources/theme/default/icons/mActionMoveFeature.svg": "46a3b71b7e3282a07bcc8fe11a20916f", "resources/theme/default/icons/mActionMoveUp.svg": "c09793cc8b982dfc7160ca61174aa3af", "resources/theme/default/icons/mActionMoveVertexTool.svg": "fd8cdb5ff4b54efded668628786520da", "resources/theme/default/icons/mActionMultiEdit.svg": "1b4e305d58dbf3793eab33d6b474c28a", "resources/theme/default/icons/mActionNetworkStorage.svg": "e62bba269bce3c8ea8c4cf96e11bdd7c", "resources/theme/default/icons/mActionNew.svg": "c04a77f3fadc8f682577a4a1ffa31ea6", "resources/theme/default/icons/mActionNewAttribute.svg": "286fff33a3eb371889d740ae643cf9c4", "resources/theme/default/icons/mActionNewBookmark.svg": "5b5885cbdcefd11ac0e9d54461cfd914", "resources/theme/default/icons/mActionNewConn.svg": "4113d50441e7461c420e93a0b59d9bbf", "resources/theme/default/icons/mActionNewDataSpecification.svg": "c9c120156d64453162f017b2a4abbee7", "resources/theme/default/icons/mActionNewFileGdb.svg": "f5721d9a6558d415f3a67c63b78a95cc", "resources/theme/default/icons/mActionNewFolder.svg": "d7afc17f864be5b81aae706f5bb9141e", "resources/theme/default/icons/mActionNewMapElement.svg": "6566f4e1671d310ab3ed30117c44e5ef", "resources/theme/default/icons/mActionNewPKG.svg": "4156b6fbf9e9a09e8889e1ae0b0330e4", "resources/theme/default/icons/mActionNewSchemeData.svg": "7807bb77c398afb16afd46463672f893", "resources/theme/default/icons/mActionNewTableRow.svg": "f2fd0ead19625b325f33359e6f12a55f", "resources/theme/default/icons/mActionNewTask.svg": "7618baf3704550ed1ae0505eb74d1037", "resources/theme/default/icons/mActionNewTileClass.svg": "0166b686d417d0a81b5ae5b1791b4169", "resources/theme/default/icons/mActionNodeDiluting.svg": "6a864c4a9ce1fdd07ba68f132c980643", "resources/theme/default/icons/mActionOpenData.svg": "8cdca77f195e267228b878d1d7d82fe4", "resources/theme/default/icons/mActionOpenDirectory.svg": "d37744c14f87326d779ce634086a4a9b", "resources/theme/default/icons/mActionOpenJS.svg": "de439993a9ab1770b8e2fb17f5a8d0e1", "resources/theme/default/icons/mActionOpenLayout.svg": "da607b272ab28493f7b354a684388bae", "resources/theme/default/icons/mActionOpenScheme.svg": "781accf4794f648dcf2f8c4097ff8fa7", "resources/theme/default/icons/mActionOpenTable.svg": "b1bdd4c59286ff406b8262f1b61d54e2", "resources/theme/default/icons/mActionOptions.svg": "50f9ee8b59e5e364ba52222d2c55a8fb", "resources/theme/default/icons/mActionOraganizationManager.svg": "d386d4dc75ac892a620c5f726295214e", "resources/theme/default/icons/mActionOverView.svg": "c8df9418bb2d3606aa53e30ab4daa93f", "resources/theme/default/icons/mActionPan.svg": "98a5dbf4184531ba2a2c68db92806456", "resources/theme/default/icons/mActionPanToSelected.svg": "974a9cf2f5750b418b474942b5dcb2a1", "resources/theme/default/icons/mActionParamSetting.svg": "10cb60a011e7788aac961c212e1c8608", "resources/theme/default/icons/mActionPolygonIncise.svg": "2d82c0b0f5739c6bf787c4eab8064a6a", "resources/theme/default/icons/mActionPolygonInterattraction.svg": "622f8a50bc5ceb068e484420b9241020", "resources/theme/default/icons/mActionPolygonOverlay.svg": "95411d192cad3e679a01556a44ba29d5", "resources/theme/default/icons/mActionPolygonToPolyline.svg": "22b2360fe102a0c1d93c7c387aa86026", "resources/theme/default/icons/mActionPolylineToPolygon.svg": "c08885aa366dc2ec78104616d564ec31", "resources/theme/default/icons/mActionPreprocessingScheme.svg": "2cce25bc39ad3c0e64e4ede8a7dbf790", "resources/theme/default/icons/mActionPreprovessingTB.svg": "f8c7613e852b0c9af5f8e3038fd4b73f", "resources/theme/default/icons/mActionPrint.svg": "31c995fa5d814d9746b94d0e3780260b", "resources/theme/default/icons/mActionPrivilegeManager.svg": "d406a3ea8e70ac6188f7ff6cda412fdf", "resources/theme/default/icons/mActionProperties.svg": "643c2dbf159641c13a90dd282c750b66", "resources/theme/default/icons/mActionPropertiesWidget.svg": "7b8bdc3ad81fe89c339754370467ef05", "resources/theme/default/icons/mActionProperty.svg": "0f5fd9de9f38273462de2ef945b9a3ca", "resources/theme/default/icons/mActionQgsAddView.svg": "77154a2bd7a278ec5417f54bea869039", "resources/theme/default/icons/mActionQueryByLine.svg": "100a77b190342c4f4d612d2f4db8964c", "resources/theme/default/icons/mActionQueryByPoint.svg": "15630a334591356d90a90216319a3d48", "resources/theme/default/icons/mActionQueryByPolygon.svg": "6a70cc390d0f99105123a68aac36ee17", "resources/theme/default/icons/mActionQueryRoot.svg": "f442d02fb5ef3741fdf1624a3f02f5d7", "resources/theme/default/icons/mActionRasterImport.svg": "2417938f794ce78543633dc544bca4d0", "resources/theme/default/icons/mActionRedo.svg": "8c028f9ef70e474d7387f9b460a65eb6", "resources/theme/default/icons/mActionRefresh.svg": "52fa4b4278264c367eda284c53d15f89", "resources/theme/default/icons/mActionRegionExport.svg": "845dd15fd40ad4f743623469b5b3fdb4", "resources/theme/default/icons/mActionRegionImport.svg": "8dbd184a3b428ce6e102f7056e116f8b", "resources/theme/default/icons/mActionReload.svg": "43784ed0de3112c14d5921b6303d63a3", "resources/theme/default/icons/mActionRemove.svg": "e6754b6e1d730a5c882c2db92eea2950", "resources/theme/default/icons/mActionRemoveAllLayer.svg": "a6da6d6063b7329140356336a91090cf", "resources/theme/default/icons/mActionRemoveLayer.svg": "1c49ecbc2147eba97ec8aad9633f7ccd", "resources/theme/default/icons/mActionRemoveRepeatData.svg": "95b636aa514362fb03154a017f8eadc9", "resources/theme/default/icons/mActionRemoveRepeatedPoints.svg": "f3559b1f0ead7a72c40d87131c92acd8", "resources/theme/default/icons/mActionRemoveSchemeData.svg": "9194bf7bea36f62dc7dc2f3b30b90cbc", "resources/theme/default/icons/mActionRemoveVertexTool.svg": "8ae82b51d34a411968de1a9d97307367", "resources/theme/default/icons/mActionReName.svg": "2f4f8cbcf71196f22f219881f3e971d4", "resources/theme/default/icons/mActionReset.svg": "741f7fb42d42849d612fc20b12b7cd35", "resources/theme/default/icons/mActionResetDirPath.svg": "c344596ea4ddfc767152bdeb7c02ed27", "resources/theme/default/icons/mActionResolveSharePointError.svg": "f59d218ed27b3d3e93b66853590a8e94", "resources/theme/default/icons/mActionResultExport.svg": "e05fe9e6f120fff6f9fe567427d73eae", "resources/theme/default/icons/mActionResultPreprocessing.svg": "d915ed03dcd61c0d516c76a4b87d0aa4", "resources/theme/default/icons/mActionReversePolyline.svg": "d55a0018bb397763cc96e462198fdbe2", "resources/theme/default/icons/mActionRevertToRevision.svg": "0392f2d180aee9d9365959f9245e265b", "resources/theme/default/icons/mActionRightAngle.svg": "4c55097541c57f3764ca125d0920ef85", "resources/theme/default/icons/mActionRotateFeature.svg": "3e22ce01e2c0c965d96fc17ccf57f686", "resources/theme/default/icons/mActionRuleManage.svg": "15482f473e3fe2874ab18f9f2e8fbadb", "resources/theme/default/icons/mActionSaveAllEdits.svg": "94a89bc9b11cb90a59fc8f044c0a5936", "resources/theme/default/icons/mActionSaveAsScheme.svg": "13d249919c23815a608a9ff5d28846d6", "resources/theme/default/icons/mActionSaveEdits.svg": "777486ea8c29a2a365dfe80977fa17c1", "resources/theme/default/icons/mActionSaveLayout.svg": "8b3789e6e0dd8223130b84000d3ed79f", "resources/theme/default/icons/mActionSaveScheme.svg": "4f281e045c96432fd1cd7a82ca6f606b", "resources/theme/default/icons/mActionScaleBar.svg": "537d6a364f6c093e6b585b7e049b29af", "resources/theme/default/icons/mActionScaleNode.svg": "50f99f8accdc8751b270032cb556b39f", "resources/theme/default/icons/mActionSchemeBatch.svg": "dc5b9b397f9281a476733882693b6c47", "resources/theme/default/icons/mActionSchemeFilter.svg": "717cb0048b15305a8cadd452718d340c", "resources/theme/default/icons/mActionSchemeFit.svg": "43ce16a7914f0ed136fb52182040b068", "resources/theme/default/icons/mActionSchemeManage.svg": "c3514670076c47f378bf24f59d007e43", "resources/theme/default/icons/mActionSchemeNoFit.svg": "ddb52d6845b46bb2da8f44d6515c48ad", "resources/theme/default/icons/mActionSchemeShow.svg": "bd93bfc5885d5307f38203892ee44ef2", "resources/theme/default/icons/mActionSchemeSourceManage.svg": "49f8d190098edf63f5afa2a4fcb1b343", "resources/theme/default/icons/mActionSchemeTargetRoot.svg": "4c51b15a288a6857cb595ca87badc50d", "resources/theme/default/icons/mActionSearch.svg": "c53fee0f34c42a7a9d8e898532a3b86d", "resources/theme/default/icons/mActionSelect.svg": "af097cfbf7068555e0316d56705f153a", "resources/theme/default/icons/mActionSelectAll.svg": "05fe2f51d3089764d072f9ace468d8b4", "resources/theme/default/icons/mActionSelectAllLayers.svg": "fc3274b291accf2f3c94e7b6d21b496d", "resources/theme/default/icons/mActionSelectPolygon.svg": "de8682bdb5e3b611224a6ee8b52515ab", "resources/theme/default/icons/mActionSelectRadius.svg": "06c743aeea2546796c169625cdbfeb0c", "resources/theme/default/icons/mActionSeparateFeatures.svg": "47a255fe5cc8c8cd23a512e458ccf01a", "resources/theme/default/icons/mActionSeparateLayer.svg": "dca40eff33ddc48a7c0a1221c0aae739", "resources/theme/default/icons/mActionServer.svg": "1ab2380a5b5e7915e80d6b7f232d3bd2", "resources/theme/default/icons/mActionSetBottom.svg": "efce405a42a008cf74a9663e46362ab0", "resources/theme/default/icons/mActionSetClipEnv.svg": "cfc446c1a8ce1801fb3682f4caf2185b", "resources/theme/default/icons/mActionSetClipPolygon.svg": "0fc055217ef4255c7e588f08e3edf780", "resources/theme/default/icons/mActionSetDataSource.svg": "92fea7f7b80c74bb4f22cbedbd160e81", "resources/theme/default/icons/mActionSetNoClip.svg": "729fd670d5e947741b64c6ed7ee385b7", "resources/theme/default/icons/mActionSetNull.svg": "33deea05fa20968c10a0c3123e47c0d6", "resources/theme/default/icons/mActionSetSpatialReference.svg": "64f9c175f0e21d1128c10aa08dbcad85", "resources/theme/default/icons/mActionSetting.svg": "2c6d37e1466939c8e16f1cd04ff48d24", "resources/theme/default/icons/mActionSettings.svg": "98160adbf1b7bc7dfa0064d5da680278", "resources/theme/default/icons/mActionSetTop.svg": "57517e5161ba671327848c4c2495ce56", "resources/theme/default/icons/mActionSharing.svg": "8f5e6dffd6231a6858fc5f56ce1a60b2", "resources/theme/default/icons/mActionSharingExport.svg": "10de78f4fe80a0cef1a3609dd40a67f0", "resources/theme/default/icons/mActionSharingImport.svg": "9bed0a42714abdd71b65742d1ca1b26b", "resources/theme/default/icons/mActionShowAllHide.svg": "f6dea326e1c06484a7dbaa3a1c2313fe", "resources/theme/default/icons/mActionShowAllLayers.svg": "1069904cfba0dbc27c02e6659bacbc49", "resources/theme/default/icons/mActionShowBookmarks.svg": "18aa642ffd432316af2e4410f4b29d7d", "resources/theme/default/icons/mActionShowFilter.svg": "ace4d7974edfb60aca730fd5095c3259", "resources/theme/default/icons/mActionShowGridTool.svg": "682e24e73864e87cfdb33e9bd3e9809f", "resources/theme/default/icons/mActionShowLayersSet.svg": "2ac82d593bce15962b546a4aebe8fe48", "resources/theme/default/icons/mActionShowPluginManager.svg": "99436035696bb2cb8f10f5c098c4b44f", "resources/theme/default/icons/mActionShowResults.svg": "f56c5cd78219b41cd930774bb121024b", "resources/theme/default/icons/mActionShowSelectedLayers.svg": "a838ae1987783d19767e54228de05aa1", "resources/theme/default/icons/mActionSimplify.svg": "9c03c4c49fcd354e04832dd253c880a1", "resources/theme/default/icons/mActionSmoothTool.svg": "707f96d7e602170da613f80b679c8905", "resources/theme/default/icons/mActionSpatial.svg": "bef0b6a230ff89396636be69e8b40ef7", "resources/theme/default/icons/mActionSpecialAttributeBrush.svg": "24fc0d1c64c25fe84b8b2cfe896141cd", "resources/theme/default/icons/mActionSplitByPolygon.svg": "538583d5099e9abc25d5b1a9268e10e7", "resources/theme/default/icons/mActionSplitByPolyLine.svg": "8701b4efde0bae3ed46376ef173e4787", "resources/theme/default/icons/mActionSplitBySelect.svg": "26580003f206a37877bd8d4fb7c7e83a", "resources/theme/default/icons/mActionSplitFeatures.svg": "58f0d4717c6590d5116d6c4e3cf6661a", "resources/theme/default/icons/mActionSql.svg": "19852025b86712876a6f8722151414af", "resources/theme/default/icons/mActionStartCheck.svg": "36f7176cf331fccd0b34d63d0ea1330a", "resources/theme/default/icons/mActionStartImport.svg": "c6016f45b0af8a4852c51b94390c8226", "resources/theme/default/icons/mActionStreamline.svg": "e5cbf8db4c102b2afb6c8231f59b5c17", "resources/theme/default/icons/mActionStyleView.svg": "730e0af4ddac5a0fb1f5f539fd30622b", "resources/theme/default/icons/mActionSum.svg": "d017046d48ce90ed5bceab7d3e6d6f1f", "resources/theme/default/icons/mActionSwipe.svg": "12847b35a09965af255f83f9b227e3f0", "resources/theme/default/icons/mActionSysSwitch.svg": "34367f856ddc6239e12d778c5a19b816", "resources/theme/default/icons/mActionTableImport.svg": "8deaa47f6625c021e0fe3236c1d7b94d", "resources/theme/default/icons/mActionTaskManage.svg": "970769a0f3cd8be7dd294a765ab2187e", "resources/theme/default/icons/mActionTeamConfig.svg": "e84b0043b2cb13f560f94a0d6721e07c", "resources/theme/default/icons/mActionTeamEdit.svg": "b0114a6fc46120440e19c53730a0581c", "resources/theme/default/icons/mActionTeamProjectInfoStatistic.svg": "3129a2940c9803c5ca6ba12bef78cb00", "resources/theme/default/icons/mActionTeamRevisionSlider.svg": "5f1ab9a8ba1f48f0fcb3cfd5984862cc", "resources/theme/default/icons/mActionTeamServer.svg": "fecca577325287d69541a40ded17fadb", "resources/theme/default/icons/mActionTeamTimingAcquireLog.svg": "48487ce93bfd8cd3d0aa77a2c26ce9cd", "resources/theme/default/icons/mActionTeamTool.svg": "fc55777995fd4de917463da7c9f5424b", "resources/theme/default/icons/mActionTemplate.svg": "01927217e00be004e12f16b651440359", "resources/theme/default/icons/mActionTemplateCompose.svg": "af1adc1a48861d24dc688b9778d3d80e", "resources/theme/default/icons/mActionTemporaryLayer.svg": "68e4b5c61088a15f89d939c81ff86b87", "resources/theme/default/icons/mActionThematicAttributes.svg": "6b6ee135eedc395c6eb8e04b92a5cab0", "resources/theme/default/icons/mActionTileImport.svg": "903e13e6c35e1ee083a61b23918688de", "resources/theme/default/icons/mActionTiling.svg": "c8e25a3eda0bc19b9778168ecf753375", "resources/theme/default/icons/mActionTimeSlider.svg": "30df8efe796c12114471323077bca30c", "resources/theme/default/icons/mActionTimingAcquire.svg": "489dd9a97dffb720b3162dc14bc86a9e", "resources/theme/default/icons/mActionTimingAcquireSetting.svg": "7ecabd8416d95df2f92da8ff60d44da9", "resources/theme/default/icons/mActionToggleEditing.svg": "29723b6d47441f7bb494714ea429edde", "resources/theme/default/icons/mActionToolBox.svg": "2c9169fc1c7e50f15b84f71bafd17a6d", "resources/theme/default/icons/mActionTrim.svg": "225c5b9cc91e4f5e8ca4aaf8787780c6", "resources/theme/default/icons/mActionUndo.svg": "ec0a88d3076c323ae59118eca56b8163", "resources/theme/default/icons/mActionUngroup.svg": "7bc078804d80699dc8cc91e2082f84d4", "resources/theme/default/icons/mActionUp.svg": "7999c9a02eda90ac2f42b6c3641e904b", "resources/theme/default/icons/mActionUpdate.svg": "40ba9725c8cdd5e1fe69108bb6f8525d", "resources/theme/default/icons/mActionUpdateRecords.svg": "5a46d098c69fbf39923ffe3178018278", "resources/theme/default/icons/mActionUpdateToRevision.svg": "80e6f808eeb64d71829e44a827b9b51f", "resources/theme/default/icons/mActionUploadData.svg": "4817a531daa8606bc9531d50957971c6", "resources/theme/default/icons/mActionUserRoleManager.svg": "e3e59465ff0ccfc907414df8628b5723", "resources/theme/default/icons/mActionVertexTool.svg": "daa8e981408b835ddbe36af3bed62e19", "resources/theme/default/icons/mActionVTSPreview.svg": "f9c9643fd1b174e56abae7e8a5ffe7ba", "resources/theme/default/icons/mActionWeldPolyline.svg": "35c012816b7f000837e465109dcb1f95", "resources/theme/default/icons/mActionYearChangeNavigation.svg": "943edf66d680b0ee7fc4a2d68504b6ab", "resources/theme/default/icons/mActionYearChangeRegression.svg": "b4f5be92104d693da3a88a817166c20e", "resources/theme/default/icons/mActionZoomFullExtent.svg": "6f21d610891d2924bcfe88c73fb764f5", "resources/theme/default/icons/mActionZoomIn.svg": "1042c451e38bbbdea2736329514618a0", "resources/theme/default/icons/mActionZoomInCenter.svg": "f02ed86aaccae6e9a97ecbb852a0b224", "resources/theme/default/icons/mActionZoomLast.svg": "5c42ac5098f8e0f648dbe3de9bb4bad8", "resources/theme/default/icons/mActionZoomNext.svg": "a9fa6a5c006dd0c0840aa4e1774de19d", "resources/theme/default/icons/mActionZoomOut.svg": "7a84e09a885a48b381d69d0b5d3ff69a", "resources/theme/default/icons/mActionZoomOutCenter.svg": "c84df5bb54c1fc4da3656025dd3bd6bd", "resources/theme/default/icons/mActionZoomToBookmark.svg": "9073facd9ee913217bed1c2093a4e098", "resources/theme/default/icons/mActionZoomToLayer.svg": "82c760fa362ccf923603a9ab657d737f", "resources/theme/default/icons/mActionZoomToSelected.svg": "e934636f2b504da8ab0e4e6d3e859bac", "resources/theme/default/icons/MaintainRegionDic.svg": "e220cc3e4c97e63296684f3d9e38d5f8", "resources/theme/default/icons/mapConfig.png": "f7b863779cc89c8be7605bc56a65703c", "resources/theme/default/icons/mapConfig.svg": "c7a0b74014cc0599c1e82c0c8b8be1f1", "resources/theme/default/icons/markdown.svg": "b1318f7383ab486afa82e4568aa58185", "resources/theme/default/icons/matlab.svg": "8d5f10b9fb25f9df14e71d6e4eda19a7", "resources/theme/default/icons/mAutoChangePolygon.svg": "4aed8b896926a1d832f64933d1257f52", "resources/theme/default/icons/mAutoCompletePolygon.svg": "3c11c43f10997872241c257e9987d46c", "resources/theme/default/icons/mComposeSchemeManage.svg": "d352afeceb179d2d645026aa873c5b92", "resources/theme/default/icons/mDataExportSchemeManage.svg": "a7e3c9f7ad828fb76c0cf4cfb13c097b", "resources/theme/default/icons/mDataImportSchemeManage.svg": "fc35fcdf359f9f81fa2ae510ab87c4c1", "resources/theme/default/icons/mDataTransSchemeManage.svg": "c9bf6fa291e4976bf0983bd5288934ab", "resources/theme/default/icons/measureConfig.svg": "903161e927f84e5613a6dd4c9922a1bd", "resources/theme/default/icons/merge_h.svg": "e18a2b6bbdb4a1e6d8a95644122e60d9", "resources/theme/default/icons/merge_v.svg": "a8527f6026fe7118c129f5cba6fb1016", "resources/theme/default/icons/metadata.svg": "b2e273799dbfa6dbc52e8228f5bdbdd9", "resources/theme/default/icons/mGeneratingDLJX.svg": "4c69197baf2338313faf3993d6b115e8", "resources/theme/default/icons/mGeoPackage.svg": "7c73ffb090a38af7feab29aa12152423", "resources/theme/default/icons/mIconAddDBServer.svg": "8c825e00c2816ab43183b541733d71f8", "resources/theme/default/icons/mIconAddServer.svg": "7112be64f8be6720026bc2a8bf9c625d", "resources/theme/default/icons/mIconAfs.svg": "26bc632ff2d7c289e202f6c8f61e1c03", "resources/theme/default/icons/mIconAms.svg": "630bac3c1a415087351fe6492e4bb83d", "resources/theme/default/icons/mIconAnalyseFlow.svg": "ea21512744b1a11c266e5b9d1520389a", "resources/theme/default/icons/mIconAnnotationLayer.svg": "fbe179641e24ec8c54a302265cca4f2f", "resources/theme/default/icons/mIconAnnotationMLayer.svg": "68d1aa7332c4c52ba3e969a14417ae19", "resources/theme/default/icons/mIconApplication.svg": "94bea3681edb4be10e23727c67923918", "resources/theme/default/icons/mIconApply.svg": "51876eb236913705ce2756c72b7fb49a", "resources/theme/default/icons/mIconAttirbuteAssign.svg": "b93e85f9184b920d402f862abf94357b", "resources/theme/default/icons/mIconAttributeTable.svg": "e7db893217b5b1baac3961b549108479", "resources/theme/default/icons/mIconAuxiliaryStorage.svg": "424505094deccde793bb07080494a9b1", "resources/theme/default/icons/mIconCad.svg": "fef13370469b1722e4dcc26d46a89f57", "resources/theme/default/icons/mIconCalEllipsoid.svg": "823474172c2ccec64f5adaee114fd82a", "resources/theme/default/icons/mIconCatalogResource.svg": "6c8e7c6dfd9c958328f27092fb00c3c3", "resources/theme/default/icons/mIconCatalogRoot.svg": "01afcfbe57ea1a4a90b6daf78af9a2a9", "resources/theme/default/icons/mIconChange.svg": "7cc1b16efb9fcf0c8144b9c1e86d258e", "resources/theme/default/icons/mIconCheckLayer.svg": "841eb956c21eb7f7557937b56648eea2", "resources/theme/default/icons/mIconClearText.svg": "f20b46fc4f835d7d636f97f1d7770c63", "resources/theme/default/icons/mIconClearTextHover.svg": "6bf93a9f31af24cc804503c71aadbfa7", "resources/theme/default/icons/mIconClose.svg": "7787829dfdddf0c0ff3306e96b5998fe", "resources/theme/default/icons/mIconCode.svg": "e6351f72e8d1f68207c574af6f6557ad", "resources/theme/default/icons/mIconCodeSpecifiation.svg": "e0a81b82fa77022b5850eb16a0382690", "resources/theme/default/icons/mIconCodingScheme.svg": "654f59de125796f54e270e6278e60d34", "resources/theme/default/icons/mIconCodingSchemeRoot.svg": "06c8ad7cf27e496674ed6bdc31db1ba7", "resources/theme/default/icons/mIconCompoundLayer.svg": "d1026c32abaa5a0591f7a5c7b693eac8", "resources/theme/default/icons/mIconConnect.png": "c4d29a2a304cc265c34def6e2a1cc7ed", "resources/theme/default/icons/mIconCritical.svg": "479fc5b611396c2c535a2bc18a808ef6", "resources/theme/default/icons/mIconDaMeng.svg": "74e85fd3c915ccc760b40a05f961bae5", "resources/theme/default/icons/mIconDataSet.svg": "cb718d909534dadd72d4b72dbddb1a4a", "resources/theme/default/icons/mIconDataStructure.svg": "fd89f1f6fae2b928a841aea919995f34", "resources/theme/default/icons/mIconDbSchema.png": "1b79c7357dd1f0e5eb1f4d47f759b4f6", "resources/theme/default/icons/mIconDelete.svg": "b7645486da851a6b3272d304f2a6dd86", "resources/theme/default/icons/mIconDeselected.svg": "75733116db9e219b0f4382385342b511", "resources/theme/default/icons/mIconDicItem.svg": "68e57fc565b36ae2e1d060ce6dc8c964", "resources/theme/default/icons/mIconEditableEdits.svg": "455990cd70b279a43e6fe1483ef47b3b", "resources/theme/default/icons/mIconError.svg": "affb8053194df39dda4f105433c209d7", "resources/theme/default/icons/mIconExportCatalogDataNode.svg": "66c846fd3b5f7a771e834b12555d546e", "resources/theme/default/icons/mIconExportCatalogFileNode.svg": "719a5a83513ddb084966a21c3909092a", "resources/theme/default/icons/mIconExportCatalogRootNode.svg": "51d5c34391a171eed24ebfc860fc710b", "resources/theme/default/icons/mIconExportSchemeNode.svg": "810c27933bac0c415bba59ad0e28619a", "resources/theme/default/icons/mIconExportSchemeRootNode.svg": "cd1a0f9d1adc73d30ad78f03e26e3125", "resources/theme/default/icons/mIconExpression.svg": "4a7c9010d4faef5cb7dd50d4b063bf1d", "resources/theme/default/icons/mIconExpressionSelect.svg": "d5a11b917f78b8963933e25463ed3f13", "resources/theme/default/icons/mIconExternApplication.svg": "76860ff954accd3d41779f1b13614a03", "resources/theme/default/icons/mIconFcs.svg": "842114c237df2e6488115f32bf6179a0", "resources/theme/default/icons/mIconFifthLevel.svg": "64fd05b7bc8fcc3d5613bff14477230b", "resources/theme/default/icons/mIconFile.svg": "3047ab35641e5516a3eb281bada2613a", "resources/theme/default/icons/mIconFirstLevel.svg": "411ddf0a99c68e8adf1c08389bd2ea10", "resources/theme/default/icons/mIconFolder.svg": "cf1ca6b8396b31dcca5b8f33eb43ceb4", "resources/theme/default/icons/mIconFolderCatalog.svg": "4eb1ba9a379ada6a5d6bdb205259bb04", "resources/theme/default/icons/mIconFourthLevel.svg": "638de27d6466f21ec317cb203840ae5d", "resources/theme/default/icons/mIconFtp.svg": "a199167e87d48e535a28a0f1bba7b62b", "resources/theme/default/icons/mIconFtpConnection.svg": "8a392e40f69a0f418fdbdd881165da08", "resources/theme/default/icons/mIconFtpFile.svg": "dd9f4404c69b29266260a9a9d8df269b", "resources/theme/default/icons/mIconGBase.svg": "fb1586a853c2904b60aaae867f76c85a", "resources/theme/default/icons/mIconGdb.svg": "faf3ba5fe031bfdce3aed9a3f46f078f", "resources/theme/default/icons/mIconGeoMap.svg": "5fddaa3bf5d340522608d62df060290f", "resources/theme/default/icons/mIconGeoModel.svg": "1062170310062340d8e756bf956bc804", "resources/theme/default/icons/mIconImage.svg": "d7d21b7fb0e283fcf8411c03f3e31845", "resources/theme/default/icons/mIconInfo.svg": "3c0cf72d3f29ee6108e7d408a9103694", "resources/theme/default/icons/mIconInnerLayer.svg": "8191e0d1b9834eec58a8ee696da1ceeb", "resources/theme/default/icons/mIconKingBase.svg": "8ad55c2113ceee058166bdd7da6fd468", "resources/theme/default/icons/mIconLayer.png": "21cf151b551b35eed6e034ed7112dbc0", "resources/theme/default/icons/mIconLayer.svg": "9540c68b59d83e5f407a81cb364fe39e", "resources/theme/default/icons/mIconLayoutTemplate.svg": "215df0affb2b3b0d8895161fcc9bdafa", "resources/theme/default/icons/mIconLayoutTemplateRoot.svg": "99d5dce6929dd587527835636b774346", "resources/theme/default/icons/mIconLineLayer.svg": "4c69197baf2338313faf3993d6b115e8", "resources/theme/default/icons/mIconLineMLayer.svg": "fe6b4178b48f8999d83c10155c1a0b0e", "resources/theme/default/icons/mIconLoading.gif": "8fa9058348a86d759eb315dd66de2714", "resources/theme/default/icons/mIconLocalServer.svg": "f890500950ee4e3e5468082e57eabcf9", "resources/theme/default/icons/mIconMapFile.svg": "627ca8d2c2c1796f646e9216d9349bea", "resources/theme/default/icons/mIconMapStyle.svg": "b6356008ed553c02b7a31e7be2b3289f", "resources/theme/default/icons/mIconMixCatalog.svg": "4eb1ba9a379ada6a5d6bdb205259bb04", "resources/theme/default/icons/mIconModelLayer.svg": "72f4b78bf3b06435aef0e06e5ca151e2", "resources/theme/default/icons/mIconModifyServer.svg": "c7d9ee62d4aa9720f2f15a3f22e86b3e", "resources/theme/default/icons/mIconMySQL.svg": "a99effe63b8a0afe08ca87c291451c6e", "resources/theme/default/icons/mIconNewGroup.svg": "e27402c3f3b6866ea996eff280c1329d", "resources/theme/default/icons/mIconNow.svg": "6a8ae624d3a553b4db8d03e60170fee0", "resources/theme/default/icons/mIconOffice.png": "6303c5abbdcdd3a24281007c3f75910e", "resources/theme/default/icons/mIconOracleSpatial.svg": "96b27c5bb382407c8fbb3f1d5cdfbb45", "resources/theme/default/icons/mIconOutsideLayer.svg": "2e84bf82fd9ba6d6d3b170ff227e43b7", "resources/theme/default/icons/mIconOws.svg": "861d35b91c66a25907dd731a522795fd", "resources/theme/default/icons/mIconPg.svg": "870e241bd22d89b83aa26e69b79e44d9", "resources/theme/default/icons/mIconPhysicalTable.svg": "62556fe79abc5c1aa1b139a4caab4f97", "resources/theme/default/icons/mIconPhysicalTenseTable.svg": "8495bd64272f9519ba3639bb108ce6a1", "resources/theme/default/icons/mIconPointLayer.svg": "95251fc052d089796b41a46072661ad2", "resources/theme/default/icons/mIconPointMLayer.svg": "3ace9ed797c1cd91848f6e75df4ce5cf", "resources/theme/default/icons/mIconPolygonLayer.svg": "da646fa3fef5ce81f9f0e9ace1d0be1e", "resources/theme/default/icons/mIconPolygonMLayer.svg": "b76b1c64744e78f711a6ed163b6fbf29", "resources/theme/default/icons/mIconPostgis.svg": "d95101a8f8b9eaa80083d646e5383659", "resources/theme/default/icons/mIconProjectionEnabled.svg": "820493124a75fe953400f9f2ceba9d34", "resources/theme/default/icons/mIconProperties.svg": "b57f75b3eccce67243c8143fc010f052", "resources/theme/default/icons/mIconRaster.svg": "a65c7579f3849569b88caa69d46d6d1c", "resources/theme/default/icons/mIconRasterGroup.svg": "b22c2b30b5b48777566a041b02ca20d7", "resources/theme/default/icons/mIconRasterLayer.svg": "37358bd2779dd20a7580a1b4b354913b", "resources/theme/default/icons/mIconRegionItemNode.svg": "0c892403655a142a739da4a445f10d69", "resources/theme/default/icons/mIconRegionNode.svg": "2b8d8fcdb3054ca0b50d165d76a0e529", "resources/theme/default/icons/mIconRegionRoot.svg": "a05b73abd59d65752a387c0887afeaa1", "resources/theme/default/icons/mIconRegionRootNode.svg": "b7b0001fe203a7c6e9bfaf88d2f2db4c", "resources/theme/default/icons/mIconRegionTree.svg": "981c3314204c6c2ddb53e50da0181732", "resources/theme/default/icons/mIconReload.svg": "4ee18f3e56c0a3ce09a1cc261ca9d12d", "resources/theme/default/icons/mIconRemoveServer.svg": "1644b9d1c6aa137862d1ae8788374b3e", "resources/theme/default/icons/mIconReportWizard.svg": "fc1234e9224f00f23323c50723568edb", "resources/theme/default/icons/mIconReserveSelection.svg": "8ab255dc5906d1cf88675446c3e09240", "resources/theme/default/icons/mIconResourceCatalog.svg": "4eb1ba9a379ada6a5d6bdb205259bb04", "resources/theme/default/icons/mIconRule.svg": "5115e07bbedf98741550b7db9dae61a4", "resources/theme/default/icons/mIconRuleGroup.svg": "99610d48d732d9f76ad9dd28fba1bb1d", "resources/theme/default/icons/mIconRuleRoot.svg": "013cdba55f00d20c4d9d1429a33d3981", "resources/theme/default/icons/mIconSave.svg": "5e0bcceceac29567885531806f0df5eb", "resources/theme/default/icons/mIconSaveAs.svg": "92eae31b2324f5a7b3fc959de7858a54", "resources/theme/default/icons/mIconSecondLevel.svg": "f830a82f170fdc417e8243e99f6463ef", "resources/theme/default/icons/mIconSelectAll.svg": "2c19a865fc8951dc6b0d76e03192fa57", "resources/theme/default/icons/mIconSelectNone.svg": "728e2fbb4f50cd340e739df3efa98505", "resources/theme/default/icons/mIconSelectServer.svg": "7a40c874356f04dfcf9dc16631b01e36", "resources/theme/default/icons/mIconServer.svg": "b037d88ce726e5e2e9022bcfccf1ab1d", "resources/theme/default/icons/mIconServerManager.svg": "db323ee83983521b1fe55d718137118b", "resources/theme/default/icons/mIconSetColor.svg": "04cd244f3efa35e3e49c3d15a41dfd8b", "resources/theme/default/icons/mIconShowStyle.svg": "82e6f287175dfa2492b04316a1001113", "resources/theme/default/icons/mIconShp.svg": "d4b18ad9e5808b63b022799c9b9ad7fe", "resources/theme/default/icons/mIconSixthLevel.svg": "e7553e75a3071065b15b6bcf2bdc8e96", "resources/theme/default/icons/mIconSnapping.svg": "63f355964cf029d5dcaf7181384b8762", "resources/theme/default/icons/mIconSqlite.svg": "aaa168559302aad794f38c3eba3bddd4", "resources/theme/default/icons/mIconSuccess.svg": "2d9a7e0389e29ef07f2f881f478acc57", "resources/theme/default/icons/mIconSystem.svg": "fa75b6702dfc9a38d955c4db45bce8de", "resources/theme/default/icons/mIconTable.svg": "21615bcc2dab3f7d81b36b7267d5c003", "resources/theme/default/icons/mIconTableLayer.svg": "f097c9a2709c4bb46a5c69edcaeb877d", "resources/theme/default/icons/mIconTableMLayer.svg": "3777b46cbf789491572e2ab6c3841384", "resources/theme/default/icons/mIconTense.svg": "a9506e8a7e783d03a27c777d50f639c5", "resources/theme/default/icons/mIconThirdLevel.svg": "1b82d2a307fc30d45b11ea616c7b5c10", "resources/theme/default/icons/mIconTile.svg": "b73d838ec69991e754f3cb8eb1f47366", "resources/theme/default/icons/mIconTileLayer.svg": "e65e45cc49d05e235d10096ad817fc82", "resources/theme/default/icons/mIconTimerContinue.svg": "cfd5c0fdbd6fdcd6a414dac9c092a633", "resources/theme/default/icons/mIconTimerPause.svg": "56f9fb6e1a8b29efb238c60c7abb2682", "resources/theme/default/icons/mIconTxt.svg": "2a5d39dfd28028ea033bbe28e5e5bfad", "resources/theme/default/icons/mIconType.svg": "652794ba38eacdf6364786d77e138b92", "resources/theme/default/icons/mIconValueRangDic.svg": "b5fbdf278321d22243dcc2a0dfa25e72", "resources/theme/default/icons/mIconValueRange.svg": "3656fff54e067f46659582350822c3bd", "resources/theme/default/icons/mIconValueRangeRange.svg": "2b8d8fcdb3054ca0b50d165d76a0e529", "resources/theme/default/icons/mIconVCT.svg": "9de1e1bdb8620f90226157e87ff08f35", "resources/theme/default/icons/mIconWarning.svg": "469d5f397cb89f1768473b9fc065cb1f", "resources/theme/default/icons/mIconWcs.svg": "d50746b54aaf55fe0f070cd61f42b589", "resources/theme/default/icons/mIconWfs.svg": "4bb63bff96c7076f1372a1a21fc746a3", "resources/theme/default/icons/mIconWms.svg": "ff83723069a766205aaeedbd335906dc", "resources/theme/default/icons/mIconWmts.svg": "ad3fe79ff517b26bf49f6ee5d6a31b17", "resources/theme/default/icons/mIconXYZTile.svg": "d0c0b73adfc969f20bf96165e9581276", "resources/theme/default/icons/mIconYearChangeDataInittail.svg": "53d895ee3af75bc504e52de69b648768", "resources/theme/default/icons/missing_value.svg": "864863c0054f6b72766d2f8a3e030f30", "resources/theme/default/icons/mLayerSaveAs.svg": "42e4a4ffc0342a7cc626cc2df0f4748e", "resources/theme/default/icons/model_selection.svg": "222c717feb5de2c675cfb7c85c41b9b4", "resources/theme/default/icons/modified.png": "430719999ef1aad4e546d47662f64a82", "resources/theme/default/icons/Mouse.svg": "87a75d7d8243dad6fd65d9815db3e7c0", "resources/theme/default/icons/mPolygonDifference.svg": "c7fcfd8353e8661aade6824f2f946878", "resources/theme/default/icons/mPolygonIntersection.svg": "8a8cef3083c30309df6946d6ccd660b7", "resources/theme/default/icons/mPubLayerRoot.svg": "5073514803a9dc01c5c70c3c18305ffc", "resources/theme/default/icons/mPubLayerSet.svg": "a64220d6ecb203571ef6f1d8b0bbe5c2", "resources/theme/default/icons/mPubLayerSimple.svg": "40a8f32e0ec29323c176effbab6a1199", "resources/theme/default/icons/mPyramidManage.svg": "f50928a886510ab2489f3875930c252a", "resources/theme/default/icons/mRefreshErrorStatus.svg": "07bf753129c84e7d476e939ee906c4cd", "resources/theme/default/icons/mReportExport.svg": "e4c0687252e51c27e8967b033352b09e", "resources/theme/default/icons/mReportSetting.svg": "9f12bcd4a448ceeed1ddfa07a289d712", "resources/theme/default/icons/mSourceFields.svg": "c29a122376138fa67964299e1cd554dc", "resources/theme/default/icons/mStartCheckProcess.svg": "1a43b916dc8effdee18fbc2126c77714", "resources/theme/default/icons/multieditChangedValues.svg": "fa2114de837cf105940390a270ca0fbb", "resources/theme/default/icons/multieditMixedValues.svg": "41e09b3a0af251bf17d9283b8f81a577", "resources/theme/default/icons/multieditSameValues.svg": "f280c0cd500fd7679b7bc4cc7abec436", "resources/theme/default/icons/mvectorlayercache.svg": "52656796a777dcd29c43e584de7e40ca", "resources/theme/default/icons/MySQL.svg": "b4b89c2f14fe912b9acc90c32f1b3dc8", "resources/theme/default/icons/nActionBasicStatistic.svg": "f58a64edfbcc2a499d017d021caa854f", "resources/theme/default/icons/net.svg": "01609ed2d769c8d7ad4c97460cac8f69", "resources/theme/default/icons/New post.svg": "0faf63c8a6a31411cfedca48447870cd", "resources/theme/default/icons/new.svg": "f39a9d36090c7cad6757b3621aea0dbf", "resources/theme/default/icons/newvectorfile.svg": "56abc8aa55c3e2143c78b52af8a364d9", "resources/theme/default/icons/new_project.svg": "3cd72c019015b461870f3ad6e871aca1", "resources/theme/default/icons/next.png": "ff36945b6427eb8226bec7fe2743af70", "resources/theme/default/icons/nextConflict.svg": "a086d4b6331680eeb568a4df98937305", "resources/theme/default/icons/non_versioned.png": "25ba46f98f7aee7477ffb7d7fc1a7d62", "resources/theme/default/icons/normal.png": "795d679579e414fd37489f552b604b94", "resources/theme/default/icons/numpy.svg": "f36ed6c85013eb6f6bb3e49a8a9ee713", "resources/theme/default/icons/open.svg": "200a281b71b9104332781e9eb2fcc6de", "resources/theme/default/icons/open_folder.svg": "4aac2ccabd5e0ee6afe3286e61b8c85a", "resources/theme/default/icons/oracle.svg": "62030f9604e445ac5ef6b9edc94da181", "resources/theme/default/icons/orderlayergroup.svg": "2dd8c13c7243f17d69087176d29ddbb3", "resources/theme/default/icons/overlay.png": "17f28aabcf682bbf6a22d0a80070b1ce", "resources/theme/default/icons/package.svg": "db8542fb863117d5d74f7a6676ab419e", "resources/theme/default/icons/pagesetup.svg": "b25656114dc0af77191a6d7b206c7e91", "resources/theme/default/icons/paste.svg": "02b0ecdb9e40d6e9a3a589be40ea0790", "resources/theme/default/icons/pasteElement.svg": "deede2c394febaf7818a5a74220adb58", "resources/theme/default/icons/pausefly.svg": "aec4d2aee5c1a96174b7d0efbb654d35", "resources/theme/default/icons/plugin-installed.svg": "715e5c20df916d7d0a959ef6a6efbc40", "resources/theme/default/icons/plugin.svg": "c5c2d3092b1c2958aae669b0ce891492", "resources/theme/default/icons/plugins.svg": "e04bad7db3b8435bff70fd5ebbebc369", "resources/theme/default/icons/postgresql.svg": "5c2c9989c1a23c934678119d55c088b8", "resources/theme/default/icons/previous.png": "af4d639c2ccefd5b57d4772e3cc3bb62", "resources/theme/default/icons/previousConflict.svg": "099b009a440d54790c0cda1d60842134", "resources/theme/default/icons/print.svg": "3ba6cb43288c30503450361636fae5aa", "resources/theme/default/icons/processingAlgorithm.svg": "5acd3a3ea3ac4302ebed7d35fc1f16ae", "resources/theme/default/icons/project.svg": "72a57d9485a1a0a78e977a2f472e2213", "resources/theme/default/icons/projectDataTree.svg": "23c638ad38fe4f0dc6a4661730f24081", "resources/theme/default/icons/providerQGeomap.svg": "2c9169fc1c7e50f15b84f71bafd17a6d", "resources/theme/default/icons/pypi.svg": "6b9ea3945cf090b33076ea91ce2e9612", "resources/theme/default/icons/pypi_color.svg": "aefe42c944201ae3a898d71d5ec62075", "resources/theme/default/icons/pyramids.png": "051b61faa344c78af9a059e9cfc78dae", "resources/theme/default/icons/python.svg": "cdf88def02be5b2ce0e4c40b89d2c6ce", "resources/theme/default/icons/python_gray.svg": "b0b260fbbaa0b24915e46e80a5d7af9d", "resources/theme/default/icons/qgeodataspecificationmanagertool.ico": "c86f314e1777d0e7541709d50f9d726c", "resources/theme/default/icons/R.svg": "c27cc0791576e0ca3616b76731907b9e", "resources/theme/default/icons/reduce.svg": "1e88d456b31261ed99c18414a5c47671", "resources/theme/default/icons/regression.svg": "ea2efc714a40dfba39ecbd5218268fa6", "resources/theme/default/icons/remove.svg": "a6328dbd354bd5965aae818d26b273da", "resources/theme/default/icons/removeElement.svg": "7f477ed033b711e6e3b96f9bf22c79e1", "resources/theme/default/icons/rendering.svg": "55bdf00e277bc65c5639e48f8d9f42d2", "resources/theme/default/icons/replace.svg": "fbba9c3ecb7acb6dc76d5fc36cb03302", "resources/theme/default/icons/resolved.svg": "4a3f747ccc1459e54666994682080e38", "resources/theme/default/icons/ribbonMaximize.png": "e5dc4278826e32c25a4723fc87b943b2", "resources/theme/default/icons/ribbonMinimize.png": "42094d61d30e8a72a1a2cbff7a0df598", "resources/theme/default/icons/right.svg": "5dca8df9c3b66d4d1449494b34f936fe", "resources/theme/default/icons/roaming.svg": "4db76c4b080e95322cde6b52d36cfcfb", "resources/theme/default/icons/rubberBandConfig.svg": "9dd7c66dd566eaabd42ab2edd443654c", "resources/theme/default/icons/run.svg": "ec156027329d2c13877625ff3fcbee01", "resources/theme/default/icons/sample.svg": "59c88431eeb5015338430a9b7d1be8c5", "resources/theme/default/icons/sas.ico": "c4c83c2060701e0389262d9d07362896", "resources/theme/default/icons/save.svg": "89eafd6a566e859840d717e73872df13", "resources/theme/default/icons/save_layout.svg": "de4fd322d23ce467c2d30c2f2cb4fec0", "resources/theme/default/icons/scale.svg": "6b074bd9946d667c2f3e6502e634ecd2", "resources/theme/default/icons/scaleAdd.svg": "1724ab174f6709f9485d555a2d372639", "resources/theme/default/icons/scalebartext.svg": "1c6d5b3e2d85f1cbfb7305ebc7c2ee94", "resources/theme/default/icons/scaleClear.svg": "c62dd67d91bfb849c20eb5a1bd37e215", "resources/theme/default/icons/scaleRemove.svg": "0fb15d6df30ef9a5248804f9d082ad04", "resources/theme/default/icons/scaleReset.svg": "c69773f8695e60c85c82f65bfb44a29e", "resources/theme/default/icons/scorecard.svg": "9919e029f7566ce49fbe7338493e0473", "resources/theme/default/icons/script.svg": "cb1493bb56cd8d924de2516d7dbb5187", "resources/theme/default/icons/search.svg": "b116a8aa818d1cd7ec41752a0c29816b", "resources/theme/default/icons/section.svg": "aea2b044eb14c55452bc8172df030f92", "resources/theme/default/icons/selectedrecords.svg": "171b3d8de98149a3675b853745105c22", "resources/theme/default/icons/setExtent.svg": "7080d56468833b423897465aeca93138", "resources/theme/default/icons/setting.svg": "41cf928d70d4e8ed012cc2d15d224ed2", "resources/theme/default/icons/shengcunfenxi.svg": "5ae32cbb038032ed1443b8005baff03c", "resources/theme/default/icons/shortcutsConfig.svg": "4cb9c5a2687e14ba17d7ad9f4dcbfea9", "resources/theme/default/icons/simplearrow.svg": "157c76b4552215d148febb98baf92b1d", "resources/theme/default/icons/situationpoint.svg": "af69811bf86271d54800957689c28aec", "resources/theme/default/icons/skip_line.svg": "eedc935dd2773c44ff7e54a50811030a", "resources/theme/default/icons/slider_close.svg": "3402865a23ff073e953448e3d2480c24", "resources/theme/default/icons/slider_lasttime.svg": "da6ead6853a3db08a482f28ceaf933e3", "resources/theme/default/icons/slider_lastversion.svg": "3d10f7b5692a9ea4b8929e5a65528bfb", "resources/theme/default/icons/slider_nexttime.svg": "b54ce11ec64888d09640861fcc2ed0a5", "resources/theme/default/icons/slider_nextversion.svg": "54213dc4e51bccdd8348bcbdc0102d7b", "resources/theme/default/icons/slider_zoomin.svg": "abeda64aa9f3fa4e6d3bb2eefe65a89e", "resources/theme/default/icons/slider_zoomout.svg": "b9a24b973053cf1d836872b788180f00", "resources/theme/default/icons/sloperuler.svg": "16d462cb766b85c0ec907ce5c529bb02", "resources/theme/default/icons/spss.svg": "4d8cb6044137301a4101755e43b83fc1", "resources/theme/default/icons/sql.svg": "a0997f68ee9c263ffecd0efbaa524236", "resources/theme/default/icons/stata.svg": "a62e991cde9e8389dea6bf46b0bccb43", "resources/theme/default/icons/sun.svg": "e98cbd692ec62d35d27c78c31d91f86f", "resources/theme/default/icons/symbology.svg": "062e0ba664ee2320d21cda33ddaf3525", "resources/theme/default/icons/symbolreorder.svg": "062e0ba664ee2320d21cda33ddaf3525", "resources/theme/default/icons/system.svg": "c76354112390bc2f9151b4baf729ccfc", "resources/theme/default/icons/table.svg": "21615bcc2dab3f7d81b36b7267d5c003", "resources/theme/default/icons/tablegroup.svg": "b9a57bb1b7a98b1ea2220552ae77f576", "resources/theme/default/icons/task.svg": "1223b13e13ffccea138013fd33b71775", "resources/theme/default/icons/teamEditProject.svg": "35828465cb83319c8579c9cb9a904481", "resources/theme/default/icons/terrain.svg": "5e2ccfec44e7d6a40320912b4137615e", "resources/theme/default/icons/threenorth.svg": "96eada7b3eeebdca632533988e617717", "resources/theme/default/icons/time_series.svg": "94fd406769655b721b17f4cb17bc673f", "resources/theme/default/icons/top.svg": "22d2fefe180a699944a6a5ab545ab5a9", "resources/theme/default/icons/topologyConfig.svg": "2af77dff1d80bcccdefd9079ed4d68b6", "resources/theme/default/icons/transformed.svg": "5b98c431c64bf1f5697a2c63cab6ca11", "resources/theme/default/icons/transparency.png": "17c24a4daf81d8d7f8b9c7642e2fa7e7", "resources/theme/default/icons/transposition.svg": "07d926b580577fcc30c3a709cbde68bc", "resources/theme/default/icons/tree.svg": "14ae56a092c0268672520520ffc4443a", "resources/theme/default/icons/txt.svg": "867501b0b384f46cf6d4f225605995c1", "resources/theme/default/icons/undo.svg": "5484c93bf4a89654d20a2ba6bb66d912", "resources/theme/default/icons/uploaddata.svg": "e2a9a1ccdb820364d99b96d1a3ab39b6", "resources/theme/default/icons/upWard.svg": "b1c72b33367343316cc888d977ebb43d", "resources/theme/default/icons/useLeftAll.svg": "fddc9b7c0d601a881ab55af95659d3cd", "resources/theme/default/icons/useLeftSelected.svg": "1764c036105882a4743fc4e34e384404", "resources/theme/default/icons/useRightAll.svg": "c7020f993a82c847054dadf2b5709a5a", "resources/theme/default/icons/useRightSelected.svg": "18d35ec89cbb795bb88449c8859b3d81", "resources/theme/default/icons/var.svg": "c3d9e7461e40e74c17f3ce5873905f51", "resources/theme/default/icons/var_open.svg": "b023934e733448d74563926e0d5f9a1f", "resources/theme/default/icons/vectorTileStyle.svg": "670fc3779ea1e4b103aa75efd3832baf", "resources/theme/default/icons/view_var.svg": "a495e2e336064e1549622b87c69dd649", "resources/theme/default/icons/visibleMap.svg": "7ec32396ae7a2da699cf55929aed4f98", "resources/theme/default/icons/walking.svg": "790a25d6b2f642d0e2c04e5f5e5efd01", "resources/theme/default/icons/website.svg": "c789f48d1f500ad317ef3cde4818b240", "resources/theme/default/icons/windowicon.ico": "dd4b17c0ec69f51b039c51b6e0887c31", "resources/theme/default/images/addNode.svg": "d11d5a3ce54b3a864281b2f5f3311e36", "resources/theme/default/images/attributeBrush.svg": "ddeee5f9e2e1e63792eff8cbbb840164", "resources/theme/default/images/background.png": "ddf7168c491dac078b9b0c7c6771d602", "resources/theme/default/images/breakByOnePoint.svg": "ed5c5d11f74011fcb4c8a027eeb31599", "resources/theme/default/images/breakByTwoPoints.svg": "ce13a12a1afde966f284661dfdb1471f", "resources/theme/default/images/copyFeature.svg": "0293921cdf1de395e600349621565051", "resources/theme/default/images/cursor_leftbottom.svg": "3e9b60706a3f0bf7d729c48bd440cdc7", "resources/theme/default/images/cursor_leftright.svg": "418f31c70c594b53922604cdddb036e5", "resources/theme/default/images/cursor_lefttop.svg": "c364223abf37f51dd7def7162d9be9af", "resources/theme/default/images/cursor_move.svg": "8a82e82a355f18c8f410bd9bbf0c5f8e", "resources/theme/default/images/cursor_topbottom.svg": "ec0ad72e180c458e3155a88a2db6c023", "resources/theme/default/images/deleteNode.svg": "2aae237e59216f92c6b87dd7757c1a95", "resources/theme/default/images/editDraw.svg": "23e810e15d33b68a03427f8a40a408a1", "resources/theme/default/images/editSelect.svg": "8859783cdb6f54f380faadc8ad60c3c0", "resources/theme/default/images/error.png": "450eb59729737d72ceb03818c43ffcc9", "resources/theme/default/images/extensionPolyline.svg": "91c86deac1a658ed6b928487fb076723", "resources/theme/default/images/identify.svg": "ac8cb0d555021521270e9e6cc0d8ce59", "resources/theme/default/images/information.png": "423578ade54524e34a95580788d7729d", "resources/theme/default/images/mCapturePoint.svg": "5caf3405335284af6d880495b01d5776", "resources/theme/default/images/measure.svg": "23e810e15d33b68a03427f8a40a408a1", "resources/theme/default/images/mIconDeselected.svg": "75733116db9e219b0f4382385342b511", "resources/theme/default/images/mIconSelected.svg": "cc341d78f94f446e0e6ca672482edd15", "resources/theme/default/images/move.svg": "8a82e82a355f18c8f410bd9bbf0c5f8e", "resources/theme/default/images/moveFeature.svg": "8a82e82a355f18c8f410bd9bbf0c5f8e", "resources/theme/default/images/moveNode.svg": "84a9570e8f6d04cd64c450c1523aad4f", "resources/theme/default/images/mPageLayoutPan.svg": "67a143805264211f1031ebb79eb356a4", "resources/theme/default/images/mPageLayoutZoomIn.svg": "0d4e308d01775da24950c7c7869cadc2", "resources/theme/default/images/mPageLayoutZoomOut.svg": "ffa286230cff03f5bf4003a7f2e77a94", "resources/theme/default/images/mPanClose.svg": "a0603e76de7727c6a3a524adec772f49", "resources/theme/default/images/overlayUpdates.png": "75a5188a1a36b3e55a0c26bf0692ec3c", "resources/theme/default/images/pan.svg": "8fa8827e723ea97094655ce0ab60bcbb", "resources/theme/default/images/pyramidfirst.png": "6336d1c87d3a53c59f42ab682a7ef68d", "resources/theme/default/images/pyramidfourth.png": "3ca3b9cdbb6b88a50f6a270c24504df2", "resources/theme/default/images/pyramidsecond.png": "d8eb0fae632533ce16178629a61e77d6", "resources/theme/default/images/pyramidthird.png": "2dba3fd37994c231b6fe468f5922144a", "resources/theme/default/images/rotateFeature.svg": "d47a6e3f34dd3d3484440c7e4a96b58c", "resources/theme/default/images/select.svg": "128d8d763c91a71599ad86fcc069ad63", "resources/theme/default/images/selectbypolygon.svg": "5efdd8dc872ba52a262b939cfcd70515", "resources/theme/default/images/selectbyradius.svg": "c2d9e4efd0649941dfee9a843284ccca", "resources/theme/default/images/splitBySelect.svg": "021d0ae70cb8157d9d6f18feef0db270", "resources/theme/default/images/SwipeDown.svg": "21f3775a767d4cfb81d748276baf522a", "resources/theme/default/images/SwipeLeft.svg": "457aac45411c29aa4d6f1b63e6d999ba", "resources/theme/default/images/SwipeLeftRight.svg": "21193a1f1446c5c392d7a3807427e25a", "resources/theme/default/images/SwipeRight.svg": "cc700b8a09d58417f17496738db67404", "resources/theme/default/images/SwipeUp.svg": "b440961cee34d1649815aa594a896318", "resources/theme/default/images/SwipeUpDown.svg": "e21665a6f5d712ab4b25022ddb738ca5", "resources/theme/default/images/systemabout.png": "067610e0d804b16c75496b4ed77005dd", "resources/theme/default/images/thematicAttributes.svg": "ac8cb0d555021521270e9e6cc0d8ce59", "resources/theme/default/images/warning.png": "9323d69c25b6f79fc974023fee921af0", "resources/theme/default/images/zoomin.svg": "6bf9cf3ff08b719f4f872977e7ee201f", "resources/theme/default/images/zoomout.svg": "debcf4106754577838008ca423288371", "static/README.md": "2b883d8ed7e177b421e29625caf11a10", "static/tutorials_page.html": "7e6350719caf6a92de7a495441cd65ed", "static/css/iview.min.css": "b56ab90b84c3ac9f460f50906cbf5ae8", "static/js/echarts.min.js": "40874546a400f6e1b358c0495998a43f", "static/js/form-create.min.js": "5b2e899b103ec5bade920ac909268db4", "static/js/iview.min.js": "cb94a058fc714808d440d2b9835fed0d", "static/js/jquery-3.5.1.min.js": "dc5e7f18c8d36ac1d3d4753a87c98d0a", "static/js/vue.min.js": "b0473a59bd7e655c4da3d26f50dbba1e", "static/js/element-ui/CHANGELOG.en-US.md": "f90a50248e8a935d4bf94939b2b83bc1", "static/js/element-ui/CHANGELOG.es.md": "cd352a41d428cfe359e2dd25b3e69216", "static/js/element-ui/CHANGELOG.fr-FR.md": "2a5cb94c1cc391f95705a822c50906d2", "static/js/element-ui/CHANGELOG.zh-CN.md": "fae21798d847ea9bd4827f78766d634e", "static/js/element-ui/package.json": "ed15655a88db7bf4e42f2879000f0241", "static/js/element-ui/README.md": "29e235e7c8cba854610d2faa0424c5a0", "static/js/element-ui/lib/alert.js": "44bd2ce7e47fc65112066ebe354ceaca", "static/js/element-ui/lib/aside.js": "6af3243f56f6c0bda3eba64e2cd8078a", "static/js/element-ui/lib/autocomplete.js": "8cfc24a3a0f63f57b0bbefd1d43150b1", "static/js/element-ui/lib/avatar.js": "0c3f24bdffc3f4c3016149e5b589bf58", "static/js/element-ui/lib/backtop.js": "4ba1079d79f186a4b73395ab8048ce30", "static/js/element-ui/lib/badge.js": "d124ddea127b3b87fb7fc0e3d28f976e", "static/js/element-ui/lib/breadcrumb-item.js": "3ee84610ae6e927194916dc1850be078", "static/js/element-ui/lib/breadcrumb.js": "3b696a8b4d753bce1c9a42f1f2f2e559", "static/js/element-ui/lib/button-group.js": "c13e97a9b747982a6fe3475ce0963202", "static/js/element-ui/lib/button.js": "db479a095696d3b41e4e8e5ca573b2d9", "static/js/element-ui/lib/calendar.js": "78b60123511490a4957bb7ecc90ca562", "static/js/element-ui/lib/card.js": "a63bc5d2a60ecffc3fe581d2b9a86b19", "static/js/element-ui/lib/carousel-item.js": "24398b44ec0a885f9f7dcf7b4511b140", "static/js/element-ui/lib/carousel.js": "dc86769400de82ac847dfe71d278331a", "static/js/element-ui/lib/cascader-panel.js": "400c0c6ad5bb41a6d0aa489f027a849c", "static/js/element-ui/lib/cascader.js": "a97cc547f182cb959ff0af6d31f0769c", "static/js/element-ui/lib/checkbox-button.js": "e867a0fcfa56c197d8cbbcd8000ae723", "static/js/element-ui/lib/checkbox-group.js": "81593c2eeeee70e69fff71af4dfe212c", "static/js/element-ui/lib/checkbox.js": "09780b33e4add1d32b2b870c8ccc5a4b", "static/js/element-ui/lib/col.js": "227c1189111f2dfbada9a8f26e1433ed", "static/js/element-ui/lib/collapse-item.js": "0ef8b7bea75fa2d185730fa809d86148", "static/js/element-ui/lib/collapse.js": "8cc69b5cbaefb354e3fa19e748a54118", "static/js/element-ui/lib/color-picker.js": "25752f575ad81607f8b3b4652e1e1790", "static/js/element-ui/lib/container.js": "f2118022ef7e900503a8028e72a47bf2", "static/js/element-ui/lib/date-picker.js": "160c4f0f12cc50d77eeff03eb3e2abce", "static/js/element-ui/lib/dialog.js": "3446278e381ae6162bfc309cd883b84a", "static/js/element-ui/lib/divider.js": "ce49dbf8658355e06eb9ca852acd98e0", "static/js/element-ui/lib/drawer.js": "2a4f53c5e574a8fbe04eb528ccdb4e6b", "static/js/element-ui/lib/dropdown-item.js": "1c440ab125200ed27bb4402f26ae6edf", "static/js/element-ui/lib/dropdown-menu.js": "51e13aaf4f6dea2a6ec4eb6a06f49c1a", "static/js/element-ui/lib/dropdown.js": "02786a92e4ed20d950ad7d09e6dd03e7", "static/js/element-ui/lib/element-ui.common.js": "b6b435a52f5cdc204fb8a023bc856a16", "static/js/element-ui/lib/footer.js": "4d392ecf90fa43ebf25caa31e1783354", "static/js/element-ui/lib/form-item.js": "98d4dd1fa2c81e68ff40dc3344620df9", "static/js/element-ui/lib/form.js": "88eeb8b813539099fec26e9993789af8", "static/js/element-ui/lib/header.js": "9c9ab6bc2af99e67eee8367ab3ed855b", "static/js/element-ui/lib/icon.js": "658fcece90b39788b763c25f17d60207", "static/js/element-ui/lib/image.js": "650a56522f7a590b8fca25fdfb09690a", "static/js/element-ui/lib/index.js": "28fb829b428bc14fe9d26f852dbc11a9", "static/js/element-ui/lib/infinite-scroll.js": "3c09c5248512b2f0281e8e8bf4ecee8c", "static/js/element-ui/lib/input-number.js": "3dd23cc075c49822fe68366cef6f09a3", "static/js/element-ui/lib/input.js": "bb52be1067cfd9b1dd8ded0b664f5605", "static/js/element-ui/lib/link.js": "312480ee5f2292f314346434faa3518f", "static/js/element-ui/lib/loading.js": "9bb5753b57ee274668c5748d38eac2ab", "static/js/element-ui/lib/main.js": "92a26937eaac62367d2b81ebec36d9f0", "static/js/element-ui/lib/menu-item-group.js": "2098666a7bbd0c271cc084c3924d4ec3", "static/js/element-ui/lib/menu-item.js": "bf8db518c9b73c9cd3771dabf91bcaca", "static/js/element-ui/lib/menu.js": "0188cd906e1159936cbee28980fbec39", "static/js/element-ui/lib/message-box.js": "e275b026ca058dff799673d5740b6e22", "static/js/element-ui/lib/message.js": "0f64248045730556999011fa779d1dfa", "static/js/element-ui/lib/notification.js": "6190c53c839faa5b01a815de6769c899", "static/js/element-ui/lib/option-group.js": "f2759330e59d0518f60a6c5547d3c827", "static/js/element-ui/lib/option.js": "1c418a74feafdc73cfa5360764f1b90a", "static/js/element-ui/lib/page-header.js": "6a9f088700adf20d389cc542e6a0deaf", "static/js/element-ui/lib/pagination.js": "17b696cc294ca8bbc37af7b1c5968f7a", "static/js/element-ui/lib/popconfirm.js": "6bcceddbd44e456d716b66a018beaf7c", "static/js/element-ui/lib/popover.js": "a5cf39911205f08d5e5691a158bd81c6", "static/js/element-ui/lib/progress.js": "6f206f6d72b0622ea6a6c2fccd062bb5", "static/js/element-ui/lib/radio-button.js": "efdf34b9f8c565b99aeae587e11eb87a", "static/js/element-ui/lib/radio-group.js": "93003dbff84a768dbb1a12e7d6e7796a", "static/js/element-ui/lib/radio.js": "66201ad4319bcf5330d5711537cb85c6", "static/js/element-ui/lib/rate.js": "d57f0eede17b4ff2c80fbc3a5899d030", "static/js/element-ui/lib/row.js": "4d92917609e4db63a0acc64bca5d9ab4", "static/js/element-ui/lib/scrollbar.js": "76120f65f535b535d19fc1e820e959d5", "static/js/element-ui/lib/select.js": "5e900a932de2f3be38ef8f436fa201e4", "static/js/element-ui/lib/slider.js": "faf0a48d331a5c869c7bf7c8a07abe58", "static/js/element-ui/lib/spinner.js": "83f0cb9cc66bba831681c58c230890fd", "static/js/element-ui/lib/step.js": "cd744e288e5eeb55dde52cbe0909016d", "static/js/element-ui/lib/steps.js": "7cb3835a98f56194a2ca85cbeb197bf2", "static/js/element-ui/lib/submenu.js": "07584a153decdad6f82fe2de4e565f15", "static/js/element-ui/lib/switch.js": "aa60bca689f5db14f3bad0fcd8cb3ece", "static/js/element-ui/lib/tab-pane.js": "9cba2ce5089e06c7a6f9560687e0e150", "static/js/element-ui/lib/table-column.js": "bcf79a6f1de840db943cdee7c4e41921", "static/js/element-ui/lib/table.js": "f017fc250b36fbfebeef1b462b6984f8", "static/js/element-ui/lib/tabs.js": "f19f934d0ee0cb9b4234c9d81301f0b7", "static/js/element-ui/lib/tag.js": "66492bd336c57f37bbd9da3cac97b51c", "static/js/element-ui/lib/time-picker.js": "d2561b513c3dfdbb57f34f3f6855bd5b", "static/js/element-ui/lib/time-select.js": "c562bd018cb94d0a5f993a23bdb2e687", "static/js/element-ui/lib/timeline-item.js": "84ce5c070a064909355b56610bd8ff2f", "static/js/element-ui/lib/timeline.js": "e66393cdd8e7a22413acad9ab1f6f8af", "static/js/element-ui/lib/tooltip.js": "7f6a6999519c70a0485d2ae7d03ea6ab", "static/js/element-ui/lib/transfer.js": "b5825b45e447c403e3b0833e7975fa96", "static/js/element-ui/lib/tree.js": "16346c71a54ffbaddc37b635296704ed", "static/js/element-ui/lib/upload.js": "1682818d886774e15dd8267e40d81ecb", "static/js/element-ui/lib/directives/mousewheel.js": "d57acb00ba5ec4322dd7b4e37dedbb69", "static/js/element-ui/lib/directives/repeat-click.js": "ee7cb0bb5822588e21af009528f04490", "static/js/element-ui/lib/locale/format.js": "471399a5f5d8d53d46cb7e2fcad68d0d", "static/js/element-ui/lib/locale/index.js": "7f48cd4280e7b5be536b37a33af7a441", "static/js/element-ui/lib/locale/lang/af-ZA.js": "1cab2e3d954eaf982bff6464654f162a", "static/js/element-ui/lib/locale/lang/ar.js": "a973ecdc856fa1ea02fc3a1f1d436ddb", "static/js/element-ui/lib/locale/lang/bg.js": "100d5a70d89307668e0a512d953bbbd3", "static/js/element-ui/lib/locale/lang/ca.js": "f85b7ef8722b22ab636c540dc90c9aae", "static/js/element-ui/lib/locale/lang/cs-CZ.js": "5b0aeb0ab3f5177a7d2ae975db11ff20", "static/js/element-ui/lib/locale/lang/da.js": "7b04b9b382da5785b105084c0b1895fc", "static/js/element-ui/lib/locale/lang/de.js": "165678f7ff2d5c05c1b196c1fe36919f", "static/js/element-ui/lib/locale/lang/ee.js": "77cab864f3b5c0d02700606da528e0c9", "static/js/element-ui/lib/locale/lang/el.js": "1ab144c6f49d289c320e8672fdd78d05", "static/js/element-ui/lib/locale/lang/en.js": "fbea06cf549fdf1bc8107e258676825d", "static/js/element-ui/lib/locale/lang/eo.js": "91b4bd398fd27e80d331675e0567eae8", "static/js/element-ui/lib/locale/lang/es.js": "e1845d63e50ee4f117aa16c8b7756cc7", "static/js/element-ui/lib/locale/lang/eu.js": "6851cf67a17a2988f1e905557ebba981", "static/js/element-ui/lib/locale/lang/fa.js": "67763039da687c77702e59237ce66e04", "static/js/element-ui/lib/locale/lang/fi.js": "0b35a1d0fb672b74e243a5f41defdd70", "static/js/element-ui/lib/locale/lang/fr.js": "64c0cbb50596da4841223e8c85c34c1b", "static/js/element-ui/lib/locale/lang/he.js": "2a3719ffb84a33ed3dd1e9799c03a3dd", "static/js/element-ui/lib/locale/lang/hr.js": "81661a420afc076d1792c21f0432a50e", "static/js/element-ui/lib/locale/lang/hu.js": "0fa9fc603c3f5ad1bbbab426db5fa3cf", "static/js/element-ui/lib/locale/lang/hy-AM.js": "a6d88e26e14fd663a5aebe98623b1e5d", "static/js/element-ui/lib/locale/lang/id.js": "ace3933e6fba75bf17787c34580d1430", "static/js/element-ui/lib/locale/lang/it.js": "ddbaa0c9de79f04c13510abf9e9fe347", "static/js/element-ui/lib/locale/lang/ja.js": "927e2dbb268276b854f4ab58486c029d", "static/js/element-ui/lib/locale/lang/kg.js": "321e5d0e5cd78eb71baeea679f9866ad", "static/js/element-ui/lib/locale/lang/km.js": "712afcf5ce5bd04235c9da5cabd3211c", "static/js/element-ui/lib/locale/lang/ko.js": "a1462b4c5f54be3312a11745525baf79", "static/js/element-ui/lib/locale/lang/ku.js": "0843793ace9f2be0d749f90ae499fd25", "static/js/element-ui/lib/locale/lang/kz.js": "7da2b07e5b87618e3a7176ddfe04887a", "static/js/element-ui/lib/locale/lang/lt.js": "9525aa7bd194094651d15525bd32d060", "static/js/element-ui/lib/locale/lang/lv.js": "6a911e229e2501001d303a8fdf425d32", "static/js/element-ui/lib/locale/lang/mn.js": "2b91da6fd41d16584339b227082dd47f", "static/js/element-ui/lib/locale/lang/nb-NO.js": "91463e2c92f200c50e1dccd73e03695f", "static/js/element-ui/lib/locale/lang/nl.js": "feab63aa44ae350f99f1d484a91d942c", "static/js/element-ui/lib/locale/lang/pl.js": "a27556391421cdbf5d2424d38955b645", "static/js/element-ui/lib/locale/lang/pt-br.js": "1a7d47d3cca171739de21a0cc99387f1", "static/js/element-ui/lib/locale/lang/pt.js": "87ef9e7ed1f27c582e7faabc406cf483", "static/js/element-ui/lib/locale/lang/ro.js": "b608c43b34a6975d98e4b7b2b7295402", "static/js/element-ui/lib/locale/lang/ru-RU.js": "0504c7d75043cb31bcb692a689743cef", "static/js/element-ui/lib/locale/lang/sk.js": "3dd18f7fd8a017f994feba8b6d9a0890", "static/js/element-ui/lib/locale/lang/sl.js": "9ce57a4806c0740551d7dc51e786c86e", "static/js/element-ui/lib/locale/lang/sr.js": "d2ff6141f3ecd471a8cf846a24d7a8ed", "static/js/element-ui/lib/locale/lang/sv-SE.js": "3ca29384088f1f47398278654fa3011c", "static/js/element-ui/lib/locale/lang/ta.js": "b4de5e6d6863d34dfa6e98ccf495d504", "static/js/element-ui/lib/locale/lang/th.js": "cf6e83d1c133279705c8296ef90e775b", "static/js/element-ui/lib/locale/lang/tk.js": "7c799830a4ba20120bc2e7bccf3419d8", "static/js/element-ui/lib/locale/lang/tr-TR.js": "061e2668858c3654f3bf6750276546be", "static/js/element-ui/lib/locale/lang/ua.js": "399ff5fe5470ec3e4a57f88d1ae6fe6b", "static/js/element-ui/lib/locale/lang/ug-CN.js": "fa8d94b219647056165f1baaa843b5e6", "static/js/element-ui/lib/locale/lang/uz-UZ.js": "3607e6bf57b1d37e917736c460d7dd63", "static/js/element-ui/lib/locale/lang/vi.js": "3c54c8dec1d0c8afccd03efb53a0b53c", "static/js/element-ui/lib/locale/lang/zh-CN.js": "0b60542152156fa18a045d408bed61ea", "static/js/element-ui/lib/locale/lang/zh-TW.js": "d886891267165c838c345606c972856e", "static/js/element-ui/lib/mixins/emitter.js": "f38340cf5d69582efda171286efafa65", "static/js/element-ui/lib/mixins/focus.js": "b113513900bcf212968edd814c163760", "static/js/element-ui/lib/mixins/locale.js": "9b538e9a7f8d56c2c1f85984a9d4b2d2", "static/js/element-ui/lib/mixins/migrating.js": "7774a8a036683cd8b2b3340e0a63b132", "static/js/element-ui/lib/theme-chalk/alert.css": "4a233d163ac6641263075f6e7c4821db", "static/js/element-ui/lib/theme-chalk/aside.css": "edcc490fb0053f1cce4a71a5c047bdf9", "static/js/element-ui/lib/theme-chalk/autocomplete.css": "47ce2bcdfb1b1b08f5c623f9387391ff", "static/js/element-ui/lib/theme-chalk/avatar.css": "58a5fedd9b307c75a19c3b0533d813b8", "static/js/element-ui/lib/theme-chalk/backtop.css": "8b2ca61408dad479b881960763d3db98", "static/js/element-ui/lib/theme-chalk/badge.css": "ea8bfd89345e13ad428bdfb5574237b4", "static/js/element-ui/lib/theme-chalk/base.css": "f25cc484087487b1e1ef6ce8bf62ca44", "static/js/element-ui/lib/theme-chalk/breadcrumb-item.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/breadcrumb.css": "16d9a27c1cd682756f3173c95a83d0ed", "static/js/element-ui/lib/theme-chalk/button-group.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/button.css": "41b0f4e08fdd9eedc5a38b9713ddd904", "static/js/element-ui/lib/theme-chalk/calendar.css": "982528766ad2393b8617dbbf05dfaa7f", "static/js/element-ui/lib/theme-chalk/card.css": "48a5b07b8b0a6fb0c5eb6eafacdd0cd5", "static/js/element-ui/lib/theme-chalk/carousel-item.css": "6c97d1a467ad4b215ee69ed21754a728", "static/js/element-ui/lib/theme-chalk/carousel.css": "53761931f0070d80e77e2073f18b1d23", "static/js/element-ui/lib/theme-chalk/cascader-panel.css": "d897ade7fa060cfb4a7fc14503e61a44", "static/js/element-ui/lib/theme-chalk/cascader.css": "bdded6ec78f1471b771f1c4e4e07b596", "static/js/element-ui/lib/theme-chalk/checkbox-button.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/checkbox-group.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/checkbox.css": "515b3e4a1c684ea0538292380b38ca5f", "static/js/element-ui/lib/theme-chalk/col.css": "cd14b27ef81a2fc60079ecf869bca3cd", "static/js/element-ui/lib/theme-chalk/collapse-item.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/collapse.css": "9b8b3424302f453728fe80719c97de40", "static/js/element-ui/lib/theme-chalk/color-picker.css": "083fba820bd7440f18cf93af1d6a31dd", "static/js/element-ui/lib/theme-chalk/container.css": "e79009df433ab4403029e519ddc3340d", "static/js/element-ui/lib/theme-chalk/date-picker.css": "bd299472f79e6132d0dc6845176cadbe", "static/js/element-ui/lib/theme-chalk/dialog.css": "d30bf0818a97168906dc243c0e04a1bf", "static/js/element-ui/lib/theme-chalk/display.css": "c110a2385504d5ee6adb4377365270d7", "static/js/element-ui/lib/theme-chalk/divider.css": "6e52365004d46117ecfb085bc38479b0", "static/js/element-ui/lib/theme-chalk/drawer.css": "6ba86d8fe04df4d3549e4b3fc950bcb4", "static/js/element-ui/lib/theme-chalk/dropdown-item.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/dropdown-menu.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/dropdown.css": "246cd4c0d0f8d4493c5e987d0e330925", "static/js/element-ui/lib/theme-chalk/footer.css": "0a75eee620a1eec96cf1718047bde916", "static/js/element-ui/lib/theme-chalk/form-item.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/form.css": "8521897ad3a147046fd049b37f6775c8", "static/js/element-ui/lib/theme-chalk/header.css": "4bf1a1be0a4af7778c092b276458787a", "static/js/element-ui/lib/theme-chalk/icon.css": "83dc46b9fc11c99b5c0511d0a9a9987e", "static/js/element-ui/lib/theme-chalk/image.css": "32f4b078377ca3c364767e55bba62ede", "static/js/element-ui/lib/theme-chalk/index.css": "2414fd307c22e07b681e50e0720cbc23", "static/js/element-ui/lib/theme-chalk/infinite-scroll.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/infiniteScroll.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/input-number.css": "a30d3d47860db05882de8597cbb35a3f", "static/js/element-ui/lib/theme-chalk/input.css": "88d9749aad3cfd64720e1090474a8573", "static/js/element-ui/lib/theme-chalk/link.css": "b29ddaa7be960e0dabeee5b23fed982c", "static/js/element-ui/lib/theme-chalk/loading.css": "ee99b8ad8874ed9e7df64b7dab4159a6", "static/js/element-ui/lib/theme-chalk/main.css": "9923eb608c01cf001501708e1c8df0bc", "static/js/element-ui/lib/theme-chalk/menu-item-group.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/menu-item.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/menu.css": "30fa354841214de1919e49d8e831a5bf", "static/js/element-ui/lib/theme-chalk/message-box.css": "6fe773d1292f0acbc9ddad9153548c58", "static/js/element-ui/lib/theme-chalk/message.css": "6ab057c82772aa70e8ecc9902d4284ec", "static/js/element-ui/lib/theme-chalk/notification.css": "875a37cf12d647c1dee9e7e467ea01fb", "static/js/element-ui/lib/theme-chalk/option-group.css": "b3a13247682f590bb17b55517326ec39", "static/js/element-ui/lib/theme-chalk/option.css": "1698a68c50ff2618face813daa89982e", "static/js/element-ui/lib/theme-chalk/page-header.css": "3508c570c80476d68b49a13d4c14e27c", "static/js/element-ui/lib/theme-chalk/pagination.css": "93a1866f64772a9d877796cc27f59e2b", "static/js/element-ui/lib/theme-chalk/popconfirm.css": "24183a243d4c79db8c383f296300a5be", "static/js/element-ui/lib/theme-chalk/popover.css": "4d19d78df7b0a3157772b86018648ee9", "static/js/element-ui/lib/theme-chalk/popper.css": "36fcdf5712174c8177fe2fcb73977ed5", "static/js/element-ui/lib/theme-chalk/progress.css": "a3d92e7241d275e11982945ad7c7e284", "static/js/element-ui/lib/theme-chalk/radio-button.css": "d126d35d880b72736a6d24b94170b9eb", "static/js/element-ui/lib/theme-chalk/radio-group.css": "86af00fe6b72f9005bf39e3481691586", "static/js/element-ui/lib/theme-chalk/radio.css": "a3110894bf315e7eedf9111a951840f6", "static/js/element-ui/lib/theme-chalk/rate.css": "0f33a694a0e389f05cd24aeec4b1765e", "static/js/element-ui/lib/theme-chalk/reset.css": "3db1afd65a0400d0d643f705d44250f5", "static/js/element-ui/lib/theme-chalk/row.css": "b1ac86fc178549496bb90eec4ba5da5e", "static/js/element-ui/lib/theme-chalk/scrollbar.css": "23449b1c81727518ce5d914579ee6174", "static/js/element-ui/lib/theme-chalk/select-dropdown.css": "f95dc3aef5e200e495d51f00a81b4e20", "static/js/element-ui/lib/theme-chalk/select.css": "d16e8c09f8fa26ec7d03b5652cdbb797", "static/js/element-ui/lib/theme-chalk/slider.css": "c7db7b54589f0ba57dd2b3a1057bfb90", "static/js/element-ui/lib/theme-chalk/spinner.css": "5b5d1e5f3e4422c063adc569a8346fca", "static/js/element-ui/lib/theme-chalk/step.css": "cf252484e262941d6d6c081042e38074", "static/js/element-ui/lib/theme-chalk/steps.css": "ac6f8637955a659b97ea3548a20fcfba", "static/js/element-ui/lib/theme-chalk/submenu.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/switch.css": "dbe3a9344c8c3594fce88715c118edf9", "static/js/element-ui/lib/theme-chalk/tab-pane.css": "d41d8cd98f00b204e9800998ecf8427e", "static/js/element-ui/lib/theme-chalk/table-column.css": "e11267cebea4826211fbfbc1f88c6ec8", "static/js/element-ui/lib/theme-chalk/table.css": "facf1d994f3fda680e22163b2e4148c2", "static/js/element-ui/lib/theme-chalk/tabs.css": "ec342796738d9d1a87137c0451c6b784", "static/js/element-ui/lib/theme-chalk/tag.css": "52178a68247a9fe015ccb5e965ce3fd3", "static/js/element-ui/lib/theme-chalk/time-picker.css": "7a25549a612c52a82dcc265f20566f0c", "static/js/element-ui/lib/theme-chalk/time-select.css": "a230a27e1eae140dc38e608de2651ba6", "static/js/element-ui/lib/theme-chalk/timeline-item.css": "743e68350200b578c75506697a7f5789", "static/js/element-ui/lib/theme-chalk/timeline.css": "b5d2b9a92b38005cff9a5752246992e1", "static/js/element-ui/lib/theme-chalk/tooltip.css": "4722109edb63d5c732ca61ecb3d47175", "static/js/element-ui/lib/theme-chalk/transfer.css": "14928e12a3985c3d0e516e6799601d65", "static/js/element-ui/lib/theme-chalk/tree.css": "5f5d85942e52967f08d4f1698cee2683", "static/js/element-ui/lib/theme-chalk/upload.css": "077d8dc834e6d88194879db89ff54bc5", "static/js/element-ui/lib/theme-chalk/fonts/element-icons.ttf": "4b1a4d348209ad29243b0a042de1b557", "static/js/element-ui/lib/theme-chalk/fonts/element-icons.woff": "f717deee44e7fcc757c6c1ade5cce443", "static/js/element-ui/lib/transitions/collapse-transition.js": "354e780b8a86771cd737928485f7d798", "static/js/element-ui/lib/umd/locale/af-ZA.js": "0fc2ac6b8936f2cd76256d2672b8b426", "static/js/element-ui/lib/umd/locale/ar.js": "18bbfc189ca1a3da137e560c112e0aba", "static/js/element-ui/lib/umd/locale/bg.js": "2c6245f699c62437fa7ee0d163cdd65c", "static/js/element-ui/lib/umd/locale/ca.js": "f494da9c984efa37bb7aff8be66d026e", "static/js/element-ui/lib/umd/locale/cs-CZ.js": "6554aa484f5ec7b4d1e3ad176cc69b71", "static/js/element-ui/lib/umd/locale/da.js": "0fb807a6d9d65b4d438c1e8563e369c4", "static/js/element-ui/lib/umd/locale/de.js": "835ff2b17f107ff50be524805b201f3c", "static/js/element-ui/lib/umd/locale/ee.js": "a363d6f90a0acfea8f31f1a6be49aeee", "static/js/element-ui/lib/umd/locale/el.js": "e9b50aee66128071682125d1e9d98c23", "static/js/element-ui/lib/umd/locale/en.js": "eccc9db8251b0ef6f69337f39234d423", "static/js/element-ui/lib/umd/locale/eo.js": "373c79e4396edec41165343f5a889f99", "static/js/element-ui/lib/umd/locale/es.js": "f33d213c3974b2acdc167886b61f2e47", "static/js/element-ui/lib/umd/locale/eu.js": "62bcc16524b72c7094a20162832b36b5", "static/js/element-ui/lib/umd/locale/fa.js": "273a1e35316564355fed5eb3bfeb2ab8", "static/js/element-ui/lib/umd/locale/fi.js": "9f9889caf4adcffc6cc78c229c457a7a", "static/js/element-ui/lib/umd/locale/fr.js": "ecd5286ed9b8faf83a56bbe449df9e00", "static/js/element-ui/lib/umd/locale/he.js": "db9119fe9dc05c872421bf4758de2eca", "static/js/element-ui/lib/umd/locale/hr.js": "9396ffd6cc82eeb3a09ce0d8ed1dd483", "static/js/element-ui/lib/umd/locale/hu.js": "1e6962cb413392e68dae16680bcd6370", "static/js/element-ui/lib/umd/locale/hy-AM.js": "85701cecfaf21f5813aeebe91b2b7560", "static/js/element-ui/lib/umd/locale/id.js": "1b99f644e81cf63fc7cecd34746dc66e", "static/js/element-ui/lib/umd/locale/it.js": "4fd6e8ba9c2d5fe7c9e74f753b95379c", "static/js/element-ui/lib/umd/locale/ja.js": "567701dd5feb9804153acefabfaa44b9", "static/js/element-ui/lib/umd/locale/kg.js": "4b4067ad2fb93f84a6c9e8c35de483e9", "static/js/element-ui/lib/umd/locale/km.js": "c69fc83984c03a7a4f04cdc6967cca58", "static/js/element-ui/lib/umd/locale/ko.js": "9bdf5bf54251017cb60636c8b4fe0ecf", "static/js/element-ui/lib/umd/locale/ku.js": "581e50d5471c9ec4c535ebcdb5ba68c8", "static/js/element-ui/lib/umd/locale/kz.js": "3113fafaec1e5f4ae75275df004a438f", "static/js/element-ui/lib/umd/locale/lt.js": "1f9ab75d239d73dd6450d20719d14e4c", "static/js/element-ui/lib/umd/locale/lv.js": "06ed0fdc7d15aaaa254ddafd5cc2d984", "static/js/element-ui/lib/umd/locale/mn.js": "a30050261567e96fcae3eb8184077738", "static/js/element-ui/lib/umd/locale/nb-NO.js": "8f696776c06ee3e12e43a4cdd6cce736", "static/js/element-ui/lib/umd/locale/nl.js": "99258a51fa6d3bb2eaf24687b08681b6", "static/js/element-ui/lib/umd/locale/pl.js": "727bf2dd0d8c2917c7d7b67651fa4e2c", "static/js/element-ui/lib/umd/locale/pt-br.js": "9b175360cb8d7199055506a7f57e7d4b", "static/js/element-ui/lib/umd/locale/pt.js": "1626947603ba9812628e5388526e02e7", "static/js/element-ui/lib/umd/locale/ro.js": "fb9eef3d28ce9ecc7ad83b41caf17864", "static/js/element-ui/lib/umd/locale/ru-RU.js": "02776c80fbb8f65298ef9156d79d11ee", "static/js/element-ui/lib/umd/locale/sk.js": "34457b2d2b6c75a8025a0eba36931523", "static/js/element-ui/lib/umd/locale/sl.js": "2d186805c45ce5292f9dfa2f2255677f", "static/js/element-ui/lib/umd/locale/sr.js": "57009e2dc4bc4de61794b29c659a132d", "static/js/element-ui/lib/umd/locale/sv-SE.js": "315c4a945b2b89e206ade38990a7e9f9", "static/js/element-ui/lib/umd/locale/ta.js": "da94b97bf773e49867bdd4c91b738cdf", "static/js/element-ui/lib/umd/locale/th.js": "76dde843e7f874f2e45cc1ea1dadc638", "static/js/element-ui/lib/umd/locale/tk.js": "710dfdc6ba97ebc5b6ed5e5c6ba40c79", "static/js/element-ui/lib/umd/locale/tr-TR.js": "2af8060b6746a342827a94ee1d45972c", "static/js/element-ui/lib/umd/locale/ua.js": "c2240b43e3d70e4b2c25db2580d5b9ad", "static/js/element-ui/lib/umd/locale/ug-CN.js": "6ddaea800aed2638e2d02b06322ceb07", "static/js/element-ui/lib/umd/locale/uz-UZ.js": "ddb5d919cc3756722a2980f995086225", "static/js/element-ui/lib/umd/locale/vi.js": "764578d2497752bec40d8cf3982501d5", "static/js/element-ui/lib/umd/locale/zh-CN.js": "af0b3424682dedebc3064a17c8c8f047", "static/js/element-ui/lib/umd/locale/zh-TW.js": "ee9c9d202116bd367a54b6542b41e9ac", "static/js/element-ui/lib/utils/after-leave.js": "5b6eab2e19137f22b7f155c64c127b5a", "static/js/element-ui/lib/utils/aria-dialog.js": "584e07ba3bcd14fe02d84d456461a381", "static/js/element-ui/lib/utils/aria-utils.js": "bccd0c0998fa688607a28aef5d3954be", "static/js/element-ui/lib/utils/clickoutside.js": "1d2c86f338286924e65ba0beb1e9275b", "static/js/element-ui/lib/utils/date-util.js": "f7bc86e6068c4c4f23c86f7d6fafce77", "static/js/element-ui/lib/utils/date.js": "63bca9e7033fcf66fa9b93ccaf4dad0f", "static/js/element-ui/lib/utils/dom.js": "7a5bf584e8d01360f759c1a78305219c", "static/js/element-ui/lib/utils/merge.js": "48d430909e3583f3010bf4e844bd1163", "static/js/element-ui/lib/utils/popper.js": "f90f278e90fdff8f4afd382145b2db9b", "static/js/element-ui/lib/utils/resize-event.js": "96628f5f98b18fee3c4972e8f39b6481", "static/js/element-ui/lib/utils/scroll-into-view.js": "a2847b7f5d7ae6860f66669f645ce861", "static/js/element-ui/lib/utils/scrollbar-width.js": "6dd9aafa50bf2028e1fa542feb995f89", "static/js/element-ui/lib/utils/shared.js": "e9becb8ca1ad71a5d64d989e28778b11", "static/js/element-ui/lib/utils/types.js": "b76cda09d6b97c9598681b372f3efd6e", "static/js/element-ui/lib/utils/util.js": "c4013004a996dd7d030f779c721c7c2d", "static/js/element-ui/lib/utils/vdom.js": "a72f60b0134bd47f49c537ad28982bad", "static/js/element-ui/lib/utils/vue-popper.js": "ea42e858259fa582acfadc15265b1d30", "static/js/element-ui/lib/utils/menu/aria-menubar.js": "172afd68add2537d32f299e5369c8ad8", "static/js/element-ui/lib/utils/menu/aria-menuitem.js": "2c51a053252827845cce692634443771", "static/js/element-ui/lib/utils/menu/aria-submenu.js": "2d5f39c9bc644388bc57563ee8ecae87", "static/js/element-ui/lib/utils/popup/index.js": "8df7b5c153e9187359c20ab602ad6740", "static/js/element-ui/lib/utils/popup/popup-manager.js": "68856d04bb10de1e999fc10693a73999", "tests/README.md": "bad9b5c1d18429a006532bb7faebd117", "tests/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/assets/\u5168\u90e8\u6d4b\u8bd5.png": "dbed30a78bcec42c7523471fcf514700", "tests/assets/\u6253\u5f00pycharm\u7684pytest\u529f\u80fd.png": "b849c96411e8d82fc8a50f0ff816c1f7", "tests/assets/\u6267\u884c\u6d4b\u8bd5\u7528\u4f8b.png": "39116cca185c73ae3d4517ee0f84133a", "tests/assets/\u6d4b\u8bd5\u5931\u8d25.png": "d04228741f3da0b6e977211fe41c6438", "tests/assets/\u6d4b\u8bd5\u6210\u529f.png": "6669c0c45442db35ab92b06bf9993c06", "tests/test_algorithms/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_algorithms/test_linear_algebra/test_linear_space.py": "6df796d85cb787fd7f045a0ab5c63a1c", "tests/test_algorithms/test_linear_algebra/test_matrix_cross.py": "4b574f0a6131847d802761bd83a6d6ae", "tests/test_algorithms/test_linear_algebra/test_matrix_determinant.py": "041bf9f620c2dcb093bf20cf4ed20c72", "tests/test_algorithms/test_linear_algebra/test_matrix_diagonal.py": "b8afb32ca59752683d9c605a9885e2f5", "tests/test_algorithms/test_linear_algebra/test_matrix_divide.py": "2b63a05c372c145b05920fe5c8e7ee1d", "tests/test_algorithms/test_linear_algebra/test_matrix_dot.py": "3f80ff34c4b639fc33344ce8c23ab153", "tests/test_algorithms/test_linear_algebra/test_matrix_eigenvalue.py": "0edcf7066a5e3143d089a0222d8659be", "tests/test_algorithms/test_linear_algebra/test_matrix_inverse.py": "8f502aabb41e4faaae06663c45fe52a9", "tests/test_algorithms/test_linear_algebra/test_matrix_multiply.py": "45423c811a4db180b269945908595383", "tests/test_algorithms/test_linear_algebra/test_matrix_transpose.py": "d92dc7600a77f090f5f897dff9d099e4", "tests/test_algorithms/test_linear_algebra/test_ones.py": "faa005c5fce0def31dcc4aa83861f4de", "tests/test_algorithms/test_linear_algebra/test_reshape.py": "ff0f15e2ba03d7bd3ee086deab31f9d0", "tests/test_algorithms/test_linear_algebra/test_shape.py": "29a411d526e34d9070155e1a198195db", "tests/test_algorithms/test_linear_algebra/test_zeros.py": "5c8802a5d2bd87dc00e67e7f2f892c9e", "tests/test_algorithms/test_linear_algebra/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_algorithms/test_statistics/run_distribution.py": "fd81a148edd8cb40ed4390dde9c9af9d", "tests/test_algorithms/test_statistics/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_data_adapter/run_dump_load.py": "4b7572b7ce9b3c6ce0ed6d6811b64e6b", "tests/test_data_adapter/test_array.py": "717162f0f2da526e3a18456b12dfbd10", "tests/test_data_adapter/test_data_frame.py": "e56b02672c2d979b495c24fdc113eafc", "tests/test_data_adapter/test_detector.py": "92495e21a9b05068d73833d4d6a8f3f9", "tests/test_data_adapter/test_universal.py": "f69c1055e69f2e844596a3b80ab20c88", "tests/test_data_adapter/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_dev/__init__.py": "4277383d96a0b390d7299069af45c987", "tests/test_dev/test_doc/test_file_tree_node.py": "362ef3dedb9be7eca57248be44f93e3f", "tests/test_dev/test_doc/__init__.py": "d2b42ea8d4d60b51c772335633e18078", "tests/test_dev/test_doc/test_case_for_file_tree_node/main.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_dev/test_doc/test_case_for_file_tree_node/README.md": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_dev/test_doc/test_case_for_file_tree_node/_exclude.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_dev/test_doc/test_case_for_file_tree_node/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_dev/test_doc/test_case_for_file_tree_node/sub_dir/core1.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_dev/test_doc/test_case_for_file_tree_node/sub_dir/test1.txt": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_dev/test_doc/test_case_for_file_tree_node/sub_dir/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_dev/test_doc/test_case_for_file_tree_node/sub_dir2/test2.txt": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_dev/test_doc/test_case_for_file_tree_node/sub_dir2/__init__.pyw": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_dev/test_doc/test_case_for_file_tree_node/sub_dir3/core3.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_dev/test_doc/test_case_for_file_tree_node/sub_dir3/test3.txt": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_widgets/test_maccabe/mccaberun.py": "17cdb5bf5d1157aeba779172c294ad95", "tests/test_widgets/test_maccabe/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_widgets/test_normal/test_display.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_widgets/test_util/test_util.py": "8db14adf951ddecf73952d6841b2aa93", "tests/test_pmtoolbox/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "tests/test_ui/test_ipython_support.py": "4e109800eedb8113b7da674b05e08bac", "tests/test_ui/test_pmgpanel.py": "a8d7ede78b58ae7be4f738f322f9be98", "tests/test_workspace2/test_data_manager.py": "bcf209b27357532443d75b3c368e68c7", "tests/test_workspace2/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "utils/environ.py": "b8bfeb3f35922c0337c197abea87829f", "utils/http_client.py": "3a5ced47c6f09803ad68b4da5b59d7f1", "utils/path.py": "93a5404fabe530d18791aaafecfd1b40", "utils/platform.py": "2da872dbe9abece68ed95e204c2ff655", "utils/__init__.py": "7e6452c9cf8ce5c4c856dcbdd3cf6b2f", "utils/debug/debuggerprocess.py": "0fe81aa6f61d81df42ef9afcc2d25bd5", "utils/debug/pdbtest.py": "b79fc6b32634f06788247e8d58402d41", "utils/debug/pmdebug.py": "d384421931a8e48e26cab0fba3c4241c", "utils/debug/test2.py": "b53f37a4994356be41f962afcb0a0b10", "utils/debug/__init__.py": "350446184463764465845852bd3ef50e", "utils/doc/file_tree.py": "36bd540c3b4c32b184dee9f2182f0819", "utils/doc/index.rst": "2488cd21dbd21544665f802ab3f0045e", "utils/doc/rst_generator.py": "fca7802a37b44b4a62924aa7521d6a2a", "utils/doc/__init__.py": "892fd1aca5b1cae0f580cd809094dad2", "utils/doc/doc_guide/choose_position.md": "482843264c94f2bd50c448fec43a3cc3", "utils/doc/doc_guide/compile.md": "eeee41c8eefce5234f176c76a0b15ee9", "utils/doc/doc_guide/index.rst": "0f1e7af0e52ee3073edf77f0eb9b0de6", "utils/doc/doc_guide/md_guide.rst": "809611fdb43d6b05edfadf5cc3a8c7c7", "utils/doc/doc_guide/rst_guide.rst": "8fa7462d9a5b56d6c5a67646e2ba4acc", "utils/doc/doc_guide/use_chevereto.rst": "3cb77997069a4c98b34360c9490f5733", "utils/doc/template/module.rst_t": "3af408a56d3d4adb3acfe09f653128ac", "utils/doc/template/package.rst_t": "801c621e304bc973e95f2e2519de04f8", "utils/doc/template/toc.rst_t": "a7cf368655e8fd91249cee24406aaff8", "utils/doc_figures/\u5e15\u7d2f\u6258\u56fe.jpg": "17c5690d782d1554a0eee747b496dcac", "utils/doc_figures/\u5e94\u7528\u5de5\u5177\u680f.png": "52d6b3bbabc43c2d368ce7748e8a9c7d", "utils/doc_figures/\u5f02\u5e38\u503c\u68c0\u6d4b": "49f442e7d72e05764e8353e36a824637", "utils/doc_figures/\u6807\u51c6\u79d1\u5b66\u8ba1\u7b97app\u754c\u9762\u539f\u578b.png": "5a6aa5719ae6d2ab0a3e2deeb4d9f841", "utils/doc_figures/\u9879\u76ee\u7ec4\u6210\u7ed3\u6784.png": "7d7896300a5e62d2fad7062bcba88aee", "utils/io/file_import.py": "d41d8cd98f00b204e9800998ecf8427e", "utils/io/piputil.py": "d45b0ab10e658c8bd1a260e7a7b0daca", "utils/io/__init__.py": "e9846ca05e2cf2393e2d36e130f7e84f", "utils/io/dbconnect/dbBaseTool.py": "964e1d0151fac429242dfdd163d6693b", "utils/io/dbconnect/dbConnectAccount.pkl": "410722595449e489f364425f6cd8c240", "utils/io/dbconnect/dbutils.py": "a83eac2b48d43e4bbb144b6ee30ac5a6", "utils/io/dbconnect/test_dbBaseTool_add_connection.py": "9777701d02960adae45e4ebc5afdabbc", "utils/io/dbconnect/test_dbBaseTool_query.py": "637037577573d38544ed3a801e4a8238", "utils/io/dbconnect/__init__.py": "5111d7ba85f86397e7318a88362fd502", "utils/io/fileutil/compressutils.py": "d79d280d2f6ddc66de6687a7b8201338", "utils/io/fileutil/encoding.py": "f70ddeb46e587c343134428e3a1d9b6c", "utils/io/fileutil/search_in_path.py": "3a369957683bec55df7e6e63cb3ce499", "utils/io/fileutil/variableutils.py": "e980e969d1f33da86e71e1207f282fe5", "utils/io/fileutil/__init__.py": "86e37c6a62dd940b6add84e2f3464697", "utils/io/fileutil/source/encoding/test_ascii.csv": "930b9d198c8fe4cf62cb40bf86deb059", "utils/io/fileutil/source/encoding/test_gb2312.csv": "b78fff031a5e71e40eaa348a628dd45e", "utils/io/fileutil/source/encoding/test_gbk.csv": "b78fff031a5e71e40eaa348a628dd45e", "utils/io/fileutil/source/encoding/test_utf8.csv": "b3c2e6ec634b81b2ebd348a3983fc165", "utils/io/fileutil/test/test_word_in_line.py": "0e2699f99428b73b86fdeb3fca2ee3b3", "utils/io/pmserial/pmqtreadserial.py": "7f31cf4aa590690a8c9e53b4d3599ce8", "utils/io/pmserial/readserial.py": "5cafed26a707eecdac54883dca6d2c33", "utils/io/pmserial/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "utils/settings/settings.py": "a65d80cc4a777b526b057b8efebd6989", "utils/settings/__init__.py": "43d16a290ee34cb378a6c6244e8c0a32", "utils/ui/translation.py": "a75ca908fa328d348adc9106554e35d7", "utils/ui/variableselect.py": "6623e3057f2291f6ad61ee1e55ac58c4", "utils/ui/__init__.py": "05099d0c6cd09966cb7621c9e57c2f13", "utils/ui/app/pmbasicapp.py": "a8cf494179389639ee3a68e39167cdc1", "utils/ui/app/__init__.py": "d41d8cd98f00b204e9800998ecf8427e", "utils/ui/app/test/basicapp_abnormal.py": "74d0fab0335ac9847dd589275e304c64", "utils/ui/app/test/basicapp_pareto.py": "522a51d208d3d32c68e9de0267a9f1fb", "utils/ui/src/balance.png": "0e13e9c8fd6853eedef19ac2ca0d9f01", "utils/ui/src/chemestry.png": "be3234787dcd5ad6b8c03cce7d142dfd", "utils/ui/src/electricity.png": "390b497d0687540630694599edc71dac", "utils/ui/src/engineering machinery.png": "099cdd52b97926617032ff66487cc311", "utils/ui/src/fracture.png": "e2dcccc7d7751d10b0e439a9a8b8f7d3", "utils/ui/src/function.png": "0a6dd73740c6800c913a5dc8556539cb", "utils/ui/src/gauge.png": "167e1ea4af8c9e504c30e734e7761488", "utils/ui/src/histogram.png": "231deddcfe9222512173e3d0b7829728", "utils/ui/src/ic_common_\u751f\u7269&\u81ea\u7136.png": "351343f4046d6d2c7a3405065d3a66a2", "utils/ui/src/loadwave.png": "95280a0b2dbc61a6c817d40e705f0379", "utils/ui/src/log.png": "117ef9da5ed765f2cc31f65bd2a49db9", "utils/ui/src/math_1.png": "a62c3b980b0f9332c56452c2dfac5a8c", "utils/ui/src/math_2.png": "d98240ad143bbc7cc739cac16b18e60a", "utils/ui/src/math_3.png": "0dbbea9ccc5e7cfa409a65e7ab0d16c4", "utils/ui/src/mechanics.png": "e730af4dc4083d52675826590d05dfa8", "utils/ui/src/money.png": "078a6fec99543566f7abd7f9d235cfc1", "utils/ui/src/motor.png": "cb1071e863f9d4108d509a1f0a29ce8e", "utils/ui/src/motor_2.png": "b71bdb729289a54a9330af929f477b39", "utils/ui/src/normal_distribution.png": "18785959972c6076e3201e41ba68d971", "utils/ui/src/physics.png": "67a6b64cee517d8678e2001ebd2af37d", "utils/ui/src/physics_2.png": "75796555027a4edc1bf441a69800de78", "utils/ui/src/plot_1.png": "484e1553dbd4d3b51dc986af7da9126d", "utils/ui/src/plot_2.png": "fa13d212836a556060df8fbee4dd8593", "utils/ui/src/plot_3.png": "bf548bcb5eba63b655fcaafa9af1e50e", "utils/ui/src/plot_4.png": "63658bb0aa5e10850d4cbcefb31bc5ec", "utils/ui/src/roboarm.png": "adc612a5fbe26cbc1ef9964078be30d0", "utils/ui/src/run.png": "fcf7e23f264801b79df841dabdab9417", "utils/ui/src/run_cell.png": "e40eb239205653b61fa2ba5a57f8d378", "utils/ui/src/settings_1.png": "4dc1f5c80740868b1cf0a4f50909dc20", "utils/ui/src/settings_2.png": "e8cd30a0264a6ee00fbe461e72a9fb6e", "utils/ui/src/settings_3.png": "2917d7c1b222d2e235a85f7e677935ca", "utils/ui/src/sinsidual.png": "371e0fdb35b52764d7d9c17774c36a07", "utils/ui/src/statistics.png": "3e1dd67456f93098bc0276c3b37e1b45", "utils/ui/src/statistics_2.png": "524756b6420d9a60cec2212e23112dbc", "utils/ui/src/statistics_3.png": "737c1756516487220aafa992210374bb", "utils/ui/src/viberation.png": "49ccb24861c08a92834e814bf8ea4767", "utils/ui/src/wave.png": "402b0e16aaf792596c734ee454ba25dd", "utils/ui/src/wave_2.png": "ce59ead6a036cca68b02950c9bdcb91a", "utils/ui/src/wave_history.png": "566e2a0ac92bf12f6291e0dd4d272091", "utils/ui/src/\u5206\u5b50\u751f\u7269\u5b66\u5e73\u53f0-\u7070.png": "6a5127ef0f94e54506455e0dd5d8d712", "utils/ui/src/\u751f\u547d\u5065\u5eb7\u4ea7\u4e1a_2\u751f\u7269\u533b\u836f.png": "c6f652bcc942e7c3c244d06726fcf063", "utils/ui/src/\u751f\u7269 (1).png": "9f4b502be694761d44c106a09a61be19", "utils/ui/src/\u751f\u7269 (2).png": "58da036ee4e5ead077590ebb65b80460", "utils/ui/src/\u751f\u7269 (3).png": "9cf816bfb76439a5f366e9beca9bea92", "utils/ui/src/\u751f\u7269 (4).png": "3b699f6ca234d635a8bc3274a7b44020", "utils/ui/src/\u751f\u7269.png": "4bcc3068ba68359848f293c23ac0b670", "utils/ui/src/\u751f\u7269\u8bc6\u522b (1).png": "021f770979cb264bc827ac041bab972b", "utils/ui/src/\u751f\u7269\u8bc6\u522b.png": "d73c19821dbdc9b46facd404afda33f4", "utils/ui/uiutil/datashowutil.py": "a88e7ef43145082c8278f6273e1bfb05", "utils/ui/uiutil/workspaceutil.py": "4a74ba5a7a176257d0e659d4be892301", "utils/ui/uiutil/__init__.py": "d611c6031ffff2a4570872c525a95660", "utils/ui/uiutil/formatting/textformat.py": "dee7f3523b301ceb8bdb0b35da44e456", "utils/ui/uiutil/formatting/__init__.py": "73a717186f47f07e16e0dcb8ed4ed76d", "widgets/frame_less_window.py": "e2a0208d327013c21bd75c71535d05af"}}
\ No newline at end of file
diff --git a/pyminer/app2.py b/pyminer/app2.py
index 0d1717df3c4973707136a920617a1195ad44c238..0fb9901c815ce569f113aece5b8f78c458d7e7b5 100644
--- a/pyminer/app2.py
+++ b/pyminer/app2.py
@@ -64,21 +64,21 @@ from typing import List, Callable, Tuple, ClassVar, Optional
from PySide2.QtCore import Signal, QTimer, Qt, QCoreApplication
from PySide2.QtGui import QCloseEvent, QCursor, QTextCursor, QResizeEvent, QMoveEvent, QFont, QIcon, QPixmap
from PySide2.QtWidgets import QApplication, QMessageBox, QSplashScreen, QStatusBar, QDialog, QVBoxLayout, QProgressBar
-import pmgwidgets
+import widgets
-pmgwidgets.in_unit_test = lambda: False
-from pmgwidgets import PMGToolBar
+widgets.in_unit_test = lambda: False
+from widgets import PMGToolBar
-from features.extensions.extensions_manager.manager import extensions_manager
-from features.main_window import base
-from features.io.settings import load_theme
-from features.interpretermanager.interpretermanager import InterpreterManagerWidget
-from features.util.update import UpdateTipClient
-from features.feedback import FeedbackClient
-from features.ui.widgets.controlpanel import PMPageExt
-from features.ui.pmwidgets import BaseMainWindow
-from features.io.exceptions import PMExceptions
-from features.pluginsmanager.pluginsmanager import MarketplaceForm
+from lib.extensions.extensions_manager.manager import extensions_manager
+from lib.main_window import base
+from lib.io.settings import load_theme
+from lib.interpretermanager.interpretermanager import InterpreterManagerWidget
+from lib.util.update import UpdateTipClient
+from lib.feedback import FeedbackClient
+from lib.ui.widgets.controlpanel import PMPageExt
+from lib.ui.pmwidgets import BaseMainWindow
+from lib.io.exceptions import PMExceptions
+from lib.pluginsmanager.pluginsmanager import MarketplaceForm
from load_modules import load_translator, load_fonts
@@ -144,7 +144,7 @@ class MainWindow(BaseMainWindow):
extensions: 如果指定,则仅加载指定的插件,否则加载全部插件
"""
super().__init__(parent)
- self.setWindowFlags(Qt.FramelessWindowHint)
+ # self.setWindowFlags(Qt.FramelessWindowHint) # 无边框
self.extension_names = extensions # 需要加载的插件名称
# settings = Settings()
t00 = time.time()
@@ -437,7 +437,7 @@ class MainWindow(BaseMainWindow):
def main_install_update(self):
closed = self.close()
if closed:
- from pmgwidgets import run_python_file_in_terminal
+ from widgets import run_python_file_in_terminal
path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'update', 'update.py')
run_python_file_in_terminal(path + ' -i')
diff --git a/pyminer/check_dependency.py b/pyminer/check_dependency.py
index 833df7a49db786cf5218d73917e96b6bf86e2374..89ac73f918a703be89093db167717bcc95588384 100644
--- a/pyminer/check_dependency.py
+++ b/pyminer/check_dependency.py
@@ -110,7 +110,7 @@ def check_installed_packages():
try:
from PySide2.QtWidgets import QApplication, QVBoxLayout, QTextBrowser, QLabel, QHBoxLayout, QPushButton, QDialog
- from features.io.exceptions import PyMinerException
+ from lib.io.exceptions import PyMinerException
class ExceptionHandlerDialog(QDialog):
diff --git a/pyminer/lib/README.md b/pyminer/lib/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..20374cc94ed4db09a2aa0b2493fe80fa84e25ed8
--- /dev/null
+++ b/pyminer/lib/README.md
@@ -0,0 +1,60 @@
+# 图形界面部分代码说明以及代码结构
+
+## 项目规范说明
+
+图形界面部分的项目规范基于Python3.8,但尽量不使用装饰器、动态属性、海象运算符等特殊语法,从而保证规范、清晰、易于维护。
+
+### 大规模使用的特性:
+
+- 1、typehint。(类型提示)
+主要包含typehint的一般写法(比如`def func(s:str):`)提示时导入的TYPE_CHECKING。
+- 2、lambda的匿名函数及其相关性质
+- 3、PyQt5的基础知识(包括布局、信号与槽等)。界面部分可以使用设计师或者纯手写代码。
+
+### 代码命名规范:
+
+- 1、**变量名、函数名、方法名** 使用小写字母加下划线,从而与pyqt的原生接口相区分。
+- 2、**类名** 使用大写字母驼峰命名,首字母或者缩写大写。
+- 3、**包名、文件名** 使用纯小写字母+数字,除必要之外不使用下划线。
+
+## 项目的结构:
+
+与图形界面有关的类都定义在ui文件夹下面。
+
+- base文件夹:里面是与老版本Pyminer进行兼容的代码
+- common文件夹:通用的代码,负责与操作系统沟通,对Pyminer提供统一的函数。比如打开系统的命令行窗口等。
+- generalwidgets:定义了通用控件,只依赖于PyQt或者generalwidgets包内部互相依赖,不依赖于pyminer。
+- pmwidgets:半通用控件,与其他部分没有耦合,但依赖于Pyminer的主界面才能执行功能。
+- source:资源文件,图像、图标等
+- test:测试文件(目前不用)
+
+注意在generalwidgets和pmwidgets命名的区别:generalwidgets中控件类名都以PMG开头,pmwidgets里面都以PM开头。G代表‘general’
+
+## 界面插入的逻辑
+
+对于对话框、工具栏来讲,自然无所谓插入界面的问题;但对于以dockwidget形式停靠在窗体各个位置的控件而言,位置的问题就很重要了!
+
+相应的参数有:{'n', 's', 'w', 'e', 'nw', 'ne', 'sw', 'se', 'c'}几种,分别代表上、下、左、右,左上....
+
+逻辑是这样的:
+
+* n代表y方向最小(因为Qt的坐标系统就是左上角为原点),x方向中等;
+* w代表x方向最小,y方向中等;
+* se代表x,y方向都是最大(右下角)。
+* c代表x\y都是中等大小。
+
+比如屏幕中的控件有三列的时候:
+
+
+
+'中等'代表插入第二列,'最小'代表插入第一列;’最大‘代表插入最后一列。
+如果列数不足三列,假如为1或者2列,那么max代表的自然就是第1和第2列。因此即便不满足要求,也不会出现异常。
+
+中间的含义是第2个(如果有的话。如果没有,那么也是插入第一列。)
+
+选出列之后,再选择行,也就是Y坐标。同样n代表Y最小,s代表y最大。没有n或者s的话,代表取中间。
+这样,将参数设置为`sw`之后,就可以插入到这里了。
+
+
+
+p.s. 以上的`n`、`s`、`w`、`e`、`c`代表的分别是`东西南北中`,而比如`sw`就代表西北,亦即左上角。
diff --git a/pyminer/lib/__init__.py b/pyminer/lib/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/lib/auth/__init__.py b/pyminer/lib/auth/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/lib/auth/authlocalserver.py b/pyminer/lib/auth/authlocalserver.py
new file mode 100644
index 0000000000000000000000000000000000000000..a8fc3bf38edafc7d7367296e19013354c2f767f2
--- /dev/null
+++ b/pyminer/lib/auth/authlocalserver.py
@@ -0,0 +1,18 @@
+from flask import Blueprint, render_template
+
+auth = Blueprint('auth', __name__, template_folder='templates')
+
+
+@auth.route('/register')
+def show_register_page():
+ return render_template('register.html')
+
+
+@auth.route('/login')
+def show_login_page():
+ return render_template('login.html')
+
+
+@auth.route('/reset_password')
+def show_reset_password_page():
+ pass
diff --git a/pyminer/lib/auth/authmanager.py b/pyminer/lib/auth/authmanager.py
new file mode 100644
index 0000000000000000000000000000000000000000..938c9f03482269d9eb1aeda95dfd0cc195cf5095
--- /dev/null
+++ b/pyminer/lib/auth/authmanager.py
@@ -0,0 +1,42 @@
+class AuthManager():
+ """
+ 用户认证管理类
+ """
+
+ @classmethod
+ def __new__(cls, *args):
+ if not hasattr(cls, 'instance'):
+ instance = super().__new__(cls)
+ cls.instance = instance
+ return cls.instance
+
+ def __init__(self):
+ self.login_status = False
+ self.browser_id = None
+
+ def show_login_page(self):
+ from features.extensions.extensionlib.extension_lib import extension_lib
+ extension_lib.get_interface('embedded_browser').open_url(url='http://localhost:5000/auth/login', side='right')
+
+ def show_register_page(self):
+ from features.extensions.extensionlib.extension_lib import extension_lib
+ extension_lib.get_interface('embedded_browser').open_url(url='http://localhost:5000/auth/register', side='right')
+
+ @staticmethod
+ def get_instance() -> 'AuthManager':
+ return AuthManager.instance
+
+
+_am = AuthManager()
+
+from pmlocalserver.server import server
+from .authlocalserver import auth
+
+server.register_blueprint(auth, url_prefix='/auth')
+
+if __name__ == '__main__':
+ am = AuthManager()
+ AuthManager.get_instance().login_status = True
+ assert True == AuthManager.get_instance().login_status
+ AuthManager.get_instance().login_status = False
+ assert False == AuthManager.get_instance().login_status
diff --git a/pyminer/lib/auth/templates/login.html b/pyminer/lib/auth/templates/login.html
new file mode 100644
index 0000000000000000000000000000000000000000..f3c019d1819563c571e1574933de7aa7f6380427
--- /dev/null
+++ b/pyminer/lib/auth/templates/login.html
@@ -0,0 +1,127 @@
+
+
+
+ 8_常用注册页面的表单实例(含验证).html
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 登录:
+
+
+
\ No newline at end of file
diff --git a/pyminer/lib/auth/templates/register.html b/pyminer/lib/auth/templates/register.html
new file mode 100644
index 0000000000000000000000000000000000000000..e8083998d6de72b31217fafbbd5ac052a4aca352
--- /dev/null
+++ b/pyminer/lib/auth/templates/register.html
@@ -0,0 +1,207 @@
+
+
+
+
+ 8_常用注册页面的表单实例(含验证).html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 用户注册:
+
+
+
+
\ No newline at end of file
diff --git a/pyminer/lib/extensions/__init__.py b/pyminer/lib/extensions/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/lib/extensions/extensionlib/__init__.py b/pyminer/lib/extensions/extensionlib/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..8de2f6c420b2022f72494ccb9b046a19d2a7a9c7
--- /dev/null
+++ b/pyminer/lib/extensions/extensionlib/__init__.py
@@ -0,0 +1,3 @@
+from .baseext import BaseExtension, BaseInterface
+from .extension_lib import extension_lib
+from lib.ui import pmwidgets
diff --git a/pyminer/lib/extensions/extensionlib/baseext.py b/pyminer/lib/extensions/extensionlib/baseext.py
new file mode 100644
index 0000000000000000000000000000000000000000..8b3f359844ff09fe4116e49f98b1fb2b53c28a21
--- /dev/null
+++ b/pyminer/lib/extensions/extensionlib/baseext.py
@@ -0,0 +1,61 @@
+import logging
+from typing import Dict, TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from PySide2.QtWidgets import QWidget
+ from features.extensions.extensionlib.extension_lib import ExtensionLib
+
+logger = logging.getLogger(__name__)
+
+
+class BaseExtension:
+ widget_classes: Dict[str, 'QWidget'] = None
+ widgets: Dict[str, 'QWidget'] = None
+ extension_lib: 'ExtensionLib' = None
+ public_interface: 'BaseInterface' = None
+ settings: Dict[str, object] = None
+ interface: 'BaseInterface'
+
+ def get_widget(self, name: str):
+ return self.widgets[name]
+
+ def _on_loading(self):
+ logger.info(f'{self.info.display_name}即将被加载')
+ self.on_loading()
+
+ def on_loading(self):
+ pass
+
+ def _on_load(self):
+ logger.info(f'{self.info.display_name}已经被加载')
+ self.interface._set_extension(self)
+ self.on_load()
+
+ def on_load(self):
+ pass
+
+ def _on_install(self):
+ logger.info(f'{self.info.display_name}被安装')
+ self.on_install()
+
+ def on_install(self):
+ pass
+
+ def _on_uninstall(self):
+ logger.info(f'{self.info.display_name}被卸载')
+ self.on_uninstall()
+
+ def on_uninstall(self):
+ pass
+
+ def _on_close(self):
+ logger.info(f'{self.info.display_name}已关闭')
+ self.on_close()
+
+ def on_close(self):
+ pass
+
+
+class BaseInterface:
+ def _set_extension(self, extension):
+ self.extension = extension
diff --git a/pyminer/lib/extensions/extensionlib/extension_lib.py b/pyminer/lib/extensions/extensionlib/extension_lib.py
new file mode 100644
index 0000000000000000000000000000000000000000..08ffd79eb8beebeb1a8c9da97f0ddcfacc890dea
--- /dev/null
+++ b/pyminer/lib/extensions/extensionlib/extension_lib.py
@@ -0,0 +1,553 @@
+import inspect
+import os
+from typing import TYPE_CHECKING, Callable, Dict, List, Tuple, Optional, Any
+
+from PySide2.QtCore import QRect, Signal
+from PySide2.QtWidgets import QWidget, QDialog
+
+import utils
+from lib.extensions.extensionlib import baseext
+from lib.extensions.extensions_manager.manager import extensions_manager
+from lib.io.exceptions import PMExceptions
+from lib.ui import pmwidgets
+from lib.workspace.data_adapter import UniversalAdapter
+from lib.workspace.data_manager import data_manager
+from lib.workspace_old.datamanager.datamanager import data_manager as data_manager_old
+from pyminer_comm.base import DataDesc
+from utils import get_main_window
+
+if TYPE_CHECKING:
+ from widgets import PMGToolBar
+
+
+class ExtensionLib:
+ pm_widgets = pmwidgets
+ BaseInterface = baseext.BaseInterface
+ BaseExtension = baseext.BaseExtension
+
+ @staticmethod
+ def get_interface(name: str) -> BaseInterface:
+ """获取名为 ``name`` 的插件的公共接口(由interface定义)
+
+ Args:
+ name: 插件名称
+ """
+ return extensions_manager.get_extension(name).public_interface
+
+ @staticmethod
+ def insert_widget(widget, insert_mode, config=None):
+ """
+ 在主界面上插入一个控件。
+ Args:
+ widget:
+ insert_mode:
+ config:
+
+ Returns:
+
+ """
+ return extensions_manager.ui_inserters[insert_mode](
+ widget, config)
+
+ @staticmethod
+ def get_main_program_dir():
+ """
+ 获取主程序的根目录
+ Returns:
+
+ """
+ return utils.get_root_dir()
+
+ class UI:
+ @staticmethod
+ def widget_exists(widget_name: str) -> bool:
+ if widget_name in get_main_window().dock_widgets.keys():
+ return True
+ else:
+ return False
+
+ @staticmethod
+ def get_toolbar(toolbar_name: str) -> 'PMGToolBar':
+ """
+ 获取工具栏
+
+ Args:
+ toolbar_name:工具栏名称
+
+ Returns:
+
+ """
+ tb = get_main_window().toolbars.get(toolbar_name)
+
+ return tb
+
+ @staticmethod
+ def get_toolbar_widget(toolbar_name: str, widget_name: str) -> Optional['QWidget']:
+ """
+ 获取工具栏上的控件(按钮等)
+
+ Args:
+ toolbar_name:工具栏名称
+ widget_name:工具栏上的控件名称
+
+ Returns:
+
+ """
+ toolbar = ExtensionLib.UI.get_toolbar(toolbar_name)
+ if toolbar is not None:
+ widget = toolbar.get_control_widget(widget_name)
+ return widget
+ return None
+
+ @staticmethod
+ def get_main_window_geometry() -> 'QRect':
+ """
+ 获取主界面的尺寸
+
+ Returns:
+
+ """
+ return get_main_window().geometry()
+
+ @staticmethod
+ def raise_dock_into_view(dock_widget_name: str) -> None:
+ """
+ 将界面上的控件提升到可视的位置
+
+ Args:
+ dock_widget_name:
+
+ Returns:
+
+ """
+ return get_main_window().raise_dock_into_view(dock_widget_name)
+
+ @staticmethod
+ def get_default_font() -> str:
+ """
+ 获取默认字体文件
+
+ Returns: Filepath of font.
+
+ """
+ app = get_main_window()
+ return os.path.join(app.font_dir, app.default_font)
+
+ @staticmethod
+ def switch_toolbar(toolbar_name: str, switch_only: bool = True):
+ """
+ 切换工具栏
+
+ Args:
+ toolbar_name:
+ switch_only:
+
+ Returns:
+
+ """
+ app = get_main_window()
+ app.switch_toolbar(toolbar_name, switch_only)
+
+ @staticmethod
+ def exec_dialog(dlg: QDialog):
+ assert isinstance(dlg, QDialog)
+ win = get_main_window()
+ dlg.setParent(win)
+ dlg.exec_()
+
+ class Signal:
+ @staticmethod
+ def get_close_signal() -> Signal:
+ """
+ 获取关闭信号
+
+ Returns:
+
+ """
+ return get_main_window().close_signal
+
+ @staticmethod
+ def get_window_geometry_changed_signal():
+ """
+ 获取窗口位置和尺寸变化的事件
+
+ Returns:
+
+ """
+ return get_main_window().window_geometry_changed_signal
+
+ @staticmethod
+ def get_layouts_ready_signal():
+ """
+ 获取布局加载完毕的事件
+
+ Returns:
+
+ """
+ return get_main_window().layouts_ready_signal
+
+ @staticmethod
+ def get_widgets_ready_signal():
+ """
+ 获取控件加载完毕的事件
+
+ Returns:
+
+ """
+ return get_main_window().widgets_ready_signal
+
+ @staticmethod
+ def get_events_ready_signal():
+ """
+ 获取界面信号和事件绑定完毕的事件。
+
+ Returns:
+
+ """
+ return get_main_window().events_ready_signal
+
+ @staticmethod
+ def get_settings_changed_signal() -> 'Signal':
+ """
+ 获取设置发生变化时的事件。
+
+ Returns:
+
+ """
+ return get_main_window().settings_changed_signal
+
+ class Program:
+ @staticmethod
+ def add_settings_panel(text: str, panel_content: List[Tuple[str, str]]):
+ """
+ 添加设置面板
+
+ Args:
+ text:
+ panel_content:
+
+ Returns:
+
+ """
+
+ return get_main_window().main_option_form.add_settings_panel(text, panel_content)
+
+ @staticmethod
+ def show_log(level: str, module: str, content: str) -> None:
+ """
+ 调用——PluginInterface.show_log('info','CodeEditor','新建文件')
+ 输出——2020-08-29 23:43:10 hzy INFO [CodeEditor]:新建文件
+ Args:
+ level: 类型,比如‘info’
+ module: 模块。比如'Jupyter'
+ content: 内容。自定义的字符串
+
+ Returns:
+ """
+ get_main_window().slot_flush_console(level, module, content)
+
+ @staticmethod
+ def get_main_program_dir():
+ """
+ 获取主程序路径
+
+ Returns:
+
+ """
+ return utils.get_root_dir()
+
+ @staticmethod
+ def get_settings_item_from_file(file: str, item: str, mode: str = "user"):
+ """
+ 从设置文件中获取设置项
+ Args:
+ file:
+ item:
+ mode:
+
+ Returns:
+
+ """
+ return utils.get_settings_item_from_file(file, item, mode)
+
+ @staticmethod
+ def write_settings_item_to_file(file: str, item: str, value: Any):
+ """
+ 将配置项写入设置文件中
+ Args:
+ file:
+ item:
+ value:
+
+ Returns:
+
+ """
+ utils.write_settings_item_to_file(file, item, value)
+ get_main_window().settings_changed_signal.emit()
+
+ @staticmethod
+ def set_work_dir(work_dir: str) -> None:
+ """
+ 设置当前工作路径
+
+ Args:
+ work_dir:
+
+ Returns:
+
+ """
+ utils.write_settings_item_to_file("config.ini", "MAIN/PATH_WORKDIR", work_dir)
+ get_main_window().settings_changed_signal.emit()
+
+ @staticmethod
+ def get_work_dir() -> str:
+ """
+ 获取当前工作路径
+
+ Returns:
+
+ """
+
+ dir = utils.get_settings_item_from_file("config.ini", "MAIN/PATH_WORKDIR")
+ if (not isinstance(dir, str)) or (not os.path.exists(dir)):
+ dir = os.path.join(os.path.expanduser("~"), "Desktop")
+ utils.write_settings_item_to_file("config.ini", "MAIN/PATH_WORKDIR", dir)
+ return dir
+
+ @staticmethod
+ def get_theme() -> str:
+ """
+ 获取主题
+ Returns:
+
+ """
+ return utils.get_settings_item_from_file("config.ini", "MAIN/THEME")
+
+ @staticmethod
+ def run_python_file(file_path: str, interpreter_path: str):
+ """
+ 运行Python文件命令
+ TODO: Write a shell console into pyminer.
+
+ Args:
+ file_path:
+
+ Returns:
+
+ """
+ raise DeprecationWarning
+
+ @staticmethod
+ def get_plugin_data_path(plugins_name: str) -> str:
+ """
+ 获取插件的数据文件路径
+ Args:
+ plugins_name:
+
+ Returns:str,文件夹路径。
+
+ """
+ ext = extensions_manager.get_extension(plugins_name)
+ # assert ext is not None, 'Extension named %s isn\'t exist!' % plugins_name
+ path = os.path.join(os.path.expanduser('~'), '.pyminer', 'packages')
+ if not os.path.exists(path):
+ os.mkdir(path)
+ plugin_data_path = os.path.join(path, plugins_name)
+ if not os.path.exists(plugin_data_path):
+ os.mkdir(plugin_data_path)
+ return plugin_data_path
+
+ @staticmethod
+ def show_exception_occured_panel(error: BaseException, solution: str, solution_command: str = ''):
+ """
+
+ :param error:
+ :param solution:
+ :param solution_command:
+ :return:
+ """
+ PMExceptions.get_instance().emit_exception_occured_signal(error, solution, solution_command)
+
+ class Data:
+ @staticmethod
+ def get_adapter(key: str) -> UniversalAdapter:
+ return data_manager[key]
+
+ @staticmethod
+ def clear():
+ data_manager_old.clear()
+
+ @staticmethod
+ def delete_variable(var_name: str, provider: str = 'unknown'):
+ """
+ 删除变量
+ Args:
+ var_name:
+ provider:
+
+ Returns:
+
+ """
+ data_manager_old.delete_data(var_name, provider)
+
+ @staticmethod
+ def get_all_variable_names() -> List[str]:
+ """
+ 获取所有的变量名
+
+ Returns:
+
+ """
+ return list(data_manager.container.keys())
+
+ @staticmethod
+ def get_all_public_variable_names() -> List[str]:
+ """
+ 获取所有非保留的变量的名称
+
+ Returns:
+
+ """
+ return list(data_manager_old.get_all_public_var().keys())
+
+ @staticmethod
+ def get_all_variables() -> Dict[str, object]:
+ """
+ 获取全部变量(包含保留类型,可能返回结果比较乱,需要审慎使用)
+
+ Returns:
+ """
+ return data_manager_old.get_all_var()
+
+ @staticmethod
+ def get_all_public_variables() -> Dict[str, object]:
+ """
+ 获取所有的外部可访问变量。
+
+ Returns:
+ """
+ return data_manager_old.get_all_public_var()
+
+ @staticmethod
+ def add_data_changed_callback(callback: Callable[[str, Any, str], None]) -> None:
+ """
+ 添加数据改变时触发的回调函数
+
+ Args:
+ callback:
+
+ Returns:None
+ """
+ assert callable(callback)
+ assert len(
+ list(inspect.signature(callback).parameters.keys())) == 3, 'Function args should be 3'
+ data_manager_old.on_modification(callback)
+
+ @staticmethod
+ def remove_data_changed_callback(callback: Callable):
+ if callback in data_manager_old.modification_callback_actions:
+ data_manager_old.modification_callback_actions.remove(callback)
+ print('removed callback!')
+
+ @staticmethod
+ def add_data_deleted_callback(deletion_callback: Callable[[str, str], None]):
+ """
+ 绑定的函数,要求其输入的函数参数为两个。
+
+ Args:
+ deletion_callback:
+
+ Returns:
+
+ """
+ assert callable(deletion_callback)
+ assert len(
+ list(inspect.signature(deletion_callback).parameters.keys())) == 2, 'Function args should be 2'
+ data_manager_old.on_deletion(deletion_callback)
+
+ @staticmethod
+ def var_exists(var_name: str):
+ """
+ 判断var_name对应的变量是否存在
+ Args:
+ var_name:
+
+ Returns:
+
+ """
+ return var_name in data_manager
+
+ @staticmethod
+ def set_var(varname: str, variable, provider='unknown', **info):
+ """
+
+ Args:
+ varname:
+ variable:
+ provider:
+ **info:
+
+ Returns:
+
+ """
+ assert isinstance(variable, DataDesc), \
+ 'variable name %s ,%s is not type DataDesc' % (varname, variable)
+ data_manager_old.set_var(varname, variable, provider)
+
+ @staticmethod
+ def get_var(var_name: str) -> object:
+ """
+
+ Args:
+ var_name:
+
+ Returns:
+
+ """
+ raise DeprecationWarning
+ # return get_var(var_name)
+
+ @staticmethod
+ def get_data_desc(var_name) -> DataDesc:
+ desc = data_manager_old.get_var(var_name)
+ assert isinstance(desc, DataDesc), repr(desc)
+ return desc
+
+ @staticmethod
+ def update_var_dic(var_dic: dict, provider: str, metadata_dic: dict = None):
+ """
+
+ Args:
+ var_dic:
+ provider:
+ metadata_dic:
+
+ Returns:
+
+ """
+ raise DeprecationWarning
+
+ @staticmethod
+ def get_metadata(varname: str) -> dict:
+ """
+
+ Args:
+ varname:
+
+ Returns:
+
+ """
+ return data_manager_old.get_data_info(varname)
+
+ @staticmethod
+ def get_all_metadata() -> dict:
+ """
+
+ Returns:
+
+ """
+ d = {k: v for k, v in data_manager_old.metadataset.items()}
+ return d
+
+
+extension_lib = ExtensionLib()
diff --git a/pyminer/lib/extensions/extensionlib/extensionlib.md b/pyminer/lib/extensions/extensionlib/extensionlib.md
new file mode 100644
index 0000000000000000000000000000000000000000..5e5bf1e8a0d90ba36bb5fe22acb43a4cb74fabfa
--- /dev/null
+++ b/pyminer/lib/extensions/extensionlib/extensionlib.md
@@ -0,0 +1,397 @@
+ExtensionLib:
+
+## 基本方法
+
+def get_interface(self, name: str) -> BaseInterface:
+获取名为`name`的插件的公共接口(由interface定义)
+
+- Args:
+- name:
+
+Returns: Interface,须继承BaseInterface,并且在插件的main.py文件中定义。
+
+### 在界面的可停靠窗口上插入控件
+
+
+def insert_widget(self, widget, insert_mode, config=None):
+
+在主界面上插入一个控件。
+Args:
+- widget: 被插入的控件(继承QWidget)
+- insert_mode: 插入方式(字符串描述)
+- config: 插入的位置等。
+
+Returns:
+
+### 获取主程序路径
+
+def get_main_program_dir(self):
+
+- 获取主程序的根目录
+
+
+## UI子类(图形界面方法)
+
+class UI():
+### 获取工具栏方法
+@staticmethod
+def get_toolbar(toolbar_name: str) -> 'PMGToolBar':
+获取工具栏
+
+Args:
+- toolbar_name:工具栏名称
+
+Returns: 当前工具栏。若工具栏不存在,则返回None。
+
+### 获取工具栏上的控件的方法
+@staticmethod
+def get_toolbar_widget(toolbar_name: str, widget_name: str) -> Optional['QWidget']:
+获取工具栏上的控件(按钮等)
+
+Args:
+- toolbar_name:工具栏名称
+- widget_name:工具栏上的控件名称
+
+Returns: QWidget,工具栏上的控件。如果没有则返回None。
+
+
+@staticmethod
+def add_translation_file(file_name: str) -> None:
+添加翻译文件
+TODO:这个功能不太好,建议去掉,直接用QApplication里的方法来添加。
+Args:
+file_name:
+Deprecated!!
+Returns:
+### 获取主界面的位置和尺寸
+@staticmethod
+def get_main_window_geometry() -> 'QRect':
+获取主界面的尺寸
+
+Returns: QRect(x,y,w,h),亦即MainWindow的geometry。
+### 将当前的停靠窗口提升到可见位置
+@staticmethod
+def raise_dock_into_view(dock_widget_name: str) -> None:
+将界面上的控件提升到可视的位置
+
+Args:
+dock_widget_name:
+
+Returns:
+### 获取默认字体文件的路径
+@staticmethod
+def get_default_font() -> str:
+
+获取默认字体文件的位置
+
+Returns: Filepath of font.
+### 切换工具栏
+@staticmethod
+def switch_toolbar(toolbar_name: str, switch_only: bool = True):
+切换工具栏
+
+Args:
+- toolbar_name:
+- switch_only: 如果为True,那么在调用时只会切换工具栏,若当前的工具栏名称为`name`,则调用多次,也不会改变当前工具栏的显示状态。为False的时候,若当前工具栏名称为`name`,那么就会改变显示状态,亦即原先显示的隐藏,原先隐藏的显示。
+
+Returns: None
+## 信号相关
+class Signal():
+### 注意事项:
+PyMiner采用QtPy做PySide2和PyQt5之间的转换。目前,PyMiner使用的是PyQt5,因此返回类型为pyqtSignal。未来移植到PySide2平台后,则为Signal.
+### 获取主界面关闭事件
+@staticmethod
+def get_close_signal() -> Signal:
+获取关闭信号
+
+Returns: 主界面的窗口关闭信号
+
+
+### 获取窗口位置、尺寸改变的信号
+@staticmethod
+def get_window_geometry_changed_signal():
+
+获取窗口位置和尺寸变化的信号
+
+Returns
+
+### 获取布局加载完成信号
+@staticmethod
+def get_layouts_ready_signal():
+"""
+获取布局加载完毕的事件
+
+Returns:
+
+"""
+### 获取控件加载完毕的信号
+@staticmethod
+def get_widgets_ready_signal():
+获取控件加载完毕的事件
+
+Returns:
+
+### 获取事件绑定完毕的信号
+@staticmethod
+def get_events_ready_signal():
+获取界面信号和事件绑定完毕的事件。
+
+Returns:
+
+### 获取设置被改变的信号
+@staticmethod
+def get_settings_changed_signal() -> 'Signal':
+获取设置发生变化时的事件。
+
+Returns: 主界面设置发生变更时的信号
+## 程序
+
+ class Program():
+### 添加设置面板
+@staticmethod
+def add_settings_panel(text: str, panel_content: List[Tuple[str, str]]):
+添加一个设置面板到主界面的设置栏。
+要求就是设置栏的语法符合pmgpanel语法。
+Args:
+text:
+panel_content:
+
+Returns:
+
+### 在日志面板显示日志
+@staticmethod
+def show_log(level: str, module: str, content: str) -> None:
+
+调用——PluginInterface.show_log('info','CodeEditor','新建文件')
+输出——2020-08-29 23:43:10 hzy INFO [CodeEditor]:新建文件
+Args:
+level: 类型,比如‘info’
+module: 模块。比如'Jupyter'
+content: 内容。自定义的字符串
+
+Returns:
+
+### 获取主程序路径
+@staticmethod
+def get_main_program_dir():
+获取主程序路径
+
+Returns:
+
+### 添加翻译文件
+@staticmethod
+def add_translation(locale: str, text: dict):
+"""
+
+Args:
+locale:
+text:
+
+Returns:
+
+"""
+return pmlocale.add_locale(locale, text)
+
+### 翻译
+@staticmethod
+def _(text):
+
+Args:
+text:
+
+Returns:
+
+
+### 获取设置
+@staticmethod
+def get_settings() -> Dict[str, str]:
+"""
+
+Returns:
+
+"""
+
+### 设置当前工作路径
+@staticmethod
+def set_work_dir(work_dir: str) -> None:
+
+设置当前工作路径
+
+Args:
+work_dir:
+
+Returns:
+
+"""
+
+### 获取当前工作路径
+
+@staticmethod
+def get_work_dir() -> str:
+
+获取当前工作路径
+
+Returns:
+### 运行Python文件命令
+@staticmethod
+def run_python_file(file_path: str):
+"""
+运行Python文件命令
+TODO: Write a shell console into pyminer.
+
+Args:
+file_path:
+
+Returns: None
+
+## 数据服务相关
+
+class Data():
+### 删除变量
+@staticmethod
+def delete_variable(var_name: str, provider: str = 'unknown'):
+"""
+删除变量
+Args:
+var_name:
+provider:
+
+Returns:
+
+"""
+data_manager.delete_data(var_name, provider)
+
+### 获取所有的变量名
+@staticmethod
+def get_all_variable_names() -> List[str]:
+
+获取所有的变量名
+
+Returns:
+
+### 获取所有非保留的变量的名称
+@staticmethod
+def get_all_public_variable_names() -> List[str]:
+
+获取所有非保留的变量的名称
+
+Returns:
+
+### 获取全部变量(包含保留类型)
+@staticmethod
+def get_all_variables() -> Dict[str, object]:
+
+获取全部变量(包含保留类型,可能返回结果比较乱,需要审慎使用)
+
+Returns:
+
+### 获取所有的外部可访问变量。
+@staticmethod
+def get_all_public_variables() -> Dict[str, object]:
+
+获取所有的外部可访问变量。
+
+Returns:
+
+### 添加数据改变时触发的回调函数
+@staticmethod
+def add_data_changed_callback(callback: Callable[[str, str], None]) -> None:
+
+添加数据改变时触发的回调函数
+
+Args:
+callback:
+
+Returns:None
+
+### 移除数据改变时触发的回调函数
+@staticmethod
+def remove_data_changed_callback(callback: Callable):
+
+
+### 添加数据删除时触发的回调函数
+@staticmethod
+def add_data_deleted_callback(deletion_callback: Callable[[str, str], None]):
+绑定的函数,要求其输入的函数参数为两个。
+
+Args:
+deletion_callback:
+
+Returns:
+
+
+
+@staticmethod
+def get_all_vars_of_type(types: Union[object, Tuple]):
+
+按照类型过滤变量。
+
+Args:
+types:
+
+Returns:
+
+
+@staticmethod
+def var_exists(var_name: str):
+
+判断var_name对应的变量是否存在
+Args:
+var_name:
+
+Returns:
+
+
+@staticmethod
+def set_var(varname: str, variable, provider='unknown', **info):
+
+Args:
+varname:
+variable:
+provider:
+\*\*info:
+
+Returns: None
+
+@staticmethod
+def get_var(var_name: str) -> object:
+
+Args:
+var_name:
+
+Returns:
+
+
+@staticmethod
+def update_var_dic(var_dic: dict, provider: str, metadata_dic: dict = None):
+
+Args:
+var_dic:
+provider:
+metadata_dic:
+
+Returns:
+
+
+@staticmethod
+def get_metadata(varname: str) -> dict:
+ """
+
+Args:
+varname:
+
+Returns:
+
+"""
+return data_manager.get_data_info(varname)
+
+@staticmethod
+def get_all_metadata() -> dict:
+"""
+
+Returns:
+
+"""
+d = {k: v for k, v in data_manager.metadataset.items()}
+return d
+
diff --git a/pyminer/lib/extensions/extensionlib/readme.md b/pyminer/lib/extensions/extensionlib/readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..e17cd64f0415e8839ff2992e655660b0055edc71
--- /dev/null
+++ b/pyminer/lib/extensions/extensionlib/readme.md
@@ -0,0 +1,31 @@
+# 插件开发指南:
+## PyMiner自带的可停靠子窗口名称:
+|中文名称|程序内部名称|
+|---------|------------|
+| 编辑器 | code_editor|
+|ipython控制台| ipython_console|
+|工作空间显示器| workspace_inspector|
+|变量视图|data_view_table|
+|文件树|file_explorer|
+
+这些控件都有便捷的借口函数方式对其进行访问。将它们的名称列在这里的原因是,
+插件和以上系统自带的控件,**名称不能相同**。
+
+## Pyminer工具栏中按钮的获取
+### 主页工具栏
+|内部名称 |中文名称|
+|---------|---------|
+|'button_new_script'|'新建\n脚本'|
+|'button_new'|'新建'|
+|'button_open'|'打开'|
+|'button_import_data'|'导入\n数据',|
+|'button_save_workspace'|'保存\n工作区',|
+|'button_new_variable'|'新建变量'|
+|'button_open_variable'|'打开变量'|
+|'button_clear_workspace'|'清除工作区'|
+|'button_search_for_files'|'查找文件'|
+|'button_compare_files'|'文件比较'|
+|'button_settings'|'设置'|
+|'button_help'| '帮助'|
+|'view_config'|'视图'|
+
diff --git a/pyminer/lib/extensions/extensions_manager/ExtensionLoader.py b/pyminer/lib/extensions/extensions_manager/ExtensionLoader.py
new file mode 100644
index 0000000000000000000000000000000000000000..5edfd2ee9742fe065d3d70b6b19123239c834e20
--- /dev/null
+++ b/pyminer/lib/extensions/extensions_manager/ExtensionLoader.py
@@ -0,0 +1,199 @@
+import importlib
+import json
+import logging
+import os
+import sys
+from collections import namedtuple
+
+import utils
+from lib.io.exceptions import pyminer_exc_mgr
+
+logger = logging.getLogger('extensionmanager.extensionloader')
+# from features.extensions.extensions_manager import log
+
+
+Info = namedtuple('Info',
+ ['icon',
+ 'name',
+ 'display_name',
+ 'version',
+ 'description',
+ 'path',
+ 'type_',
+ 'group'])
+
+
+class PublicInterface:
+ pass
+
+
+class ExtensionLoader:
+ def __init__(self, manager):
+ self.manager = manager
+ # 这里不能直接写self.path,因为所有入口文件都叫main.py,会被Python缓存起来
+ self.import_path = os.path.join(utils.get_root_dir(), 'packages')
+ sys.path.append(self.import_path) # 将模块导入路径设置为扩展文件夹,这样可以直接导入入口文件
+ self.extension_lib_path = os.path.join(
+ utils.get_root_dir(), 'pyminer2', 'extensions', 'extensionlib')
+ sys.path.append(self.extension_lib_path)
+
+ def load(self, file, ui_inserters):
+ self.package = json.load(file)
+ self.ui_inserters = ui_inserters
+ try:
+ self.name = self.package['name']
+ self.display_name = self.package['display_name']
+ self.version = self.package['version']
+ self.description = self.package['description']
+ self.icon = self.package['icon']
+ self.type_, self.group = self.package.get(
+ 'group', '未知类型/未知分组').split('/')
+
+ self.path = os.path.join(
+ utils.get_root_dir(), 'packages',
+ self.name) # 扩展文件夹路径
+
+ main_config = self.package.get(
+ 'main', {'file': 'main.py', 'main': 'Extension'})
+ main_class = self.load_class(
+ main_config['file'], main_config['main'])
+ self.main = main_class()
+ from lib.extensions.extensionlib.extension_lib import extension_lib
+ self.main.extension_lib = extension_lib
+ self.binding_info()
+ try:
+ self.main._on_loading()
+ except Exception as e:
+ logger.error(e, exc_info=True)
+
+ interface_config = self.package.get(
+ 'interface', {'file': 'main.py', 'interface': 'Interface'})
+ self.main.interface = self.load_class(
+ interface_config['file'], interface_config['interface'])()
+ self.main.public_interface = self.create_public_interface(
+ self.main.interface)
+
+ for key in getattr(self.main.interface, 'ui_inserters', []):
+ self.ui_inserters[f'extension_{self.name}_{key}'] = self.main.interface.ui_inserters[key]
+
+ self.main.widget_classes = {}
+ self.main.widgets = {} # store auto inserted widgets
+ for widget in self.package['widgets']:
+ widget_class = self.load_widget(widget)
+ widget_class_name = widget_class.__name__
+ self.main.widget_classes[widget_class_name] = widget_class
+
+ if 'settings' in self.package:
+ assert isinstance(self.package['settings'], str)
+ settings_path = os.path.join(
+ self.path, self.package['settings'])
+ try:
+ with open(settings_path, 'r') as f:
+ settings = json.loads(f.read())
+ except Exception as e:
+ logger.exception(e, exc_info=True)
+ else:
+ self.main.settings = settings
+
+ self.manager.vermanager.set_current_modules(
+ [f'{self.name}=={self.version}'])
+
+ if 'requirements' in self.package:
+ requirements = self.package['requirements']
+ assert isinstance(requirements, list)
+ solvable, conflict = self.manager.vermanager.check_requirements(
+ requirements)
+ if conflict:
+ raise Exception(f'Conflicts in extensions {conflict}')
+ if solvable:
+ for requirement in solvable:
+ self.manager.enable_extension(requirement)
+
+ try:
+ self.main._on_load()
+ except ImportError as e:
+ import traceback
+ traceback.print_exc()
+ if hasattr(e, 'name'):
+ command = '{executable} -m pip install {package_name}'.format(
+ package_name=e.name, executable=sys.executable)
+ else:
+ command = ''
+ pyminer_exc_mgr.emit_exception_occured_signal(
+ e, 'try install module', command)
+
+ except Exception as e:
+ logger.error(e, exc_info=True)
+ return self.main
+ except KeyError as e:
+ logger.error(f'插件的Package.json不完整 {e}', exc_info=True)
+
+ def binding_info(self):
+ self.main.info = Info(
+ name=self.name,
+ icon=self.icon,
+ display_name=self.display_name,
+ description=self.description,
+ version=self.version,
+ path=self.path,
+ group=self.group,
+ type_=self.type_
+ )
+
+ def import_module(self, path):
+ filepath = os.path.join(self.path, path)
+
+ # pyminer_paths = [path for path in sys.path if 'pyminer' in path and path not in
+ # (self.import_path, self.extension_lib_path)]
+ # for path in pyminer_paths:
+ # sys.path.remove(path)
+ # pyminer_paths = []
+
+ try:
+ package_name = self.name
+ module_name = os.path.splitext(os.path.basename(filepath))[0]
+ module_path = f'{package_name}.{module_name}'
+ module = None
+ module = importlib.import_module(module_path)
+
+ except Exception as e:
+ logger.error(e, exc_info=True)
+ module = None
+ # sys.path.extend(pyminer_paths)
+ return module
+
+ def load_class(self, file, class_name):
+ path = os.path.join(self.path, file)
+ # TODO 这里的设置将导致奇怪的错误,即模块不再为单例模式,而这是Python中的一个重要特性
+ module = self.import_module(path)
+ if module:
+ if hasattr(module, class_name):
+ return getattr(module, class_name)
+ else:
+ logger.error(f"{file}文件中不存在{class_name}类", exc_info=True)
+ else:
+ logger.error(f"{file}文件不存在或有误", exc_info=True)
+
+ def load_widget(self, widget_config):
+ try:
+ widget_class = self.load_class(
+ widget_config['file'], widget_config['widget'])
+ if widget_config.get('auto_insert', True):
+ widget_config = self.ui_inserters[widget_config['position']](
+ widget_class, widget_config['config'])
+ self.main.widgets[widget_class.__name__] = widget_config
+ return widget_class
+ except KeyError as e:
+ logger.error(f"插件{self.name}的widgets配置不正确!")
+ logger.error(f"位置:{widget_config}", exc_info=True)
+
+ def create_public_interface(self, interface):
+ public_interface = PublicInterface()
+ for attr in dir(interface):
+ obj = getattr(interface, attr)
+ if not attr.startswith('_') and callable(obj):
+ setattr(public_interface, attr, obj)
+ return public_interface
+
+ def reset(self):
+ pass
diff --git a/pyminer/lib/extensions/extensions_manager/README.md b/pyminer/lib/extensions/extensions_manager/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..0b27302a49b881b77acbed87a9edfbb55b656cd0
--- /dev/null
+++ b/pyminer/lib/extensions/extensions_manager/README.md
@@ -0,0 +1,39 @@
+# Package.json 包结构
+
+~~~
+{
+ "name":...,
+ "display_name":...,
+ "version":...,
+ "description":"...",
+ "icon":"..."
+ "main":{
+ "file":"",
+ "main":""
+ },
+ "interface":{
+ "file":"",
+ "interface":""
+ },
+ "widgets":[
+ {
+ "file":"",
+ "widget":"",
+ "position":"..."
+ "config":{
+
+ }
+ }
+ ],
+ "requirements":[
+ "name==version",
+ "name>=version",
+ "name<=version"
+ ],
+ "dependencies":[
+ "name==version",
+ "name>=version",
+ "name<=version"
+ ]
+}
+~~~
\ No newline at end of file
diff --git a/pyminer/lib/extensions/extensions_manager/UIInserter.py b/pyminer/lib/extensions/extensions_manager/UIInserter.py
new file mode 100644
index 0000000000000000000000000000000000000000..5dee5f9fb80f85aba6dc3b7b19b88348669ba219
--- /dev/null
+++ b/pyminer/lib/extensions/extensions_manager/UIInserter.py
@@ -0,0 +1,154 @@
+"""
+控件插入器
+作者:冰中火
+
+这是加载UI控件的插件类。
+插件导入的时候,传递的widget_class是控件的类。
+1、对于新建的工具栏类、插入工具栏的控件类、插入主窗口的控件类,控件插入器直接将控件类实例化,
+然后插入在由config.json中指定的位置;
+2、对于对话框类,控件插入器不会将其实例化,而会将其保存在对话框类管理器中。使用时,调用相应的方法,可以显示对话框。
+请注意,对话框并不是什么只能问问题,点‘确定、取消’的类。只要继承QDialog的界面都要这样处理。
+以上1中所述的情况将在应用初始化时完成;而2则是动态的过程,可以在应用初始化完成后就弹出对话框进行显示。
+
+
+插件导入时参数由json中的config设置项所决定。
+
+config:是一个字典,但是现在传入的参数还远远不够。
+以这个文件的json配置为例,有以下要求:
+"file":声明了控件类的入口位置。一般就是在main.py之中。
+position:插件插入位置。有两种选项:‘new_dock_window’和‘new_toolbar’
+config:设置属性。
+config.message:插件的设置信息,可以为空。
+config.name:插件的名称
+config.side:插件插入窗口时的位置(当position=new_dock_window时有效),有left\right\top\bottom四个选项
+config.text:插件的文字。会显示在dockwidget或者工具栏上
+{
+ "file":"main.py",
+ "widget_class":"WidgetTest",
+ "position":"new_dock_window",
+ "config":{
+ "message":"no",
+ "name":"code_editor",
+ "side": "right",
+ "text": "编辑器"
+ }
+}
+
+
+插件管理器下一步的目的就是将插件尽可能不一次性的加载完,插件可以自行设置插入到程序的时间,
+尽可能在主界面发出初始化完成信号之后在进行调用。
+"""
+from typing import TYPE_CHECKING, ClassVar
+import utils
+if TYPE_CHECKING:
+ from PySide2.QtWidgets import QWidget
+
+
+def get_item_coor(coors: set, pos: str):
+ l = list(coors)
+ if pos == 'max':
+ return max(l)
+ elif pos == 'min':
+ return min(l)
+ else:
+ l = sorted(l)
+ if len(l) == 1:
+ return l[0]
+ else:
+ return l[1]
+
+
+def get_dock_by_position(pos: str):
+ if not pos in {'top', 'bottom', 'left', 'right'}:
+ raise Exception('dockwidget的位置须由合法字符串指定,这些字符串是:\'top\', \'bottom\', \'left\', \'right\'')
+ pos_dic = {'top': ('med', 'min'), 'bottom': ('med', 'max'), 'right': ('max', 'med'), 'left': ('min', 'med')}
+
+ x_set = set()
+ y_set = set()
+ x_policy, y_policy = pos_dic[pos]
+
+ for k in utils.get_main_window().dock_widgets.keys():
+ w2 = utils.get_main_window().get_dock_widget(k)
+ if w2.x() >= 0:
+ x_set.add(w2.x())
+ x_pos = get_item_coor(coors=x_set, pos=x_policy)
+ for k in utils.get_main_window().dock_widgets.keys():
+ w2 = utils.get_main_window().get_dock_widget(k)
+ if w2.x() == x_pos and w2.y() >= 0:
+ y_set.add(w2.y())
+ if 0 in y_set and len(list(y_set)) > 1:
+ y_set.remove(0)
+
+ y_pos = get_item_coor(coors=y_set, pos=y_policy)
+
+ for k in utils.get_main_window().dock_widgets.keys():
+ w2 = utils.get_main_window().get_dock_widget(k)
+ if w2.x() == x_pos and w2.y() == y_pos:
+ return w2
+
+
+class UiInserter(dict):
+ def __init__(self):
+ self.update({
+ 'new_dock_window': self.new_dock_window,
+ 'new_toolbar': self.new_toolbar,
+ 'append_to_toolbar': self.append_to_toolbar,
+ 'new_dock_window_obj': self.new_dock_window_obj
+ })
+
+ def new_toolbar(self, widget_class: ClassVar['QWidget'], config=None):
+ name = config['name']
+
+ widget = widget_class()
+ text = widget.get_toolbar_text()
+ utils.get_main_window().add_toolbar(name=name, toolbar=widget, text=text)
+ return widget
+
+ def new_dock_window(self, widget_class: ClassVar['QWidget'], config=None):
+ dock_name = config['name']
+
+ side = config['side']
+ widget = widget_class()
+ text = widget.get_widget_text()
+ dock = utils.get_main_window().add_widget_on_dock(dock_name=dock_name,
+ widget=widget, text=text, side=side)
+
+ if side in {'top', 'bottom', 'left', 'right'}:
+ w2 = get_dock_by_position(side)
+ utils.get_main_window().tabifyDockWidget(w2, dock)
+
+ dock.raise_into_view()
+ return widget
+
+ def new_dock_window_obj(self, widget: ClassVar['QWidget'], config=None):
+ """
+ 以窗口对象插入,适用于已经创建的窗口。
+ :param widget:
+ :param config:
+ :return:
+ """
+ dock_name = config['name']
+ text = widget.get_widget_text()
+ side = config['side']
+
+ widget = widget
+ dock = utils.get_main_window().add_widget_on_dock(dock_name=dock_name,
+ widget=widget, text=text, side='left')
+
+ if side in {'top', 'bottom', 'left', 'right'}:
+ w2 = get_dock_by_position(side)
+ utils.get_main_window().tabifyDockWidget(w2, dock)
+ utils.get_main_window()
+ dock.raise_into_view()
+ return widget
+
+ def append_to_toolbar(self, widget_class: ClassVar['QWidget'], config=None):
+ button_name = config['name']
+ toolbar_name = config['toolbar']
+ widget = widget_class()
+ toolbar = utils.get_main_window().toolbars.get(toolbar_name)
+ toolbar.add_widget(button_name, widget)
+ return widget
+
+
+ui_inserters = UiInserter()
diff --git a/pyminer/lib/extensions/extensions_manager/__init__.py b/pyminer/lib/extensions/extensions_manager/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/lib/extensions/extensions_manager/log.py b/pyminer/lib/extensions/extensions_manager/log.py
new file mode 100644
index 0000000000000000000000000000000000000000..4e4e86661a59fdcbf0ad794cd2fff2417815c549
--- /dev/null
+++ b/pyminer/lib/extensions/extensions_manager/log.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import logging
+from utils import get_main_window
+
+
+def log(text):
+ """日志输出text"""
+ # TODO (panhaoyu) 这个函数是否无用可删?
+ get_main_window().slot_flush_console('info', 'Extension', 'Log:' + str(text))
+
+
+def error(text):
+ # TODO (panhaoyu) 这个函数是否无用可删?
+ print("Error:", text)
+ get_main_window().slot_flush_console(
+ 'error', 'Extension', 'Error:' + str(text))
+
+
+def assert_(boolean, text):
+ """
+ boolean:bool
+ text:str
+ 若bool为false,以异常级输出text
+ """
+ # TODO (panhaoyu) 这个函数是否无用可删?
+ if not boolean:
+ error(text)
+
+
+class ColorHandler(logging.Handler):
+ """部分关键词彩色,以及添加到日志控件中
+ """
+
+ Colors = {
+ logging.DEBUG: 'black',
+ logging.INFO: 'green',
+ logging.WARNING: 'yellow',
+ logging.ERROR: 'red',
+ logging.CRITICAL: 'red'
+ }
+
+ def format(self, record):
+ """
+ Format the specified record.
+
+ If a formatter is set, use it. Otherwise, use the default formatter
+ for the module.
+ """
+ if self.formatter:
+ fmt = self.formatter
+ else:
+ fmt = logging.Formatter()
+
+ if fmt.usesTime():
+ record.asctime = fmt.formatTime(record, fmt.datefmt)
+
+ color = self.Colors.get(record.levelno, 'black')
+ # record.asctime
+ # record.name
+ # record.module
+ # record.funcName
+ # record.lineno
+ # record.levelname
+ # record.message
+ record.message = record.getMessage()
+
+ s = fmt.formatMessage(record)
+ if record.exc_info:
+ # Cache the traceback text to avoid converting it multiple times
+ # (it's constant anyway)
+ if not record.exc_text:
+ record.exc_text = fmt.formatException(record.exc_info)
+ if record.exc_text:
+ if not s.endswith('
'):
+ s += '
'
+ s += record.exc_text
+ if record.stack_info:
+ if not s.endswith('
'):
+ s += '
'
+ s += fmt.formatStack(record.stack_info)
+ s = '{1}'.format(color, s.replace('\n', '
').replace(' ', ' '))
+ return s
+
+ def emit(self, record):
+ """
+ Emit a record.
+ """
+ try:
+ msg = self.format(record)
+ try:
+ get_main_window().log_output_console.append(msg)
+ except Exception:
+ pass
+ except RecursionError: # See issue 36272
+ raise
+ except Exception:
+ self.handleError(record)
\ No newline at end of file
diff --git a/pyminer/lib/extensions/extensions_manager/manager.py b/pyminer/lib/extensions/extensions_manager/manager.py
new file mode 100644
index 0000000000000000000000000000000000000000..8a13e9f0098fff919ae39d40f0a9d42693019f10
--- /dev/null
+++ b/pyminer/lib/extensions/extensions_manager/manager.py
@@ -0,0 +1,206 @@
+import importlib
+import json
+import json.decoder
+import logging
+import os
+import shutil
+import sys
+import time
+import zipfile
+
+import utils
+from lib.extensions.extensions_manager.ExtensionLoader import ExtensionLoader
+from lib.extensions.extensions_manager.UIInserter import ui_inserters
+from .vermanager import VersionsManager
+
+logger = logging.getLogger('extensionmanager')
+logger.setLevel(logging.DEBUG)
+
+
+class ExtensionsManager:
+ """
+ 扩展管理类
+ """
+
+ def __init__(self):
+ self.extensions = {} # 扩展类实例
+ self.ui_inserters = ui_inserters
+ self.id_ = 0 # 扩展id,动态分配的
+ self.setting_path = os.path.join(
+ utils.get_root_dir(), 'configuration', 'extensions.json') # 扩展配置文件位置
+ if not os.path.exists(self.setting_path):
+ raise FileNotFoundError(self.setting_path)
+ try:
+ with open(self.setting_path, 'r') as f:
+ self.setting = json.load(f)
+ except:
+ logger.warn('extension.json 已损坏')
+ self.setting = {}
+ self.loader = ExtensionLoader(self)
+ # self.extensions_directory = os.path.join(
+ # utils.get_root_dir(), 'extensions', 'packages')
+ self.extensions_directory = os.path.join(
+ utils.get_root_dir(), 'packages')
+ self.vermanager = VersionsManager()
+
+ def enable_extension(self, name):
+ if name in self.setting:
+ self.setting[name]['enabled'] = True
+ else:
+ self.load(name)
+ self.setting[name] = {'enabled': True}
+
+ def disable_extension(self, name):
+ self.setting[name]['enabled'] = False
+ self.unload(name)
+
+ def load(self, name):
+ with open(os.path.join(self.extensions_directory, name, 'package.json'), encoding='utf-8') as f:
+ main = self.loader.load(f, self.ui_inserters)
+ self.extensions[main.info.name] = main
+ main.id_ = self.id_
+ self.id_ += 1
+ self.loader.reset()
+
+ def load_from_extension_folder(self, callback):
+ """
+ 加载扩展
+ """
+ ext_load_status = {}
+ ext_path = os.path.join(utils.get_root_dir(), 'configuration', 'extensions.json')
+ with open(ext_path) as f:
+ ext_dict = json.load(f)
+ packages = ext_dict.keys()
+ ext_load_status['ext_count'] = len(packages)
+ ext_load_status['loaded'] = 0
+ for package in packages:
+ t0 = time.time()
+ ext_load_status['loaded'] += 1
+ if not os.path.isdir(os.path.join(self.extensions_directory, package)):
+ logger.debug(f'{package} is not dir, ignored')
+ elif package in self.setting:
+ if self.setting[package].get('enabled', True):
+ self.setting[package] = {'enabled': True}
+ ext_load_status['ext_name'] = package
+ try:
+ logger.info(f'load extension {package}')
+ self.load(package)
+ except ModuleNotFoundError as e:
+ raise e
+ except BaseException:
+ logger.error(
+ f'{package} is not a valid package', exc_info=True)
+ elif package not in self.setting:
+ self.setting[package] = {'enabled': True}
+ ext_load_status['ext_name'] = package
+ try:
+ logger.info(f'load extension {package} for the first time')
+ self.load(package)
+ except BaseException:
+ logger.error(
+ f'{package} is not a valid package', exc_info=True)
+ pass
+ if callable(callback):
+ callback(ext_load_status)
+ t1 = time.time()
+ logger.info(f'boot time elapsed for {str(package)} : {t1 - t0:f} s')
+
+ def get_extension(self, name):
+ """通过名称获取扩展"""
+ return self.extensions.get(name, None)
+
+ def unload(self, name):
+ ext = self.extensions[name]
+ # 卸载生命周期函数
+ try:
+ ext._on_close()
+ except Exception as e:
+ logger.error(e, exc_info=True)
+ del self.extensions[name]
+
+ def uninstall(self, name):
+ """
+ 卸载扩展
+ """
+ ext = self.get_extension(name)
+
+ # 卸载生命周期函数
+ try:
+ ext._on_uninstall()
+ except Exception as e:
+ logger.error(e, exc_info=True)
+
+ # 删除扩展实例
+ del self.extensions[name]
+
+ # 修改配置文件
+ with open(self.setting_path, encoding='utf-8') as f:
+ config = json.load(f)
+ for index, c in enumerate(config['extensions']):
+ if c['name'] == ext.info.name:
+ del config['extensions'][index]
+ break
+ with open(self.setting_path, 'w', encoding='utf-8') as f:
+ json.dump(config, f)
+
+ # 最后删除扩展文件夹,注意顺序
+ path = ext.info.path
+ shutil.rmtree(path)
+
+ def install(self, path):
+ """本地安装扩展"""
+ try:
+ # 解压扩展
+ file = zipfile.ZipFile(path)
+ dirname = os.path.join(utils.get_root_dir(), 'packages',
+ '.'.join(os.path.basename(path).split('.')[:-1]))
+ extract_dir = os.path.join(
+ utils.get_root_dir(), 'packages')
+ file.extractall(extract_dir)
+ file.close()
+
+ # 配置文件
+ with open(self.setting_path, encoding='utf-8') as f:
+ config = json.load(f)
+ # 扩展内的package.json配置文件
+ with open(os.path.join(dirname, 'package.json'), encoding='utf-8') as f:
+ package = json.load(f)
+ # 自动安装依赖
+ for i in package.get('requirements', []):
+ if not self.get_extension(i):
+ self.install_web(*i)
+
+ # 写入配置文件
+ config['extensions'].append({
+ 'name': package['name'],
+ 'enable': True
+ })
+ with open(self.setting_path, 'w', encoding='utf-8') as f:
+ json.dump(config, f)
+ except Exception as e:
+ logger.error(f"安装失败 {e}", exc_info=True)
+ return
+ else: # else表示没有异常的情况
+ self.load(package['name'])
+
+ def download(self, name, version=''):
+ # 从插件商店安装,之后完成
+ pass
+
+ def stop(self):
+ with open(self.setting_path, 'w') as f:
+ json.dump(self.setting, f, indent=2)
+
+ def refresh(self, id_):
+ ext = self.get_extension(id_)
+ name = ext.info.name
+ self.unload(name)
+ with open(os.path.join(ext.info.path, 'package.json'), 'r', encoding='utf-8') as f:
+ main_config = json.load(f).get(
+ 'main', {'file': 'main.py', 'main': 'Extension'})
+ module_name = f"{name}.{main_config['file'].split('.')[0]}"
+ importlib.reload(sys.modules[module_name])
+ self.load(name)
+
+
+extensions_manager = ExtensionsManager()
diff --git a/pyminer/lib/extensions/extensions_manager/readme.drawio b/pyminer/lib/extensions/extensions_manager/readme.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..080c8be1a97f10d47811517ab75928b458ba37d9
--- /dev/null
+++ b/pyminer/lib/extensions/extensions_manager/readme.drawio
@@ -0,0 +1 @@
+7VrZbuM2FP0aAu1Lod3ko+RlWiBFC+ShM08DxmJsdWTRkOnY7tf3kqJW0ktQO5lxCgQBeXkpkeeQd5ORP17tP5V0vfydpyxHnpPukT9Bnhf6GP5LwaESBIEWLMosrURuK3jM/mFa6GjpNkvZpqcoOM9Ftu4L57wo2Fz0ZLQs+a6v9szz/lvXdMEMweOc5qb0rywVy0qKvVEr/5Vli2X9Zjci1ciK1sp6J5slTfmuI/KnyB+XnIuqtdqPWS6xq3Gp5s2OjDYLK1khLpmg1/VC863em16XONSbXZR8u0Z+shEl/8bGPOelGvAJGY9n8ODkmRdiRldZLun8REu64kWq5R19xwlmBFaRmIvU635hpWB7G4X0qV5OixKcLsZXTJQH0NOz/PqIHGrodX/X8uRhLVt2OAocLaT6bCyaZ7fwQUMjaEfTO48mgFmkTOq7gMRumQn2uKZzObqDuwKypVjleviWkJMjkJvQvgl0vgndNEJ4hgg0ApQkKIkMMGHloo8YzbNFAe057JMBCIncXwY3N9YDqyxN5XQr9C05zneDfj3qDQ62ea4DCzfeFagJLNQQhEOEJ2g6QskExVNJFgG+RsfJ+gFPfj0a9bGPTOxdxwJ+dAXwQwv4gDCWIAP4MUYYGiFKYnlZ7gdz7zzk3o0gjwzIM2lMniVudwuwG1ocpc2iuMEVIB4ZEG8zZe+nKL5LEzJA2/cuRPsa5xmfcq2hNORxKBuEgDn5aZt9zYqN3Fi5+fmOGPD7DDT9DgO24Ca8AgG1r+4wkGdPBraQC6xlc37IMwC5PA/wU8XGw1MjoPNvC8XRH1sBT2FvwMSx2P0oE6PzlieyMIGvwYRroM5SSO10l5diyRe8oPm0lQ6iwlbngfO1xvdvJsRB56l0K3ifJrbPxGc9Xba/yPYvoe5N9p2hyaHuFLC1z/UDZKczS3bbaapXz3sDpjd8W85ZLzwRtFywWkvzJIE9eRxKllORvfRzahu7ampclvTQUVhz8MqbzpP/lIKOxR1eeGeQCZ/Rd4kzOFnVCtpz1mzlsqNnSQ6l9Y1lDCftsYOwqyQYkSqkS2RUVwXWiaNC7QTh4B7ToVfbEG+Y51uMyK3yIdcMEN/FqvwYFsINTBMR3MRCGFc6Gl5pTPqPqNakZw0OwCtv9+h/x3LzYzO69rG5+MaTd2W3YfRLZ8TO7juxdOubbJQDhmH5kZv82qBhFPTfE7qng4Yz+v89aLDV3oyyqA4jIETAshoHf3U2ZxzbD5NjXBAf3CzJsCXcGMUzVbMLFXvRZSXUsRQCsQlRtFdq5kQzaCSITOQRaAPLs8WVDxQ9RoNr61qKAbeKHj2zGGA6lyKN5WdKiX1ON5tsfiT0a9z0d+PDu/46utA5dFAPT5Rgbu1DKsdm+BDzQYMCRjT8/nUlZ2QUDaPw9LqG+v51nVFzz/oZLKSmOFJ2CoxRYHdPYKRIrCRgvIi0XNCVqSw0ApUAB+oLxtRWmcTSJsax8m7g5pLO9KNlTJvB1ToPnErHVz/kt6a2L+d6CI+1ply5+tISTxrlbnFUWWdQmCrL2ywSJoJddvV7cbNrtRLiqD2a62+wCpQHn7XIfGAz7ZPBRbN89HRH17HT0G1/dVFdjvanK/70Xw==
\ No newline at end of file
diff --git a/pyminer/lib/extensions/extensions_manager/vermanager/__init__.py b/pyminer/lib/extensions/extensions_manager/vermanager/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..02f1553c907fd3b61553cd21c0c4be53b97e415d
--- /dev/null
+++ b/pyminer/lib/extensions/extensions_manager/vermanager/__init__.py
@@ -0,0 +1 @@
+from .vermanager import Module, VersionsManager
\ No newline at end of file
diff --git a/pyminer/lib/extensions/extensions_manager/vermanager/vermanager.py b/pyminer/lib/extensions/extensions_manager/vermanager/vermanager.py
new file mode 100644
index 0000000000000000000000000000000000000000..f31b9f877c2b8f222fc9970cfda039a45f3fc9cc
--- /dev/null
+++ b/pyminer/lib/extensions/extensions_manager/vermanager/vermanager.py
@@ -0,0 +1,138 @@
+import threading
+import subprocess
+import time
+import re
+from typing import List
+import logging
+
+logger = logging.getLogger(__name__)
+
+class Module(object):
+ '''
+ Notation:
+ ---
+ name: name
+ version: 1.2.3
+ ver_num: [1, 2, 3]
+ relation: ==|>=|<=
+ module_str: name==1.2.3
+ requirement: name(==|>=|<=)1.2.3
+ '''
+ def __init__(self, module_str: str):
+ if module_str.count('==') != 1:
+ self.valid = False
+ return
+ module_str = module_str.strip()
+ self.name, self.version = module_str.split('==')
+ try:
+ assert re.match('^[a-zA-Z][-_a-zA-Z0-9]*', self.name) is not None
+ self.ver_num = self.parse(self.version)
+ except ValueError:
+ self.valid = False
+ return
+ self.requirements = []
+ self.valid = True
+
+ def meet_requirement_str(self, requirement: str) -> bool:
+ requirement = requirement.strip()
+ match = re.search(r'==|<=|>=', requirement)
+ if match:
+ relation = match.group(0)
+ name, version = re.split(r'==|<=|>=', requirement)
+ assert self.name == name
+ return self.meet_requirement(relation, version)
+ else:
+ assert self.name == requirement
+ return True
+
+ def meet_requirement(self, relation: str, version: str):
+ met = self._meet_requirement(relation, version)
+ self.requirements.append((relation, version)) # in case error do not record
+ return met
+
+ def _meet_requirement(self, relation: str, version: str) -> bool:
+ if version == self.version:
+ return True
+ elif relation == '==':
+ return False
+ else:
+ cmp = self.version_compare(self.ver_num, self.parse(version))
+ if relation == '>=':
+ return cmp >= 0
+ elif relation == '<=':
+ return cmp <= 0
+ else:
+ raise ValueError(f'{relation} is not a valid relation')
+
+ def is_conflict(self):
+ for relation, version in self.requirements:
+ if not self._meet_requirement(relation, version): # do not record again
+ return True
+ else:
+ return False
+
+ def update(self, version: str):
+ self.version, version = version, self.version
+ self.ver_num, ver_num = self.parse(self.version), self.ver_num
+ if self.is_conflict():
+ self.version = version
+ self.ver_num = ver_num
+ raise Exception('Conflict with requirements')
+ else:
+ logger.error(f'{self.name} should be updated to {self.version}')
+
+ def parse(self, version: str) -> List[int]:
+ ver_match = re.search(r'\d+(\.\d+)*', version)
+ if ver_match is None:
+ raise ValueError(f'unsupported version {version}')
+ ver = ver_match.group(0)
+ try:
+ return [int(i) for i in ver.split('.')]
+ except:
+ raise ValueError(f'invalid version {ver}')
+
+ def version_compare(self, ver_num1: str, ver_num2: str) -> int:
+ for v1, v2 in zip(ver_num1, ver_num2):
+ if v1 > v2:
+ return 1
+ elif v1 < v2:
+ return -1
+ else:
+ continue
+ return 0
+
+ def __str__(self):
+ if not self.valid:
+ return 'invalid version'
+ return f'{self.name}=={self.version}'
+
+ __repr__ = __str__
+
+class VersionsManager(object):
+ def __init__(self):
+ self.current_modules = {}
+
+ def set_current_modules(self, current_modules_lst: List[str]):
+ current_modules = [Module(module_str) for module_str in current_modules_lst if module_str]
+ self.current_modules.update({module.name: module for module in current_modules if module.valid})
+
+ def check_requirements(self, requirements: List[str]) -> set:
+ solvable = set() # requirements
+ conflict = set() # module names
+ for requirement in requirements:
+ name_version_tuple = re.split(r'==|<=|>=', requirement)
+ name = name_version_tuple[0]
+ if name not in self.current_modules:
+ logger.error(f'No version {name} was found')
+ solvable.add(requirement)
+ continue
+ module = self.current_modules[name]
+ if not module.meet_requirement_str(requirement):
+ try:
+ version = name_version_tuple[1] # if not met, there must be version requirement
+ module.update(version)
+ solvable.add(requirement)
+ except:
+ logger.error(f'Unsolved conflict in version {name}')
+ conflict.add(name)
+ return solvable, conflict
diff --git a/pyminer/lib/extensions/index.rst b/pyminer/lib/extensions/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..0da2d95edff9aca49673b5a2b0b18b31a5e945ce
--- /dev/null
+++ b/pyminer/lib/extensions/index.rst
@@ -0,0 +1,25 @@
+==============================================================================
+插件管理工具
+==============================================================================
+
+.. automodule:: features.extensions
+ :members:
+ :undoc-members:
+
+.. toctree::
+ :maxdepth: 2
+
+ extensionlib/index.rst
+ extensions_manager/index.rst
+ package_manager/index.rst
+ test_demo/index.rst
+ extensionlib/readme.md
+ extensions_manager/README.md
+ test_demo/README.md
+
+
+本程序包是PyMiner的插件管理工具,此处主要介绍该工具的实现原理及接口,关于用户级的插件开发文档可以见 extensions_ 。
+
+.. _extensions: https://gitee.com/py2cn/pyminer/wikis/%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8?sort_id=2809600
+
+
diff --git a/pyminer/lib/feedback.py b/pyminer/lib/feedback.py
new file mode 100644
index 0000000000000000000000000000000000000000..d2df3cdc8358fab41b0e3ae1688605934d5c04a7
--- /dev/null
+++ b/pyminer/lib/feedback.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+# @Time : 2021/2/12 16:24
+# @Author : jcl
+# @Email : 2195932461@qq.com
+# @File : feedback.py
+# @Software: PyCharm
+import requests
+from PySide2.QtWidgets import QDialog, QTextEdit, QLabel, QVBoxLayout, QApplication, QPushButton, QMessageBox
+
+from lib.settings import Setting
+
+
+class FeedbackClient(QDialog):
+ def __init__(self):
+ super().__init__()
+ self.vbox = QVBoxLayout()
+ self.setLayout(self.vbox)
+ self.text_edit = QTextEdit()
+ self.setWindowTitle(self.tr('Feedback'))
+ self.label = QLabel(
+ self.tr('You can give feedback through issue on suggestions or problems encountered in use! (<200 words)'))
+ self.confirm_button = QPushButton(self.tr('confirm'))
+ self.confirm_button.setFixedWidth(75)
+ self.confirm_button.clicked.connect(self.post)
+ self.vbox.addWidget(self.label)
+ self.vbox.addWidget(self.text_edit)
+ self.vbox.addWidget(self.confirm_button)
+ setting = Setting()
+ self.version = setting.get_system_version()
+ self.exec_()
+
+ def post(self):
+ text = self.text_edit.toPlainText()
+ version = self.version
+ url = 'http://www.pyminer.com/api/v1/feedback/'
+ data = {'core': version, 'feedback': text}
+ r = requests.post(url, data)
+ if r.status_code == 201:
+ QMessageBox.about(self, self.tr('result'), self.tr('Submitted successfully!'))
+ else:
+ QMessageBox.warning(self, self.tr('result'), r.text, QMessageBox.Yes)
+
+
+if __name__ == '__main__':
+ import sys
+
+ app = QApplication(sys.argv)
+ FeedbackClient()
diff --git a/pyminer/lib/index.rst b/pyminer/lib/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..d62cefe57eb2830f7f9193ac7de86c3e22b550db
--- /dev/null
+++ b/pyminer/lib/index.rst
@@ -0,0 +1,167 @@
+==================
+主界面及应用说明
+==================
+
+.. sectionauthor:: 侯展意
+
+重要补充说明
+-------------------
+
+.. sectionauthor:: 郑君
+
+``Workspace`` 需要用到 ``DataServer``,目前采用 ``FastAPI`` 框架。
+但无论什么框架,均引用 ``signal`` 模块,该模块只能在 ``main thread`` 使用。
+因此,主线程上必须运行 ``DataServer``,我在 ``pmappmoderm`` 中把 ``GUI`` 移动到新的线程中,``QT`` 会报 ``warning``,但是不影响运行。
+如有问题及时联系我。
+
+
+主界面代码说明
+-------------------
+
+源码位置及继承关系
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+主界面类指定义在 ``pmappmodern.py`` 的 ``MainWindow`` 类。
+
+
+继承自 ``features.ui.generalwidgets.BaseMainWindow`` 。
+
+界面功能
+^^^^^^^^^^^^
+
+1. 具有若干可自由拖动的停靠窗口,并可以记忆窗口的布局。
+#. 具有若干可通过选项卡切换的工具栏。
+
+工具栏
+^^^^^^^^^
+
+添加工具栏
+""""""""""""""
+
+主界面的工具栏使用的是 ``PMGToolBar`` 这个类,继承自 ``QToolBar`` 。
+
+主窗口 ``add_tool_bar()`` 方法,可以将 ``QToolBar`` 或 ``PMGToolBar`` ,抑或是你自己的继承于 ``QToolBar`` 的工具栏添加到主窗口。
+
+获取工具栏
+""""""""""""""
+
+工具栏通过它的名称进行访问。
+
+预定义的工具栏只有一个,名曰 ``'toolbar_home'`` 。
+
+举个例子讲,要获取主页对应的工具栏,那么就使用 ``MainWindow.toolbars.get('toolbar_home')`` 进行获取就可以了。
+
+在工具栏上添加按钮
+"""""""""""""""""""""""""""
+
+获取工具栏后,调用 ``MainWindow.add_tool_button()`` 即可添加一个按钮,或者调用 ``add_tool_buttons()`` 添加多个竖排的按钮。
+这两个函数的返回值分别为 ``QPushButton`` 和 ``List[QPushButton]`` 。
+
+在工具栏上添加控件
+"""""""""""""""""""""""""""""
+
+添加控件的方法与在 ``QToolBar`` 上添加控件完全相同,亦即继承了 ``addWidget`` 方法,在此不再赘述。
+
+添加带有菜单的按钮
+""""""""""""""""""""""""""""
+
+如果需要菜单效果,可以用 ``QMenu`` 写一个菜单,然后添加到按钮之上。
+
+工具栏实现细节
+"""""""""""""""""""""""""""
+
+工具栏看似使用了选项卡,其实不然。
+
+最顶端的“选项卡样控件”实为插入了按钮的 ``QToolBar`` ,可以依靠其按钮的点击来进行工具栏的切换。
+
+切换工具栏时,其他工具栏隐藏,只有按钮对应的工具栏可以显示。详见switch_toolbar方法。
+
+主界面停靠窗口
+^^^^^^^^^^^^^^^^^^
+
+主界面由一系列 ``PMDockWidget`` 组成。
+此控件继承于 ``QDockWidget`` 。
+
+添加停靠窗口的方法
+""""""""""""""""""""""""""""
+
+``MainWindow.add_widget_on_dock()`` 方法可以用来将任意控件添加到dock,而且下次加载之时布局会被保存。
+
+停靠窗口的获取
+""""""""""""""""""""
+
+``MainWindow.get_dock_widget(widget_name:str)->PMDockWidget``
+
+根据名称进行停靠窗口的获取。如果名称不存在,那么就会抛出异常。
+
+关于快速启动的说明
+"""""""""""""""""""""""""""""
+
+为了加快软件启动速度,控件(假设其类名为 ``Widget`` )可以定义方法 ``Widget.setup_ui`` 。
+
+当加载时,首先执行控件的 ``__init__`` ,并且将 ``setup_ui`` 压入任务栈之中,
+等到主界面显示出来之后再用定时器调用执行控件的 ``setup_ui`` 方法。
+
+对于核心控件可以定义 ``show_directly=True`` ,保证立即执行 ``setup_ui`` 方法。
+或者干脆不写``setup_ui`` 方法,而是将启动方法放在 ``__init__`` 之中。
+
+窗口隐藏与销毁
+""""""""""""""""
+
+``dockwidget`` 的隐藏与显示可以通过主页选项卡中’视图‘菜单进行管理。
+当点击窗口关闭按钮的时候,窗口会被“关闭”。
+但实际上窗口并未被销毁,也就是被隐藏起来了。
+如果确实要销毁窗口,那么请重写 ``PMDockWidget`` 中的 ``closeEvent`` 方法,确保窗口在被关闭的时候被正确销毁了。
+
+对于工具栏基类的建议
+""""""""""""""""""""""""""
+
+无论是主界面还是插件,建议继承 ``PMGToolBar`` 类,这个类有设置好的样式。
+
+使用 ``QToolbar`` 尽管不会出错,但是样式很难统一。
+
+.. code-block:: python
+
+ from widgets import PMGToolBar
+
+
+主界面的全局获取方法
+-------------------------
+
+在 ``pyminer.globals`` 中定义了关于主界面的一些全局操作。
+
+
+获取主界面
+^^^^^^^^^^^^^^^^
+
+.. code-block:: python
+
+ get_main_window()->MainWindow
+
+主界面是一个全局变量,应用启动时对其赋值。
+这种写法尽管原始,但主要目的在于尽可能的减少循环导入的可能性,方便未来的插件开发。
+
+获取根路径
+^^^^^^^^^^^^^^^^^
+
+.. code-block:: python
+
+ get_root_path()->str
+
+
+
+.. toctree::
+ :maxdepth: 2
+
+ config/index.rst
+ data_adapter/index.rst
+ extensions/index.rst
+ features/index.rst
+ tests/index.rst
+ ui/index.rst
+ workspace/index.rst
+ workspace2/index.rst
+
+ pmappmodern.rst
+ globals.rst
+
diff --git a/pyminer/lib/interpretermanager/__init__.py b/pyminer/lib/interpretermanager/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/lib/interpretermanager/interpretermanager.py b/pyminer/lib/interpretermanager/interpretermanager.py
new file mode 100644
index 0000000000000000000000000000000000000000..7586d3e67f2a14b3c75fba35b613091067912aa1
--- /dev/null
+++ b/pyminer/lib/interpretermanager/interpretermanager.py
@@ -0,0 +1,204 @@
+"""
+settings['interpreters'] =
+{'base':{'name':'base',
+ 'info':'pypy3 python 3.6 compatible',
+ 'path':'/home/.../python3.8'}
+}
+"""
+import os
+import sys
+from typing import Dict, List
+
+from PySide2.QtWidgets import QPushButton, QHBoxLayout, QVBoxLayout, QWidget, QDialog, \
+ QListWidget, QMessageBox, QSpacerItem, QSizePolicy
+
+import utils
+from widgets import PMGPanelDialog
+from utils import get_settings_item_from_file
+
+
+class Interpreter():
+ def __init__(self, name: str, absolute_path: str, version: str):
+ self.name = name
+ self.path = absolute_path
+ self.versioin = version
+
+
+class InterpreterManager():
+ interpreter_paths = []
+
+ def __init__(self):
+ pass
+
+
+def get_interpreter_version(interpreter_path: str) -> str:
+ version = os.popen('%s -c \"import sys;print(sys.version.split()[0])\"' % interpreter_path).read().strip()
+ return version
+
+
+def get_all_external_interpreters() -> List[Dict]:
+ """
+ 获取所有的外部解释器
+ Returns:
+
+ """
+ return utils.get_settings_item_from_file("config.ini", "RUN/EXTERNAL_INTERPRETERS")
+
+
+def modify_interpreter_config(mode, info: Dict = None, index: int = -1):
+ """
+
+ Args:
+ info: 一个存储解释器信息的字典
+ mode: "add","delete","modify"
+
+ Returns:
+
+ """
+
+ external_interpreters = utils.get_settings_item_from_file("config.ini", "RUN/EXTERNAL_INTERPRETERS")
+ if mode == "add":
+ external_interpreters.append(info)
+ elif mode == "modify":
+ external_interpreters[index] = info
+ elif mode == "delete":
+ external_interpreters.pop(index)
+ else:
+ raise NotImplementedError(mode)
+ utils.write_settings_item_to_file("config.ini", "RUN/EXTERNAL_INTERPRETERS", external_interpreters)
+
+
+class InterpreterManagerWidget(QWidget):
+ def __init__(self, parent=None):
+ super(InterpreterManagerWidget, self).__init__(parent)
+ self.interpreters_list_show = QListWidget()
+ self.button_add = QPushButton('+')
+ self.button_delete = QPushButton('-')
+ self.button_edit = QPushButton(self.tr('Edit'))
+ self.button_manage_packages = QPushButton(self.tr('Packages'))
+ self.setLayout(QHBoxLayout())
+ self.layout().addWidget(self.interpreters_list_show)
+ self.button_layout = QVBoxLayout()
+ self.layout().addLayout(self.button_layout)
+ self.button_layout.addWidget(self.button_add)
+ self.button_layout.addWidget(self.button_edit)
+ self.button_layout.addWidget(self.button_manage_packages)
+ self.button_layout.addWidget(self.button_delete)
+ self.button_layout.addItem(QSpacerItem(0, 20, QSizePolicy.Minimum, QSizePolicy.Expanding))
+
+ self.button_add.clicked.connect(self.add)
+ self.button_edit.clicked.connect(self.edit)
+ self.button_delete.clicked.connect(self.remove)
+ self.button_manage_packages.clicked.connect(self.manage_packages)
+ self.interpreters_list_show.currentItemChanged.connect(self.on_list_current_item_changed)
+ self.show_interpreters()
+
+ def gen_info_template(self):
+ views = [
+ ('line_ctrl', 'name', '名称', ''),
+ ('file_ctrl', 'interpreter_path', '解释器路径', '')
+ ]
+
+ def add(self):
+ views = [
+ ('line_ctrl', 'name', self.tr('Name'), ''),
+ ('file_ctrl', 'path', self.tr('Executable Path'), '', 'Executables(*.exe)')
+ ]
+
+ def set_interpreter_name(settings):
+ interpreter_path = settings['path']
+ dlg.panel.get_ctrl('name').set_value(os.path.basename(interpreter_path))
+
+ dlg = PMGPanelDialog(parent=self, views=views)
+ dlg.panel.set_param_changed_callback('path', set_interpreter_name)
+
+ ret = dlg.exec_()
+ if ret == QDialog.Accepted:
+ res = dlg.get_value()
+ d = {'name': res['name'], 'path': res['path'], 'version': get_interpreter_version(res['path'])}
+ modify_interpreter_config("add", d)
+ self.show_interpreters()
+
+ def edit(self):
+ current_index = self.interpreters_list_show.currentRow()
+ list_index = current_index - 1 # 第一个是默认解释器,所以要减去1
+
+ external_interpreters = get_all_external_interpreters()
+ current_interpreter = external_interpreters[list_index]
+
+ views = [
+ ('line_ctrl', 'name', self.tr('Name'), current_interpreter['name']),
+ ('file_ctrl', 'path', self.tr('Executable Path'), current_interpreter['path'])
+ ]
+
+ dlg = PMGPanelDialog(parent=self, views=views)
+ dlg.verify = self.verify
+ ret = dlg.exec_()
+
+ if ret == QDialog.Accepted:
+ res = dlg.get_value()
+ d = {'name': res['name'], 'path': res['path'], 'version': get_interpreter_version(res['path'])}
+ modify_interpreter_config("modify", d, list_index)
+ self.show_interpreters()
+
+ def manage_packages(self):
+ from features.interpretermanager.packagemanager import MarketPlace
+ current_interpreter = self.get_current_interpreter()
+ if current_interpreter is not None:
+ path = current_interpreter['path']
+ mp = MarketPlace(path)
+ mp.exec_()
+
+ def verify(self, values) -> bool:
+ LEN = 16
+ if len(values['name']) > LEN:
+ QMessageBox.warning(self, self.tr('Warning'),
+ self.tr(
+ f'Name should be less than {repr(LEN)} characters,but your input was %d characters.' %
+ len(values['name'])))
+ return False
+ else:
+ return True
+
+ def remove(self):
+ modify_interpreter_config("delete", index=self.interpreters_list_show.currentRow() - 1)
+ self.show_interpreters()
+
+ def show_interpreters(self):
+ self.interpreters_list_show.clear()
+ self.interpreters_list_show.addItem(self.tr('BuiltIn (3.8.5)'))
+ ext_interpreters = get_all_external_interpreters()
+ for interpreter in ext_interpreters:
+ name = interpreter['name']
+ version = interpreter['version']
+ text = name + ' (%s)' % version
+ self.interpreters_list_show.addItem(text)
+
+ def on_list_current_item_changed(self):
+ """
+ 如果是默认解释器的时候,不支持修改。
+ Returns:
+
+ """
+ self.button_delete.setEnabled(not self.interpreters_list_show.currentRow() == 0)
+ self.button_edit.setEnabled(not self.interpreters_list_show.currentRow() == 0)
+
+ def get_current_interpreter(self) -> Dict:
+
+ current_index = self.interpreters_list_show.currentRow()
+ if current_index == 0:
+ return {'name': 'Builtin', 'path': sys.executable, 'version': sys.version.split()[0]}
+ elif current_index < 0:
+ return
+ else:
+ list_index = current_index - 1
+ return get_all_external_interpreters()[list_index]
+
+
+if __name__ == '__main__':
+ from PySide2.QtWidgets import QApplication
+
+ app = QApplication([])
+ w = InterpreterManagerWidget()
+ w.show()
+ app.exec_()
diff --git a/pyminer/lib/interpretermanager/packagemanager.py b/pyminer/lib/interpretermanager/packagemanager.py
new file mode 100644
index 0000000000000000000000000000000000000000..3f3695135f5ec53697da8b0bd88c10ab80f637dc
--- /dev/null
+++ b/pyminer/lib/interpretermanager/packagemanager.py
@@ -0,0 +1,200 @@
+import subprocess
+import sys
+from PySide2.QtWidgets import QFrame, QTableWidgetItem, QTableWidget, QMessageBox, QDialog, QDesktopWidget, QHeaderView, \
+ QProgressDialog
+from PySide2.QtGui import QCloseEvent
+from PySide2.QtCore import Signal, Qt, QUrl, QPropertyAnimation, QCoreApplication
+from lib.ui.base.pm_marketplace.package_manager_main import Ui_Form as marketplace_Ui_Form
+from lib.ui.base.pm_marketplace.install import Ui_Form as marketplace_install_Ui_Form
+from lib.ui.base.pm_marketplace.uninstall import Ui_Dialog as marketplace_uninstall_Ui_Dialog
+
+from widgets import PMGOneShotThreadRunner
+
+
+class MarketPlaceUninstall(QDialog, marketplace_uninstall_Ui_Dialog):
+ signal_packages_changed = Signal()
+
+ def __init__(self, parent=None, executable='', package_name=''):
+ super(MarketPlaceUninstall, self).__init__(parent=parent)
+ if executable == '':
+ executable = sys.executable
+ self._executable = executable
+ self.setupUi(self)
+
+ self.log_console.set_args(['%s' % self._executable, '-m', 'pip', 'uninstall', package_name, '-y'])
+ self.log_console.button_to_start.hide()
+ self.log_console.button_to_terminate.hide()
+ self.log_console.start_process()
+ self.button_close.clicked.connect(self.close)
+
+ def closeEvent(self, a0: QCloseEvent) -> None:
+ self.signal_packages_changed.emit()
+
+
+class MarketPlaceInstall(QDialog, marketplace_install_Ui_Form):
+ signal_packages_changed = Signal()
+
+ def __init__(self, parent=None, executable=''):
+ super(MarketPlaceInstall, self).__init__(parent=parent)
+ if executable == '':
+ executable = sys.executable
+ self._executable = executable
+ self.setupUi(self)
+ self.center()
+ self.groupBox.setVisible(False) # 隐藏详情窗口。这是因为pip search功能现在已经无法使用。
+ self.exec_log.button_to_start.setVisible(False)
+ self.exec_log.button_to_terminate.setVisible(False)
+
+ self.exec_log.set_args(['%s' % self._executable, '-m', 'pip', 'download', 'pyqtgraph',
+ '-i', 'https://pypi.tuna.tsinghua.edu.cn/simple'])
+ # self.exec_log.start_process()
+
+ self.checkBox_version.clicked.connect(self.checkbox_version_clicked)
+ self.pushButton_install.clicked.connect(self.install)
+ self.pushButton_close.clicked.connect(self.close)
+ _trans = lambda text: QCoreApplication.translate('marketplace_install_Ui_Form', text)
+
+ self.origins = ['https://mirrors.cloud.tencent.com/pypi/simple',
+ 'https://pypi.org/simple',
+ 'https://pypi.tuna.tsinghua.edu.cn/simple',
+ 'https://mirrors.aliyun.com/pypi/simple/',
+ 'http://pypi.douban.com/simple/'
+ ]
+ self.label_3.hide()
+ self.comboBox_dir.hide()
+ self.lineEdit_dir.hide()
+ self.toolButton.hide()
+ self.checkbox_version_clicked(None)
+ self.comboBox_source.currentIndexChanged.connect(self.combobox_source_changed)
+ self.lineEdit_source.setEnabled(False)
+
+ def checkbox_version_clicked(self, checked):
+ if self.checkBox_version.isChecked():
+ self.lineEdit_version.setEnabled(True)
+ else:
+ self.lineEdit_version.setEnabled(False)
+ self.lineEdit_version.setText('')
+
+ def combobox_source_changed(self, s):
+ index = self.comboBox_source.currentIndex()
+ if index == len(self.origins):
+ self.lineEdit_source.setEnabled(True)
+ else:
+ self.lineEdit_source.setEnabled(False)
+ self.lineEdit_source.setText(self.origins[index])
+
+ def center(self):
+ qr = self.frameGeometry()
+ cp = QDesktopWidget().availableGeometry().center()
+ qr.moveCenter(cp)
+ self.move(qr.topLeft())
+
+ def install(self):
+ package_name = self.lineEdit_name.text()
+ origin_url = self.lineEdit_source.text()
+ version = self.lineEdit_version.text()
+ version = version if self.checkBox_version.isChecked() else ''
+
+ if package_name.strip() == '':
+ QMessageBox.warning(self, self.tr('Warning'),
+ self.tr('Package Name should not be empty!' % self.comboBox_source.currentText()))
+ return
+ if origin_url == '':
+ QMessageBox.warning(self, self.tr('Warning'),
+ self.tr('Origin %s not exist!' % self.comboBox_source.currentText()))
+ return
+ if version != '':
+ package_name = package_name + '==' + version
+ self.exec_log.set_args(['%s' % self._executable, '-m', 'pip', 'install', package_name, '-i', origin_url])
+ self.exec_log.start_process()
+
+ def closeEvent(self, a0: QCloseEvent) -> None:
+ self.signal_packages_changed.emit()
+
+
+class MarketPlace(QDialog, marketplace_Ui_Form):
+ def __init__(self, executable: str = ''):
+ super(MarketPlace, self).__init__()
+ if executable == '':
+ executable = sys.executable
+ self._executable = executable
+ self.setupUi(self)
+
+ self.tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+ self.center()
+
+ self.pip_list()
+
+ # 绑定事件
+ self.toolButton_install.clicked.connect(self.pip_install_display)
+ self.toolButton_uninstall.clicked.connect(self.pip_uninstall_display)
+
+ def keyPressEvent(self, e):
+ """
+ 按键盘Escape退出当前窗口
+ @param e:
+ """
+ if e.key() == Qt.Key_Escape:
+ self.close()
+
+ def center(self):
+ qr = self.frameGeometry()
+ cp = QDesktopWidget().availableGeometry().center()
+ qr.moveCenter(cp)
+ self.move(qr.topLeft())
+
+ def pip_list(self):
+
+ def list_pip_packages(executable):
+ cmd = executable + ' -m pip list'
+ p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
+ out, err = p.communicate()
+ x = 1
+ package_str = list()
+ for r in out.splitlines():
+ if x > 2:
+ package_str.append(r)
+ x = x + 1
+ return package_str
+
+ def refresh_table(package_str):
+ package_len = len(package_str)
+ # 设置表格长度
+ self.tableWidget.setRowCount(package_len)
+
+ # 将结果显示在表格中
+ for i in range(package_len):
+ for j in range(2):
+ items_value = package_str[i].split()[j]
+ item = QTableWidgetItem(str(items_value, encoding="utf-8"))
+ item.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
+ self.tableWidget.setItem(i, j, item)
+
+ self.process_dlg = QProgressDialog(parent=self, labelText=('Scanning packages..'))
+ self.process_dlg.setCancelButton(None)
+ self.process_dlg.setWindowFlags(self.process_dlg.windowFlags() | Qt.FramelessWindowHint)
+ self.process_dlg.setRange(0, 0)
+
+ self.pip_th = PMGOneShotThreadRunner(list_pip_packages, args=(self._executable,))
+ self.pip_th.signal_finished.connect(refresh_table)
+ self.pip_th.signal_finished.connect(self.process_dlg.close)
+ self.process_dlg.show()
+
+ def pip_install_display(self):
+ pm_pack_install = MarketPlaceInstall(self, executable=self._executable)
+ pm_pack_install.show()
+ pm_pack_install.signal_packages_changed.connect(self.pip_list)
+
+ def pip_uninstall_display(self):
+ row = self.tableWidget.currentRow()
+ if row >= 0:
+ current_package = self.tableWidget.item(row, 0).text()
+ ret = QMessageBox.warning(self, self.tr('Warning'),
+ self.tr('Are you sure to remove package \'%s\'?' % current_package),
+ QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
+
+ if ret == QMessageBox.Yes:
+ pm_pack_uninstall = MarketPlaceUninstall(self, executable=self._executable,
+ package_name=current_package)
+ pm_pack_uninstall.show()
+ pm_pack_uninstall.signal_packages_changed.connect(self.pip_list)
diff --git a/pyminer/lib/io/__init__.py b/pyminer/lib/io/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/lib/io/database.py b/pyminer/lib/io/database.py
new file mode 100644
index 0000000000000000000000000000000000000000..eced28f725c16bab9df6ec03ea8e239a086dde10
--- /dev/null
+++ b/pyminer/lib/io/database.py
@@ -0,0 +1,155 @@
+from typing import List
+
+from widgets import PMGPanel
+from PySide2.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QMessageBox, QListWidget, QListWidgetItem, \
+ QComboBox, QDialog, QInputDialog
+from utils import dbConnectTool, get_all_accounts_list, get_all_accounts, dbConnectAccountTool, create_account
+from lib.io.dbConn import DatabaseConn
+
+
+class DatabaseConfigPanel(QDialog):
+ def __init__(self, parent=None):
+ super(DatabaseConfigPanel, self).__init__(parent)
+ self.setLayout(QVBoxLayout())
+ self.combo_select_database_type = QComboBox()
+ self.combo_select_database_type.addItems(['MySQL', 'PostgreSQL', 'Oracle', 'SQL Server', 'SQLite'])
+ self.combo_select_database_type.currentIndexChanged.connect(self.on_database_selected)
+ self.layout().addWidget(self.combo_select_database_type)
+ conn = DatabaseConn()
+ views, engine = conn.create_conn('mysql')
+
+ self.settings_panel = PMGPanel(parent=self, views=views)
+ self.settings_panel.signal_settings_changed.connect(self.on_settings_changed)
+ self.upper_layout = QHBoxLayout()
+ self.layout().addLayout(self.upper_layout)
+ self.upper_layout.addWidget(self.settings_panel)
+ self.conn_list = QListWidget()
+ self.conn_list.itemDoubleClicked.connect(self.item_double_clicked)
+ self.upper_layout.addWidget(self.conn_list)
+ self.button_test_connection = QPushButton(self.tr('Test Connection'))
+ self.button_test_connection.clicked.connect(self.test_connection)
+ self.button_create_account = QPushButton(self.tr('Create Account'))
+ self.button_create_account.clicked.connect(self.create_account)
+ self.button_layout = QHBoxLayout()
+ self.button_layout.addWidget(self.button_test_connection)
+ self.button_layout.addWidget(self.button_create_account)
+ self.layout().addLayout(self.button_layout)
+ # self.settings_panel.on_settings_changed()
+ index = self.combo_select_database_type.currentIndex()
+ self.on_database_selected(index)
+
+ def get_current_db_type(self) -> str:
+ return self.combo_select_database_type.currentText()
+
+ def on_database_selected(self, index):
+ text = self.get_current_db_type().lower()
+ conn = DatabaseConn()
+ views, engine = conn.create_conn(text)
+ self.settings_panel.set_items(views)
+
+ db_type = self.get_current_db_type().lower()
+ accounts = get_all_accounts()
+ db_accounts = accounts.get(db_type)
+ if db_accounts is not None:
+ l = list(db_accounts.keys())
+ else:
+ l = []
+ self.show_accounts(l)
+
+ def on_settings_changed(self, settings):
+ pass
+
+ def item_double_clicked(self, item: QListWidgetItem):
+ """
+ 列表项双击触发回调
+ :param item:
+ :return:
+ """
+ settings = self.settings_panel.get_value()
+ db_type = self.get_current_db_type().lower()
+ conn_name = item.text()
+ print(db_type, conn_name)
+ # 模拟获取某个账号的信息、SSH账号等信息
+ accounts = get_all_accounts()
+ print(accounts, db_type, conn_name)
+ if accounts.get(db_type) is not None:
+ account = accounts.get(db_type).get(conn_name)
+ if account is not None:
+ self.settings_panel.set_value(account.get('account'))
+
+ # print(accounts.get(db_type).get(conn_name))
+
+ def show_accounts(self, accounts: List[str]):
+ """
+ 在列表显示所有的连接
+ :return:
+ """
+ self.conn_list.clear()
+ self.conn_list.addItems(accounts)
+
+ def test_connection(self):
+ """
+ 连接检测
+ :return:
+ """
+ params = self.settings_panel.get_value()
+ connectaccount = dict(
+ account=dict(
+ user="root", password="123456", host="localhost",
+ port="3306", database="learning", charset="utf-8"
+ ),
+ usessh=False,
+ SSH={},
+ connectdescribe="这又是一个测试"
+ )
+ connectaccount['account'].update(params)
+ print(connectaccount)
+ # dbCA = dbConnectAccountTool()
+ mysql_url = "mysql+pymysql://{user}:{password}@{host}:{port}/{database}"
+ dbCT = dbConnectTool(account=connectaccount, conn_url=mysql_url)
+ connect_status = dbCT.createConn()
+ print(connect_status)
+ # db_functool = dbFuncTool(connectaccount, mysql_url)
+ # print(query(db_functool,'select * from scores;'))
+ if connect_status.get('status') == 'connect':
+ QMessageBox.information(self, self.tr('Connection Test'), self.tr('Connection Succeeded!'), QMessageBox.Ok)
+ else:
+ QMessageBox.information(self, self.tr('Connection Test'), self.tr('Connection Failed!'), QMessageBox.Ok)
+
+ def create_account(self):
+ """
+ 创建一个新的账户
+ [TODO]:尚未连接事件
+ :return:
+ """
+ print('创建账户——目前尚未连接事件')
+ account_name, stat = QInputDialog.getText(self, '输入账户名称', '请输入账户名称')
+ if account_name != '':
+ params = self.settings_panel.get_value()
+ connectaccount = dict(
+ account=dict(
+ user="root", password="123456", host="localhost",
+ port="3306", database="learning", charset="utf-8"
+ ),
+ usessh=False,
+ SSH={},
+ connectdescribe="这又是一个测试"
+ )
+ connectaccount['account'].update(params)
+ create_account(account_name, connectaccount)
+ index = self.combo_select_database_type.currentIndex()
+ self.on_database_selected(index)
+
+
+if __name__ == '__main__':
+ import sys
+ import cgitb
+
+ cgitb.enable()
+ from PySide2.QtWidgets import QApplication
+
+ app = QApplication(sys.argv)
+ sp2 = DatabaseConfigPanel()
+ # sp2.signal_settings_changed.connect(lambda settings: print('views2-settings', settings))
+ sp2.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/lib/io/dbConn.py b/pyminer/lib/io/dbConn.py
new file mode 100644
index 0000000000000000000000000000000000000000..13e22097e979a62510990f05c42670842f197215
--- /dev/null
+++ b/pyminer/lib/io/dbConn.py
@@ -0,0 +1,131 @@
+# -*- coding:utf8 -*-
+class DatabaseConn(object):
+
+ def tr(self, s: str):
+ return s
+
+ def __init__(self):
+ self.conn_url = "{user}:{password}@{host}:{port}/{database}"
+
+ def create_conn(self, dbtype="mysql"):
+ """
+ 选择创建的通道
+ Auth:
+ GanDaiWei
+ Args:
+ dbtype: 传入数据库类型
+ Returns:
+ view: 连接通道的视图效果
+ engine: 链接的 url 字符串
+ """
+ conn_dict = {
+ "mysql": self.mysql_conn,
+ "postgresql": self.pgsql_conn,
+ "oracle": self.oracle_conn,
+ "sql server": self.mssql_conn,
+ "sqlite": self.sqlite_conn
+ }
+
+ assert dbtype in conn_dict, 'Can not create connection to {}'.format(dbtype)
+ view, engine, engine_type = conn_dict[dbtype]()
+ engine = engine if engine_type else engine + self.conn_url # 如果 url 不完整情况下需要进行拼接
+ return (view, engine)
+
+ def mssql_conn(self):
+ """
+ 创建 SQL server 的链接
+ mssql = Microsoft SQL server,即 SQLserver
+ Returns:
+ 前端界面视图、使用引擎、是否完整
+ """
+ mssql_view = [
+ ('line_ctrl', 'database', self.tr('Database Name'), ''),
+ [
+ ('line_ctrl', 'host', self.tr('Host Name/IP'), ''),
+ ('line_ctrl', 'port', self.tr('Port'), '1521')
+ ],
+ ('line_ctrl', 'initialDB', self.tr('Initial Database'), 'master'), # 初始数据库连接
+ ('line_ctrl', 'user', self.tr('User'), ''),
+ ('password_ctrl', 'password', self.tr('Password'), ''),
+ ('line_ctrl', 'dbDesc', self.tr('Database Description'), '')
+ ]
+ return (mssql_view, "mssql+pymssql://", False)
+
+ def sqlite_conn(self):
+ """
+ 创建 SQLite 的链接
+ Returns:
+ 前端界面视图、使用引擎、是否完整
+ """
+ sqlite_view = [
+ ('line_ctrl', 'database', self.tr('Database Name'), ''),
+ ('line_ctrl', 'file', self.tr('Database File Road'), ''),
+ ('line_ctrl', 'user', self.tr('User'), ''),
+ ('password_ctrl', 'password', self.tr('Password'), ''),
+ ('line_ctrl', 'dbDesc', self.tr('Database Description'), '')
+ ]
+ return (sqlite_view, "sqlite://{}", True)
+
+ def oracle_conn(self):
+ """
+ 创建 Oracle 的链接
+ Returns:
+ 前端界面视图、使用引擎、是否完整
+ """
+ oracle_view = [
+ ('line_ctrl', 'database', self.tr('Database Name'), ''),
+ # ('combo_ctrl', 'connType', self.tr('Connect Type'), 'Basic', ['Basic', 'TNS']), # 暂时只支持 Basic 类型连接
+ [
+ ('line_ctrl', 'host', self.tr('Host Name/IP'), ''),
+ ('line_ctrl', 'port', self.tr('Port'), '1521')
+ ],
+ ('line_ctrl', 'user', self.tr('User'), ''),
+ ('password_ctrl', 'password', self.tr('Password'), ''),
+ ('line_ctrl', 'databasedesc', self.tr('Database Description'), '')
+ ]
+ return (oracle_view, "oracle://", False)
+
+ def pgsql_conn(self):
+ """
+ 创建 PgSQL 的链接
+ Returns:
+ 前端界面视图、使用引擎、是否完整
+ """
+ pgsql_view = [
+ ('line_ctrl', 'database', self.tr('Database Name'), ''),
+ [
+ ('line_ctrl', 'host', self.tr('Host Name/IP'), 'localhost'),
+ ('line_ctrl', 'port', self.tr('Port'), '5432')
+ ],
+ ('line_ctrl', 'initialDB', self.tr('Initial Database'), 'postgres'), # 初始数据库连接
+ ('line_ctrl', 'user', self.tr('User'), 'postgres'),
+ ('password_ctrl', 'password', self.tr('Password'), ''),
+ ('line_ctrl', 'databasedesc', self.tr('Database Description'), '')
+ ]
+ return (pgsql_view, "postgresql://", False)
+
+ def mysql_conn(self):
+ """
+ 创建MySQL的链接
+ Returns:
+ 前端界面视图、使用引擎、是否完整
+ """
+ mysql_view = [
+ ('line_ctrl', 'database', self.tr('Database Name'), ''),
+ [
+ ('line_ctrl', 'host', self.tr('Host Name/IP'), 'localhost'),
+ ('line_ctrl', 'port', self.tr('Port'), '3306')
+ ],
+ ('line_ctrl', 'user', self.tr('User'), 'root'),
+ ('password_ctrl', 'password', self.tr('Password'), ''),
+ ('line_ctrl', 'databasedesc', self.tr('Database Description'), '')
+ ]
+ return (mysql_view, "mysql+pymysql://", False)
+
+
+if __name__ == "__main__":
+ dbConn = DatabaseConn()
+ a = dbConn.create_conn("MySQL")
+ b = dbConn.create_conn("PgSQL")
+ print(a, b)
+ error = dbConn.create_conn("Mysql")
diff --git a/pyminer/lib/io/dbConnectAccount.pkl b/pyminer/lib/io/dbConnectAccount.pkl
new file mode 100644
index 0000000000000000000000000000000000000000..578b0ddcced73a5e27fc2b9e848266e362eeeaa2
Binary files /dev/null and b/pyminer/lib/io/dbConnectAccount.pkl differ
diff --git a/pyminer/lib/io/encoding.py b/pyminer/lib/io/encoding.py
new file mode 100644
index 0000000000000000000000000000000000000000..62ff2cb961e9f4db19ae1686cbff8cc00f2d296c
--- /dev/null
+++ b/pyminer/lib/io/encoding.py
@@ -0,0 +1,66 @@
+import os
+
+from PySide2.QtWidgets import QDialog, QPushButton, QVBoxLayout, QHBoxLayout, QTextBrowser, QMessageBox
+
+from widgets import PMGPanel
+import utils
+from utils import file_encoding_convert
+
+
+class EncodingConversionWidget(QDialog):
+ def __init__(self, parent=None):
+ super(EncodingConversionWidget, self).__init__(parent=parent)
+ views = [
+ ('file_ctrl', 'input_file', '读取文件名', '', '',
+ utils.get_settings_item_from_file("config.ini", "MAIN/PATH_WORKDIR")),
+ ('file_ctrl', 'output_file', '输出为文件', '', '',
+ utils.get_settings_item_from_file("config.ini", "MAIN/PATH_WORKDIR"), 'save'),
+ [
+ ('combo_ctrl', 'input_encoding', '读取编码方式', 'UTF8', ['UTF8', 'GBK', 'ASCII']),
+ ('combo_ctrl', 'output_encoding', '输出编码方式', 'UTF8', ['UTF8', 'GBK', 'ASCII'])
+ ]
+
+ ]
+ self.panel = PMGPanel(parent=self, views=views)
+ self.text_view = QTextBrowser()
+ self.button_preview = QPushButton('预览')
+ self.button_convert = QPushButton('转换')
+ self.setLayout(QVBoxLayout())
+
+ self.layout().addWidget(self.panel)
+ self.layout().addWidget(self.text_view)
+ button_layout = QHBoxLayout()
+ self.layout().addLayout(button_layout)
+ button_layout.addWidget(self.button_preview)
+ button_layout.addWidget(self.button_convert)
+
+ self.button_preview.clicked.connect(self.preview)
+ self.button_convert.clicked.connect(self.convert)
+
+ def preview(self):
+ info = self.panel.get_value()
+ size = os.path.getsize(info['input_file'])
+ with open(info['input_file'], 'r', encoding=info['input_encoding'], errors='replace')as f:
+ if size > 1000:
+ text = f.read(1000)
+ else:
+ text = f.read()
+ self.text_view.setText(text)
+
+ def convert(self):
+ info = self.panel.get_value()
+ try:
+ file_encoding_convert(info['input_file'], info['input_encoding'], info['output_file'],
+ info['output_encoding'])
+ QMessageBox.information(self, '提示', '转换完成!', QMessageBox.Ok, QMessageBox.Ok)
+ except FileNotFoundError:
+ QMessageBox.information(self, '未找到输出文件', '未找到输出文件', QMessageBox.Ok, QMessageBox.Ok)
+
+
+if __name__ == '__main__':
+ from PySide2.QtWidgets import QApplication
+
+ app = QApplication([])
+ w = EncodingConversionWidget()
+ w.show()
+ app.exec_()
diff --git a/pyminer/lib/io/exceptions.py b/pyminer/lib/io/exceptions.py
new file mode 100644
index 0000000000000000000000000000000000000000..59f34775bdd648e752740fff309d8bbc9f652f24
--- /dev/null
+++ b/pyminer/lib/io/exceptions.py
@@ -0,0 +1,49 @@
+# -*- coding:utf-8 -*-
+# @Time: 2021/1/27 10:22
+# @Author: Zhanyi Hou
+# @Email: 1295752786@qq.com
+# @File: exceptions.py
+
+from PySide2.QtCore import QObject, Signal, QCoreApplication
+
+
+class PyMinerException(BaseException):
+ def __init__(self, error: BaseException, solution: str, solution_command: str = ''):
+ self.error = error
+ self.solution = solution
+ self.solution_command = solution_command
+
+ def to_markdown(self):
+ return QCoreApplication.translate('PyMinerException',
+ """
+# {error}
+## Solutions:
+{solution}
+ """.format(error=self.error, solution=self.solution))
+
+
+class PMExceptions(QObject):
+ """
+ 单例!
+ """
+ signal_exception_occured = Signal(BaseException)
+
+ @classmethod
+ def __new__(cls, *args):
+ if not hasattr(cls, 'instance'):
+ instance = super().__new__(cls)
+ cls.instance = instance
+ return cls.instance
+
+ def __init__(self):
+ super(PMExceptions, self).__init__()
+
+ @staticmethod
+ def get_instance() -> 'PMExceptions':
+ return PMExceptions.instance
+
+ def emit_exception_occured_signal(self, error: BaseException, solution: str, solution_command: str):
+ self.signal_exception_occured.emit(PyMinerException(error, solution, solution_command))
+
+
+pyminer_exc_mgr = PMExceptions()
diff --git a/pyminer/lib/io/settings.py b/pyminer/lib/io/settings.py
new file mode 100644
index 0000000000000000000000000000000000000000..6055621639a97eceef2414e2309a2c8d8a81f06e
--- /dev/null
+++ b/pyminer/lib/io/settings.py
@@ -0,0 +1,175 @@
+"""
+TODO:DEPRECATED。这个文件将被废弃!
+settings.py负责设置文件的输入输出。
+Settings继承字典类,另外写了存储函数。
+还有其他设置的函数
+设置文件:
+~/.pyminer文件夹存储设置文件。
+~/.pyminer/pyminer_config存储全局界面的设置文件
+~/.pyminer/packages下存储插件的文件夹,插件的文件也放在下面。
+
+插件获取的方式:
+extension_lib.Program.get_plugin_data_path(plugin_name)
+比如:
+
+extension_lib.Program.get_plugin_data_path('code_editor')
+返回的路径就是~/.pyminer/packages/code_editor文件夹。
+
+其中的文件敬请插件开发者管理。建议不要向其中放太多数据,避免过多占用用户的磁盘空间。
+TODO:未来我们会增加一个插件读写设置的接口。可以将插件的文件放到里面。
+===============================
+方案1:
+settings = Extension.create_default_settings() # 创建默认设置。
+settings = Extension.read_settings('settings.json') # settings值为一个字典,{'width':100,'height':161}
+Extension.save_settings(settings,'settings.json') # 需要手动调用这个回调函数。
+===============================
+方案2:
+创建一个默认的属性Extension.settings,与插件目录下的extsettings.json保持关联。
+启动时,插件加载之前调用:
+ext = Extension()
+ext.create_default_settings() # 创建默认设置
+ext.update_settings() # 从设置文件中读取设置并且更新设置
+ext.save_settings() # pyminer 关闭时自动调用
+
+此时直接拿到settings就可以使用了。
+对于多进程插件,可以在启动的子进程中直接获取相关的设置路径。但一般的科学计算插件,是无需保存设置的。
+"""
+import logging
+import os
+import json
+import platform
+from typing import Dict
+
+from PySide2.QtWidgets import QApplication
+
+import qdarkstyle
+import utils
+
+logger = logging.getLogger(__name__)
+
+
+def get_pyminer_data_path() -> str:
+ path = os.path.join(os.path.expanduser('~'), '.pyminer')
+ if not os.path.exists(path):
+ os.makedirs(path)
+ return path
+
+
+def load_theme(style: str):
+ """
+ 设置主题。
+ :param style:
+ :return:
+ """
+ from utils import get_main_window
+ app = QApplication.instance()
+ mw = get_main_window()
+ style = style.lower()
+ if style == 'fusion':
+ mw.setStyleSheet('')
+ app.setStyleSheet('')
+ standard_ss = mw.get_stylesheet('standard')
+ fusion_ss = mw.get_stylesheet('Fusion')
+ app.setStyleSheet(standard_ss + '\n' + fusion_ss)
+ mw.setStyleSheet(standard_ss + '\n' + fusion_ss)
+ # app.setStyle('Fusion')
+
+ elif style == 'qdarkstyle':
+ app.setStyleSheet('')
+ mw.setStyleSheet('')
+ black_ss = mw.get_stylesheet('Qdarkstyle')
+ app.setStyleSheet(qdarkstyle.load_stylesheet()) # qt_api='pyqt5'))
+ app.setStyleSheet(app.styleSheet() + '\n' + black_ss)
+ mw.setStyleSheet(app.styleSheet() + '\n' + black_ss)
+ # app.setStyle('Windows')
+
+ elif style.lower() == 'windowsvista':
+ app.setStyleSheet('')
+ mw.setStyleSheet('')
+ app.setStyleSheet(mw.get_stylesheet('windowsvista'))
+ mw.setStyleSheet(mw.get_stylesheet('windowsvista'))
+ # app.setStyle("windowsvista")
+
+ elif style.lower() == 'windows':
+ app.setStyleSheet('')
+ mw.setStyleSheet('')
+ app.setStyleSheet(mw.get_stylesheet('Windows'))
+ mw.setStyleSheet(mw.get_stylesheet('Windows'))
+ # app.setStyle("Windows")
+
+
+# class Settings(dict):
+# """
+# 单例!
+# """
+#
+# @classmethod
+# def __new__(cls, *args):
+# if not hasattr(cls, 'instance'):
+# instance = super().__new__(cls)
+# cls.instance = instance
+# return cls.instance
+#
+# def __init__(self):
+# super(Settings, self).__init__()
+# self.check_pyminer_settings_dir()
+# self.update(self.load())
+#
+# def check_pyminer_settings_dir(self):
+# self.data_path = get_pyminer_data_path()
+# path = os.path.join(self.data_path, 'pyminer_config')
+# self.settings_path = path
+# if not os.path.exists(path):
+# os.mkdir(path)
+#
+# @staticmethod
+# def get_instance() -> 'Settings':
+# return Settings.instance
+#
+# def load(self) -> Dict[str, str]:
+# """
+# 加载设置项。
+# default_settings是默认设置项
+# :return:
+# """
+# with open(os.path.join(utils.get_root_dir(), 'configuration', 'default_settings.json'), 'r') as f:
+# default_settings = json.load(f)
+# if platform.system().lower() == 'windows':
+# default_settings['work_dir'] = os.path.expanduser('~')
+# else:
+# default_settings['work_dir'] = os.environ['HOME']
+# if not os.path.exists(default_settings['work_dir']):
+# os.mkdir(default_settings['work_dir'])
+#
+# try:
+# with open(os.path.join(self.settings_path, 'pyminer_settings.json'), 'r') as f:
+# settings = json.load(f)
+# except BaseException:
+# settings = {}
+#
+# pmsettings = default_settings
+# pmsettings.update(settings)
+# if not os.path.exists(pmsettings['work_dir']):
+# pmsettings['work_dir'] = os.path.expanduser('~')
+# return pmsettings
+#
+# def save(self):
+# """
+# 保存
+# :return:
+# """
+# import json
+# try:
+# config_file = os.path.join(self.settings_path, 'pyminer_settings.json')
+# with open(config_file, 'w') as f:
+# json.dump(self, f, indent=4)
+# except FileNotFoundError as e:
+# logging.warning(e)
+
+
+if __name__ == '__main__':
+ s1 = Settings()
+ s2 = Settings()
+ s3 = Settings.instance
+ print(s3)
+ print(id(s1), id(s2), s1 is s2, id(s3))
diff --git a/pyminer/lib/main_window/base.py b/pyminer/lib/main_window/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..b591423f3d05c5e183bf1295417349ae44ecaad7
--- /dev/null
+++ b/pyminer/lib/main_window/base.py
@@ -0,0 +1,731 @@
+import base64
+import json
+import logging
+import os
+import sys
+import time
+import webbrowser
+from multiprocessing import shared_memory
+from typing import List
+
+import qdarkstyle
+from PySide2.QtCore import QPoint, QRectF
+from PySide2.QtGui import QMouseEvent, QPainter, QLinearGradient, QCursor
+from PySide2.QtGui import QCloseEvent
+from PySide2.QtCore import Signal, Qt, QUrl, QPropertyAnimation
+from PySide2.QtGui import QCloseEvent
+from PySide2.QtGui import QMouseEvent
+from PySide2.QtWebEngineWidgets import *
+from PySide2.QtWidgets import QListWidgetItem, QWizard, QMessageBox
+from PySide2.QtWidgets import QWidget, QDesktopWidget, QFileDialog, QApplication, QDialog
+
+import utils
+from lib.extensions.extensionlib.extension_lib import extension_lib
+from lib.ui.ui_aboutme import Ui_Form as About_Ui_Form
+from lib.ui.ui_appstore import Ui_Form as appStore_Ui_Form
+from lib.ui.ui_first_form import Ui_Form as first_Ui_Form
+from lib.ui.ui_login import Ui_Form as login_Ui_Form
+from lib.ui.ui_logined import Ui_Form as logined_Ui_Form
+from lib.ui.ui_option import Ui_Form as Option_Ui_Form
+from lib.ui.ui_project_wizard import Ui_Wizard as Project_Ui_Form
+from widgets import PMGPanel
+from utils import get_main_window, http_client
+
+logger = logging.getLogger(__name__)
+
+
+class OptionForm(QDialog, Option_Ui_Form):
+ """
+ 打开"选项"窗口
+ """
+ signal_settings_changed = Signal()
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.setupUi(self)
+ self.center()
+ self.page_format.setEnabled(False)
+ self.page_appearance.setEnabled(False)
+
+ self.setup_ui()
+
+ # 通过combobox控件选择窗口风格
+ self.comboBox_theme.activated[str].connect(self.slot_theme_changed)
+
+ self.setting = dict()
+
+ self.listWidget.currentRowChanged.connect(self.option_change)
+ self.toolButton_workspace.clicked.connect(self.slot_change_workspace)
+ self.toolButton_output.clicked.connect(self.slot_change_output)
+ self.pushButton_cancel.clicked.connect(self.close)
+ self.pushButton_ok.clicked.connect(self.close)
+ self.pushButton_help.clicked.connect(self.get_help)
+
+ def setup_ui(self):
+ self.comboBox_9.setEnabled(False)
+ self.comboBox_8.setEnabled(False)
+ # self.checkbox_show_startpage.setEnabled(False)
+ # self.checkBox_minitray.setEnabled(False)
+
+ def add_settings_panel(self, text: str, settings_content: List):
+ settings_widget = PMGPanel(views=settings_content)
+ self.signal_settings_changed.connect(settings_widget.emit_settings_changed_signal)
+ self.stackedWidget.addWidget(settings_widget)
+ self.listWidget.addItem(QListWidgetItem(text))
+ return settings_widget
+
+ def add_page(self, text, page: QWidget):
+ self.stackedWidget.addWidget(page)
+ self.listWidget.addItem(QListWidgetItem(text))
+ return page
+
+ def closeEvent(self, a0: 'QCloseEvent') -> None:
+ super(OptionForm, self).closeEvent(a0)
+ self.refresh_settings()
+
+ def keyPressEvent(self, e):
+ """
+ 按键盘Escape退出当前窗口
+ @param e:
+ """
+ if e.key() == Qt.Key_Escape:
+ self.close()
+
+ def center(self):
+ qr = self.frameGeometry()
+ cp = QDesktopWidget().availableGeometry().center()
+ qr.moveCenter(cp)
+ self.move(qr.topLeft())
+
+ def option_change(self, i):
+ self.stackedWidget.setCurrentIndex(i)
+
+ def slot_theme_changed(self, style):
+ """
+ 在主题颜色改变时触发的回调
+ :param style:
+ :return:
+ """
+ from features.io.settings import load_theme
+ load_theme(style)
+ utils.write_settings_item_to_file("config.ini", "MAIN/THEME", self.comboBox_theme.currentText())
+ get_main_window().settings_changed_signal.emit()
+
+ def slot_change_workspace(self):
+ """
+ 改变工作路径时的回调
+ Returns:
+
+ """
+ work_dir = utils.get_settings_item_from_file("config.ini", "MAIN/THEME", self.comboBox_theme.currentText())
+ directory = QFileDialog.getExistingDirectory(self, "选择工作路径位置", directory=work_dir)
+ if not directory == '':
+ self.lineEdit_worksapce.setText(directory)
+
+ def slot_change_output(self):
+ directory = QFileDialog.getExistingDirectory(self, "选择输出文件夹位置", os.path.expanduser('~'))
+ self.lineEdit_output.setText(directory)
+
+ def load_settings(self):
+ """
+ 在show()之前调用这个方法
+ 从而每次重新显示的时候都可以刷新数据。
+ :return:
+ """
+ settings = utils.get_settings_from_file("config.ini")
+ logger.debug("PATH/WORKDIR", settings.value('MAIN/PATH_WORKDIR'))
+ if settings.value('MAIN/THEME') is not None:
+ for i in range(self.comboBox_theme.count()):
+ if self.comboBox_theme.itemText(i) == settings.value('MAIN/THEME'):
+ self.comboBox_theme.setCurrentIndex(i)
+ self.lineEdit_worksapce.setText(settings.value("MAIN/PATH_WORKDIR"))
+ self.lineEdit_output.setText(settings.value("MAIN/PATH_OUTPUT"))
+
+ check_update = utils.get_settings_item_from_file("config.ini", "MAIN/CHECK_UPDATE")
+ show_start_page = utils.get_settings_item_from_file("config.ini", "MAIN/SHOW_START_PAGE")
+ self.check_box_check_upd_on_startup.setChecked(check_update)
+ self.checkbox_show_startpage.setChecked(show_start_page)
+
+ def refresh_settings(self):
+ """
+ 窗口关闭时,调用此方法,刷新主界面设置项。
+ :return:
+ """
+ utils.write_settings_item_to_file("config.ini", "MAIN/THEME", self.comboBox_theme.currentText())
+ utils.write_settings_item_to_file("config.ini", "MAIN/PATH_WORKDIR", self.lineEdit_worksapce.text())
+ utils.write_settings_item_to_file("config.ini", "MAIN/PATH_OUTPUT", self.lineEdit_output.text())
+ utils.write_settings_item_to_file("config.ini", "MAIN/CHECK_UPDATE",
+ self.check_box_check_upd_on_startup.isChecked())
+ utils.write_settings_item_to_file("config.ini", "MAIN/SHOW_START_PAGE",
+ self.checkbox_show_startpage.isChecked())
+
+ get_main_window().on_settings_changed()
+ self.signal_settings_changed.emit()
+
+ def show(self):
+ """
+ 重写此方法,在显示之前重新加载一遍设置。
+ :return:
+ """
+ self.load_settings()
+ super(OptionForm, self).show()
+
+ def exec_(self):
+ """
+ 继承exec_方法。
+ :return:
+ """
+ self.load_settings()
+ super(OptionForm, self).exec_()
+
+ def get_help(self):
+ webbrowser.open('https://gitee.com/py2cn/pyminer/wikis/%E9%85%8D%E7%BD%AEPyMiner?sort_id=3263840')
+
+
+class AppstoreForm(QWidget, appStore_Ui_Form):
+ def __init__(self):
+ super(AppstoreForm, self).__init__()
+ self.setupUi(self)
+ self.center()
+
+ self.browser = QWebEngineView()
+ # 加载外部的web界面
+ self.browser.load(QUrl('https://chrome.zzzmh.cn/index#ext'))
+ self.horizontalLayout_2.addWidget(self.browser)
+
+ self.toolButton_help.clicked.connect(self.main_help_display)
+
+ def keyPressEvent(self, e):
+ """
+ 按键盘Escape退出当前窗口
+ @param e:
+ """
+ if e.key() == Qt.Key_Escape:
+ self.close()
+
+ def center(self):
+ qr = self.frameGeometry()
+ cp = QDesktopWidget().availableGeometry().center()
+ qr.moveCenter(cp)
+ self.move(qr.topLeft())
+
+ def main_help_display(self):
+ """
+ 打开帮助页面
+ """
+ try:
+ webbrowser.get('chrome').open_new_tab("http://www.pyminer.com")
+ except Exception as e:
+ webbrowser.open_new_tab("http://www.pyminer.com")
+
+
+class AboutForm(QWidget, About_Ui_Form):
+ """
+ 关于 弹出框
+
+ """
+
+ def __init__(self):
+ super(AboutForm, self).__init__()
+ self.setupUi(self)
+ self.center()
+ AUTHOR = utils.get_settings_item_from_file("config.ini", "INFO/AUTHOR", "default")
+ MAIL = utils.get_settings_item_from_file("config.ini", "INFO/MAIL", "default")
+ self.textedit_about.setMarkdown("""# PyMiner
+PyMiner 是一款基于Python的开源、跨平台数据分析环境。它以方便Python初学者为己任,在Python的知识理论和工作实践之间搭建桥梁,竭诚为初学者服务。
+- PyMiner开箱即用,大大减少配置解释器环境的繁琐性。不仅提供了编程运行的功能,还能够以交互式的形式进行常见的数据分析操作,减少代码编写和文档查阅的时间。
+- PyMiner通过加载各种插件实现不同的需求,开发者可以通过编写插件,将PyMiner扩展的更强大、更趁手,甚至创建一番自己的商用程序。
+- PyMiner提供面向新手的快速入门教程,教程正由开发团队编写中。
+- 我们诚挚希望与Python培训或教育机构/个人合作,让我们的产品帮助到更多学习Python的人。
+
+作者:{AUTHOR}
+
+邮箱:{MAIL}
+""".format(AUTHOR=AUTHOR, MAIL=MAIL))
+
+ self.main_about_display()
+
+ def keyPressEvent(self, e):
+ """
+ 按键盘Escape退出当前窗口
+ @param e:
+ """
+ if e.key() == Qt.Key_Escape:
+ self.close()
+
+ def center(self):
+ qr = self.frameGeometry()
+ cp = QDesktopWidget().availableGeometry().center()
+ qr.moveCenter(cp)
+ self.move(qr.topLeft())
+
+ def main_about_display(self):
+ """
+ 打开关于页面
+ """
+ import platform
+ python_info = 'Python版本: ' + platform.python_version() + ' ' + platform.python_compiler()
+ system_info = '系统信息: ' + platform.platform() + ' ' + platform.architecture()[0]
+ cpu_info = 'CPU信息: ' + platform.processor()
+ self.feedback.setPlainText(python_info + '\n' + system_info + '\n' + cpu_info)
+ self.label_version_show.setText(utils.get_settings_item_from_file("config.ini", "INFO/VERSION", "default"))
+
+
+class ProjectWizardForm(QWizard, Project_Ui_Form):
+ """
+ 新建项目引导窗口
+ """
+
+ def __init__(self, parent=None):
+ super(ProjectWizardForm, self).__init__(parent=None)
+ self.setupUi(self)
+ self.center()
+ self.default_setting()
+ self.init()
+
+ def init(self):
+ # 初始化项目路径
+ project_name = self.projectNameLineEdit.text()
+ workspace_dir = os.path.join(os.path.expanduser('~'), 'PyMiner_workspace', project_name)
+ file_dir = os.path.join(os.path.expanduser('~'), 'PyMiner_workspace', project_name, 'main.py')
+ self.projectDirectoryEditLine.setText(workspace_dir)
+ self.absoluteDirectoryEditLine.setText(file_dir)
+
+ # 浏览按钮触发事件
+ self.toolButton.clicked.connect(self.getProjectDirectory)
+ # 向导界面finish按钮按下后触发的事件
+ self.button(QWizard.FinishButton).clicked.connect(self.finishWizard)
+ # 项目名称框text发生改变时触发的事件
+ self.projectNameLineEdit.textChanged.connect(self.projectNameLineEditTextChange)
+ # 选择不同项目类型时下方展示不同的类型描述
+ self.file_list.itemClicked.connect(self.fileListItemClicked)
+
+ def getProjectDirectory(self):
+ """
+ 浏览按钮触发的事件,选择文件夹
+ :return:
+ """
+ absolute_directory = self.absoluteDirectoryEditLine.text()
+ project_directory = self.projectDirectoryEditLine.text()
+ directory_name = QFileDialog.getExistingDirectory(None, "请选择文件夹路径", "./").replace("/", "\\")
+ self.projectDirectoryEditLine.setText(directory_name)
+ project_name = self.projectNameLineEdit.text()
+ if len(project_name) != 0:
+ if len(directory_name) != 0:
+ self.absoluteDirectoryEditLine.setText(directory_name + "\\" + project_name + '\\main.py')
+ self.projectDirectoryEditLine.setText(directory_name + "\\" + project_name)
+ else:
+ self.absoluteDirectoryEditLine.setText(absolute_directory)
+ self.projectDirectoryEditLine.setText(project_directory)
+ else:
+ if len(directory_name) != 0:
+ self.absoluteDirectoryEditLine.setText(directory_name + "\\" + project_name + '\\main.py')
+ self.projectDirectoryEditLine.setText(directory_name + "\\" + project_name)
+ else:
+ self.absoluteDirectoryEditLine.setText(absolute_directory)
+ self.projectDirectoryEditLine.setText(project_directory)
+ project_directory = self.projectDirectoryEditLine.text()
+ if project_directory != "" and os.path.exists(project_directory):
+ # 警告:该项目已存在,完成向导后原来的项目将会被覆盖!!!
+ self.warningLabel.adjustSize()
+ self.warningLabel.setText("Warning: The project already exists, the original \nproject will be overwritten "
+ "after completing the \nwizard! ! !")
+ else:
+ self.warningLabel.setText("")
+
+ def finishWizard(self):
+ """
+ 完成项目创建向导后做的动作,文件夹不存在时创建路径并创建空的main.py文件,文件存在时只创建main.py,然后在主窗口中打开
+ :return:
+ """
+ import os
+ import pathlib
+ file_path = self.absoluteDirectoryEditLine.text()
+ project_path = self.projectDirectoryEditLine.text()
+ current_project_type = self.file_list.currentRow()
+ if os.path.exists(project_path):
+ if current_project_type != 3: # Python-Template-PySide2
+ pathlib.Path(file_path).touch() # 创建空文件
+ else:
+ from shutil import rmtree
+ rmtree(project_path)
+ else:
+ if current_project_type != 3: # Python-Template-PySide2
+ os.mkdir(project_path)
+ pathlib.Path(file_path).touch()
+ from shutil import copyfile
+ if current_project_type == 0: # Python-Empty # 创建空项目
+ template_dir = "features/project/template/Empty-Template.py"
+ if os.path.exists(template_dir):
+ copyfile(template_dir, file_path)
+ else: # 若模板文件不存在,默认新建空main.py
+ with open(file_path, "w") as f:
+ f.write("# --coding:utf-8--\n")
+ f.write("")
+ elif current_project_type == 1: # Python-Template-Basic # 创建base template项目
+ template_dir = "features/project/template/Basic-Template.py"
+ if os.path.exists(template_dir):
+ copyfile(template_dir, file_path)
+ else: # 若模板文件不存在,默认将以下内容写入main.py
+ with open(file_path, "w") as f:
+ f.write("# --coding:utf-8--\n")
+ f.write("if __name__ == '__main__':\n")
+ f.write(" # Create your codes here")
+ f.write(" pass")
+ elif current_project_type == 2: # Python-Template-Plot # 创建plot template项目
+ template_dir = "features/project/template/Plot-Template.py"
+ if os.path.exists(template_dir):
+ copyfile(template_dir, file_path)
+ else: # 若模板文件不存在,默认将以下内容写入main.py
+ with open(file_path, "w") as f:
+ f.write("# --coding:utf-8--\n")
+ f.write("\n")
+ f.write("import matplotlib.pyplot as plt\n")
+ f.write("import numpy as np\n")
+ f.write("\n")
+ f.write("\n")
+ f.write("def demoTemplate():\n")
+ f.write(" x = np.linspace(0, 5, 200)\n")
+ f.write(" y1 = x + 1\n")
+ f.write(" y2 = x - 1\n")
+ f.write(" plt.figure()\n")
+ f.write(" ax = plt.axes()\n")
+ f.write(" ax.spines['top'].set_visible(False)\n")
+ f.write(" ax.spines['right'].set_visible(False)\n")
+ f.write(" plt.grid(axis='both', linestyle='-.', c='b')\n")
+ f.write(" plt.plot(x, y1, 'c--')\n")
+ f.write(" plt.plot(x, y2, 'r-.')\n")
+ f.write(" plt.text(1, 0.5, 'text')\n")
+ f.write(" plt.legend(['y1', 'y2'])\n")
+ f.write(" plt.xlabel('xlabel')\n")
+ f.write(" plt.ylabel('ylabel')\n")
+ f.write(" plt.title('title')\n")
+ f.write(" plt.show()\n")
+ f.write("\n")
+ f.write("\n")
+ f.write("if __name__ == '__main__':\n")
+ f.write(" demoTemplate()\n")
+ elif current_project_type == 3: # Python-Template-PySide2 # 创建pyqt template项目
+ template_dir = "features/project/template/PySide2Template"
+ if os.path.exists(template_dir):
+ from shutil import copytree
+ copytree(template_dir, project_path)
+ else: # 若模板文件不存在,默认将以下内容写入main.py
+ QMessageBox.warning(self, "警告", "模板路径不存在,请确保模板在程序根目录下的features/project/template/PySide2Template",
+ QMessageBox.Ok)
+ extension_lib.Program.set_work_dir(project_path) # 在文件树区域打开新建项目,将当前工作路径切换为新建的项目
+
+ def projectNameLineEditTextChange(self):
+ """
+ 项目名称发生改变时同步改变绝对路径
+ Returns
+ -------
+
+ """
+ project_name = self.projectNameLineEdit.text()
+ absolute_directory = self.absoluteDirectoryEditLine.text()
+ # 将文件路径按照\分割成列表,然后把右边2个元素也就是main.py与项目名称pop()移出列表,最后再拼接成完整的路径
+ absolute_directory_list = absolute_directory.split("\\")
+ absolute_directory_list.pop() # 移除最右边的元素"main.py"
+ absolute_directory_list.pop() # 移除右边的项目名称元素
+ if absolute_directory != "":
+ # 将新项目名称和main.py与浏览按钮选择的路径进行拼接组合成新的绝对路径和项目路径
+ self.projectDirectoryEditLine.setText("\\".join(absolute_directory_list) + "\\" + project_name)
+ self.absoluteDirectoryEditLine.setText("\\".join(absolute_directory_list) + "\\" + project_name
+ + "\\main.py")
+ project_dir = self.projectDirectoryEditLine.text()
+ if os.path.exists(project_dir):
+ # 警告:该项目已存在,完成向导后原来的项目将会被覆盖!!!
+ self.warningLabel.setText("Warning: The project already exists, the original \nproject will be overwritten "
+ "after completing the \nwizard! ! !")
+ else:
+ self.warningLabel.setText("")
+
+ def fileListItemClicked(self):
+ current_project_type = self.file_list.currentRow()
+ if current_project_type == 0: # Python-Empty
+ self.plainTextEdit.setPlainText("Create a Python Project containing an Empty main.py.")
+ elif current_project_type == 1: # Python-Template-Basic
+ self.plainTextEdit.setPlainText("Create a Python Project containing a Base Template main.py.")
+ elif current_project_type == 2: # Python-Template-Plot
+ self.plainTextEdit.setPlainText("Create a Python Project containing a Plot Template main.py.")
+ elif current_project_type == 3: # Python-Template-PySide2
+ self.plainTextEdit.setPlainText("Create a Python Project containing a PySide2 Template main.py.")
+
+ def keyPressEvent(self, e):
+ """
+ 按键盘Escape退出当前窗口
+ @param e:
+ """
+ if e.key() == Qt.Key_Escape:
+ self.close()
+
+ def center(self):
+ qr = self.frameGeometry()
+ cp = QDesktopWidget().availableGeometry().center()
+ qr.moveCenter(cp)
+ self.move(qr.topLeft())
+
+ def default_setting(self):
+ item = self.file_list.item(1)
+ item.setSelected(True)
+
+
+class FirstForm(QDialog, first_Ui_Form):
+ """
+ 快速操作窗口
+ """
+
+ def __init__(self, parent=None):
+ super(FirstForm, self).__init__(parent)
+ self.setupUi(self)
+ self.center()
+ self.setWindowOpacity(0.95)
+ self.setWindowTitle(self.tr('Quick Start'))
+ # self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Popup) # 无边框、弹出式
+ self.animation = None
+ # self.setStyleSheet("border-radius:10px;border:none;")
+ # self.setAttribute(Qt.WA_TranslucentBackground)
+
+ # 绑定事件
+ self.btn_open_python.clicked.connect(self.open_script)
+ self.btn_manual.clicked.connect(self.open_manual)
+ self.btn_website.clicked.connect(self.open_website)
+ self.btn_source.clicked.connect(self.open_source)
+ self.btn_member.clicked.connect(self.open_member)
+ self.btn_donate.clicked.connect(self.open_donate)
+
+ self.btn_open_csv.clicked.connect(self.open_csv)
+ self.btn_open_excel.clicked.connect(self.open_excel)
+ self.btn_open_matlab.clicked.connect(self.open_matlab)
+ self.btn_open_folder.clicked.connect(self.open_folder)
+
+ def closeEvent(self, event):
+ if self.animation is None:
+ self.animation = QPropertyAnimation(self, b'windowOpacity', self.parent())
+ # self.animation.setPropertyName(b'windowOpacity')
+ self.animation.setDuration(200)
+ self.animation.setStartValue(self.windowOpacity())
+ self.animation.setEndValue(0)
+ self.animation.finished.connect(self.close)
+ self.animation.start()
+ event.ignore()
+
+ def mouseMoveEvent(self, e: QMouseEvent): # 重写移动事件
+ self._endPos = e.pos() - self._startPos
+ self.move(self.pos() + self._endPos)
+ super(FirstForm, self).mouseMoveEvent(e)
+
+ def mousePressEvent(self, e: QMouseEvent):
+ if e.button() == Qt.LeftButton:
+ self._isTracking = True
+ self._startPos = QPoint(e.x(), e.y())
+ super(FirstForm, self).mousePressEvent(e)
+
+ def mouseReleaseEvent(self, e: QMouseEvent):
+ super(FirstForm, self).mouseReleaseEvent(e)
+ if e.button() == Qt.LeftButton:
+ self._isTracking = False
+ self._startPos = None
+ self._endPos = None
+
+ def keyPressEvent(self, e):
+ """
+ 按键盘Escape退出当前窗口
+ @param e:
+ """
+ super(FirstForm, self).keyPressEvent(e)
+ if e.key() == Qt.Key_Escape:
+ self.close()
+
+ def center(self):
+ qr = self.frameGeometry()
+ cp = QDesktopWidget().availableGeometry().center()
+ qr.moveCenter(cp)
+ self.move(qr.topLeft())
+
+ def open_script(self):
+ user_home = os.path.expanduser('~')
+ file_name, filetype = QFileDialog.getOpenFileName(self, "选取文件", user_home, "Python Files (*.py);;All Files (*)")
+ self.hide()
+ extension_lib.get_interface('code_editor').open_script(file_name)
+ self.close()
+
+ def open_manual(self):
+ """
+ 打开快速入门
+ :return:
+ """
+ utils.open_url("https://gitee.com/py2cn/pyminer/wikis/%E5%85%A5%E9%97%A8%E6%95%99%E7%A8%8B?sort_id=3137860")
+
+ def open_website(self):
+ """
+ 打开快速入门
+ :return:
+ """
+ utils.open_url("http://www.pyminer.com")
+
+ def open_source(self):
+ """
+ 打开快速入门
+ :return:
+ """
+ utils.open_url("https://gitee.com/py2cn/pyminer")
+
+ def open_member(self):
+ """
+ 打开 ‘加入我们’ 页面
+ :return:
+ """
+ utils.open_url("https://gitee.com/py2cn/pyminer/wikis/%E8%81%94%E7%B3%BB%E6%88%91%E4%BB%AC?sort_id=2761039")
+
+ def open_donate(self):
+ """
+ 打开 ‘捐赠’ 页面
+ :return:
+ """
+ utils.open_url("https://gitee.com/py2cn/pyminer/wikis/%E6%8D%90%E8%B5%A0?sort_id=2925146")
+
+ def open_csv(self):
+ """
+ 调用主程序打开csv到工作区间
+ :return:
+ """
+ self.hide()
+ extension_lib.get_interface('dataio').show_import_file_dialog('csv', '')
+ self.close()
+
+ def open_excel(self):
+ """
+ 调用主程序打开excel到工作区间
+ :return:
+ """
+ self.hide()
+ extension_lib.get_interface('dataio').show_import_file_dialog('excel', '')
+ self.close()
+
+ def open_matlab(self):
+ """
+ 调用主程序打开matlab到工作区间
+ :return:
+ """
+ self.hide()
+ extension_lib.get_interface('dataio').show_import_file_dialog('matlab', '')
+ # get_main_window().process_file('matlab')
+ self.close()
+
+ def open_folder(self):
+ user_home = os.path.expanduser('~')
+ project_path = QFileDialog.getExistingDirectory(self, "选取文件夹", user_home)
+ self.hide()
+ extension_lib.Program.set_work_dir(project_path)
+ self.close()
+
+
+class LoginForm(QDialog, login_Ui_Form):
+ """
+ 登录窗口
+ """
+
+ def __init__(self, parent=None):
+ super(LoginForm, self).__init__(parent)
+ self.setupUi(self)
+ self.init()
+
+ def init(self):
+ self.loginButton.clicked.connect(self.login)
+ self.forgetPwdButton.clicked.connect(self.forgetPwd)
+ self.usernameLineEdit.textChanged.connect(self.usernameErrorChange)
+
+ def login(self):
+ username = self.usernameLineEdit.text()
+ password = self.passwordLineEdit.text()
+ flag = False
+ if username == "":
+ self.usernameError.setText("用户名不能为空")
+ flag = True
+ if password == "":
+ self.passwordError.setText("密码不能为空")
+ flag = True
+ if not flag:
+ data = {
+ "usr": username,
+ "password": base64.b64encode(password.encode())
+ }
+ url = http_client.API + http_client.LOGIN_URL
+ resp = http_client.client(url, "post", data)
+ resp_info = json.loads(resp.text)
+ if resp.status_code == 200:
+ if resp_info["code"] == 10000:
+ token = resp_info["token"]
+ token_len = len(token)
+ username_len = len(username.encode("utf-8"))
+ """
+ 将登录后django返回的token令牌保存在共享内存token中,共享内存token在启动pyminer主程序时附带启动了本地flask服务然后
+ 创建共享内存token,详情见pmlocalserver/server.py
+ """
+ shared_memo = shared_memory.SharedMemory(name="sharedMemory") # 通过name找到共享内存token
+ buff = shared_memo.buf
+ # token长度随着用户名长度的增加而增加,用户名最长为21个汉字加一个字符
+ # buff[:3]存放token长度,目前最长的用户名21汉字加一字符生成的token长度为343
+ # buff[3:5]存放用户名长度,用户名字段最长为64字节,最多21个汉字(字符集utf8)
+ # buff[5:token_len+5]位存放token
+ # buff[token_len+5:实际用户名长度(用utf8转换为bytes后的长度)]存放用户名
+ buff[:3] = str(token_len).encode("utf-8") # 存放token长度
+ buff[3:5] = str(username_len).encode("utf-8") # 将用户名长度存放共享内存
+ buff[5:token_len+5] = token.encode("utf-8") # 将token存放进共享内存中,工作空间重启后也能获取到
+ buff[token_len+5:token_len+5+username_len] = username.encode("utf-8") # 存放用户名
+ self.usernameError.setText("登录成功")
+ time.sleep(0.5)
+ self.close()
+ else:
+ self.usernameError.setText(resp_info["msg"][0])
+
+ def forgetPwd(self):
+ utils.open_url("http://pyminer.com/forgetpassword")
+
+ def usernameErrorChange(self):
+ self.usernameError.setText("")
+ self.passwordError.setText("")
+ username = self.usernameLineEdit.text()
+ if len(username.encode("utf-8")) > 64:
+ self.usernameError.setText("最多只能输入21个汉字")
+ self.loginButton.setEnabled(False)
+ self.loginButton.setCursor(QCursor(Qt.ForbiddenCursor))
+ else:
+ self.usernameError.setText("")
+ self.loginButton.setEnabled(True)
+ self.loginButton.setCursor(QCursor(Qt.PointingHandCursor))
+
+
+class LoginedForm(QDialog, logined_Ui_Form):
+
+ def __init__(self, parent=None):
+ super(LoginedForm, self).__init__(parent)
+ self.setupUi(self)
+ self.init()
+ shared_memo = shared_memory.SharedMemory(name="sharedMemory") # 通过name找到共享内存token
+ buff = shared_memo.buf
+ token_len = int(bytes(buff[:3]).decode())
+ username_len = int(bytes(buff[3:5]).decode())
+ username = bytes(buff[token_len+5:token_len+5+username_len]).decode("utf-8")
+ self.usernameLabel.setText(username)
+
+ def init(self):
+ self.loginOutButton.clicked.connect(self.logout)
+
+ def logout(self):
+ shared_memo = shared_memory.SharedMemory(name="sharedMemory") # 通过name找到共享内存token
+ buff = shared_memo.buf
+ for i in range(0, len(buff)):
+ buff[i:i + 1] = "\x00".encode()
+ time.sleep(0.5)
+ self.close()
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ # form = FirstForm()
+ form = LoginedForm()
+ form.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/lib/openprocess.py b/pyminer/lib/openprocess.py
new file mode 100644
index 0000000000000000000000000000000000000000..1d4cc8dfda70ce5337c4df17265b947e4f599f56
--- /dev/null
+++ b/pyminer/lib/openprocess.py
@@ -0,0 +1,108 @@
+import platform
+import queue
+import subprocess
+import sys
+import threading
+import time
+import chardet
+from typing import List
+
+import packages.code_editor.utils.utils
+
+
+class PMProcess():
+ def __init__(self, args: List[str]):
+ self.terminate = False
+ self.q = queue.Queue()
+ self.on_command_received = lambda cmd: print(cmd)
+ self.on_error_received = lambda error: print(error)
+ self.args = args
+ self.process = subprocess.Popen(self.args,
+ stdin=subprocess.PIPE,
+ shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ self.to = threading.Thread(
+ target=self.enqueue_stream, args=(
+ self.process.stdout, self.q, 1))
+ self.te = threading.Thread(
+ target=self.enqueue_stream_err, args=(
+ self.process.stderr, self.q, 2))
+ self.tp = threading.Thread(target=self.consoleLoop)
+ self.to.setDaemon(True)
+ self.te.setDaemon(True)
+ self.tp.setDaemon(True)
+ self.te.start()
+ self.to.start()
+ self.tp.start()
+
+ def enqueue_stream_err(self, stream, queue, type):
+ """
+ stdout写入到队列q中。
+ Args:
+ stream:
+ queue:
+ type:
+
+ Returns:
+
+ """
+ for line in iter(stream.readline, b''):
+ if self.terminate:
+ break
+ if platform.system().lower() == 'linux':
+ encoding = 'utf-8'
+ else:
+ encoding = chardet.detect(line)['encoding']
+ queue.put(str(type) + packages.code_editor.utils.utils.decode(encoding))
+ print(line)
+ stream.close()
+
+ def enqueue_stream(self, stream, queue, type): # 将stderr或者stdout写入到队列q中。
+ """
+ stdout写入到队列q中。
+ Args:
+ stream:
+ queue:
+ type:
+
+ Returns:
+
+ """
+ for line in iter(lambda: stream.read(1), b''):
+ if self.terminate:
+ break
+ if platform.system().lower() == 'linux':
+ encoding = 'utf-8'
+ else:
+ encoding = chardet.detect(line)['encoding']
+ queue.put(str(type) + packages.code_editor.utils.utils.decode(encoding))
+ stream.close()
+
+ def consoleLoop(self): # 封装后的内容。
+ return
+ idleLoops = 0
+ while True:
+ if not self.q.empty():
+ line = self.q.get()
+ if line[0] == '1':
+ self.on_command_received(line[1:])
+ else:
+ self.on_error_received(line[1:])
+ sys.stdout.flush()
+ else:
+ time.sleep(0.01)
+ if idleLoops >= 5:
+ idleLoops = 0
+ # print('write!!')
+ self.process.stdin.write(
+ 'messsage\n'.encode('ascii')) # 模拟输入
+ self.process.stdin.flush()
+ continue
+ idleLoops += 1
+
+
+if __name__ == '__main__':
+ pmp = PMProcess(['python', '-u',
+ 'test_open_app.py'])
+ while (1):
+ time.sleep(2)
+ pass
diff --git a/pyminer/lib/pluginsmanager/__init__.py b/pyminer/lib/pluginsmanager/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/lib/pluginsmanager/pluginsmanager.py b/pyminer/lib/pluginsmanager/pluginsmanager.py
new file mode 100644
index 0000000000000000000000000000000000000000..9c4a3865292bcebbca4a228beeb1beabb8edbdab
--- /dev/null
+++ b/pyminer/lib/pluginsmanager/pluginsmanager.py
@@ -0,0 +1,15 @@
+import subprocess
+import sys
+from PySide2.QtWidgets import QFrame, QTableWidgetItem, QTableWidget, QMessageBox, QDialog, QDesktopWidget, QHeaderView, \
+ QProgressDialog
+from PySide2.QtGui import QCloseEvent
+from PySide2.QtCore import Signal, Qt, QUrl, QPropertyAnimation
+from lib.ui.pm_marketplace.main import Ui_Form as marketplace_Ui_Form
+
+from widgets import PMGOneShotThreadRunner
+
+
+class MarketplaceForm(QDialog, marketplace_Ui_Form):
+ def __init__(self, parent=None):
+ super(MarketplaceForm, self).__init__(parent)
+ self.setupUi(self)
diff --git a/pyminer/lib/project/template/Basic-Template.py b/pyminer/lib/project/template/Basic-Template.py
new file mode 100644
index 0000000000000000000000000000000000000000..6788f5a14368d20291240c5fc7836cb2276b8c8f
--- /dev/null
+++ b/pyminer/lib/project/template/Basic-Template.py
@@ -0,0 +1,14 @@
+# --coding:utf-8--
+
+"""
+Please make sure the content of the file is complete.
+If you customize the template, please make sure it can be executed correctly
+by the interpreter after modification.
+Do not delete the file and the directory where the file is located.
+请确保文件内容完整。
+若自定义模板,请在修改后确保能够被解释器正确执行。
+【请勿删除】该文件以及文件所在目录。
+"""
+if __name__ == '__main__':
+ # Create your codes here
+ pass
diff --git a/pyminer/lib/project/template/Empty-Template.py b/pyminer/lib/project/template/Empty-Template.py
new file mode 100644
index 0000000000000000000000000000000000000000..365f9f7f9072a1edc2f54d58303ea732ce124be7
--- /dev/null
+++ b/pyminer/lib/project/template/Empty-Template.py
@@ -0,0 +1,10 @@
+# --coding:utf-8--
+"""
+Please make sure the content of the file is complete.
+If you customize the template, please make sure it can be executed correctly
+by the interpreter after modification.
+Do not delete the file and the directory where the file is located.
+请确保文件内容完整。
+若自定义模板,请在修改后确保能够被解释器正确执行。
+【请勿删除】该文件以及文件所在目录
+"""
\ No newline at end of file
diff --git a/pyminer/lib/project/template/Plot-Template.py b/pyminer/lib/project/template/Plot-Template.py
new file mode 100644
index 0000000000000000000000000000000000000000..000c5ab169cd4daba858ea52e011046d54880bf2
--- /dev/null
+++ b/pyminer/lib/project/template/Plot-Template.py
@@ -0,0 +1,36 @@
+# --coding:utf-8--
+
+"""
+Please make sure the content of the file is complete.
+If you customize the template, please make sure it can be executed correctly
+by the interpreter after modification.
+Do not delete the file and the directory where the file is located.
+请确保文件内容完整。
+若自定义模板,请在修改后确保能够被解释器正确执行。
+【请勿删除】该文件以及文件所在目录
+"""
+import matplotlib.pyplot as plt
+import numpy as np
+
+
+def demoTemplate():
+ x = np.linspace(0, 5, 200)
+ y1 = x + 1
+ y2 = x - 1
+ plt.figure()
+ ax = plt.axes()
+ ax.spines['top'].set_visible(False)
+ ax.spines['right'].set_visible(False)
+ plt.grid(axis="both", linestyle='-.', c='b')
+ plt.plot(x, y1, 'c--')
+ plt.plot(x, y2, 'r-.')
+ plt.text(1, 0.5, "text")
+ plt.legend(["y1", "y2"])
+ plt.xlabel("xlabel")
+ plt.ylabel("ylabel")
+ plt.title("title")
+ plt.show()
+
+
+if __name__ == '__main__':
+ demoTemplate()
diff --git a/pyminer/lib/project/template/PyQt-Template.py b/pyminer/lib/project/template/PyQt-Template.py
new file mode 100644
index 0000000000000000000000000000000000000000..e920801d5bd7eb0671ca452af912570e5a3756be
--- /dev/null
+++ b/pyminer/lib/project/template/PyQt-Template.py
@@ -0,0 +1,16 @@
+# --coding:utf-8--
+import sys
+from PySide2.QtWidgets import QApplication, QDialog
+
+
+class CallPyQtTemplate(object):
+ def __init__(self):
+ super(CallPyQtTemplate, self).__init__()
+ self.setupUi(self)
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ form = CallPyQtTemplate()
+ form.show()
+ sys.exit(app.exec())
diff --git a/pyminer/lib/project/template/PySide2Template/PySide2_Template.py b/pyminer/lib/project/template/PySide2Template/PySide2_Template.py
new file mode 100644
index 0000000000000000000000000000000000000000..7b9215ba81393a15168c1a1d728e3bf1b07916e6
--- /dev/null
+++ b/pyminer/lib/project/template/PySide2Template/PySide2_Template.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'PySide2_Template.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import *
+from PySide2.QtGui import *
+from PySide2.QtWidgets import *
+
+
+class Ui_PySide2Template(object):
+ def setupUi(self, PySide2Template):
+ if not PySide2Template.objectName():
+ PySide2Template.setObjectName(u"PySide2Template")
+ PySide2Template.resize(402, 307)
+ icon = QIcon()
+ icon.addFile(u"../../ui/source/icons/logo.ico", QSize(), QIcon.Normal, QIcon.Off)
+ PySide2Template.setWindowIcon(icon)
+ self.verticalLayoutWidget = QWidget(PySide2Template)
+ self.verticalLayoutWidget.setObjectName(u"verticalLayoutWidget")
+ self.verticalLayoutWidget.setGeometry(QRect(9, 19, 381, 281))
+ self.verticalLayout = QVBoxLayout(self.verticalLayoutWidget)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.verticalLayout.setContentsMargins(0, 0, 0, 0)
+ self.textBrowser = QTextBrowser(self.verticalLayoutWidget)
+ self.textBrowser.setObjectName(u"textBrowser")
+
+ self.verticalLayout.addWidget(self.textBrowser)
+
+
+ self.retranslateUi(PySide2Template)
+
+ QMetaObject.connectSlotsByName(PySide2Template)
+ # setupUi
+
+ def retranslateUi(self, PySide2Template):
+ PySide2Template.setWindowTitle(QCoreApplication.translate("PySide2Template", u"PySide2Template", None))
+ self.textBrowser.setHtml(QCoreApplication.translate("PySide2Template", u"\n"
+"\n"
+"This is a PySide2 Template.
", None))
+ # retranslateUi
+
diff --git a/pyminer/lib/project/template/PySide2Template/PySide2_Template.ui b/pyminer/lib/project/template/PySide2Template/PySide2_Template.ui
new file mode 100644
index 0000000000000000000000000000000000000000..40ee0698acdade5e1ef9fb3c76b26505fc646e63
--- /dev/null
+++ b/pyminer/lib/project/template/PySide2Template/PySide2_Template.ui
@@ -0,0 +1,46 @@
+
+
+ PySide2Template
+
+
+
+ 0
+ 0
+ 402
+ 307
+
+
+
+ PySide2Template
+
+
+
+ ../../ui/source/icons/logo.ico../../ui/source/icons/logo.ico
+
+
+
+
+ 9
+ 19
+ 381
+ 281
+
+
+
+ -
+
+
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
+<html><head><meta name="qrichtext" content="1" /><style type="text/css">
+p, li { white-space: pre-wrap; }
+</style></head><body style=" font-family:'SimSun'; font-size:9pt; font-weight:400; font-style:normal;">
+<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:14pt; font-weight:600; color:#ff0000;">This is a PySide2 Template.</span></p></body></html>
+
+
+
+
+
+
+
+
+
diff --git a/pyminer/lib/project/template/PySide2Template/__init__.py b/pyminer/lib/project/template/PySide2Template/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/lib/project/template/PySide2Template/main.py b/pyminer/lib/project/template/PySide2Template/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..6c1c6520d4f5299dc3e169b42c94c6ee56edd380
--- /dev/null
+++ b/pyminer/lib/project/template/PySide2Template/main.py
@@ -0,0 +1,26 @@
+# --coding:utf-8--
+"""
+Please make sure the content of the file is complete.
+If you customize the template, please make sure it can be executed correctly
+by the interpreter after modification.
+Do not delete the file and the directory where the file is located.
+请确保文件内容完整。
+若自定义模板,请在修改后确保能够被解释器正确执行。
+【请勿删除】该文件以及文件所在目录。
+"""
+import sys
+from PySide2.QtWidgets import QApplication, QDialog
+from PySide2_Template import Ui_PySide2Template
+
+
+class CallPySideTemplate(QDialog, Ui_PySide2Template):
+ def __init__(self):
+ super(CallPySideTemplate, self).__init__()
+ self.setupUi(self)
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ form = CallPySideTemplate()
+ form.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/lib/project/template/__init__.py b/pyminer/lib/project/template/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/lib/settings.py b/pyminer/lib/settings.py
new file mode 100644
index 0000000000000000000000000000000000000000..edacc0dee8e2b926b0764862ef44250e625fd2e6
--- /dev/null
+++ b/pyminer/lib/settings.py
@@ -0,0 +1,35 @@
+import os
+import json
+
+class Setting:
+ """用于读写pyminer系统信息"""
+
+ def __init__(self):
+ self.setting_path = os.path.join(os.path.dirname(__file__), 'settings.json')
+ assert os.path.exists(self.setting_path)
+ with open(self.setting_path,'r',encoding='utf-8') as f:
+ self.system_info=json.load(f)
+
+ def get_system_info(self):
+ return self.system_info
+
+ def get_system_version(self):
+ """获取系统版本"""
+ return self.system_info['core']['version']
+
+ def set_system_version(self,version:str):
+ """设置系统版本"""
+ self.system_info['core']['version']=version
+
+ def save(self):
+ with open(self.setting_path, 'w',encoding='utf-8') as f:
+ json.dump(self.system_info, f,ensure_ascii=False,indent=4, separators=(',', ': ')) # 确保中文能正确显示,且不是只在一行
+
+
+if __name__ == '__main__':
+ setting=Setting()
+ print(setting.get_system_version())
+ setting.set_system_version('1.0.3')
+ setting.save()
+
+
diff --git a/pyminer/lib/ui/__init__.py b/pyminer/lib/ui/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/lib/ui/common/__init__.py b/pyminer/lib/ui/common/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/lib/ui/common/debug_process_with_pyqt.py b/pyminer/lib/ui/common/debug_process_with_pyqt.py
new file mode 100644
index 0000000000000000000000000000000000000000..4b36ff6303b08c616ebb865d149dd1606d8de556
--- /dev/null
+++ b/pyminer/lib/ui/common/debug_process_with_pyqt.py
@@ -0,0 +1,324 @@
+"""
+作者:侯展意
+有关QThread为什么行,为什么不行
+我也不知道啊...
+"""
+import os
+import platform
+import re
+import time
+import sys
+import logging
+from PySide2.QtCore import QThread, QObject, Signal, QTimer, Qt
+from PySide2.QtGui import QCloseEvent
+from PySide2.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QPushButton, QTextBrowser, QCheckBox, QTextEdit
+from typing import TYPE_CHECKING, List
+from widgets import PMGJsonTree
+
+logger = logging.getLogger(__name__)
+if TYPE_CHECKING:
+ from lib.openprocess import PMProcess
+ from packages.code_editor.widgets.tab_widget import PMCodeEditTabWidget
+else:
+ from lib.openprocess import PMProcess
+
+
+class DebugProcess(PMProcess):
+ """
+ TODO:Ugly structure and it must be refactored soon!!!!!!!!!
+ """
+
+ def enqueue_stream(self, stream, queue, type):
+ self.enqueue_stream_err(stream, queue, type)
+
+
+class ProcessMonitorThread(QObject):
+ on_err = Signal(str)
+ on_out = Signal(str)
+ signal_process_started = Signal()
+ on_finished = Signal()
+
+ def __init__(self):
+ super().__init__()
+ self.process_terminated = False
+ self.args = None
+ self.process: 'DebugProcess' = None
+
+ def stop(self):
+ self.process.terminate = True
+ self.process_terminated = True
+
+ def run(self):
+ self.process = DebugProcess(self.args)
+ self.signal_process_started.emit()
+ self.q = self.process.q
+ idleLoops = 0
+ while True:
+ if self.process_terminated == True:
+ return
+ if not self.q.empty():
+ line = self.q.get()
+ if line[0] == '1':
+ self.on_out.emit(line[1:])
+ else:
+ self.on_err.emit(line[1:])
+ sys.stdout.flush()
+ else:
+ time.sleep(0.01)
+ if idleLoops >= 5:
+ idleLoops = 0
+ if self.process.process.poll() is None:
+ pass
+ else:
+ self.on_finished.emit()
+ return
+ continue
+ idleLoops += 1
+
+
+class ProcessConsole(QTextEdit):
+ signal_stop_qthread = Signal()
+ signal_process_stopped = Signal()
+ signal_process_started = Signal()
+ signal_goto_file = Signal(str, int)
+
+ def __init__(self, args: list = None):
+ super().__init__()
+ self._is_running = False
+ self.auto_scroll = True
+ self.args = args #
+ self.setContentsMargins(20, 20, 0, 0)
+ self.monitor_thread: 'ProcessMonitorThread' = None
+ self.out_thread: 'QThread' = None
+
+ def is_running(self):
+ if self.monitor_thread is not None:
+ if self.monitor_thread.process.process.poll() is None:
+ return True
+ return False
+
+ def start_process(self):
+ if not self.is_running():
+ self.out_thread = QThread(self)
+ self.monitor_thread = ProcessMonitorThread()
+ self.monitor_thread.args = self.args
+ self.monitor_thread.moveToThread(self.out_thread)
+
+ self.out_thread.started.connect(self.monitor_thread.run)
+ self.out_thread.start()
+
+ self.monitor_thread.on_out.connect(self.on_stdout)
+ self.monitor_thread.on_err.connect(self.on_stderr)
+
+ self.signal_stop_qthread.connect(self.monitor_thread.stop)
+
+ self.out_thread.finished.connect(self.out_thread.deleteLater)
+ self.out_thread.finished.connect(self.monitor_thread.deleteLater)
+
+ self.monitor_thread.on_finished.connect(self.terminate_process)
+ self.monitor_thread.signal_process_started.connect(lambda: self.signal_process_started.emit())
+
+ def on_stdout(self, text):
+ cmd = text.strip()
+ file_paths = re.findall(r'>(.+?)\(', cmd)
+ if len(file_paths) > 0:
+ path = file_paths[0].strip()
+ splitted = cmd.split(path)
+ if len(splitted) == 2:
+ if os.path.exists(path):
+ remaining_words = splitted[1].strip()
+ current_row = re.findall(r'\((.+?)\)', remaining_words)
+ if len(current_row) >= 1:
+ # self.insertHtml('' + path + ';' + current_row[0] + '
')
+ print(file_paths[0], current_row, remaining_words)
+ self.signal_goto_file.emit(path, int(current_row[0]))
+ self.insertHtml('' + text + '
')
+ if self.auto_scroll:
+ self.ensureCursorVisible()
+
+ def on_stderr(self, text):
+ self.insertHtml('' + text + '
')
+ if self.auto_scroll:
+ self.ensureCursorVisible()
+
+ def terminate_process(self):
+ if self.monitor_thread is not None:
+ self.monitor_thread.process_terminated = True
+ self.monitor_thread.process.process.terminate()
+ if self.out_thread.isRunning():
+ self.signal_stop_qthread.emit()
+ self.out_thread.quit()
+ self.out_thread.wait(500)
+ self.monitor_thread = None
+ self.out_thread = None
+ self.signal_process_stopped.emit()
+
+ def keyPressEvent(self, e: 'QKeyEvent'):
+ if e.key() == Qt.Key_Backspace or e.key() == Qt.Key_Delete:
+ return
+ print(e.key(), e.text())
+ if e.key() == Qt.Key_Return:
+ text = '\n'
+ else:
+ text = e.text()
+ print(text,e.text())
+ if text != '' and self.monitor_thread is not None:
+ try:
+ print('sent:', text)
+ self.monitor_thread.process.process.stdin.write(text.encode('utf8'))
+ self.monitor_thread.process.process.stdin.flush()
+ except:
+ import traceback
+ traceback.print_exc()
+ super(ProcessConsole, self).keyPressEvent(e)
+
+
+class PMGDebugConsoleWidget(QWidget):
+ def __init__(self, args: list = None, editor_tab_widget: 'PMCodeEditTabWidget' = None):
+ super().__init__()
+ self.extension_lib = None
+
+ self.editor_tab_widget: 'PMCodeEditTabWidget' = editor_tab_widget
+ self.hbox_layout = QHBoxLayout()
+ self.tool_widget = QWidget()
+ self.input_queue: List[str] = []
+ self.tool_widget.setLayout(QVBoxLayout())
+ self.process_console = ProcessConsole(args=args)
+ self.process_console.signal_process_stopped.connect(self.on_terminated)
+
+ self.button_to_start = QPushButton('start')
+ self.tool_widget.layout().addWidget(self.button_to_start)
+ self.button_to_start.clicked.connect(self.start_process)
+
+ self.button_to_terminate = QPushButton('termi')
+ self.tool_widget.layout().addWidget(self.button_to_terminate)
+ self.button_to_terminate.clicked.connect(self.terminate_process)
+
+ self.button_to_clear = QPushButton('step')
+ self.tool_widget.layout().addWidget(self.button_to_clear)
+ self.button_to_clear.clicked.connect(lambda: self.input('s'))
+
+ self.button_to_clear = QPushButton('continue')
+ self.tool_widget.layout().addWidget(self.button_to_clear)
+ self.button_to_clear.clicked.connect(lambda: self.input('tobreak'))
+
+ self.button_to_clear = QPushButton('clear')
+ self.tool_widget.layout().addWidget(self.button_to_clear)
+ self.button_to_clear.clicked.connect(lambda: self.process_console.clear())
+
+ self.autoscroll_checker = QCheckBox()
+ self.autoscroll_checker.setToolTip('autoscroll')
+ self.tool_widget.layout().addWidget(self.autoscroll_checker)
+ self.autoscroll_checker.stateChanged.connect(self.set_autoscroll)
+ self.autoscroll_checker.setChecked(True)
+ self.set_autoscroll()
+
+ self.hbox_layout.addWidget(self.tool_widget)
+
+ self.hbox_layout.addWidget(self.process_console)
+ self.var_viewer = PMGJsonTree()
+ self.hbox_layout.addWidget(self.var_viewer)
+ self.setLayout(self.hbox_layout)
+ self.process_console.signal_process_started.connect(self.clear_input_queue)
+
+ def set_extension_lib(self, extension_lib):
+ self.extension_lib = extension_lib
+ self.extension_lib.Data.add_data_changed_callback(self.on_data_changed)
+
+ def on_data_changed(self, data_name: str, var: object, provider: str):
+ if data_name == 'debug_vars':
+ self.var_viewer.set_data_dic(var)
+
+ def input(self, message):
+ """
+ 输入命令。
+ 若在windows的终端运行则需要用gbk编码;若在pycharm中运行,则无需gbk编码。
+ 因此注意,下面多了个判断过程!
+ :param message:
+ :return:
+ """
+ line_ending = '\n'
+ # if platform.system().lower() == 'windows':
+ # line_ending = '\r\n'
+ if not message.endswith(line_ending):
+ message += line_ending
+ th = self.process_console.monitor_thread
+ if th is not None:
+ if th.process is not None:
+ process = th.process.process
+ encoded = b''
+ if platform.system().lower() == 'windows' and sys.stdout.isatty(): # 如果在windows的终端运行
+ encoded = message.encode('gbk')
+ else:
+ encoded = message.encode('utf-8')
+ process.stdin.write(encoded) # 模拟输入
+ process.stdin.flush()
+ return
+
+ self.input_queue.append(message)
+
+ def clear_input_queue(self):
+ """
+ 进程启动之后,调用这个队列处理器,逐条运行未能运行的命令。
+ :return:
+ """
+ for m in self.input_queue:
+ self.input(m)
+
+ def set_autoscroll(self):
+ print(self.autoscroll_checker.isChecked())
+ self.process_console.auto_scroll = self.autoscroll_checker.isChecked()
+
+ def on_terminated(self):
+ self.button_to_start.setEnabled(True)
+ self.button_to_terminate.setEnabled(False)
+
+ def on_started(self):
+ self.button_to_start.setEnabled(False)
+ self.button_to_terminate.setEnabled(True)
+
+ def start_process(self):
+ bp_input = self.editor_tab_widget.get_all_breakpoints('python')
+ bp_input = bp_input.strip()
+ self.input(bp_input)
+ self.process_console.start_process()
+ self.on_started()
+ s = r"""
+!import sys;sys.path.append(r'%s')
+alias pi !import pmtoolbox,os;pmtoolbox.debug.pmdebug.insight(locals());
+alias tobreak c;;pi
+alias ps pi self
+""" % r'E:\Python\pyminer_bin\PyMiner\bin'
+ self.input(s)
+
+ def terminate_process(self):
+ for index in range(self.editor_tab_widget.count()):
+ self.editor_tab_widget.widget(index).remove_debug_indicator()
+ self.process_console.terminate_process()
+ self.on_terminated()
+
+ def is_process_running(self) -> bool:
+ return self.process_console.is_running()
+
+ def close(self) -> bool:
+ self.terminate_process()
+ self.extension_lib.Data.remove_data_changed_callback(self.on_data_changed)
+ return super().close()
+
+ def closeEvent(self, a0: QCloseEvent) -> None:
+ self.terminate_process()
+ super(PMGDebugConsoleWidget, self).closeEvent(a0)
+
+# if __name__ == '__main__':
+# import cgitb
+#
+# cgitb.enable(format='text')
+# from PyQt5.QtWidgets import QApplication
+#
+# app = QApplication(sys.argv)
+#
+# w = PMGDebugConsoleWidget(['python', '-u', '-m', 'pdb',
+# r'E:\Python\pyminer_bin\PyMiner\bin\pmtoolbox\debug\test.py'])
+# w.show()
+# w.start_process()
+# sys.exit(app.exec_())
diff --git a/pyminer/lib/ui/common/open_process_with_pyqt.py b/pyminer/lib/ui/common/open_process_with_pyqt.py
new file mode 100644
index 0000000000000000000000000000000000000000..31f539820ee954875afe300d3ae4bf96ca664ee8
--- /dev/null
+++ b/pyminer/lib/ui/common/open_process_with_pyqt.py
@@ -0,0 +1,249 @@
+"""
+作者:侯展意
+有关QThread为什么行,为什么不行
+我也不知道啊...
+"""
+import time
+import sys
+import logging
+
+from PySide2.QtCore import QThread, QObject, Signal, Qt
+from PySide2.QtGui import QCloseEvent, QKeyEvent
+from PySide2.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QPushButton, QTextBrowser, QCheckBox, QTextEdit
+from typing import TYPE_CHECKING
+
+logger = logging.getLogger(__name__)
+if TYPE_CHECKING:
+ from features.openprocess import PMProcess
+else:
+ from features.openprocess import PMProcess
+
+
+class ProcessMonitorThread(QObject):
+ on_err = Signal(str)
+ on_out = Signal(str)
+ on_finished = Signal()
+
+ def __init__(self):
+ super().__init__()
+ self.process_terminated = False
+ self.args = None
+
+ def stop(self):
+ self.process.terminate = True
+ self.process_terminated = True
+
+ def run(self):
+ self.process = PMProcess(self.args)
+ self.q = self.process.q
+ idleLoops = 0
+ while True:
+ if self.process_terminated == True:
+ return
+ if not self.q.empty():
+ line = self.q.get()
+ if line[0] == '1':
+ self.on_out.emit(line[1:])
+ else:
+ self.on_err.emit(line[1:])
+ sys.stdout.flush()
+ else:
+ time.sleep(0.01)
+ if idleLoops >= 5:
+ idleLoops = 0
+ if self.process.process.poll() is None:
+ pass
+ # try:
+ # self.process.process.stdin.write(
+ # 'messsage\n'.encode('ascii')) # 模拟输入
+ # self.process.process.stdin.flush()
+ # except OSError:
+ # logger.info('Process with args \'%s\' terminates(or is terminated).' % repr(self.args))
+ # self.on_finished.emit()
+ # return
+ # except:
+ # import traceback
+ # traceback.print_exc()
+ # self.on_finished.emit()
+ # return
+ else:
+ self.on_finished.emit()
+ return
+ continue
+ idleLoops += 1
+
+
+class ProcessConsole(QTextEdit):
+ signal_stop_qthread = Signal()
+ signal_process_stopped = Signal()
+ signal_process_started = Signal()
+
+ insert_mode = ''
+
+ def __init__(self, args: list = None):
+ super().__init__()
+ self._is_running = False
+ self.auto_scroll = True
+ self.args = args #
+ self.setContentsMargins(20, 20, 0, 0)
+ self.monitor_thread: 'ProcessMonitorThread' = None
+ self.out_thread: 'QThread' = None
+
+ def is_running(self):
+ if self.monitor_thread is not None:
+ if self.monitor_thread.process.process.poll() is None:
+ return True
+ return False
+
+ def start_process(self):
+ if not self.is_running():
+ self.out_thread = QThread(self)
+ self.monitor_thread = ProcessMonitorThread()
+ self.monitor_thread.args = self.args
+ self.monitor_thread.moveToThread(self.out_thread)
+
+ self.out_thread.started.connect(self.monitor_thread.run)
+ self.out_thread.start()
+
+ self.monitor_thread.on_out.connect(self.on_stdout)
+ self.monitor_thread.on_err.connect(self.on_stderr)
+
+ self.signal_stop_qthread.connect(self.monitor_thread.stop)
+
+ self.out_thread.finished.connect(self.out_thread.deleteLater)
+ self.out_thread.finished.connect(self.monitor_thread.deleteLater)
+
+ self.monitor_thread.on_finished.connect(self.terminate_process)
+
+ def on_stdout(self, text):
+ if self.insert_mode == 'error':
+ self.insertHtml('' + '========' + '
')
+ self.insert_mode = 'stdout'
+
+ self.insertPlainText(text)
+ if self.auto_scroll:
+ self.ensureCursorVisible()
+
+ def on_stderr(self, text):
+ self.insert_mode = 'error'
+ self.insertHtml('' + text + '
')
+ print(text)
+ if self.auto_scroll:
+ self.ensureCursorVisible()
+
+ def terminate_process(self):
+ if self.monitor_thread is not None:
+ self.monitor_thread.process_terminated = True
+ self.monitor_thread.process.process.terminate()
+ if self.out_thread.isRunning():
+ self.signal_stop_qthread.emit()
+ self.out_thread.quit()
+ self.out_thread.wait(500)
+ self.monitor_thread = None
+ self.out_thread = None
+ self.signal_process_stopped.emit()
+
+ def keyPressEvent(self, e: 'QKeyEvent'):
+ if e.key() == Qt.Key_Backspace or e.key() == Qt.Key_Delete:
+ return
+ print(e.key(), e.text())
+ if e.key() == Qt.Key_Return:
+ text = '\n'
+ else:
+ text = e.text()
+ if text != '' and self.monitor_thread is not None:
+ try:
+ print('sent:', text)
+ self.monitor_thread.process.process.stdin.write(text.encode('utf8'))
+ self.monitor_thread.process.process.stdin.flush()
+ except:
+ import traceback
+ traceback.print_exc()
+ super(ProcessConsole, self).keyPressEvent(e)
+
+
+class PMGProcessConsoleWidget(QWidget):
+ def __init__(self, args: list = None):
+ super().__init__()
+ self.hbox_layout = QHBoxLayout()
+ self.tool_widget = QWidget()
+ self.tool_widget.setLayout(QVBoxLayout())
+ self.process_console = ProcessConsole(args=args)
+ self.process_console.signal_process_stopped.connect(self.on_terminated)
+
+ self.button_to_start = QPushButton('start')
+ self.tool_widget.layout().addWidget(self.button_to_start)
+ self.button_to_start.clicked.connect(self.start_process)
+
+ self.button_to_terminate = QPushButton('termi')
+ self.tool_widget.layout().addWidget(self.button_to_terminate)
+ self.button_to_terminate.clicked.connect(self.terminate_process)
+
+ self.button_to_clear = QPushButton('clear')
+ self.tool_widget.layout().addWidget(self.button_to_clear)
+ self.button_to_clear.clicked.connect(lambda: self.process_console.clear())
+
+ self.autoscroll_checker = QCheckBox()
+ self.autoscroll_checker.setToolTip('autoscroll')
+
+ self.tool_widget.layout().addWidget(self.autoscroll_checker)
+ self.autoscroll_checker.stateChanged.connect(self.set_autoscroll)
+ self.autoscroll_checker.setChecked(True)
+ self.set_autoscroll()
+
+ self.hbox_layout.addWidget(self.tool_widget)
+ vbox = QVBoxLayout()
+ self.hbox_layout.addLayout(vbox)
+ vbox.addWidget(self.process_console)
+ # self.command_input = QLineEdit()
+ # vbox.addWidget()
+ self.setLayout(self.hbox_layout)
+
+ def set_autoscroll(self):
+ print(self.autoscroll_checker.isChecked())
+ self.process_console.auto_scroll = self.autoscroll_checker.isChecked()
+
+ def on_terminated(self):
+ self.button_to_start.setEnabled(True)
+ self.button_to_terminate.setEnabled(False)
+
+ def on_started(self):
+ self.button_to_start.setEnabled(False)
+ self.button_to_terminate.setEnabled(True)
+
+ def start_process(self):
+ self.process_console.start_process()
+ self.on_started()
+
+ def terminate_process(self):
+ self.process_console.terminate_process()
+ self.on_terminated()
+
+ def is_process_running(self) -> bool:
+ return self.process_console.is_running()
+
+ def closeEvent(self, a0: QCloseEvent) -> None:
+ self.terminate_process()
+ # if self.process_console.out_thread.isRunning():
+ # self.process_console.monitor_thread.stop()
+ # self.process_console.out_thread.quit()
+ # self.process_console.out_thread.wait(500)
+
+ # self.process_console.monitor_thread.stop()
+ # self.process_console.monitor_thread.deleteLater()
+ # self.process_console.out_thread.deleteLater()
+ super(PMGProcessConsoleWidget, self).closeEvent(a0)
+
+
+if __name__ == '__main__':
+ import cgitb
+ import sys
+
+ cgitb.enable(format='text')
+ from PySide2.QtWidgets import QApplication, QTextEdit
+
+ app = QApplication(sys.argv)
+ w = PMGProcessConsoleWidget([sys.executable, '-u', 'test_open_app.py'])
+ w.show()
+
+ sys.exit(app.exec_())
diff --git a/pyminer/lib/ui/common/openprocess.py b/pyminer/lib/ui/common/openprocess.py
new file mode 100644
index 0000000000000000000000000000000000000000..00872fdab7a06a02a0764c62dfb2dadf85b37590
--- /dev/null
+++ b/pyminer/lib/ui/common/openprocess.py
@@ -0,0 +1,109 @@
+import queue
+import re
+import subprocess
+import sys
+import threading
+import time
+import chardet
+from typing import List
+
+import packages.code_editor.utils.utils
+
+
+class PMProcess():
+ def __init__(self, args: List[str]):
+ self.terminate = False
+ self.q = queue.Queue()
+ self.on_error_received = lambda error: print(error)
+ self.args = args
+ self.process = subprocess.Popen(self.args,
+ stdin=subprocess.PIPE,
+ shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ self.to = threading.Thread(
+ target=self.enqueue_stream, args=(
+ self.process.stdout, self.q, 1))
+ self.te = threading.Thread(
+ target=self.enqueue_stream, args=(
+ self.process.stderr, self.q, 2))
+ self.tp = threading.Thread(target=self.consoleLoop)
+ self.to.setDaemon(True)
+ self.te.setDaemon(True)
+ self.tp.setDaemon(True)
+ self.te.start()
+ self.to.start()
+ self.tp.start()
+
+ def enqueue_stream(self, stream, queue, type): # 将stderr或者stdout写入到队列q中。
+ for line in iter(stream.readline, b''):
+ if self.terminate: break
+ encoding = chardet.detect(line)['encoding']
+ queue.put(str(type) + packages.code_editor.utils.utils.decode(encoding))
+ stream.close()
+
+ def consoleLoop(self): # 封装后的内容。
+ # idleLoops = 0
+ while True:
+ if not self.q.empty():
+ line = self.q.get()
+ if line[0] == '1':
+ self.on_command_received(line[1:])
+ else:
+ self.on_error_received(line[1:])
+ sys.stdout.flush()
+ else:
+ time.sleep(0.01)
+ # idleLoops += 1
+
+ def input(self, message: str):
+ if not message.endswith('\n'):
+ message += '\n'
+ self.process.stdin.write(
+ message.encode('utf-8')) # 模拟输入
+ self.process.stdin.flush()
+
+ def on_command_received(self, cmd: str):
+ # print(cmd)
+ cmd = cmd.strip()
+ file_paths = re.findall(r'>(.+?)\(', cmd)
+ if len(file_paths) > 0:
+ path = file_paths[0].strip()
+ splitted = cmd.split(path)
+ if len(splitted) == 2:
+ remaining_words = splitted[1].strip()
+ current_row = re.findall(r'\((.+?)\)', remaining_words)
+ if len(current_row) >= 1:
+ print(path, int(current_row[0]))
+ # if remaining_words.startswith('<'):
+
+ # print('file:', file_result.group())
+ # while(1):
+ # if cmd.startswith('(Pdb)'):
+ # cmd = cmd.strip()
+ # cmd = cmd.strip('(Pdb)')
+ # else:
+ # cmd = cmd.strip()
+ # # if cmd.startswith('>'):
+ #
+ # break
+ print(cmd)
+
+
+if __name__ == '__main__':
+ s = r"""
+import os
+import pmdebug
+__global_keys = set(globals().keys())
+
+b E:\Python\pyminer_bin\PyMiner\bin\pmtoolbox\debug\test2.py:2
+alias pi for k in locals().keys(): pmdebug.insight(k,locals()[k]);print(12333333333333,os.path.dirname(r'c:\123123'))
+alias tobreak c;;pi a
+alias ps pi self
+
+"""
+ pmp = PMProcess(['python', '-u', '-m', 'pdb',
+ r'E:\Python\pyminer_bin\PyMiner\bin\pmtoolbox\debug\test.py'])
+ pmp.input(s)
+ while (1):
+ pmp.input('c')
+ time.sleep(2)
+ pass
diff --git a/pyminer/lib/ui/common/platformutil.py b/pyminer/lib/ui/common/platformutil.py
new file mode 100644
index 0000000000000000000000000000000000000000..3c3678685bb048b5a83f84e7484adc8dbf03457a
--- /dev/null
+++ b/pyminer/lib/ui/common/platformutil.py
@@ -0,0 +1,37 @@
+import platform
+import subprocess
+import logging
+logger = logging.getLogger(__name__)
+
+def check_platform() -> str:
+ system = platform.system()
+ return system.lower()
+
+
+def run_command_in_terminal(cmd: str, close_mode: str = 'wait_key'):
+ logging.warning('this platform is :',check_platform())
+ platform_name = check_platform()
+ if platform_name == 'windows':
+ close_action = {'auto': 'start cmd.exe /k \"%s &&exit \"',
+ 'no': 'start cmd.exe /k \"%s \"',
+ 'wait_key': 'start cmd.exe /k \"%s &&pause &&exit \"'
+ }
+ command = close_action[close_mode] % cmd
+
+ elif platform_name == 'deepin':
+ command = 'deepin-terminal -x bash -c \" %s \" ' % (cmd)
+ elif platform_name == 'linux':
+ command = 'gnome-terminal -x bash -c \"%s ;read\" ' % (cmd)
+ else:
+ return
+ subprocess.Popen(command, shell=True)
+
+
+if __name__ == '__main__':
+ def test_run_in_terminal():
+ import time
+ run_command_in_terminal('dir', close_mode='no')
+ time.sleep(1)
+ run_command_in_terminal('dir', close_mode='wait_key')
+ time.sleep(1)
+ run_command_in_terminal('dir', close_mode='auto')
diff --git a/pyminer/lib/ui/common/pmlocale.py b/pyminer/lib/ui/common/pmlocale.py
new file mode 100644
index 0000000000000000000000000000000000000000..7a4876dd20702e5d99a284a51b9b6290217f89cc
--- /dev/null
+++ b/pyminer/lib/ui/common/pmlocale.py
@@ -0,0 +1,47 @@
+from typing import Dict
+
+
+class Locale():
+ locale: str = 'zh_CN'
+ valid_locales: set = {'zh_CN', 'en'}
+ translations: Dict[str, Dict] = {}
+
+ def __init__(self):
+ pass
+
+ def add_locale(self, locale_name: str, translations: Dict[str, str]):
+ if locale_name not in self.valid_locales:
+ raise Exception('invalid locale selection:%s' % locale_name)
+ locale_dic = self.translations.get(locale_name)
+ if locale_dic is None:
+ self.translations[locale_name] = translations
+ else:
+ for k in translations:
+ if locale_dic.get(k) is None:
+ locale_dic[k]=translations[k]
+
+ def translate(self, text: str) -> str:
+ if self.locale=='en':
+ return text
+ locale = self.translations.get(self.locale)
+ if locale is not None:
+ translation = locale.get(text)
+ if translation is not None:
+ return translation
+ return text
+
+ def _(self, text: str) -> str:
+ return self.translate(text)
+
+
+pmlocale = Locale()
+pmlocale.locale = 'zh_CN'
+if __name__ == '__main__':
+ l = pmlocale
+ l.locale = 'zh_CN'
+ l.add_locale('en', {'interpreter': 'interpreter'})
+ l.add_locale('en', {'console': 'console'})
+ l.add_locale('zh_CN', {'console': '控制台'})
+ l.add_locale('zh_CN', {'interpreter': '解释器'})
+ s = l.translate('interpreter')
+ # print(s)
diff --git a/pyminer/lib/ui/common/test_open_app.py b/pyminer/lib/ui/common/test_open_app.py
new file mode 100644
index 0000000000000000000000000000000000000000..add9330200b324ab309841781ab95018c455a168
--- /dev/null
+++ b/pyminer/lib/ui/common/test_open_app.py
@@ -0,0 +1,12 @@
+if __name__ == '__main__':
+ print('hello world!!')
+ while (1):
+ try:
+ s = input('>>>')
+ print('you input', s)
+ assert s != '123'
+ except:
+ import traceback
+
+ traceback.print_exc()
+ pass
diff --git a/pyminer/lib/ui/main.py b/pyminer/lib/ui/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..6c7b700b963dda7ceac0f0733af56f78da9aeece
--- /dev/null
+++ b/pyminer/lib/ui/main.py
@@ -0,0 +1,88 @@
+from PySide2.QtCore import Qt
+from PySide2.QtGui import QFont
+from PySide2.QtWidgets import QApplication, QDialog, QTableWidget, QHeaderView, QAbstractItemView, QFrame, \
+ QTableWidgetItem
+
+
+from ui_data_normal import Ui_Dialog
+import sys
+import numpy as np
+import logging
+from decimal import Decimal
+
+
+class MyWindow(QDialog, Ui_Dialog):
+ def __init__(self):
+ super(MyWindow, self).__init__()
+ self.setupUi(self)
+
+ # 初始化一组正态分布数据
+ self.build_normal()
+
+ self.buttonBox.accepted.connect(self.build_normal)
+ self.buttonBox.rejected.connect(self.close)
+
+ def custom_menu(self):
+ '''
+ 定制鼠标右键菜单
+ :return:
+ '''
+
+ def keyPressEvent(self, event):
+ """ Ctrl + C复制表格内容 """
+ if event.modifiers() == Qt.ControlModifier and event.key() == Qt.Key_C:
+ # 获取表格的选中行
+ selected_ranges = self.tableWidget.selectedRanges()[0] # 只取第一个数据块,其他的如果需要要做遍历,简单功能就不写得那么复杂了
+ text_str = "" # 最后总的内容
+ # 行(选中的行信息读取)
+ for row in range(selected_ranges.topRow(), selected_ranges.bottomRow() + 1):
+ row_str = ""
+ # 列(选中的列信息读取)
+ for col in range(selected_ranges.leftColumn(), selected_ranges.rightColumn() + 1):
+ item = self.tableWidget.item(row, col)
+ row_str += item.text() + '\t' # 制表符间隔数据
+ text_str += row_str + '\n' # 换行
+ clipboard = QApplication.clipboard() # 获取剪贴板
+ clipboard.setText(text_str) # 内容写入剪贴板
+
+ def build_normal(self):
+ self.v_precision = self.spinBox_precison.value()
+ self.v_mean = self.doubleSpinBox_mean.value()
+ self.v_std = self.doubleSpinBox_std.value()
+ self.v_count = self.spinBox_count.value()
+
+ np.set_printoptions(precision=self.v_precision)
+ self.data = np.random.normal(loc=self.v_mean, scale=self.v_std, size=self.v_count)
+ print(self.data)
+ print(type(self.data))
+ # logging.info(data)
+
+ self.show_table()
+
+ def show_table(self):
+
+ self.tableWidget.setSortingEnabled(True) # 设置表头可以自动排序
+ self.tableWidget.setColumnCount(1)
+ self.tableWidget.setRowCount(len(self.data))
+ self.tableWidget.setHorizontalHeaderLabels(['正态分布数据'])
+
+ for i in range(len(self.data)):
+
+ # 调用Decimal 设置数据精度格式
+ if self.v_precision == 0:
+ decimal_format = '0'
+ else:
+ decimal_format = '0.' + '0' * self.v_precision
+
+ random_value = Decimal(self.data[i]).quantize(Decimal(decimal_format)) # 调用Decimal 设置数据精度
+
+ item = QTableWidgetItem(str(random_value))
+ item.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
+ self.tableWidget.setItem(i, 0, item)
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ form1 = MyWindow()
+ form1.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/lib/ui/pm_marketplace/__init__.py b/pyminer/lib/ui/pm_marketplace/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/lib/ui/pm_marketplace/install.py b/pyminer/lib/ui/pm_marketplace/install.py
new file mode 100644
index 0000000000000000000000000000000000000000..dc48061fc45f0eb079cee4931bde55057759b547
--- /dev/null
+++ b/pyminer/lib/ui/pm_marketplace/install.py
@@ -0,0 +1,212 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'install.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import *
+from PySide2.QtGui import *
+from PySide2.QtWidgets import *
+
+from widgets import PMGProcessConsoleWidget
+
+
+class Ui_Form(object):
+ def setupUi(self, Form):
+ if not Form.objectName():
+ Form.setObjectName(u"Form")
+ Form.resize(800, 600)
+ self.verticalLayout = QVBoxLayout(Form)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.horizontalLayout_4 = QHBoxLayout()
+ self.horizontalLayout_4.setObjectName(u"horizontalLayout_4")
+ self.formLayout = QFormLayout()
+ self.formLayout.setObjectName(u"formLayout")
+ self.label_2 = QLabel(Form)
+ self.label_2.setObjectName(u"label_2")
+
+ self.formLayout.setWidget(0, QFormLayout.LabelRole, self.label_2)
+
+ self.horizontalLayout_7 = QHBoxLayout()
+ self.horizontalLayout_7.setObjectName(u"horizontalLayout_7")
+ self.lineEdit_name = QLineEdit(Form)
+ self.lineEdit_name.setObjectName(u"lineEdit_name")
+
+ self.horizontalLayout_7.addWidget(self.lineEdit_name)
+
+ self.checkBox_version = QCheckBox(Form)
+ self.checkBox_version.setObjectName(u"checkBox_version")
+
+ self.horizontalLayout_7.addWidget(self.checkBox_version)
+
+ self.lineEdit_version = QLineEdit(Form)
+ self.lineEdit_version.setObjectName(u"lineEdit_version")
+
+ self.horizontalLayout_7.addWidget(self.lineEdit_version)
+
+
+ self.formLayout.setLayout(0, QFormLayout.FieldRole, self.horizontalLayout_7)
+
+ self.label = QLabel(Form)
+ self.label.setObjectName(u"label")
+
+ self.formLayout.setWidget(1, QFormLayout.LabelRole, self.label)
+
+ self.horizontalLayout_3 = QHBoxLayout()
+ self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
+ self.comboBox_source = QComboBox(Form)
+ self.comboBox_source.addItem("")
+ self.comboBox_source.addItem("")
+ self.comboBox_source.addItem("")
+ self.comboBox_source.addItem("")
+ self.comboBox_source.addItem("")
+ self.comboBox_source.addItem("")
+ self.comboBox_source.setObjectName(u"comboBox_source")
+ self.comboBox_source.setEnabled(True)
+ self.comboBox_source.setMinimumSize(QSize(100, 0))
+ self.comboBox_source.setMaximumSize(QSize(100, 16777215))
+
+ self.horizontalLayout_3.addWidget(self.comboBox_source)
+
+ self.lineEdit_source = QLineEdit(Form)
+ self.lineEdit_source.setObjectName(u"lineEdit_source")
+ self.lineEdit_source.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter)
+ self.lineEdit_source.setReadOnly(True)
+
+ self.horizontalLayout_3.addWidget(self.lineEdit_source)
+
+
+ self.formLayout.setLayout(1, QFormLayout.FieldRole, self.horizontalLayout_3)
+
+ self.label_3 = QLabel(Form)
+ self.label_3.setObjectName(u"label_3")
+
+ self.formLayout.setWidget(2, QFormLayout.LabelRole, self.label_3)
+
+ self.horizontalLayout = QHBoxLayout()
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.comboBox_dir = QComboBox(Form)
+ self.comboBox_dir.addItem("")
+ self.comboBox_dir.addItem("")
+ self.comboBox_dir.addItem("")
+ self.comboBox_dir.addItem("")
+ self.comboBox_dir.setObjectName(u"comboBox_dir")
+ self.comboBox_dir.setMinimumSize(QSize(100, 0))
+ self.comboBox_dir.setMaximumSize(QSize(100, 16777215))
+
+ self.horizontalLayout.addWidget(self.comboBox_dir)
+
+ self.lineEdit_dir = QLineEdit(Form)
+ self.lineEdit_dir.setObjectName(u"lineEdit_dir")
+ self.lineEdit_dir.setReadOnly(True)
+
+ self.horizontalLayout.addWidget(self.lineEdit_dir)
+
+ self.toolButton = QToolButton(Form)
+ self.toolButton.setObjectName(u"toolButton")
+
+ self.horizontalLayout.addWidget(self.toolButton)
+
+
+ self.formLayout.setLayout(2, QFormLayout.FieldRole, self.horizontalLayout)
+
+
+ self.horizontalLayout_4.addLayout(self.formLayout)
+
+
+ self.verticalLayout.addLayout(self.horizontalLayout_4)
+
+ self.groupBox = QGroupBox(Form)
+ self.groupBox.setObjectName(u"groupBox")
+ self.horizontalLayout_5 = QHBoxLayout(self.groupBox)
+ self.horizontalLayout_5.setObjectName(u"horizontalLayout_5")
+ self.horizontalLayout_5.setContentsMargins(0, 0, 0, 0)
+ self.textEdit_desc = QTextEdit(self.groupBox)
+ self.textEdit_desc.setObjectName(u"textEdit_desc")
+
+ self.horizontalLayout_5.addWidget(self.textEdit_desc)
+
+
+ self.verticalLayout.addWidget(self.groupBox)
+
+ self.groupBox_2 = QGroupBox(Form)
+ self.groupBox_2.setObjectName(u"groupBox_2")
+ self.horizontalLayout_6 = QHBoxLayout(self.groupBox_2)
+ self.horizontalLayout_6.setObjectName(u"horizontalLayout_6")
+ self.horizontalLayout_6.setContentsMargins(0, 0, 0, 0)
+ self.exec_log = PMGProcessConsoleWidget(self.groupBox_2)
+ self.exec_log.setObjectName(u"exec_log")
+ self.exec_log.setMinimumSize(QSize(0, 200))
+
+ self.horizontalLayout_6.addWidget(self.exec_log)
+
+
+ self.verticalLayout.addWidget(self.groupBox_2)
+
+ self.widget = QWidget(Form)
+ self.widget.setObjectName(u"widget")
+ self.widget.setMaximumSize(QSize(16777215, 50))
+ self.verticalLayout_2 = QVBoxLayout(self.widget)
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.horizontalLayout_2 = QHBoxLayout()
+ self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_2.addItem(self.horizontalSpacer)
+
+ self.pushButton_install = QPushButton(self.widget)
+ self.pushButton_install.setObjectName(u"pushButton_install")
+
+ self.horizontalLayout_2.addWidget(self.pushButton_install)
+
+ self.pushButton_close = QPushButton(self.widget)
+ self.pushButton_close.setObjectName(u"pushButton_close")
+
+ self.horizontalLayout_2.addWidget(self.pushButton_close)
+
+
+ self.verticalLayout_2.addLayout(self.horizontalLayout_2)
+
+
+ self.verticalLayout.addWidget(self.widget)
+
+
+ self.retranslateUi(Form)
+
+ QMetaObject.connectSlotsByName(Form)
+ # setupUi
+
+ def retranslateUi(self, Form):
+ Form.setWindowTitle(QCoreApplication.translate("Form", u"Install", None))
+ self.label_2.setText(QCoreApplication.translate("Form", u"Package:", None))
+ self.lineEdit_name.setText("")
+ self.checkBox_version.setText(QCoreApplication.translate("Form", u"\u6307\u5b9a\u7248\u672c", None))
+ self.lineEdit_version.setText(QCoreApplication.translate("Form", u"", None))
+ self.label.setText(QCoreApplication.translate("Form", u"Source:", None))
+ self.comboBox_source.setItemText(0, QCoreApplication.translate("Form", u"Tencent", None))
+ self.comboBox_source.setItemText(1, QCoreApplication.translate("Form", u"Official", None))
+ self.comboBox_source.setItemText(2, QCoreApplication.translate("Form", u"Tsinghua", None))
+ self.comboBox_source.setItemText(3, QCoreApplication.translate("Form", u"AliYun", None))
+ self.comboBox_source.setItemText(4, QCoreApplication.translate("Form", u"DouBan", None))
+ self.comboBox_source.setItemText(5, QCoreApplication.translate("Form", u"Customize..", None))
+
+ self.lineEdit_source.setText(QCoreApplication.translate("Form", u"https://mirrors.cloud.tencent.com/pypi/simple", None))
+ self.lineEdit_source.setPlaceholderText(QCoreApplication.translate("Form", u"\u817e\u8baf\u955c\u50cf\u6e90", None))
+ self.label_3.setText(QCoreApplication.translate("Form", u"Options:", None))
+ self.comboBox_dir.setItemText(0, QCoreApplication.translate("Form", u"\u9ed8\u8ba4\u4f4d\u7f6e", None))
+ self.comboBox_dir.setItemText(1, QCoreApplication.translate("Form", u"\u7528\u6237\u76ee\u5f55", None))
+ self.comboBox_dir.setItemText(2, QCoreApplication.translate("Form", u"\u4ec5\u4e0b\u8f7d", None))
+ self.comboBox_dir.setItemText(3, QCoreApplication.translate("Form", u"\u81ea\u5b9a\u4e49", None))
+
+ self.lineEdit_dir.setPlaceholderText(QCoreApplication.translate("Form", u"\u9ed8\u8ba4", None))
+ self.toolButton.setText(QCoreApplication.translate("Form", u"...", None))
+ self.groupBox.setTitle(QCoreApplication.translate("Form", u"\u8be6\u60c5", None))
+ self.groupBox_2.setTitle(QCoreApplication.translate("Form", u"Log", None))
+ self.pushButton_install.setText(QCoreApplication.translate("Form", u"Install", None))
+ self.pushButton_close.setText(QCoreApplication.translate("Form", u"Close", None))
+ # retranslateUi
+
diff --git a/pyminer/lib/ui/pm_marketplace/install.ui b/pyminer/lib/ui/pm_marketplace/install.ui
new file mode 100644
index 0000000000000000000000000000000000000000..6716d8c5f22a0982f07f9f52e151ddd4698c6300
--- /dev/null
+++ b/pyminer/lib/ui/pm_marketplace/install.ui
@@ -0,0 +1,307 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 800
+ 600
+
+
+
+ Install
+
+
+ -
+
+
-
+
+
-
+
+
+ Package:
+
+
+
+ -
+
+
-
+
+
+
+
+
+
+ -
+
+
+ 指定版本
+
+
+
+ -
+
+
+ <Latest>
+
+
+
+
+
+ -
+
+
+ Source:
+
+
+
+ -
+
+
-
+
+
+ true
+
+
+
+ 100
+ 0
+
+
+
+
+ 100
+ 16777215
+
+
+
-
+
+ Tencent
+
+
+ -
+
+ Official
+
+
+ -
+
+ Tsinghua
+
+
+ -
+
+ AliYun
+
+
+ -
+
+ DouBan
+
+
+ -
+
+ Customize..
+
+
+
+
+ -
+
+
+ https://mirrors.cloud.tencent.com/pypi/simple
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+ true
+
+
+ 腾讯镜像源
+
+
+
+
+
+ -
+
+
+ Options:
+
+
+
+ -
+
+
-
+
+
+
+ 100
+ 0
+
+
+
+
+ 100
+ 16777215
+
+
+
-
+
+ 默认位置
+
+
+ -
+
+ 用户目录
+
+
+ -
+
+ 仅下载
+
+
+ -
+
+ 自定义
+
+
+
+
+ -
+
+
+ true
+
+
+ 默认
+
+
+
+ -
+
+
+ ...
+
+
+
+
+
+
+
+
+
+ -
+
+
+ 详情
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+
+
+ -
+
+
+ Log
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 200
+
+
+
+
+
+
+
+ -
+
+
+
+ 16777215
+ 50
+
+
+
+
-
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Install
+
+
+
+ -
+
+
+ Close
+
+
+
+
+
+
+
+
+
+
+
+
+ PMGProcessConsoleWidget
+ QWidget
+
+ 1
+
+
+
+
+
diff --git a/pyminer/lib/ui/pm_marketplace/main.py b/pyminer/lib/ui/pm_marketplace/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..1602faab7a9defaa902d0178765bfa672cb2e93a
--- /dev/null
+++ b/pyminer/lib/ui/pm_marketplace/main.py
@@ -0,0 +1,137 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'main.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import *
+from PySide2.QtGui import *
+from PySide2.QtWidgets import *
+
+
+class Ui_Form(object):
+ def setupUi(self, Form):
+ if not Form.objectName():
+ Form.setObjectName(u"Form")
+ Form.resize(978, 664)
+ self.verticalLayout = QVBoxLayout(Form)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.horizontalLayout = QHBoxLayout()
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.toolButton_install = QToolButton(Form)
+ self.toolButton_install.setObjectName(u"toolButton_install")
+ self.toolButton_install.setEnabled(True)
+
+ self.horizontalLayout.addWidget(self.toolButton_install)
+
+ self.toolButton_downloan = QToolButton(Form)
+ self.toolButton_downloan.setObjectName(u"toolButton_downloan")
+ self.toolButton_downloan.setEnabled(False)
+
+ self.horizontalLayout.addWidget(self.toolButton_downloan)
+
+ self.toolButton_detail = QToolButton(Form)
+ self.toolButton_detail.setObjectName(u"toolButton_detail")
+ self.toolButton_detail.setEnabled(False)
+
+ self.horizontalLayout.addWidget(self.toolButton_detail)
+
+ self.toolButton_update = QToolButton(Form)
+ self.toolButton_update.setObjectName(u"toolButton_update")
+ self.toolButton_update.setEnabled(False)
+
+ self.horizontalLayout.addWidget(self.toolButton_update)
+
+ self.toolButton_uninstall = QToolButton(Form)
+ self.toolButton_uninstall.setObjectName(u"toolButton_uninstall")
+ self.toolButton_uninstall.setEnabled(False)
+
+ self.horizontalLayout.addWidget(self.toolButton_uninstall)
+
+ self.toolButton_check_update = QToolButton(Form)
+ self.toolButton_check_update.setObjectName(u"toolButton_check_update")
+ self.toolButton_check_update.setEnabled(False)
+
+ self.horizontalLayout.addWidget(self.toolButton_check_update)
+
+ self.toolButton_install_requirements = QToolButton(Form)
+ self.toolButton_install_requirements.setObjectName(u"toolButton_install_requirements")
+ self.toolButton_install_requirements.setEnabled(False)
+
+ self.horizontalLayout.addWidget(self.toolButton_install_requirements)
+
+ self.toolButton_freeze_requirements = QToolButton(Form)
+ self.toolButton_freeze_requirements.setObjectName(u"toolButton_freeze_requirements")
+ self.toolButton_freeze_requirements.setEnabled(False)
+
+ self.horizontalLayout.addWidget(self.toolButton_freeze_requirements)
+
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout.addItem(self.horizontalSpacer)
+
+ self.lineEdit = QLineEdit(Form)
+ self.lineEdit.setObjectName(u"lineEdit")
+ self.lineEdit.setEnabled(False)
+
+ self.horizontalLayout.addWidget(self.lineEdit)
+
+
+ self.verticalLayout.addLayout(self.horizontalLayout)
+
+ self.tableWidget = QTableWidget(Form)
+ if (self.tableWidget.columnCount() < 6):
+ self.tableWidget.setColumnCount(6)
+ __qtablewidgetitem = QTableWidgetItem()
+ self.tableWidget.setHorizontalHeaderItem(0, __qtablewidgetitem)
+ __qtablewidgetitem1 = QTableWidgetItem()
+ self.tableWidget.setHorizontalHeaderItem(1, __qtablewidgetitem1)
+ __qtablewidgetitem2 = QTableWidgetItem()
+ self.tableWidget.setHorizontalHeaderItem(2, __qtablewidgetitem2)
+ __qtablewidgetitem3 = QTableWidgetItem()
+ self.tableWidget.setHorizontalHeaderItem(3, __qtablewidgetitem3)
+ __qtablewidgetitem4 = QTableWidgetItem()
+ self.tableWidget.setHorizontalHeaderItem(4, __qtablewidgetitem4)
+ __qtablewidgetitem5 = QTableWidgetItem()
+ self.tableWidget.setHorizontalHeaderItem(5, __qtablewidgetitem5)
+ if (self.tableWidget.rowCount() < 9995):
+ self.tableWidget.setRowCount(9995)
+ self.tableWidget.setObjectName(u"tableWidget")
+ self.tableWidget.setRowCount(9995)
+
+ self.verticalLayout.addWidget(self.tableWidget)
+
+
+ self.retranslateUi(Form)
+
+ QMetaObject.connectSlotsByName(Form)
+ # setupUi
+
+ def retranslateUi(self, Form):
+ Form.setWindowTitle(QCoreApplication.translate("Form", u"\u6269\u5c55\u4e2d\u5fc3", None))
+ self.toolButton_install.setText(QCoreApplication.translate("Form", u"Install", None))
+ self.toolButton_downloan.setText(QCoreApplication.translate("Form", u"Download", None))
+ self.toolButton_detail.setText(QCoreApplication.translate("Form", u"Details", None))
+ self.toolButton_update.setText(QCoreApplication.translate("Form", u"Update", None))
+ self.toolButton_uninstall.setText(QCoreApplication.translate("Form", u"Uninstall", None))
+ self.toolButton_check_update.setText(QCoreApplication.translate("Form", u"CheckUpdate", None))
+ self.toolButton_install_requirements.setText(QCoreApplication.translate("Form", u"Import", None))
+ self.toolButton_freeze_requirements.setText(QCoreApplication.translate("Form", u"Export", None))
+ ___qtablewidgetitem = self.tableWidget.horizontalHeaderItem(0)
+ ___qtablewidgetitem.setText(QCoreApplication.translate("Form", u"Name", None));
+ ___qtablewidgetitem1 = self.tableWidget.horizontalHeaderItem(1)
+ ___qtablewidgetitem1.setText(QCoreApplication.translate("Form", u"Version", None));
+ ___qtablewidgetitem2 = self.tableWidget.horizontalHeaderItem(2)
+ ___qtablewidgetitem2.setText(QCoreApplication.translate("Form", u"Latest Version", None));
+ ___qtablewidgetitem3 = self.tableWidget.horizontalHeaderItem(3)
+ ___qtablewidgetitem3.setText(QCoreApplication.translate("Form", u"Type", None));
+ ___qtablewidgetitem4 = self.tableWidget.horizontalHeaderItem(4)
+ ___qtablewidgetitem4.setText(QCoreApplication.translate("Form", u"Update", None));
+ ___qtablewidgetitem5 = self.tableWidget.horizontalHeaderItem(5)
+ ___qtablewidgetitem5.setText(QCoreApplication.translate("Form", u"Uninstall", None));
+ # retranslateUi
+
diff --git a/pyminer/lib/ui/pm_marketplace/main.ui b/pyminer/lib/ui/pm_marketplace/main.ui
new file mode 100644
index 0000000000000000000000000000000000000000..2561758ffeeca4c3c5d05d134ee7c6299648df93
--- /dev/null
+++ b/pyminer/lib/ui/pm_marketplace/main.ui
@@ -0,0 +1,10157 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 978
+ 664
+
+
+
+ 扩展中心
+
+
+ -
+
+
-
+
+
+ true
+
+
+ Install
+
+
+
+ -
+
+
+ false
+
+
+ Download
+
+
+
+ -
+
+
+ false
+
+
+ Details
+
+
+
+ -
+
+
+ false
+
+
+ Update
+
+
+
+ -
+
+
+ false
+
+
+ Uninstall
+
+
+
+ -
+
+
+ false
+
+
+ CheckUpdate
+
+
+
+ -
+
+
+ false
+
+
+ Import
+
+
+
+ -
+
+
+ false
+
+
+ Export
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ false
+
+
+
+
+
+ -
+
+
+ 9995
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name
+
+
+
+
+ Version
+
+
+
+
+ Latest Version
+
+
+
+
+ Type
+
+
+
+
+ Update
+
+
+
+
+ Uninstall
+
+
+
+
+
+
+
+
+
diff --git a/pyminer/lib/ui/pm_marketplace/package_manager_main.py b/pyminer/lib/ui/pm_marketplace/package_manager_main.py
new file mode 100644
index 0000000000000000000000000000000000000000..650f9604bdd85057d25c64bc9b7039fa8d3b2446
--- /dev/null
+++ b/pyminer/lib/ui/pm_marketplace/package_manager_main.py
@@ -0,0 +1,110 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'package_manager_main.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import *
+from PySide2.QtGui import *
+from PySide2.QtWidgets import *
+
+
+class Ui_Form(object):
+ def setupUi(self, Form):
+ if not Form.objectName():
+ Form.setObjectName(u"Form")
+ Form.resize(978, 664)
+ self.verticalLayout = QVBoxLayout(Form)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.horizontalLayout = QHBoxLayout()
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.toolButton_install = QToolButton(Form)
+ self.toolButton_install.setObjectName(u"toolButton_install")
+ self.toolButton_install.setEnabled(True)
+
+ self.horizontalLayout.addWidget(self.toolButton_install)
+
+ self.toolButton_update = QToolButton(Form)
+ self.toolButton_update.setObjectName(u"toolButton_update")
+ self.toolButton_update.setEnabled(True)
+
+ self.horizontalLayout.addWidget(self.toolButton_update)
+
+ self.toolButton_uninstall = QToolButton(Form)
+ self.toolButton_uninstall.setObjectName(u"toolButton_uninstall")
+ self.toolButton_uninstall.setEnabled(True)
+
+ self.horizontalLayout.addWidget(self.toolButton_uninstall)
+
+ self.toolButton_install_requirements = QToolButton(Form)
+ self.toolButton_install_requirements.setObjectName(u"toolButton_install_requirements")
+ self.toolButton_install_requirements.setEnabled(False)
+
+ self.horizontalLayout.addWidget(self.toolButton_install_requirements)
+
+ self.toolButton_freeze_requirements = QToolButton(Form)
+ self.toolButton_freeze_requirements.setObjectName(u"toolButton_freeze_requirements")
+ self.toolButton_freeze_requirements.setEnabled(False)
+
+ self.horizontalLayout.addWidget(self.toolButton_freeze_requirements)
+
+ self.toolButton_downloan = QToolButton(Form)
+ self.toolButton_downloan.setObjectName(u"toolButton_downloan")
+ self.toolButton_downloan.setEnabled(False)
+
+ self.horizontalLayout.addWidget(self.toolButton_downloan)
+
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout.addItem(self.horizontalSpacer)
+
+ self.lineEdit = QLineEdit(Form)
+ self.lineEdit.setObjectName(u"lineEdit")
+ self.lineEdit.setEnabled(False)
+
+ self.horizontalLayout.addWidget(self.lineEdit)
+
+
+ self.verticalLayout.addLayout(self.horizontalLayout)
+
+ self.tableWidget = QTableWidget(Form)
+ if (self.tableWidget.columnCount() < 2):
+ self.tableWidget.setColumnCount(2)
+ __qtablewidgetitem = QTableWidgetItem()
+ self.tableWidget.setHorizontalHeaderItem(0, __qtablewidgetitem)
+ __qtablewidgetitem1 = QTableWidgetItem()
+ self.tableWidget.setHorizontalHeaderItem(1, __qtablewidgetitem1)
+ if (self.tableWidget.rowCount() < 100):
+ self.tableWidget.setRowCount(100)
+ self.tableWidget.setObjectName(u"tableWidget")
+ self.tableWidget.setRowCount(100)
+ self.tableWidget.horizontalHeader().setCascadingSectionResizes(False)
+ self.tableWidget.horizontalHeader().setProperty("showSortIndicator", False)
+ self.tableWidget.horizontalHeader().setStretchLastSection(False)
+
+ self.verticalLayout.addWidget(self.tableWidget)
+
+
+ self.retranslateUi(Form)
+
+ QMetaObject.connectSlotsByName(Form)
+ # setupUi
+
+ def retranslateUi(self, Form):
+ Form.setWindowTitle(QCoreApplication.translate("Form", u"Package Manager", None))
+ self.toolButton_install.setText(QCoreApplication.translate("Form", u"Install", None))
+ self.toolButton_update.setText(QCoreApplication.translate("Form", u"Update", None))
+ self.toolButton_uninstall.setText(QCoreApplication.translate("Form", u"Uninstall", None))
+ self.toolButton_install_requirements.setText(QCoreApplication.translate("Form", u"Import", None))
+ self.toolButton_freeze_requirements.setText(QCoreApplication.translate("Form", u"Export", None))
+ self.toolButton_downloan.setText(QCoreApplication.translate("Form", u"Download", None))
+ ___qtablewidgetitem = self.tableWidget.horizontalHeaderItem(0)
+ ___qtablewidgetitem.setText(QCoreApplication.translate("Form", u"Name", None));
+ ___qtablewidgetitem1 = self.tableWidget.horizontalHeaderItem(1)
+ ___qtablewidgetitem1.setText(QCoreApplication.translate("Form", u"Version", None));
+ # retranslateUi
+
diff --git a/pyminer/lib/ui/pm_marketplace/package_manager_main.ui b/pyminer/lib/ui/pm_marketplace/package_manager_main.ui
new file mode 100644
index 0000000000000000000000000000000000000000..930d7a1befbca9e3915bac2693b503fdeca223bd
--- /dev/null
+++ b/pyminer/lib/ui/pm_marketplace/package_manager_main.ui
@@ -0,0 +1,231 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 978
+ 664
+
+
+
+ Package Manager
+
+
+ -
+
+
-
+
+
+ true
+
+
+ Install
+
+
+
+ -
+
+
+ true
+
+
+ Update
+
+
+
+ -
+
+
+ true
+
+
+ Uninstall
+
+
+
+ -
+
+
+ false
+
+
+ Import
+
+
+
+ -
+
+
+ false
+
+
+ Export
+
+
+
+ -
+
+
+ false
+
+
+ Download
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ false
+
+
+
+
+
+ -
+
+
+ 100
+
+
+ false
+
+
+ false
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name
+
+
+
+
+ Version
+
+
+
+
+
+
+
+
+
diff --git a/pyminer/lib/ui/pm_marketplace/uninstall.py b/pyminer/lib/ui/pm_marketplace/uninstall.py
new file mode 100644
index 0000000000000000000000000000000000000000..9245082dc96e84c8a5dfe7308e5275e6511ad5c8
--- /dev/null
+++ b/pyminer/lib/ui/pm_marketplace/uninstall.py
@@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'uninstall.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import *
+from PySide2.QtGui import *
+from PySide2.QtWidgets import *
+
+from widgets import PMGProcessConsoleWidget
+
+
+class Ui_Dialog(object):
+ def setupUi(self, Dialog):
+ if not Dialog.objectName():
+ Dialog.setObjectName(u"Dialog")
+ Dialog.resize(800, 573)
+ self.verticalLayout = QVBoxLayout(Dialog)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.groupBox = QGroupBox(Dialog)
+ self.groupBox.setObjectName(u"groupBox")
+ self.horizontalLayout_2 = QHBoxLayout(self.groupBox)
+ self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
+ self.log_console = PMGProcessConsoleWidget(self.groupBox)
+ self.log_console.setObjectName(u"log_console")
+
+ self.horizontalLayout_2.addWidget(self.log_console)
+
+
+ self.verticalLayout.addWidget(self.groupBox)
+
+ self.widget = QWidget(Dialog)
+ self.widget.setObjectName(u"widget")
+ self.widget.setMaximumSize(QSize(16777215, 50))
+ self.widget.setSizeIncrement(QSize(0, 0))
+ self.horizontalLayout = QHBoxLayout(self.widget)
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.horizontalSpacer = QSpacerItem(680, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout.addItem(self.horizontalSpacer)
+
+ self.button_close = QPushButton(self.widget)
+ self.button_close.setObjectName(u"button_close")
+
+ self.horizontalLayout.addWidget(self.button_close)
+
+
+ self.verticalLayout.addWidget(self.widget)
+
+
+ self.retranslateUi(Dialog)
+
+ QMetaObject.connectSlotsByName(Dialog)
+ # setupUi
+
+ def retranslateUi(self, Dialog):
+ Dialog.setWindowTitle(QCoreApplication.translate("Dialog", u"Dialog", None))
+ self.groupBox.setTitle(QCoreApplication.translate("Dialog", u"Log", None))
+ self.button_close.setText(QCoreApplication.translate("Dialog", u"Close", None))
+ # retranslateUi
+
diff --git a/pyminer/lib/ui/pm_marketplace/uninstall.ui b/pyminer/lib/ui/pm_marketplace/uninstall.ui
new file mode 100644
index 0000000000000000000000000000000000000000..f992008bf300ab4cb36ee0083afb295362adb012
--- /dev/null
+++ b/pyminer/lib/ui/pm_marketplace/uninstall.ui
@@ -0,0 +1,79 @@
+
+
+ Dialog
+
+
+
+ 0
+ 0
+ 800
+ 573
+
+
+
+ Dialog
+
+
+ -
+
+
+ Log
+
+
+
-
+
+
+
+
+
+ -
+
+
+
+ 16777215
+ 50
+
+
+
+
+ 0
+ 0
+
+
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 680
+ 20
+
+
+
+
+ -
+
+
+ Close
+
+
+
+
+
+
+
+
+
+
+ PMGProcessConsoleWidget
+ QWidget
+
+ 1
+
+
+
+
+
diff --git a/pyminer/lib/ui/pmwidgets/__init__.py b/pyminer/lib/ui/pmwidgets/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..0df559b7fe396d043aaafc2e0d48220d30a503ec
--- /dev/null
+++ b/pyminer/lib/ui/pmwidgets/__init__.py
@@ -0,0 +1,3 @@
+from .dockwidget import PMDockWidget
+from .pmmainwindow import BaseMainWindow
+from .toplevel import TopLevelWidget
diff --git a/pyminer/lib/ui/pmwidgets/dockwidget.py b/pyminer/lib/ui/pmwidgets/dockwidget.py
new file mode 100644
index 0000000000000000000000000000000000000000..637bd6d698100e153b7ec2be5cf403cae1ee046f
--- /dev/null
+++ b/pyminer/lib/ui/pmwidgets/dockwidget.py
@@ -0,0 +1,21 @@
+from PySide2.QtGui import QCloseEvent
+from widgets import PMGDockWidget
+
+
+class PMDockWidget(PMGDockWidget):
+
+ def closeEvent(self, event: 'QCloseEvent'):
+ from utils import get_main_window
+ main_window = get_main_window()
+ w = self.widget()
+ if hasattr(w, 'on_closed_action'):
+ if w.on_closed_action == 'delete':
+ main_window.delete_dock_widget(self.name)
+ return
+ self.hide()
+ event.accept()
+ main_window.refresh_view_configs()
+
+ def bind_events(self):
+ """绑定该控件的所有事件,会在主程序加载结束后自动执行,不需要在__init__里面手动执行"""
+ pass
diff --git a/pyminer/lib/ui/pmwidgets/pmmainwindow.py b/pyminer/lib/ui/pmwidgets/pmmainwindow.py
new file mode 100644
index 0000000000000000000000000000000000000000..2a98ff719f09b0792c2019977a577c4f61c088c3
--- /dev/null
+++ b/pyminer/lib/ui/pmwidgets/pmmainwindow.py
@@ -0,0 +1,373 @@
+"""
+
+这里定义了MainWindow的基类。
+
+基类主要包含带选项卡工具栏的管理功能,以及浮动窗口的管理功能。
+
+添加浮动窗口时,默认‘关闭’事件就是隐藏。如果是彻底的关闭,需要进行重写。
+
+每次界面关闭时,布局会被存入文件pyminer/config/customized/layout.ini之中。
+
+再次启动时,若这个文件存在,就会加载,反之不会加载。
+
+"""
+
+import logging
+import os
+from typing import Dict, TYPE_CHECKING
+
+import chardet
+from PySide2.QtCore import Qt
+from PySide2.QtWidgets import QMainWindow, QToolBar, QPushButton, QWidget, QMenu, QDialog
+
+import utils
+from lib.ui.common.pmlocale import pmlocale
+from widgets import TopToolBarRight
+
+if TYPE_CHECKING:
+ from app2 import PMToolBarHome
+ from lib.ui.pmwidgets.dockwidget import PMDockWidget
+ from widgets import ActionWithMessage
+logger = logging.getLogger(__name__)
+
+
+class BaseMainWindow(QMainWindow):
+ """
+ PyMiner MainWindow主界面类的基类.
+
+ """
+ dialog_classes: Dict[str, 'QDialog'] = {}
+ toolbars: Dict[str, QToolBar] = {}
+ _current_toolbar_name: str = '' # 当前的窗口标题栏选项卡
+ __dock_widgets: Dict[str, 'PMDockWidget']
+ dock_places = {'left': Qt.LeftDockWidgetArea, 'right': Qt.RightDockWidgetArea, 'top': Qt.TopDockWidgetArea,
+ 'bottom': Qt.BottomDockWidgetArea}
+
+ @property
+ def dock_widgets(self):
+ return self.__dock_widgets
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.__dock_widgets = {}
+ self.setContextMenuPolicy(Qt.NoContextMenu) # 隐藏主界面的菜单栏。
+
+ def set_dock_titlebar_visible(self, show: bool):
+ """
+ 切换停靠窗口的标题栏是否可见。只有可见的时候才能拖动。
+ Args:
+ show:
+
+ Returns:
+
+ """
+ if show:
+ for k, w in self.dock_widgets.items():
+ w.setTitleBarWidget(None)
+
+ else:
+ for k, w in self.dock_widgets.items():
+ w.setTitleBarWidget(QWidget())
+ utils.write_settings_item_to_file("config.ini", "MAIN/PATH_WORKDIR", show)
+
+ @staticmethod
+ def get_stylesheet(style_sheet_name: str = 'standard'):
+ """
+ 获取样式表
+ TODO:这个方法应该拿到外面去,不应该和主界面深度绑定!
+ Args:
+ style_sheet_name:
+
+ Returns:
+
+ """
+ style_sheet = ""
+ with open(os.path.join(utils.get_root_dir(), 'resources', 'qss', '%s.qss' % style_sheet_name), 'rb') as f:
+ b = f.read()
+ enc = chardet.detect(b)
+ if enc.get('encoding') is not None:
+ s = b.decode(enc['encoding'])
+ style_sheet = s
+ else:
+ logger.fatal("加载样式表%s失败!" % style_sheet_name)
+ return style_sheet
+
+ def init_toolbar_tab(self):
+ """
+ 初始化工具栏的选项卡
+ Returns:
+
+ """
+ from widgets import TopToolBar
+ ttb = TopToolBar()
+ ttb.setObjectName('tab_bar_for_tool_bar')
+ self.addToolBar(ttb)
+ ttbr = TopToolBarRight()
+ ttbr.setObjectName('top_toolbar_right')
+ self.addToolBar(ttbr)
+ self.toolbar_hide_button = ttbr.hide_button
+ self.toolbar_hide_button.clicked.connect(self.on_toolbar_hide_button_pressed)
+ self.top_toolbar_tab = ttb
+
+ def on_toolbar_hide_button_pressed(self):
+ """
+ 当点击隐藏工具栏的按钮时
+ Returns:
+
+ """
+ current_toolbar = self.toolbars[self._current_toolbar_name]
+ if current_toolbar.isVisible():
+ current_toolbar.hide()
+
+ else:
+ current_toolbar.show()
+ self.refresh_toolbar_hide_button_status()
+
+ def refresh_toolbar_appearance(self, force_to_show: bool = True):
+ """
+ 刷新工具栏的外观
+ Args:
+ force_to_show:
+
+ Returns:
+
+ """
+ for k in self.toolbars.keys():
+ tab_button: 'QPushButton' = self.toolbars[k].tab_button
+ width = len(tab_button.text()) * 10 + 20
+ button: 'QPushButton' = self.toolbars[k].tab_button
+ if k != self._current_toolbar_name:
+ self.toolbars[k].hide()
+ button.setProperty('stat', 'unselected')
+ else:
+ if force_to_show:
+ self.toolbars[k].show()
+ button.setProperty('stat', 'selected')
+ button.setStyle(button.style())
+
+ def refresh_toolbar_hide_button_status(self):
+ """
+ 刷新工具栏隐藏按钮的状态(按钮的箭头向上还是向下)
+ Returns:
+
+ """
+ current_toolbar = self.toolbars[self._current_toolbar_name]
+ if not current_toolbar.isVisible():
+ self.toolbar_hide_button.setArrowType(Qt.DownArrow)
+ else:
+ self.toolbar_hide_button.setArrowType(Qt.UpArrow)
+
+ def on_toolbar_switch_button_clicked(self, name):
+ self.switch_toolbar(name, switch_only=False)
+
+ def switch_toolbar(self, name: str, switch_only: bool = True):
+ """
+ name:工具栏的名称
+ switch_only:如果为True,那么在调用时只会切换工具栏,当当前的工具栏名称与name相等时,
+ 调用多次不会改变当前工具栏的显示状态。为False的时候,若当前工具栏名称为name,那么就会改变显示状态,
+ 亦即原先显示的隐藏,原先隐藏的显示。
+ """
+ if self.toolbars.get(name) is not None:
+ if name == self._current_toolbar_name:
+ if not switch_only:
+ current_tb = self.toolbars[self._current_toolbar_name]
+ current_tb.setVisible(not current_tb.isVisible())
+ else:
+ self._current_toolbar_name = name
+ self.refresh_toolbar_appearance(force_to_show=True)
+ else:
+ raise Exception('toolbar tab \'%s\' is not defined!' % name)
+ self.refresh_toolbar_hide_button_status()
+
+ def save_layout(self):
+ """
+ 当关闭程序时保存布局
+ Returns:
+
+ """
+ cfg_dir = utils.get_user_config_dir()
+ layout_path = os.path.join(cfg_dir, 'layout.ini')
+ try:
+ with open(layout_path, 'wb') as f:
+ s = b'' # self.saveState()
+ f.write(s)
+ except FileNotFoundError:
+ logging.warning("file not found:" + layout_path)
+
+ def load_layout(self):
+ # p = os.path.join(Settings.get_instance().settings_path, 'layout.ini')
+
+ # if os.path.exists(p):
+ # with open(p, 'rb') as f:
+ # s = f.read()
+ # self.restoreState(s)
+
+ p = os.path.join(utils.get_root_dir(), 'resources', 'qss', 'standard.ini')
+ with open(p, 'rb') as f:
+ s = f.read()
+ self.restoreState(s)
+ self.refresh_view_configs()
+
+ def load_predefined_layout(self, layout_type: str = 'standard'):
+ layouts = {'standard': 'standard.ini'}
+ p = os.path.join(utils.get_root_dir(), 'resources', 'qss', layouts[layout_type])
+ if os.path.exists(p):
+ with open(p, 'rb') as f:
+ s = f.read()
+ self.restoreState(s)
+ for name in self.toolbars.keys():
+ self.toolbars[name].setVisible(False)
+ self.toolbars[self._current_toolbar_name].setVisible(True)
+ self.refresh_view_configs()
+
+ def add_widget_on_dock(self, dock_name: str,
+ widget: QWidget, text: str = '', side='left'):
+ """
+ 向界面添加控件
+ Args:
+ dock_name:
+ widget:
+ text:
+ side:
+
+ Returns:
+
+ """
+ from lib.ui.pmwidgets import PMDockWidget
+ text = pmlocale.translate(text)
+ dw = PMDockWidget(name=dock_name, text=text, parent=self)
+ dw.text = text
+ dw.setObjectName(dock_name)
+ dw.setWidget(widget)
+
+ if hasattr(widget, 'setup_ui'):
+ if hasattr(widget, 'show_directly'):
+ if widget.show_directly:
+ widget.setup_ui()
+ else:
+ self.setupui_tasks.append(widget.setup_ui)
+ else:
+ self.setupui_tasks.append(widget.setup_ui)
+
+ self.addDockWidget(self.dock_places[side], dw)
+ if dock_name not in self.dock_widgets.keys():
+ self.dock_widgets[dock_name] = dw
+ else:
+ raise Exception(
+ 'docked widget name: \'%s\' is already used!' %
+ dock_name)
+
+ self.refresh_view_configs()
+ return dw
+
+ def get_dock_widget(self, widget_name: str) -> 'PMDockWidget':
+ """
+ 获取停靠的控件
+ Args:
+ widget_name:
+
+ Returns:
+
+ """
+ dw = self.dock_widgets.get(widget_name)
+ if dw is None:
+ logging.debug('dockwidget named \'%s\' is not defined!' % widget_name)
+ return dw
+
+ def delete_dock_widget(self, widget_name: str):
+ """
+ 删除dock_widget。
+ Args:
+ widget_name:
+
+ Returns:
+
+ """
+ if self.dock_widgets.get(widget_name) is not None:
+ dock_widget = self.dock_widgets.pop(widget_name)
+ if hasattr(dock_widget.widget(), 'on_dock_widget_deleted'):
+ dock_widget.widget().on_dock_widget_deleted()
+ dock_widget.deleteLater()
+
+ def refresh_view_configs(self):
+ """
+ 刷新工具栏的可见状态,更新视图菜单
+ Returns:
+
+ """
+ from widgets import ActionWithMessage
+
+ home_toolbar: 'PMToolBarHome' = self.toolbars.get('toolbar_home')
+ # menu = home_toolbar.get_control_widget('view_config').menu()
+ # if menu is None:
+ menu = QMenu()
+ menu.triggered.connect(home_toolbar.process_visibility_actions)
+ for k in self.dock_widgets.keys():
+ a = ActionWithMessage(
+ text=self.dock_widgets[k].text,
+ parent=home_toolbar,
+ message=k)
+ a.setCheckable(True)
+ a.setChecked(self.dock_widgets[k].widget().isVisible())
+ menu.addAction(a)
+ menu.addSeparator()
+ a = ActionWithMessage(
+ text=self.tr('Normal View'),
+ parent=home_toolbar,
+ message='load_standard_layout')
+ menu.addAction(a)
+ a = ActionWithMessage(
+ text=self.tr('Lock UI Layout'),
+ parent=home_toolbar,
+ message='lock_layout')
+ a.setCheckable(True)
+ dock_title_visible = utils.get_settings_item_from_file("config.ini", "MAIN/DOCK_TITLEBAR_VISIBLE")
+ a.setChecked(not dock_title_visible)
+ menu.addAction(a)
+ home_toolbar.get_control_widget('view_config').setMenu(menu)
+ self._view_config_menu = menu
+
+ def on_main_window_shown(self):
+ """
+ 主界面显示时调用的方法
+ Returns:
+
+ """
+ self.refresh_view_configs()
+
+ def raise_dock_into_view(self, dock_name: str):
+ """
+ 将dockwidget提升到可见。
+ """
+ dock = self.get_dock_widget(dock_name)
+ if dock is not None:
+ dock.raise_into_view()
+ dock.setVisible(True)
+
+ def delete_temporary_dock_windows(self):
+ """
+ 删除临时性的窗口
+ """
+ keys = list(self.dock_widgets.keys())
+ for dock_name in keys:
+ dock = self.dock_widgets.get(dock_name)
+ if dock.widget().is_temporary():
+ self.dock_widgets.pop(dock_name)
+ logging.info('Closing and deleting temporary dock widget object:%s,named:%s, with widget :%s'
+ % (dock, dock_name, dock.widget()))
+ dock.close()
+ dock.deleteLater()
+
+ def bind_events(self):
+ """
+ 让全部的控件都绑定事件。
+
+ 在启动的最后调用这个绑定事件的方法,
+ 这样可以避免绑定的时候,由于对应控件未加载,发生找不到对应控件的错误
+ """
+ for k, w in self.dock_widgets.items():
+ if hasattr(w, 'bind_events'):
+ w.bind_events()
+ for k, w in self.toolbars.items():
+ if hasattr(w, 'bind_events'):
+ w.bind_events()
diff --git a/pyminer/lib/ui/pmwidgets/toplevel.py b/pyminer/lib/ui/pmwidgets/toplevel.py
new file mode 100644
index 0000000000000000000000000000000000000000..553149f94ef3252d21e4ae878e39c7bc8e9adb7c
--- /dev/null
+++ b/pyminer/lib/ui/pmwidgets/toplevel.py
@@ -0,0 +1,53 @@
+from PySide2.QtCore import Qt, QPoint
+from PySide2.QtGui import QFocusEvent, QMouseEvent
+from PySide2.QtWidgets import QDialog, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel
+
+from utils import get_main_window
+
+
+class TopLevelWidget(QDialog):
+ def __init__(self, parent=None):
+ super(TopLevelWidget, self).__init__(parent)
+ self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint) # 点击其他位置之后,可以隐藏。
+ self.setLayout(QVBoxLayout())
+ self.title_layout = QHBoxLayout()
+ self.layout().addLayout(self.title_layout)
+ b = QPushButton('x')
+ self.title_layout.addWidget(QLabel())
+ self.title_layout.addWidget(b)
+ b.setMaximumWidth(20)
+ b.clicked.connect(self.hide)
+ self.central_widget = None
+
+ # get_main_window().window_geometry_changed_signal.connect(self.refresh_position)
+ self.position: QPoint = None
+ self.width: int = 500
+ self.height = 500
+
+ def set_central_widget(self, widget: 'QWidget'):
+ self.layout().addWidget(widget)
+ self.central_widget = widget
+
+ def set_position(self, position: 'QPoint'):
+ self.position = position
+ self.refresh_position()
+
+ def set_width(self, width: int):
+ self.width = width
+
+ def refresh_position(self) -> None:
+ if self.position is None:
+ return
+ mw = get_main_window()
+ self.setGeometry(mw.geometry().x() + self.position.x(), mw.geometry().y() + self.position.y() + 16,
+ self.width, self.height)
+
+ def mousePressEvent(self, a0: QMouseEvent) -> None:
+ """
+ 在鼠标事件中,当鼠标点击弹出的窗口外部时,弹出窗口应当会隐藏。但如果不改写这一事件,在点击其他位置的时候,
+ 假如点击的位置时按钮,那么就会在隐藏窗口的同时触发按钮事件。如果这个按钮恰好可以控制该窗口的弹出和隐藏,
+ 那么就会发现窗口消失之后又立刻蹦了出来,这是因为窗口消失之后,它的出现事件又被触发了。
+ 设置为Qt.WA_NoMouseReplay,就是为了避免这种糟糕的状况。
+ """
+ self.setAttribute(Qt.WA_NoMouseReplay)
+ super().mousePressEvent(a0)
diff --git a/pyminer/lib/ui/ui_aboutme.py b/pyminer/lib/ui/ui_aboutme.py
new file mode 100644
index 0000000000000000000000000000000000000000..878455db4b2d2ee3b865a69d79e17fb0b31031b1
--- /dev/null
+++ b/pyminer/lib/ui/ui_aboutme.py
@@ -0,0 +1,186 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'ui_aboutme.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import *
+from PySide2.QtGui import *
+from PySide2.QtWidgets import *
+
+import pyqtsource_rc
+
+class Ui_Form(object):
+ def setupUi(self, Form):
+ if not Form.objectName():
+ Form.setObjectName(u"Form")
+ Form.resize(800, 600)
+ Form.setMinimumSize(QSize(800, 600))
+ Form.setMaximumSize(QSize(800, 600))
+ font = QFont()
+ font.setFamily(u"Albany AMT")
+ Form.setFont(font)
+ self.verticalLayout = QVBoxLayout(Form)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.horizontalLayout = QHBoxLayout()
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout.addItem(self.horizontalSpacer)
+
+ self.label_3 = QLabel(Form)
+ self.label_3.setObjectName(u"label_3")
+ self.label_3.setMinimumSize(QSize(85, 85))
+ self.label_3.setMaximumSize(QSize(85, 85))
+ self.label_3.setPixmap(QPixmap(u":/logo/icons/logo.png"))
+ self.label_3.setScaledContents(True)
+
+ self.horizontalLayout.addWidget(self.label_3)
+
+ self.label = QLabel(Form)
+ self.label.setObjectName(u"label")
+ font1 = QFont()
+ font1.setFamily(u"Microsoft YaHei UI")
+ font1.setPointSize(24)
+ font1.setBold(True)
+ font1.setWeight(75)
+ self.label.setFont(font1)
+ self.label.setScaledContents(True)
+ self.label.setAlignment(Qt.AlignCenter)
+
+ self.horizontalLayout.addWidget(self.label)
+
+ self.label_2 = QLabel(Form)
+ self.label_2.setObjectName(u"label_2")
+ font2 = QFont()
+ font2.setFamily(u"\u9ed1\u4f53")
+ self.label_2.setFont(font2)
+ self.label_2.setAlignment(Qt.AlignBottom|Qt.AlignHCenter)
+
+ self.horizontalLayout.addWidget(self.label_2)
+
+ self.label_version_show = QLabel(Form)
+ self.label_version_show.setObjectName(u"label_version_show")
+ self.label_version_show.setFont(font2)
+ self.label_version_show.setAlignment(Qt.AlignBottom|Qt.AlignHCenter)
+
+ self.horizontalLayout.addWidget(self.label_version_show)
+
+ self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout.addItem(self.horizontalSpacer_2)
+
+
+ self.verticalLayout.addLayout(self.horizontalLayout)
+
+ self.tabWidget = QTabWidget(Form)
+ self.tabWidget.setObjectName(u"tabWidget")
+ font3 = QFont()
+ font3.setFamily(u"\u9ed1\u4f53")
+ font3.setPointSize(11)
+ self.tabWidget.setFont(font3)
+ self.tab = QWidget()
+ self.tab.setObjectName(u"tab")
+ self.verticalLayout_2 = QVBoxLayout(self.tab)
+ self.verticalLayout_2.setSpacing(0)
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
+ self.textedit_about = QTextEdit(self.tab)
+ self.textedit_about.setObjectName(u"textedit_about")
+ self.textedit_about.setFont(font3)
+ self.textedit_about.setFrameShape(QFrame.NoFrame)
+
+ self.verticalLayout_2.addWidget(self.textedit_about)
+
+ self.tabWidget.addTab(self.tab, "")
+ self.tab_2 = QWidget()
+ self.tab_2.setObjectName(u"tab_2")
+ self.verticalLayout_3 = QVBoxLayout(self.tab_2)
+ self.verticalLayout_3.setSpacing(0)
+ self.verticalLayout_3.setObjectName(u"verticalLayout_3")
+ self.verticalLayout_3.setContentsMargins(0, 0, 0, 0)
+ self.feedback = QPlainTextEdit(self.tab_2)
+ self.feedback.setObjectName(u"feedback")
+ font4 = QFont()
+ font4.setFamily(u"Microsoft YaHei UI")
+ font4.setPointSize(11)
+ self.feedback.setFont(font4)
+ self.feedback.setFrameShape(QFrame.NoFrame)
+
+ self.verticalLayout_3.addWidget(self.feedback)
+
+ self.tabWidget.addTab(self.tab_2, "")
+ self.tab_3 = QWidget()
+ self.tab_3.setObjectName(u"tab_3")
+ self.verticalLayout_4 = QVBoxLayout(self.tab_3)
+ self.verticalLayout_4.setSpacing(0)
+ self.verticalLayout_4.setObjectName(u"verticalLayout_4")
+ self.verticalLayout_4.setContentsMargins(0, 0, 0, 0)
+ self.plainTextEdit_2 = QPlainTextEdit(self.tab_3)
+ self.plainTextEdit_2.setObjectName(u"plainTextEdit_2")
+ self.plainTextEdit_2.setFont(font3)
+ self.plainTextEdit_2.setFrameShape(QFrame.NoFrame)
+
+ self.verticalLayout_4.addWidget(self.plainTextEdit_2)
+
+ self.tabWidget.addTab(self.tab_3, "")
+ self.tab_4 = QWidget()
+ self.tab_4.setObjectName(u"tab_4")
+ self.horizontalLayout_2 = QHBoxLayout(self.tab_4)
+ self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
+ self.label_4 = QLabel(self.tab_4)
+ self.label_4.setObjectName(u"label_4")
+ self.label_4.setPixmap(QPixmap(u":/images/images/weixin.png"))
+ self.label_4.setScaledContents(True)
+
+ self.horizontalLayout_2.addWidget(self.label_4)
+
+ self.label_5 = QLabel(self.tab_4)
+ self.label_5.setObjectName(u"label_5")
+ self.label_5.setPixmap(QPixmap(u":/images/images/zhifubao.png"))
+ self.label_5.setScaledContents(True)
+
+ self.horizontalLayout_2.addWidget(self.label_5)
+
+ self.tabWidget.addTab(self.tab_4, "")
+
+ self.verticalLayout.addWidget(self.tabWidget)
+
+
+ self.retranslateUi(Form)
+
+ self.tabWidget.setCurrentIndex(0)
+
+
+ QMetaObject.connectSlotsByName(Form)
+ # setupUi
+
+ def retranslateUi(self, Form):
+ Form.setWindowTitle(QCoreApplication.translate("Form", u"About", None))
+ self.label_3.setText("")
+ self.label.setText(QCoreApplication.translate("Form", u"PyMiner", None))
+ self.label_2.setText(QCoreApplication.translate("Form", u"Version:", None))
+ self.label_version_show.setText(QCoreApplication.translate("Form", u"v2.0 Beta", None))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), QCoreApplication.translate("Form", u"About", None))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), QCoreApplication.translate("Form", u"System", None))
+ self.plainTextEdit_2.setPlainText(QCoreApplication.translate("Form", u"\u4faf\u5c55\u610f\n"
+"py2cn\n"
+"Junruoyu-Zheng\n"
+"\u5fc3\u968f\u98ce\n"
+"nihk\n"
+"cl-jiang\n"
+"Irony\n"
+"\u51b0\u4e2d\u706b\n"
+"houxinluo\n"
+"\u5f00\u59cb\u8bf4\u6545\u4e8b\n"
+"...", None))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), QCoreApplication.translate("Form", u"Credit", None))
+ self.label_4.setText("")
+ self.label_5.setText("")
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_4), QCoreApplication.translate("Form", u"Donate", None))
+ # retranslateUi
+
diff --git a/pyminer/lib/ui/ui_aboutme.ui b/pyminer/lib/ui/ui_aboutme.ui
new file mode 100644
index 0000000000000000000000000000000000000000..747bd4a4577215361299eb1614f077c6b0daef82
--- /dev/null
+++ b/pyminer/lib/ui/ui_aboutme.ui
@@ -0,0 +1,310 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 800
+ 600
+
+
+
+
+ 800
+ 600
+
+
+
+
+ 800
+ 600
+
+
+
+
+ Albany AMT
+
+
+
+ About
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+ 85
+ 85
+
+
+
+
+ 85
+ 85
+
+
+
+
+
+
+ :/logo/icons/logo.png
+
+
+ true
+
+
+
+ -
+
+
+
+ Microsoft YaHei UI
+ 24
+ 75
+ true
+
+
+
+ PyMiner
+
+
+ true
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+
+ 黑体
+
+
+
+ Version:
+
+
+ Qt::AlignBottom|Qt::AlignHCenter
+
+
+
+ -
+
+
+
+ 黑体
+
+
+
+ v2.0 Beta
+
+
+ Qt::AlignBottom|Qt::AlignHCenter
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+
+ 黑体
+ 11
+
+
+
+ 0
+
+
+
+ About
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 黑体
+ 11
+
+
+
+ QFrame::NoFrame
+
+
+
+
+
+
+
+ System
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+
+ Microsoft YaHei UI
+ 11
+
+
+
+ QFrame::NoFrame
+
+
+
+
+
+
+
+ Credit
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+
+ 黑体
+ 11
+
+
+
+ QFrame::NoFrame
+
+
+ 侯展意
+py2cn
+Junruoyu-Zheng
+心随风
+nihk
+cl-jiang
+Irony
+冰中火
+houxinluo
+开始说故事
+...
+
+
+
+
+
+
+
+ Donate
+
+
+ -
+
+
+
+
+
+ :/images/images/weixin.png
+
+
+ true
+
+
+
+ -
+
+
+
+
+
+ :/images/images/zhifubao.png
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pyminer/lib/ui/ui_appstore.py b/pyminer/lib/ui/ui_appstore.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ab438a184be34c181069127cbcafeb5e72dd12d
--- /dev/null
+++ b/pyminer/lib/ui/ui_appstore.py
@@ -0,0 +1,154 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'ui_appstore.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import *
+from PySide2.QtGui import *
+from PySide2.QtWidgets import *
+
+import pyqtsource_rc
+
+class Ui_Form(object):
+ def setupUi(self, Form):
+ if not Form.objectName():
+ Form.setObjectName(u"Form")
+ Form.resize(1300, 809)
+ Form.setStyleSheet(u"")
+ self.verticalLayout = QVBoxLayout(Form)
+ self.verticalLayout.setSpacing(0)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.verticalLayout.setContentsMargins(0, 0, 0, 0)
+ self.widget = QWidget(Form)
+ self.widget.setObjectName(u"widget")
+ self.widget.setMaximumSize(QSize(16777215, 50))
+ self.widget.setStyleSheet(u"background-color: rgb(49, 70,95);")
+ self.horizontalLayout = QHBoxLayout(self.widget)
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
+ self.horizontalSpacer_3 = QSpacerItem(849, 5, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout.addItem(self.horizontalSpacer_3)
+
+ self.toolButton_help = QToolButton(self.widget)
+ self.toolButton_help.setObjectName(u"toolButton_help")
+ self.toolButton_help.setStyleSheet(u"color: rgb(255, 255, 255);")
+ self.toolButton_help.setAutoRaise(True)
+
+ self.horizontalLayout.addWidget(self.toolButton_help)
+
+ self.line = QFrame(self.widget)
+ self.line.setObjectName(u"line")
+ self.line.setStyleSheet(u"color: rgb(255, 255, 255);")
+ self.line.setFrameShape(QFrame.VLine)
+ self.line.setFrameShadow(QFrame.Sunken)
+
+ self.horizontalLayout.addWidget(self.line)
+
+ self.toolButton_manage = QToolButton(self.widget)
+ self.toolButton_manage.setObjectName(u"toolButton_manage")
+ self.toolButton_manage.setStyleSheet(u"color: rgb(255, 255, 255);")
+ self.toolButton_manage.setAutoRaise(True)
+
+ self.horizontalLayout.addWidget(self.toolButton_manage)
+
+
+ self.verticalLayout.addWidget(self.widget)
+
+ self.widget_3 = QWidget(Form)
+ self.widget_3.setObjectName(u"widget_3")
+ self.widget_3.setMaximumSize(QSize(16777215, 50))
+ self.widget_3.setStyleSheet(u"background-color: rgb(49, 70, 95);")
+ self.horizontalLayout_3 = QHBoxLayout(self.widget_3)
+ self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
+ self.horizontalLayout_3.setContentsMargins(0, 0, 0, 0)
+ self.toolButton_4 = QToolButton(self.widget_3)
+ self.toolButton_4.setObjectName(u"toolButton_4")
+ self.toolButton_4.setEnabled(False)
+ icon = QIcon()
+ icon.addFile(u":/color/theme/default/icons/useLeftAll.svg", QSize(), QIcon.Normal, QIcon.Off)
+ self.toolButton_4.setIcon(icon)
+ self.toolButton_4.setAutoRaise(True)
+
+ self.horizontalLayout_3.addWidget(self.toolButton_4)
+
+ self.toolButton_5 = QToolButton(self.widget_3)
+ self.toolButton_5.setObjectName(u"toolButton_5")
+ icon1 = QIcon()
+ icon1.addFile(u":/color/theme/default/icons/mIconModelLayer.svg", QSize(), QIcon.Normal, QIcon.Off)
+ self.toolButton_5.setIcon(icon1)
+ self.toolButton_5.setIconSize(QSize(35, 35))
+ self.toolButton_5.setAutoRaise(True)
+
+ self.horizontalLayout_3.addWidget(self.toolButton_5)
+
+ self.label = QLabel(self.widget_3)
+ self.label.setObjectName(u"label")
+ font = QFont()
+ font.setFamily(u"Microsoft YaHei UI")
+ font.setPointSize(15)
+ self.label.setFont(font)
+ self.label.setStyleSheet(u"color: rgb(255, 255, 255);")
+
+ self.horizontalLayout_3.addWidget(self.label)
+
+ self.horizontalSpacer_4 = QSpacerItem(436, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_3.addItem(self.horizontalSpacer_4)
+
+ self.lineEdit = QLineEdit(self.widget_3)
+ self.lineEdit.setObjectName(u"lineEdit")
+ self.lineEdit.setMaximumSize(QSize(500, 35))
+ self.lineEdit.setStyleSheet(u"background-color: rgb(255, 255, 255);border:0px groove gray;border-radius:5px;padding:2px 4px")
+
+ self.horizontalLayout_3.addWidget(self.lineEdit)
+
+ self.toolButton_3 = QToolButton(self.widget_3)
+ self.toolButton_3.setObjectName(u"toolButton_3")
+ self.toolButton_3.setMaximumSize(QSize(40, 40))
+ icon2 = QIcon()
+ icon2.addFile(u":/color/theme/default/icons/search.svg", QSize(), QIcon.Normal, QIcon.Off)
+ self.toolButton_3.setIcon(icon2)
+ self.toolButton_3.setIconSize(QSize(30, 30))
+ self.toolButton_3.setAutoRaise(True)
+
+ self.horizontalLayout_3.addWidget(self.toolButton_3)
+
+
+ self.verticalLayout.addWidget(self.widget_3)
+
+ self.widget_2 = QWidget(Form)
+ self.widget_2.setObjectName(u"widget_2")
+ self.verticalLayout_2 = QVBoxLayout(self.widget_2)
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
+ self.horizontalLayout_2 = QHBoxLayout()
+ self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
+
+ self.verticalLayout_2.addLayout(self.horizontalLayout_2)
+
+
+ self.verticalLayout.addWidget(self.widget_2)
+
+
+ self.retranslateUi(Form)
+
+ QMetaObject.connectSlotsByName(Form)
+ # setupUi
+
+ def retranslateUi(self, Form):
+ Form.setWindowTitle(QCoreApplication.translate("Form", u"PyMiner App Store", None))
+ self.toolButton_help.setText(QCoreApplication.translate("Form", u"Help", None))
+ self.toolButton_manage.setText(QCoreApplication.translate("Form", u"Manage Apps", None))
+ self.toolButton_4.setText(QCoreApplication.translate("Form", u"...", None))
+ self.toolButton_5.setText(QCoreApplication.translate("Form", u"...", None))
+ self.label.setText(QCoreApplication.translate("Form", u"App Store", None))
+ self.lineEdit.setPlaceholderText(QCoreApplication.translate("Form", u"Search in sotre...", None))
+ self.toolButton_3.setText(QCoreApplication.translate("Form", u"...", None))
+ # retranslateUi
+
diff --git a/pyminer/lib/ui/ui_appstore.ui b/pyminer/lib/ui/ui_appstore.ui
new file mode 100644
index 0000000000000000000000000000000000000000..dcd075d085605e3fe68bc1eaa849e91408a77ca8
--- /dev/null
+++ b/pyminer/lib/ui/ui_appstore.ui
@@ -0,0 +1,273 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 1300
+ 809
+
+
+
+ PyMiner App Store
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+
+ 16777215
+ 50
+
+
+
+ background-color: rgb(49, 70,95);
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 849
+ 5
+
+
+
+
+ -
+
+
+ color: rgb(255, 255, 255);
+
+
+ Help
+
+
+ true
+
+
+
+ -
+
+
+ color: rgb(255, 255, 255);
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+ color: rgb(255, 255, 255);
+
+
+ Manage Apps
+
+
+ true
+
+
+
+
+
+
+ -
+
+
+
+ 16777215
+ 50
+
+
+
+ background-color: rgb(49, 70, 95);
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ false
+
+
+ ...
+
+
+
+ :/color/theme/default/icons/useLeftAll.svg:/color/theme/default/icons/useLeftAll.svg
+
+
+ true
+
+
+
+ -
+
+
+ ...
+
+
+
+ :/color/theme/default/icons/mIconModelLayer.svg:/color/theme/default/icons/mIconModelLayer.svg
+
+
+
+ 35
+ 35
+
+
+
+ true
+
+
+
+ -
+
+
+
+ Microsoft YaHei UI
+ 15
+
+
+
+ color: rgb(255, 255, 255);
+
+
+ App Store
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 436
+ 20
+
+
+
+
+ -
+
+
+
+ 500
+ 35
+
+
+
+ background-color: rgb(255, 255, 255);border:0px groove gray;border-radius:5px;padding:2px 4px
+
+
+ Search in sotre...
+
+
+
+ -
+
+
+
+ 40
+ 40
+
+
+
+ ...
+
+
+
+ :/color/theme/default/icons/search.svg:/color/theme/default/icons/search.svg
+
+
+
+ 30
+ 30
+
+
+
+ true
+
+
+
+
+
+
+ -
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pyminer/lib/ui/ui_check_update.py b/pyminer/lib/ui/ui_check_update.py
new file mode 100644
index 0000000000000000000000000000000000000000..f1eb9da61aeeb10569b18d43c8f5d20d5dd9c2af
--- /dev/null
+++ b/pyminer/lib/ui/ui_check_update.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'ui_check_update.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import *
+from PySide2.QtGui import *
+from PySide2.QtWidgets import *
+
+
+class Ui_Dialog(object):
+ def setupUi(self, Dialog):
+ if not Dialog.objectName():
+ Dialog.setObjectName(u"Dialog")
+ Dialog.resize(546, 173)
+ self.verticalLayout_2 = QVBoxLayout(Dialog)
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.verticalLayout = QVBoxLayout()
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.label = QLabel(Dialog)
+ self.label.setObjectName(u"label")
+
+ self.verticalLayout.addWidget(self.label)
+
+ self.horizontalLayout = QHBoxLayout()
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.button_detail = QPushButton(Dialog)
+ self.button_detail.setObjectName(u"button_detail")
+ self.button_detail.setMaximumSize(QSize(20, 16777215))
+
+ self.horizontalLayout.addWidget(self.button_detail)
+
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout.addItem(self.horizontalSpacer)
+
+
+ self.verticalLayout.addLayout(self.horizontalLayout)
+
+ self.table = QTableWidget(Dialog)
+ self.table.setObjectName(u"table")
+
+ self.verticalLayout.addWidget(self.table)
+
+ self.horizontalLayout_5 = QHBoxLayout()
+ self.horizontalLayout_5.setObjectName(u"horizontalLayout_5")
+ self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_5.addItem(self.horizontalSpacer_2)
+
+ self.button_confirm = QPushButton(Dialog)
+ self.button_confirm.setObjectName(u"button_confirm")
+
+ self.horizontalLayout_5.addWidget(self.button_confirm)
+
+ self.button_close = QPushButton(Dialog)
+ self.button_close.setObjectName(u"button_close")
+
+ self.horizontalLayout_5.addWidget(self.button_close)
+
+
+ self.verticalLayout.addLayout(self.horizontalLayout_5)
+
+
+ self.verticalLayout_2.addLayout(self.verticalLayout)
+
+
+ self.retranslateUi(Dialog)
+
+ QMetaObject.connectSlotsByName(Dialog)
+ # setupUi
+
+ def retranslateUi(self, Dialog):
+ Dialog.setWindowTitle(QCoreApplication.translate("Dialog", u"\u66f4\u65b0\u68c0\u6d4b\u7a0b\u5e8f", None))
+ self.label.setText("")
+ self.button_detail.setText(QCoreApplication.translate("Dialog", u"+", None))
+ self.button_confirm.setText(QCoreApplication.translate("Dialog", u"\u66f4\u65b0", None))
+ self.button_close.setText(QCoreApplication.translate("Dialog", u"\u4e0b\u6b21\u518d\u8bf4", None))
+ # retranslateUi
+
diff --git a/pyminer/lib/ui/ui_check_update.ui b/pyminer/lib/ui/ui_check_update.ui
new file mode 100644
index 0000000000000000000000000000000000000000..6fcaa5e03bbc5e6cd78756cc36c19e08bd7d107b
--- /dev/null
+++ b/pyminer/lib/ui/ui_check_update.ui
@@ -0,0 +1,96 @@
+
+
+ Dialog
+
+
+
+ 0
+ 0
+ 546
+ 173
+
+
+
+ 更新检测程序
+
+
+ -
+
+
-
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 20
+ 16777215
+
+
+
+ +
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ 更新
+
+
+
+ -
+
+
+ 下次再说
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pyminer/lib/ui/ui_data_normal.py b/pyminer/lib/ui/ui_data_normal.py
new file mode 100644
index 0000000000000000000000000000000000000000..0579ff9ab138f5a46ba46d0ba05f8e93185392b2
--- /dev/null
+++ b/pyminer/lib/ui/ui_data_normal.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'ui_data_normal.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import *
+from PySide2.QtGui import *
+from PySide2.QtWidgets import *
+
+
+class Ui_Dialog(object):
+ def setupUi(self, Dialog):
+ if not Dialog.objectName():
+ Dialog.setObjectName(u"Dialog")
+ Dialog.resize(458, 405)
+ self.verticalLayout = QVBoxLayout(Dialog)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.horizontalLayout = QHBoxLayout()
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.tableWidget = QTableWidget(Dialog)
+ self.tableWidget.setObjectName(u"tableWidget")
+
+ self.horizontalLayout.addWidget(self.tableWidget)
+
+ self.formLayout = QFormLayout()
+ self.formLayout.setObjectName(u"formLayout")
+ self.label = QLabel(Dialog)
+ self.label.setObjectName(u"label")
+
+ self.formLayout.setWidget(0, QFormLayout.LabelRole, self.label)
+
+ self.label_2 = QLabel(Dialog)
+ self.label_2.setObjectName(u"label_2")
+
+ self.formLayout.setWidget(1, QFormLayout.LabelRole, self.label_2)
+
+ self.label_3 = QLabel(Dialog)
+ self.label_3.setObjectName(u"label_3")
+
+ self.formLayout.setWidget(2, QFormLayout.LabelRole, self.label_3)
+
+ self.label_4 = QLabel(Dialog)
+ self.label_4.setObjectName(u"label_4")
+
+ self.formLayout.setWidget(3, QFormLayout.LabelRole, self.label_4)
+
+ self.spinBox_precison = QSpinBox(Dialog)
+ self.spinBox_precison.setObjectName(u"spinBox_precison")
+ self.spinBox_precison.setValue(2)
+
+ self.formLayout.setWidget(3, QFormLayout.FieldRole, self.spinBox_precison)
+
+ self.spinBox_count = QSpinBox(Dialog)
+ self.spinBox_count.setObjectName(u"spinBox_count")
+ self.spinBox_count.setValue(30)
+
+ self.formLayout.setWidget(2, QFormLayout.FieldRole, self.spinBox_count)
+
+ self.doubleSpinBox_std = QDoubleSpinBox(Dialog)
+ self.doubleSpinBox_std.setObjectName(u"doubleSpinBox_std")
+ self.doubleSpinBox_std.setValue(1.000000000000000)
+
+ self.formLayout.setWidget(1, QFormLayout.FieldRole, self.doubleSpinBox_std)
+
+ self.doubleSpinBox_mean = QDoubleSpinBox(Dialog)
+ self.doubleSpinBox_mean.setObjectName(u"doubleSpinBox_mean")
+
+ self.formLayout.setWidget(0, QFormLayout.FieldRole, self.doubleSpinBox_mean)
+
+
+ self.horizontalLayout.addLayout(self.formLayout)
+
+
+ self.verticalLayout.addLayout(self.horizontalLayout)
+
+ self.buttonBox = QDialogButtonBox(Dialog)
+ self.buttonBox.setObjectName(u"buttonBox")
+ self.buttonBox.setOrientation(Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok)
+
+ self.verticalLayout.addWidget(self.buttonBox)
+
+
+ self.retranslateUi(Dialog)
+
+ QMetaObject.connectSlotsByName(Dialog)
+ # setupUi
+
+ def retranslateUi(self, Dialog):
+ Dialog.setWindowTitle(QCoreApplication.translate("Dialog", u"\u6570\u636e\u751f\u6210-\u6b63\u6001\u5206\u5e03", None))
+ self.label.setText(QCoreApplication.translate("Dialog", u"\u5747\u503c:", None))
+ self.label_2.setText(QCoreApplication.translate("Dialog", u"\u6807\u51c6\u5dee:", None))
+ self.label_3.setText(QCoreApplication.translate("Dialog", u"\u6570\u91cf:", None))
+ self.label_4.setText(QCoreApplication.translate("Dialog", u"\u7cbe\u5ea6:", None))
+ # retranslateUi
+
diff --git a/pyminer/lib/ui/ui_data_normal.ui b/pyminer/lib/ui/ui_data_normal.ui
new file mode 100644
index 0000000000000000000000000000000000000000..86cef4e3f411f0fc79dc429be4f7a97e0e1f13a2
--- /dev/null
+++ b/pyminer/lib/ui/ui_data_normal.ui
@@ -0,0 +1,94 @@
+
+
+ Dialog
+
+
+
+ 0
+ 0
+ 458
+ 405
+
+
+
+ 数据生成-正态分布
+
+
+ -
+
+
-
+
+
+ -
+
+
-
+
+
+ 均值:
+
+
+
+ -
+
+
+ 标准差:
+
+
+
+ -
+
+
+ 数量:
+
+
+
+ -
+
+
+ 精度:
+
+
+
+ -
+
+
+ 2
+
+
+
+ -
+
+
+ 30
+
+
+
+ -
+
+
+ 1.000000000000000
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
diff --git a/pyminer/lib/ui/ui_first_form.py b/pyminer/lib/ui/ui_first_form.py
new file mode 100644
index 0000000000000000000000000000000000000000..8d51960685e1eebbdce1a2131185b57eb2760f8b
--- /dev/null
+++ b/pyminer/lib/ui/ui_first_form.py
@@ -0,0 +1,444 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'ui_first_form.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import *
+from PySide2.QtGui import *
+from PySide2.QtWidgets import *
+
+import pyqtsource_rc
+
+class Ui_Form(object):
+ def setupUi(self, Form):
+ if not Form.objectName():
+ Form.setObjectName(u"Form")
+ Form.resize(620, 580)
+ Form.setMinimumSize(QSize(620, 580))
+ Form.setMaximumSize(QSize(620, 580))
+ self.verticalLayout = QVBoxLayout(Form)
+ self.verticalLayout.setSpacing(0)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.verticalLayout.setContentsMargins(0, 0, 0, 0)
+ self.widget_2 = QWidget(Form)
+ self.widget_2.setObjectName(u"widget_2")
+ self.widget_2.setStyleSheet(u"")
+ self.horizontalLayout = QHBoxLayout(self.widget_2)
+ self.horizontalLayout.setSpacing(0)
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
+ self.label_3 = QLabel(self.widget_2)
+ self.label_3.setObjectName(u"label_3")
+ font = QFont()
+ font.setFamily(u"Consolas")
+ font.setPointSize(28)
+ self.label_3.setFont(font)
+ self.label_3.setPixmap(QPixmap(u":/images/images/bg.png"))
+ self.label_3.setScaledContents(True)
+
+ self.horizontalLayout.addWidget(self.label_3)
+
+
+ self.verticalLayout.addWidget(self.widget_2)
+
+ self.widget = QWidget(Form)
+ self.widget.setObjectName(u"widget")
+ self.widget.setMinimumSize(QSize(0, 270))
+ self.widget.setMaximumSize(QSize(16777215, 270))
+ self.widget.setStyleSheet(u"background-color: rgb(33, 33, 33);")
+ self.horizontalLayout_2 = QHBoxLayout(self.widget)
+ self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
+ self.verticalLayout_6 = QVBoxLayout()
+ self.verticalLayout_6.setSpacing(5)
+ self.verticalLayout_6.setObjectName(u"verticalLayout_6")
+ self.verticalLayout_6.setContentsMargins(0, -1, -1, -1)
+ self.horizontalLayout_7 = QHBoxLayout()
+ self.horizontalLayout_7.setSpacing(0)
+ self.horizontalLayout_7.setObjectName(u"horizontalLayout_7")
+ self.label_24 = QLabel(self.widget)
+ self.label_24.setObjectName(u"label_24")
+ self.label_24.setMinimumSize(QSize(20, 0))
+ self.label_24.setMaximumSize(QSize(20, 16777215))
+ font1 = QFont()
+ font1.setPointSize(9)
+ self.label_24.setFont(font1)
+ self.label_24.setStyleSheet(u"color: rgb(229, 229, 229);")
+
+ self.horizontalLayout_7.addWidget(self.label_24)
+
+ self.label_23 = QLabel(self.widget)
+ self.label_23.setObjectName(u"label_23")
+ self.label_23.setFont(font1)
+ self.label_23.setStyleSheet(u"color: rgb(229, 229, 229);")
+
+ self.horizontalLayout_7.addWidget(self.label_23)
+
+
+ self.verticalLayout_6.addLayout(self.horizontalLayout_7)
+
+ self.widget_3 = QWidget(self.widget)
+ self.widget_3.setObjectName(u"widget_3")
+ self.widget_3.setMinimumSize(QSize(300, 0))
+ self.verticalLayout_2 = QVBoxLayout(self.widget_3)
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.horizontalLayout_4 = QHBoxLayout()
+ self.horizontalLayout_4.setSpacing(0)
+ self.horizontalLayout_4.setObjectName(u"horizontalLayout_4")
+ self.toolButton_2 = QToolButton(self.widget_3)
+ self.toolButton_2.setObjectName(u"toolButton_2")
+ icon = QIcon()
+ icon.addFile(u":/color/theme/default/icons/python.svg", QSize(), QIcon.Normal, QIcon.Off)
+ self.toolButton_2.setIcon(icon)
+ self.toolButton_2.setIconSize(QSize(18, 18))
+
+ self.horizontalLayout_4.addWidget(self.toolButton_2)
+
+ self.btn_open_python = QToolButton(self.widget_3)
+ self.btn_open_python.setObjectName(u"btn_open_python")
+ self.btn_open_python.setStyleSheet(u"color: rgb(255, 255, 255);")
+ self.btn_open_python.setAutoRaise(True)
+
+ self.horizontalLayout_4.addWidget(self.btn_open_python)
+
+ self.horizontalSpacer_4 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_4.addItem(self.horizontalSpacer_4)
+
+
+ self.verticalLayout_2.addLayout(self.horizontalLayout_4)
+
+ self.horizontalLayout_5 = QHBoxLayout()
+ self.horizontalLayout_5.setSpacing(0)
+ self.horizontalLayout_5.setObjectName(u"horizontalLayout_5")
+ self.toolButton_3 = QToolButton(self.widget_3)
+ self.toolButton_3.setObjectName(u"toolButton_3")
+ icon1 = QIcon()
+ icon1.addFile(u":/color/theme/default/icons/csv.svg", QSize(), QIcon.Normal, QIcon.Off)
+ self.toolButton_3.setIcon(icon1)
+ self.toolButton_3.setIconSize(QSize(18, 18))
+
+ self.horizontalLayout_5.addWidget(self.toolButton_3)
+
+ self.btn_open_csv = QToolButton(self.widget_3)
+ self.btn_open_csv.setObjectName(u"btn_open_csv")
+ self.btn_open_csv.setStyleSheet(u"color: rgb(255, 255, 255);")
+ self.btn_open_csv.setAutoRaise(True)
+
+ self.horizontalLayout_5.addWidget(self.btn_open_csv)
+
+ self.horizontalSpacer_5 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_5.addItem(self.horizontalSpacer_5)
+
+
+ self.verticalLayout_2.addLayout(self.horizontalLayout_5)
+
+ self.horizontalLayout_6 = QHBoxLayout()
+ self.horizontalLayout_6.setSpacing(0)
+ self.horizontalLayout_6.setObjectName(u"horizontalLayout_6")
+ self.toolButton_4 = QToolButton(self.widget_3)
+ self.toolButton_4.setObjectName(u"toolButton_4")
+ icon2 = QIcon()
+ icon2.addFile(u":/color/theme/default/icons/excel.svg", QSize(), QIcon.Normal, QIcon.Off)
+ self.toolButton_4.setIcon(icon2)
+ self.toolButton_4.setIconSize(QSize(18, 18))
+
+ self.horizontalLayout_6.addWidget(self.toolButton_4)
+
+ self.btn_open_excel = QToolButton(self.widget_3)
+ self.btn_open_excel.setObjectName(u"btn_open_excel")
+ self.btn_open_excel.setStyleSheet(u"color: rgb(255, 255, 255);")
+ self.btn_open_excel.setAutoRaise(True)
+
+ self.horizontalLayout_6.addWidget(self.btn_open_excel)
+
+ self.horizontalSpacer_6 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_6.addItem(self.horizontalSpacer_6)
+
+
+ self.verticalLayout_2.addLayout(self.horizontalLayout_6)
+
+ self.horizontalLayout_14 = QHBoxLayout()
+ self.horizontalLayout_14.setSpacing(0)
+ self.horizontalLayout_14.setObjectName(u"horizontalLayout_14")
+ self.toolButton_10 = QToolButton(self.widget_3)
+ self.toolButton_10.setObjectName(u"toolButton_10")
+ icon3 = QIcon()
+ icon3.addFile(u":/color/theme/default/icons/E-matlab.svg", QSize(), QIcon.Normal, QIcon.Off)
+ self.toolButton_10.setIcon(icon3)
+ self.toolButton_10.setIconSize(QSize(18, 18))
+
+ self.horizontalLayout_14.addWidget(self.toolButton_10)
+
+ self.btn_open_matlab = QToolButton(self.widget_3)
+ self.btn_open_matlab.setObjectName(u"btn_open_matlab")
+ self.btn_open_matlab.setStyleSheet(u"color: rgb(255, 255, 255);")
+ self.btn_open_matlab.setAutoRaise(True)
+
+ self.horizontalLayout_14.addWidget(self.btn_open_matlab)
+
+ self.horizontalSpacer_7 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_14.addItem(self.horizontalSpacer_7)
+
+
+ self.verticalLayout_2.addLayout(self.horizontalLayout_14)
+
+ self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
+
+ self.verticalLayout_2.addItem(self.verticalSpacer)
+
+
+ self.verticalLayout_6.addWidget(self.widget_3)
+
+ self.widget_6 = QWidget(self.widget)
+ self.widget_6.setObjectName(u"widget_6")
+ self.verticalLayout_5 = QVBoxLayout(self.widget_6)
+ self.verticalLayout_5.setObjectName(u"verticalLayout_5")
+ self.horizontalLayout_3 = QHBoxLayout()
+ self.horizontalLayout_3.setSpacing(0)
+ self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
+ self.toolButton = QToolButton(self.widget_6)
+ self.toolButton.setObjectName(u"toolButton")
+ icon4 = QIcon()
+ icon4.addFile(u":/color/theme/default/icons/open_folder.svg", QSize(), QIcon.Normal, QIcon.Off)
+ self.toolButton.setIcon(icon4)
+ self.toolButton.setIconSize(QSize(18, 18))
+
+ self.horizontalLayout_3.addWidget(self.toolButton)
+
+ self.btn_open_folder = QToolButton(self.widget_6)
+ self.btn_open_folder.setObjectName(u"btn_open_folder")
+ self.btn_open_folder.setStyleSheet(u"color: rgb(255, 255, 255);")
+ self.btn_open_folder.setAutoRaise(True)
+
+ self.horizontalLayout_3.addWidget(self.btn_open_folder)
+
+ self.horizontalSpacer_8 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_3.addItem(self.horizontalSpacer_8)
+
+
+ self.verticalLayout_5.addLayout(self.horizontalLayout_3)
+
+
+ self.verticalLayout_6.addWidget(self.widget_6)
+
+
+ self.horizontalLayout_2.addLayout(self.verticalLayout_6)
+
+ self.verticalLayout_9 = QVBoxLayout()
+ self.verticalLayout_9.setSpacing(5)
+ self.verticalLayout_9.setObjectName(u"verticalLayout_9")
+ self.verticalLayout_9.setContentsMargins(0, -1, -1, -1)
+ self.horizontalLayout_8 = QHBoxLayout()
+ self.horizontalLayout_8.setSpacing(0)
+ self.horizontalLayout_8.setObjectName(u"horizontalLayout_8")
+ self.label_25 = QLabel(self.widget)
+ self.label_25.setObjectName(u"label_25")
+ self.label_25.setMinimumSize(QSize(20, 0))
+ self.label_25.setMaximumSize(QSize(20, 16777215))
+ self.label_25.setFont(font1)
+ self.label_25.setStyleSheet(u"color: rgb(229, 229, 229);")
+
+ self.horizontalLayout_8.addWidget(self.label_25)
+
+ self.label_26 = QLabel(self.widget)
+ self.label_26.setObjectName(u"label_26")
+ self.label_26.setFont(font1)
+ self.label_26.setStyleSheet(u"color: rgb(229, 229, 229);")
+
+ self.horizontalLayout_8.addWidget(self.label_26)
+
+
+ self.verticalLayout_9.addLayout(self.horizontalLayout_8)
+
+ self.widget_8 = QWidget(self.widget)
+ self.widget_8.setObjectName(u"widget_8")
+ self.verticalLayout_10 = QVBoxLayout(self.widget_8)
+ self.verticalLayout_10.setObjectName(u"verticalLayout_10")
+ self.horizontalLayout_9 = QHBoxLayout()
+ self.horizontalLayout_9.setSpacing(0)
+ self.horizontalLayout_9.setObjectName(u"horizontalLayout_9")
+ self.toolButton_5 = QToolButton(self.widget_8)
+ self.toolButton_5.setObjectName(u"toolButton_5")
+ icon5 = QIcon()
+ icon5.addFile(u":/color/theme/default/icons/website.svg", QSize(), QIcon.Normal, QIcon.Off)
+ self.toolButton_5.setIcon(icon5)
+ self.toolButton_5.setIconSize(QSize(18, 18))
+
+ self.horizontalLayout_9.addWidget(self.toolButton_5)
+
+ self.btn_manual = QToolButton(self.widget_8)
+ self.btn_manual.setObjectName(u"btn_manual")
+ self.btn_manual.setStyleSheet(u"color: rgb(255, 255, 255);")
+ self.btn_manual.setAutoRaise(True)
+
+ self.horizontalLayout_9.addWidget(self.btn_manual)
+
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_9.addItem(self.horizontalSpacer)
+
+
+ self.verticalLayout_10.addLayout(self.horizontalLayout_9)
+
+ self.horizontalLayout_10 = QHBoxLayout()
+ self.horizontalLayout_10.setSpacing(0)
+ self.horizontalLayout_10.setObjectName(u"horizontalLayout_10")
+ self.toolButton_6 = QToolButton(self.widget_8)
+ self.toolButton_6.setObjectName(u"toolButton_6")
+ self.toolButton_6.setIcon(icon5)
+ self.toolButton_6.setIconSize(QSize(18, 18))
+
+ self.horizontalLayout_10.addWidget(self.toolButton_6)
+
+ self.btn_website = QToolButton(self.widget_8)
+ self.btn_website.setObjectName(u"btn_website")
+ self.btn_website.setStyleSheet(u"color: rgb(255, 255, 255);")
+ self.btn_website.setAutoRaise(True)
+
+ self.horizontalLayout_10.addWidget(self.btn_website)
+
+ self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_10.addItem(self.horizontalSpacer_2)
+
+
+ self.verticalLayout_10.addLayout(self.horizontalLayout_10)
+
+ self.horizontalLayout_11 = QHBoxLayout()
+ self.horizontalLayout_11.setSpacing(0)
+ self.horizontalLayout_11.setObjectName(u"horizontalLayout_11")
+ self.toolButton_7 = QToolButton(self.widget_8)
+ self.toolButton_7.setObjectName(u"toolButton_7")
+ self.toolButton_7.setIcon(icon5)
+ self.toolButton_7.setIconSize(QSize(18, 18))
+
+ self.horizontalLayout_11.addWidget(self.toolButton_7)
+
+ self.btn_source = QToolButton(self.widget_8)
+ self.btn_source.setObjectName(u"btn_source")
+ self.btn_source.setStyleSheet(u"color: rgb(255, 255, 255);")
+ self.btn_source.setAutoRaise(True)
+
+ self.horizontalLayout_11.addWidget(self.btn_source)
+
+ self.horizontalSpacer_3 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_11.addItem(self.horizontalSpacer_3)
+
+
+ self.verticalLayout_10.addLayout(self.horizontalLayout_11)
+
+ self.verticalSpacer_3 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
+
+ self.verticalLayout_10.addItem(self.verticalSpacer_3)
+
+
+ self.verticalLayout_9.addWidget(self.widget_8)
+
+ self.widget_9 = QWidget(self.widget)
+ self.widget_9.setObjectName(u"widget_9")
+ self.verticalLayout_11 = QVBoxLayout(self.widget_9)
+ self.verticalLayout_11.setObjectName(u"verticalLayout_11")
+ self.horizontalLayout_12 = QHBoxLayout()
+ self.horizontalLayout_12.setSpacing(0)
+ self.horizontalLayout_12.setObjectName(u"horizontalLayout_12")
+ self.toolButton_8 = QToolButton(self.widget_9)
+ self.toolButton_8.setObjectName(u"toolButton_8")
+ self.toolButton_8.setIcon(icon5)
+ self.toolButton_8.setIconSize(QSize(18, 18))
+
+ self.horizontalLayout_12.addWidget(self.toolButton_8)
+
+ self.btn_member = QToolButton(self.widget_9)
+ self.btn_member.setObjectName(u"btn_member")
+ self.btn_member.setStyleSheet(u"color: rgb(255, 255, 255);")
+ self.btn_member.setAutoRaise(True)
+
+ self.horizontalLayout_12.addWidget(self.btn_member)
+
+ self.horizontalSpacer_10 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_12.addItem(self.horizontalSpacer_10)
+
+
+ self.verticalLayout_11.addLayout(self.horizontalLayout_12)
+
+ self.horizontalLayout_13 = QHBoxLayout()
+ self.horizontalLayout_13.setSpacing(0)
+ self.horizontalLayout_13.setObjectName(u"horizontalLayout_13")
+ self.toolButton_9 = QToolButton(self.widget_9)
+ self.toolButton_9.setObjectName(u"toolButton_9")
+ icon6 = QIcon()
+ icon6.addFile(u":/color/theme/default/icons/donate.svg", QSize(), QIcon.Normal, QIcon.Off)
+ self.toolButton_9.setIcon(icon6)
+ self.toolButton_9.setIconSize(QSize(18, 18))
+
+ self.horizontalLayout_13.addWidget(self.toolButton_9)
+
+ self.btn_donate = QToolButton(self.widget_9)
+ self.btn_donate.setObjectName(u"btn_donate")
+ self.btn_donate.setStyleSheet(u"color: rgb(255, 255, 255);")
+ self.btn_donate.setAutoRaise(True)
+
+ self.horizontalLayout_13.addWidget(self.btn_donate)
+
+ self.horizontalSpacer_9 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_13.addItem(self.horizontalSpacer_9)
+
+
+ self.verticalLayout_11.addLayout(self.horizontalLayout_13)
+
+
+ self.verticalLayout_9.addWidget(self.widget_9)
+
+
+ self.horizontalLayout_2.addLayout(self.verticalLayout_9)
+
+
+ self.verticalLayout.addWidget(self.widget)
+
+
+ self.retranslateUi(Form)
+
+ QMetaObject.connectSlotsByName(Form)
+ # setupUi
+
+ def retranslateUi(self, Form):
+ Form.setWindowTitle(QCoreApplication.translate("Form", u"Form", None))
+ self.label_3.setText("")
+ self.label_24.setText("")
+ self.label_23.setText(QCoreApplication.translate("Form", u"Open File", None))
+ self.toolButton_2.setText(QCoreApplication.translate("Form", u"...", None))
+ self.btn_open_python.setText(QCoreApplication.translate("Form", u"Python File", None))
+ self.toolButton_3.setText(QCoreApplication.translate("Form", u"...", None))
+ self.btn_open_csv.setText(QCoreApplication.translate("Form", u"CSV File", None))
+ self.toolButton_4.setText(QCoreApplication.translate("Form", u"...", None))
+ self.btn_open_excel.setText(QCoreApplication.translate("Form", u"Excel File", None))
+ self.toolButton_10.setText(QCoreApplication.translate("Form", u"...", None))
+ self.btn_open_matlab.setText(QCoreApplication.translate("Form", u"MATLAB File", None))
+ self.toolButton.setText(QCoreApplication.translate("Form", u"...", None))
+ self.btn_open_folder.setText(QCoreApplication.translate("Form", u"Open Folder...", None))
+ self.label_25.setText("")
+ self.label_26.setText(QCoreApplication.translate("Form", u"Quick Start", None))
+ self.toolButton_5.setText(QCoreApplication.translate("Form", u"...", None))
+ self.btn_manual.setText(QCoreApplication.translate("Form", u"Use Manual", None))
+ self.toolButton_6.setText(QCoreApplication.translate("Form", u"...", None))
+ self.btn_website.setText(QCoreApplication.translate("Form", u"Official Site", None))
+ self.toolButton_7.setText(QCoreApplication.translate("Form", u"...", None))
+ self.btn_source.setText(QCoreApplication.translate("Form", u"Gitee Repo", None))
+ self.toolButton_8.setText(QCoreApplication.translate("Form", u"...", None))
+ self.btn_member.setText(QCoreApplication.translate("Form", u"Join Us", None))
+ self.toolButton_9.setText(QCoreApplication.translate("Form", u"...", None))
+ self.btn_donate.setText(QCoreApplication.translate("Form", u"Donate", None))
+ # retranslateUi
+
diff --git a/pyminer/lib/ui/ui_first_form.ui b/pyminer/lib/ui/ui_first_form.ui
new file mode 100644
index 0000000000000000000000000000000000000000..dfa8c2bf1f5ca5ee46f32d92adf9d1725ddeb6c6
--- /dev/null
+++ b/pyminer/lib/ui/ui_first_form.ui
@@ -0,0 +1,788 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 620
+ 580
+
+
+
+
+ 620
+ 580
+
+
+
+
+ 620
+ 580
+
+
+
+ Form
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ Consolas
+ 28
+
+
+
+
+
+
+ :/images/images/bg.png
+
+
+ true
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 270
+
+
+
+
+ 16777215
+ 270
+
+
+
+ background-color: rgb(33, 33, 33);
+
+
+
-
+
+
+ 5
+
+
+ 0
+
+
-
+
+
+ 0
+
+
-
+
+
+
+ 20
+ 0
+
+
+
+
+ 20
+ 16777215
+
+
+
+
+ 9
+
+
+
+ color: rgb(229, 229, 229);
+
+
+
+
+
+
+ -
+
+
+
+ 9
+
+
+
+ color: rgb(229, 229, 229);
+
+
+ Open File
+
+
+
+
+
+ -
+
+
+
+ 300
+ 0
+
+
+
+
-
+
+
+ 0
+
+
-
+
+
+ ...
+
+
+
+ :/color/theme/default/icons/python.svg:/color/theme/default/icons/python.svg
+
+
+
+ 18
+ 18
+
+
+
+
+ -
+
+
+ color: rgb(255, 255, 255);
+
+
+ Python File
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+ ...
+
+
+
+ :/color/theme/default/icons/csv.svg:/color/theme/default/icons/csv.svg
+
+
+
+ 18
+ 18
+
+
+
+
+ -
+
+
+ color: rgb(255, 255, 255);
+
+
+ CSV File
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+ ...
+
+
+
+ :/color/theme/default/icons/excel.svg:/color/theme/default/icons/excel.svg
+
+
+
+ 18
+ 18
+
+
+
+
+ -
+
+
+ color: rgb(255, 255, 255);
+
+
+ Excel File
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+ ...
+
+
+
+ :/color/theme/default/icons/E-matlab.svg:/color/theme/default/icons/E-matlab.svg
+
+
+
+ 18
+ 18
+
+
+
+
+ -
+
+
+ color: rgb(255, 255, 255);
+
+
+ MATLAB File
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+ -
+
+
+
-
+
+
+ 0
+
+
-
+
+
+ ...
+
+
+
+ :/color/theme/default/icons/open_folder.svg:/color/theme/default/icons/open_folder.svg
+
+
+
+ 18
+ 18
+
+
+
+
+ -
+
+
+ color: rgb(255, 255, 255);
+
+
+ Open Folder...
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+ 5
+
+
+ 0
+
+
-
+
+
+ 0
+
+
-
+
+
+
+ 20
+ 0
+
+
+
+
+ 20
+ 16777215
+
+
+
+
+ 9
+
+
+
+ color: rgb(229, 229, 229);
+
+
+
+
+
+
+ -
+
+
+
+ 9
+
+
+
+ color: rgb(229, 229, 229);
+
+
+ Quick Start
+
+
+
+
+
+ -
+
+
+
-
+
+
+ 0
+
+
-
+
+
+ ...
+
+
+
+ :/color/theme/default/icons/website.svg:/color/theme/default/icons/website.svg
+
+
+
+ 18
+ 18
+
+
+
+
+ -
+
+
+ color: rgb(255, 255, 255);
+
+
+ Use Manual
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+ ...
+
+
+
+ :/color/theme/default/icons/website.svg:/color/theme/default/icons/website.svg
+
+
+
+ 18
+ 18
+
+
+
+
+ -
+
+
+ color: rgb(255, 255, 255);
+
+
+ Official Site
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+ ...
+
+
+
+ :/color/theme/default/icons/website.svg:/color/theme/default/icons/website.svg
+
+
+
+ 18
+ 18
+
+
+
+
+ -
+
+
+ color: rgb(255, 255, 255);
+
+
+ Gitee Repo
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+ -
+
+
+
-
+
+
+ 0
+
+
-
+
+
+ ...
+
+
+
+ :/color/theme/default/icons/website.svg:/color/theme/default/icons/website.svg
+
+
+
+ 18
+ 18
+
+
+
+
+ -
+
+
+ color: rgb(255, 255, 255);
+
+
+ Join Us
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+ ...
+
+
+
+ :/color/theme/default/icons/donate.svg:/color/theme/default/icons/donate.svg
+
+
+
+ 18
+ 18
+
+
+
+
+ -
+
+
+ color: rgb(255, 255, 255);
+
+
+ Donate
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pyminer/lib/ui/ui_login.py b/pyminer/lib/ui/ui_login.py
new file mode 100644
index 0000000000000000000000000000000000000000..843dd6abdd09f4667a8a1c1dcc386d045dfc5809
--- /dev/null
+++ b/pyminer/lib/ui/ui_login.py
@@ -0,0 +1,204 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'ui_login.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import *
+from PySide2.QtGui import *
+from PySide2.QtWidgets import *
+
+
+class Ui_Form(object):
+ def setupUi(self, Form):
+ if not Form.objectName():
+ Form.setObjectName(u"Form")
+ Form.setWindowModality(Qt.ApplicationModal)
+ Form.resize(407, 361)
+ sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth())
+ Form.setSizePolicy(sizePolicy)
+ Form.setMinimumSize(QSize(407, 361))
+ Form.setMaximumSize(QSize(407, 361))
+ Form.setStyleSheet(u"")
+ self.verticalLayout = QVBoxLayout(Form)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.widget = QWidget(Form)
+ self.widget.setObjectName(u"widget")
+ self.widget.setMaximumSize(QSize(407, 361))
+ self.widget.setSizeIncrement(QSize(407, 361))
+ self.widget.setBaseSize(QSize(407, 361))
+ self.widget.setStyleSheet(u"QPushButton{\n"
+" padding: 8px 20px;\n"
+" font-size: 13px;\n"
+" line-height: 1.65;\n"
+" border-radius: 20px;\n"
+" display: inline-block;\n"
+" margin-bottom: 0;\n"
+" font-weight: 600;\n"
+" text-align: center;\n"
+" vertical-align: middle;\n"
+" touch-action: manipulation;\n"
+" cursor: pointer;\n"
+" background-image: none;\n"
+" border: 1px solid transparent;\n"
+" white-space: nowrap;\n"
+" font-family: inherit;\n"
+" text-transform: none;\n"
+" background-color: #00a6fd;\n"
+" border-color: #00a6fd;\n"
+" color: #fff;\n"
+" \n"
+"}")
+ self.layoutWidget = QWidget(self.widget)
+ self.layoutWidget.setObjectName(u"layoutWidget")
+ self.layoutWidget.setGeometry(QRect(0, 0, 452, 331))
+ self.verticalLayout_2 = QVBoxLayout(self.layoutWidget)
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
+ self.verticalLayout_6 = QVBoxLayout()
+ self.verticalLayout_6.setObjectName(u"verticalLayout_6")
+ self.usernameError = QLabel(self.layoutWidget)
+ self.usernameError.setObjectName(u"usernameError")
+ self.usernameError.setEnabled(True)
+ self.usernameError.setMinimumSize(QSize(380, 32))
+ self.usernameError.setMaximumSize(QSize(380, 32))
+ font = QFont()
+ font.setFamily(u"18thCentury")
+ font.setPointSize(10)
+ font.setBold(False)
+ font.setWeight(50)
+ self.usernameError.setFont(font)
+ self.usernameError.setAutoFillBackground(False)
+ self.usernameError.setStyleSheet(u"*{\n"
+"color:red;\n"
+"}")
+ self.usernameError.setAlignment(Qt.AlignCenter)
+
+ self.verticalLayout_6.addWidget(self.usernameError)
+
+ self.horizontalLayout_2 = QHBoxLayout()
+ self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
+ self.label = QLabel(self.layoutWidget)
+ self.label.setObjectName(u"label")
+ self.label.setMinimumSize(QSize(70, 0))
+ self.label.setMaximumSize(QSize(70, 16777215))
+ self.label.setBaseSize(QSize(70, 0))
+ self.label.setAlignment(Qt.AlignCenter)
+
+ self.horizontalLayout_2.addWidget(self.label)
+
+ self.usernameLineEdit = QLineEdit(self.layoutWidget)
+ self.usernameLineEdit.setObjectName(u"usernameLineEdit")
+ self.usernameLineEdit.setMinimumSize(QSize(300, 32))
+ self.usernameLineEdit.setMaximumSize(QSize(300, 32))
+ self.usernameLineEdit.setStyleSheet(u"QLineEdit{\n"
+"background-color: white;\n"
+"selection-color: white;\n"
+"selection-background-color: blue;\n"
+"}")
+ self.usernameLineEdit.setMaxLength(64)
+
+ self.horizontalLayout_2.addWidget(self.usernameLineEdit)
+
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_2.addItem(self.horizontalSpacer)
+
+
+ self.verticalLayout_6.addLayout(self.horizontalLayout_2)
+
+ self.passwordError = QLabel(self.layoutWidget)
+ self.passwordError.setObjectName(u"passwordError")
+ self.passwordError.setMinimumSize(QSize(380, 32))
+ self.passwordError.setMaximumSize(QSize(380, 32))
+ self.passwordError.setFont(font)
+ self.passwordError.setStyleSheet(u"*{\n"
+"color:red;\n"
+"}")
+ self.passwordError.setAlignment(Qt.AlignCenter)
+
+ self.verticalLayout_6.addWidget(self.passwordError)
+
+ self.horizontalLayout_3 = QHBoxLayout()
+ self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
+ self.label_2 = QLabel(self.layoutWidget)
+ self.label_2.setObjectName(u"label_2")
+ self.label_2.setMinimumSize(QSize(70, 0))
+ self.label_2.setMaximumSize(QSize(70, 16777215))
+ self.label_2.setBaseSize(QSize(70, 0))
+ self.label_2.setAlignment(Qt.AlignCenter)
+
+ self.horizontalLayout_3.addWidget(self.label_2)
+
+ self.passwordLineEdit = QLineEdit(self.layoutWidget)
+ self.passwordLineEdit.setObjectName(u"passwordLineEdit")
+ self.passwordLineEdit.setMinimumSize(QSize(300, 0))
+ self.passwordLineEdit.setMaximumSize(QSize(300, 32))
+ self.passwordLineEdit.setBaseSize(QSize(300, 32))
+ self.passwordLineEdit.setStyleSheet(u"QLineEdit{\n"
+"background-color: white;\n"
+"selection-color: white;\n"
+"selection-background-color: blue;\n"
+"}")
+ self.passwordLineEdit.setEchoMode(QLineEdit.Password)
+
+ self.horizontalLayout_3.addWidget(self.passwordLineEdit)
+
+ self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_3.addItem(self.horizontalSpacer_2)
+
+
+ self.verticalLayout_6.addLayout(self.horizontalLayout_3)
+
+
+ self.verticalLayout_2.addLayout(self.verticalLayout_6)
+
+ self.horizontalLayout = QHBoxLayout()
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.loginButton = QPushButton(self.layoutWidget)
+ self.loginButton.setObjectName(u"loginButton")
+ self.loginButton.setMaximumSize(QSize(150, 16777215))
+ self.loginButton.setCursor(QCursor(Qt.PointingHandCursor))
+ self.loginButton.setStyleSheet(u"")
+
+ self.horizontalLayout.addWidget(self.loginButton)
+
+ self.forgetPwdButton = QPushButton(self.layoutWidget)
+ self.forgetPwdButton.setObjectName(u"forgetPwdButton")
+ self.forgetPwdButton.setMaximumSize(QSize(150, 16777215))
+ self.forgetPwdButton.setCursor(QCursor(Qt.PointingHandCursor))
+
+ self.horizontalLayout.addWidget(self.forgetPwdButton)
+
+
+ self.verticalLayout_2.addLayout(self.horizontalLayout)
+
+
+ self.verticalLayout.addWidget(self.widget)
+
+
+ self.retranslateUi(Form)
+
+ QMetaObject.connectSlotsByName(Form)
+ # setupUi
+
+ def retranslateUi(self, Form):
+ Form.setWindowTitle(QCoreApplication.translate("Form", u"\u7528\u6237\u767b\u5f55", None))
+ self.usernameError.setText("")
+ self.label.setText(QCoreApplication.translate("Form", u"\u7528 \u6237 \u540d", None))
+ self.usernameLineEdit.setPlaceholderText(QCoreApplication.translate("Form", u"\u8bf7\u8f93\u5165\u7528\u6237\u540d", None))
+ self.passwordError.setText("")
+ self.label_2.setText(QCoreApplication.translate("Form", u"\u5bc6 \u7801", None))
+ self.passwordLineEdit.setPlaceholderText(QCoreApplication.translate("Form", u"\u8bf7\u8f93\u5165\u5bc6\u7801", None))
+ self.loginButton.setText(QCoreApplication.translate("Form", u"\u767b\u5f55", None))
+ self.forgetPwdButton.setText(QCoreApplication.translate("Form", u"\u5fd8\u8bb0\u5bc6\u7801\uff1f", None))
+ # retranslateUi
+
diff --git a/pyminer/lib/ui/ui_login.ui b/pyminer/lib/ui/ui_login.ui
new file mode 100644
index 0000000000000000000000000000000000000000..290a39b5b485e97a2993a33c6ef100e07b4d21fa
--- /dev/null
+++ b/pyminer/lib/ui/ui_login.ui
@@ -0,0 +1,376 @@
+
+
+ Form
+
+
+ Qt::ApplicationModal
+
+
+
+ 0
+ 0
+ 407
+ 361
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 407
+ 361
+
+
+
+
+ 407
+ 361
+
+
+
+ 用户登录
+
+
+
+
+
+ -
+
+
+
+ 407
+ 361
+
+
+
+
+ 407
+ 361
+
+
+
+
+ 407
+ 361
+
+
+
+ QPushButton{
+ padding: 8px 20px;
+ font-size: 13px;
+ line-height: 1.65;
+ border-radius: 20px;
+ display: inline-block;
+ margin-bottom: 0;
+ font-weight: 600;
+ text-align: center;
+ vertical-align: middle;
+ touch-action: manipulation;
+ cursor: pointer;
+ background-image: none;
+ border: 1px solid transparent;
+ white-space: nowrap;
+ font-family: inherit;
+ text-transform: none;
+ background-color: #00a6fd;
+ border-color: #00a6fd;
+ color: #fff;
+
+}
+
+
+
+
+ 0
+ 0
+ 452
+ 331
+
+
+
+
-
+
+
-
+
+
+ true
+
+
+
+ 380
+ 32
+
+
+
+
+ 380
+ 32
+
+
+
+
+ 18thCentury
+ 10
+ 50
+ false
+
+
+
+ false
+
+
+ *{
+color:red;
+}
+
+
+
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
-
+
+
+
+ 70
+ 0
+
+
+
+
+ 70
+ 16777215
+
+
+
+
+ 70
+ 0
+
+
+
+ 用 户 名
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+
+ 300
+ 32
+
+
+
+
+ 300
+ 32
+
+
+
+ QLineEdit{
+background-color: white;
+selection-color: white;
+selection-background-color: blue;
+}
+
+
+ 64
+
+
+ 请输入用户名
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+
+ 380
+ 32
+
+
+
+
+ 380
+ 32
+
+
+
+
+ 18thCentury
+ 10
+ 50
+ false
+
+
+
+ *{
+color:red;
+}
+
+
+
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
-
+
+
+
+ 70
+ 0
+
+
+
+
+ 70
+ 16777215
+
+
+
+
+ 70
+ 0
+
+
+
+ 密 码
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+
+ 300
+ 0
+
+
+
+
+ 300
+ 32
+
+
+
+
+ 300
+ 32
+
+
+
+ QLineEdit{
+background-color: white;
+selection-color: white;
+selection-background-color: blue;
+}
+
+
+ QLineEdit::Password
+
+
+ 请输入密码
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 150
+ 16777215
+
+
+
+ PointingHandCursor
+
+
+
+
+
+ 登录
+
+
+
+ -
+
+
+
+ 150
+ 16777215
+
+
+
+ PointingHandCursor
+
+
+ 忘记密码?
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pyminer/lib/ui/ui_logined.py b/pyminer/lib/ui/ui_logined.py
new file mode 100644
index 0000000000000000000000000000000000000000..8d469fb4a4838c23747f684cb92c94b4b328be25
--- /dev/null
+++ b/pyminer/lib/ui/ui_logined.py
@@ -0,0 +1,112 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'ui_logined.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import *
+from PySide2.QtGui import *
+from PySide2.QtWidgets import *
+
+
+class Ui_Form(object):
+ def setupUi(self, Form):
+ if not Form.objectName():
+ Form.setObjectName(u"Form")
+ Form.resize(407, 361)
+ Form.setMinimumSize(QSize(407, 361))
+ Form.setMaximumSize(QSize(407, 361))
+ Form.setBaseSize(QSize(407, 361))
+ Form.setStyleSheet(u"QPushButton{\n"
+" padding: 8px 20px;\n"
+" font-size: 13px;\n"
+" line-height: 1.65;\n"
+" border-radius: 20px;\n"
+" display: inline-block;\n"
+" margin-bottom: 0;\n"
+" font-weight: 600;\n"
+" text-align: center;\n"
+" vertical-align: middle;\n"
+" touch-action: manipulation;\n"
+" cursor: pointer;\n"
+" background-image: none;\n"
+" border: 1px solid transparent;\n"
+" white-space: nowrap;\n"
+" font-family: inherit;\n"
+" text-transform: none;\n"
+" background-color: #00a6fd;\n"
+" border-color: #00a6fd;\n"
+" color: #fff;\n"
+" \n"
+"}")
+ self.layoutWidget = QWidget(Form)
+ self.layoutWidget.setObjectName(u"layoutWidget")
+ self.layoutWidget.setGeometry(QRect(10, 0, 391, 341))
+ self.verticalLayout = QVBoxLayout(self.layoutWidget)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.verticalLayout.setContentsMargins(0, 0, 0, 0)
+ self.horizontalLayout = QHBoxLayout()
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.label = QLabel(self.layoutWidget)
+ self.label.setObjectName(u"label")
+ self.label.setFrameShape(QFrame.NoFrame)
+ self.label.setFrameShadow(QFrame.Plain)
+ self.label.setTextFormat(Qt.AutoText)
+ self.label.setAlignment(Qt.AlignCenter)
+ self.label.setMargin(0)
+
+ self.horizontalLayout.addWidget(self.label)
+
+ self.verticalSpacer_2 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
+
+ self.horizontalLayout.addItem(self.verticalSpacer_2)
+
+ self.usernameLabel = QLabel(self.layoutWidget)
+ self.usernameLabel.setObjectName(u"usernameLabel")
+ self.usernameLabel.setCursor(QCursor(Qt.IBeamCursor))
+ self.usernameLabel.setLayoutDirection(Qt.LeftToRight)
+ self.usernameLabel.setWordWrap(True)
+ self.usernameLabel.setMargin(0)
+ self.usernameLabel.setTextInteractionFlags(Qt.TextSelectableByMouse)
+
+ self.horizontalLayout.addWidget(self.usernameLabel)
+
+
+ self.verticalLayout.addLayout(self.horizontalLayout)
+
+ self.horizontalLayout_2 = QHBoxLayout()
+ self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_2.addItem(self.horizontalSpacer)
+
+ self.loginOutButton = QPushButton(self.layoutWidget)
+ self.loginOutButton.setObjectName(u"loginOutButton")
+ self.loginOutButton.setCursor(QCursor(Qt.PointingHandCursor))
+
+ self.horizontalLayout_2.addWidget(self.loginOutButton)
+
+ self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
+
+ self.horizontalLayout_2.addItem(self.verticalSpacer)
+
+
+ self.verticalLayout.addLayout(self.horizontalLayout_2)
+
+
+ self.retranslateUi(Form)
+
+ QMetaObject.connectSlotsByName(Form)
+ # setupUi
+
+ def retranslateUi(self, Form):
+ Form.setWindowTitle(QCoreApplication.translate("Form", u"\u7528\u6237\u4fe1\u606f", None))
+ self.label.setText(QCoreApplication.translate("Form", u"\u7528 \u6237 \u540d", None))
+ self.usernameLabel.setText("")
+ self.loginOutButton.setText(QCoreApplication.translate("Form", u"\u6ce8 \u9500", None))
+ # retranslateUi
+
diff --git a/pyminer/lib/ui/ui_logined.ui b/pyminer/lib/ui/ui_logined.ui
new file mode 100644
index 0000000000000000000000000000000000000000..45a4af71e0e0b09d7f4db3eab6ad02247804f348
--- /dev/null
+++ b/pyminer/lib/ui/ui_logined.ui
@@ -0,0 +1,174 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 407
+ 361
+
+
+
+
+ 407
+ 361
+
+
+
+
+ 407
+ 361
+
+
+
+
+ 407
+ 361
+
+
+
+ 用户信息
+
+
+ QPushButton{
+ padding: 8px 20px;
+ font-size: 13px;
+ line-height: 1.65;
+ border-radius: 20px;
+ display: inline-block;
+ margin-bottom: 0;
+ font-weight: 600;
+ text-align: center;
+ vertical-align: middle;
+ touch-action: manipulation;
+ cursor: pointer;
+ background-image: none;
+ border: 1px solid transparent;
+ white-space: nowrap;
+ font-family: inherit;
+ text-transform: none;
+ background-color: #00a6fd;
+ border-color: #00a6fd;
+ color: #fff;
+
+}
+
+
+
+
+ 10
+ 0
+ 391
+ 341
+
+
+
+ -
+
+
-
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Plain
+
+
+ 用 户 名
+
+
+ Qt::AutoText
+
+
+ Qt::AlignCenter
+
+
+ 0
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ IBeamCursor
+
+
+ Qt::LeftToRight
+
+
+
+
+
+ true
+
+
+ 0
+
+
+ Qt::TextSelectableByMouse
+
+
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ PointingHandCursor
+
+
+ 注 销
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pyminer/lib/ui/ui_option.py b/pyminer/lib/ui/ui_option.py
new file mode 100644
index 0000000000000000000000000000000000000000..7340b2170d05daae177441a84b475dbf2e3b1520
--- /dev/null
+++ b/pyminer/lib/ui/ui_option.py
@@ -0,0 +1,441 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'ui_option.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import *
+from PySide2.QtGui import *
+from PySide2.QtWidgets import *
+
+
+class Ui_Form(object):
+ def setupUi(self, Form):
+ if not Form.objectName():
+ Form.setObjectName(u"Form")
+ Form.resize(705, 515)
+ self.verticalLayout_2 = QVBoxLayout(Form)
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.splitter = QSplitter(Form)
+ self.splitter.setObjectName(u"splitter")
+ self.splitter.setOrientation(Qt.Horizontal)
+ self.listWidget = QListWidget(self.splitter)
+ QListWidgetItem(self.listWidget)
+ QListWidgetItem(self.listWidget)
+ QListWidgetItem(self.listWidget)
+ self.listWidget.setObjectName(u"listWidget")
+ self.listWidget.setMaximumSize(QSize(200, 16777215))
+ self.splitter.addWidget(self.listWidget)
+ self.stackedWidget = QStackedWidget(self.splitter)
+ self.stackedWidget.setObjectName(u"stackedWidget")
+ self.page_general = QWidget()
+ self.page_general.setObjectName(u"page_general")
+ self.horizontalLayout_3 = QHBoxLayout(self.page_general)
+ self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
+ self.horizontalLayout_3.setContentsMargins(0, 0, 0, 0)
+ self.tabWidget = QTabWidget(self.page_general)
+ self.tabWidget.setObjectName(u"tabWidget")
+ self.tabWidget.setTabPosition(QTabWidget.North)
+ self.tabBase = QWidget()
+ self.tabBase.setObjectName(u"tabBase")
+ self.verticalLayout_8 = QVBoxLayout(self.tabBase)
+ self.verticalLayout_8.setObjectName(u"verticalLayout_8")
+ self.formLayout_2 = QFormLayout()
+ self.formLayout_2.setObjectName(u"formLayout_2")
+ self.label_theme = QLabel(self.tabBase)
+ self.label_theme.setObjectName(u"label_theme")
+
+ self.formLayout_2.setWidget(0, QFormLayout.LabelRole, self.label_theme)
+
+ self.comboBox_theme = QComboBox(self.tabBase)
+ self.comboBox_theme.addItem("")
+ self.comboBox_theme.addItem("")
+ self.comboBox_theme.addItem("")
+ self.comboBox_theme.addItem("")
+ self.comboBox_theme.setObjectName(u"comboBox_theme")
+
+ self.formLayout_2.setWidget(0, QFormLayout.FieldRole, self.comboBox_theme)
+
+ self.label = QLabel(self.tabBase)
+ self.label.setObjectName(u"label")
+
+ self.formLayout_2.setWidget(1, QFormLayout.LabelRole, self.label)
+
+ self.horizontalLayout_14 = QHBoxLayout()
+ self.horizontalLayout_14.setObjectName(u"horizontalLayout_14")
+ self.lineEdit_worksapce = QLineEdit(self.tabBase)
+ self.lineEdit_worksapce.setObjectName(u"lineEdit_worksapce")
+
+ self.horizontalLayout_14.addWidget(self.lineEdit_worksapce)
+
+ self.toolButton_workspace = QToolButton(self.tabBase)
+ self.toolButton_workspace.setObjectName(u"toolButton_workspace")
+
+ self.horizontalLayout_14.addWidget(self.toolButton_workspace)
+
+
+ self.formLayout_2.setLayout(1, QFormLayout.FieldRole, self.horizontalLayout_14)
+
+ self.label_15 = QLabel(self.tabBase)
+ self.label_15.setObjectName(u"label_15")
+
+ self.formLayout_2.setWidget(2, QFormLayout.LabelRole, self.label_15)
+
+ self.horizontalLayout_15 = QHBoxLayout()
+ self.horizontalLayout_15.setObjectName(u"horizontalLayout_15")
+ self.lineEdit_output = QLineEdit(self.tabBase)
+ self.lineEdit_output.setObjectName(u"lineEdit_output")
+
+ self.horizontalLayout_15.addWidget(self.lineEdit_output)
+
+ self.toolButton_output = QToolButton(self.tabBase)
+ self.toolButton_output.setObjectName(u"toolButton_output")
+
+ self.horizontalLayout_15.addWidget(self.toolButton_output)
+
+
+ self.formLayout_2.setLayout(2, QFormLayout.FieldRole, self.horizontalLayout_15)
+
+ self.label_16 = QLabel(self.tabBase)
+ self.label_16.setObjectName(u"label_16")
+
+ self.formLayout_2.setWidget(3, QFormLayout.LabelRole, self.label_16)
+
+ self.comboBox_9 = QComboBox(self.tabBase)
+ self.comboBox_9.addItem("")
+ self.comboBox_9.addItem("")
+ self.comboBox_9.setObjectName(u"comboBox_9")
+
+ self.formLayout_2.setWidget(3, QFormLayout.FieldRole, self.comboBox_9)
+
+ self.label_11 = QLabel(self.tabBase)
+ self.label_11.setObjectName(u"label_11")
+
+ self.formLayout_2.setWidget(4, QFormLayout.LabelRole, self.label_11)
+
+ self.comboBox_8 = QComboBox(self.tabBase)
+ self.comboBox_8.addItem("")
+ self.comboBox_8.addItem("")
+ self.comboBox_8.setObjectName(u"comboBox_8")
+
+ self.formLayout_2.setWidget(4, QFormLayout.FieldRole, self.comboBox_8)
+
+ self.check_box_check_upd_on_startup = QCheckBox(self.tabBase)
+ self.check_box_check_upd_on_startup.setObjectName(u"check_box_check_upd_on_startup")
+ self.check_box_check_upd_on_startup.setChecked(True)
+
+ self.formLayout_2.setWidget(5, QFormLayout.LabelRole, self.check_box_check_upd_on_startup)
+
+ self.checkbox_show_startpage = QCheckBox(self.tabBase)
+ self.checkbox_show_startpage.setObjectName(u"checkbox_show_startpage")
+ self.checkbox_show_startpage.setChecked(True)
+
+ self.formLayout_2.setWidget(6, QFormLayout.LabelRole, self.checkbox_show_startpage)
+
+
+ self.verticalLayout_8.addLayout(self.formLayout_2)
+
+ self.verticalSpacer_2 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
+
+ self.verticalLayout_8.addItem(self.verticalSpacer_2)
+
+ self.tabWidget.addTab(self.tabBase, "")
+
+ self.horizontalLayout_3.addWidget(self.tabWidget)
+
+ self.stackedWidget.addWidget(self.page_general)
+ self.page_appearance = QWidget()
+ self.page_appearance.setObjectName(u"page_appearance")
+ self.verticalLayout_6 = QVBoxLayout(self.page_appearance)
+ self.verticalLayout_6.setObjectName(u"verticalLayout_6")
+ self.verticalLayout_5 = QVBoxLayout()
+ self.verticalLayout_5.setObjectName(u"verticalLayout_5")
+ self.checkBox_3 = QCheckBox(self.page_appearance)
+ self.checkBox_3.setObjectName(u"checkBox_3")
+ self.checkBox_3.setChecked(True)
+
+ self.verticalLayout_5.addWidget(self.checkBox_3)
+
+ self.horizontalLayout_11 = QHBoxLayout()
+ self.horizontalLayout_11.setObjectName(u"horizontalLayout_11")
+ self.label_10 = QLabel(self.page_appearance)
+ self.label_10.setObjectName(u"label_10")
+
+ self.horizontalLayout_11.addWidget(self.label_10)
+
+ self.pushButton = QPushButton(self.page_appearance)
+ self.pushButton.setObjectName(u"pushButton")
+
+ self.horizontalLayout_11.addWidget(self.pushButton)
+
+ self.horizontalSpacer_4 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_11.addItem(self.horizontalSpacer_4)
+
+
+ self.verticalLayout_5.addLayout(self.horizontalLayout_11)
+
+
+ self.verticalLayout_6.addLayout(self.verticalLayout_5)
+
+ self.horizontalLayout_6 = QHBoxLayout()
+ self.horizontalLayout_6.setObjectName(u"horizontalLayout_6")
+
+ self.verticalLayout_6.addLayout(self.horizontalLayout_6)
+
+ self.horizontalLayout_5 = QHBoxLayout()
+ self.horizontalLayout_5.setObjectName(u"horizontalLayout_5")
+ self.label_3 = QLabel(self.page_appearance)
+ self.label_3.setObjectName(u"label_3")
+
+ self.horizontalLayout_5.addWidget(self.label_3)
+
+ self.lineEdit_2 = QLineEdit(self.page_appearance)
+ self.lineEdit_2.setObjectName(u"lineEdit_2")
+ self.lineEdit_2.setMaximumSize(QSize(60, 16777215))
+
+ self.horizontalLayout_5.addWidget(self.lineEdit_2)
+
+ self.horizontalSpacer_3 = QSpacerItem(120, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_5.addItem(self.horizontalSpacer_3)
+
+
+ self.verticalLayout_6.addLayout(self.horizontalLayout_5)
+
+ self.horizontalLayout_4 = QHBoxLayout()
+ self.horizontalLayout_4.setObjectName(u"horizontalLayout_4")
+ self.label_2 = QLabel(self.page_appearance)
+ self.label_2.setObjectName(u"label_2")
+
+ self.horizontalLayout_4.addWidget(self.label_2)
+
+ self.comboBox = QComboBox(self.page_appearance)
+ self.comboBox.addItem("")
+ self.comboBox.setObjectName(u"comboBox")
+
+ self.horizontalLayout_4.addWidget(self.comboBox)
+
+ self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_4.addItem(self.horizontalSpacer_2)
+
+
+ self.verticalLayout_6.addLayout(self.horizontalLayout_4)
+
+ self.textEdit = QTextEdit(self.page_appearance)
+ self.textEdit.setObjectName(u"textEdit")
+ self.textEdit.setMaximumSize(QSize(16777215, 16777215))
+
+ self.verticalLayout_6.addWidget(self.textEdit)
+
+ self.stackedWidget.addWidget(self.page_appearance)
+ self.page_format = QWidget()
+ self.page_format.setObjectName(u"page_format")
+ self.verticalLayout_3 = QVBoxLayout(self.page_format)
+ self.verticalLayout_3.setObjectName(u"verticalLayout_3")
+ self.formLayout = QFormLayout()
+ self.formLayout.setObjectName(u"formLayout")
+ self.label_4 = QLabel(self.page_format)
+ self.label_4.setObjectName(u"label_4")
+
+ self.formLayout.setWidget(0, QFormLayout.LabelRole, self.label_4)
+
+ self.comboBox_2 = QComboBox(self.page_format)
+ self.comboBox_2.addItem("")
+ self.comboBox_2.addItem("")
+ self.comboBox_2.addItem("")
+ self.comboBox_2.setObjectName(u"comboBox_2")
+
+ self.formLayout.setWidget(0, QFormLayout.FieldRole, self.comboBox_2)
+
+ self.comboBox_3 = QComboBox(self.page_format)
+ self.comboBox_3.addItem("")
+ self.comboBox_3.addItem("")
+ self.comboBox_3.addItem("")
+ self.comboBox_3.setObjectName(u"comboBox_3")
+
+ self.formLayout.setWidget(1, QFormLayout.FieldRole, self.comboBox_3)
+
+ self.label_5 = QLabel(self.page_format)
+ self.label_5.setObjectName(u"label_5")
+
+ self.formLayout.setWidget(1, QFormLayout.LabelRole, self.label_5)
+
+ self.comboBox_4 = QComboBox(self.page_format)
+ self.comboBox_4.addItem("")
+ self.comboBox_4.addItem("")
+ self.comboBox_4.addItem("")
+ self.comboBox_4.setObjectName(u"comboBox_4")
+
+ self.formLayout.setWidget(2, QFormLayout.FieldRole, self.comboBox_4)
+
+ self.label_6 = QLabel(self.page_format)
+ self.label_6.setObjectName(u"label_6")
+
+ self.formLayout.setWidget(2, QFormLayout.LabelRole, self.label_6)
+
+ self.comboBox_5 = QComboBox(self.page_format)
+ self.comboBox_5.addItem("")
+ self.comboBox_5.addItem("")
+ self.comboBox_5.addItem("")
+ self.comboBox_5.addItem("")
+ self.comboBox_5.setObjectName(u"comboBox_5")
+
+ self.formLayout.setWidget(3, QFormLayout.FieldRole, self.comboBox_5)
+
+ self.label_7 = QLabel(self.page_format)
+ self.label_7.setObjectName(u"label_7")
+
+ self.formLayout.setWidget(3, QFormLayout.LabelRole, self.label_7)
+
+ self.comboBox_6 = QComboBox(self.page_format)
+ self.comboBox_6.addItem("")
+ self.comboBox_6.addItem("")
+ self.comboBox_6.setObjectName(u"comboBox_6")
+
+ self.formLayout.setWidget(4, QFormLayout.FieldRole, self.comboBox_6)
+
+ self.label_8 = QLabel(self.page_format)
+ self.label_8.setObjectName(u"label_8")
+
+ self.formLayout.setWidget(4, QFormLayout.LabelRole, self.label_8)
+
+
+ self.verticalLayout_3.addLayout(self.formLayout)
+
+ self.stackedWidget.addWidget(self.page_format)
+ self.splitter.addWidget(self.stackedWidget)
+
+ self.verticalLayout_2.addWidget(self.splitter)
+
+ self.widget = QWidget(Form)
+ self.widget.setObjectName(u"widget")
+ self.widget.setMaximumSize(QSize(16777215, 50))
+ self.horizontalLayout_7 = QHBoxLayout(self.widget)
+ self.horizontalLayout_7.setObjectName(u"horizontalLayout_7")
+ self.horizontalLayout_7.setContentsMargins(0, 0, 0, 0)
+ self.horizontalLayout = QHBoxLayout()
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.pushButton_help = QPushButton(self.widget)
+ self.pushButton_help.setObjectName(u"pushButton_help")
+
+ self.horizontalLayout.addWidget(self.pushButton_help)
+
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout.addItem(self.horizontalSpacer)
+
+ self.pushButton_ok = QPushButton(self.widget)
+ self.pushButton_ok.setObjectName(u"pushButton_ok")
+
+ self.horizontalLayout.addWidget(self.pushButton_ok)
+
+ self.pushButton_cancel = QPushButton(self.widget)
+ self.pushButton_cancel.setObjectName(u"pushButton_cancel")
+
+ self.horizontalLayout.addWidget(self.pushButton_cancel)
+
+
+ self.horizontalLayout_7.addLayout(self.horizontalLayout)
+
+
+ self.verticalLayout_2.addWidget(self.widget)
+
+
+ self.retranslateUi(Form)
+
+ self.stackedWidget.setCurrentIndex(0)
+ self.tabWidget.setCurrentIndex(0)
+
+
+ QMetaObject.connectSlotsByName(Form)
+ # setupUi
+
+ def retranslateUi(self, Form):
+ Form.setWindowTitle(QCoreApplication.translate("Form", u"Settings", None))
+
+ __sortingEnabled = self.listWidget.isSortingEnabled()
+ self.listWidget.setSortingEnabled(False)
+ ___qlistwidgetitem = self.listWidget.item(0)
+ ___qlistwidgetitem.setText(QCoreApplication.translate("Form", u"\u5e38\u89c4", None));
+ ___qlistwidgetitem1 = self.listWidget.item(1)
+ ___qlistwidgetitem1.setText(QCoreApplication.translate("Form", u"\u5916\u89c2", None));
+ ___qlistwidgetitem2 = self.listWidget.item(2)
+ ___qlistwidgetitem2.setText(QCoreApplication.translate("Form", u"\u683c\u5f0f\u5316", None));
+ self.listWidget.setSortingEnabled(__sortingEnabled)
+
+ self.label_theme.setText(QCoreApplication.translate("Form", u"UI Theme", None))
+ self.comboBox_theme.setItemText(0, QCoreApplication.translate("Form", u"Fusion", None))
+ self.comboBox_theme.setItemText(1, QCoreApplication.translate("Form", u"Qdarkstyle", None))
+ self.comboBox_theme.setItemText(2, QCoreApplication.translate("Form", u"windowsvista", None))
+ self.comboBox_theme.setItemText(3, QCoreApplication.translate("Form", u"Windows", None))
+
+ self.label.setText(QCoreApplication.translate("Form", u"Work Directory", None))
+ self.toolButton_workspace.setText(QCoreApplication.translate("Form", u"...", None))
+ self.label_15.setText(QCoreApplication.translate("Form", u"Output Directory", None))
+ self.toolButton_output.setText(QCoreApplication.translate("Form", u"...", None))
+ self.label_16.setText(QCoreApplication.translate("Form", u"UI Language", None))
+ self.comboBox_9.setItemText(0, QCoreApplication.translate("Form", u"\u7b80\u4f53\u4e2d\u6587", None))
+ self.comboBox_9.setItemText(1, QCoreApplication.translate("Form", u"English", None))
+
+ self.label_11.setText(QCoreApplication.translate("Form", u"Encoding", None))
+ self.comboBox_8.setItemText(0, QCoreApplication.translate("Form", u"utf-8", None))
+ self.comboBox_8.setItemText(1, QCoreApplication.translate("Form", u"gb2312", None))
+
+ self.check_box_check_upd_on_startup.setText(QCoreApplication.translate("Form", u"Check upd on startup", None))
+ self.checkbox_show_startpage.setText(QCoreApplication.translate("Form", u"\u663e\u793a\u8d77\u59cb\u9875\u9762", None))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabBase), QCoreApplication.translate("Form", u"Basic", None))
+ self.checkBox_3.setText(QCoreApplication.translate("Form", u"Interlaced coloring", None))
+ self.label_10.setText(QCoreApplication.translate("Form", u"Table Header Background:", None))
+ self.pushButton.setText(QCoreApplication.translate("Form", u"Color", None))
+ self.label_3.setText(QCoreApplication.translate("Form", u"Size:", None))
+ self.lineEdit_2.setText(QCoreApplication.translate("Form", u"15", None))
+ self.label_2.setText(QCoreApplication.translate("Form", u"Font:", None))
+ self.comboBox.setItemText(0, QCoreApplication.translate("Form", u"Source Code Pro", None))
+
+ self.textEdit.setHtml(QCoreApplication.translate("Form", u"\n"
+"\n"
+"Patata is a full-featured IDE
\n"
+"with a high level of usability and outstanding
\n"
+"advanced code editing and refactoring support.
\n"
+"
\n"
+"abcdefghijklmnopqrstuvwxyz 0123456789 (){}[]
\n"
+"ABCDEFGHIJKLMNOPQRSTUVWXYZ +-*/= .,;:!? #&$%@|^
", None))
+ self.label_4.setText(QCoreApplication.translate("Form", u"\u65e5\u671f\u683c\u5f0f:", None))
+ self.comboBox_2.setItemText(0, QCoreApplication.translate("Form", u"2020-01-01", None))
+ self.comboBox_2.setItemText(1, QCoreApplication.translate("Form", u"2020/01/01", None))
+ self.comboBox_2.setItemText(2, "")
+
+ self.comboBox_3.setItemText(0, QCoreApplication.translate("Form", u"15:30:01(24-\u5c0f\u65f6\u5236)", None))
+ self.comboBox_3.setItemText(1, QCoreApplication.translate("Form", u"3:30:01 PM(12-\u5c0f\u65f6\u5236)", None))
+ self.comboBox_3.setItemText(2, "")
+
+ self.label_5.setText(QCoreApplication.translate("Form", u"\u65f6\u95f4\u683c\u5f0f:", None))
+ self.comboBox_4.setItemText(0, QCoreApplication.translate("Form", u"\u7f8e\u5143US Dollar", None))
+ self.comboBox_4.setItemText(1, QCoreApplication.translate("Form", u"\u4eba\u6c11\u5e01Chinese Yuan", None))
+ self.comboBox_4.setItemText(2, "")
+
+ self.label_6.setText(QCoreApplication.translate("Form", u"\u8d27\u5e01\u5355\u4f4d:", None))
+ self.comboBox_5.setItemText(0, QCoreApplication.translate("Form", u"\uffe5", None))
+ self.comboBox_5.setItemText(1, QCoreApplication.translate("Form", u"CNY", None))
+ self.comboBox_5.setItemText(2, QCoreApplication.translate("Form", u"$", None))
+ self.comboBox_5.setItemText(3, QCoreApplication.translate("Form", u"USD", None))
+
+ self.label_7.setText(QCoreApplication.translate("Form", u"\u8d27\u5e01\u7b26\u53f7:", None))
+ self.comboBox_6.setItemText(0, QCoreApplication.translate("Form", u"\u5217\u6807\u9898\u5185", None))
+ self.comboBox_6.setItemText(1, QCoreApplication.translate("Form", u"\u5355\u5143\u683c\u5185", None))
+
+ self.label_8.setText(QCoreApplication.translate("Form", u"\u8d27\u5e01\u7b26\u53f7\u4f4d\u4e8e:", None))
+ self.pushButton_help.setText(QCoreApplication.translate("Form", u"Help", None))
+ self.pushButton_ok.setText(QCoreApplication.translate("Form", u"OK", None))
+ self.pushButton_cancel.setText(QCoreApplication.translate("Form", u"Cancel", None))
+ # retranslateUi
+
diff --git a/pyminer/lib/ui/ui_option.ui b/pyminer/lib/ui/ui_option.ui
new file mode 100644
index 0000000000000000000000000000000000000000..f9394124cfd07d880f577bf506eea314a30dcdd7
--- /dev/null
+++ b/pyminer/lib/ui/ui_option.ui
@@ -0,0 +1,585 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 705
+ 515
+
+
+
+ Settings
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+
+ 200
+ 16777215
+
+
+
-
+
+ 常规
+
+
+ -
+
+ 外观
+
+
+ -
+
+ 格式化
+
+
+
+
+
+ 0
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+ QTabWidget::North
+
+
+ 0
+
+
+
+ Basic
+
+
+
-
+
+
-
+
+
+ UI Theme
+
+
+
+ -
+
+
-
+
+ Fusion
+
+
+ -
+
+ Qdarkstyle
+
+
+ -
+
+ windowsvista
+
+
+ -
+
+ Windows
+
+
+
+
+ -
+
+
+ Work Directory
+
+
+
+ -
+
+
-
+
+
+ -
+
+
+ ...
+
+
+
+
+
+ -
+
+
+ Output Directory
+
+
+
+ -
+
+
-
+
+
+ -
+
+
+ ...
+
+
+
+
+
+ -
+
+
+ UI Language
+
+
+
+ -
+
+
-
+
+ 简体中文
+
+
+ -
+
+ English
+
+
+
+
+ -
+
+
+ Encoding
+
+
+
+ -
+
+
-
+
+ utf-8
+
+
+ -
+
+ gb2312
+
+
+
+
+ -
+
+
+ Check upd on startup
+
+
+ true
+
+
+
+ -
+
+
+ 显示起始页面
+
+
+ true
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Interlaced coloring
+
+
+ true
+
+
+
+ -
+
+
-
+
+
+ Table Header Background:
+
+
+
+ -
+
+
+ Color
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+ -
+
+
+ -
+
+
-
+
+
+ Size:
+
+
+
+ -
+
+
+
+ 60
+ 16777215
+
+
+
+ 15
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 120
+ 20
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Font:
+
+
+
+ -
+
+
-
+
+ Source Code Pro
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+
+ 16777215
+ 16777215
+
+
+
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
+<html><head><meta name="qrichtext" content="1" /><style type="text/css">
+p, li { white-space: pre-wrap; }
+</style></head><body style=" font-family:'SimSun'; font-size:9pt; font-weight:400; font-style:normal;">
+<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Patata is a full-featured IDE</p>
+<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">with a high level of usability and outstanding</p>
+<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">advanced code editing and refactoring support.</p>
+<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>
+<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">abcdefghijklmnopqrstuvwxyz 0123456789 (){}[]</p>
+<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">ABCDEFGHIJKLMNOPQRSTUVWXYZ +-*/= .,;:!? #&$%@|^</p></body></html>
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+ 日期格式:
+
+
+
+ -
+
+
-
+
+ 2020-01-01
+
+
+ -
+
+ 2020/01/01
+
+
+ -
+
+
+
+
+
+
+ -
+
+
-
+
+ 15:30:01(24-小时制)
+
+
+ -
+
+ 3:30:01 PM(12-小时制)
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+ 时间格式:
+
+
+
+ -
+
+
-
+
+ 美元US Dollar
+
+
+ -
+
+ 人民币Chinese Yuan
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+ 货币单位:
+
+
+
+ -
+
+
-
+
+ ¥
+
+
+ -
+
+ CNY
+
+
+ -
+
+ $
+
+
+ -
+
+ USD
+
+
+
+
+ -
+
+
+ 货币符号:
+
+
+
+ -
+
+
-
+
+ 列标题内
+
+
+ -
+
+ 单元格内
+
+
+
+
+ -
+
+
+ 货币符号位于:
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ 16777215
+ 50
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
-
+
+
+ Help
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ OK
+
+
+
+ -
+
+
+ Cancel
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pyminer/lib/ui/ui_preferences.py b/pyminer/lib/ui/ui_preferences.py
new file mode 100644
index 0000000000000000000000000000000000000000..2aa18e93c1d7dfaa4b393bf712a45a89b4dd68c8
--- /dev/null
+++ b/pyminer/lib/ui/ui_preferences.py
@@ -0,0 +1,641 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'ui_preferences.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import *
+from PySide2.QtGui import *
+from PySide2.QtWidgets import *
+
+
+class Ui_Form(object):
+ def setupUi(self, Form):
+ if not Form.objectName():
+ Form.setObjectName(u"Form")
+ Form.resize(705, 515)
+ self.verticalLayout_4 = QVBoxLayout(Form)
+ self.verticalLayout_4.setObjectName(u"verticalLayout_4")
+ self.verticalLayout_4.setContentsMargins(9, 0, 9, 9)
+ self.splitter = QSplitter(Form)
+ self.splitter.setObjectName(u"splitter")
+ self.splitter.setOrientation(Qt.Horizontal)
+ self.widget_2 = QWidget(self.splitter)
+ self.widget_2.setObjectName(u"widget_2")
+ self.widget_2.setMaximumSize(QSize(200, 16777215))
+ self.verticalLayout_2 = QVBoxLayout(self.widget_2)
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.verticalLayout_2.setContentsMargins(-1, -1, 0, -1)
+ self.verticalLayout = QVBoxLayout()
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.lineEdit = QLineEdit(self.widget_2)
+ self.lineEdit.setObjectName(u"lineEdit")
+
+ self.verticalLayout.addWidget(self.lineEdit)
+
+ self.listWidget = QListWidget(self.widget_2)
+ QListWidgetItem(self.listWidget)
+ QListWidgetItem(self.listWidget)
+ QListWidgetItem(self.listWidget)
+ QListWidgetItem(self.listWidget)
+ QListWidgetItem(self.listWidget)
+ self.listWidget.setObjectName(u"listWidget")
+ self.listWidget.setMaximumSize(QSize(200, 16777215))
+
+ self.verticalLayout.addWidget(self.listWidget)
+
+
+ self.verticalLayout_2.addLayout(self.verticalLayout)
+
+ self.splitter.addWidget(self.widget_2)
+ self.stackedWidget = QStackedWidget(self.splitter)
+ self.stackedWidget.setObjectName(u"stackedWidget")
+ self.page_general = QWidget()
+ self.page_general.setObjectName(u"page_general")
+ self.horizontalLayout_3 = QHBoxLayout(self.page_general)
+ self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
+ self.horizontalLayout_3.setContentsMargins(0, 0, 0, 0)
+ self.tabWidget = QTabWidget(self.page_general)
+ self.tabWidget.setObjectName(u"tabWidget")
+ self.tabWidget.setTabPosition(QTabWidget.North)
+ self.tabBase = QWidget()
+ self.tabBase.setObjectName(u"tabBase")
+ self.verticalLayout_8 = QVBoxLayout(self.tabBase)
+ self.verticalLayout_8.setObjectName(u"verticalLayout_8")
+ self.formLayout_2 = QFormLayout()
+ self.formLayout_2.setObjectName(u"formLayout_2")
+ self.label = QLabel(self.tabBase)
+ self.label.setObjectName(u"label")
+
+ self.formLayout_2.setWidget(1, QFormLayout.LabelRole, self.label)
+
+ self.label_15 = QLabel(self.tabBase)
+ self.label_15.setObjectName(u"label_15")
+
+ self.formLayout_2.setWidget(2, QFormLayout.LabelRole, self.label_15)
+
+ self.checkBox = QCheckBox(self.tabBase)
+ self.checkBox.setObjectName(u"checkBox")
+ self.checkBox.setChecked(True)
+
+ self.formLayout_2.setWidget(5, QFormLayout.LabelRole, self.checkBox)
+
+ self.checkBox_2 = QCheckBox(self.tabBase)
+ self.checkBox_2.setObjectName(u"checkBox_2")
+
+ self.formLayout_2.setWidget(8, QFormLayout.LabelRole, self.checkBox_2)
+
+ self.label_16 = QLabel(self.tabBase)
+ self.label_16.setObjectName(u"label_16")
+
+ self.formLayout_2.setWidget(3, QFormLayout.LabelRole, self.label_16)
+
+ self.comboBox_language = QComboBox(self.tabBase)
+ self.comboBox_language.addItem("")
+ self.comboBox_language.addItem("")
+ self.comboBox_language.setObjectName(u"comboBox_language")
+
+ self.formLayout_2.setWidget(3, QFormLayout.FieldRole, self.comboBox_language)
+
+ self.label_11 = QLabel(self.tabBase)
+ self.label_11.setObjectName(u"label_11")
+
+ self.formLayout_2.setWidget(4, QFormLayout.LabelRole, self.label_11)
+
+ self.comboBox_encoding = QComboBox(self.tabBase)
+ self.comboBox_encoding.addItem("")
+ self.comboBox_encoding.addItem("")
+ self.comboBox_encoding.setObjectName(u"comboBox_encoding")
+
+ self.formLayout_2.setWidget(4, QFormLayout.FieldRole, self.comboBox_encoding)
+
+ self.horizontalLayout_14 = QHBoxLayout()
+ self.horizontalLayout_14.setObjectName(u"horizontalLayout_14")
+ self.lineEdit_worksapce = QLineEdit(self.tabBase)
+ self.lineEdit_worksapce.setObjectName(u"lineEdit_worksapce")
+
+ self.horizontalLayout_14.addWidget(self.lineEdit_worksapce)
+
+ self.toolButton_workspace = QToolButton(self.tabBase)
+ self.toolButton_workspace.setObjectName(u"toolButton_workspace")
+
+ self.horizontalLayout_14.addWidget(self.toolButton_workspace)
+
+
+ self.formLayout_2.setLayout(1, QFormLayout.FieldRole, self.horizontalLayout_14)
+
+ self.horizontalLayout_15 = QHBoxLayout()
+ self.horizontalLayout_15.setObjectName(u"horizontalLayout_15")
+ self.lineEdit_output = QLineEdit(self.tabBase)
+ self.lineEdit_output.setObjectName(u"lineEdit_output")
+
+ self.horizontalLayout_15.addWidget(self.lineEdit_output)
+
+ self.toolButton_output = QToolButton(self.tabBase)
+ self.toolButton_output.setObjectName(u"toolButton_output")
+
+ self.horizontalLayout_15.addWidget(self.toolButton_output)
+
+
+ self.formLayout_2.setLayout(2, QFormLayout.FieldRole, self.horizontalLayout_15)
+
+ self.label_theme = QLabel(self.tabBase)
+ self.label_theme.setObjectName(u"label_theme")
+
+ self.formLayout_2.setWidget(0, QFormLayout.LabelRole, self.label_theme)
+
+ self.comboBox_theme = QComboBox(self.tabBase)
+ self.comboBox_theme.addItem("")
+ self.comboBox_theme.addItem("")
+ self.comboBox_theme.addItem("")
+ self.comboBox_theme.addItem("")
+ self.comboBox_theme.setObjectName(u"comboBox_theme")
+
+ self.formLayout_2.setWidget(0, QFormLayout.FieldRole, self.comboBox_theme)
+
+ self.checkBox_minitray = QCheckBox(self.tabBase)
+ self.checkBox_minitray.setObjectName(u"checkBox_minitray")
+ self.checkBox_minitray.setChecked(True)
+
+ self.formLayout_2.setWidget(6, QFormLayout.LabelRole, self.checkBox_minitray)
+
+ self.checkBox_startpage = QCheckBox(self.tabBase)
+ self.checkBox_startpage.setObjectName(u"checkBox_startpage")
+ self.checkBox_startpage.setChecked(True)
+
+ self.formLayout_2.setWidget(7, QFormLayout.LabelRole, self.checkBox_startpage)
+
+
+ self.verticalLayout_8.addLayout(self.formLayout_2)
+
+ self.verticalSpacer_2 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
+
+ self.verticalLayout_8.addItem(self.verticalSpacer_2)
+
+ self.tabWidget.addTab(self.tabBase, "")
+
+ self.horizontalLayout_3.addWidget(self.tabWidget)
+
+ self.stackedWidget.addWidget(self.page_general)
+ self.page_appearance = QWidget()
+ self.page_appearance.setObjectName(u"page_appearance")
+ self.verticalLayout_6 = QVBoxLayout(self.page_appearance)
+ self.verticalLayout_6.setObjectName(u"verticalLayout_6")
+ self.verticalLayout_5 = QVBoxLayout()
+ self.verticalLayout_5.setObjectName(u"verticalLayout_5")
+ self.checkBox_3 = QCheckBox(self.page_appearance)
+ self.checkBox_3.setObjectName(u"checkBox_3")
+ self.checkBox_3.setChecked(True)
+
+ self.verticalLayout_5.addWidget(self.checkBox_3)
+
+ self.horizontalLayout_11 = QHBoxLayout()
+ self.horizontalLayout_11.setObjectName(u"horizontalLayout_11")
+ self.label_10 = QLabel(self.page_appearance)
+ self.label_10.setObjectName(u"label_10")
+
+ self.horizontalLayout_11.addWidget(self.label_10)
+
+ self.pushButton = QPushButton(self.page_appearance)
+ self.pushButton.setObjectName(u"pushButton")
+
+ self.horizontalLayout_11.addWidget(self.pushButton)
+
+ self.horizontalSpacer_4 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_11.addItem(self.horizontalSpacer_4)
+
+
+ self.verticalLayout_5.addLayout(self.horizontalLayout_11)
+
+
+ self.verticalLayout_6.addLayout(self.verticalLayout_5)
+
+ self.horizontalLayout_6 = QHBoxLayout()
+ self.horizontalLayout_6.setObjectName(u"horizontalLayout_6")
+
+ self.verticalLayout_6.addLayout(self.horizontalLayout_6)
+
+ self.horizontalLayout_5 = QHBoxLayout()
+ self.horizontalLayout_5.setObjectName(u"horizontalLayout_5")
+ self.label_3 = QLabel(self.page_appearance)
+ self.label_3.setObjectName(u"label_3")
+
+ self.horizontalLayout_5.addWidget(self.label_3)
+
+ self.lineEdit_2 = QLineEdit(self.page_appearance)
+ self.lineEdit_2.setObjectName(u"lineEdit_2")
+ self.lineEdit_2.setMaximumSize(QSize(60, 16777215))
+
+ self.horizontalLayout_5.addWidget(self.lineEdit_2)
+
+ self.horizontalSpacer_3 = QSpacerItem(120, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_5.addItem(self.horizontalSpacer_3)
+
+
+ self.verticalLayout_6.addLayout(self.horizontalLayout_5)
+
+ self.horizontalLayout_4 = QHBoxLayout()
+ self.horizontalLayout_4.setObjectName(u"horizontalLayout_4")
+ self.label_2 = QLabel(self.page_appearance)
+ self.label_2.setObjectName(u"label_2")
+
+ self.horizontalLayout_4.addWidget(self.label_2)
+
+ self.comboBox = QComboBox(self.page_appearance)
+ self.comboBox.addItem("")
+ self.comboBox.setObjectName(u"comboBox")
+
+ self.horizontalLayout_4.addWidget(self.comboBox)
+
+ self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_4.addItem(self.horizontalSpacer_2)
+
+
+ self.verticalLayout_6.addLayout(self.horizontalLayout_4)
+
+ self.textEdit = QTextEdit(self.page_appearance)
+ self.textEdit.setObjectName(u"textEdit")
+ self.textEdit.setMaximumSize(QSize(16777215, 16777215))
+
+ self.verticalLayout_6.addWidget(self.textEdit)
+
+ self.stackedWidget.addWidget(self.page_appearance)
+ self.page = QWidget()
+ self.page.setObjectName(u"page")
+ self.verticalLayout_11 = QVBoxLayout(self.page)
+ self.verticalLayout_11.setObjectName(u"verticalLayout_11")
+ self.verticalLayout_11.setContentsMargins(0, 0, 0, 0)
+ self.widget_3 = QWidget(self.page)
+ self.widget_3.setObjectName(u"widget_3")
+ self.verticalLayout_9 = QVBoxLayout(self.widget_3)
+ self.verticalLayout_9.setObjectName(u"verticalLayout_9")
+ self.label_9 = QLabel(self.widget_3)
+ self.label_9.setObjectName(u"label_9")
+ font = QFont()
+ font.setBold(True)
+ font.setWeight(75)
+ self.label_9.setFont(font)
+
+ self.verticalLayout_9.addWidget(self.label_9)
+
+ self.line = QFrame(self.widget_3)
+ self.line.setObjectName(u"line")
+ self.line.setFrameShape(QFrame.HLine)
+ self.line.setFrameShadow(QFrame.Sunken)
+
+ self.verticalLayout_9.addWidget(self.line)
+
+ self.widget_4 = QWidget(self.widget_3)
+ self.widget_4.setObjectName(u"widget_4")
+ self.widget_4.setMaximumSize(QSize(16777215, 150))
+ self.verticalLayout_13 = QVBoxLayout(self.widget_4)
+ self.verticalLayout_13.setObjectName(u"verticalLayout_13")
+ self.horizontalLayout_2 = QHBoxLayout()
+ self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
+ self.tableWidget = QTableWidget(self.widget_4)
+ if (self.tableWidget.columnCount() < 2):
+ self.tableWidget.setColumnCount(2)
+ __qtablewidgetitem = QTableWidgetItem()
+ self.tableWidget.setHorizontalHeaderItem(0, __qtablewidgetitem)
+ __qtablewidgetitem1 = QTableWidgetItem()
+ self.tableWidget.setHorizontalHeaderItem(1, __qtablewidgetitem1)
+ self.tableWidget.setObjectName(u"tableWidget")
+
+ self.horizontalLayout_2.addWidget(self.tableWidget)
+
+ self.verticalLayout_7 = QVBoxLayout()
+ self.verticalLayout_7.setObjectName(u"verticalLayout_7")
+ self.pushButton_2 = QPushButton(self.widget_4)
+ self.pushButton_2.setObjectName(u"pushButton_2")
+
+ self.verticalLayout_7.addWidget(self.pushButton_2)
+
+ self.pushButton_3 = QPushButton(self.widget_4)
+ self.pushButton_3.setObjectName(u"pushButton_3")
+
+ self.verticalLayout_7.addWidget(self.pushButton_3)
+
+ self.pushButton_4 = QPushButton(self.widget_4)
+ self.pushButton_4.setObjectName(u"pushButton_4")
+
+ self.verticalLayout_7.addWidget(self.pushButton_4)
+
+ self.pushButton_5 = QPushButton(self.widget_4)
+ self.pushButton_5.setObjectName(u"pushButton_5")
+
+ self.verticalLayout_7.addWidget(self.pushButton_5)
+
+ self.pushButton_6 = QPushButton(self.widget_4)
+ self.pushButton_6.setObjectName(u"pushButton_6")
+
+ self.verticalLayout_7.addWidget(self.pushButton_6)
+
+ self.verticalSpacer_4 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
+
+ self.verticalLayout_7.addItem(self.verticalSpacer_4)
+
+ self.verticalSpacer_3 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
+
+ self.verticalLayout_7.addItem(self.verticalSpacer_3)
+
+
+ self.horizontalLayout_2.addLayout(self.verticalLayout_7)
+
+
+ self.verticalLayout_13.addLayout(self.horizontalLayout_2)
+
+
+ self.verticalLayout_9.addWidget(self.widget_4)
+
+ self.tabWidget_2 = QTabWidget(self.widget_3)
+ self.tabWidget_2.setObjectName(u"tabWidget_2")
+ self.tab = QWidget()
+ self.tab.setObjectName(u"tab")
+ self.verticalLayout_12 = QVBoxLayout(self.tab)
+ self.verticalLayout_12.setObjectName(u"verticalLayout_12")
+ self.horizontalLayout_8 = QHBoxLayout()
+ self.horizontalLayout_8.setObjectName(u"horizontalLayout_8")
+ self.tableWidget_2 = QTableWidget(self.tab)
+ if (self.tableWidget_2.columnCount() < 2):
+ self.tableWidget_2.setColumnCount(2)
+ __qtablewidgetitem2 = QTableWidgetItem()
+ self.tableWidget_2.setHorizontalHeaderItem(0, __qtablewidgetitem2)
+ __qtablewidgetitem3 = QTableWidgetItem()
+ self.tableWidget_2.setHorizontalHeaderItem(1, __qtablewidgetitem3)
+ self.tableWidget_2.setObjectName(u"tableWidget_2")
+
+ self.horizontalLayout_8.addWidget(self.tableWidget_2)
+
+ self.verticalLayout_10 = QVBoxLayout()
+ self.verticalLayout_10.setObjectName(u"verticalLayout_10")
+ self.pushButton_7 = QPushButton(self.tab)
+ self.pushButton_7.setObjectName(u"pushButton_7")
+
+ self.verticalLayout_10.addWidget(self.pushButton_7)
+
+ self.pushButton_8 = QPushButton(self.tab)
+ self.pushButton_8.setObjectName(u"pushButton_8")
+
+ self.verticalLayout_10.addWidget(self.pushButton_8)
+
+ self.pushButton_9 = QPushButton(self.tab)
+ self.pushButton_9.setObjectName(u"pushButton_9")
+
+ self.verticalLayout_10.addWidget(self.pushButton_9)
+
+ self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
+
+ self.verticalLayout_10.addItem(self.verticalSpacer)
+
+
+ self.horizontalLayout_8.addLayout(self.verticalLayout_10)
+
+
+ self.verticalLayout_12.addLayout(self.horizontalLayout_8)
+
+ self.tabWidget_2.addTab(self.tab, "")
+
+ self.verticalLayout_9.addWidget(self.tabWidget_2)
+
+
+ self.verticalLayout_11.addWidget(self.widget_3)
+
+ self.stackedWidget.addWidget(self.page)
+ self.page_format = QWidget()
+ self.page_format.setObjectName(u"page_format")
+ self.verticalLayout_3 = QVBoxLayout(self.page_format)
+ self.verticalLayout_3.setObjectName(u"verticalLayout_3")
+ self.formLayout = QFormLayout()
+ self.formLayout.setObjectName(u"formLayout")
+ self.label_4 = QLabel(self.page_format)
+ self.label_4.setObjectName(u"label_4")
+
+ self.formLayout.setWidget(0, QFormLayout.LabelRole, self.label_4)
+
+ self.comboBox_2 = QComboBox(self.page_format)
+ self.comboBox_2.addItem("")
+ self.comboBox_2.addItem("")
+ self.comboBox_2.addItem("")
+ self.comboBox_2.setObjectName(u"comboBox_2")
+
+ self.formLayout.setWidget(0, QFormLayout.FieldRole, self.comboBox_2)
+
+ self.comboBox_3 = QComboBox(self.page_format)
+ self.comboBox_3.addItem("")
+ self.comboBox_3.addItem("")
+ self.comboBox_3.addItem("")
+ self.comboBox_3.setObjectName(u"comboBox_3")
+
+ self.formLayout.setWidget(1, QFormLayout.FieldRole, self.comboBox_3)
+
+ self.label_5 = QLabel(self.page_format)
+ self.label_5.setObjectName(u"label_5")
+
+ self.formLayout.setWidget(1, QFormLayout.LabelRole, self.label_5)
+
+ self.comboBox_4 = QComboBox(self.page_format)
+ self.comboBox_4.addItem("")
+ self.comboBox_4.addItem("")
+ self.comboBox_4.addItem("")
+ self.comboBox_4.setObjectName(u"comboBox_4")
+
+ self.formLayout.setWidget(2, QFormLayout.FieldRole, self.comboBox_4)
+
+ self.label_6 = QLabel(self.page_format)
+ self.label_6.setObjectName(u"label_6")
+
+ self.formLayout.setWidget(2, QFormLayout.LabelRole, self.label_6)
+
+ self.comboBox_5 = QComboBox(self.page_format)
+ self.comboBox_5.addItem("")
+ self.comboBox_5.addItem("")
+ self.comboBox_5.addItem("")
+ self.comboBox_5.addItem("")
+ self.comboBox_5.setObjectName(u"comboBox_5")
+
+ self.formLayout.setWidget(3, QFormLayout.FieldRole, self.comboBox_5)
+
+ self.label_7 = QLabel(self.page_format)
+ self.label_7.setObjectName(u"label_7")
+
+ self.formLayout.setWidget(3, QFormLayout.LabelRole, self.label_7)
+
+ self.comboBox_6 = QComboBox(self.page_format)
+ self.comboBox_6.addItem("")
+ self.comboBox_6.addItem("")
+ self.comboBox_6.setObjectName(u"comboBox_6")
+
+ self.formLayout.setWidget(4, QFormLayout.FieldRole, self.comboBox_6)
+
+ self.label_8 = QLabel(self.page_format)
+ self.label_8.setObjectName(u"label_8")
+
+ self.formLayout.setWidget(4, QFormLayout.LabelRole, self.label_8)
+
+
+ self.verticalLayout_3.addLayout(self.formLayout)
+
+ self.stackedWidget.addWidget(self.page_format)
+ self.splitter.addWidget(self.stackedWidget)
+
+ self.verticalLayout_4.addWidget(self.splitter)
+
+ self.widget = QWidget(Form)
+ self.widget.setObjectName(u"widget")
+ self.widget.setMaximumSize(QSize(16777215, 50))
+ self.horizontalLayout_7 = QHBoxLayout(self.widget)
+ self.horizontalLayout_7.setObjectName(u"horizontalLayout_7")
+ self.horizontalLayout_7.setContentsMargins(0, 0, 0, 0)
+ self.horizontalLayout = QHBoxLayout()
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.pushButton_help = QPushButton(self.widget)
+ self.pushButton_help.setObjectName(u"pushButton_help")
+
+ self.horizontalLayout.addWidget(self.pushButton_help)
+
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout.addItem(self.horizontalSpacer)
+
+ self.pushButton_ok = QPushButton(self.widget)
+ self.pushButton_ok.setObjectName(u"pushButton_ok")
+
+ self.horizontalLayout.addWidget(self.pushButton_ok)
+
+ self.pushButton_cancel = QPushButton(self.widget)
+ self.pushButton_cancel.setObjectName(u"pushButton_cancel")
+
+ self.horizontalLayout.addWidget(self.pushButton_cancel)
+
+
+ self.horizontalLayout_7.addLayout(self.horizontalLayout)
+
+
+ self.verticalLayout_4.addWidget(self.widget)
+
+
+ self.retranslateUi(Form)
+
+ self.stackedWidget.setCurrentIndex(0)
+ self.tabWidget.setCurrentIndex(0)
+ self.tabWidget_2.setCurrentIndex(0)
+
+
+ QMetaObject.connectSlotsByName(Form)
+ # setupUi
+
+ def retranslateUi(self, Form):
+ Form.setWindowTitle(QCoreApplication.translate("Form", u"Preferences", None))
+ self.lineEdit.setPlaceholderText(QCoreApplication.translate("Form", u"type filter text", None))
+
+ __sortingEnabled = self.listWidget.isSortingEnabled()
+ self.listWidget.setSortingEnabled(False)
+ ___qlistwidgetitem = self.listWidget.item(0)
+ ___qlistwidgetitem.setText(QCoreApplication.translate("Form", u"General", None));
+ ___qlistwidgetitem1 = self.listWidget.item(1)
+ ___qlistwidgetitem1.setText(QCoreApplication.translate("Form", u"Appearance", None));
+ ___qlistwidgetitem2 = self.listWidget.item(2)
+ ___qlistwidgetitem2.setText(QCoreApplication.translate("Form", u"Interpreters", None));
+ ___qlistwidgetitem3 = self.listWidget.item(3)
+ ___qlistwidgetitem3.setText(QCoreApplication.translate("Form", u"Editor", None));
+ ___qlistwidgetitem4 = self.listWidget.item(4)
+ ___qlistwidgetitem4.setText(QCoreApplication.translate("Form", u"Fonts", None));
+ self.listWidget.setSortingEnabled(__sortingEnabled)
+
+ self.label.setText(QCoreApplication.translate("Form", u"Work Directory", None))
+ self.label_15.setText(QCoreApplication.translate("Form", u"Output Directory", None))
+ self.checkBox.setText(QCoreApplication.translate("Form", u"Check upd on startup", None))
+ self.checkBox_2.setText(QCoreApplication.translate("Form", u"Start with system", None))
+ self.label_16.setText(QCoreApplication.translate("Form", u"UI Language", None))
+ self.comboBox_language.setItemText(0, QCoreApplication.translate("Form", u"English", None))
+ self.comboBox_language.setItemText(1, QCoreApplication.translate("Form", u"\u7b80\u4f53\u4e2d\u6587", None))
+
+ self.label_11.setText(QCoreApplication.translate("Form", u"Encoding", None))
+ self.comboBox_encoding.setItemText(0, QCoreApplication.translate("Form", u"utf-8", None))
+ self.comboBox_encoding.setItemText(1, QCoreApplication.translate("Form", u"gb2312", None))
+
+ self.toolButton_workspace.setText(QCoreApplication.translate("Form", u"...", None))
+ self.toolButton_output.setText(QCoreApplication.translate("Form", u"...", None))
+ self.label_theme.setText(QCoreApplication.translate("Form", u"UI Theme", None))
+ self.comboBox_theme.setItemText(0, QCoreApplication.translate("Form", u"Fusion", None))
+ self.comboBox_theme.setItemText(1, QCoreApplication.translate("Form", u"Qdarkstyle", None))
+ self.comboBox_theme.setItemText(2, QCoreApplication.translate("Form", u"windowsvista", None))
+ self.comboBox_theme.setItemText(3, QCoreApplication.translate("Form", u"Windows", None))
+
+ self.checkBox_minitray.setText(QCoreApplication.translate("Form", u"Minimize to tray", None))
+ self.checkBox_startpage.setText(QCoreApplication.translate("Form", u"Display StartPage", None))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabBase), QCoreApplication.translate("Form", u"Basic", None))
+ self.checkBox_3.setText(QCoreApplication.translate("Form", u"Interlaced coloring", None))
+ self.label_10.setText(QCoreApplication.translate("Form", u"Table Header Background:", None))
+ self.pushButton.setText(QCoreApplication.translate("Form", u"Color", None))
+ self.label_3.setText(QCoreApplication.translate("Form", u"Size:", None))
+ self.lineEdit_2.setText(QCoreApplication.translate("Form", u"15", None))
+ self.label_2.setText(QCoreApplication.translate("Form", u"Font:", None))
+ self.comboBox.setItemText(0, QCoreApplication.translate("Form", u"Source Code Pro", None))
+
+ self.textEdit.setHtml(QCoreApplication.translate("Form", u"\n"
+"\n"
+"Patata is a full-featured IDE
\n"
+"with a high level of usability and outstanding
\n"
+"advanced code editing and refactoring support.
\n"
+"
\n"
+"abcdefghijklmnopqrstuvwxyz 0123456789 (){}[]
\n"
+"ABCDEFGHIJKLMNOPQRSTUVWXYZ +-*/= .,;:!? #&$%@|^
", None))
+ self.label_9.setText(QCoreApplication.translate("Form", u"Python Interpreters", None))
+ ___qtablewidgetitem = self.tableWidget.horizontalHeaderItem(0)
+ ___qtablewidgetitem.setText(QCoreApplication.translate("Form", u"Name", None));
+ ___qtablewidgetitem1 = self.tableWidget.horizontalHeaderItem(1)
+ ___qtablewidgetitem1.setText(QCoreApplication.translate("Form", u"Location", None));
+ self.pushButton_2.setText(QCoreApplication.translate("Form", u"New ...", None))
+ self.pushButton_3.setText(QCoreApplication.translate("Form", u"Up", None))
+ self.pushButton_4.setText(QCoreApplication.translate("Form", u"Down", None))
+ self.pushButton_5.setText(QCoreApplication.translate("Form", u"Remove", None))
+ self.pushButton_6.setText(QCoreApplication.translate("Form", u"Config Conda", None))
+ ___qtablewidgetitem2 = self.tableWidget_2.horizontalHeaderItem(0)
+ ___qtablewidgetitem2.setText(QCoreApplication.translate("Form", u"Name", None));
+ ___qtablewidgetitem3 = self.tableWidget_2.horizontalHeaderItem(1)
+ ___qtablewidgetitem3.setText(QCoreApplication.translate("Form", u"Version", None));
+ self.pushButton_7.setText(QCoreApplication.translate("Form", u"Manage with pip", None))
+ self.pushButton_8.setText(QCoreApplication.translate("Form", u"Manage with conda", None))
+ self.pushButton_9.setText(QCoreApplication.translate("Form", u"Manage with pipenv", None))
+ self.tabWidget_2.setTabText(self.tabWidget_2.indexOf(self.tab), QCoreApplication.translate("Form", u"Packages", None))
+ self.label_4.setText(QCoreApplication.translate("Form", u"\u65e5\u671f\u683c\u5f0f:", None))
+ self.comboBox_2.setItemText(0, QCoreApplication.translate("Form", u"2020-01-01", None))
+ self.comboBox_2.setItemText(1, QCoreApplication.translate("Form", u"2020/01/01", None))
+ self.comboBox_2.setItemText(2, "")
+
+ self.comboBox_3.setItemText(0, QCoreApplication.translate("Form", u"15:30:01(24-\u5c0f\u65f6\u5236)", None))
+ self.comboBox_3.setItemText(1, QCoreApplication.translate("Form", u"3:30:01 PM(12-\u5c0f\u65f6\u5236)", None))
+ self.comboBox_3.setItemText(2, "")
+
+ self.label_5.setText(QCoreApplication.translate("Form", u"\u65f6\u95f4\u683c\u5f0f:", None))
+ self.comboBox_4.setItemText(0, QCoreApplication.translate("Form", u"\u7f8e\u5143US Dollar", None))
+ self.comboBox_4.setItemText(1, QCoreApplication.translate("Form", u"\u4eba\u6c11\u5e01Chinese Yuan", None))
+ self.comboBox_4.setItemText(2, "")
+
+ self.label_6.setText(QCoreApplication.translate("Form", u"\u8d27\u5e01\u5355\u4f4d:", None))
+ self.comboBox_5.setItemText(0, QCoreApplication.translate("Form", u"\uffe5", None))
+ self.comboBox_5.setItemText(1, QCoreApplication.translate("Form", u"CNY", None))
+ self.comboBox_5.setItemText(2, QCoreApplication.translate("Form", u"$", None))
+ self.comboBox_5.setItemText(3, QCoreApplication.translate("Form", u"USD", None))
+
+ self.label_7.setText(QCoreApplication.translate("Form", u"\u8d27\u5e01\u7b26\u53f7:", None))
+ self.comboBox_6.setItemText(0, QCoreApplication.translate("Form", u"\u5217\u6807\u9898\u5185", None))
+ self.comboBox_6.setItemText(1, QCoreApplication.translate("Form", u"\u5355\u5143\u683c\u5185", None))
+
+ self.label_8.setText(QCoreApplication.translate("Form", u"\u8d27\u5e01\u7b26\u53f7\u4f4d\u4e8e:", None))
+ self.pushButton_help.setText(QCoreApplication.translate("Form", u"Help", None))
+ self.pushButton_ok.setText(QCoreApplication.translate("Form", u"Ok", None))
+ self.pushButton_cancel.setText(QCoreApplication.translate("Form", u"Cancel", None))
+ # retranslateUi
+
diff --git a/pyminer/lib/ui/ui_preferences.ui b/pyminer/lib/ui/ui_preferences.ui
new file mode 100644
index 0000000000000000000000000000000000000000..4fb629cdaacca08a1c706a4a55e95132df2f5e80
--- /dev/null
+++ b/pyminer/lib/ui/ui_preferences.ui
@@ -0,0 +1,857 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 705
+ 515
+
+
+
+ Preferences
+
+
+
+ 9
+
+
+ 0
+
+
+ 9
+
+
+ 9
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+
+ 200
+ 16777215
+
+
+
+
+ 0
+
+
-
+
+
-
+
+
+ type filter text
+
+
+
+ -
+
+
+
+ 200
+ 16777215
+
+
+
-
+
+ General
+
+
+ -
+
+ Appearance
+
+
+ -
+
+ Interpreters
+
+
+ -
+
+ Editor
+
+
+ -
+
+ Fonts
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+ QTabWidget::North
+
+
+ 0
+
+
+
+ Basic
+
+
+
-
+
+
-
+
+
+ Work Directory
+
+
+
+ -
+
+
+ Output Directory
+
+
+
+ -
+
+
+ Check upd on startup
+
+
+ true
+
+
+
+ -
+
+
+ Start with system
+
+
+
+ -
+
+
+ UI Language
+
+
+
+ -
+
+
-
+
+ English
+
+
+ -
+
+ 简体中文
+
+
+
+
+ -
+
+
+ Encoding
+
+
+
+ -
+
+
-
+
+ utf-8
+
+
+ -
+
+ gb2312
+
+
+
+
+ -
+
+
-
+
+
+ -
+
+
+ ...
+
+
+
+
+
+ -
+
+
-
+
+
+ -
+
+
+ ...
+
+
+
+
+
+ -
+
+
+ UI Theme
+
+
+
+ -
+
+
-
+
+ Fusion
+
+
+ -
+
+ Qdarkstyle
+
+
+ -
+
+ windowsvista
+
+
+ -
+
+ Windows
+
+
+
+
+ -
+
+
+ Minimize to tray
+
+
+ true
+
+
+
+ -
+
+
+ Display StartPage
+
+
+ true
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Interlaced coloring
+
+
+ true
+
+
+
+ -
+
+
-
+
+
+ Table Header Background:
+
+
+
+ -
+
+
+ Color
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+ -
+
+
+ -
+
+
-
+
+
+ Size:
+
+
+
+ -
+
+
+
+ 60
+ 16777215
+
+
+
+ 15
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 120
+ 20
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Font:
+
+
+
+ -
+
+
-
+
+ Source Code Pro
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+
+ 16777215
+ 16777215
+
+
+
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
+<html><head><meta name="qrichtext" content="1" /><style type="text/css">
+p, li { white-space: pre-wrap; }
+</style></head><body style=" font-family:'SimSun'; font-size:9pt; font-weight:400; font-style:normal;">
+<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Patata is a full-featured IDE</p>
+<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">with a high level of usability and outstanding</p>
+<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">advanced code editing and refactoring support.</p>
+<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>
+<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">abcdefghijklmnopqrstuvwxyz 0123456789 (){}[]</p>
+<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">ABCDEFGHIJKLMNOPQRSTUVWXYZ +-*/= .,;:!? #&$%@|^</p></body></html>
+
+
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+
-
+
+
+
+ 75
+ true
+
+
+
+ Python Interpreters
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 16777215
+ 150
+
+
+
+
-
+
+
-
+
+
+
+ Name
+
+
+
+
+ Location
+
+
+
+
+ -
+
+
-
+
+
+ New ...
+
+
+
+ -
+
+
+ Up
+
+
+
+ -
+
+
+ Down
+
+
+
+ -
+
+
+ Remove
+
+
+
+ -
+
+
+ Config Conda
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+ 0
+
+
+
+ Packages
+
+
+
-
+
+
-
+
+
+
+ Name
+
+
+
+
+ Version
+
+
+
+
+ -
+
+
-
+
+
+ Manage with pip
+
+
+
+ -
+
+
+ Manage with conda
+
+
+
+ -
+
+
+ Manage with pipenv
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+ 日期格式:
+
+
+
+ -
+
+
-
+
+ 2020-01-01
+
+
+ -
+
+ 2020/01/01
+
+
+ -
+
+
+
+
+
+
+ -
+
+
-
+
+ 15:30:01(24-小时制)
+
+
+ -
+
+ 3:30:01 PM(12-小时制)
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+ 时间格式:
+
+
+
+ -
+
+
-
+
+ 美元US Dollar
+
+
+ -
+
+ 人民币Chinese Yuan
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+ 货币单位:
+
+
+
+ -
+
+
-
+
+ ¥
+
+
+ -
+
+ CNY
+
+
+ -
+
+ $
+
+
+ -
+
+ USD
+
+
+
+
+ -
+
+
+ 货币符号:
+
+
+
+ -
+
+
-
+
+ 列标题内
+
+
+ -
+
+ 单元格内
+
+
+
+
+ -
+
+
+ 货币符号位于:
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ 16777215
+ 50
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
-
+
+
+ Help
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Ok
+
+
+
+ -
+
+
+ Cancel
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pyminer/lib/ui/ui_project_wizard.py b/pyminer/lib/ui/ui_project_wizard.py
new file mode 100644
index 0000000000000000000000000000000000000000..0e37d326b9eb58265d9431b2debf0acdce0bdd89
--- /dev/null
+++ b/pyminer/lib/ui/ui_project_wizard.py
@@ -0,0 +1,358 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'ui_project_wizard.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import *
+from PySide2.QtGui import *
+from PySide2.QtWidgets import *
+
+import pyqtsource_rc
+
+class Ui_Wizard(object):
+ def setupUi(self, Wizard):
+ if not Wizard.objectName():
+ Wizard.setObjectName(u"Wizard")
+ Wizard.resize(736, 505)
+ Wizard.setWizardStyle(QWizard.ModernStyle)
+ Wizard.setOptions(QWizard.HelpButtonOnRight|QWizard.NoBackButtonOnStartPage)
+ self.wizardPage1 = QWizardPage()
+ self.wizardPage1.setObjectName(u"wizardPage1")
+ self.horizontalLayout = QHBoxLayout(self.wizardPage1)
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.horizontalLayout.setContentsMargins(9, 9, 9, 9)
+ self.widget_left = QWidget(self.wizardPage1)
+ self.widget_left.setObjectName(u"widget_left")
+ self.widget_left.setMinimumSize(QSize(200, 0))
+ self.widget_left.setMaximumSize(QSize(180, 16777215))
+ self.verticalLayout = QVBoxLayout(self.widget_left)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.verticalLayout.setContentsMargins(5, 5, 5, 5)
+ self.label = QLabel(self.widget_left)
+ self.label.setObjectName(u"label")
+
+ self.verticalLayout.addWidget(self.label)
+
+ self.line = QFrame(self.widget_left)
+ self.line.setObjectName(u"line")
+ self.line.setFrameShape(QFrame.HLine)
+ self.line.setFrameShadow(QFrame.Sunken)
+
+ self.verticalLayout.addWidget(self.line)
+
+ self.listWidget = QListWidget(self.widget_left)
+ font = QFont()
+ font.setBold(True)
+ font.setWeight(75)
+ __qlistwidgetitem = QListWidgetItem(self.listWidget)
+ __qlistwidgetitem.setFont(font);
+ QListWidgetItem(self.listWidget)
+ self.listWidget.setObjectName(u"listWidget")
+ self.listWidget.setStyleSheet(u"border:0px;")
+
+ self.verticalLayout.addWidget(self.listWidget)
+
+ self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
+
+ self.verticalLayout.addItem(self.verticalSpacer)
+
+
+ self.horizontalLayout.addWidget(self.widget_left)
+
+ self.widget_right = QWidget(self.wizardPage1)
+ self.widget_right.setObjectName(u"widget_right")
+ self.verticalLayout_4 = QVBoxLayout(self.widget_right)
+ self.verticalLayout_4.setSpacing(5)
+ self.verticalLayout_4.setObjectName(u"verticalLayout_4")
+ self.verticalLayout_4.setContentsMargins(0, 0, 0, 0)
+ self.horizontalLayout_2 = QHBoxLayout()
+ self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
+ self.label_11 = QLabel(self.widget_right)
+ self.label_11.setObjectName(u"label_11")
+ self.label_11.setMaximumSize(QSize(15, 15))
+ self.label_11.setPixmap(QPixmap(u":/color/theme/default/icons/search.svg"))
+ self.label_11.setScaledContents(True)
+
+ self.horizontalLayout_2.addWidget(self.label_11)
+
+ self.label_2 = QLabel(self.widget_right)
+ self.label_2.setObjectName(u"label_2")
+
+ self.horizontalLayout_2.addWidget(self.label_2)
+
+ self.lineEdit = QLineEdit(self.widget_right)
+ self.lineEdit.setObjectName(u"lineEdit")
+
+ self.horizontalLayout_2.addWidget(self.lineEdit)
+
+
+ self.verticalLayout_4.addLayout(self.horizontalLayout_2)
+
+ self.horizontalLayout_3 = QHBoxLayout()
+ self.horizontalLayout_3.setSpacing(9)
+ self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
+ self.horizontalLayout_3.setContentsMargins(0, -1, -1, -1)
+ self.widget_3 = QWidget(self.widget_right)
+ self.widget_3.setObjectName(u"widget_3")
+ self.widget_3.setMaximumSize(QSize(180, 16777215))
+ self.verticalLayout_2 = QVBoxLayout(self.widget_3)
+ self.verticalLayout_2.setSpacing(5)
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
+ self.label_3 = QLabel(self.widget_3)
+ self.label_3.setObjectName(u"label_3")
+
+ self.verticalLayout_2.addWidget(self.label_3)
+
+ self.treeWidget = QTreeWidget(self.widget_3)
+ icon = QIcon()
+ icon.addFile(u":/color/theme/default/icons/folder_yellow.svg", QSize(), QIcon.Normal, QIcon.Off)
+ __qtreewidgetitem = QTreeWidgetItem(self.treeWidget)
+ __qtreewidgetitem.setIcon(0, icon);
+ self.treeWidget.setObjectName(u"treeWidget")
+ self.treeWidget.header().setVisible(False)
+
+ self.verticalLayout_2.addWidget(self.treeWidget)
+
+
+ self.horizontalLayout_3.addWidget(self.widget_3)
+
+ self.widget_4 = QWidget(self.widget_right)
+ self.widget_4.setObjectName(u"widget_4")
+ self.widget_4.setMaximumSize(QSize(16777215, 16777215))
+ self.verticalLayout_3 = QVBoxLayout(self.widget_4)
+ self.verticalLayout_3.setObjectName(u"verticalLayout_3")
+ self.verticalLayout_3.setContentsMargins(0, 0, 0, 0)
+ self.label_4 = QLabel(self.widget_4)
+ self.label_4.setObjectName(u"label_4")
+
+ self.verticalLayout_3.addWidget(self.label_4)
+
+ self.file_list = QListWidget(self.widget_4)
+ icon1 = QIcon()
+ icon1.addFile(u":/color/theme/default/icons/python.svg", QSize(), QIcon.Normal, QIcon.Off)
+ __qlistwidgetitem1 = QListWidgetItem(self.file_list)
+ __qlistwidgetitem1.setIcon(icon1);
+ __qlistwidgetitem2 = QListWidgetItem(self.file_list)
+ __qlistwidgetitem2.setIcon(icon1);
+ __qlistwidgetitem3 = QListWidgetItem(self.file_list)
+ __qlistwidgetitem3.setIcon(icon1);
+ __qlistwidgetitem4 = QListWidgetItem(self.file_list)
+ __qlistwidgetitem4.setIcon(icon1);
+ self.file_list.setObjectName(u"file_list")
+
+ self.verticalLayout_3.addWidget(self.file_list)
+
+
+ self.horizontalLayout_3.addWidget(self.widget_4)
+
+
+ self.verticalLayout_4.addLayout(self.horizontalLayout_3)
+
+ self.plainTextEdit = QPlainTextEdit(self.widget_right)
+ self.plainTextEdit.setObjectName(u"plainTextEdit")
+ self.plainTextEdit.setMaximumSize(QSize(16777215, 90))
+ self.plainTextEdit.setStyleSheet(u"background-color: rgb(243, 243, 243);")
+ self.plainTextEdit.setReadOnly(True)
+
+ self.verticalLayout_4.addWidget(self.plainTextEdit)
+
+
+ self.horizontalLayout.addWidget(self.widget_right)
+
+ Wizard.addPage(self.wizardPage1)
+ self.wizardPage2 = QWizardPage()
+ self.wizardPage2.setObjectName(u"wizardPage2")
+ self.horizontalLayout_6 = QHBoxLayout(self.wizardPage2)
+ self.horizontalLayout_6.setObjectName(u"horizontalLayout_6")
+ self.widget_left_2 = QWidget(self.wizardPage2)
+ self.widget_left_2.setObjectName(u"widget_left_2")
+ self.widget_left_2.setMinimumSize(QSize(200, 0))
+ self.widget_left_2.setMaximumSize(QSize(180, 16777215))
+ self.verticalLayout_5 = QVBoxLayout(self.widget_left_2)
+ self.verticalLayout_5.setObjectName(u"verticalLayout_5")
+ self.verticalLayout_5.setContentsMargins(5, 5, 5, 5)
+ self.label_5 = QLabel(self.widget_left_2)
+ self.label_5.setObjectName(u"label_5")
+
+ self.verticalLayout_5.addWidget(self.label_5)
+
+ self.line_2 = QFrame(self.widget_left_2)
+ self.line_2.setObjectName(u"line_2")
+ self.line_2.setFrameShape(QFrame.HLine)
+ self.line_2.setFrameShadow(QFrame.Sunken)
+
+ self.verticalLayout_5.addWidget(self.line_2)
+
+ self.listWidget_2 = QListWidget(self.widget_left_2)
+ font1 = QFont()
+ font1.setBold(False)
+ font1.setWeight(50)
+ __qlistwidgetitem5 = QListWidgetItem(self.listWidget_2)
+ __qlistwidgetitem5.setFont(font1);
+ __qlistwidgetitem6 = QListWidgetItem(self.listWidget_2)
+ __qlistwidgetitem6.setFont(font);
+ self.listWidget_2.setObjectName(u"listWidget_2")
+ self.listWidget_2.setStyleSheet(u"border:0px;")
+
+ self.verticalLayout_5.addWidget(self.listWidget_2)
+
+ self.verticalSpacer_2 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
+
+ self.verticalLayout_5.addItem(self.verticalSpacer_2)
+
+
+ self.horizontalLayout_6.addWidget(self.widget_left_2)
+
+ self.widget_right_2 = QWidget(self.wizardPage2)
+ self.widget_right_2.setObjectName(u"widget_right_2")
+ self.verticalLayout_6 = QVBoxLayout(self.widget_right_2)
+ self.verticalLayout_6.setObjectName(u"verticalLayout_6")
+ self.label_9 = QLabel(self.widget_right_2)
+ self.label_9.setObjectName(u"label_9")
+
+ self.verticalLayout_6.addWidget(self.label_9)
+
+ self.line_3 = QFrame(self.widget_right_2)
+ self.line_3.setObjectName(u"line_3")
+ self.line_3.setFrameShape(QFrame.HLine)
+ self.line_3.setFrameShadow(QFrame.Sunken)
+
+ self.verticalLayout_6.addWidget(self.line_3)
+
+ self.formLayout_2 = QFormLayout()
+ self.formLayout_2.setObjectName(u"formLayout_2")
+ self.projectNameText = QLabel(self.widget_right_2)
+ self.projectNameText.setObjectName(u"projectNameText")
+
+ self.formLayout_2.setWidget(0, QFormLayout.LabelRole, self.projectNameText)
+
+ self.projectNameLineEdit = QLineEdit(self.widget_right_2)
+ self.projectNameLineEdit.setObjectName(u"projectNameLineEdit")
+ self.projectNameLineEdit.setClearButtonEnabled(False)
+
+ self.formLayout_2.setWidget(0, QFormLayout.FieldRole, self.projectNameLineEdit)
+
+ self.projectDirectory = QLabel(self.widget_right_2)
+ self.projectDirectory.setObjectName(u"projectDirectory")
+
+ self.formLayout_2.setWidget(1, QFormLayout.LabelRole, self.projectDirectory)
+
+ self.horizontalLayout_4 = QHBoxLayout()
+ self.horizontalLayout_4.setObjectName(u"horizontalLayout_4")
+ self.projectDirectoryEditLine = QLineEdit(self.widget_right_2)
+ self.projectDirectoryEditLine.setObjectName(u"projectDirectoryEditLine")
+ self.projectDirectoryEditLine.setStyleSheet(u"")
+ self.projectDirectoryEditLine.setReadOnly(True)
+
+ self.horizontalLayout_4.addWidget(self.projectDirectoryEditLine)
+
+ self.toolButton = QToolButton(self.widget_right_2)
+ self.toolButton.setObjectName(u"toolButton")
+
+ self.horizontalLayout_4.addWidget(self.toolButton)
+
+
+ self.formLayout_2.setLayout(1, QFormLayout.FieldRole, self.horizontalLayout_4)
+
+ self.absoluteDirectory = QLabel(self.widget_right_2)
+ self.absoluteDirectory.setObjectName(u"absoluteDirectory")
+
+ self.formLayout_2.setWidget(2, QFormLayout.LabelRole, self.absoluteDirectory)
+
+ self.absoluteDirectoryEditLine = QLineEdit(self.widget_right_2)
+ self.absoluteDirectoryEditLine.setObjectName(u"absoluteDirectoryEditLine")
+ self.absoluteDirectoryEditLine.setStyleSheet(u"")
+ self.absoluteDirectoryEditLine.setReadOnly(True)
+
+ self.formLayout_2.setWidget(2, QFormLayout.FieldRole, self.absoluteDirectoryEditLine)
+
+ self.verticalSpacer_3 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
+
+ self.formLayout_2.setItem(4, QFormLayout.LabelRole, self.verticalSpacer_3)
+
+ self.warningLabel = QLabel(self.widget_right_2)
+ self.warningLabel.setObjectName(u"warningLabel")
+ self.warningLabel.setFont(font)
+ self.warningLabel.setCursor(QCursor(Qt.IBeamCursor))
+ self.warningLabel.setStyleSheet(u"color: rgb(255, 102, 0);")
+ self.warningLabel.setTextFormat(Qt.AutoText)
+
+ self.formLayout_2.setWidget(3, QFormLayout.FieldRole, self.warningLabel)
+
+
+ self.verticalLayout_6.addLayout(self.formLayout_2)
+
+
+ self.horizontalLayout_6.addWidget(self.widget_right_2)
+
+ Wizard.addPage(self.wizardPage2)
+
+ self.retranslateUi(Wizard)
+
+ QMetaObject.connectSlotsByName(Wizard)
+ # setupUi
+
+ def retranslateUi(self, Wizard):
+ Wizard.setWindowTitle(QCoreApplication.translate("Wizard", u"New Project", None))
+ self.label.setText(QCoreApplication.translate("Wizard", u"Steps", None))
+
+ __sortingEnabled = self.listWidget.isSortingEnabled()
+ self.listWidget.setSortingEnabled(False)
+ ___qlistwidgetitem = self.listWidget.item(0)
+ ___qlistwidgetitem.setText(QCoreApplication.translate("Wizard", u"1. Select Project Type", None));
+ ___qlistwidgetitem1 = self.listWidget.item(1)
+ ___qlistwidgetitem1.setText(QCoreApplication.translate("Wizard", u"2. Configure Project", None));
+ self.listWidget.setSortingEnabled(__sortingEnabled)
+
+ self.label_11.setText("")
+ self.label_2.setText(QCoreApplication.translate("Wizard", u"Search", None))
+ self.label_3.setText(QCoreApplication.translate("Wizard", u"Language", None))
+ ___qtreewidgetitem = self.treeWidget.headerItem()
+ ___qtreewidgetitem.setText(0, QCoreApplication.translate("Wizard", u"Type", None));
+
+ __sortingEnabled1 = self.treeWidget.isSortingEnabled()
+ self.treeWidget.setSortingEnabled(False)
+ ___qtreewidgetitem1 = self.treeWidget.topLevelItem(0)
+ ___qtreewidgetitem1.setText(0, QCoreApplication.translate("Wizard", u"Python", None));
+ self.treeWidget.setSortingEnabled(__sortingEnabled1)
+
+ self.label_4.setText(QCoreApplication.translate("Wizard", u"Project Type", None))
+
+ __sortingEnabled2 = self.file_list.isSortingEnabled()
+ self.file_list.setSortingEnabled(False)
+ ___qlistwidgetitem2 = self.file_list.item(0)
+ ___qlistwidgetitem2.setText(QCoreApplication.translate("Wizard", u"Python-Empty", None));
+ ___qlistwidgetitem3 = self.file_list.item(1)
+ ___qlistwidgetitem3.setText(QCoreApplication.translate("Wizard", u"Python-Template-Basic", None));
+ ___qlistwidgetitem4 = self.file_list.item(2)
+ ___qlistwidgetitem4.setText(QCoreApplication.translate("Wizard", u"Python-Template-Plot", None));
+ ___qlistwidgetitem5 = self.file_list.item(3)
+ ___qlistwidgetitem5.setText(QCoreApplication.translate("Wizard", u"Python-Template-PySide2", None));
+ self.file_list.setSortingEnabled(__sortingEnabled2)
+
+ self.plainTextEdit.setPlainText("")
+ self.label_5.setText(QCoreApplication.translate("Wizard", u"Steps", None))
+
+ __sortingEnabled3 = self.listWidget_2.isSortingEnabled()
+ self.listWidget_2.setSortingEnabled(False)
+ ___qlistwidgetitem6 = self.listWidget_2.item(0)
+ ___qlistwidgetitem6.setText(QCoreApplication.translate("Wizard", u"1. Select Project Type", None));
+ ___qlistwidgetitem7 = self.listWidget_2.item(1)
+ ___qlistwidgetitem7.setText(QCoreApplication.translate("Wizard", u"2. Configure Project", None));
+ self.listWidget_2.setSortingEnabled(__sortingEnabled3)
+
+ self.label_9.setText(QCoreApplication.translate("Wizard", u"Project Configuration", None))
+ self.projectNameText.setText(QCoreApplication.translate("Wizard", u"Project Name:", None))
+ self.projectNameLineEdit.setText(QCoreApplication.translate("Wizard", u"PyMinerProject", None))
+ self.projectDirectory.setText(QCoreApplication.translate("Wizard", u"Project Directory:", None))
+ self.toolButton.setText(QCoreApplication.translate("Wizard", u"...", None))
+ self.absoluteDirectory.setText(QCoreApplication.translate("Wizard", u"Absolute Directory:", None))
+ self.warningLabel.setText("")
+ # retranslateUi
+
diff --git a/pyminer/lib/ui/ui_project_wizard.ui b/pyminer/lib/ui/ui_project_wizard.ui
new file mode 100644
index 0000000000000000000000000000000000000000..f59a3618e3f7c60e8d53d2dbf1b32e3e2aef0054
--- /dev/null
+++ b/pyminer/lib/ui/ui_project_wizard.ui
@@ -0,0 +1,543 @@
+
+
+ Wizard
+
+
+
+ 0
+ 0
+ 736
+ 505
+
+
+
+ New Project
+
+
+ QWizard::ModernStyle
+
+
+ QWizard::HelpButtonOnRight|QWizard::NoBackButtonOnStartPage
+
+
+
+
+ 9
+
+
+ 9
+
+
+ 9
+
+
+ 9
+
+ -
+
+
+
+ 200
+ 0
+
+
+
+
+ 180
+ 16777215
+
+
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
-
+
+
+ Steps
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ border:0px;
+
+
-
+
+ 1. Select Project Type
+
+
+
+ 75
+ true
+
+
+
+ -
+
+ 2. Configure Project
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+ -
+
+
+
+ 5
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
-
+
+
+
+ 15
+ 15
+
+
+
+
+
+
+ :/color/theme/default/icons/search.svg
+
+
+ true
+
+
+
+ -
+
+
+ Search
+
+
+
+ -
+
+
+
+
+ -
+
+
+ 9
+
+
+ 0
+
+
-
+
+
+
+ 180
+ 16777215
+
+
+
+
+ 5
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ Language
+
+
+
+ -
+
+
+ false
+
+
+
+ Type
+
+
+
-
+
+ Python
+
+
+
+ :/color/theme/default/icons/folder_yellow.svg:/color/theme/default/icons/folder_yellow.svg
+
+
+
+
+
+
+
+ -
+
+
+
+ 16777215
+ 16777215
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ Project Type
+
+
+
+ -
+
+
-
+
+ Python-Empty
+
+
+
+ :/color/theme/default/icons/python.svg:/color/theme/default/icons/python.svg
+
+
+ -
+
+ Python-Template-Basic
+
+
+
+ :/color/theme/default/icons/python.svg:/color/theme/default/icons/python.svg
+
+
+ -
+
+ Python-Template-Plot
+
+
+
+ :/color/theme/default/icons/python.svg:/color/theme/default/icons/python.svg
+
+
+ -
+
+ Python-Template-PySide2
+
+
+
+ :/color/theme/default/icons/python.svg:/color/theme/default/icons/python.svg
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ 16777215
+ 90
+
+
+
+ background-color: rgb(243, 243, 243);
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ 200
+ 0
+
+
+
+
+ 180
+ 16777215
+
+
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
-
+
+
+ Steps
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ border:0px;
+
+
-
+
+ 1. Select Project Type
+
+
+
+ 50
+ false
+
+
+
+ -
+
+ 2. Configure Project
+
+
+
+ 75
+ true
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+ -
+
+
+
-
+
+
+ Project Configuration
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
-
+
+
+ Project Name:
+
+
+
+ -
+
+
+ PyMinerProject
+
+
+ false
+
+
+
+ -
+
+
+ Project Directory:
+
+
+
+ -
+
+
-
+
+
+
+
+
+ true
+
+
+
+ -
+
+
+ ...
+
+
+
+
+
+ -
+
+
+ Absolute Directory:
+
+
+
+ -
+
+
+
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+
+ 75
+ true
+
+
+
+ IBeamCursor
+
+
+ color: rgb(255, 102, 0);
+
+
+
+
+
+ Qt::AutoText
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pyminer/lib/ui/ui_workspace_launcher.py b/pyminer/lib/ui/ui_workspace_launcher.py
new file mode 100644
index 0000000000000000000000000000000000000000..7afda4805ca7c16d021b5de6c6602b8ce479d72a
--- /dev/null
+++ b/pyminer/lib/ui/ui_workspace_launcher.py
@@ -0,0 +1,121 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'ui_workspace_launcher.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import *
+from PySide2.QtGui import *
+from PySide2.QtWidgets import *
+
+
+class Ui_Dialog(object):
+ def setupUi(self, Dialog):
+ if not Dialog.objectName():
+ Dialog.setObjectName(u"Dialog")
+ Dialog.resize(444, 300)
+ self.verticalLayout = QVBoxLayout(Dialog)
+ self.verticalLayout.setSpacing(6)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.verticalLayout.setContentsMargins(0, 0, 0, 9)
+ self.widget = QWidget(Dialog)
+ self.widget.setObjectName(u"widget")
+ self.widget.setStyleSheet(u"background-color: rgb(255, 255, 255);")
+ self.verticalLayout_3 = QVBoxLayout(self.widget)
+ self.verticalLayout_3.setObjectName(u"verticalLayout_3")
+ self.verticalLayout_2 = QVBoxLayout()
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.label_4 = QLabel(self.widget)
+ self.label_4.setObjectName(u"label_4")
+ font = QFont()
+ font.setBold(True)
+ self.label_4.setFont(font)
+
+ self.verticalLayout_2.addWidget(self.label_4)
+
+ self.label_2 = QLabel(self.widget)
+ self.label_2.setObjectName(u"label_2")
+
+ self.verticalLayout_2.addWidget(self.label_2)
+
+ self.label_5 = QLabel(self.widget)
+ self.label_5.setObjectName(u"label_5")
+
+ self.verticalLayout_2.addWidget(self.label_5)
+
+
+ self.verticalLayout_3.addLayout(self.verticalLayout_2)
+
+
+ self.verticalLayout.addWidget(self.widget)
+
+ self.line = QFrame(Dialog)
+ self.line.setObjectName(u"line")
+ self.line.setFrameShape(QFrame.HLine)
+ self.line.setFrameShadow(QFrame.Sunken)
+
+ self.verticalLayout.addWidget(self.line)
+
+ self.widget_2 = QWidget(Dialog)
+ self.widget_2.setObjectName(u"widget_2")
+ self.verticalLayout_4 = QVBoxLayout(self.widget_2)
+ self.verticalLayout_4.setObjectName(u"verticalLayout_4")
+ self.horizontalLayout_3 = QHBoxLayout()
+ self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
+ self.label_3 = QLabel(self.widget_2)
+ self.label_3.setObjectName(u"label_3")
+
+ self.horizontalLayout_3.addWidget(self.label_3)
+
+ self.lineEdit_3 = QLineEdit(self.widget_2)
+ self.lineEdit_3.setObjectName(u"lineEdit_3")
+
+ self.horizontalLayout_3.addWidget(self.lineEdit_3)
+
+ self.toolButton_3 = QToolButton(self.widget_2)
+ self.toolButton_3.setObjectName(u"toolButton_3")
+
+ self.horizontalLayout_3.addWidget(self.toolButton_3)
+
+
+ self.verticalLayout_4.addLayout(self.horizontalLayout_3)
+
+ self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
+
+ self.verticalLayout_4.addItem(self.verticalSpacer)
+
+ self.checkBox = QCheckBox(self.widget_2)
+ self.checkBox.setObjectName(u"checkBox")
+
+ self.verticalLayout_4.addWidget(self.checkBox)
+
+ self.buttonBox = QDialogButtonBox(self.widget_2)
+ self.buttonBox.setObjectName(u"buttonBox")
+ self.buttonBox.setOrientation(Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok)
+
+ self.verticalLayout_4.addWidget(self.buttonBox)
+
+
+ self.verticalLayout.addWidget(self.widget_2)
+
+
+ self.retranslateUi(Dialog)
+
+ QMetaObject.connectSlotsByName(Dialog)
+ # setupUi
+
+ def retranslateUi(self, Dialog):
+ Dialog.setWindowTitle(QCoreApplication.translate("Dialog", u"Workspace launcher", None))
+ self.label_4.setText(QCoreApplication.translate("Dialog", u"Select a workspace", None))
+ self.label_2.setText(QCoreApplication.translate("Dialog", u"PyMiner stores your projects in a folder called a workspace.", None))
+ self.label_5.setText(QCoreApplication.translate("Dialog", u"Choose a workspace folder to use for this session.", None))
+ self.label_3.setText(QCoreApplication.translate("Dialog", u"Workspace", None))
+ self.toolButton_3.setText(QCoreApplication.translate("Dialog", u"Browse...", None))
+ self.checkBox.setText(QCoreApplication.translate("Dialog", u"Use this as the default and do not ask again", None))
+ # retranslateUi
+
diff --git a/pyminer/lib/ui/ui_workspace_launcher.ui b/pyminer/lib/ui/ui_workspace_launcher.ui
new file mode 100644
index 0000000000000000000000000000000000000000..8a63c0e5519f4269556943efef7e1ae6609d2d75
--- /dev/null
+++ b/pyminer/lib/ui/ui_workspace_launcher.ui
@@ -0,0 +1,139 @@
+
+
+ Dialog
+
+
+
+ 0
+ 0
+ 444
+ 300
+
+
+
+ Workspace launcher
+
+
+
+ 6
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 9
+
+ -
+
+
+ background-color: rgb(255, 255, 255);
+
+
+
-
+
+
-
+
+
+
+ true
+
+
+
+ Select a workspace
+
+
+
+ -
+
+
+ PyMiner stores your projects in a folder called a workspace.
+
+
+
+ -
+
+
+ Choose a workspace folder to use for this session.
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
-
+
+
-
+
+
+ Workspace
+
+
+
+ -
+
+
+ -
+
+
+ Browse...
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ Use this as the default and do not ask again
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pyminer/lib/ui/widgets/README.md b/pyminer/lib/ui/widgets/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..f9b18e2e316cf44bae768b897c0cb14b00157600
--- /dev/null
+++ b/pyminer/lib/ui/widgets/README.md
@@ -0,0 +1,3 @@
+# 文本编辑器
+`codeeditwidget.py`当中。
+目前自动补全还是有一定的问题:异步输入的时候,返回的结果往往不正确。
\ No newline at end of file
diff --git a/pyminer/lib/ui/widgets/__init__.py b/pyminer/lib/ui/widgets/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..61e1a2ca63070353bb2b1efe8a9ac910fefe6a8d
--- /dev/null
+++ b/pyminer/lib/ui/widgets/__init__.py
@@ -0,0 +1,2 @@
+from .controlpanel import PMPageExt
+
diff --git a/pyminer/lib/ui/widgets/controlpanel.py b/pyminer/lib/ui/widgets/controlpanel.py
new file mode 100644
index 0000000000000000000000000000000000000000..3bd0939e367c25b5a8d371b4c6965226b935de29
--- /dev/null
+++ b/pyminer/lib/ui/widgets/controlpanel.py
@@ -0,0 +1,164 @@
+import os
+import webbrowser
+
+# 也就是widget_right
+from PySide2.QtWidgets import QWidget, QSizePolicy, QListWidget, QListWidgetItem, \
+ QHBoxLayout, QLabel, QPushButton, QFileDialog, QVBoxLayout, QMenu, QTableWidget, QTableWidgetItem
+from PySide2.QtGui import QPixmap, QCursor
+from PySide2.QtCore import Qt, QSize
+from widgets import PMDockObject
+from lib.extensions.extensions_manager.manager import extensions_manager
+
+
+class ExtInfoWidget(QWidget):
+ """
+ 扩展信息组件,扩展列表中的一项
+ """
+
+ def __init__(self, parent, ext, ext_manager):
+ """
+ parent:父组件
+ ext:信息组件对应扩展
+ ext_manager:扩展管理器
+ """
+ super().__init__(parent)
+ self.ext = ext
+ self.ext_manager = ext_manager
+ self.page = parent
+ self.init_ui()
+
+ def uninstall(self):
+ """卸载操作"""
+ self.ext_manager.uninstall(self.ext.info.name)
+ self.page.init_extensions()
+
+ def refresh(self):
+ """刷新操作"""
+ self.ext_manager.refresh(self.ext.info.name)
+
+ def show_menu(self, p):
+ """右键菜单"""
+ menu = QMenu(self)
+ action_info = menu.addAction('卸载')
+ action_info.triggered.connect(self.uninstall)
+ action_refresh = menu.addAction('刷新')
+ action_refresh.triggered.connect(self.refresh)
+ menu.exec_(QCursor.pos())
+
+ def info(self):
+ """展示扩展信息(扩展商店中的)"""
+ url = f'http://py2cn.com/extensions?name={self.ext.info.name}'
+ webbrowser.open(url)
+
+ def init_ui(self):
+ """初始化ui"""
+ self.layout = QHBoxLayout(self)
+
+ # 扩展图标
+ img = QLabel(self)
+ img_path = os.path.join(self.ext.info.path, self.ext.info.icon)
+ pixmap = QPixmap(img_path)
+ pixmap = pixmap.scaledToHeight(50)
+ img.setPixmap(pixmap)
+ self.layout.addWidget(img)
+
+ # 扩展名称
+ ext_name = QLabel(self)
+ ext_name.setText(self.ext.info.display_name)
+ self.layout.addWidget(ext_name)
+
+ # 设置菜单模式,关联菜单事件
+ self.setContextMenuPolicy(Qt.CustomContextMenu)
+ self.customContextMenuRequested.connect(self.show_menu)
+
+ self.setLayout(self.layout)
+ self.show()
+
+ def mouseDoubleClickEvent(self, *args):
+ self.info()
+
+
+class PMPageExt(QWidget, PMDockObject):
+ """
+ 扩展选项卡页
+ """
+
+ def __init__(self, main_window):
+ """
+ main_window:主窗口
+ """
+ super().__init__()
+ self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
+ self.main_window = main_window
+ self.ext_manager = extensions_manager
+ self.init_ui()
+
+ # def sizeHint(self) -> QSize:
+ # return QSize(100,300)
+ # def resizeEvent(self, a0: QResizeEvent) -> None:
+ # self.setMaximumWidth(300)
+ # super().resizeEvent(a0)
+
+ def install(self):
+ """安装扩展"""
+ path, _ = QFileDialog.getOpenFileName(
+ parent=None,
+ caption='请选择要安装的压缩包',
+ directory='C:/',
+ filter='所有文件(*.*);;zip文件(*.zip)',
+ initialFilter='zip文件(*.zip)'
+ )
+ self.ext_manager.install(path)
+ # 刷新扩展列表
+ self.init_extensions()
+
+ def init_extensions(self):
+ self.ext_list.clear() # 先清空
+ for ext in self.ext_manager.extensions.values():
+ item = QListWidgetItem(self.ext_list, 0)
+ item.setSizeHint(QSize(self.ext_list.width() - 20, 50))
+ w = ExtInfoWidget(self, ext, self.ext_manager)
+ self.ext_list.addItem(item)
+ self.ext_list.setItemWidget(item, w)
+
+ def init_ui(self):
+ """初始化ui"""
+ self.layout = QVBoxLayout(self)
+
+ # 扩展列表
+ self.ext_list = QListWidget(self)
+ self.init_extensions()
+ self.ext_list.show()
+ self.layout.addWidget(self.ext_list)
+
+ # 从本地安装按钮
+ self.install_btn = QPushButton(self)
+ self.install_btn.setText('安装 - 从本地')
+ self.install_btn.clicked.connect(self.install)
+ self.layout.addWidget(self.install_btn)
+
+ self.setLayout(self.layout)
+
+
+class PMWorkspaceInspectWidget(QTableWidget):
+ def __init__(self, parnet=None):
+ super().__init__(parent=None)
+ self.setRowCount(4)
+ self.setColumnCount(10)
+ for i, name in enumerate(['名称', '类型', '大小', '值']):
+ self.setHorizontalHeaderItem(i, QTableWidgetItem(name))
+ # self.setCellWidget()
+ # self.setItem(0,0,QTableWidgetItem('123123123'))
+
+ def set_data_view(self):
+ pass
+
+
+if __name__ == '__main__':
+ from PySide2.QtWidgets import QApplication
+ import sys
+
+ app = QApplication(sys.argv)
+ sa = PMWorkspaceInspectWidget()
+ sa.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/lib/ui/widgets/notificationwidget.py b/pyminer/lib/ui/widgets/notificationwidget.py
new file mode 100644
index 0000000000000000000000000000000000000000..bc16acca49c16d9ec6be2f46b097c0414ca5ddbd
--- /dev/null
+++ b/pyminer/lib/ui/widgets/notificationwidget.py
@@ -0,0 +1,224 @@
+import base64
+
+from PySide2.QtCore import Signal, QTimer, QRectF, Qt, QSize
+from PySide2.QtGui import QColor, QPainter, QPainterPath, QPixmap, QImage
+from PySide2.QtWidgets import QListWidget, QWidget, QListWidgetItem, QHBoxLayout, QGridLayout, QLabel, QSpacerItem, \
+ QSizePolicy, QGraphicsDropShadowEffect, QApplication
+
+
+class NotificationItem(QWidget):
+ closed = Signal(QListWidgetItem)
+
+ def __init__(self, title, message, item, *args,
+ ntype=0, callback=None, **kwargs):
+ super(NotificationItem, self).__init__(*args, **kwargs)
+ self.item = item
+ self.callback = callback
+ layout = QHBoxLayout(self)
+ layout.setSpacing(0)
+ layout.setContentsMargins(0, 0, 0, 0)
+ self.bgWidget = QWidget(self) # 背景控件, 用于支持动画效果
+ layout.addWidget(self.bgWidget)
+
+ layout = QGridLayout(self.bgWidget)
+ layout.setHorizontalSpacing(15)
+ layout.setVerticalSpacing(10)
+
+ # 标题左边图标
+ layout.addWidget(
+ QLabel(self, pixmap=NotificationIcon.icon(ntype)), 0, 0)
+
+ # 标题
+ self.labelTitle = QLabel(title, self)
+ font = self.labelTitle.font()
+ font.setBold(True)
+ font.setPixelSize(22)
+ self.labelTitle.setFont(font)
+
+ # 关闭按钮
+ self.labelClose = QLabel(
+ self, cursor=Qt.PointingHandCursor, pixmap=NotificationIcon.icon(NotificationIcon.Close))
+
+ # 消息内容
+ self.labelMessage = QLabel(
+ message, self, cursor=Qt.PointingHandCursor, wordWrap=True, alignment=Qt.AlignLeft | Qt.AlignTop)
+ font = self.labelMessage.font()
+ font.setPixelSize(20)
+ self.labelMessage.setFont(font)
+ self.labelMessage.adjustSize()
+
+ # 添加到布局
+ layout.addWidget(self.labelTitle, 0, 1)
+ layout.addItem(QSpacerItem(
+ 40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum), 0, 2)
+ layout.addWidget(self.labelClose, 0, 3)
+ layout.addWidget(self.labelMessage, 1, 1, 1, 2)
+
+ # 边框阴影
+ effect = QGraphicsDropShadowEffect(self)
+ effect.setBlurRadius(12)
+ effect.setColor(QColor(0, 0, 0, 25))
+ effect.setOffset(0, 2)
+ self.setGraphicsEffect(effect)
+
+ self.adjustSize()
+
+ # 5秒自动关闭
+ self._timer = QTimer(self, timeout=self.doClose)
+ self._timer.setSingleShot(True) # 只触发一次
+ self._timer.start(5000)
+
+ def doClose(self):
+ # noinspection PyBroadException
+ try:
+ # 可能由于手动点击导致item已经被删除了
+ self.closed.emit(self.item)
+ except BaseException:
+ pass
+
+ def showAnimation(self, width):
+ # 显示动画
+ pass
+
+ def closeAnimation(self):
+ # 关闭动画
+ pass
+
+ def mousePressEvent(self, event):
+ super(NotificationItem, self).mousePressEvent(event)
+ w = self.childAt(event.pos())
+ if not w:
+ return
+ if w == self.labelClose: # 点击关闭图标
+ # 先尝试停止计时器
+ self._timer.stop()
+ self.closed.emit(self.item)
+ elif w == self.labelMessage and self.callback and callable(self.callback):
+ # 点击消息内容
+ self._timer.stop()
+ self.closed.emit(self.item)
+ self.callback() # 回调
+
+ def paintEvent(self, event):
+ # 圆角以及背景色
+ super(NotificationItem, self).paintEvent(event)
+ painter = QPainter(self)
+ path = QPainterPath()
+ path.addRoundedRect(QRectF(self.rect()), 6, 6)
+ painter.fillPath(path, Qt.white)
+
+
+class NotificationWindow(QListWidget):
+ _instance = None
+
+ def __init__(self, *args, **kwargs):
+ super(NotificationWindow, self).__init__(*args, **kwargs)
+ self.setSpacing(20)
+ self.setMinimumWidth(412)
+ self.setMaximumWidth(412)
+ QApplication.instance().setQuitOnLastWindowClosed(True)
+ # 隐藏任务栏,无边框,置顶等
+ self.setWindowFlags(self.windowFlags() | Qt.Tool |
+ Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
+ # 去掉窗口边框
+ self.setFrameShape(self.NoFrame)
+ # 背景透明
+ self.viewport().setAutoFillBackground(False)
+ self.setAttribute(Qt.WA_TranslucentBackground, True)
+ # 不显示滚动条
+ self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+ self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+ # 获取屏幕高宽
+ rect = QApplication.instance().desktop().availableGeometry(self)
+ self.setMinimumHeight(rect.height())
+ self.setMaximumHeight(rect.height())
+ self.move(rect.width() - self.minimumWidth() - 18, 0)
+
+ def removeItem(self, item):
+ # 删除item
+ w = self.itemWidget(item)
+ self.removeItemWidget(item)
+ item = self.takeItem(self.indexFromItem(item).row())
+ w.close()
+ w.deleteLater()
+ del item
+
+ @classmethod
+ def _createInstance(cls):
+ # 创建实例
+ if not cls._instance:
+ cls._instance = NotificationWindow()
+ cls._instance.show()
+ NotificationIcon.init()
+
+ @classmethod
+ def info(cls, title, message, callback=None):
+ cls._createInstance()
+ item = QListWidgetItem(cls._instance)
+ w = NotificationItem(title, message, item, cls._instance,
+ ntype=NotificationIcon.Info, callback=callback)
+ w.closed.connect(cls._instance.removeItem)
+ item.setSizeHint(QSize(cls._instance.width() -
+ cls._instance.spacing(), w.height()))
+ cls._instance.setItemWidget(item, w)
+
+ @classmethod
+ def success(cls, title, message, callback=None):
+ cls._createInstance()
+ item = QListWidgetItem(cls._instance)
+ w = NotificationItem(title, message, item, cls._instance,
+ ntype=NotificationIcon.Success, callback=callback)
+ w.closed.connect(cls._instance.removeItem)
+ item.setSizeHint(QSize(cls._instance.width() -
+ cls._instance.spacing(), w.height()))
+ cls._instance.setItemWidget(item, w)
+
+ @classmethod
+ def warning(cls, title, message, callback=None):
+ cls._createInstance()
+ item = QListWidgetItem(cls._instance)
+ w = NotificationItem(title, message, item, cls._instance,
+ ntype=NotificationIcon.Warning, callback=callback)
+ w.closed.connect(cls._instance.removeItem)
+ item.setSizeHint(QSize(cls._instance.width() -
+ cls._instance.spacing(), w.height()))
+ cls._instance.setItemWidget(item, w)
+
+ @classmethod
+ def error(cls, title, message, callback=None):
+ cls._createInstance()
+ item = QListWidgetItem(cls._instance)
+ w = NotificationItem(title, message, item,
+ ntype=NotificationIcon.Error, callback=callback)
+ w.closed.connect(cls._instance.removeItem)
+ width = cls._instance.width() - cls._instance.spacing()
+ item.setSizeHint(QSize(width, w.height()))
+ cls._instance.setItemWidget(item, w)
+
+
+class NotificationIcon:
+ Info, Success, Warning, Error, Close = range(5)
+ Types = {
+ Info: None,
+ Success: None,
+ Warning: None,
+ Error: None,
+ Close: None
+ }
+
+ @classmethod
+ def init(cls):
+ cls.Types[cls.Info] = QPixmap(QImage.fromData(base64.b64decode(
+ 'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAC5ElEQVRYR8VX0VHbQBB9e/bkN3QQU0FMBSEVYFcQ8xPBJLJ1FWAqOMcaxogfTAWQCiAVRKkgTgfmM4zRZu6QhGzL0p0nDPr17e7bt7tv14RX/uiV48MJgAon+8TiAMRtMFogaqUJxADPwRRzg67kl8+xbWJWANR40iPQSSFgtX/mGQkaDr56V3VAKgGos4s2JXwJoF3naMPvMS+SrpTHs032GwGkdF+DsFMVnJm/oyGGeHico0EjIjpYes+YMyVd6R/flfkpBWCCQ9zaZM2LZDfLMGXsZ5kdI/lYBmINgHHyyLd1mWdBbAFAM/GY7K2WYx1AeB4T6L1N9umbGxZ0qktATaEAdCps48D39oq/LwEw3U5CN92LfczJoewfT7MAywDCaEbAuxeLrh0zz4L+0e4aAJfGy+sP3IMxlH1vpMJoSMCJDXgWtJeJVc6ACs9HBBrYODCJAFdYvAmkPJxnNqMwYht7Bn+T/lGg3z4DGEd3RPhQ54DBvwAOVkeqagRXfTLjh+x7+8sALOtfHLuiYzWOAiLoKbD58mnIGbCmLxUepS6NQmYlUGE0JeCTTXT9JvA9E9sZgO5iIpoyc6/YzcqSwQzgGgBXB7oXpH9klpRSkxY1xW/b7Iu2zk34PILPnazCqEPAtTWA8iZ0HsOu9L0bw4DzCJeNocMGNDpQ3IKO+6NUiJ4ysZNiBv5I3zPnmJmG5oM+wbS+9+qkvGi7NAXGmeUy0ioofa+XA0jH0UaMKpdRWs/adcwMqfV/tenqpqHY/Znt+j2gJi00RUzA201dXaxh9iZdZloJS+9H1otrkbRrD5InFqpPskxEshJQ468CkSmJC+i1HigaaxCAuCljgoDhwPdOjf7rFVxxuJrMkXScjtKc1rOLNpJk6nii5XmYzbngzlZn+RIb40kPJPTBYXUt6VEDJ8Pi6bWpNFb/jFYY6YGpDeKdjBmTKdMcxDGEmP73v2a2Gr/NOycGtglQZ/MPzEqCMLGckJEAAAAASUVORK5CYII=')))
+ cls.Types[cls.Success] = QPixmap(QImage.fromData(base64.b64decode(
+ 'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACZUlEQVRYR8VXS3LTQBDtVsDbcAPMCbB3limkcAKSG4QFdnaYE2BOQLKzxSLJCeAGSUQheSnfwLmB2VJhXmpExpFHI2sk2RWv5FJPv9evP9NieuIfPzE+VSJw8qt3IMDvmahDoDYxt2UAACXMWIIowR5ffn8TJbaBWRE4CXvHAH9RgKXOgQUI48CfXZbZbiTw8Xe/w3d0zkydMkem91IZpyWOJu5sUXS+kEAqt3B+MNOLOuDqDEBLxxFHk7eza5MfIwEJDjhXTYD1s8zinYlEjsCD7FdNI9cJpEq0RFdPR47AMOzLCn69zegz6UgCP+pmfa8RSKudnPNdgCufTOLDxJtdPP7PoA1Cd8HEL5sSUCCD0B0x8bc1f8Bi6sevcgS2VXh6hMOwDz0gsUddNaxWKRjeuKfE/KlJ9Dq4UYH/o/Ns6scj+bgiMAjdayb26xLQwTfVEwg3gRcf6ARq578KuLo7VDc8psCQqwfjr4EfjYvkrAquFJ56UYpdSkAZSmNd1rrg0leOQFELgvA58OJTxVyRaAJORPOpF6UXnFUR5sDiXjs7UqsOMGMRlrWhTkJXpFL3mNrQZhA1lH3F0TiI5FurUQyMpn58VjhkSqQA4Tbw4nSVW6sBU5VXktXSeONlJH3s8jrOVr9RgVSFuNcWfzlh5n3LoKzMAPxxWuiULiQpiR2sZNnCyzIuWUr5Z1Ml0sgdHFZaShVDuR86/0huL3VXtDk/F4e11vKsTHLSCeKx7bYkW80hjLOrV1GhWH0ZrSlyh2MwdZhYfi8oZeYgLBmUiGd8sfVPM6syr2lUSYGaGBuP3QN6rVUwYV/egwAAAABJRU5ErkJggg==')))
+ cls.Types[cls.Warning] = QPixmap(QImage.fromData(base64.b64decode(
+ 'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACmElEQVRYR8VXTW7TUBD+xjYSXZFukOIsSE9AskNJJMoJmq4r7OYEwAkabhBOkB/Emt4gVIojdpgbpIumEitX6gKB7UHPkauXxLHfc4F6Z3l+vvnmm/fGhAd+6IHzQwvA9cfOITMfAdQAcx1EdVEAM/tEFADsWyaPn57MfdXClABcT1qnzHSWJiwMzrwgoF91vXGRbS6AH59ajd8hDYmoURQo67tgxoij42rv62KX/04Agu44xmciVMokT32YERgGjquvZ1+y4mQCWPUa0/sk3vQlwqssEFsAVrQbU4XKL/ai2+5PPK6waQ4AOsoDnDARh83NdmwBuJq0fQI9L6p+L7rd3+/5gbAToMPI+FbkIzRRc72mbLcGIFE7jGFRIPHddmZrvstJh1X8CHGv6sxHqe1GkPYCoGcqgcoCAPPCdr2DLQC6wqMoPEj7qdqCNKllxs30sLpjYDluDUDGG5XqhY2sal3w4PiD7c7fJnHShMtJR8zpy/8CALiwndnhBgD1/t+XAXkaZAaUVHwnHulg0W6BNEWlAQD8zna8gQB0Ne70iXCm2j55jCUAei1gxvuaO+uXAcDg7zXHSy640iKUAehOEDJFqDmGQkiPLO5Fv+KADXOqvCuIsrPGsIyQdHou22YeRMJgOdHTQTkAfGk7XrLKrWlAvOhcRgBfWiZ3RQti0zxXuUFXCXMuo0TRitfxugjbIxC5RYzI6s9kIGFh+KLOpiW22id5AUuI8IaisFG4kCQg/sFKJgtPLix3KWXGeRETRbQDuCFCV2spTYMm+2FEI1WBbYIRPTeiqFtqLZeDraaD+qrbkpgQAvfl1WsXU0p/RjIjYYhTkNFgcCVlRlRKoAAc+5aF0V//NVPoc2kTLQZKZ8lx/AMXBmMwuXUwOAAAAABJRU5ErkJggg==')))
+ cls.Types[cls.Error] = QPixmap(QImage.fromData(base64.b64decode(
+ 'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACrklEQVRYR82XW27aQBSG/4PtiNhIpStouoImKwjZAV1B07coWCpZQcgK6kh2lLeSFZSsIOwgdAdkBaUSEBQDpxpjU9vM+EJR03nDzJz/mzm3GcIrD3plfZQCeD47O1ho2jERNRmoE9AQG2BgBGBAwIiZe5Zh3JPjiG+5oxCAEF5q2iWITnMtRhOYu5XF4mr/9naYtSYXYGLbHQCXhYVTEwlom657rVqvBOB2uz71/a+ldq1SYe6ahnEhc4sSYGzbfQKOt915eh0D/ZrrnqS/SwEmrVYXRJ92Jb4OC+C65rrtuN0NgIltNwF837V4zN5Hy3V70e9NgFZrCKJ3CQDmJ9MwDsW36XzeB/AhA/CHqeuN2WxWX2paX2JraHneeynA+Pz8lCqVbxLjV5brimxAEJxqiEA8CjZVBvFy+bl2c9MV9hInoAw85qFpGEeRYQVEQjzMokcQHWxsiPne8jzh6j8AodGfyqNlHpiGcaKAkIk/gChwm2yYuv5W2FqfwLNtN5bAQ2bwySB83zENo50A8/1McaFRAU72XVek+mpk+D/JlIKI/xkee654uCbIhjVAqZIrgSgpLhiCwN4OAEj4vEB2yDybBCjsAol4ZD0nRdMQSRcUCsKUeNSw4o2mKMRGEOamoVx8FXDZKVosDYNMUHXAsBRnppo8RQcbpTgIGEkhykpFjnWxzGhPQYxt2yHgS/oIlKVYTJxImpG482nz+VG1Wh1N84pMCCGa0ULXHwmoJwCYnyzPW5fn/68dh7EgPbrMMl3gz7gro+n/7EoWD7w4a96l1NnJ1Yz5Lt6wCgFEk0r1CIkbiPnC9DxH5aHcd4FYGD5MOqVOg/muslh0/vphkm63k5eXZvA0I6qD+ZCI3jDzLxANiHn1NNvb6+30aVYgwLeeUsgFW1svsPA3Ncq4MHzVeO8AAAAASUVORK5CYII=')))
+ cls.Types[cls.Close] = QPixmap(QImage.fromData(base64.b64decode(
+ 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAeElEQVQ4T2NkoBAwUqifgboGzJy76AIjE3NCWmL0BWwumzV/qcH/f38XpCfHGcDkUVwAUsDw9+8GBmbmAHRDcMlheAGbQnwGYw0DZA1gp+JwFUgKZyDCDQGpwuIlrGGAHHAUGUCRFygKRIqjkeKERE6+oG5eIMcFAOqSchGwiKKAAAAAAElFTkSuQmCC')))
+
+ @classmethod
+ def icon(cls, ntype):
+ return cls.Types.get(ntype)
diff --git a/pyminer/lib/ui/widgets/reportwidget.py b/pyminer/lib/ui/widgets/reportwidget.py
new file mode 100644
index 0000000000000000000000000000000000000000..9d490968165f2f12f5efd5c49a3d8c8cf0f44762
--- /dev/null
+++ b/pyminer/lib/ui/widgets/reportwidget.py
@@ -0,0 +1,37 @@
+from PySide2.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QSpacerItem, QSizePolicy, QPushButton
+from PySide2.QtCore import QSize, QCoreApplication
+
+
+class PMReportWidget(QWidget):
+ def __init__(self):
+ super().__init__()
+ _translate = QCoreApplication.translate
+ self.setObjectName("tab_report")
+
+ self.verticalLayout_2 = QVBoxLayout(self)
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.widget_2 = QWidget(self)
+ self.widget_2.setMaximumSize(QSize(16777215, 30))
+ self.widget_2.setObjectName("widget_2")
+ self.horizontalLayout_5 = QHBoxLayout(self.widget_2)
+ self.horizontalLayout_5.setContentsMargins(0, 0, 0, 0)
+ self.horizontalLayout_5.setObjectName("horizontalLayout_5")
+ self.horizontalLayout_4 = QHBoxLayout()
+ self.horizontalLayout_4.setObjectName("horizontalLayout_4")
+ spacerItem1 = QSpacerItem(
+ 40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+ self.horizontalLayout_4.addItem(spacerItem1)
+ self.pushButton_browser_open = QPushButton(self.widget_2)
+ self.pushButton_browser_open.setMinimumSize(QSize(80, 0))
+ self.pushButton_browser_open.setObjectName("pushButton_browser_open")
+ self.horizontalLayout_4.addWidget(self.pushButton_browser_open)
+ self.horizontalLayout_5.addLayout(self.horizontalLayout_4)
+ self.verticalLayout_2.addWidget(self.widget_2)
+ self.horizontalLayout_result = QHBoxLayout()
+ self.horizontalLayout_result.setObjectName("horizontalLayout_result")
+ spacerItem2 = QSpacerItem(
+ 20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
+ self.horizontalLayout_result.addItem(spacerItem2)
+ self.verticalLayout_2.addLayout(self.horizontalLayout_result)
+
+ self.pushButton_browser_open.setText(_translate("MainWindow", "浏览器打开"))
diff --git a/pyminer/lib/ui/widgets/resources.py b/pyminer/lib/ui/widgets/resources.py
new file mode 100644
index 0000000000000000000000000000000000000000..b6c0463a40950a8f119167be38ae3ecec13cd02b
--- /dev/null
+++ b/pyminer/lib/ui/widgets/resources.py
@@ -0,0 +1,380 @@
+from PySide2.QtGui import QPixmap, QIcon
+
+icon_lc_dbreportedit = QIcon()
+icon_lc_dbreportedit.addPixmap(QPixmap(":/pyqt/source/images/lc_dbreportedit.png"), QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_draw_chart = QIcon()
+icon_lc_draw_chart.addPixmap(QPixmap(":/pyqt/source/images/lc_drawchart.png"), QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_save = QIcon()
+icon_lc_save.addPixmap(
+ QPixmap(":/pyqt/source/images/lc_save.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_statisiticsmenu = QIcon()
+icon_lc_statisiticsmenu.addPixmap(QPixmap(":/pyqt/source/images/lc_statisticsmenu.png"), QIcon.Normal,
+ QIcon.Off)
+
+icon_sc_shadowcurser = QIcon()
+icon_sc_shadowcurser.addPixmap(
+ QPixmap(":/pyqt/source/images/sc_shadowcursor.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_connectorlinesarrowend = QIcon()
+icon_lc_connectorlinesarrowend.addPixmap(QPixmap(":/pyqt/source/images/lc_connectorlinesarrowend.png"), QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_undo = QIcon()
+icon_lc_undo.addPixmap(
+ QPixmap(":/pyqt/source/images/lc_undo.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_redo = QIcon()
+icon_lc_redo.addPixmap(
+ QPixmap(":/pyqt/source/images/lc_redo.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_sc_deletepage = QIcon()
+icon_sc_deletepage.addPixmap(
+ QPixmap(":/pyqt/source/images/sc_deletepage.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_aligncenter = QIcon()
+icon_lc_aligncenter.addPixmap(
+ QPixmap(":/pyqt/source/images/lc_aligncenter.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_alignmiddle = QIcon()
+icon_lc_alignmiddle.addPixmap(
+ QPixmap(":/pyqt/source/images/lc_alignmiddle.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_zoomin = QIcon()
+icon_lc_zoomin.addPixmap(
+ QPixmap(":/pyqt/source/images/lc_zoomin.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_zoomout = QIcon()
+icon_lc_zoomout.addPixmap(
+ QPixmap(":/pyqt/source/images/lc_zoomout.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_dataprovider = QIcon()
+icon_dataprovider.addPixmap(
+ QPixmap(":/pyqt/source/images/dataprovider.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_sc_autosum = QIcon()
+icon_sc_autosum.addPixmap(
+ QPixmap(":/pyqt/source/images/sc_autosum.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_drawchart = QIcon()
+icon_lc_drawchart.addPixmap(
+ QPixmap(":/pyqt/source/images/lc_drawchart.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_statisticsmenu = QIcon()
+icon_lc_statisticsmenu.addPixmap(QPixmap(":/pyqt/source/images/lc_statisticsmenu.png"), QIcon.Normal,
+ QIcon.Off)
+
+icon_wordcountdialog = QIcon()
+icon_wordcountdialog.addPixmap(
+ QPixmap(":/pyqt/source/images/wordcountdialog.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_distributecolumns = QIcon()
+icon_distributecolumns.addPixmap(QPixmap(":/pyqt/source/images/distributecolumns.png"), QIcon.Normal,
+ QIcon.Off)
+
+icon_dbdistinctvalues = QIcon()
+icon_dbdistinctvalues.addPixmap(QPixmap(":/pyqt/source/images/dbdistinctvalues.png"), QIcon.Normal,
+ QIcon.Off)
+
+icon_delete_columns = QIcon()
+icon_delete_columns.addPixmap(
+ QPixmap(":/pyqt/source/images/deletecolumns.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_mergedocuments = QIcon()
+icon_mergedocuments.addPixmap(
+ QPixmap(":/pyqt/source/images/mergedocuments.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_dataarearefresh = QIcon()
+icon_lc_dataarearefresh.addPixmap(QPixmap(":/pyqt/source/images/lc_dataarearefresh.png"), QIcon.Normal,
+ QIcon.Off)
+
+icon_data_provider = QIcon()
+icon_data_provider.addPixmap(QPixmap(":/pyqt/source/images/dataprovider.png"), QIcon.Normal,
+ QIcon.Off)
+
+icon_nostackdirectboth_52x60 = QIcon()
+icon_nostackdirectboth_52x60.addPixmap(QPixmap(":/pyqt/source/images/nostackdirectboth_52x60.png"), QIcon.Normal,
+ QIcon.Off)
+
+icon_columns_52x60 = QIcon()
+icon_columns_52x60.addPixmap(
+ QPixmap(":/pyqt/source/images/columns_52x60.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_formfilternavigator = QIcon()
+icon_formfilternavigator.addPixmap(QPixmap(":/pyqt/source/images/formfilternavigator.png"), QIcon.Normal,
+ QIcon.Off)
+icon_lc_connectorcurve = QIcon()
+icon_lc_connectorcurve.addPixmap(QPixmap(":/pyqt/source/images/lc_connectorcurve.png"), QIcon.Normal,
+ QIcon.Off)
+icon_transition_random = QIcon()
+icon_transition_random.addPixmap(QPixmap(":/pyqt/source/images/transition-random.png"), QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_accepttrackedchange = QIcon()
+icon_lc_accepttrackedchange.addPixmap(QPixmap(":/pyqt/source/images/lc_accepttrackedchange.png"), QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_datasubtotals = QIcon()
+icon_lc_datasubtotals.addPixmap(QPixmap(":/pyqt/source/images/lc_datasubtotals.png"), QIcon.Normal,
+ QIcon.Off)
+
+icon_entirecolumn = QIcon()
+icon_entirecolumn.addPixmap(
+ QPixmap(":/pyqt/source/images/entirecolumn.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_selectdb = QIcon()
+icon_lc_selectdb.addPixmap(
+ QPixmap(":/pyqt/source/images/lc_selectdb.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_togglemergecells = QIcon()
+icon_lc_togglemergecells.addPixmap(QPixmap(":/pyqt/source/images/lc_togglemergecells.png"), QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_datadatapilotrun = QIcon()
+icon_lc_datadatapilotrun.addPixmap(QPixmap(":/pyqt/source/images/lc_datadatapilotrun.png"), QIcon.Normal,
+ QIcon.Off)
+
+icon_NavOverFlow_Info = QIcon()
+icon_NavOverFlow_Info.addPixmap(QPixmap(":/pyqt/source/images/NavOverFlow_Info.png"), QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_dbviewtablenames = QIcon()
+icon_lc_dbviewtablenames.addPixmap(QPixmap(":/pyqt/source/images/lc_dbviewtablenames.png"), QIcon.Normal,
+ QIcon.Off)
+
+icon_graphicfilterpopart = QIcon()
+icon_graphicfilterpopart.addPixmap(QPixmap(":/pyqt/source/images/graphicfilterpopart.png"), QIcon.Normal,
+ QIcon.Off)
+
+icon_deleterows = QIcon()
+icon_deleterows.addPixmap(
+ QPixmap(":/pyqt/source/images/deleterows.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_renametable = QIcon()
+icon_lc_renametable.addPixmap(
+ QPixmap(":/pyqt/source/images/lc_renametable.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_formatcolumns = QIcon()
+icon_lc_formatcolumns.addPixmap(QPixmap(":/pyqt/source/images/lc_formatcolumns.png"), QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_closedoc = QIcon()
+icon_lc_closedoc.addPixmap(
+ QPixmap(":/pyqt/source/images/lc_closedoc.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_netfill = QIcon()
+icon_netfill.addPixmap(
+ QPixmap(":/pyqt/source/images/netfill_52x60.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_stockcolumns = QIcon()
+icon_stockcolumns.addPixmap(QPixmap(":/pyqt/source/images/stockcolumns_52x60.png"), QIcon.Normal,
+ QIcon.Off)
+
+icon_bubble = QIcon()
+icon_bubble.addPixmap(
+ QPixmap(":/pyqt/source/images/bubble_52x60.png"),
+ QIcon.Normal,
+ QIcon.Off)
+icon_areas = QIcon()
+icon_areas.addPixmap(
+ QPixmap(":/pyqt/source/images/areas_52x60.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_pie = QIcon()
+icon_pie.addPixmap(
+ QPixmap(":/pyqt/source/images/pie_52x60.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_areaspiled = QIcon()
+icon_areaspiled.addPixmap(QPixmap(":/pyqt/source/images/areaspiled_52x60.png"), QIcon.Normal,
+ QIcon.Off)
+
+icon_dbviewtables = QIcon()
+icon_dbviewtables.addPixmap(
+ QPixmap(":/pyqt/source/images/dbviewtables.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_autosum = QIcon()
+icon_lc_autosum.addPixmap(
+ QPixmap(":/pyqt/source/images/lc_autosum.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_New = QIcon()
+icon_New.addPixmap(
+ QPixmap(":/pyqt/source/images/New.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_folder = QIcon()
+icon_folder.addPixmap(
+ QPixmap(":/pyqt/source/images/folder.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_input = QIcon()
+icon_input.addPixmap(
+ QPixmap(":/pyqt/source/images/input.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_Save = QIcon()
+icon_Save.addPixmap(
+ QPixmap(":/pyqt/source/images/Save.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_wb_setting_normal = QIcon()
+icon_wb_setting_normal.addPixmap(QPixmap(":/pyqt/source/images/wb-setting-normal.png"), QIcon.Normal,
+ QIcon.Off)
+
+icon_Delete = QIcon()
+icon_Delete.addPixmap(
+ QPixmap(":/pyqt/source/images/Delete.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_Cut = QIcon()
+icon_Cut.addPixmap(
+ QPixmap(":/pyqt/source/images/Cut.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_Copy = QIcon()
+icon_Copy.addPixmap(
+ QPixmap(":/pyqt/source/images/Copy.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_Paste = QIcon()
+icon_Paste.addPixmap(
+ QPixmap(":/pyqt/source/images/Paste.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_formfilternavigator = QIcon()
+icon_lc_formfilternavigator.addPixmap(QPixmap(":/pyqt/source/images/lc_formfilternavigator.png"), QIcon.Normal,
+ QIcon.Off)
+icon_lc_mergecells = QIcon()
+icon_lc_mergecells.addPixmap(
+ QPixmap(":/pyqt/source/images/lc_mergecells.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_hlinettp = QIcon()
+icon_hlinettp.addPixmap(
+ QPixmap(":/pyqt/source/images/hlinettp.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_dbqueryedit = QIcon()
+icon_dbqueryedit.addPixmap(
+ QPixmap(":/pyqt/source/images/dbqueryedit.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_infobox = QIcon()
+icon_infobox.addPixmap(
+ QPixmap(":/pyqt/source/images/infobox.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_dbqueryrename = QIcon()
+icon_lc_dbqueryrename.addPixmap(QPixmap(":/pyqt/source/images/lc_dbqueryrename.png"), QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_dia = QIcon()
+icon_lc_dia.addPixmap(
+ QPixmap(":/pyqt/source/images/lc_dia.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_lc_dbsortingandgrouping = QIcon()
+icon_lc_dbsortingandgrouping.addPixmap(QPixmap(":/pyqt/source/images/lc_dbsortingandgrouping.png"), QIcon.Normal,
+ QIcon.Off)
+icon_lc_insertplugin = QIcon()
+icon_lc_insertplugin.addPixmap(
+ QPixmap(":/pyqt/source/images/lc_insertplugin.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_jupyter = QIcon()
+icon_jupyter.addPixmap(
+ QPixmap(":/pyqt/source/images/jupyter.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_python = QIcon()
+icon_python.addPixmap(
+ QPixmap(":/pyqt/source/images/python.png"),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_ext = QIcon()
+icon_ext.addPixmap(
+ QPixmap(':/pyqt/source/images/extension_32.png'),
+ QIcon.Normal,
+ QIcon.Off)
+
+icon_ext_install = QIcon()
+icon_ext_install.addPixmap(
+ QPixmap(':/pyqt/source/images/Extensions.png.png'),
+ QIcon.Normal,
+ QIcon.Off)
+
+
+icon_lc_arrowshapes_right_arrow_callout = QIcon()
+icon_lc_arrowshapes_right_arrow_callout.addPixmap(
+ QPixmap(":/pyqt/source/images/lc_arrowshapes.right-arrow-callout.png"),
+ QIcon.Normal, QIcon.Off)
diff --git a/pyminer/lib/util/__init__.py b/pyminer/lib/util/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/lib/util/check_update_ui.py b/pyminer/lib/util/check_update_ui.py
new file mode 100644
index 0000000000000000000000000000000000000000..f5c30de9915cf420cd9e48030c165e52cfb6e96b
--- /dev/null
+++ b/pyminer/lib/util/check_update_ui.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'check_update_ui.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import *
+from PySide2.QtGui import *
+from PySide2.QtWidgets import *
+
+
+class Ui_Dialog(object):
+ def setupUi(self, Dialog):
+ if not Dialog.objectName():
+ Dialog.setObjectName(u"Dialog")
+ Dialog.resize(546, 173)
+ self.verticalLayout_2 = QVBoxLayout(Dialog)
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.verticalLayout = QVBoxLayout()
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.label = QLabel(Dialog)
+ self.label.setObjectName(u"label")
+
+ self.verticalLayout.addWidget(self.label)
+
+ self.horizontalLayout = QHBoxLayout()
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.button_detail = QPushButton(Dialog)
+ self.button_detail.setObjectName(u"button_detail")
+ self.button_detail.setMaximumSize(QSize(20, 16777215))
+
+ self.horizontalLayout.addWidget(self.button_detail)
+
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout.addItem(self.horizontalSpacer)
+
+
+ self.verticalLayout.addLayout(self.horizontalLayout)
+
+ self.table = QTableWidget(Dialog)
+ self.table.setObjectName(u"table")
+
+ self.verticalLayout.addWidget(self.table)
+
+ self.horizontalLayout_5 = QHBoxLayout()
+ self.horizontalLayout_5.setObjectName(u"horizontalLayout_5")
+ self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_5.addItem(self.horizontalSpacer_2)
+
+ self.button_confirm = QPushButton(Dialog)
+ self.button_confirm.setObjectName(u"button_confirm")
+
+ self.horizontalLayout_5.addWidget(self.button_confirm)
+
+ self.button_close = QPushButton(Dialog)
+ self.button_close.setObjectName(u"button_close")
+
+ self.horizontalLayout_5.addWidget(self.button_close)
+
+
+ self.verticalLayout.addLayout(self.horizontalLayout_5)
+
+
+ self.verticalLayout_2.addLayout(self.verticalLayout)
+
+
+ self.retranslateUi(Dialog)
+
+ QMetaObject.connectSlotsByName(Dialog)
+ # setupUi
+
+ def retranslateUi(self, Dialog):
+ Dialog.setWindowTitle(QCoreApplication.translate("Dialog", u"\u66f4\u65b0\u68c0\u6d4b\u7a0b\u5e8f", None))
+ self.label.setText("")
+ self.button_detail.setText(QCoreApplication.translate("Dialog", u"+", None))
+ self.button_confirm.setText(QCoreApplication.translate("Dialog", u"\u66f4\u65b0", None))
+ self.button_close.setText(QCoreApplication.translate("Dialog", u"\u4e0b\u6b21\u518d\u8bf4", None))
+ # retranslateUi
+
diff --git a/pyminer/lib/util/check_update_ui.ui b/pyminer/lib/util/check_update_ui.ui
new file mode 100644
index 0000000000000000000000000000000000000000..6fcaa5e03bbc5e6cd78756cc36c19e08bd7d107b
--- /dev/null
+++ b/pyminer/lib/util/check_update_ui.ui
@@ -0,0 +1,96 @@
+
+
+ Dialog
+
+
+
+ 0
+ 0
+ 546
+ 173
+
+
+
+ 更新检测程序
+
+
+ -
+
+
-
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 20
+ 16777215
+
+
+
+ +
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ 更新
+
+
+
+ -
+
+
+ 下次再说
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pyminer/lib/util/make_update.py b/pyminer/lib/util/make_update.py
new file mode 100644
index 0000000000000000000000000000000000000000..2f29fc66edb33fe0b32798889af9b0b9c0e874ab
--- /dev/null
+++ b/pyminer/lib/util/make_update.py
@@ -0,0 +1,96 @@
+import hashlib
+import json
+import os
+import pathlib
+from typing import Dict
+
+import pathspec
+
+import utils
+from utils import is_mac_platform, is_linux_platform, is_windows_platform
+
+WINDOWS_X64 = 0
+LINUX_X64 = 1
+MAC_X64 = 2
+CURRENT_PLATFORM = -1
+if is_mac_platform():
+ STRIP_LINEEND_FCN = lambda b: b.replace(b'\r', b'\n')
+elif is_linux_platform():
+ STRIP_LINEEND_FCN = lambda b: b
+elif is_windows_platform():
+ STRIP_LINEEND_FCN = lambda b: b.replace(b'\r', b'')
+
+PROJ_ROOT = utils.get_root_dir()
+
+FOLDERS_TO_IGNORE = [".git"]
+FOLDER_NAMES_TO_IGNORE = ["__pycache__"]
+FILE_NAMES_TO_IGNORE = ["__latest.json"]
+
+FOLDERS_TO_IGNORE = [os.path.join(PROJ_ROOT, f) for f in FOLDERS_TO_IGNORE]
+
+GITIGNORE_PATH = os.path.join(PROJ_ROOT, '.gitignore')
+
+
+def get_filter_list():
+ """从gitignore获取忽略文件"""
+ with open(GITIGNORE_PATH, 'r') as f:
+ filter_list = f.read().splitlines()
+ filter_list.append("__latest.json")
+ return filter_list
+
+
+spec = pathspec.PathSpec.from_lines(pathspec.patterns.GitWildMatchPattern, get_filter_list())
+
+
+def should_be_recorded(path: pathlib.Path) -> bool:
+ """
+
+ :return:
+ """
+ if path.is_dir():
+ return False # 文件夹不记录
+ if '.git' in str(path):
+ return False
+ if spec.match_file(path):
+ return False
+ return True
+
+
+def get_file_md5(path):
+ if not os.path.isfile(path):
+ return ""
+ myhash = hashlib.md5()
+ f = open(path, 'rb')
+ while True:
+ b = f.read(8096)
+
+ b = STRIP_LINEEND_FCN(b) # 这里是因为git会自动转换\r\n,为了保证多平台统一,在计算md5码时去除\r
+ if not b:
+ break
+ myhash.update(b)
+ f.close()
+ md5 = myhash.hexdigest()
+ return md5
+
+
+if __name__ == '__main__':
+ d: Dict[str, str] = {}
+ for root, dirs, files in os.walk(PROJ_ROOT):
+ # if folder_should_be_ignored(root):
+ # continue
+ for file in files:
+
+ abso_path = os.path.join(root, file)
+
+ path = pathlib.Path(abso_path)
+ re_path = str(path.relative_to(PROJ_ROOT)).replace('\\', '/')
+ if not should_be_recorded(pathlib.Path(abso_path)):
+ continue
+ d[re_path] = get_file_md5(abso_path)
+ if file.startswith("."):
+ print("aaaaaa", file)
+ print(re_path)
+ print(d[re_path])
+ print(d)
+ with open(os.path.join(PROJ_ROOT, "__latest.json"), "w", encoding="utf8") as f:
+ json.dump({"files": d}, f)
diff --git a/pyminer/lib/util/openprocess.py b/pyminer/lib/util/openprocess.py
new file mode 100644
index 0000000000000000000000000000000000000000..1d4cc8dfda70ce5337c4df17265b947e4f599f56
--- /dev/null
+++ b/pyminer/lib/util/openprocess.py
@@ -0,0 +1,108 @@
+import platform
+import queue
+import subprocess
+import sys
+import threading
+import time
+import chardet
+from typing import List
+
+import packages.code_editor.utils.utils
+
+
+class PMProcess():
+ def __init__(self, args: List[str]):
+ self.terminate = False
+ self.q = queue.Queue()
+ self.on_command_received = lambda cmd: print(cmd)
+ self.on_error_received = lambda error: print(error)
+ self.args = args
+ self.process = subprocess.Popen(self.args,
+ stdin=subprocess.PIPE,
+ shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ self.to = threading.Thread(
+ target=self.enqueue_stream, args=(
+ self.process.stdout, self.q, 1))
+ self.te = threading.Thread(
+ target=self.enqueue_stream_err, args=(
+ self.process.stderr, self.q, 2))
+ self.tp = threading.Thread(target=self.consoleLoop)
+ self.to.setDaemon(True)
+ self.te.setDaemon(True)
+ self.tp.setDaemon(True)
+ self.te.start()
+ self.to.start()
+ self.tp.start()
+
+ def enqueue_stream_err(self, stream, queue, type):
+ """
+ stdout写入到队列q中。
+ Args:
+ stream:
+ queue:
+ type:
+
+ Returns:
+
+ """
+ for line in iter(stream.readline, b''):
+ if self.terminate:
+ break
+ if platform.system().lower() == 'linux':
+ encoding = 'utf-8'
+ else:
+ encoding = chardet.detect(line)['encoding']
+ queue.put(str(type) + packages.code_editor.utils.utils.decode(encoding))
+ print(line)
+ stream.close()
+
+ def enqueue_stream(self, stream, queue, type): # 将stderr或者stdout写入到队列q中。
+ """
+ stdout写入到队列q中。
+ Args:
+ stream:
+ queue:
+ type:
+
+ Returns:
+
+ """
+ for line in iter(lambda: stream.read(1), b''):
+ if self.terminate:
+ break
+ if platform.system().lower() == 'linux':
+ encoding = 'utf-8'
+ else:
+ encoding = chardet.detect(line)['encoding']
+ queue.put(str(type) + packages.code_editor.utils.utils.decode(encoding))
+ stream.close()
+
+ def consoleLoop(self): # 封装后的内容。
+ return
+ idleLoops = 0
+ while True:
+ if not self.q.empty():
+ line = self.q.get()
+ if line[0] == '1':
+ self.on_command_received(line[1:])
+ else:
+ self.on_error_received(line[1:])
+ sys.stdout.flush()
+ else:
+ time.sleep(0.01)
+ if idleLoops >= 5:
+ idleLoops = 0
+ # print('write!!')
+ self.process.stdin.write(
+ 'messsage\n'.encode('ascii')) # 模拟输入
+ self.process.stdin.flush()
+ continue
+ idleLoops += 1
+
+
+if __name__ == '__main__':
+ pmp = PMProcess(['python', '-u',
+ 'test_open_app.py'])
+ while (1):
+ time.sleep(2)
+ pass
diff --git a/pyminer/lib/util/update.py b/pyminer/lib/util/update.py
new file mode 100644
index 0000000000000000000000000000000000000000..0f5b30bd72748f2139424c7cd8d6d6597257297f
--- /dev/null
+++ b/pyminer/lib/util/update.py
@@ -0,0 +1,364 @@
+import hashlib
+import logging
+import os
+import platform
+import subprocess
+import sys
+from pathlib import Path
+
+import requests
+from PySide2.QtCore import Qt, QThread, Signal
+from PySide2.QtWidgets import QProgressBar, QVBoxLayout, QLabel, QApplication, QDesktopWidget, QDialog, QHBoxLayout, \
+ QPushButton, QTableWidgetItem, QHeaderView, QTableView
+
+import utils
+from lib.util.check_update_ui import Ui_Dialog
+from lib.util.make_update import should_be_recorded
+
+"""
+自动更新逻辑
+1. 在程序启动脚本中,先运行本文件,如果检测到上次更新未完成(存在update.log),则执行更新程序。这部分逻辑应在打包程序时添加
+2. 在系统启动时,后台开启更新检测,若存在更新,弹出对话框,选择是否更新。如是执行更新程序
+3. 手动点击,执行更新程序
+"""
+
+# MD5_JSON_URL = "https://gitee.com/py2cn/pyminer/raw/dev/__latest.json"
+MD5_JSON_URL = "https://gitee.com/py2cn/pyminer/raw/master/__latest.json"
+
+# REMOTE_URL = 'https://gitee.com/py2cn/pyminer/raw/dev/' # 稳定版
+REMOTE_URL = "https://gitee.com/py2cn/pyminer/raw/master/"
+
+logger = logging.getLogger(__name__)
+
+
+class BaseUpdateThread(QThread):
+ def __init__(self, url):
+ super().__init__()
+ self.url = url
+ self.local_files_info = {}
+ self.remote_files_info = {}
+ self.delete_files_info = []
+ self.update_files_info = []
+ self.local_pip_list = []
+ self.remote_pip_list = []
+ self.root = Path(__file__).parent.parent.parent
+
+ def get_pip_list(self):
+ platform_ = platform.system()
+ requirement = 'requirements.txt'
+ if platform_ == "Linux":
+ requirement = 'requirements_linux.txt'
+ elif platform_ == "Mac":
+ requirement = 'requirements_mac.txt'
+ path = self.root.joinpath(requirement)
+ pip_list = []
+ if path.is_file():
+ with open(path, 'r', encoding='utf-8') as f:
+ pip_list = f.read().splitlines()
+ return pip_list
+
+ def get_file_md5(self, path):
+ if not os.path.isfile(path):
+ return ""
+ myhash = hashlib.md5()
+ f = open(path, 'rb')
+ while True:
+ b = f.read(8096)
+ b = b.replace(b'\r', b'') # 这里是因为git会自动转换\r\n,为了保证多平台统一,在计算md5码时去除\r
+ if not b:
+ break
+ myhash.update(b)
+ f.close()
+ md5 = myhash.hexdigest()
+ return md5
+
+ def generate_local_files_info(self):
+ """生成目录树信息"""
+ for path in self.root.glob('**/*'):
+ if should_be_recorded(path):
+ re_path = str(path.relative_to(self.root)).replace('\\', '/')
+ md5 = self.get_file_md5(path)
+ self.local_files_info.update(
+ {
+ re_path: md5
+ }
+ )
+
+ def get_remote_json_info(self):
+ """获取远程文件信息"""
+ try:
+ response = requests.get(url=self.url, timeout=5)
+ except Exception as e:
+ return
+ if response.status_code == 200:
+ self.remote_files_info = response.json()['files']
+
+ def generate_delete_files_info(self):
+ if not self.remote_files_info or not self.local_files_info:
+ return
+ for key in self.local_files_info.keys():
+ if not self.remote_files_info.get(key):
+ self.delete_files_info.append(key)
+
+ def generate_update_files_info(self):
+ if not self.remote_files_info or not self.local_files_info:
+ return
+ for key in self.remote_files_info.keys():
+ if self.local_files_info.get(key):
+ if self.remote_files_info[key] != self.local_files_info[key]: # md5值不同
+ self.update_files_info.append(key)
+ else:
+ self.update_files_info.append(key) # 本地不存在
+
+
+class UpdateTipThread(BaseUpdateThread):
+ """用于程序启动时检测更新"""
+ exist_update = Signal(bool)
+ update_files_list = Signal(list)
+ delete_files_list = Signal(list)
+
+ def __init__(self, url):
+ super().__init__(url)
+
+ def run(self):
+ self.generate_local_files_info()
+ self.local_pip_list = self.get_pip_list() # 获取当前的pip列表
+ self.get_remote_json_info()
+ self.generate_delete_files_info()
+ self.generate_update_files_info()
+ if self.update_files_info or self.delete_files_info: # 检测到可用更新
+ # if self.update_files_info: # 检测到可用更新
+ self.exist_update.emit(True)
+ self.update_files_list.emit(self.update_files_info)
+ self.delete_files_list.emit(self.delete_files_info)
+
+
+class UpdateClientThread(BaseUpdateThread):
+ """用于执行更新"""
+ upgrade_bar = Signal(int) # 更新进度条
+ tip_label = Signal(str) # 更新进度条
+ exit_sign = Signal(bool)
+
+ def __init__(self, url):
+ super().__init__(url)
+
+ def run(self):
+ log_path = Path(__file__).parent.joinpath('update.log')
+ if not log_path.is_file():
+ log_path.touch()
+ self.tip_label.emit('正在收集本地文件信息')
+ self.generate_local_files_info()
+ self.local_pip_list = self.get_pip_list() # 获取当前的pip列表
+ self.upgrade_bar.emit(20)
+ self.tip_label.emit('正在查找更新')
+ self.get_remote_json_info()
+ self.upgrade_bar.emit(40)
+ self.tip_label.emit('正在查找可删除文件')
+ self.generate_delete_files_info()
+ self.upgrade_bar.emit(50)
+ self.tip_label.emit('正在查找可更新文件')
+ self.generate_update_files_info()
+ self.upgrade_bar.emit(60)
+ self.tip_label.emit('正在执行更新')
+ self.perform_update()
+ self.upgrade_bar.emit(80)
+ self.tip_label.emit('正在删除多余文件')
+ self.perform_delete()
+ self.remote_pip_list = self.get_pip_list() # 再次获取pip列表
+ self.upgrade_bar.emit(90)
+ self.tip_label.emit('正在检查是否需要安装新的第三方库')
+ self.perform_pip()
+ os.remove(log_path) # 删除空文件,标志着更新完成
+ self.tip_label.emit('更新完成')
+ self.exit_sign.emit(True)
+ self.upgrade_bar.emit(100)
+
+ def perform_update(self):
+ counts = len(self.update_files_info)
+ for index, item in enumerate(self.update_files_info):
+ try:
+ url = REMOTE_URL + item
+ self.tip_label.emit('正在下载:%s' % url)
+ response = requests.get(url=url, timeout=5)
+ except Exception as e:
+ return
+ if response.status_code == 200:
+ local_path = self.root.joinpath(item)
+ if not local_path.parent.is_dir():
+ os.makedirs(local_path.parent)
+ with open(local_path, 'wb') as f:
+ f.write(response.content)
+ self.upgrade_bar.emit(60 + int((index + 1) * 20 / counts))
+
+ def perform_delete(self):
+ """执行删除"""
+ for item in self.delete_files_info:
+ abs_path = self.root.joinpath(item)
+ if abs_path.is_file():
+ os.remove(abs_path)
+
+ def perform_pip(self):
+ for item in self.remote_pip_list:
+ if item not in self.local_pip_list:
+ self.tip_label.emit('正在安装:%s' % item)
+ # 推荐的安装方式
+ subprocess.check_call(
+ [sys.executable, "-m", "pip", "install", item, '-i', 'https://pypi.douban.com/simple'])
+
+
+class UpdateTipClient(Ui_Dialog):
+ def __init__(self, startup: bool):
+ self.dialog = QDialog()
+ self.setupUi(self.dialog)
+ self.url = MD5_JSON_URL
+ self.update_files_list = []
+ self.delete_files_list = []
+ self.root = Path(__file__).parent.parent.parent.parent
+ self.is_start_thread(startup)
+
+ def is_start_thread(self, startup: bool):
+ """
+ 根据根目录下是否存在.git目录,判断是否处于开发状态,开发状态不检查更新
+ 根据设置界面检查更新checkbox是否为True,决定是否启动检查更新
+ """
+ if self.root.joinpath('.git').is_dir():
+ return
+ if not (startup and (not utils.get_settings_item_from_file("config.ini", "MAIN/CHECK_UPDATE"))):
+ self.thread = UpdateTipThread(url=self.url)
+ self.thread.update_files_list.connect(self.set_update_files_list)
+ self.thread.delete_files_list.connect(self.set_delete_files_list)
+ self.thread.exist_update.connect(self.set_exist_update)
+ self.thread.start()
+
+ def init_gui(self):
+ self.update_files_num = 0
+ self.delete_files_num = 0
+ self.button_close.clicked.connect(self.dialog.close)
+ self.button_confirm.clicked.connect(self.perform_update)
+ self.button_detail.clicked.connect(self.show_table)
+ self.table.setColumnCount(2)
+ self.table.setHorizontalHeaderLabels(['status', 'file'])
+ self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+ self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents)
+ self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeToContents)
+ self.table.setEditTriggers(QTableView.NoEditTriggers)
+ self.table.setMinimumHeight(300)
+ self.table.setVisible(False)
+ self.dialog.setFixedHeight(100)
+ self.move_center()
+ if not QApplication.instance():
+ app = QApplication(sys.argv)
+ self.dialog.exec_()
+
+ def move_center(self):
+ qr = self.dialog.frameGeometry()
+ cp = QDesktopWidget().availableGeometry().center()
+ qr.moveCenter(cp)
+ self.dialog.move(qr.topLeft())
+
+ def set_update_files_list(self, files):
+ self.update_files_list = files
+ row = self.table.rowCount()
+ self.table.setRowCount(self.table.rowCount() + len(files))
+ for index, item in enumerate(self.update_files_list):
+ self.table.setItem(row + index, 1, QTableWidgetItem(item))
+ self.table.setItem(row + index, 0, QTableWidgetItem('update'))
+ self.set_label()
+
+ def set_delete_files_list(self, files):
+ self.delete_files_list = files
+ row = self.table.rowCount()
+ self.table.setRowCount(row + len(files))
+ for index, item in enumerate(self.delete_files_list):
+ self.table.setItem(row + index, 1, QTableWidgetItem(item))
+ self.table.setItem(row + index, 0, QTableWidgetItem('delete'))
+ self.set_label()
+
+ def set_label(self):
+ self.label.setText(
+ "检测到新版本,此次包含{0}个文件更新,{1}个文件删除。".format(
+ len(self.update_files_list), len(self.delete_files_list)))
+
+ def set_exist_update(self, flag):
+ """
+ 存在更新则启动界面
+ """
+ if flag:
+ self.init_gui()
+
+ def perform_update(self):
+ self.dialog.close()
+ UpgradeClient(url=self.url)
+
+ def show_table(self):
+ if self.table.isVisible():
+ self.button_detail.setText('+')
+ self.table.setVisible(False)
+ self.dialog.setFixedHeight(100)
+ else:
+ self.dialog.setFixedHeight(400)
+ self.button_detail.setText('-')
+ self.table.setVisible(True)
+
+
+class UpgradeClient(QDialog):
+ def __init__(self, url):
+ super().__init__()
+ self.setWindowTitle("Pyminer客户端升级助手")
+ self.v_layout = QVBoxLayout()
+ self.h_layout = QHBoxLayout()
+ self.button = QPushButton('退出')
+ self.button.setEnabled(False)
+ self.button.clicked.connect(lambda: self.close())
+ self.upgrade_bar = QProgressBar()
+ self.tip_label = QLabel("")
+ self.upgrade_bar.setRange(0, 100)
+ self.upgrade_bar.setValue(0)
+ self.v_layout.addWidget(self.tip_label)
+ self.h_layout.addWidget(self.upgrade_bar)
+ self.h_layout.addWidget(self.button)
+ self.v_layout.addLayout(self.h_layout)
+ self.setWindowFlags(Qt.WindowMinimizeButtonHint)
+ self.setFixedWidth(500)
+ self.setFixedHeight(70)
+ self.move_center()
+ self.setLayout(self.v_layout)
+ self.thread = UpdateClientThread(url=url)
+ self.thread.upgrade_bar.connect(self.set_upgrade_bar)
+ self.thread.tip_label.connect(self.set_tip_label)
+ self.thread.exit_sign.connect(self.set_exit_sign)
+ self.thread.start()
+ self.exec_()
+
+ def move_center(self):
+ qr = self.frameGeometry()
+ cp = QDesktopWidget().availableGeometry().center()
+ qr.moveCenter(cp)
+ self.move(qr.topLeft())
+
+ def set_upgrade_bar(self, i):
+ self.upgrade_bar.setValue(i)
+
+ def set_tip_label(self, text):
+ self.tip_label.setText(text)
+
+ def set_exit_sign(self, sign):
+ self.button.setEnabled(sign)
+
+
+def perform_update():
+ if not QApplication.instance():
+ app = QApplication(sys.argv)
+ UpgradeClient(url=MD5_JSON_URL)
+
+
+def check_update_onload():
+ """在启动时检查是否需要先执行更新"""
+ log_path = Path(__file__).parent.joinpath('update.log')
+ if log_path.is_file():
+ perform_update()
+
+
+if __name__ == '__main__':
+ check_update_onload()
+ perform_update()
diff --git a/pyminer/lib/workspace/__init__.py b/pyminer/lib/workspace/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/lib/workspace/blinker/__init__.py b/pyminer/lib/workspace/blinker/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..3ea239c66afc9c25965aebc7712c610f429d2201
--- /dev/null
+++ b/pyminer/lib/workspace/blinker/__init__.py
@@ -0,0 +1,22 @@
+from blinker.base import (
+ ANY,
+ NamedSignal,
+ Namespace,
+ Signal,
+ WeakNamespace,
+ receiver_connected,
+ signal,
+)
+
+__all__ = [
+ 'ANY',
+ 'NamedSignal',
+ 'Namespace',
+ 'Signal',
+ 'WeakNamespace',
+ 'receiver_connected',
+ 'signal',
+ ]
+
+
+__version__ = '1.4'
diff --git a/pyminer/lib/workspace/blinker/_saferef.py b/pyminer/lib/workspace/blinker/_saferef.py
new file mode 100644
index 0000000000000000000000000000000000000000..269e36246831a04c93c107f679a8190ffae8b578
--- /dev/null
+++ b/pyminer/lib/workspace/blinker/_saferef.py
@@ -0,0 +1,234 @@
+# extracted from Louie, http://pylouie.org/
+# updated for Python 3
+#
+# Copyright (c) 2006 Patrick K. O'Brien, Mike C. Fletcher,
+# Matthew R. Scott
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided
+# with the distribution.
+#
+# * Neither the name of the nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+"""Refactored 'safe reference from dispatcher.py"""
+
+import operator
+import sys
+import traceback
+import weakref
+
+
+try:
+ callable
+except NameError:
+ def callable(object):
+ return hasattr(object, '__call__')
+
+
+if sys.version_info < (3,):
+ get_self = operator.attrgetter('im_self')
+ get_func = operator.attrgetter('im_func')
+else:
+ get_self = operator.attrgetter('__self__')
+ get_func = operator.attrgetter('__func__')
+
+
+def safe_ref(target, on_delete=None):
+ """Return a *safe* weak reference to a callable target.
+
+ - ``target``: The object to be weakly referenced, if it's a bound
+ method reference, will create a BoundMethodWeakref, otherwise
+ creates a simple weakref.
+
+ - ``on_delete``: If provided, will have a hard reference stored to
+ the callable to be called after the safe reference goes out of
+ scope with the reference object, (either a weakref or a
+ BoundMethodWeakref) as argument.
+ """
+ try:
+ im_self = get_self(target)
+ except AttributeError:
+ if callable(on_delete):
+ return weakref.ref(target, on_delete)
+ else:
+ return weakref.ref(target)
+ else:
+ if im_self is not None:
+ # Turn a bound method into a BoundMethodWeakref instance.
+ # Keep track of these instances for lookup by disconnect().
+ assert hasattr(target, 'im_func') or hasattr(target, '__func__'), (
+ "safe_ref target %r has im_self, but no im_func, "
+ "don't know how to create reference" % target)
+ reference = BoundMethodWeakref(target=target, on_delete=on_delete)
+ return reference
+
+
+class BoundMethodWeakref(object):
+ """'Safe' and reusable weak references to instance methods.
+
+ BoundMethodWeakref objects provide a mechanism for referencing a
+ bound method without requiring that the method object itself
+ (which is normally a transient object) is kept alive. Instead,
+ the BoundMethodWeakref object keeps weak references to both the
+ object and the function which together define the instance method.
+
+ Attributes:
+
+ - ``key``: The identity key for the reference, calculated by the
+ class's calculate_key method applied to the target instance method.
+
+ - ``deletion_methods``: Sequence of callable objects taking single
+ argument, a reference to this object which will be called when
+ *either* the target object or target function is garbage
+ collected (i.e. when this object becomes invalid). These are
+ specified as the on_delete parameters of safe_ref calls.
+
+ - ``weak_self``: Weak reference to the target object.
+
+ - ``weak_func``: Weak reference to the target function.
+
+ Class Attributes:
+
+ - ``_all_instances``: Class attribute pointing to all live
+ BoundMethodWeakref objects indexed by the class's
+ calculate_key(target) method applied to the target objects.
+ This weak value dictionary is used to short-circuit creation so
+ that multiple references to the same (object, function) pair
+ produce the same BoundMethodWeakref instance.
+ """
+
+ _all_instances = weakref.WeakValueDictionary()
+
+ def __new__(cls, target, on_delete=None, *arguments, **named):
+ """Create new instance or return current instance.
+
+ Basically this method of construction allows us to
+ short-circuit creation of references to already- referenced
+ instance methods. The key corresponding to the target is
+ calculated, and if there is already an existing reference,
+ that is returned, with its deletion_methods attribute updated.
+ Otherwise the new instance is created and registered in the
+ table of already-referenced methods.
+ """
+ key = cls.calculate_key(target)
+ current = cls._all_instances.get(key)
+ if current is not None:
+ current.deletion_methods.append(on_delete)
+ return current
+ else:
+ base = super(BoundMethodWeakref, cls).__new__(cls)
+ cls._all_instances[key] = base
+ base.__init__(target, on_delete, *arguments, **named)
+ return base
+
+ def __init__(self, target, on_delete=None):
+ """Return a weak-reference-like instance for a bound method.
+
+ - ``target``: The instance-method target for the weak reference,
+ must have im_self and im_func attributes and be
+ reconstructable via the following, which is true of built-in
+ instance methods::
+
+ target.im_func.__get__( target.im_self )
+
+ - ``on_delete``: Optional callback which will be called when
+ this weak reference ceases to be valid (i.e. either the
+ object or the function is garbage collected). Should take a
+ single argument, which will be passed a pointer to this
+ object.
+ """
+ def remove(weak, self=self):
+ """Set self.isDead to True when method or instance is destroyed."""
+ methods = self.deletion_methods[:]
+ del self.deletion_methods[:]
+ try:
+ del self.__class__._all_instances[self.key]
+ except KeyError:
+ pass
+ for function in methods:
+ try:
+ if callable(function):
+ function(self)
+ except Exception:
+ try:
+ traceback.print_exc()
+ except AttributeError:
+ e = sys.exc_info()[1]
+ print ('Exception during saferef %s '
+ 'cleanup function %s: %s' % (self, function, e))
+ self.deletion_methods = [on_delete]
+ self.key = self.calculate_key(target)
+ im_self = get_self(target)
+ im_func = get_func(target)
+ self.weak_self = weakref.ref(im_self, remove)
+ self.weak_func = weakref.ref(im_func, remove)
+ self.self_name = str(im_self)
+ self.func_name = str(im_func.__name__)
+
+ def calculate_key(cls, target):
+ """Calculate the reference key for this reference.
+
+ Currently this is a two-tuple of the id()'s of the target
+ object and the target function respectively.
+ """
+ return (id(get_self(target)), id(get_func(target)))
+ calculate_key = classmethod(calculate_key)
+
+ def __str__(self):
+ """Give a friendly representation of the object."""
+ return "%s(%s.%s)" % (
+ self.__class__.__name__,
+ self.self_name,
+ self.func_name,
+ )
+
+ __repr__ = __str__
+
+ def __nonzero__(self):
+ """Whether we are still a valid reference."""
+ return self() is not None
+
+ def __cmp__(self, other):
+ """Compare with another reference."""
+ if not isinstance(other, self.__class__):
+ return cmp(self.__class__, type(other))
+ return cmp(self.key, other.key)
+
+ def __call__(self):
+ """Return a strong reference to the bound method.
+
+ If the target cannot be retrieved, then will return None,
+ otherwise returns a bound instance method for our object and
+ function.
+
+ Note: You may call this method any number of times, as it does
+ not invalidate the reference.
+ """
+ target = self.weak_self()
+ if target is not None:
+ function = self.weak_func()
+ if function is not None:
+ return function.__get__(target)
+ return None
diff --git a/pyminer/lib/workspace/blinker/_utilities.py b/pyminer/lib/workspace/blinker/_utilities.py
new file mode 100644
index 0000000000000000000000000000000000000000..056270d7dec35d51ece7421bd754e8ba0ae9cbb6
--- /dev/null
+++ b/pyminer/lib/workspace/blinker/_utilities.py
@@ -0,0 +1,163 @@
+from weakref import ref
+
+from blinker._saferef import BoundMethodWeakref
+
+
+try:
+ callable
+except NameError:
+ def callable(object):
+ return hasattr(object, '__call__')
+
+
+try:
+ from collections import defaultdict
+except:
+ class defaultdict(dict):
+
+ def __init__(self, default_factory=None, *a, **kw):
+ if (default_factory is not None and
+ not hasattr(default_factory, '__call__')):
+ raise TypeError('first argument must be callable')
+ dict.__init__(self, *a, **kw)
+ self.default_factory = default_factory
+
+ def __getitem__(self, key):
+ try:
+ return dict.__getitem__(self, key)
+ except KeyError:
+ return self.__missing__(key)
+
+ def __missing__(self, key):
+ if self.default_factory is None:
+ raise KeyError(key)
+ self[key] = value = self.default_factory()
+ return value
+
+ def __reduce__(self):
+ if self.default_factory is None:
+ args = tuple()
+ else:
+ args = self.default_factory,
+ return type(self), args, None, None, self.items()
+
+ def copy(self):
+ return self.__copy__()
+
+ def __copy__(self):
+ return type(self)(self.default_factory, self)
+
+ def __deepcopy__(self, memo):
+ import copy
+ return type(self)(self.default_factory,
+ copy.deepcopy(self.items()))
+
+ def __repr__(self):
+ return 'defaultdict(%s, %s)' % (self.default_factory,
+ dict.__repr__(self))
+
+
+try:
+ from contextlib import contextmanager
+except ImportError:
+ def contextmanager(fn):
+ def oops(*args, **kw):
+ raise RuntimeError("Python 2.5 or above is required to use "
+ "context managers.")
+ oops.__name__ = fn.__name__
+ return oops
+
+class _symbol(object):
+
+ def __init__(self, name):
+ """Construct a new named symbol."""
+ self.__name__ = self.name = name
+
+ def __reduce__(self):
+ return symbol, (self.name,)
+
+ def __repr__(self):
+ return self.name
+_symbol.__name__ = 'symbol'
+
+
+class symbol(object):
+ """A constant symbol.
+
+ >>> symbol('foo') is symbol('foo')
+ True
+ >>> symbol('foo')
+ foo
+
+ A slight refinement of the MAGICCOOKIE=object() pattern. The primary
+ advantage of symbol() is its repr(). They are also singletons.
+
+ Repeated calls of symbol('name') will all return the same instance.
+
+ """
+ symbols = {}
+
+ def __new__(cls, name):
+ try:
+ return cls.symbols[name]
+ except KeyError:
+ return cls.symbols.setdefault(name, _symbol(name))
+
+
+try:
+ text = (str, unicode)
+except NameError:
+ text = str
+
+
+def hashable_identity(obj):
+ if hasattr(obj, '__func__'):
+ return (id(obj.__func__), id(obj.__self__))
+ elif hasattr(obj, 'im_func'):
+ return (id(obj.im_func), id(obj.im_self))
+ elif isinstance(obj, text):
+ return obj
+ else:
+ return id(obj)
+
+
+WeakTypes = (ref, BoundMethodWeakref)
+
+
+class annotatable_weakref(ref):
+ """A weakref.ref that supports custom instance attributes."""
+
+
+def reference(object, callback=None, **annotations):
+ """Return an annotated weak ref."""
+ if callable(object):
+ weak = callable_reference(object, callback)
+ else:
+ weak = annotatable_weakref(object, callback)
+ for key, value in annotations.items():
+ setattr(weak, key, value)
+ return weak
+
+
+def callable_reference(object, callback=None):
+ """Return an annotated weak ref, supporting bound instance methods."""
+ if hasattr(object, 'im_self') and object.im_self is not None:
+ return BoundMethodWeakref(target=object, on_delete=callback)
+ elif hasattr(object, '__self__') and object.__self__ is not None:
+ return BoundMethodWeakref(target=object, on_delete=callback)
+ return annotatable_weakref(object, callback)
+
+
+class lazy_property(object):
+ """A @property that is only evaluated once."""
+
+ def __init__(self, deferred):
+ self._deferred = deferred
+ self.__doc__ = deferred.__doc__
+
+ def __get__(self, obj, cls):
+ if obj is None:
+ return self
+ value = self._deferred(obj)
+ setattr(obj, self._deferred.__name__, value)
+ return value
diff --git a/pyminer/lib/workspace/blinker/base.py b/pyminer/lib/workspace/blinker/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..cc5880e6943142a0794d1c5effc12cb62a22818e
--- /dev/null
+++ b/pyminer/lib/workspace/blinker/base.py
@@ -0,0 +1,455 @@
+# -*- coding: utf-8; fill-column: 76 -*-
+"""Signals and events.
+
+A small implementation of signals, inspired by a snippet of Django signal
+API client code seen in a blog post. Signals are first-class objects and
+each manages its own receivers and message emission.
+
+The :func:`signal` function provides singleton behavior for named signals.
+
+"""
+from warnings import warn
+from weakref import WeakValueDictionary
+
+from blinker._utilities import (
+ WeakTypes,
+ contextmanager,
+ defaultdict,
+ hashable_identity,
+ lazy_property,
+ reference,
+ symbol,
+ )
+
+
+ANY = symbol('ANY')
+ANY.__doc__ = 'Token for "any sender".'
+ANY_ID = 0
+
+
+class Signal(object):
+ """A notification emitter."""
+
+ #: An :obj:`ANY` convenience synonym, allows ``Signal.ANY``
+ #: without an additional import.
+ ANY = ANY
+
+ @lazy_property
+ def receiver_connected(self):
+ """Emitted after each :meth:`connect`.
+
+ The signal sender is the signal instance, and the :meth:`connect`
+ arguments are passed through: *receiver*, *sender*, and *weak*.
+
+ .. versionadded:: 1.2
+
+ """
+ return Signal(doc="Emitted after a receiver connects.")
+
+ @lazy_property
+ def receiver_disconnected(self):
+ """Emitted after :meth:`disconnect`.
+
+ The sender is the signal instance, and the :meth:`disconnect` arguments
+ are passed through: *receiver* and *sender*.
+
+ Note, this signal is emitted **only** when :meth:`disconnect` is
+ called explicitly.
+
+ The disconnect signal can not be emitted by an automatic disconnect
+ (due to a weakly referenced receiver or sender going out of scope),
+ as the receiver and/or sender instances are no longer available for
+ use at the time this signal would be emitted.
+
+ An alternative approach is available by subscribing to
+ :attr:`receiver_connected` and setting up a custom weakref cleanup
+ callback on weak receivers and senders.
+
+ .. versionadded:: 1.2
+
+ """
+ return Signal(doc="Emitted after a receiver disconnects.")
+
+ def __init__(self, doc=None):
+ """
+ :param doc: optional. If provided, will be assigned to the signal's
+ __doc__ attribute.
+
+ """
+ if doc:
+ self.__doc__ = doc
+ #: A mapping of connected receivers.
+ #:
+ #: The values of this mapping are not meaningful outside of the
+ #: internal :class:`Signal` implementation, however the boolean value
+ #: of the mapping is useful as an extremely efficient check to see if
+ #: any receivers are connected to the signal.
+ self.receivers = {}
+ self._by_receiver = defaultdict(set)
+ self._by_sender = defaultdict(set)
+ self._weak_senders = {}
+
+ def connect(self, receiver, sender=ANY, weak=True):
+ """Connect *receiver* to signal events sent by *sender*.
+
+ :param receiver: A callable. Will be invoked by :meth:`send` with
+ `sender=` as a single positional argument and any \*\*kwargs that
+ were provided to a call to :meth:`send`.
+
+ :param sender: Any object or :obj:`ANY`, defaults to ``ANY``.
+ Restricts notifications delivered to *receiver* to only those
+ :meth:`send` emissions sent by *sender*. If ``ANY``, the receiver
+ will always be notified. A *receiver* may be connected to
+ multiple *sender* values on the same Signal through multiple calls
+ to :meth:`connect`.
+
+ :param weak: If true, the Signal will hold a weakref to *receiver*
+ and automatically disconnect when *receiver* goes out of scope or
+ is garbage collected. Defaults to True.
+
+ """
+ receiver_id = hashable_identity(receiver)
+ if weak:
+ receiver_ref = reference(receiver, self._cleanup_receiver)
+ receiver_ref.receiver_id = receiver_id
+ else:
+ receiver_ref = receiver
+ if sender is ANY:
+ sender_id = ANY_ID
+ else:
+ sender_id = hashable_identity(sender)
+
+ self.receivers.setdefault(receiver_id, receiver_ref)
+ self._by_sender[sender_id].add(receiver_id)
+ self._by_receiver[receiver_id].add(sender_id)
+ del receiver_ref
+
+ if sender is not ANY and sender_id not in self._weak_senders:
+ # wire together a cleanup for weakref-able senders
+ try:
+ sender_ref = reference(sender, self._cleanup_sender)
+ sender_ref.sender_id = sender_id
+ except TypeError:
+ pass
+ else:
+ self._weak_senders.setdefault(sender_id, sender_ref)
+ del sender_ref
+
+ # broadcast this connection. if receivers raise, disconnect.
+ if ('receiver_connected' in self.__dict__ and
+ self.receiver_connected.receivers):
+ try:
+ self.receiver_connected.send(self,
+ receiver=receiver,
+ sender=sender,
+ weak=weak)
+ except:
+ self.disconnect(receiver, sender)
+ raise
+ if receiver_connected.receivers and self is not receiver_connected:
+ try:
+ receiver_connected.send(self,
+ receiver_arg=receiver,
+ sender_arg=sender,
+ weak_arg=weak)
+ except:
+ self.disconnect(receiver, sender)
+ raise
+ return receiver
+
+ def connect_via(self, sender, weak=False):
+ """Connect the decorated function as a receiver for *sender*.
+
+ :param sender: Any object or :obj:`ANY`. The decorated function
+ will only receive :meth:`send` emissions sent by *sender*. If
+ ``ANY``, the receiver will always be notified. A function may be
+ decorated multiple times with differing *sender* values.
+
+ :param weak: If true, the Signal will hold a weakref to the
+ decorated function and automatically disconnect when *receiver*
+ goes out of scope or is garbage collected. Unlike
+ :meth:`connect`, this defaults to False.
+
+ The decorated function will be invoked by :meth:`send` with
+ `sender=` as a single positional argument and any \*\*kwargs that
+ were provided to the call to :meth:`send`.
+
+
+ .. versionadded:: 1.1
+
+ """
+ def decorator(fn):
+ self.connect(fn, sender, weak)
+ return fn
+ return decorator
+
+ @contextmanager
+ def connected_to(self, receiver, sender=ANY):
+ """Execute a block with the signal temporarily connected to *receiver*.
+
+ :param receiver: a receiver callable
+ :param sender: optional, a sender to filter on
+
+ This is a context manager for use in the ``with`` statement. It can
+ be useful in unit tests. *receiver* is connected to the signal for
+ the duration of the ``with`` block, and will be disconnected
+ automatically when exiting the block:
+
+ .. testsetup::
+
+ from __future__ import with_statement
+ from blinker import Signal
+ on_ready = Signal()
+ receiver = lambda sender: None
+
+ .. testcode::
+
+ with on_ready.connected_to(receiver):
+ # do stuff
+ on_ready.send(123)
+
+ .. versionadded:: 1.1
+
+ """
+ self.connect(receiver, sender=sender, weak=False)
+ try:
+ yield None
+ except:
+ self.disconnect(receiver)
+ raise
+ else:
+ self.disconnect(receiver)
+
+ def temporarily_connected_to(self, receiver, sender=ANY):
+ """An alias for :meth:`connected_to`.
+
+ :param receiver: a receiver callable
+ :param sender: optional, a sender to filter on
+
+ .. versionadded:: 0.9
+
+ .. versionchanged:: 1.1
+ Renamed to :meth:`connected_to`. ``temporarily_connected_to`` was
+ deprecated in 1.2 and will be removed in a subsequent version.
+
+ """
+ warn("temporarily_connected_to is deprecated; "
+ "use connected_to instead.",
+ DeprecationWarning)
+ return self.connected_to(receiver, sender)
+
+ def send(self, *sender, **kwargs):
+ """Emit this signal on behalf of *sender*, passing on \*\*kwargs.
+
+ Returns a list of 2-tuples, pairing receivers with their return
+ value. The ordering of receiver notification is undefined.
+
+ :param \*sender: Any object or ``None``. If omitted, synonymous
+ with ``None``. Only accepts one positional argument.
+
+ :param \*\*kwargs: Data to be sent to receivers.
+
+ """
+ # Using '*sender' rather than 'sender=None' allows 'sender' to be
+ # used as a keyword argument- i.e. it's an invisible name in the
+ # function signature.
+ if len(sender) == 0:
+ sender = None
+ elif len(sender) > 1:
+ raise TypeError('send() accepts only one positional argument, '
+ '%s given' % len(sender))
+ else:
+ sender = sender[0]
+ if not self.receivers:
+ return []
+ else:
+ return [(receiver, receiver(sender, **kwargs))
+ for receiver in self.receivers_for(sender)]
+
+ def has_receivers_for(self, sender):
+ """True if there is probably a receiver for *sender*.
+
+ Performs an optimistic check only. Does not guarantee that all
+ weakly referenced receivers are still alive. See
+ :meth:`receivers_for` for a stronger search.
+
+ """
+ if not self.receivers:
+ return False
+ if self._by_sender[ANY_ID]:
+ return True
+ if sender is ANY:
+ return False
+ return hashable_identity(sender) in self._by_sender
+
+ def receivers_for(self, sender):
+ """Iterate all live receivers listening for *sender*."""
+ # TODO: test receivers_for(ANY)
+ if self.receivers:
+ sender_id = hashable_identity(sender)
+ if sender_id in self._by_sender:
+ ids = (self._by_sender[ANY_ID] |
+ self._by_sender[sender_id])
+ else:
+ ids = self._by_sender[ANY_ID].copy()
+ for receiver_id in ids:
+ receiver = self.receivers.get(receiver_id)
+ if receiver is None:
+ continue
+ if isinstance(receiver, WeakTypes):
+ strong = receiver()
+ if strong is None:
+ self._disconnect(receiver_id, ANY_ID)
+ continue
+ receiver = strong
+ yield receiver
+
+ def disconnect(self, receiver, sender=ANY):
+ """Disconnect *receiver* from this signal's events.
+
+ :param receiver: a previously :meth:`connected` callable
+
+ :param sender: a specific sender to disconnect from, or :obj:`ANY`
+ to disconnect from all senders. Defaults to ``ANY``.
+
+ """
+ if sender is ANY:
+ sender_id = ANY_ID
+ else:
+ sender_id = hashable_identity(sender)
+ receiver_id = hashable_identity(receiver)
+ self._disconnect(receiver_id, sender_id)
+
+ if ('receiver_disconnected' in self.__dict__ and
+ self.receiver_disconnected.receivers):
+ self.receiver_disconnected.send(self,
+ receiver=receiver,
+ sender=sender)
+
+ def _disconnect(self, receiver_id, sender_id):
+ if sender_id == ANY_ID:
+ if self._by_receiver.pop(receiver_id, False):
+ for bucket in self._by_sender.values():
+ bucket.discard(receiver_id)
+ self.receivers.pop(receiver_id, None)
+ else:
+ self._by_sender[sender_id].discard(receiver_id)
+ self._by_receiver[receiver_id].discard(sender_id)
+
+ def _cleanup_receiver(self, receiver_ref):
+ """Disconnect a receiver from all senders."""
+ self._disconnect(receiver_ref.receiver_id, ANY_ID)
+
+ def _cleanup_sender(self, sender_ref):
+ """Disconnect all receivers from a sender."""
+ sender_id = sender_ref.sender_id
+ assert sender_id != ANY_ID
+ self._weak_senders.pop(sender_id, None)
+ for receiver_id in self._by_sender.pop(sender_id, ()):
+ self._by_receiver[receiver_id].discard(sender_id)
+
+ def _cleanup_bookkeeping(self):
+ """Prune unused sender/receiver bookeeping. Not threadsafe.
+
+ Connecting & disconnecting leave behind a small amount of bookeeping
+ for the receiver and sender values. Typical workloads using Blinker,
+ for example in most web apps, Flask, CLI scripts, etc., are not
+ adversely affected by this bookkeeping.
+
+ With a long-running Python process performing dynamic signal routing
+ with high volume- e.g. connecting to function closures, "senders" are
+ all unique object instances, and doing all of this over and over- you
+ may see memory usage will grow due to extraneous bookeeping. (An empty
+ set() for each stale sender/receiver pair.)
+
+ This method will prune that bookeeping away, with the caveat that such
+ pruning is not threadsafe. The risk is that cleanup of a fully
+ disconnected receiver/sender pair occurs while another thread is
+ connecting that same pair. If you are in the highly dynamic, unique
+ receiver/sender situation that has lead you to this method, that
+ failure mode is perhaps not a big deal for you.
+ """
+ for mapping in (self._by_sender, self._by_receiver):
+ for _id, bucket in list(mapping.items()):
+ if not bucket:
+ mapping.pop(_id, None)
+
+ def _clear_state(self):
+ """Throw away all signal state. Useful for unit tests."""
+ self._weak_senders.clear()
+ self.receivers.clear()
+ self._by_sender.clear()
+ self._by_receiver.clear()
+
+
+receiver_connected = Signal("""\
+Sent by a :class:`Signal` after a receiver connects.
+
+:argument: the Signal that was connected to
+:keyword receiver_arg: the connected receiver
+:keyword sender_arg: the sender to connect to
+:keyword weak_arg: true if the connection to receiver_arg is a weak reference
+
+.. deprecated:: 1.2
+
+As of 1.2, individual signals have their own private
+:attr:`~Signal.receiver_connected` and
+:attr:`~Signal.receiver_disconnected` signals with a slightly simplified
+call signature. This global signal is planned to be removed in 1.6.
+
+""")
+
+
+class NamedSignal(Signal):
+ """A named generic notification emitter."""
+
+ def __init__(self, name, doc=None):
+ Signal.__init__(self, doc)
+
+ #: The name of this signal.
+ self.name = name
+
+ def __repr__(self):
+ base = Signal.__repr__(self)
+ return "%s; %r>" % (base[:-1], self.name)
+
+
+class Namespace(dict):
+ """A mapping of signal names to signals."""
+
+ def signal(self, name, doc=None):
+ """Return the :class:`NamedSignal` *name*, creating it if required.
+
+ Repeated calls to this function will return the same signal object.
+
+ """
+ try:
+ return self[name]
+ except KeyError:
+ return self.setdefault(name, NamedSignal(name, doc))
+
+
+class WeakNamespace(WeakValueDictionary):
+ """A weak mapping of signal names to signals.
+
+ Automatically cleans up unused Signals when the last reference goes out
+ of scope. This namespace implementation exists for a measure of legacy
+ compatibility with Blinker <= 1.2, and may be dropped in the future.
+
+ .. versionadded:: 1.3
+
+ """
+
+ def signal(self, name, doc=None):
+ """Return the :class:`NamedSignal` *name*, creating it if required.
+
+ Repeated calls to this function will return the same signal object.
+
+ """
+ try:
+ return self[name]
+ except KeyError:
+ return self.setdefault(name, NamedSignal(name, doc))
+
+
+signal = Namespace().signal
diff --git a/pyminer/lib/workspace/data/__init__.py b/pyminer/lib/workspace/data/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/lib/workspace/data_adapter/__init__.py b/pyminer/lib/workspace/data_adapter/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..5427329dd24e1aa97b3a9227a5a39cd6df70c3e4
--- /dev/null
+++ b/pyminer/lib/workspace/data_adapter/__init__.py
@@ -0,0 +1,8 @@
+"""
+TODO 数据结构并没有进行社区讨论,是我拍脑袋写的,后面应当再进行接口及结构类型的合理性分析。
+"""
+
+from .array import ArrayAdapter
+from .base import BaseAdapter
+from .detector import Detector
+from .universal import UniversalAdapter
diff --git a/pyminer/lib/workspace/data_adapter/array.py b/pyminer/lib/workspace/data_adapter/array.py
new file mode 100644
index 0000000000000000000000000000000000000000..177e3e4acf7b0e6a89a1fbdcf72192c3e7506f1f
--- /dev/null
+++ b/pyminer/lib/workspace/data_adapter/array.py
@@ -0,0 +1,67 @@
+"""
+这个模块是对于 ``np.ndarray`` 进行的封装,也是以此为例探索如何进行数据的封装。
+"""
+
+# from functools import cached_property
+from typing import Any, Tuple, Dict
+
+import numpy
+from sliceable_generator import SliceableGenerator
+
+from .universal import UniversalAdapter
+
+
+class ArrayAdapter(UniversalAdapter):
+ data: numpy.ndarray
+
+ # @cached_property
+ def shape(self) -> Tuple[int, ...]:
+ """
+ 获取矩阵的形状。
+
+ 矩阵的形状,或者说大小,就是单纯的 ``ndarray.shape`` 。
+
+ Returns:
+ 矩阵的形状
+ """
+ return self.data.shape
+
+ # @cached_property
+ def serialized_data(self):
+ """
+ 将数据进行序列化。
+
+ 对于 ``ndarray`` , ``numpy`` 原生的 ``ndarray.tolist()`` 已经可以完美地实现这个功能,没必要进行额外的工作。
+
+ Returns:
+ 序列化后的数据,对于 ``ndarray`` 就直接调用了 ``ndarray.tolist()`` 函数。
+ """
+ return self.data.tolist()
+
+ @classmethod
+ def load(cls, data: Dict[str, Any]) -> 'ArrayAdapter':
+ data = numpy.array(data['data'])
+ return cls(data)
+
+ def get_header_name(self, dimension=0):
+ """
+ 对于矩阵而言,其行列名就是简单的数字。
+
+ 不同于 ``DataFrame`` , ``array`` 是没有行列名的。
+ 此处仅作为占位,返回从0开始的数字列表。
+
+ 由于 ``python`` 中采用0位置作为数组的第一位,此处采纳相同的用法,采用0作为起点。
+
+ Args:
+ dimension: 行列名的维度,这个参数决定了返回值的长度。
+
+ Returns:
+ 某个维度下的表头的列表。
+
+ """
+ dimensions = len(self.data.shape)
+ if dimensions == 1 and dimension == 1: # 一维数组的维度1需要独立定义
+ return ('0' for _ in range(1))
+ assert 0 <= dimension < dimensions, f'对于此数组,维度应在[0,{dimensions}]范围内'
+ assert isinstance(dimension, int), '维度应当是整数'
+ return SliceableGenerator(str(i) for i in range(self.shape[dimension]))
diff --git a/pyminer/lib/workspace/data_adapter/base.py b/pyminer/lib/workspace/data_adapter/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..5a543bdc347ab66a61db238cf9817b8b83677b57
--- /dev/null
+++ b/pyminer/lib/workspace/data_adapter/base.py
@@ -0,0 +1,174 @@
+# from functools import cached_property
+from typing import Any, Tuple, Dict, Union
+
+from sliceable_generator import SliceableGenerator
+
+SELECTOR_TYPE = Union[Tuple[Union[int, None], Union[int, None], Union[int, None]], int]
+
+
+class BaseAdapter(object):
+ """
+ 数据适配器的基类。
+
+ 数据适配器的意义在于,将逻辑与界面进行分离。
+ 工作空间中的每一个数据都将是一个数据适配器的实例。
+ 这就确保了所有的数据接口统一,可以方便地用于在界面中进行显示,
+ 而不必要在界面中加入逻辑进行判断。
+
+ 在这个类中采用了缓存属性的方案,使得一些计算耗时比较长的内容可以仅在需要的时候才进行计算。
+
+ 所有在 ``pyminer`` 中进行跨线程、跨进程、跨插件的数据交换功能都应当采用这个类的子类的实例进行传输。
+
+ 目前(2020/11/22)这个适配器仍是一个愿景,各个插件依旧各自为战,后面需要进行整合。
+
+ 请不要直接继承此类,请继承 ``UniversalAdapter`` 类,以获得更通用的功能,以及新实现的通用方法。
+
+ """
+ data: Any # 请各个子适配器都定义这个data的类型,以便于IDE进行类型提示
+
+ def __init__(self, data: Any):
+ self.data = data
+
+ # @cached_property
+ def shape(self) -> Tuple[int, ...]:
+ """数据的形状,与 ``numpy`` 保持一致。
+
+ Returns:
+ #. 对于没有维度的数据,比如整型、字符串,其尺寸就是一个空元组。
+ #. 对于一个长度为 ``n`` 的一维列表,其尺寸就是 (n,)
+
+
+ """
+ raise NotImplementedError
+
+ # @cached_property
+ def dimensions(self):
+ """数据的维度,事实上就是 ``shape`` 的长度的简单计算。"""
+ return len(self.shape)
+
+ # @cached_property
+ def abstract(self) -> Dict[str, Any]:
+ """
+ 摘要,用于实现元数据的快速传输
+ """
+ return {
+ 'shape': list(self.shape)
+ }
+
+ # @cached_property
+ def serialized_data(self):
+ """
+ 实现实际存储的数据的序列化。
+
+ 数据转变为字符串是通过 ``json.dumps`` 实现的,
+ 这个接口只需要保证返回值是 ``json.dumps`` 可以解析的内容即可,
+ 这个接口的具体实现不需要调用 ``json.dumps`` 。
+
+ 这个接口仅处理数据本身,不包括数据的信息等元数据。
+ 关于元数据的处理将在 ``dump`` 接口中进行描述。
+
+ 以下是一些序列化的示例:
+ * 对于Numpy的矩阵可以序列化为[[1,2,3],[4,5,6]]。
+ * 对于Pandas的表格可以序列化为[[1,2,3],['Tom','Jack','Jenny']]。
+ * 对于Pandas等具有行列名的数据结构,其行列名定义在Abstract中,而非serialized_data中。
+
+ Returns:
+ 嵌套的 ``dict`` , ``list`` ,以及具体的 ``str`` , ``int`` , ``float`` 等数据。
+ 这应该是可以被 ``json.dumps`` 处理的类型。
+ """
+ raise NotImplementedError
+
+ def dump(self) -> Dict[str, Any]:
+ """
+ 将数据进行序列化。
+
+ dump/load用于进行数据的交互,即打造类似于pickle的功能,不过可以序列化后通过http进行传输。
+
+ 目前考虑可能要在Server端添加type信息,因此可能需要保留关键字type。
+
+ 这个接口将处理包括具体的数据本身,以及元数据在内的所有数据。
+ 这个接口相当于实现了 ``pickle`` 的部分功能,可以确保整个 ``Adapter`` 在处理前后保持一致。
+
+ TODO (panhaoyu) 如果后期采用高速传输方案,将修改这个接口,将value字段设置为内存地址的标识符。
+
+ Returns:
+ 所有数据的序列化。
+ """
+ result = self.abstract.copy() # 这里是浅复制,因为本函数仅修改第一层对象,因此没必要采用深复制
+ result['data'] = self.serialized_data
+ result['type'] = self.__class__.__name__ # 这个用于在反序列化时查找值
+ return result
+
+ @classmethod
+ def load(cls, data: Dict[str, Any]) -> 'BaseAdapter':
+ """
+ 将数据进行反序列化。
+
+ dump/load用于进行数据的交互,即打造类似于pickle的功能,不过可以序列化后通过http进行传输。
+
+ 这个接口用于解析由 ``dump`` 函数存储的数据。
+
+ Args:
+ data: 由 ``dump`` 进行导出的数据。
+
+ Returns:
+ 一个基于 ``dump`` 的导出数据读取得到的新的 ``Adapter`` 的实例。
+
+ """
+ raise NotImplementedError
+
+ def get_array(self) -> Union[SliceableGenerator[Any], Any]:
+ # TODO (panhaoyu) 这个类型事实上应该采用递归类型进行表示,不过我暂时不会用,就采用 ``Any`` 进行临时的表示了。
+ """将数据整合为一个多维数组。
+
+ 这个方法主要用于切片等需要对数据进行操作的场景。
+
+ 这个数组采用了 ``SliceableGenerator`` ,以获得懒加载功能以及多维切片功能,相对于 ``list`` 性能较高,
+ 不过相对于 ``numpy.ndarray`` 的直接索引,还是慢了不少,特殊数据类型还是需要进行优化。
+
+ Returns:
+ 由多层生成器构成的多维数组。
+ 这个数组的维度以及各维度的大小应当于 ``shape`` 保持一致。
+
+ Notes:
+ 这个方法并不包含对数据的切片操作,而是返回了一个可以用于切片的生成器。
+
+ 这个方法并不一定会返回一个数组!
+ 如果需要保证可以拿到数组,可以使用 ``get_array_atleast_2d`` 。
+ """
+ raise NotImplementedError
+
+ def get_array_atleast_2d(self) -> SliceableGenerator[Any]:
+ """在工作空间的数据查看等场合,需要保证数据类型至少是一个二维数组,以用于表格显式。
+
+ Returns:
+ 一个至少是二维的数组。
+
+ Notes:
+ ``get_array`` 和 ``get_array_atleast_2d`` 都是有应用场景的。
+ ``get_array`` 是用于用户进行切片操作。
+ ``get_array_atleast_2d`` 是用于进行表格显示的。
+
+ """
+ raise NotImplementedError
+
+ def get_header_name(self, dimension=0) -> SliceableGenerator[str]:
+ """
+ 获取数据在某个维度上的名称列表。
+
+ 即使是一维数组,也要同时定义dimension=0和dimension=1。
+
+ Args:
+ dimension: 需要查看的维度。
+
+ Returns:
+ 该维度上所有表头的名称。
+
+ """
+ raise NotImplementedError
+
+
+if __name__ == '__main__':
+ from doctest import testmod
+
+ testmod()
diff --git a/pyminer/lib/workspace/data_adapter/data_frame.py b/pyminer/lib/workspace/data_adapter/data_frame.py
new file mode 100644
index 0000000000000000000000000000000000000000..53231633e7b78347de1cd64251f6aea2bb8b25f0
--- /dev/null
+++ b/pyminer/lib/workspace/data_adapter/data_frame.py
@@ -0,0 +1,41 @@
+"""
+这个模块是对于 ``np.ndarray`` 进行的封装,也是以此为例探索如何进行数据的封装。
+"""
+
+# from functools import cached_property
+from typing import Any, Tuple, Dict
+
+from pandas import DataFrame
+from sliceable_generator import SliceableGenerator
+
+from . import ArrayAdapter
+
+
+class DataFrameAdapter(ArrayAdapter):
+ data: DataFrame
+
+ # @cached_property
+ def shape(self) -> Tuple[int, int]:
+ """获取表格的形状,必为一个二维数组"""
+ return self.data.shape
+
+ # @cached_property
+ def serialized_data(self):
+ return self.data.to_dict()
+
+ @classmethod
+ def load(cls, data: Dict[str, Any]) -> 'DataFrameAdapter':
+ return cls(DataFrame.from_dict(data['data']))
+
+ def get_array(self):
+ return SliceableGenerator(self.data.values, depth=2)
+
+ def get_array_atleast_2d(self):
+ return self.get_array()
+
+ def get_header_name(self, dimension=0):
+ assert dimension in (0, 1)
+ if dimension == 0:
+ return SliceableGenerator(str(i) for i in self.data.index.to_list())
+ else:
+ return SliceableGenerator(str(i) for i in self.data.columns.to_list())
diff --git a/pyminer/lib/workspace/data_adapter/detector.py b/pyminer/lib/workspace/data_adapter/detector.py
new file mode 100644
index 0000000000000000000000000000000000000000..b1e997094d96298c460011b1a600acb26436b62b
--- /dev/null
+++ b/pyminer/lib/workspace/data_adapter/detector.py
@@ -0,0 +1,121 @@
+"""
+本模块用于判断一个数据的类型,并将其封装为合适的Adapter。
+"""
+from typing import List, Tuple, Dict
+
+from numpy import ndarray
+from pandas import DataFrame
+
+from lib.workspace.data_adapter.array import ArrayAdapter
+from lib.workspace.data_adapter.base import BaseAdapter
+from lib.workspace.data_adapter.data_frame import DataFrameAdapter
+from lib.workspace.data_adapter.universal import UniversalAdapter
+
+
+class Detector:
+ """
+ 类型识别器的作用在于,将任意的类型转换为一个合适的 ``DataAdapter`` 。
+
+ 在这里补充一个知识点:在 Python 中,一切变量都是对象。
+
+ >>> class cls: pass
+ >>> assert isinstance(cls, type)
+ >>> assert isinstance(type, object)
+ >>> assert isinstance(type, type)
+ >>> assert issubclass(cls, object)
+ >>> assert issubclass(cls, type) is False
+ >>> assert issubclass(type, object)
+
+ 类是 ``type`` 的实例, ``type`` 是 ``object`` 的实例,同时 ``type`` 也是它本身的实例。
+
+ 类是 ``object`` 的子类,类不是 ``type`` 的子类, ``type`` 是 ``object`` 的子类。
+
+ 这个知识点对于本类的开发是必不可少的,因为本类的主要内容就是基于对象的类进行数据适配器的识别与分配。
+
+ 这个类主要包括了以下内容:
+
+ #. 注册数据类型及其相对应的适配器的映射;
+ #. 根据已注册的类型实现数据的自动包装;
+ #. 定义的数据类型到数据适配器的映射
+ """
+
+ def __init__(self):
+
+ # 这个变量用于进行类型与数据适配器的映射。
+ # 其中的每一项都是一个列表,采用列表的原因,这保证了数据的有序性。
+ # 这里预制了一个类型映射,即将任意类型都映射到 ``UniversalAdapter`` 。
+ # 这里并没有预制其他类型映射,因为这些应该在对象初始化时完成。
+ # 目前考虑在 ``DataManager`` 里对数据适配器进行初始化,因此内置的类型映射可以在初始化时完成。
+ # 内置类型的初始化的定义,是在本类中完成的,这主要是由于内置的数据适配器定义在了本包中,
+ # 出于解耦的考虑将他们定义在了 ``init`` 函数中。
+ self.__data: List[Tuple[type, type]] = [(object, UniversalAdapter)]
+
+ # 这个变量用于进行类型与数据适配器的快速查询。
+ # 由于在程序运行过程中,经常发生变量的改变,因此采用O(1)的字典对映射进行缓存。
+ self.__cached_data: Dict[type, type] = {}
+
+ def register(self, detect_type: type, adapter_class: type, replace=False) -> None:
+ """注册一个类型,以用于进行类型识别。
+
+ 由于PyMiner不可能涵盖所有的数据类型,因此需要插件自行实现其所需要的数据类型。
+
+ Args:
+ detect_type:
+ adapter_class:
+ replace: 如果已注册过该类型,是否覆盖注册。如不覆盖注册,则会报错。
+
+ Raises:
+ ValueError: 如果类型
+
+ Notes:
+ TODO (panhaoyu) 该方法尚不支持指定类型的插入顺序,后续应当进行调整。
+ 可选的方案是,支持传入一个 ``before`` 参数,支持一个列表的类型,使得新的类型在这些类型之前。
+
+ """
+ assert isinstance(detect_type, type)
+ assert issubclass(adapter_class, BaseAdapter)
+
+ # 判断类型是否已存在,如已存在且未指定 ``replace=True`` 则报错。
+ if any(detect == detect_type for detect, adapter in self.__data) and not replace:
+ raise ValueError('Register failed, data type already registered, user ``replace=True`` to overwrite.')
+
+ # 由于指定了新的类型,类型的映射关系可能出现改变,需要重建映射,故清空已有的映射关系。
+ self.__cached_data.clear()
+
+ # 由于暂不支持指定类型的插入位置,先将其插入在第一位。
+ self.__data.insert(0, (detect_type, adapter_class))
+
+ def detect(self, data: any) -> BaseAdapter:
+ """根据登录的类型自动识别
+
+ Args:
+ data: 任何数据
+
+ Returns:
+ 识别数据得到的数据适配器
+
+ """
+ data_type = type(data)
+ if data_type in self.__cached_data:
+ return self.__cached_data[data_type](data)
+ else:
+ # 如果没有已缓存的数据适配器,则遍历所有数据,查询当前类型对应的数据适配器。
+ available = [adapter_class for detect, adapter_class in self.__data if issubclass(data_type, detect)]
+ # 由于 ``UniversalAdapter`` 的存在,这里一定是可以得到至少一个数据适配器的。
+ self.__cached_data[data_type] = available[0]
+ return available[0](data)
+
+ def init_builtin_adapters(self):
+ """建立内置数据类型的映射。
+
+ Notes:
+ TODO (panhaoyu) 实现多种数据类型的适配,至少要实现内置的 ``list`` 等数据类型以及 ``Pandas`` 的一系列数据类型的适配。
+ """
+ self.register(ndarray, ArrayAdapter)
+ self.register(DataFrame, DataFrameAdapter)
+
+
+if __name__ == '__main__':
+ import doctest
+
+ doctest.testmod()
diff --git a/pyminer/lib/workspace/data_adapter/index.rst b/pyminer/lib/workspace/data_adapter/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..a3c98bad3c85be85aa0f5d383efb0502793034af
--- /dev/null
+++ b/pyminer/lib/workspace/data_adapter/index.rst
@@ -0,0 +1,20 @@
+=============
+数据适配功能
+=============
+
+.. toctree::
+
+ array.rst
+ base_structure.rst
+ universal.rst
+
+这个包用于容纳所有PyMiner的数据结构。
+
+数据结构主要用于实现适配功能,将不同包里面的优秀数据结构进行整合,包装为PyMiner对象,以达到为PyMiner所用的目的。
+在用户端,并不会看到这些数据结构,而是仍会拿到各个包里面的原始数据结构。
+
+目前需要讨论的点是,数据结构是采用大而全的方式,还是采用小而精的方式?
+具体的数据结构还要部分依赖于Reco的C共享内存实现,目前只是一个快速开发的版本。
+
+之前考虑过直接继承numpy.ndarray等数据结构,不过在实际的操作中发现较多问题,目前已放弃,转为采用Adapter的思路。
+
diff --git a/pyminer/lib/workspace/data_adapter/universal.py b/pyminer/lib/workspace/data_adapter/universal.py
new file mode 100644
index 0000000000000000000000000000000000000000..6e637e0adceb2a808335698da1eeb9986fce38f3
--- /dev/null
+++ b/pyminer/lib/workspace/data_adapter/universal.py
@@ -0,0 +1,47 @@
+import pickle
+# from functools import cached_property
+from typing import Any, Dict, Tuple
+
+from sliceable_generator import SliceableGenerator
+
+from .base import BaseAdapter
+
+
+class UniversalAdapter(BaseAdapter):
+ """
+ 该适配器可以传递一切对象。
+
+ TODO (panhaoyu) 不能传递动态类型
+ """
+ data: Any
+
+ # @cached_property
+ def shape(self) -> Tuple[int, ...]:
+ """通用类型是没有维度的,这可以适用于一切类型,而对于存在维度的数据类型,应该独立地定义一个适配器。"""
+ return ()
+
+ # @cached_property
+ def serialized_data(self):
+ return pickle.dumps(self.data)
+
+ @classmethod
+ def load(cls, data: Dict[str, Any]) -> 'BaseAdapter':
+ return cls(pickle.loads(data['data']))
+
+ def get_array(self):
+ if self.dimensions > 0:
+ return SliceableGenerator(self.data, depth=self.dimensions)
+ else:
+ return self.data
+
+ def get_array_atleast_2d(self):
+ if self.dimensions > 1:
+ return self.get_array()
+ elif self.dimensions == 1:
+ return SliceableGenerator((self.get_array() for _ in range(1)), depth=2)
+ else:
+ return SliceableGenerator(((self.get_array() for _ in range(1)) for __ in range(1)))
+
+ def get_header_name(self, dimension=0):
+ assert dimension in (0, 1), 'Dimension only supports `0` or `1`'
+ return SliceableGenerator('0' for _ in range(1))
diff --git a/pyminer/lib/workspace/data_manager.py b/pyminer/lib/workspace/data_manager.py
new file mode 100644
index 0000000000000000000000000000000000000000..8d559d02f1980288348ca7f8afdf83ee7e86cc11
--- /dev/null
+++ b/pyminer/lib/workspace/data_manager.py
@@ -0,0 +1,226 @@
+"""
+该模块用于承载数据管理的功能,即工作空间。
+
+``DataManager`` 类,其实例就是工作空间,提供了包括添加变量、删除变量、历史记录回溯、历史记录访问量等内容。
+
+该模块在设计时考虑了历史记录功能,不过该功能是否有必要,还有待商榷。
+
+该模块的操作对象为 ``DataAdapter``。
+将数据外面包上一层可以确保其元数据的识别等便利性。
+关于 ``DataAdapter`` 的详细内容请参见相关文档。
+
+"""
+from collections import OrderedDict
+from typing import Dict, Tuple, List, Any
+
+from lib.workspace.data_adapter import BaseAdapter
+from lib.workspace.data_adapter import Detector
+from .signals import workspace_data_created, workspace_data_deleted, workspace_data_changed
+
+
+class DataManager(object):
+ """
+ 数据管理类。
+
+ 应当注意的时,数据管理的对象,是 ``DataAdapter`` 而不是原生的数据。
+
+ 如果需要写入一个原生数据,可以采用 ``set_raw_data`` 方法。
+
+ 值得一提的是,最初的设想,通过 ``__setitem__`` 写入原生数据,然后通过 ``__getitem__`` 读出数据适配器。
+ 但是这样相当于与字典的功能发生了较大的差异,一个 python 用户的习惯应该是写入什么就取出什么,这不符合 python 用户的习惯。
+ 因此,采用了一个独立的方法 ``set_raw_data`` 用于写入原生数据并自动进行识别。
+ """
+
+ def __init__(self):
+ # TODO 将历史记录的上限和回收站的上限作为对象初始化的参数进行传值
+
+ # Container数据结构中管理器的主要内容。
+ # 其键为变量名,值为两个列表构成的元组,共同用来表示历史记录。
+ # 变量的历史记录和大多数程序一致,都是采用单线式的历史记录管理策略,如下所示:
+ # 当前记录:[1,2,3,4,5,6,7],[]
+ # 撤销一次:[1,2,3,4,5,6],[7]
+ # 撤销一次:[1,2,3,4,5],[6,7]
+ # 重做一次:[1,2,3,4,5,6],[7]
+ # 写入一次:[1,2,3,4,5,6,8],[] # 写入时删除重做列表
+ self.container: Dict[str, Tuple[List[BaseAdapter], List[BaseAdapter]]] = dict()
+
+ # RecycleBin用于存储用户明确删除的变量,其基本工作流程的伪代码如下所示:
+ # 当前空间:container={a,b,c}, recycle_bin={}
+ # 删除a: container={b,c}, recycle_bin={a}
+ # 删除b: container={c}, recycle_bin={a,b}
+ # 恢复a: container={a,c}, recycle_bin={b}
+ self.recycle_bin = OrderedDict()
+
+ # 数据适配器自动识别类
+ self.detector = Detector()
+ self.detector.init_builtin_adapters()
+
+ def __getitem__(self, key: str) -> BaseAdapter:
+ """
+ 从工作空间读取变量。
+
+ Args:
+ key: 变量名
+
+ Returns:
+ BaseAdapter: 变量值的Adapter,不是原始值
+
+ """
+ current, future = self.container[key]
+ current or self.__raise_key_error(key)
+ return current[-1]
+
+ def __setitem__(self, key: str, value: BaseAdapter):
+ """
+ 将变量写入工作空间
+
+ Args:
+ key: 变量名
+ value: 变量值,应该是 ``BaseAdapter`` 的子类。
+ """
+ created = False # 用于记录本次操作是新建了一个变量还是修改了一个变量
+ assert isinstance(value, BaseAdapter)
+
+ # 首先确保工作空间中有该变量的历史记录容器
+ if key not in self.container: # 如果工作空间中没有该变量
+ created = True
+ if key not in self.recycle_bin: # 回收站中也没有,新建该变量的历史记录
+ self.container[key] = ([], [])
+ else: # 从回站中恢复
+ self.container[key] = self.recycle_bin[key]
+ del self.recycle_bin[key]
+
+ # 处理历史记录相关内容
+ current, future = self.container[key]
+ if future:
+ future.clear()
+ if not current:
+ created = True
+ current.append(value)
+ if len(current) > 15: # 对每个变量的最多保存的历史记录数量
+ current.pop(0)
+ if created:
+ workspace_data_created.send(self, key=key)
+ else:
+ workspace_data_changed.send(self, key=key)
+
+ def __delitem__(self, key: str):
+ """
+ 在工作空间中删去一个变量。
+
+ Args:
+ key: 需要删去的变量名。
+ """
+ # TODO (panhaoyu) 这里实际应当进行当前工作空间的变量和回收站中的变量的合并,此处时间原因先采用直接替换的方式
+ key in self.container or self.__raise_key_error(key)
+ self.recycle_bin[key] = self.container[key]
+ del self.container[key]
+ workspace_data_deleted.send(self, key=key)
+
+ def __contains__(self, item: str):
+ """检查工作空间中是否已存在某个变量"""
+ return item in self.container
+
+ def __iter__(self):
+ yield from self.container
+
+ def set_raw_data(self, key: str, value: Any):
+ """将一个原生变量写入数据管理器。
+
+ Args:
+ key: 变量名。
+ value: 原生变量。
+ """
+ self[key] = self.detector.detect(value)
+
+ def back(self, key: str) -> bool:
+ """
+ 将变量撤回到前一个历史记录点。
+
+ Args:
+ key: 变量名
+
+ Returns:
+ bool: 变量是否撤销成功
+
+ """
+ key in self.container or self.__raise_key_error(key)
+ current, future = self.container[key]
+ if len(current) < 2:
+ return False
+ future.insert(0, current.pop())
+ return True
+
+ def forward(self, key: str) -> bool:
+ """
+ 重做变量,即使得变量前进一个历史记录点。
+
+ Args:
+ key: 变量名
+
+ Returns:
+ 变量是否重做成功
+ """
+ key in self.container or self.__raise_key_error(key)
+ current, future = self.container[key]
+ if not future:
+ return False
+ current.append(future.pop(0))
+ if len(current) > 15:
+ current.pop(0)
+ return True
+
+ def restore_from_recycle_bin(self, key: str):
+ """
+ 从回收站中恢复一个变量。
+
+ 这将覆盖工作空间中的同名变量!需要弹窗警告!
+ 这个方法的名字很长,就是为了防止与“从历史记录中前移一位”功能相混淆。
+
+ Args:
+ key: 变量名
+ """
+ key in self.recycle_bin or self.__raise_key_error(key, '回收站')
+ self.container[key] = self.recycle_bin[key]
+ workspace_data_created.send(key=key)
+ del self.recycle_bin[key]
+
+ def __raise_key_error(self, key: str, position='工作空间'):
+ raise KeyError(f'{position}未定义变量:{key}')
+
+ def keys(self) -> List[str]:
+ """
+ 将工作空间内的名字作为一个列表返回。
+
+ 每次都返回一个新列表。
+
+ Returns:
+ 变量名的列表。
+ """
+ return list(self.container.keys())
+
+ def values(self) -> List[BaseAdapter]:
+ """
+ 将工作空间内的值作为一个列表返回。
+
+ 每次都返回一个新列表。
+ Returns:
+ 变量值的列表。
+ """
+ return [current[-1] for current, future in self.container.values()]
+
+ def items(self) -> List[Tuple[str, BaseAdapter]]:
+ """
+ 将工作空间的键值对作为一个列表返回。
+
+ 每次都返回一个新列表。
+
+ Returns:
+ 工作空间的数据的键值对
+ """
+ return [(key, history[0][-1]) for key, history in self.container.items()]
+
+
+# 请不要直接使用此变量!
+# 目前已知的用法仅有两处,一个是在 workspace_old ,一个是在 extension_lib 。
+data_manager = DataManager()
diff --git a/pyminer/lib/workspace/index.rst b/pyminer/lib/workspace/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..8ce081cdb92aa6223c15cd2365cf18fa4086d78e
--- /dev/null
+++ b/pyminer/lib/workspace/index.rst
@@ -0,0 +1,31 @@
+==============================================================================
+工作空间v2
+==============================================================================
+
+这一部分本意是取代已有的workspace,打造一个全新的workspace。
+
+通过进行架构的调整使得其可以获得较好的性能,并可以嵌入 ``DataAdapter`` 内容。
+
+目前共享内存版本的工作空间已进入最后阶段,待共享内存版本稳定后,本工作空间将进行兼容。
+
+目前已成功替代现有的 ``workspace`` 。
+
+新版的工作空间同样是支持历史记录功能的,但是对于数据的拷贝并没有进行很好的实现,
+即,如果对数据进行原位修改,工作空间是无法识别的。
+由于现在是否需要历史记录功能还未存在定论,故暂不对此功能进行优化。
+如果确实需要进行优化,则可以在 ``adapter`` 层面添加 ``copy`` 方法,
+在 ``DataManager.__getitem__`` 里返回 ``adapter.copy()`` 。
+暂时没有实现的计划,因此工作空间的历史记录功能是不可用的。
+
+新版的工作空间仅经过了简单的单元测试,后面需要进行完善。
+
+.. automodule:: features.workspace2
+
+
+.. toctree::
+ :maxdepth: 2
+
+
+ data_manager.rst
+ signals.rst
+
diff --git a/pyminer/lib/workspace/signals.py b/pyminer/lib/workspace/signals.py
new file mode 100644
index 0000000000000000000000000000000000000000..632430017263ae1872da17a301fb63663951e533
--- /dev/null
+++ b/pyminer/lib/workspace/signals.py
@@ -0,0 +1,9 @@
+import os
+import sys
+sys.path.append(os.path.dirname(__file__))
+
+from .blinker import signal
+
+workspace_data_changed = signal('workspace-data-changed')
+workspace_data_created = signal('workspace-data-created')
+workspace_data_deleted = signal('workspace-data-deleted')
diff --git a/pyminer/lib/workspace/signals.rst b/pyminer/lib/workspace/signals.rst
new file mode 100644
index 0000000000000000000000000000000000000000..e77e29706cc10011c9abc053639bce3c50a791df
--- /dev/null
+++ b/pyminer/lib/workspace/signals.rst
@@ -0,0 +1,66 @@
+==================
+信号
+==================
+
+工作空间中发生的事件将通过信号的方式传递给插件。
+
+.. note::
+
+ 在旧版工作空间中,变量的增删改查是通过回调函数的方式添加的,在新版的工作空间中,也同样是采用的回调函数。
+ 区别在于,旧版工作空间中是将回调的管理功能写入在了工作空间类中,而新版的工作空间是采用了 blinker_ 进行实现的。
+ 有关于 blinker_ 的用法,请参考相应的文档,这里不进行过多介绍。
+
+.. _blinker: https://pythonhosted.org/blinker/
+
+回调函数的使用方法可以直接参考测试用例: ``tests.test_workspace2.test_data_manager.TestSignals`` 中的用法。
+由于正在开发中,可能会出现频繁的更新,因此以测试用例中的内容为准。
+
+目前支持以下信号:
+
+.. py:data:: workspace_data_created
+
+ 工作空间中添加了新的数据。
+
+ .. code-block:: python
+
+ @workspace_data_created.connect
+ def created(sender: DataManager, key: str):
+ pass
+
+.. py:data:: workspace_data_changed
+
+ 工作空间中已有的数据发生了改变。
+
+ .. code-block:: python
+
+ @workspace_data_changed.connect
+ def changed(sender: DataManager, key: str):
+ pass
+
+ .. note::
+
+ 这个信号的参数仅仅传入了一个 ``key`` ,因为如果需要获取其历史记录,这应该是 ``DataManager`` 提供方法进行调用,
+ 而不是通过信号的参数进行传输。
+
+.. py:data:: workspace_data_deleted
+
+ 工作空间中的数据被删除了。
+
+ .. code-block:: python
+
+ @workspace_data_deleted.connect
+ def deleted(sender: DataManager, key: str):
+ pass
+
+ .. note::
+
+ 这个数据并不会真的被删除,而是被移入了回收站,在工作空间中看不见了。
+
+.. note::
+
+ 相比于之前的旧版 ``workspace`` ,通过传递 ``provider`` 以判断是否是本插件传递过去的数据,
+ 新版的 ``workspace`` 使用完全基于信号与事件的传递方式,不需要再判断 ``provider`` 。
+
+这里支持的信号的数量比较少,因为并不清楚是否需要支持其他的信号。
+比如是否可以在数据创建前,激发一个信号,根据信号的返回值判断是否阻止创建这个数据。
+目前并没有发现相关需求,如果确实需要,可以添加。
diff --git a/pyminer/lib/workspace_old/__init__.py b/pyminer/lib/workspace_old/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/lib/workspace_old/datamanager/__init__.py b/pyminer/lib/workspace_old/datamanager/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/lib/workspace_old/datamanager/converter.py b/pyminer/lib/workspace_old/datamanager/converter.py
new file mode 100644
index 0000000000000000000000000000000000000000..8bba5c0b030dd046bb7ad74bd7121f5a9d600d95
--- /dev/null
+++ b/pyminer/lib/workspace_old/datamanager/converter.py
@@ -0,0 +1,73 @@
+from typing import TYPE_CHECKING
+if TYPE_CHECKING:
+ import numpy as np
+ import pandas as pd
+
+from pyminer2.workspace_old.datamanager.variable import Variable
+from .exceptions import ConvertError
+
+
+class Converter:
+ def __init__(self, data_manager):
+ self.data_manager = data_manager
+
+ def convert_to_data(self, var) -> dict:
+ typename = type(var).__name__
+ convert_func = f'convert_{typename.lower()}'
+ if hasattr(self, convert_func):
+ return getattr(self, convert_func)(var)
+ elif isinstance(var, Variable):
+ return var.dump()
+ else:
+ raise ConvertError(f'{var} is inconvertible')
+
+ def convert_to_var(self, data: dict):
+ assert self.data_manager.dataset.is_valid(data)
+ var = Variable(data['type'], data)
+ try:
+ # in case any error which means unsupported type
+ iconvert_func = f'iconvert_{data["type"].lower()}'
+ return getattr(self, iconvert_func)(var)
+ except BaseException:
+ # no valid converter
+ return var
+
+ # convert to data, func format: convert_obj
+
+ def convert_ndarray(self, arr: 'np.ndarray') -> dict:
+ # TODO (panhaoyu) 三维数组甚至四维数组都是很常见的数据格式,应该支持
+ import numpy as np
+ if arr.dtype in (np.int, np.float):
+ if len(arr.shape) == 2:
+ return Variable('Matrix', {'value': arr.tolist()}).dump()
+ elif len(arr.shape) == 1:
+ return Variable('Vector', {'value': arr.tolist()}).dump()
+ else:
+ raise ConvertError
+ else:
+ raise ConvertError(f'{arr} is inconvertible')
+
+ def convert_list(self, lst: list) -> dict:
+ import numpy as np
+ return self.convert_ndarray(np.array(lst))
+
+ def convert_dataframe(self, dataframe: 'pd.DataFrame') -> dict:
+ return Variable('DataFrame', {'table': dataframe.values.tolist(), 'columns': dataframe.columns.tolist()})
+
+ # convert to var, func format: iconvert_type
+ # TODO (panhaoyu) 这三个函数目前没有在pycharm中发现调用,是否可以删除?
+
+ def iconvert_matrix(self, mat: Variable) ->'np.ndarray':
+ import numpy as np
+ assert mat.type == 'Matrix'
+ return np.array(mat['value'])
+
+ def iconvert_vector(self, vec: Variable) -> 'np.ndarray':
+ import numpy as np
+ assert vec.type == 'Vector'
+ return np.array(vec['value'])
+
+ def iconvert_dataframe(self, dataframe: Variable) -> 'pd.DataFrame':
+ import pandas as pd
+ assert dataframe.type == 'DataFrame'
+ return pd.DataFrame(dataframe['table'], columns=dataframe['columns'])
diff --git a/pyminer/lib/workspace_old/datamanager/datamanager.py b/pyminer/lib/workspace_old/datamanager/datamanager.py
new file mode 100644
index 0000000000000000000000000000000000000000..3d28549af849cf2b11554f6c454b566f911ab64d
--- /dev/null
+++ b/pyminer/lib/workspace_old/datamanager/datamanager.py
@@ -0,0 +1,77 @@
+from contextlib import contextmanager
+from typing import Dict, Union, Any
+
+from pyminer_comm.base import DataDesc
+from lib.workspace.data_manager import data_manager as next_data_manager
+from lib.workspace.signals import workspace_data_created, workspace_data_changed, workspace_data_deleted
+from lib.workspace_old.datamanager.exceptions import NotFoundError
+
+
+class DataManager:
+ def __init__(self):
+ self.next_data_manager = next_data_manager
+ self.provider = 'unknown'
+ self.__weakref_protector = [] # 用于避免弱引用的回调函数被销毁
+
+ @contextmanager
+ def set_provider(self, provider='unknown'):
+ self.provider = provider
+ yield
+ self.provider = 'unknown'
+
+ def get_all_var(self) -> Dict[str, Any]:
+ return {k: v.data for k, v in self.next_data_manager.items()}
+
+ def get_all_public_var(self) -> Dict[str, Union[object, int, float]]:
+ return self.get_all_var()
+
+ def get_vars_of_types(self, types):
+ return {k: v.data for k, v in self.next_data_manager.items() if isinstance(v, types)}
+
+ def get_var(self, key: str):
+ if key not in self.next_data_manager.keys():
+ raise NotFoundError(f'{key} not found')
+ return self.next_data_manager[key].data
+
+ def get_data_info(self, key: str) -> dict:
+ if key not in self.next_data_manager.keys():
+ raise NotFoundError(f'{key} not found')
+ return self.next_data_manager[key].abstract
+
+ def set_var_dict(self, variables: dict, provider='unknown', info_dict=None):
+ with self.set_provider(provider):
+ for k, v in variables.items():
+ self.next_data_manager.set_raw_data(k, v)
+
+ def set_var(self, key: str, value, provider='unknown', **info):
+ assert isinstance(value, DataDesc), 'Variable name:%s value:%s is not instance of DataDesc!' % (key, value)
+ with self.set_provider(provider):
+ self.next_data_manager.set_raw_data(key, value)
+
+ def delete_data(self, key: str, provider='unknown'):
+ with self.set_provider(provider):
+ del self.next_data_manager[key]
+
+ def clear(self):
+ for key in self.next_data_manager.keys():
+ del self.next_data_manager[key]
+
+ def on_modification(self, modification_callback):
+ def changed(sender, key: str):
+ value = self.next_data_manager[key]
+ modification_callback(key, value.data, self.provider)
+
+ self.__weakref_protector.append(changed)
+
+ workspace_data_created.connect(changed)
+ workspace_data_changed.connect(changed)
+
+ def on_deletion(self, deletion_callback):
+ def deleted(sender, key: str):
+ deletion_callback(key, self.provider)
+
+ self.__weakref_protector.append(deleted)
+ workspace_data_deleted.connect(deleted)
+
+
+data_manager = DataManager()
diff --git a/pyminer/lib/workspace_old/datamanager/dataset.py b/pyminer/lib/workspace_old/datamanager/dataset.py
new file mode 100644
index 0000000000000000000000000000000000000000..e11bc705cc019f756a0d713adb4ec8ea17486444
--- /dev/null
+++ b/pyminer/lib/workspace_old/datamanager/dataset.py
@@ -0,0 +1,130 @@
+from pyminer2.workspace_old.datamanager.exceptions import ConflictError
+
+
+class DataSet(dict):
+ """
+ 这个类主要用于对变量进行管理,包括以下内容:
+ * 添加内置类型,定义新的类型
+ * 对变量根据类型进行校核,支持递归校核
+ """
+
+ def __init__(self):
+ super().__init__()
+ self.__insert_builtin_type__('Type', {'type': 'Type', 'structure': {
+ 'structure': 'dict',
+ }})
+ # TODO (panhaoyu) 把这种基本数据类型进行如此的定义,性能不会受影响吗?
+ # TODO (panhaoyu) 内置数据类型是否过多?
+ self.__insert_builtin_type__('Complex', {'type': 'Type', 'structure': {
+ 'real': 'float',
+ 'imag': 'float',
+ }})
+ self.__insert_builtin_type__('Matrix', {'type': 'Type', 'structure': {
+ 'value': [['float|int|Complex']],
+ }})
+ self.__insert_builtin_type__('Vector', {'type': 'Type', 'structure': {
+ 'value': ['float|int|Complex'],
+ }})
+ self.__insert_builtin_type__('TimeSeries', {'type': 'Type', 'structure': {
+ 'time': ['float|int'],
+ 'data': ['float|int'],
+ }})
+ self.__insert_builtin_type__('StateSpace', {'type': 'Type', 'structure': {
+ 'A': 'Matrix',
+ 'B': 'Matrix',
+ 'C': 'Matrix',
+ 'D': 'Matrix',
+ 'x': ['str'],
+ 'y': ['str'],
+ 'u': ['str'],
+ 'sys': 'str',
+ }})
+ self.__insert_builtin_type__('DataFrame', {'type': 'Type', 'structure': {
+ 'table': [['float|int|Complex|str']],
+ 'columns': ['str'],
+ }})
+
+ # TODO (panhaoyu) series与矩阵有什么区别呢?
+ self.__insert_builtin_type__('Series', {'type': 'Type', 'structure': {
+ 'value': [['float|int|Complex|str']],
+ }})
+ self.builtin_types = self.select_type('Type')
+
+ def __insert_builtin_type__(self, key: str, obj: dict):
+ self[key] = obj
+
+ def write(self, key: str, obj: dict):
+ assert isinstance(key, str)
+ assert key.isidentifier()
+ if key in self.builtin_types:
+ raise ConflictError('conflict variable name')
+ assert self.is_valid(obj)
+ self[key] = obj
+
+ def read(self, key: str) -> dict:
+ return self[key]
+
+ def synchronise(self, key: str, obj: dict):
+ self[key] = obj
+
+ def is_valid(self, obj: dict) -> bool:
+ # noinspection PyBroadException
+ try:
+ obj_type = obj['type']
+ type_def = self[obj_type]
+ structure = type_def['structure']
+ self.compare(obj, structure)
+ except Exception:
+ return False
+ else:
+ return True
+
+ def compare(self, obj, structure) -> None:
+ """
+ 用于判断某个对象是否符合给定的结构。
+ 目标结构可以是以下内容:
+ 字典:递归检则每一个键是否符合要求
+ 列表:检测列表对象中的每一项是否都是给定的结构
+ 字符串:
+ 字符串内包含“|”分割符:表示可能是以下类型之一
+ 字符串是int,float,str,list,dict中的一种:检测对象是否是相应的Python类型
+ 其他字符串:从内置类型列表中查询该类型并进行检测
+ :param obj: 待检测的对象
+ :param structure: 目标结构
+ :return: 无返回值,如果比较失败则报错
+ """
+ if isinstance(structure, dict):
+ for key in structure:
+ req_val = structure[key]
+ obj_val = obj[key]
+ self.compare(obj_val, req_val)
+ elif isinstance(structure, list):
+ req_type = structure[0]
+ for item in obj:
+ self.compare(item, req_type)
+ else:
+ assert isinstance(structure, str)
+ if '|' in structure:
+ valid = False
+ for sub_structure in structure.split('|'):
+ try:
+ self.compare(obj, sub_structure)
+ valid = True
+ except AssertionError:
+ pass
+ assert valid
+ elif structure in ('list', 'dict', 'float', 'int', 'str'):
+ assert type(obj).__name__ == structure
+ else:
+ assert isinstance(obj, dict) and obj.get('type', '') == structure
+ type_def = self[structure]
+ structure = type_def['structure']
+ self.compare(obj, structure)
+
+ def select_type(self, type_name: str):
+ # TODO (panhaoyu) 这个函数的意义何在?请补充注释
+ dct = {}
+ for key, value in self.items():
+ if value['type'] == type_name:
+ dct[key] = value
+ return dct
diff --git a/pyminer/lib/workspace_old/datamanager/exceptions.py b/pyminer/lib/workspace_old/datamanager/exceptions.py
new file mode 100644
index 0000000000000000000000000000000000000000..8b6f7a4dabb4f05421bd3f1f7e98d6bd67983c8c
--- /dev/null
+++ b/pyminer/lib/workspace_old/datamanager/exceptions.py
@@ -0,0 +1,18 @@
+class ConflictError(Exception):
+ pass
+
+
+class NotFoundError(Exception):
+ pass
+
+
+class ConvertError(Exception):
+ """用于在数据类型无法转换时进行报错"""
+ # TODO (panhaoyu) 建议改成ConvertError
+ # 由于改动涉及其他模块,需要在合并后的一个绝对安全的情况下进行修改
+ pass
+
+
+class WouldBlockError(Exception):
+ # TODO (panhaoyu) 建议改成DataBlockedError
+ pass
diff --git a/pyminer/lib/workspace_old/datamanager/historyset.py b/pyminer/lib/workspace_old/datamanager/historyset.py
new file mode 100644
index 0000000000000000000000000000000000000000..ba64a591c688b1b2241e04b63a8babde3a39143d
--- /dev/null
+++ b/pyminer/lib/workspace_old/datamanager/historyset.py
@@ -0,0 +1,59 @@
+from pyminer2.workspace_old.datamanager.exceptions import NotFoundError
+
+
+class HistoryError(Exception):
+ pass
+
+
+class DataHistory(list):
+ def __init__(self, max_stack_num):
+ super().__init__()
+ self.max_stack_num = max_stack_num
+ self.index = 0
+
+ def push(self, var):
+ for i in range(self.index):
+ self.pop(0)
+ self.index = 0
+ self.insert(0, var)
+ if len(self) > self.max_stack_num:
+ self.pop(-1)
+
+ def stepback(self, var):
+ self.push(var)
+ if self.index >= self.max_stack_num:
+ raise HistoryError('stack bottom of history is reached')
+ self.index += 1
+ return self[self.index]
+
+ def stepforward(self):
+ if self.index <= 0:
+ raise HistoryError('stack top of history is reached')
+ self.index -= 1
+ return self[self.index]
+
+
+class HistorySet(dict):
+ def __init__(self, max_stack_num=15):
+ super().__init__()
+ self.max_stack_num = max_stack_num
+
+ def push(self, key: str, var):
+ if key in self:
+ history = self[key]
+ else:
+ history = DataHistory(self.max_stack_num)
+ self[key] = history
+ history.push(var)
+
+ def stepback(self, key: str, var):
+ if key not in self:
+ raise NotFoundError(f'{key} not found in history')
+ history = self[key]
+ return history.stepback(var)
+
+ def stepforward(self, key: str):
+ if key not in self:
+ raise NotFoundError(f'{key} not found in history')
+ history = self[key]
+ return history.stepforward()
diff --git a/pyminer/lib/workspace_old/datamanager/metadataset.py b/pyminer/lib/workspace_old/datamanager/metadataset.py
new file mode 100644
index 0000000000000000000000000000000000000000..62dc569e1af0536118b95966a1126974acda3ba2
--- /dev/null
+++ b/pyminer/lib/workspace_old/datamanager/metadataset.py
@@ -0,0 +1,81 @@
+import contextlib
+import threading
+import time
+
+from pyminer2.workspace_old.datamanager.exceptions import ConflictError, NotFoundError
+from .exceptions import WouldBlockError
+
+
+class MetaData(dict):
+ # TODO (panhaoyu) 这里建议采用object加属性的方式进行操作,现在这种方式不支持代码提示
+ def __init__(self, provider, **info):
+ super().__init__()
+ self['provider'] = provider
+ self['modified_by'] = [provider, ]
+ self.update(info)
+ self['synchronised'] = False
+ self['deleted'] = False
+ self['lock'] = threading.RLock()
+
+
+class MetaDataSet(dict):
+ # TODO (panhaoyu) 既然所有的方法名都带有“data“,那么这个“data“可能就是冗余的。
+
+ # 这两个函数仅仅用于添加代码提示
+ def __getitem__(self, item: str) -> MetaData:
+ return super(MetaDataSet, self).__getitem__(item)
+
+ def __setitem__(self, key: str, value: MetaData):
+ super(MetaDataSet, self).__setitem__(key, value)
+
+ def define_data(self, key: str, info: MetaData):
+ if key in self and not self[key]['deleted']:
+ raise ConflictError(f'meta data {key} already exist')
+
+ # TODO (panhaoyu) 数据类型的定义是否应统一放在MataData里面
+ info['creation_time'] = time.time()
+ info['modification_time'] = [info['creation_time'], ]
+ self[key] = info
+
+ def modify_data(self, key: str, modified_by: str):
+ if key not in self:
+ raise NotFoundError(f'no such data {key}')
+ info = self[key]
+ info['modification_time'].append(time.time())
+ info['modified_by'].append(modified_by)
+ info['synchronised'] = False
+ info['deleted'] = False
+
+ def delete_data(self, key: str):
+ if key not in self or self[key]['deleted']:
+ raise NotFoundError(f'no such data {key}')
+ self[key]['deleted'] = True
+
+ def restore_data(self, key: str):
+ if key not in self:
+ raise NotFoundError(f'no such data {key}')
+ self[key]['deleted'] = False
+
+ def synchronise_data(self, key: str):
+ if key not in self or self[key]['deleted']:
+ raise NotFoundError(f'no such data {key}')
+ self[key]['synchronised'] = True
+
+ def update(self, key: str, **info):
+ if key not in self or self[key]['deleted']:
+ raise NotFoundError(f'no such data {key}')
+ self[key].update(info)
+
+ @contextlib.contextmanager
+ def lock_data(self, key: str):
+ if key not in self or self[key]['deleted']:
+ # TODO (panhaoyu) 对于不存在的变量,需要明确报错,而不是继续运行
+ yield
+ else:
+ lock = self[key]['lock']
+ if not lock.acquire(blocking=False):
+ raise WouldBlockError(key)
+ try:
+ yield lock
+ finally:
+ lock.release()
diff --git a/pyminer/lib/workspace_old/datamanager/recyclebin.py b/pyminer/lib/workspace_old/datamanager/recyclebin.py
new file mode 100644
index 0000000000000000000000000000000000000000..24d798b12ac1608f5227191c23c007a5dfe2fb5b
--- /dev/null
+++ b/pyminer/lib/workspace_old/datamanager/recyclebin.py
@@ -0,0 +1,35 @@
+from pyminer2.workspace_old.datamanager.exceptions import NotFoundError
+
+
+class RecycleBin(list):
+ """
+ 回收站,数据类型为(key, value)。
+ 使用discard方法将对象移入回收站,再使用restore方法将对象移出回收站。
+ """
+
+ def __init__(self, max_size=1000):
+ super().__init__()
+ self.max_size = max_size
+
+ def get_varname(self, index: int):
+ if index >= len(self):
+ raise NotFoundError(f'{index} out of limit')
+ return self[index][0]
+
+ def discard(self, varname: str, variable):
+ self.append((varname, variable))
+ if len(self) > self.max_size:
+ self.pop(0)
+
+ def restore(self, index: int, var_to_discard=None) -> tuple:
+ # for the case where variables with same name
+ # exist in workspace and recycle bin, if you
+ # restore the variable in recycle bin, you have
+ # to discard the variable in the workspace
+ if index >= len(self):
+ raise NotFoundError(f'{index} out of limit')
+ varname, var_to_restore = self[index]
+ self.pop(index)
+ if var_to_discard is not None:
+ self.discard(varname, var_to_discard)
+ return varname, var_to_restore
diff --git a/pyminer/lib/workspace_old/datamanager/variable.py b/pyminer/lib/workspace_old/datamanager/variable.py
new file mode 100644
index 0000000000000000000000000000000000000000..a159aa447c996d3f005b6d5f6972fd7827dfeed9
--- /dev/null
+++ b/pyminer/lib/workspace_old/datamanager/variable.py
@@ -0,0 +1,29 @@
+import copy
+import json
+
+
+class VariableError(Exception):
+ pass
+
+
+class Variable(dict):
+ def __init__(self, vartype: str, members: dict):
+ members['type'] = vartype
+ self.type = vartype
+ self.update(members)
+ super(Variable, self).__init__()
+
+ def load(self, dct: dict):
+ if 'type' not in dct:
+ raise VariableError('invalid json object')
+ return Variable(dct['type'], dct)
+
+ def loads(self, jsonstr: str):
+ dct = json.loads(jsonstr)
+ return self.load(dct)
+
+ def dump(self):
+ return copy.copy(self)
+
+ def dumps(self):
+ return json.dumps(self)
diff --git a/pyminer/lib/workspace_old/datamanager/varset.py b/pyminer/lib/workspace_old/datamanager/varset.py
new file mode 100644
index 0000000000000000000000000000000000000000..67362eadb646c3baa101960df10681011b0e9b66
--- /dev/null
+++ b/pyminer/lib/workspace_old/datamanager/varset.py
@@ -0,0 +1,31 @@
+from pyminer2.workspace_old.datamanager.variable import Variable
+from pyminer2.workspace_old.datamanager.exceptions import ConflictError
+
+
+class VarSet(dict):
+ """
+ 这个类是对于字典进行的扩展。
+ 主要功能是添加了get_var和set_var两个函数。
+ """
+
+ def insert_builtin_types(self, builtin_types: dict):
+ # TODO (panhaoyu) 基于pycharm的索引没能找到调用,是否说明该功能已弃用?
+ self.update(builtin_types)
+
+ def __getitem__(self, item: str):
+ # TODO (panhaoyu) 这个类需要类型提示
+ return super(VarSet, self).__getitem__(item)
+
+ def __setitem__(self, key: str, value):
+ assert isinstance(key, str)
+ assert key.isidentifier(), 'Key %s is not identifier!' % key
+ if key in self and isinstance(self[key], Variable) and self[key].type == 'Type':
+ raise ConflictError(f'{key} is a builtin type')
+ else:
+ super(VarSet, self).__setitem__(key, value)
+
+ def get_var(self, key: str):
+ return self[key]
+
+ def set_var(self, value: str, variable):
+ self[value] = variable
diff --git a/pyminer/lib/workspace_old/history.md b/pyminer/lib/workspace_old/history.md
new file mode 100644
index 0000000000000000000000000000000000000000..1f7ca19706078b3097f545b4837335af417ca79a
--- /dev/null
+++ b/pyminer/lib/workspace_old/history.md
@@ -0,0 +1,151 @@
+# Workspace 设计记录
+
+## 2020/08/29
+
+本次更新完成以下两个内容:
+
+- 变量的读写,基于 JSON 的传输。提供了 `read` 和 `write` 函数,便于数据服务器调用。
+- 简单的数据结构合法性检查。
+
+尚未实现的功能有数据的锁定等。
+
+### 数据结构规范
+
+变量指变量名及其对应的数据。变量名是一个字符串,该字符串首位字符必须为 `[_a-zA-Z]`,其余字符必须为 `[_a-zA-Z0-9]`。
+
+数据必须是一个 `dict` 结构,该结构必须包含至少两个字段,其中一个字段为 `'type'`,表示变量的类型。变量的类型也是变量,其类型为 `'type'`。示例:
+
+~~~
+a = {'type':'matrix', 'value':[[1,2,3],[3,2,1]]}
+
+matrix = {'type':'type', 'structure':{'value':[['float']]}})
+~~~
+
+则 `a` 是一个 `matrix`,`matrix` 是一种 `type`。`type` 是一个特殊的类型,定义如下:
+
+~~~
+type = {'type':'type', 'structure':{'structure':'dict'}}
+~~~
+
+### 数据结构的形式化定义
+
+在 `type` 的定义中,`'structure'` 字段自指性地说明了这种类型的实例应该包含哪些字段,且这些字段的类型是什么(注意,此处的类型包括了变量的类型和 json 的五种 object,分别是 dict, list, int, float, str)。从 `type` 的定义可以得知,每一个 `type` 都必须包含 `'structure'` 字段,该字段的类型是 dict。`type` 也自指地完成了定义。
+
+而 `matrix` 是一种 `type`,其定义必须符合它的类型要求,即 `type` 的要求。可以看到,`matrix` 的确包含 `'structure'` 字段,且其类型的确为 dict。进一步,又有 `a` 是一个 `matrix`,按照 `matrix` 的要求,`a` 必须包含 `'value'` 字段,且其类型必须是 list。 该 list 的子元素仍然必须为 list,内部的 list 的子元素则应是 `float`(一般认为,`int` 属于 `float` 而 `float` 不属于 `int`)。经检查,`a` 符合要求,因此 `a` 是合法的。
+
+此例中,`type` 和 `matrix` 就是对数据结构的形式化定义。
+
+### 数据结构合法性检查
+
+数据结构合法性检查是通过 `compare` 函数的递归调用实现的。注意到每个变量都显示声明了自己的类型,取得该类型的定义后,该变量的剩余部分(即除 `'type'` 字段外)和其类型的 `'structure'` 所指向的 dict 是同构的。因此,如果类型规定的是不包含子元素的结构,如 `int`,直接判断二者是否相同;如果是包含子元素的结构,则递归地比较各个子元素。
+
+如果子元素是不确定的,例如在 `type` 的定义中,`'structure'` 字段的 dict 定义是不确定的,则可以用 `'dict'` 来代替。同理,`'list'` 也可以用来指代不确定的 list。
+
+如前所述,字段也可以是已经定义好的类型。此时,先检查字段的类型是否正确,如果正确,再检查字段结构是否合法。
+
+需要注意的是,数据的结构合法性检查通过了,并不代表数据就是合法的。此形式化定义有其局限性,比如,`matrix` 要求 `'value'` 字段的值的各行元素数目相等,这个目前暂时无法判断。
+
+## 2020/08/30
+
+本次更新主要完成 `Variable` 类和 `Converter` 类的设计。目前系统架构为:
+
+~~~
+ [ DataManager ]
+InnerUser <-> [<-> VarSet <-Converter-> DataSet <->] <-> DataServer <-> OuterUser
+ [ History RecycleBin ]
+~~~
+
+### 名词定义和介绍
+
+`VarSet` 实时保存 Python 变量,比如 `np.ndarray`,`int`,`timeseries`等,其中 `timeseris` 是继承于 `Variable` 的类。`DataSet` 则缓存 `dict`,如上次更新所述。
+
+`Variable` 类是可以直接和上述数据结构(`dict`)转化的类。数据结构转化为 `Variable` 类的子类,类型为数据结构的 `type`。
+
+`Converter` 实现在 Python 对象和上述数据结构之间相互转化。其中还特殊定义了一些数据结构转化成特殊的对象(非 `Variable` 子类),比如 `matrix` 应转化成 `numpy.ndarray`。相应地,还提供从 `numpy.ndarray` 到 `matrix` 的转化。
+
+### 数据传递链路
+
+InnerUser 写入数据,直接保存至 VarSet,VarSet 通知 DataSet 该数据发生改变。数据链路结束。
+
+InnerUser 读取数据,直接从 VarSet 获取。
+
+OuterUser 写入数据,保存至 DataSet,DataSet 调用 Converter 将数据写入 VarSet,更新该数据的状态为已知。
+
+OuterUser 读取数据,从 DataSet 读取。DataSet 检测数据状态是否已知,如果已知,直接返回;如果未知,则调用 Converter 从 VarSet 获取值,修改数据状态为已知。
+
+以上读取数据链路都是基于该值存在,若不存在,按正常逻辑返回错误或空值。
+
+## 2020/08/30 #2
+
+本次更新完成 `DataManager` 全部功能,实现了 `VarSet`,`RecycleBin`,`HistorySet` 和 `MetaDataSet` 四个组件。
+
+### 为什么把 MetaDataSet 分离出来?
+
+因为删除或者撤销(回退)数据不应该引起元数据的变化。
+
+### 为什么 RecycleBin 继承于 list 而不是 dict?
+
+因为一个数据可以被删多次(删除后手动创建新的,又删除),避免覆盖
+
+### 如何实现撤销?为什么撤销需要传参?
+
+撤销传参是因为可以对特定参数进行撤销。暂不支持全局按顺序撤销。
+
+撤销时,先把当前数据推入 HistorySet,否则当前数据将被覆盖而无法重做。然后将指针进一,即可得到撤销后的值。
+
+### 为什么在 DataManager 内部,恢复值要传值?
+
+因为可能出现以下情况,准备在 RecycleBin 中恢复 `a`,但是当前 `VarSet` 中已经存在 `a`,这时候就要把当前的值放入 RecycleBin。
+
+### 什么时候会进行数据转换(上述数据结构和 Python 对象)?
+
+Python 对象转换成上述数据结构,仅发生在数据服务器请求一个值,当前值在 DataSet 中不存在或者不是最新版。
+
+上述数据结构转换成 Python 对象,仅发生在数据服务器向 DataSet 写入一个值,DataManager 自动同步到 VarSet。
+
+## 2020/08/31
+
+添加对数据上锁的功能。默认对数据上非阻塞可重入锁,如果需要阻塞锁,可以加上一层。
+
+## 2020/08/31 # 2
+
+添加数据增删改的 callback 函数,以便可视化界面刷新。具体用法见测试用例,用户向 DataManager 注册 callback 函数,在数据发生变化时回调。其中新增和修改被放在同一个回调函数中。
+
+## 2020/08/31 # 3 (修改 2020/09/01)
+
+增加数据服务器功能,并将 DataManager 和 DataServer 合并到主程序中。~~由于 DataServer 采用 FastAPI 框架,需要用到 signal 模块,因此不得不在主线程运行,所以 GUI 程序只能在新的线程运行。目前已经实现这个功能。~~目前,数据服务器采用 json-rpc 框架,可以运行在新的线程上。但此框架对 POST 请求有更复杂的规定,且不支持 GET 请求。故更改了 API。
+
+数据服务器目前支持两个 API:
+
+~~~
+post:
+ path: /
+ content-type: application/json
+ json: method: read
+ params: [dataname]
+ jsonrpc: 2.0
+ id: 0
+ func: datamanager.read_data(dataname)
+
+post:
+ path: /
+ content-type: application/json
+ json: method: read
+ params: [dataname, data, provider]
+ jsonrpc: 2.0
+ id: 0
+ func: datamanager.write_data(dataname, data, provider)
+~~~
+
+如果请求失败,失败的原因可以检测 response 的 `detail`。目前有如下几种:
+
+| 方法 | detail | 描述 |
+|--|--|--|
+| read | WOULD_BLOCK_ERROR | 数据被其他线程占用 |
+| read | NOT_FOUND_ERROR | 未找到请求的数据 |
+| read | INTERNAL_ERROR | 未知错误 |
+| write | WOULD_BLOCK_ERROR | 数据被其他线程占用 |
+| write | INVALID_VALUE_ERROR | 不合法的数据名或数据 |
+| write | CONFLICT_ERROR | 没有权限修改数据(内建类型) |
+| write | INTERNAL_ERROR | 未知错误 |
+
diff --git a/pyminer/lib/workspace_old/index.rst b/pyminer/lib/workspace_old/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..18c6697fcdf261083fb111cb9c7ca1729470577f
--- /dev/null
+++ b/pyminer/lib/workspace_old/index.rst
@@ -0,0 +1,17 @@
+==============================================================================
+工作空间
+==============================================================================
+
+.. toctree::
+ :maxdepth: 2
+
+ datamanager/index.rst
+ history.md
+
+
+
+.. automodule:: pyminer2.workspace
+ :members:
+ :undoc-members:
+
+
diff --git a/pyminer/packages/advanced_drawings_toolbar/main.py b/pyminer/packages/advanced_drawings_toolbar/main.py
index 363594d5153422794c2140161804bc66c8e06b6c..02df410ee98ee9c09248522b1d923b3a62279756 100644
--- a/pyminer/packages/advanced_drawings_toolbar/main.py
+++ b/pyminer/packages/advanced_drawings_toolbar/main.py
@@ -16,8 +16,8 @@ trans_editor.load(file_name)
app.installTranslator(trans_editor)
import numpy as np
import pandas as pd
-from features.extensions.extensionlib import BaseExtension, BaseInterface
-from pmgwidgets import PMGToolBar, create_icon
+from lib.extensions.extensionlib import BaseExtension, BaseInterface
+from widgets import PMGToolBar, create_icon
from packages.drawings_toolbar.group_chart import DialogGroup
from packages.drawings_toolbar.radar_chart import radar_factory
from matplotlib import pyplot as plt
@@ -29,7 +29,7 @@ from pyminer_comm import get_var
logger = logging.getLogger(__name__)
if TYPE_CHECKING:
- from features.extensions.extensionlib import extension_lib
+ from lib.extensions.extensionlib import extension_lib
class PMMenuToolPanel(QFrame):
@@ -270,7 +270,7 @@ class PMDrawingsToolBar(PMGToolBar):
draw_value = np.array(dv.values)
elif isinstance(draw_data, (Series, DataFrame)):
if isinstance(draw_data, DataFrame):
- from pmgwidgets import PMGPanelDialog
+ from widgets import PMGPanelDialog
dlg = PMGPanelDialog(parent=self, views=[[
'combo_ctrl', 'series', '选择系列', '全部', draw_data.select_dtypes(['number']).columns.tolist() + ['全部']]
])
@@ -745,7 +745,7 @@ class PMDrawingsToolBar(PMGToolBar):
def set_panel_visibility(self):
self.refresh_pos()
- w: 'pmgwidgets.TopLevelWidget' = self._control_widget_dic['drawing_selection_panel']
+ w: 'widgets.TopLevelWidget' = self._control_widget_dic['drawing_selection_panel']
w.setVisible(not w.isVisible())
def refresh_pos(self):
@@ -754,7 +754,7 @@ class PMDrawingsToolBar(PMGToolBar):
"""
return
btn = self.get_control_widget('button_show_more_plots')
- panel: 'pmgwidgets.TopLevelWidget' = self.get_control_widget(
+ panel: 'widgets.TopLevelWidget' = self.get_control_widget(
'drawing_selection_panel')
width = self.get_control_widget('button_list').width()
panel.set_width(width)
diff --git a/pyminer/packages/applications_toolbar/applications_toolbar.py b/pyminer/packages/applications_toolbar/applications_toolbar.py
index 2ef583f5a1f5d343b7d67361c53d31ad9393410d..89686201b160a1ae99f6392db851ae622b22355a 100644
--- a/pyminer/packages/applications_toolbar/applications_toolbar.py
+++ b/pyminer/packages/applications_toolbar/applications_toolbar.py
@@ -5,15 +5,15 @@ from PySide2.QtCore import QSize, Qt, Signal
from PySide2.QtGui import QPixmap, QContextMenuEvent
from PySide2.QtWidgets import QHBoxLayout, QWidget, QSpacerItem, QToolButton, QSizePolicy, QFrame, \
QListWidget, QListWidgetItem, QAction, QMenu, QApplication, QMessageBox, QFileDialog
-from pmgwidgets import PMGToolBar, create_icon, QIcon, QDialog, QVBoxLayout, PMGPanelDialog, open_file_manager, \
+from widgets import PMGToolBar, create_icon, QIcon, QDialog, QVBoxLayout, PMGPanelDialog, open_file_manager, \
assert_not_in, assert_in
from utils import unzip_file, make_zip
-from features.main_window import base
+from lib.main_window import base
from .manage_apps import APPManager, ToolAppDesc
from .dev_tools import DevelopTools
if TYPE_CHECKING:
- from features.extensions.extensionlib import extension_lib
+ from lib.extensions.extensionlib import extension_lib
class ApplicationsList(QListWidget):
diff --git a/pyminer/packages/applications_toolbar/apps/cftool/GUI_QT.py b/pyminer/packages/applications_toolbar/apps/cftool/GUI_QT.py
index 709c6be84a735f6dba5ebbd5341dc2c049af5214..fee87b2efa094611f6223e33109210cd719c8942 100644
--- a/pyminer/packages/applications_toolbar/apps/cftool/GUI_QT.py
+++ b/pyminer/packages/applications_toolbar/apps/cftool/GUI_QT.py
@@ -24,7 +24,7 @@ from PySide2.QtWidgets import QSizePolicy, QApplication, QHBoxLayout, QDialog, Q
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import numpy as np
-from pmgwidgets import PMGPanel, center_window, set_closable, set_minimizable, set_always_on_top
+from widgets import PMGPanel, center_window, set_closable, set_minimizable, set_always_on_top
from utils import bind_combo_with_workspace
from pyminer_comm import get_var, get_var_names
diff --git a/pyminer/packages/applications_toolbar/apps/cftool/regexvalidifyer.py b/pyminer/packages/applications_toolbar/apps/cftool/regexvalidifyer.py
index 3d6b9b8363f2f061b692803986704116bc184215..e8a3a44f282da6e503369a0ed8ed57ffe4e1eab5 100644
--- a/pyminer/packages/applications_toolbar/apps/cftool/regexvalidifyer.py
+++ b/pyminer/packages/applications_toolbar/apps/cftool/regexvalidifyer.py
@@ -2,7 +2,7 @@ import re
from PySide2.QtWidgets import QDialog, QApplication, QListWidget, QHBoxLayout, QVBoxLayout, QLineEdit, QPushButton
-from pmgwidgets import PMGPanel
+from widgets import PMGPanel
class RegexDialog(QDialog):
diff --git a/pyminer/packages/applications_toolbar/apps/flowchart/README.md b/pyminer/packages/applications_toolbar/apps/flowchart/README.md
index a104ba76e7a552f67e4bf2dc9290217f913a3c98..2ca19f3fdd3e51d99c960fc242ecc3d340f5c9f3 100644
--- a/pyminer/packages/applications_toolbar/apps/flowchart/README.md
+++ b/pyminer/packages/applications_toolbar/apps/flowchart/README.md
@@ -58,8 +58,8 @@
### 节点的文件
- 与`PyMiner`主程序有调用关系(如向工作空间传递变量)的节点,位于`pyminer2\extensions\packages\flowchart\plugin_nodes`
-- 与`PyMiner`主程序无耦合关系的节点(大多数节点都是这样的),位于`pmgwidgets/flowchart/nodes`
+- 与`PyMiner`主程序无耦合关系的节点(大多数节点都是这样的),位于`widgets/flowchart/nodes`
### 节点的注册
- 少数与`PyMiner`主程序有调用关系的节点,位于`pyminer2/extensions/packages/flowchart/start_flowchart.py`中,`DataProcessWidget`的`load_nodes_library`方法
-- 多数与`PyMiner`主程序无调用关系的节点位于`pmgwidgets/flowchart/dataprocesswidget.py`中,`PMDataProcessFlowWidget`类的`load_nodes_library`方法
+- 多数与`PyMiner`主程序无调用关系的节点位于`widgets/flowchart/dataprocesswidget.py`中,`PMDataProcessFlowWidget`类的`load_nodes_library`方法
diff --git a/pyminer/packages/applications_toolbar/apps/flowchart/examples/drop_duplicated.pmfc b/pyminer/packages/applications_toolbar/apps/flowchart/examples/drop_duplicated.pmfc
index b98fa9b2dd439123edfe3af3ddbe90878254bbc0..448389542a41dda4c637a942e864a778a221418d 100644
--- a/pyminer/packages/applications_toolbar/apps/flowchart/examples/drop_duplicated.pmfc
+++ b/pyminer/packages/applications_toolbar/apps/flowchart/examples/drop_duplicated.pmfc
@@ -7,7 +7,7 @@
-50.0,
-150.0
],
- "icon": "C:\\Users\\12957\\Documents\\Developing\\Python\\PyMiner_dev_kit\\bin\\pmgwidgets\\flowchart\\core\\icons\\logo.png",
+ "icon": "C:\\Users\\12957\\Documents\\Developing\\Python\\PyMiner_dev_kit\\bin\\widgets\\flowchart\\core\\icons\\logo.png",
"content": {
"type": "DropDuplicated",
"info": {
@@ -47,7 +47,7 @@
-330.0,
-150.0
],
- "icon": "C:\\Users\\12957\\Documents\\Developing\\Python\\PyMiner_dev_kit\\bin\\pmgwidgets\\flowchart\\core\\icons\\logo.png",
+ "icon": "C:\\Users\\12957\\Documents\\Developing\\Python\\PyMiner_dev_kit\\bin\\widgets\\flowchart\\core\\icons\\logo.png",
"content": {
"type": "PandasImport",
"info": {
diff --git a/pyminer/packages/applications_toolbar/apps/flowchart/examples/read_all_csv_files.pmfc b/pyminer/packages/applications_toolbar/apps/flowchart/examples/read_all_csv_files.pmfc
index 342c2cdfc83159227da9b92b9838dd12e5e1f364..bbc5133a16c5f58b4808051c237a2904037ed6bc 100644
--- a/pyminer/packages/applications_toolbar/apps/flowchart/examples/read_all_csv_files.pmfc
+++ b/pyminer/packages/applications_toolbar/apps/flowchart/examples/read_all_csv_files.pmfc
@@ -7,7 +7,7 @@
-335.0,
-160.0
],
- "icon": "C:\\Users\\12957\\Documents\\Developing\\Python\\PyMiner_dev_kit\\bin\\pmgwidgets\\flowchart\\core\\icons\\logo.png",
+ "icon": "C:\\Users\\12957\\Documents\\Developing\\Python\\PyMiner_dev_kit\\bin\\widgets\\flowchart\\core\\icons\\logo.png",
"content": {
"type": "ListDirs",
"info": {
@@ -35,7 +35,7 @@
-75.0,
-245.0
],
- "icon": "C:\\Users\\12957\\Documents\\Developing\\Python\\PyMiner_dev_kit\\bin\\pmgwidgets\\flowchart\\core\\icons\\logo.png",
+ "icon": "C:\\Users\\12957\\Documents\\Developing\\Python\\PyMiner_dev_kit\\bin\\widgets\\flowchart\\core\\icons\\logo.png",
"content": {
"type": "PandasImport",
"info": {
@@ -77,7 +77,7 @@
185.0,
-95.0
],
- "icon": "C:\\Users\\12957\\Documents\\Developing\\Python\\PyMiner_dev_kit\\bin\\pmgwidgets\\flowchart\\core\\icons\\logo.png",
+ "icon": "C:\\Users\\12957\\Documents\\Developing\\Python\\PyMiner_dev_kit\\bin\\widgets\\flowchart\\core\\icons\\logo.png",
"content": {
"type": "SetVariable",
"info": {
diff --git a/pyminer/packages/applications_toolbar/apps/flowchart/main.py b/pyminer/packages/applications_toolbar/apps/flowchart/main.py
index 271a90886cafd4d7978394bc0495f23969e706b9..a9413ba61d36b4fcb1a4dbd3c1140f05c1b0973b 100644
--- a/pyminer/packages/applications_toolbar/apps/flowchart/main.py
+++ b/pyminer/packages/applications_toolbar/apps/flowchart/main.py
@@ -16,7 +16,7 @@ from PySide2.QtWidgets import QApplication
sys.path.append(root_path)
-from pmgwidgets import PMDataProcessFlowWidget
+from widgets import PMDataProcessFlowWidget
if TYPE_CHECKING:
from .plugin_nodes.nodes import VariableSetter, VariableGetter
diff --git a/pyminer/packages/applications_toolbar/apps/flowchart/plugin_nodes/nodes.py b/pyminer/packages/applications_toolbar/apps/flowchart/plugin_nodes/nodes.py
index 9258b604db564fd518358de4000e9c8d0b42fcb3..6dec4049396c371d4151eb4bb74cd23d2ead425c 100644
--- a/pyminer/packages/applications_toolbar/apps/flowchart/plugin_nodes/nodes.py
+++ b/pyminer/packages/applications_toolbar/apps/flowchart/plugin_nodes/nodes.py
@@ -1,6 +1,6 @@
import os
from typing import List, Union, Tuple, Any
-from pmgwidgets import PMGFlowContent, PMGPanelDialog
+from widgets import PMGFlowContent, PMGPanelDialog
def convert_path_to_identifier(path: str):
diff --git a/pyminer/packages/applications_toolbar/apps/ligralpy/README.md b/pyminer/packages/applications_toolbar/apps/ligralpy/README.md
index a104ba76e7a552f67e4bf2dc9290217f913a3c98..2ca19f3fdd3e51d99c960fc242ecc3d340f5c9f3 100644
--- a/pyminer/packages/applications_toolbar/apps/ligralpy/README.md
+++ b/pyminer/packages/applications_toolbar/apps/ligralpy/README.md
@@ -58,8 +58,8 @@
### 节点的文件
- 与`PyMiner`主程序有调用关系(如向工作空间传递变量)的节点,位于`pyminer2\extensions\packages\flowchart\plugin_nodes`
-- 与`PyMiner`主程序无耦合关系的节点(大多数节点都是这样的),位于`pmgwidgets/flowchart/nodes`
+- 与`PyMiner`主程序无耦合关系的节点(大多数节点都是这样的),位于`widgets/flowchart/nodes`
### 节点的注册
- 少数与`PyMiner`主程序有调用关系的节点,位于`pyminer2/extensions/packages/flowchart/start_flowchart.py`中,`DataProcessWidget`的`load_nodes_library`方法
-- 多数与`PyMiner`主程序无调用关系的节点位于`pmgwidgets/flowchart/dataprocesswidget.py`中,`PMDataProcessFlowWidget`类的`load_nodes_library`方法
+- 多数与`PyMiner`主程序无调用关系的节点位于`widgets/flowchart/dataprocesswidget.py`中,`PMDataProcessFlowWidget`类的`load_nodes_library`方法
diff --git a/pyminer/packages/applications_toolbar/apps/ligralpy/examples/second_ordered.lig.json b/pyminer/packages/applications_toolbar/apps/ligralpy/examples/second_ordered.lig.json
index 9ad0305a4f20c538335f10ff4521ec401f0331ac..86e6129e2dc54ff269d47876e6aca6ecd7bdb27b 100644
--- a/pyminer/packages/applications_toolbar/apps/ligralpy/examples/second_ordered.lig.json
+++ b/pyminer/packages/applications_toolbar/apps/ligralpy/examples/second_ordered.lig.json
@@ -7,7 +7,7 @@
350.0,
-225.0
],
- "icon": "C:\\Users\\12957\\Documents\\Developing\\Python\\PyMiner_dev_kit\\bin\\pmgwidgets\\flowchart\\core\\icons\\logo.png",
+ "icon": "C:\\Users\\12957\\Documents\\Developing\\Python\\PyMiner_dev_kit\\bin\\widgets\\flowchart\\core\\icons\\logo.png",
"content": {
"type": "Scope",
"info": {}
@@ -35,7 +35,7 @@
185.0,
-225.0
],
- "icon": "C:\\Users\\12957\\Documents\\Developing\\Python\\PyMiner_dev_kit\\bin\\pmgwidgets\\flowchart\\core\\icons\\logo.png",
+ "icon": "C:\\Users\\12957\\Documents\\Developing\\Python\\PyMiner_dev_kit\\bin\\widgets\\flowchart\\core\\icons\\logo.png",
"content": {
"type": "Integrator",
"info": {}
@@ -73,7 +73,7 @@
-295.0,
-245.0
],
- "icon": "C:\\Users\\12957\\Documents\\Developing\\Python\\PyMiner_dev_kit\\bin\\pmgwidgets\\flowchart\\core\\icons\\logo.png",
+ "icon": "C:\\Users\\12957\\Documents\\Developing\\Python\\PyMiner_dev_kit\\bin\\widgets\\flowchart\\core\\icons\\logo.png",
"content": {
"type": "Calculator",
"info": {
@@ -122,7 +122,7 @@
-460.0,
-265.0
],
- "icon": "C:\\Users\\12957\\Documents\\Developing\\Python\\PyMiner_dev_kit\\bin\\pmgwidgets\\flowchart\\core\\icons\\logo.png",
+ "icon": "C:\\Users\\12957\\Documents\\Developing\\Python\\PyMiner_dev_kit\\bin\\widgets\\flowchart\\core\\icons\\logo.png",
"content": {
"type": "Constant",
"info": {
@@ -152,7 +152,7 @@
-140.0,
-225.0
],
- "icon": "C:\\Users\\12957\\Documents\\Developing\\Python\\PyMiner_dev_kit\\bin\\pmgwidgets\\flowchart\\core\\icons\\logo.png",
+ "icon": "C:\\Users\\12957\\Documents\\Developing\\Python\\PyMiner_dev_kit\\bin\\widgets\\flowchart\\core\\icons\\logo.png",
"content": {
"type": "Calculator",
"info": {
@@ -201,7 +201,7 @@
15.0,
-225.0
],
- "icon": "C:\\Users\\12957\\Documents\\Developing\\Python\\PyMiner_dev_kit\\bin\\pmgwidgets\\flowchart\\core\\icons\\logo.png",
+ "icon": "C:\\Users\\12957\\Documents\\Developing\\Python\\PyMiner_dev_kit\\bin\\widgets\\flowchart\\core\\icons\\logo.png",
"content": {
"type": "Integrator",
"info": {}
@@ -239,7 +239,7 @@
-80.0,
-55.0
],
- "icon": "C:\\Users\\12957\\Documents\\Developing\\Python\\PyMiner_dev_kit\\bin\\pmgwidgets\\flowchart\\core\\icons\\logo.png",
+ "icon": "C:\\Users\\12957\\Documents\\Developing\\Python\\PyMiner_dev_kit\\bin\\widgets\\flowchart\\core\\icons\\logo.png",
"content": {
"type": "Gain",
"info": {
diff --git a/pyminer/packages/applications_toolbar/apps/ligralpy/main.py b/pyminer/packages/applications_toolbar/apps/ligralpy/main.py
index ec33a2835fbbe035feb371aa76b14656cec6fed9..2064aaffa37e1ba2683b8ef6885444f9bad2d6a6 100644
--- a/pyminer/packages/applications_toolbar/apps/ligralpy/main.py
+++ b/pyminer/packages/applications_toolbar/apps/ligralpy/main.py
@@ -13,17 +13,17 @@ import sys
import time
from typing import List, Dict, TYPE_CHECKING
-from pmgwidgets.flowchart.core.flow_content import FlowContentForFunction, flowcontent_types, PMGFlowContent
+from widgets.flowchart.core.flow_content import FlowContentForFunction, flowcontent_types, PMGFlowContent
-from pmgwidgets.flowchart.core.flow_node import Node
-from pmgwidgets.flowchart.core.nodemanager import NodeManagerWidget
-from pmgwidgets.flowchart.core.flowchart_scene import PMGraphicsScene
-from pmgwidgets.flowchart.core.flowchart_widget import PMGraphicsView
+from widgets.flowchart.core.flow_node import Node
+from widgets.flowchart.core.nodemanager import NodeManagerWidget
+from widgets.flowchart.core.flowchart_scene import PMGraphicsScene
+from widgets.flowchart.core.flowchart_widget import PMGraphicsView
from PySide2.QtCore import QSize, QCoreApplication, QLineF, Qt, QThread, Signal
from PySide2.QtGui import QColor, QKeyEvent, QWheelEvent, QCloseEvent
from PySide2.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QToolButton, QSpacerItem, QSizePolicy, QGraphicsView, \
QFrame, QApplication, QFileDialog, QMessageBox
-from pmgwidgets.flowchart.core import PMFlowWidget
+from widgets.flowchart.core import PMFlowWidget
COLOR_NORMAL = QColor(212, 227, 242)
COLOR_HOVER = QColor(255, 200, 00)
@@ -136,7 +136,7 @@ class PMGSimulationWidget(PMFlowWidget):
file_path = os.path.join(os.path.dirname(__file__), 'temp.lig.json')
with open(file_path, 'w')as f:
f.write(text)
- from pmgwidgets.utilities.platform import run_command_in_terminal_block
+ from widgets.utilities.platform import run_command_in_terminal_block
run_command_in_terminal_block('%s %s --json' % (ligral_path, file_path))
diff --git a/pyminer/packages/applications_toolbar/apps/ligralpy/nodes/simulation.py b/pyminer/packages/applications_toolbar/apps/ligralpy/nodes/simulation.py
index 52def92a8f5dff4ea80de8382db125ea391f13f3..4906df360aff21dd1e1f9c9986d5d90d9c44fe7d 100644
--- a/pyminer/packages/applications_toolbar/apps/ligralpy/nodes/simulation.py
+++ b/pyminer/packages/applications_toolbar/apps/ligralpy/nodes/simulation.py
@@ -2,7 +2,7 @@ import sys
from typing import List, Dict
from PySide2.QtWidgets import QApplication, QDialog, QVBoxLayout, QLineEdit
-from pmgwidgets import PMFlowWidget, PMGFlowContent, PMGPanelDialog
+from widgets import PMFlowWidget, PMGFlowContent, PMGPanelDialog
class BaseLigralGenerator(PMGFlowContent):
diff --git a/pyminer/packages/applications_toolbar/dev_tools.py b/pyminer/packages/applications_toolbar/dev_tools.py
index c4f82c85e6557c491f9ce0fd4d7002865d72f4db..851b6302c3479078a8c44955455059680ed78a1c 100644
--- a/pyminer/packages/applications_toolbar/dev_tools.py
+++ b/pyminer/packages/applications_toolbar/dev_tools.py
@@ -11,7 +11,7 @@ from PySide2.QtCore import QObject
from PySide2.QtWidgets import QWizard, QMessageBox, QFileDialog
from packages.applications_toolbar.ui.app_designer import Ui_Wizard
-from pmgwidgets import PMGPanelDialog
+from widgets import PMGPanelDialog
from utils import get_python_modules_directory
logger = logging.getLogger(__name__)
diff --git a/pyminer/packages/applications_toolbar/main.py b/pyminer/packages/applications_toolbar/main.py
index b39f6a09fca59204ec93ab2e2faceb270d33afaf..8cc0d486e938653650e239fb15220300e98b7938 100644
--- a/pyminer/packages/applications_toolbar/main.py
+++ b/pyminer/packages/applications_toolbar/main.py
@@ -4,11 +4,11 @@ from typing import TYPE_CHECKING, Callable, Dict, List
from PySide2.QtWidgets import QApplication
from PySide2.QtCore import Signal, QLocale, QTranslator
-from features.extensions.extensionlib import BaseExtension, BaseInterface
+from lib.extensions.extensionlib import BaseExtension, BaseInterface
if TYPE_CHECKING:
pass
- from features.extensions.extensionlib import extension_lib
+ from lib.extensions.extensionlib import extension_lib
from .applications_toolbar import PMApplicationsToolBar
from .process_monitor import PMProcessConsoleTabWidget
diff --git a/pyminer/packages/applications_toolbar/manage_apps.py b/pyminer/packages/applications_toolbar/manage_apps.py
index 285f88abb84f1228d46cee02a7be5bd3e5c7a58f..e991bc066b7323dd5a776a189b536442c41a0480 100644
--- a/pyminer/packages/applications_toolbar/manage_apps.py
+++ b/pyminer/packages/applications_toolbar/manage_apps.py
@@ -2,7 +2,7 @@ import os
import json
import sys
from typing import List, Dict, Union, Optional
-from pmgwidgets import assert_in
+from widgets import assert_in
def check_package_json(dic: Dict):
diff --git a/pyminer/packages/applications_toolbar/process_monitor.py b/pyminer/packages/applications_toolbar/process_monitor.py
index 2747afe160199fe464c4fedbc5d2451d72bda54b..633cc3c1c462cb511b17dd5c39ea184b31a076c3 100644
--- a/pyminer/packages/applications_toolbar/process_monitor.py
+++ b/pyminer/packages/applications_toolbar/process_monitor.py
@@ -3,8 +3,8 @@ import os
from PySide2.QtGui import QCloseEvent
from PySide2.QtWidgets import QTabWidget, QMessageBox
-from pmgwidgets import PMDockObject, create_icon
-from pmgwidgets import PMGProcessConsoleWidget, PMGInstantBootConsoleWidget
+from widgets import PMDockObject, create_icon
+from widgets import PMGProcessConsoleWidget, PMGInstantBootConsoleWidget
diff --git a/pyminer/packages/code_editor/main.py b/pyminer/packages/code_editor/main.py
index d6033185c6ffcc9e45650844a98b7b3bb68431db..5860571c0f75ff299b56f27d38698a5ffae9674e 100644
--- a/pyminer/packages/code_editor/main.py
+++ b/pyminer/packages/code_editor/main.py
@@ -7,11 +7,11 @@ from .utils.base_object import CodeEditorBaseObject
if TYPE_CHECKING:
pass
-from features.extensions.extensionlib import BaseInterface, BaseExtension
+from lib.extensions.extensionlib import BaseInterface, BaseExtension
from .widgets.tab_widget import PMCodeEditTabWidget
from .widgets.debugger import PMDebugConsoleTabWidget
from .widgets.toolbar import PMEditorToolbar
-from pmgwidgets import PMGPanel, load_json, dump_json
+from widgets import PMGPanel, load_json, dump_json
__prevent_from_ide_optimization = PMEditorToolbar # 这一行的目的是防止导入被编辑器自动优化。
logger = logging.getLogger('code_editor')
diff --git a/pyminer/packages/code_editor/utils/base_object.py b/pyminer/packages/code_editor/utils/base_object.py
index ba577dcf9c8a3218bdb9f88481662982f3dbed27..7f127ddd209f3540dab5a1d13e969f7c11eb3d8b 100644
--- a/pyminer/packages/code_editor/utils/base_object.py
+++ b/pyminer/packages/code_editor/utils/base_object.py
@@ -5,11 +5,11 @@ from typing import Optional, Callable, TYPE_CHECKING
from PySide2.QtCore import QTranslator, QLocale
from PySide2.QtWidgets import QMessageBox
-from features.extensions.extensionlib import extension_lib
+from lib.extensions.extensionlib import extension_lib
from ..settings import Settings
if TYPE_CHECKING:
- from features.extensions.extensionlib.extension_lib import ExtensionLib
+ from lib.extensions.extensionlib.extension_lib import ExtensionLib
from ...ipython_console.main import ConsoleInterface
from ...applications_toolbar.main import ApplicationsInterface
from ...embedded_browser.main import Interface as EmbeddedBrowserInterface
diff --git a/pyminer/packages/code_editor/widgets/debugger.py b/pyminer/packages/code_editor/widgets/debugger.py
index 355e46abe9ef725c63e64863c05349418201988a..dba4ddb35c0c4158f5293234501e54d11ebf1024 100644
--- a/pyminer/packages/code_editor/widgets/debugger.py
+++ b/pyminer/packages/code_editor/widgets/debugger.py
@@ -5,8 +5,8 @@ from PySide2.QtCore import Signal
from PySide2.QtGui import QCloseEvent
from PySide2.QtWidgets import QTabWidget, QMessageBox
-from pmgwidgets import PMDockObject
-from features.ui.common.debug_process_with_pyqt import PMGDebugConsoleWidget
+from widgets import PMDockObject
+from lib.ui.common.debug_process_with_pyqt import PMGDebugConsoleWidget
class PMDebugConsoleTabWidget(QTabWidget, PMDockObject):
signal_goto_file = Signal(str, int)
diff --git a/pyminer/packages/code_editor/widgets/dialogs/find_dialog.py b/pyminer/packages/code_editor/widgets/dialogs/find_dialog.py
index 79685eb4febe23c7c74f36e0513e428a1b75f209..d841870935993a783f5389e408d27729be317eab 100644
--- a/pyminer/packages/code_editor/widgets/dialogs/find_dialog.py
+++ b/pyminer/packages/code_editor/widgets/dialogs/find_dialog.py
@@ -2,7 +2,7 @@ from typing import Callable, TYPE_CHECKING
from PySide2.QtWidgets import QDialog, QVBoxLayout, QPushButton, QHBoxLayout
-from pmgwidgets import PMGPanel
+from widgets import PMGPanel
if TYPE_CHECKING:
from ...widgets.text_edit.base_text_edit import PMBaseCodeEdit
diff --git a/pyminer/packages/code_editor/widgets/editors/base_editor.py b/pyminer/packages/code_editor/widgets/editors/base_editor.py
index 4f3ef1d4384963436bdad7278dbe5560c40bb16f..84e1ddfcbf55e5698abf816f0f55c389aad9a297 100644
--- a/pyminer/packages/code_editor/widgets/editors/base_editor.py
+++ b/pyminer/packages/code_editor/widgets/editors/base_editor.py
@@ -35,7 +35,7 @@ from PySide2.QtCore import SignalInstance, Signal, QDir
from PySide2.QtGui import QTextDocument, QTextCursor
from PySide2.QtWidgets import QWidget, QMessageBox, QLabel, QVBoxLayout, QFileDialog
-from features.extensions.extensionlib.extension_lib import ExtensionLib
+from lib.extensions.extensionlib.extension_lib import ExtensionLib
from ..dialogs.find_dialog import PMFindDialog
from ..dialogs.goto_line_dialog import PMGotoLineDialog
from ..text_edit.base_text_edit import PMBaseCodeEdit
diff --git a/pyminer/packages/code_editor/widgets/editors/python_editor.py b/pyminer/packages/code_editor/widgets/editors/python_editor.py
index e64bd59b0b797a11f527cc593e9b94e488ae3a76..a201b076b82a42d88de2d9a156c6fbe095adffdf 100644
--- a/pyminer/packages/code_editor/widgets/editors/python_editor.py
+++ b/pyminer/packages/code_editor/widgets/editors/python_editor.py
@@ -9,7 +9,7 @@ Created on 2020/9/7
输入 ### 开头的行,可以触发运行一个模块的功能。
输入if __name__=='__main__':可以触发运行全部代码的功能。
-按照PyMiner控件协议,输入# mkval后面接pmgwidgets控件表达式,可以触发控件编辑按钮。
+按照PyMiner控件协议,输入# mkval后面接widgets控件表达式,可以触发控件编辑按钮。
"""
# -*- coding:utf-8 -*-
#
@@ -40,7 +40,7 @@ from PySide2.QtCore import SignalInstance, Signal, QDir
from PySide2.QtGui import QCloseEvent
from PySide2.QtWidgets import QMessageBox
-from pmgwidgets import in_unit_test, PMGOneShotThreadRunner, run_python_file_in_terminal, parse_simplified_pmgjson, \
+from widgets import in_unit_test, PMGOneShotThreadRunner, run_python_file_in_terminal, parse_simplified_pmgjson, \
PMGPanelDialog
from .base_editor import PMBaseEditor
from ..text_edit.python_text_edit import PMPythonCodeEdit
diff --git a/pyminer/packages/code_editor/widgets/tab_widget.py b/pyminer/packages/code_editor/widgets/tab_widget.py
index 6ed28988b8b0e1b5963d8eb552ff3e6b0167df0e..96da7ad4da217c18ddefc5b9355eb2e077ad2bc7 100644
--- a/pyminer/packages/code_editor/widgets/tab_widget.py
+++ b/pyminer/packages/code_editor/widgets/tab_widget.py
@@ -8,8 +8,8 @@ from PySide2.QtCore import QTimer, QThread, QDir
from PySide2.QtGui import QCloseEvent
from PySide2.QtWidgets import QTabWidget, QSizePolicy, QMessageBox, QFileDialog, QComboBox, QWidget
-import pmgwidgets
-from pmgwidgets import PMDockObject, UndoManager, PMGFileSystemWatchdog, in_unit_test
+import widgets
+from widgets import PMDockObject, UndoManager, PMGFileSystemWatchdog, in_unit_test
from .editors.python_editor import PMPythonEditor
from .ui.findinpath import FindInPathWidget
from ..utils.base_object import CodeEditorBaseObject
@@ -17,7 +17,7 @@ from ..utils.code_checker.base_code_checker import CodeCheckWorker
from ..utils.highlighter.python_highlighter import PythonHighlighter
if TYPE_CHECKING:
- from features.extensions.extensionlib.extension_lib import ExtensionLib
+ from lib.extensions.extensionlib.extension_lib import ExtensionLib
from .editors.base_editor import PMBaseEditor
from .editors.markdown_editor import PMMarkdownEditor
@@ -277,7 +277,7 @@ class PMCodeEditTabWidget(CodeEditorBaseObject, QTabWidget, PMDockObject):
widget.signal_request_find_in_path.connect(self.slot_find_in_path)
self.addTab(widget, widget.filename())
self.setCurrentWidget(widget)
- if not pmgwidgets.in_unit_test(): # 如果不在单元测试,则切换工具条。
+ if not widgets.in_unit_test(): # 如果不在单元测试,则切换工具条。
self.extension_lib.UI.raise_dock_into_view('code_editor')
self.extension_lib.UI.switch_toolbar('code_editor_toolbar', switch_only=True)
diff --git a/pyminer/packages/code_editor/widgets/toolbar.py b/pyminer/packages/code_editor/widgets/toolbar.py
index dca769002f2203c5ef9c3f47a7af4f00b7e1d893..be9d71f33f4d19e3428cf6fab94485162c66d51b 100644
--- a/pyminer/packages/code_editor/widgets/toolbar.py
+++ b/pyminer/packages/code_editor/widgets/toolbar.py
@@ -1,4 +1,4 @@
-from pmgwidgets import create_icon, PMGToolBar, QComboBox
+from widgets import create_icon, PMGToolBar, QComboBox
from ..utils.base_object import CodeEditorBaseObject
diff --git a/pyminer/packages/code_editor/widgets/ui/findinpath.py b/pyminer/packages/code_editor/widgets/ui/findinpath.py
index 753976b5f8c7a74abf25b3a58a070495e587fb51..0a7de4bc3a66a6bac558aab1309a8f9a6b4c3764 100644
--- a/pyminer/packages/code_editor/widgets/ui/findinpath.py
+++ b/pyminer/packages/code_editor/widgets/ui/findinpath.py
@@ -5,7 +5,7 @@
# @File: findinpath.py
import os
from typing import List, Tuple
-from pmgwidgets import PMDockObject
+from widgets import PMDockObject
from utils import search_in_path
from PySide2.QtWidgets import QDialog, QApplication, QVBoxLayout, QListWidget, QHBoxLayout, QPushButton, QCheckBox, \
QLineEdit, QListWidgetItem
diff --git a/pyminer/packages/code_editor/widgets/ui/formeditor.py b/pyminer/packages/code_editor/widgets/ui/formeditor.py
index 4c5f4343f5cf115164c210d63038e45b77f9c9a2..9ad9d32d169f2ba40cc40f4a66f709a602db06a6 100644
--- a/pyminer/packages/code_editor/widgets/ui/formeditor.py
+++ b/pyminer/packages/code_editor/widgets/ui/formeditor.py
@@ -12,7 +12,7 @@ from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
-from pmgwidgets import PMGQsciWidget
+from widgets import PMGQsciWidget
class Ui_FormEditor(object):
diff --git a/pyminer/packages/code_editor/widgets/ui/ui_formeditor.py b/pyminer/packages/code_editor/widgets/ui/ui_formeditor.py
index 36fc991f5a37825c9952e8704769c69496a9fc14..5792258d4ac4f22dcdb80b029f21c5affcec120f 100644
--- a/pyminer/packages/code_editor/widgets/ui/ui_formeditor.py
+++ b/pyminer/packages/code_editor/widgets/ui/ui_formeditor.py
@@ -54,4 +54,4 @@ class Ui_FormEditor(object):
self.label_status_ln_col.setText(_translate("FormEditor", "Ln:{0} Col:{1}"))
self.label_status_eol.setText(_translate("FormEditor", "Unix(LF)"))
-from pmgwidgets import PMGQsciWidget
+from widgets import PMGQsciWidget
diff --git a/pyminer/packages/dataio/accountutil.py b/pyminer/packages/dataio/accountutil.py
index bb72a882b4c00230d3a613c2ea5a852a86b4a638..85b706d805a7021bee43605eac2ef8cb2f1fcae7 100644
--- a/pyminer/packages/dataio/accountutil.py
+++ b/pyminer/packages/dataio/accountutil.py
@@ -7,7 +7,7 @@ import pickle
import os
import socket
from typing import Dict, List, Union, Tuple, Optional
-from pmgwidgets import create_file_if_not_exist
+from widgets import create_file_if_not_exist
class DBAccountUtil():
diff --git a/pyminer/packages/dataio/dbindexing.py b/pyminer/packages/dataio/dbindexing.py
index e210f131958a123473e908babe0f05a3ba396b80..0d4b2371503b6e10a5f6babb1854d0bbbad73e36 100644
--- a/pyminer/packages/dataio/dbindexing.py
+++ b/pyminer/packages/dataio/dbindexing.py
@@ -8,7 +8,7 @@ import pickle
import socket
from typing import List
-from pmgwidgets import create_file_if_not_exist
+from widgets import create_file_if_not_exist
class DatabaseIndexer():
diff --git a/pyminer/packages/dataio/export.py b/pyminer/packages/dataio/export.py
index 568e26693d5289adfe85e83d9c846a91414d007a..d3caa60820504ffb9f0a8967d6d9e5be146aa2db 100644
--- a/pyminer/packages/dataio/export.py
+++ b/pyminer/packages/dataio/export.py
@@ -1,6 +1,6 @@
from typing import Any, List, TYPE_CHECKING
from PySide2.QtWidgets import QApplication, QWidget, QDialog, QHBoxLayout, QVBoxLayout, QMessageBox, QDialogButtonBox
-from pmgwidgets import PMTableView, PMGPanel, in_unit_test
+from widgets import PMTableView, PMGPanel, in_unit_test
from pyminer_comm import get_var
import json
diff --git a/pyminer/packages/dataio/main.py b/pyminer/packages/dataio/main.py
index 370bd313211e0d9b6a048d1132e6178be6a5f724..db0314bfa05481303f18a321bcd52e289d6717b9 100644
--- a/pyminer/packages/dataio/main.py
+++ b/pyminer/packages/dataio/main.py
@@ -11,9 +11,9 @@ from PySide2.QtWidgets import QApplication
sys.path.append(os.path.dirname(__file__))
-from features.extensions.extensionlib import BaseInterface, BaseExtension
+from lib.extensions.extensionlib import BaseInterface, BaseExtension
-from pmgwidgets import PMGPanel, create_file_if_not_exist, load_json, dump_json, create_icon, assert_in
+from widgets import PMGPanel, create_file_if_not_exist, load_json, dump_json, create_icon, assert_in
import json
from .importutils import importutils
from .dbimport import dbimportutils
diff --git a/pyminer/packages/dataio/sample.py b/pyminer/packages/dataio/sample.py
index 8af5aed28a75ebb7d2bcdde886be99dab1929ca9..141888e38b8b7348c4b0ca0a83835295018e82ff 100644
--- a/pyminer/packages/dataio/sample.py
+++ b/pyminer/packages/dataio/sample.py
@@ -12,7 +12,7 @@ from PySide2.QtCore import *
from PySide2.QtWidgets import *
from dataImportModel import Ui_Form as dataImportFormEngine
-from pmgwidgets import kwargs_to_str
+from widgets import kwargs_to_str
from pyminer_comm import set_var, run_command
# 导入matlab加载模块
diff --git a/pyminer/packages/drawings_toolbar/fastui/base.py b/pyminer/packages/drawings_toolbar/fastui/base.py
index 616ed45eed5ecaf2eaf8367174b529a7cf108025..436fddec30248921707bffa756da3475df0c3add 100644
--- a/pyminer/packages/drawings_toolbar/fastui/base.py
+++ b/pyminer/packages/drawings_toolbar/fastui/base.py
@@ -37,7 +37,7 @@ from PySide2.QtCore import Qt, QCoreApplication, Signal
from PySide2.QtWidgets import QDialog, QVBoxLayout, QApplication, QPushButton, QComboBox, QHBoxLayout, QLabel, \
QTextBrowser
-from pmgwidgets import PMGPanel, PMGOneShotThreadRunner
+from widgets import PMGPanel, PMGOneShotThreadRunner
from pyminer_comm import get_var_names, run_command, call_interface
from utils import bind_combo_with_workspace
diff --git a/pyminer/packages/drawings_toolbar/main.py b/pyminer/packages/drawings_toolbar/main.py
index 7119ca16b1ffd5bb8cc83f40ea9d1da760a8e5cd..bac7158f808295d917facaad6abfb40307f58ad1 100644
--- a/pyminer/packages/drawings_toolbar/main.py
+++ b/pyminer/packages/drawings_toolbar/main.py
@@ -17,8 +17,8 @@ trans_editor.load(file_name)
app.installTranslator(trans_editor)
import numpy as np
import pandas as pd
-from features.extensions.extensionlib import BaseExtension, BaseInterface
-from pmgwidgets import PMGToolBar, create_icon
+from lib.extensions.extensionlib import BaseExtension, BaseInterface
+from widgets import PMGToolBar, create_icon
from packages.drawings_toolbar.group_chart import DialogGroup
from packages.drawings_toolbar.radar_chart import radar_factory
from matplotlib import pyplot as plt
@@ -30,7 +30,7 @@ from pyminer_comm import get_var
logger = logging.getLogger(__name__)
if TYPE_CHECKING:
- from features.extensions.extensionlib import extension_lib
+ from lib.extensions.extensionlib import extension_lib
from .fastui.draw_hist import DrawHistDialog
from .fastui.plot import PlotDialog
@@ -330,7 +330,7 @@ class PMDrawingsToolBar(PMGToolBar):
draw_value = np.array(dv.values)
elif isinstance(draw_data, (Series, DataFrame)):
if isinstance(draw_data, DataFrame):
- from pmgwidgets import PMGPanelDialog
+ from widgets import PMGPanelDialog
dlg = PMGPanelDialog(parent=self, views=[[
'combo_ctrl', 'series', '选择系列', '全部', draw_data.select_dtypes(['number']).columns.tolist() + ['全部']]
])
@@ -805,7 +805,7 @@ class PMDrawingsToolBar(PMGToolBar):
def set_panel_visibility(self):
self.refresh_pos()
- w: 'pmgwidgets.TopLevelWidget' = self._control_widget_dic['drawing_selection_panel']
+ w: 'widgets.TopLevelWidget' = self._control_widget_dic['drawing_selection_panel']
w.setVisible(not w.isVisible())
def refresh_pos(self):
@@ -814,7 +814,7 @@ class PMDrawingsToolBar(PMGToolBar):
"""
return
btn = self.get_control_widget('button_show_more_plots')
- panel: 'pmgwidgets.TopLevelWidget' = self.get_control_widget(
+ panel: 'widgets.TopLevelWidget' = self.get_control_widget(
'drawing_selection_panel')
width = self.get_control_widget('button_list').width()
panel.set_width(width)
diff --git a/pyminer/packages/embedded_browser/main.py b/pyminer/packages/embedded_browser/main.py
index 2b082bb2777c33bbac0a034509311a3d163adae9..e5dbebb4b8780ef9d804263b9ccfc1a2b4030e16 100644
--- a/pyminer/packages/embedded_browser/main.py
+++ b/pyminer/packages/embedded_browser/main.py
@@ -2,7 +2,7 @@ import logging
from typing import Dict
from PySide2.QtCore import QCoreApplication
-from features.extensions.extensionlib import BaseExtension, BaseInterface
+from lib.extensions.extensionlib import BaseExtension, BaseInterface
from .webbrowser import PMWebBrowser
logger = logging.getLogger(__name__)
diff --git a/pyminer/packages/embedded_browser/webbrowser.py b/pyminer/packages/embedded_browser/webbrowser.py
index 92c77fa346a1c0e1aa6a4d762efa5ed39991f833..eae0f45700cb13257fe962a66059142c79754ddf 100644
--- a/pyminer/packages/embedded_browser/webbrowser.py
+++ b/pyminer/packages/embedded_browser/webbrowser.py
@@ -1,5 +1,5 @@
-from pmgwidgets import PMDockObject
-from pmgwidgets import PMGWebBrowser, PMGWebEngineView
+from widgets import PMDockObject
+from widgets import PMGWebBrowser, PMGWebEngineView
def createWindow(self:PMGWebEngineView, QWebEnginePage_WebWindowType) -> PMGWebEngineView:
diff --git a/pyminer/packages/file_tree/file_tree.py b/pyminer/packages/file_tree/file_tree.py
index 9b37739d10f09236221627089fb3d9196a82f055..75193a0ec008c5eaef03abd39dc18d945cf67193 100644
--- a/pyminer/packages/file_tree/file_tree.py
+++ b/pyminer/packages/file_tree/file_tree.py
@@ -7,7 +7,7 @@ from PySide2.QtGui import QPixmap, QIcon, QCloseEvent
from PySide2.QtWidgets import QVBoxLayout, QWidget, QFileDialog, QPushButton, QHBoxLayout, QLineEdit, QToolButton, \
QApplication, QMessageBox
-from pmgwidgets import PMGFilesTreeview, PMDockObject, in_unit_test, UndoManager
+from widgets import PMGFilesTreeview, PMDockObject, in_unit_test, UndoManager
class PMFilesTree(QWidget, PMDockObject):
diff --git a/pyminer/packages/file_tree/main.py b/pyminer/packages/file_tree/main.py
index 697259db8ab8d6eae4938f4cb07f8393700d082b..040d4d9b9deb71260136de1677d1997f9563de4f 100644
--- a/pyminer/packages/file_tree/main.py
+++ b/pyminer/packages/file_tree/main.py
@@ -4,7 +4,7 @@ from typing import Callable
from PySide2.QtCore import QLocale, QTranslator
from PySide2.QtWidgets import QApplication
-from features.extensions.extensionlib import BaseExtension, BaseInterface
+from lib.extensions.extensionlib import BaseExtension, BaseInterface
from .file_tree import PMFilesTree
file_name = os.path.join(os.path.dirname(__file__), 'translations', 'qt_%s.qm' % QLocale.system().name())
diff --git a/pyminer/packages/ipython_console/ipythonqtconsole.py b/pyminer/packages/ipython_console/ipythonqtconsole.py
index e10b1683c6fbf14cf9592d60807c7e7b3d5f8835..b996f99e654f2a5a40448d1b9e5ebde292e7518b 100644
--- a/pyminer/packages/ipython_console/ipythonqtconsole.py
+++ b/pyminer/packages/ipython_console/ipythonqtconsole.py
@@ -10,8 +10,8 @@ Created on 2020/8/24
"""
import os
from typing import Tuple
-from pmgwidgets import PMDockObject, in_unit_test
-from pmgwidgets.widgets.basic.others.console import PMGIpythonConsole
+from widgets import PMDockObject, in_unit_test
+from widgets.widgets.basic.others.console import PMGIpythonConsole
from PySide2.QtWidgets import QApplication
if QApplication.instance() is not None:
diff --git a/pyminer/packages/ipython_console/main.py b/pyminer/packages/ipython_console/main.py
index 1492208da5d4cd739105b32b3cb2408ba6a53f5b..7ac94a56599c86c1a208eec2480d8d4929c36ab7 100644
--- a/pyminer/packages/ipython_console/main.py
+++ b/pyminer/packages/ipython_console/main.py
@@ -5,7 +5,7 @@ import tornado.platform.asyncio as async_io
from PySide2.QtCore import QLocale, QTranslator
from PySide2.QtWidgets import QApplication
-from features.extensions.extensionlib import BaseExtension, BaseInterface
+from lib.extensions.extensionlib import BaseExtension, BaseInterface
from .ipythonqtconsole import ConsoleWidget
if sys.platform == 'win32':
diff --git a/pyminer/packages/jupyter_notebook_support/ipython_data_show.py b/pyminer/packages/jupyter_notebook_support/ipython_data_show.py
index f84c94bc2d4557afb4e13ba8fb46ffb02f2fd13f..e419f774b78942dc9f2f3aca80928d4c45d80ac9 100644
--- a/pyminer/packages/jupyter_notebook_support/ipython_data_show.py
+++ b/pyminer/packages/jupyter_notebook_support/ipython_data_show.py
@@ -8,7 +8,7 @@ from typing import Dict
from PySide2.QtWidgets import QTableView, QTableWidget, QWidget, QVBoxLayout, QComboBox, QApplication, QTableWidgetItem, \
QHeaderView
-from pmgwidgets import PMDockObject
+from widgets import PMDockObject
from pyminer_comm.base import DataDesc
from pyminer_comm import get_var_names, get_var
diff --git a/pyminer/packages/jupyter_notebook_support/main.py b/pyminer/packages/jupyter_notebook_support/main.py
index 989d493363fcb4614885764eb9fd2f03e2d1c241..99cd13c31ac9c18e5ae149648a885515dbe425b2 100644
--- a/pyminer/packages/jupyter_notebook_support/main.py
+++ b/pyminer/packages/jupyter_notebook_support/main.py
@@ -13,12 +13,12 @@ from PySide2.QtWidgets import QVBoxLayout, QTextEdit, QPlainTextEdit, QTableWidg
QApplication, QLabel, QHeaderView, QMenu, QAction
from pyminer_comm.base import DataDesc
-from pmgwidgets import create_icon, QDialog
+from widgets import create_icon, QDialog
import socket
from contextlib import closing
logger = logging.getLogger(__name__)
-from features.extensions.extensionlib import BaseExtension, BaseInterface
+from lib.extensions.extensionlib import BaseExtension, BaseInterface
from .ipython_data_show import IPythonDataShow
file_name = os.path.join(os.path.dirname(__file__), 'translations', 'qt_{0}.qm'.format(QLocale.system().name()))
diff --git a/pyminer/packages/pm_calc/fastui/base.py b/pyminer/packages/pm_calc/fastui/base.py
index 9afa0d14f63c22a5bcf06799f4540e735700bd4d..82e60ee6e179783f5f0623924ed1c68fa8c53650 100644
--- a/pyminer/packages/pm_calc/fastui/base.py
+++ b/pyminer/packages/pm_calc/fastui/base.py
@@ -39,7 +39,7 @@ from PySide2.QtCore import Qt, QCoreApplication, Signal
from PySide2.QtWidgets import QDialog, QVBoxLayout, QApplication, QPushButton, QComboBox, QHBoxLayout, QLabel, \
QTextBrowser
-from pmgwidgets import PMGPanel, PMGOneShotThreadRunner, center_window
+from widgets import PMGPanel, PMGOneShotThreadRunner, center_window
from pyminer_comm import get_var_names, run_command, call_interface
from utils import input_identifier, bind_combo_with_workspace
diff --git a/pyminer/packages/pm_calc/fastui/create_random_variable.py b/pyminer/packages/pm_calc/fastui/create_random_variable.py
index cc24473e87e16411697dd913bd3353419c5d5a34..bbb9142c965268c2adf55eea631c09e94e6ec23d 100644
--- a/pyminer/packages/pm_calc/fastui/create_random_variable.py
+++ b/pyminer/packages/pm_calc/fastui/create_random_variable.py
@@ -10,7 +10,7 @@ import os
from PySide2.QtWidgets import QApplication
-from pmgwidgets import PMGPanel
+from widgets import PMGPanel
from pyminer_comm import get_var_names
if not __name__ == '__main__':
diff --git a/pyminer/packages/pm_calc/fastui/create_tensor.py b/pyminer/packages/pm_calc/fastui/create_tensor.py
index cacf6019c6a66fe17f3f64023fe02162f5cf2029..3fc1cfc364b2a043bfa986e415ba347773bbfdd7 100644
--- a/pyminer/packages/pm_calc/fastui/create_tensor.py
+++ b/pyminer/packages/pm_calc/fastui/create_tensor.py
@@ -2,7 +2,7 @@ import os
from PySide2.QtWidgets import QApplication, QLabel
-from pmgwidgets import PMGPanel
+from widgets import PMGPanel
from pyminer_comm import get_var_names
if not __name__ == '__main__':
diff --git a/pyminer/packages/pm_calc/fastui/create_vector.py b/pyminer/packages/pm_calc/fastui/create_vector.py
index bcbb3bebbfb04e1bedb4507003637d3b37b689cd..7717b3881d94c85f85733a34aae33ffcaace1c10 100644
--- a/pyminer/packages/pm_calc/fastui/create_vector.py
+++ b/pyminer/packages/pm_calc/fastui/create_vector.py
@@ -1,6 +1,6 @@
from PySide2.QtWidgets import QApplication
-from pmgwidgets import PMGPanel
+from widgets import PMGPanel
from pyminer_comm import get_var_names
if not __name__ == '__main__':
diff --git a/pyminer/packages/pm_calc/fastui/dblquad.py b/pyminer/packages/pm_calc/fastui/dblquad.py
index 853c7391b4f0c79078466e7f16859f133c696e66..695e29fb6202f1593c43932150e423f79d9548b3 100644
--- a/pyminer/packages/pm_calc/fastui/dblquad.py
+++ b/pyminer/packages/pm_calc/fastui/dblquad.py
@@ -3,7 +3,7 @@ from typing import Any, List, Tuple
from PySide2.QtWidgets import QApplication
-from pmgwidgets import FunctionGUIDialog
+from widgets import FunctionGUIDialog
class CodeVisitor(ast.NodeVisitor):
diff --git a/pyminer/packages/pm_calc/fastui/reshape_tensor.py b/pyminer/packages/pm_calc/fastui/reshape_tensor.py
index c7cb8cf1dac6366ae66ef564e5e6355987e52602..84c60c77d202db988bd042295e738b0774275bcd 100644
--- a/pyminer/packages/pm_calc/fastui/reshape_tensor.py
+++ b/pyminer/packages/pm_calc/fastui/reshape_tensor.py
@@ -2,7 +2,7 @@ import os
from PySide2.QtWidgets import QApplication, QLabel
-from pmgwidgets import PMGPanel
+from widgets import PMGPanel
from pyminer_comm import get_var_names
from utils import bind_panel_combo_ctrl_with_workspace
diff --git a/pyminer/packages/pm_calc/main.py b/pyminer/packages/pm_calc/main.py
index d467fdf960326dc15e60e956409e40a68567ff13..17ef652b103ec848b0b180e9a5b2cfd329e65a81 100644
--- a/pyminer/packages/pm_calc/main.py
+++ b/pyminer/packages/pm_calc/main.py
@@ -6,12 +6,12 @@ from PySide2.QtCore import QTranslator, QLocale, Qt
from PySide2.QtCore import Signal
from PySide2.QtWidgets import QApplication, QDialog
-from features.extensions.extensionlib import BaseExtension, BaseInterface
-from pmgwidgets import PMGToolBar, create_icon, Dict
+from lib.extensions.extensionlib import BaseExtension, BaseInterface
+from widgets import PMGToolBar, create_icon, Dict
logger = logging.getLogger(__name__)
if TYPE_CHECKING:
- from features.extensions.extensionlib import extension_lib
+ from lib.extensions.extensionlib import extension_lib
from .fastui.matrix_calc import MatrixCalcDialog
from .fastui.matrix_inv import MatrixInvDialog
from .fastui.equation_solve import LinerEquationSolveDialog
diff --git a/pyminer/packages/pm_modelling/main.py b/pyminer/packages/pm_modelling/main.py
index a2a1e743dd13bc478e7aa8879917fb72927c729b..0d1a7d1d02a9827bdb23b49d15d3c0d40288885e 100644
--- a/pyminer/packages/pm_modelling/main.py
+++ b/pyminer/packages/pm_modelling/main.py
@@ -6,12 +6,12 @@ from PySide2.QtCore import QTranslator, QLocale
from PySide2.QtWidgets import QApplication
from PySide2.QtCore import Signal
-from pmgwidgets import PMGToolBar, create_icon
-from features.extensions.extensionlib import BaseExtension, BaseInterface
+from widgets import PMGToolBar, create_icon
+from lib.extensions.extensionlib import BaseExtension, BaseInterface
logger = logging.getLogger(__name__)
if TYPE_CHECKING:
- from features.extensions.extensionlib import extension_lib
+ from lib.extensions.extensionlib import extension_lib
file_name = os.path.join(os.path.dirname(__file__), 'translations', 'qt_{0}.qm'.format(QLocale.system().name()))
app = QApplication.instance()
@@ -67,7 +67,7 @@ class PMModellingToolBar(PMGToolBar):
"""
return
btn = self.get_control_widget('button_show_more_plots')
- panel: 'pmgwidgets.TopLevelWidget' = self.get_control_widget(
+ panel: 'widgets.TopLevelWidget' = self.get_control_widget(
'drawing_selection_panel')
width = self.get_control_widget('button_list').width()
panel.set_width(width)
diff --git a/pyminer/packages/pm_preprocess/fastui/base.py b/pyminer/packages/pm_preprocess/fastui/base.py
index b161210f57305f05695083711833e147c634ceff..0b1b79c7684a1be4c73a64452f4b833ba1431e45 100644
--- a/pyminer/packages/pm_preprocess/fastui/base.py
+++ b/pyminer/packages/pm_preprocess/fastui/base.py
@@ -34,7 +34,7 @@ from abc import abstractmethod
from typing import List
from PySide2.QtCore import Qt, QCoreApplication, Signal
-from pmgwidgets import PMGPanel, PMGOneShotThreadRunner
+from widgets import PMGPanel, PMGOneShotThreadRunner
from pyminer_comm import get_var_names, run_command, call_interface
from utils import input_identifier, bind_combo_with_workspace
from PySide2.QtWidgets import QDialog, QVBoxLayout, QApplication, QPushButton, QComboBox, QHBoxLayout, QLabel, \
diff --git a/pyminer/packages/pm_preprocess/fastui/datamerge.py b/pyminer/packages/pm_preprocess/fastui/datamerge.py
index 24f89380415b49ba66ad7385e1ddfa32a7cdfe6c..e812b5a35e6aec2107ddcc9c898513954d4389a8 100644
--- a/pyminer/packages/pm_preprocess/fastui/datamerge.py
+++ b/pyminer/packages/pm_preprocess/fastui/datamerge.py
@@ -13,7 +13,7 @@
from typing import List
-from pmgwidgets import PMGPanelDialog, PMGPanel, PMGOneShotThreadRunner
+from widgets import PMGPanelDialog, PMGPanel, PMGOneShotThreadRunner
from pyminer_comm.base import is_pyminer_service_started
from pyminer_comm import get_var_names, get_var, set_var, run_command, call_interface
from utils import VariableSelect, input_identifier
diff --git a/pyminer/packages/pm_preprocess/fastui/fillna.py b/pyminer/packages/pm_preprocess/fastui/fillna.py
index f5b475dd6b76e1f98dba890fba5e1383c4c6c8bc..a04bb190009264f29f526b04945e1f8f52bb32ec 100644
--- a/pyminer/packages/pm_preprocess/fastui/fillna.py
+++ b/pyminer/packages/pm_preprocess/fastui/fillna.py
@@ -20,7 +20,7 @@
from typing import List
-from pmgwidgets import PMGPanelDialog, PMGPanel
+from widgets import PMGPanelDialog, PMGPanel
from pyminer_comm.base import is_pyminer_service_started
from pyminer_comm import get_var_names, get_var, set_var, run_command
from utils import VariableSelect, input_identifier
diff --git a/pyminer/packages/pm_preprocess/fastui/pivot.py b/pyminer/packages/pm_preprocess/fastui/pivot.py
index 6583dc48a00609a8d66c0e75cddb61cdabf687a6..08df8e660c15249515066a715dc1fc986d36b8ab 100644
--- a/pyminer/packages/pm_preprocess/fastui/pivot.py
+++ b/pyminer/packages/pm_preprocess/fastui/pivot.py
@@ -1,6 +1,6 @@
from PySide2.QtWidgets import QApplication
-from pmgwidgets import FunctionGUIDialog
+from widgets import FunctionGUIDialog
dic = {
"title": "数据透视",
diff --git a/pyminer/packages/pm_preprocess/fastui/templates/dropna.py b/pyminer/packages/pm_preprocess/fastui/templates/dropna.py
index 0b45f870dfbd773564d2a599d4b857fe42f09fa5..228dd36e84c7c56a0adeeea72d250874c6a4090f 100644
--- a/pyminer/packages/pm_preprocess/fastui/templates/dropna.py
+++ b/pyminer/packages/pm_preprocess/fastui/templates/dropna.py
@@ -1,7 +1,7 @@
from typing import List
-from pmgwidgets import PMGPanelDialog, PMGPanel
+from widgets import PMGPanelDialog, PMGPanel
from pyminer_comm.base import is_pyminer_service_started
from pyminer_comm import get_var_names, get_var, set_var, run_command
from utils import VariableSelect, input_identifier
diff --git a/pyminer/packages/pm_preprocess/fastui/templates/template.py b/pyminer/packages/pm_preprocess/fastui/templates/template.py
index 8b9b0a01551ab13460a6eea6f7d0fe437a9445d4..e831993590c4539695b1b2b79fd88b5ce135cb6c 100644
--- a/pyminer/packages/pm_preprocess/fastui/templates/template.py
+++ b/pyminer/packages/pm_preprocess/fastui/templates/template.py
@@ -12,7 +12,7 @@
code = """
from typing import List
-from pmgwidgets import PMGPanelDialog, PMGPanel
+from widgets import PMGPanelDialog, PMGPanel
from pyminer_comm.base import is_pyminer_service_started
from pyminer_comm import get_var_names, get_var, set_var, run_command
from pmtoolbox import VariableSelect, input_identifier
diff --git a/pyminer/packages/pm_preprocess/main.py b/pyminer/packages/pm_preprocess/main.py
index 9b1827a0ef14630002b83401b073df6cd34ddf0b..21e9fc0fdb71274c1e569e873c36a15496e9fd3c 100644
--- a/pyminer/packages/pm_preprocess/main.py
+++ b/pyminer/packages/pm_preprocess/main.py
@@ -6,12 +6,12 @@ from PySide2.QtCore import QTranslator, QLocale, Qt
from PySide2.QtCore import Signal
from PySide2.QtWidgets import QApplication, QDialog
-from features.extensions.extensionlib import BaseExtension, BaseInterface
-from pmgwidgets import PMGToolBar, create_icon, Dict
+from lib.extensions.extensionlib import BaseExtension, BaseInterface
+from widgets import PMGToolBar, create_icon, Dict
logger = logging.getLogger(__name__)
if TYPE_CHECKING:
- from features.extensions.extensionlib import extension_lib
+ from lib.extensions.extensionlib import extension_lib
from .datareplace import DataReplaceForm
from .datamissingvalue import DataMissingValueForm
diff --git a/pyminer/packages/pm_statistics/main.py b/pyminer/packages/pm_statistics/main.py
index 481679b5e217c742f6c1b067fade65f76d890159..777f8b9d412ac2614d2f4faca6c05d5c42b75b6d 100644
--- a/pyminer/packages/pm_statistics/main.py
+++ b/pyminer/packages/pm_statistics/main.py
@@ -5,15 +5,15 @@ from typing import TYPE_CHECKING, Callable
from PySide2.QtCore import QTranslator, QLocale
from PySide2.QtCore import Signal
from PySide2.QtWidgets import QApplication
-from pmgwidgets import PMGToolBar, create_icon
-from features.extensions.extensionlib import BaseExtension, BaseInterface
+from widgets import PMGToolBar, create_icon
+from lib.extensions.extensionlib import BaseExtension, BaseInterface
from .stat_desc import StatDescForm
logger = logging.getLogger(__name__)
if TYPE_CHECKING:
- from features.extensions.extensionlib import extension_lib
+ from lib.extensions.extensionlib import extension_lib
file_name = os.path.join(os.path.dirname(__file__), 'translations', 'qt_{0}.qm'.format(QLocale.system().name()))
app = QApplication.instance()
diff --git a/pyminer/packages/pmagg/main.py b/pyminer/packages/pmagg/main.py
index b9d797a6400686904aa8ff9f93525f6f9b42ddc1..8ee91c43184a7704c7fc9154d8f79519e8b4922c 100644
--- a/pyminer/packages/pmagg/main.py
+++ b/pyminer/packages/pmagg/main.py
@@ -7,7 +7,7 @@
import os
import logging
logger = logging.getLogger('pmagg')
-from features.extensions.extensionlib import BaseExtension, BaseInterface
+from lib.extensions.extensionlib import BaseExtension, BaseInterface
import configparser
class Extension(BaseExtension):
diff --git a/pyminer/packages/qt_vditor/main.py b/pyminer/packages/qt_vditor/main.py
index b630e7174ac001e155426d2361e5b31632fc2008..533296c26d4847b90f90f51cef831d88d763afed 100644
--- a/pyminer/packages/qt_vditor/main.py
+++ b/pyminer/packages/qt_vditor/main.py
@@ -4,7 +4,7 @@
# @FileName: main.py
import logging
logger = logging.getLogger('qt_vditor')
-from features.extensions.extensionlib import BaseExtension, BaseInterface
+from lib.extensions.extensionlib import BaseExtension, BaseInterface
from pmlocalserver.server import server
from .route import qt_vditor
diff --git a/pyminer/packages/setting_manager/main.py b/pyminer/packages/setting_manager/main.py
index a3149a3ec50e619f36f41b4c0262bf20a67c4387..0a04a93eabd6fc212a9d62e4a8743b5c4a9c586b 100644
--- a/pyminer/packages/setting_manager/main.py
+++ b/pyminer/packages/setting_manager/main.py
@@ -2,7 +2,7 @@ import logging
from PySide2.QtWidgets import QDialog, QVBoxLayout, QPushButton
-from features.extensions.extensionlib import BaseExtension, BaseInterface
+from lib.extensions.extensionlib import BaseExtension, BaseInterface
from .settings import DataManager
from .ui_inputs import inputs
diff --git a/pyminer/packages/socket_server/index.rst b/pyminer/packages/socket_server/index.rst
index 7bd469d55f0c6cabf1b144625179dade60277cd5..5071e36eacb17763969033e61c29ef3019e5a9fe 100644
--- a/pyminer/packages/socket_server/index.rst
+++ b/pyminer/packages/socket_server/index.rst
@@ -13,7 +13,7 @@ Socket数据服务器
:members:
:undoc-members:
-数据服务器基于 ``PMGWidgets`` 里面的 ``server`` ,是一个简单的基于 ``PyQt`` 的连接。
+数据服务器基于 ``Widgets`` 里面的 ``server`` ,是一个简单的基于 ``PyQt`` 的连接。
目前的方法:
@@ -28,7 +28,7 @@ Socket数据服务器
**[TODO]:之后还需要增加端口选择的功能!**
-服务器的主代码定义在插件中,当然还有一个服务器基类,定义在pmgwidgets里面。大致的工作原理是:
+服务器的主代码定义在插件中,当然还有一个服务器基类,定义在widgets里面。大致的工作原理是:
#. 服务器启动,用一个QThread开始监听套接字。
**为什么不用Threading?盖因Qt的界面与其不兼容。使用起来可能导致程序崩溃。**
@@ -52,7 +52,7 @@ Socket数据服务器
-对于心跳包的接收,定义在pmgwidgets里面。如果是普通的程序,可以用GeneralClient;
+对于心跳包的接收,定义在widgets里面。如果是普通的程序,可以用GeneralClient;
(基于Threading)如果是想要嵌入其他PyQt程序,就用PMClient(基于QThread)。
diff --git a/pyminer/packages/socket_server/main.py b/pyminer/packages/socket_server/main.py
index bad80c712b212272c5af1cd7a2775a206d832c57..7c41768f9d2299177f207bd4f9e1c4c5c596874c 100644
--- a/pyminer/packages/socket_server/main.py
+++ b/pyminer/packages/socket_server/main.py
@@ -5,7 +5,7 @@ from typing import Callable
import flask.cli
from flask import Flask
-from features.extensions.extensionlib import BaseExtension, BaseInterface
+from lib.extensions.extensionlib import BaseExtension, BaseInterface
from .server_by_socket import run, app
# 不显示flask的启动日志
diff --git a/pyminer/packages/workspace_inspector/data_viewer.py b/pyminer/packages/workspace_inspector/data_viewer.py
index 2e7df453ab19d0f114318cd5d08aa115ef95311a..b12cd9af68ac301d6a650057f263ebde277cbb4a 100644
--- a/pyminer/packages/workspace_inspector/data_viewer.py
+++ b/pyminer/packages/workspace_inspector/data_viewer.py
@@ -5,10 +5,10 @@ from typing import Dict, TYPE_CHECKING
from pyminer_comm import get_var, set_var
from PySide2.QtWidgets import QTabWidget, QTextBrowser, QWidget, QMessageBox
-from pmgwidgets import PMTableView, PMGTableWidget, PMDockObject, PMGTableViewer, PMGJsonTree
+from widgets import PMTableView, PMGTableWidget, PMDockObject, PMGTableViewer, PMGJsonTree
if TYPE_CHECKING:
- from features.extensions.extensionlib.extension_lib import extension_lib
+ from lib.extensions.extensionlib.extension_lib import extension_lib
class AbstractViewer(object):
diff --git a/pyminer/packages/workspace_inspector/inspectortable.py b/pyminer/packages/workspace_inspector/inspectortable.py
index f350a8ae013a08d4e2a9566e90383533992d78ce..81ecd098ccdf6572b37b87f42c3d8826084bede0 100644
--- a/pyminer/packages/workspace_inspector/inspectortable.py
+++ b/pyminer/packages/workspace_inspector/inspectortable.py
@@ -8,10 +8,10 @@ from PySide2.QtWidgets import QWidget, QVBoxLayout, QMenu, \
QMessageBox, QFileDialog, QInputDialog, QApplication, QTableWidget, QTableWidgetItem, QHeaderView, QAbstractItemView
from PySide2.QtCore import Signal, Qt, QLocale, QModelIndex
-from pmgwidgets import PMDockObject, in_unit_test, create_translator
+from widgets import PMDockObject, in_unit_test, create_translator
from utils import load_variable_pmd, load_variable_pkl, save_variable_pkl, save_variable_pmd, save_variable_table, \
save_variable_matrix
-from features.extensions.extensionlib import BaseExtension, BaseInterface
+from lib.extensions.extensionlib import BaseExtension, BaseInterface
from pyminer_comm.base import DataDesc
from pyminer_comm import get_var, get_var_names, set_var, del_var, set_vars, get_vars
diff --git a/pyminer/packages/workspace_inspector/main.py b/pyminer/packages/workspace_inspector/main.py
index 65a70647e2b785011b865f5500bf22a72de90729..d258b9b5544f3b7ffddae37ed9d8178619736d58 100644
--- a/pyminer/packages/workspace_inspector/main.py
+++ b/pyminer/packages/workspace_inspector/main.py
@@ -6,7 +6,7 @@ from PySide2.QtWidgets import QTreeWidget, QTreeWidgetItem, QWidget, QVBoxLayout
QMessageBox, QFileDialog, QInputDialog, QApplication
from PySide2.QtCore import Signal, Qt, QLocale, QTranslator
-from features.extensions.extensionlib import BaseExtension, BaseInterface
+from lib.extensions.extensionlib import BaseExtension, BaseInterface
if TYPE_CHECKING:
from .data_viewer import PMVariableViewerWidget
diff --git a/pyminer/pmgui.py b/pyminer/pmgui.py
index 7e425da5bab7aad32aa82b6cdb63c38713d00081..768312ae444f01626299e5ea9c026e7827100a04 100644
--- a/pyminer/pmgui.py
+++ b/pyminer/pmgui.py
@@ -1,7 +1,7 @@
from PySide2.QtCore import Qt
from PySide2.QtWidgets import QTextEdit
-from pmgwidgets import PMGToolBar, ActionWithMessage, PMDockObject, create_icon
+from widgets import PMGToolBar, ActionWithMessage, PMDockObject, create_icon
from utils import get_main_window, get_application
diff --git a/pyminer/update_translation.py b/pyminer/update_translation.py
index 696fbc8872ed9a9a8c7ef68063d5c08e6cf1f577..35f37209966f06964757c26372991190efc80afd 100644
--- a/pyminer/update_translation.py
+++ b/pyminer/update_translation.py
@@ -29,7 +29,7 @@ def update_ts(packages_path):
os.system(cmd)
-folders = ["pmgwidgets", "pmtoolbox"]
+folders = ["widgets", "pmtoolbox"]
for folder in folders:
update_ts(os.path.join(os.path.dirname(__file__), folder))
diff --git a/pyminer/utils/io/fileutil/variableutils.py b/pyminer/utils/io/fileutil/variableutils.py
index d692a795e71a68260f904a1fd0f1b4b5b5982535..cbe5c1864fe057c250c3d347ae0340bf94a31b84 100644
--- a/pyminer/utils/io/fileutil/variableutils.py
+++ b/pyminer/utils/io/fileutil/variableutils.py
@@ -31,7 +31,7 @@ def save_variable_pmd(var_names: Union[List[str], Tuple[str], str], var_value: U
:param file_path: 有效路径
:return:
"""
- from pmgwidgets import iter_isinstance
+ from widgets import iter_isinstance
try:
if isinstance(var_names, str):
var_names = [var_names]
diff --git a/pyminer/utils/io/pmserial/pmqtreadserial.py b/pyminer/utils/io/pmserial/pmqtreadserial.py
index a7a8ad59cfdc9543f6fde90ce36ac119d4ca0923..ef2cde1dee536269e70f03d68aa06b9484fde922 100644
--- a/pyminer/utils/io/pmserial/pmqtreadserial.py
+++ b/pyminer/utils/io/pmserial/pmqtreadserial.py
@@ -8,7 +8,7 @@ from PySide2.QtWidgets import QApplication, QMainWindow, QTextEdit
from PySide2.QtCore import QObject, QThread, Signal
import packages.code_editor.utils.utils
-from pmgwidgets import PMQThreadObject, QTextCursor
+from widgets import PMQThreadObject, QTextCursor
from utils.io.pmserial.readserial import get_all_serial_names
diff --git a/pyminer/utils/ui/app/pmbasicapp.py b/pyminer/utils/ui/app/pmbasicapp.py
index e3c852eb9955df782f472d41cc06a484db8ee939..c7c9a89b1d7e57441c34f80fc921fdb05e8dd852 100644
--- a/pyminer/utils/ui/app/pmbasicapp.py
+++ b/pyminer/utils/ui/app/pmbasicapp.py
@@ -1,11 +1,11 @@
from typing import Dict, Any
import time
t0 = time.time()
-from pmgwidgets import BaseClient
+from widgets import BaseClient
-from pmgwidgets.widgets.composited.buttonpanel import PMGButtonPanel
+from widgets.widgets.composited.buttonpanel import PMGButtonPanel
-from pmgwidgets.widgets.composited.generalpanel import PMGPanel, PANEL_VIEW_CLASS
+from widgets.widgets.composited.generalpanel import PMGPanel, PANEL_VIEW_CLASS
from utils.ui.variableselect import VariableSelect
from PySide2.QtWidgets import QWidget, QApplication, QGridLayout
t5 = time.time()
diff --git a/pyminer/utils/ui/uiutil/workspaceutil.py b/pyminer/utils/ui/uiutil/workspaceutil.py
index 2761cd643e1652952b999ffd602ced7e9035da99..4fd4e4d6f866e536371c5aae4081bf8ce1203889 100644
--- a/pyminer/utils/ui/uiutil/workspaceutil.py
+++ b/pyminer/utils/ui/uiutil/workspaceutil.py
@@ -84,7 +84,7 @@ def bind_panel_combo_ctrl_with_workspace(combo_ctrl: "PMGComboCtrl", type_filter
:return:
"""
from pyminer_comm import get_data_descs
- from pmgwidgets import PMGComboCtrl
+ from widgets import PMGComboCtrl
assert isinstance(combo_ctrl, PMGComboCtrl), combo_ctrl
combo = combo_ctrl.check
if type_filter == "":
diff --git a/pyminer/pmgwidgets/__init__.py b/pyminer/widgets/__init__.py
similarity index 71%
rename from pyminer/pmgwidgets/__init__.py
rename to pyminer/widgets/__init__.py
index ee3674acebf67176ac0570431fab8ee42ec1b3ab..f98dbcac1b86d0cdfc58c00b3722e3b6bfb7490a 100644
--- a/pyminer/pmgwidgets/__init__.py
+++ b/pyminer/widgets/__init__.py
@@ -3,7 +3,7 @@ import time
t0 = time.time()
import logging
-logger = logging.getLogger('pmgwidgets_init')
+logger = logging.getLogger('widgets_init')
pmgprint = lambda *x: print(*x)
from .elements import *
@@ -13,5 +13,5 @@ from .flowchart import *
from .get_time_consuming_classes import get_ipython_console_class
t1 = time.time()
-logger.warning('pmgwidgets loading time: %f s' % (t1 - t0))
+logger.warning('widgets loading time: %f s' % (t1 - t0))
diff --git a/pyminer/widgets/display/__init__.py b/pyminer/widgets/display/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..6b679ffb67cf600d733f6ab2860be036a6b9cba9
--- /dev/null
+++ b/pyminer/widgets/display/__init__.py
@@ -0,0 +1,10 @@
+hint = 'However if your program needn\'t this package,this warning is neglectable.'
+try:
+ from .matplotlib.qt5agg import PMMatplotlibQt5Widget
+except ModuleNotFoundError:
+ import warnings
+
+ warnings.warn('matplotlib is not installed. ' + hint)
+ pass
+
+from .dynamicgraph import *
diff --git a/pyminer/widgets/display/browser/__init__.py b/pyminer/widgets/display/browser/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/widgets/display/browser/browser.py b/pyminer/widgets/display/browser/browser.py
new file mode 100644
index 0000000000000000000000000000000000000000..b29be159ebbdee4445df2d5693521b65e0480029
--- /dev/null
+++ b/pyminer/widgets/display/browser/browser.py
@@ -0,0 +1,82 @@
+"""
+代码来源:
+https://www.cnblogs.com/taostaryu/p/9772492.html
+
+"""
+import sys
+from PySide2.QtWidgets import QMainWindow, QApplication, QWidget, QVBoxLayout, QToolBar, QLineEdit
+from PySide2.QtCore import QUrl, Signal
+from PySide2.QtWebEngineWidgets import QWebEngineView
+
+
+class PMGWebBrowser(QWidget):
+ def __init__(self, parent=None, toolbar='standard'):
+ """
+
+ :param parent:
+ :param toolbar:多种选项:‘no’,‘standard’,'no_url_input'
+ """
+ super().__init__(parent)
+ self.webview = PMGWebEngineView()
+ self.webview.load(QUrl("https://cn.bing.com"))
+ self.setLayout(QVBoxLayout())
+ self.toolbar = QToolBar()
+ self.url_input = QLineEdit()
+ # self.url_input.setText('https://cn.bing.com')
+ # self.load_url()
+ self.toolbar.addWidget(self.url_input)
+ self.toolbar.addAction('go').triggered.connect(lambda b: self.load_url())
+ self.toolbar.addAction('back').triggered.connect(self.webview.back)
+ self.toolbar.addAction('forward').triggered.connect(self.webview.forward)
+ self.layout().addWidget(self.toolbar)
+ if toolbar == 'no':
+ self.toolbar.hide()
+ elif toolbar == 'no_url_input':
+ self.url_input.hide()
+
+ self.layout().addWidget(self.webview)
+ self.setWindowTitle('My Browser')
+ self.showMaximized()
+
+ # command:>
+ # /home/hzy/Documents/Developing/Python/pyminer_dist_debian_deepin/python/bin/python3.8 -m jupyter notebook --port 5000 --no-browser --ip='*' --NotebookApp.token=''
+ # --NotebookApp.password='' c:\users\12957\
+
+ # self.webview.load(QUrl("http://127.0.0.1:5000/notebooks/desktop/Untitled.ipynb")) # 直接请求页面。
+ # self.webview.load(QUrl("E:\Python\pyminer_bin\PyMiner\bin\widgets\display\browser\show_formula.html")) # 直接请求页面。
+ # self.setCentralWidget(self.webview)
+
+ def load_url(self, url: str = ''):
+ if url == '':
+ url = self.url_input.text().strip()
+ # print('',url)
+ else:
+ self.url_input.setText(url)
+ self.webview.load(QUrl(url))
+
+
+class PMGWebEngineView(QWebEngineView):
+ windowList = []
+ signal_new_window_created = Signal(PMGWebBrowser)
+
+ # 重写createwindow()
+ def createWindow(self, QWebEnginePage_WebWindowType):
+ # new_webview = WebEngineView()
+ new_window = PMGWebBrowser()
+ # new_window.setCentralWidget(new_webview)
+ # new_window.show()
+ self.windowList.append(new_window) # 注:没有这句会崩溃!!!
+ self.signal_new_window_created.emit(new_window)
+ return new_window.webview
+
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+
+ w = PMGWebBrowser()
+ w.show()
+ w2 = PMGWebBrowser()
+ w2.show()
+ w3 = PMGWebBrowser()
+ w3.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/display/browser/get_ipy.py b/pyminer/widgets/display/browser/get_ipy.py
new file mode 100644
index 0000000000000000000000000000000000000000..06c7e60443d25fe2319b417bace1274eff032e9d
--- /dev/null
+++ b/pyminer/widgets/display/browser/get_ipy.py
@@ -0,0 +1,48 @@
+import json
+from sys import getsizeof
+
+from IPython import get_ipython
+from IPython.core.magics.namespace import NamespaceMagics
+
+_nms = NamespaceMagics()
+_Jupyter = get_ipython()
+_nms.shell = _Jupyter.kernel.shell
+
+try:
+ import numpy as np # noqa: F401
+except ImportError:
+ pass
+
+
+def _getsizeof(x):
+ # return the size of variable x. Amended version of sys.getsizeof
+ # which also supports ndarray, Series and DataFrame
+ if type(x).__name__ in ['ndarray', 'Series']:
+ return x.nbytes
+ elif type(x).__name__ == 'DataFrame':
+ return x.memory_usage().sum()
+ else:
+ return getsizeof(x)
+
+
+def _getshapeof(x):
+ # returns the shape of x if it has one
+ # returns None otherwise - might want to return an empty string for an empty collum
+ try:
+ return x.shape
+ except AttributeError: # x does not have a shape
+ return None
+
+
+def var_dic_list():
+ types_to_exclude = ['module', 'function', 'builtin_function_or_method',
+ 'instance', '_Feature', 'type', 'ufunc']
+ values = _nms.who_ls()
+ vardic = [{'varName': v, 'varType': type(eval(v)).__name__, 'varSize': str(_getsizeof(eval(v))), 'varShape': str(_getshapeof(eval(v))) if _getshapeof(eval(v)) else '', 'varContent': str(eval(v))[:200]} # noqa
+
+ for v in values if (v not in ['_html', '_nms', 'NamespaceMagics', '_Jupyter']) & (type(eval(v)).__name__ not in types_to_exclude)] # noqa
+ return json.dumps(vardic)
+
+if __name__=='__main__':
+ # command to refresh the list of variables
+ print(var_dic_list())
diff --git a/pyminer/widgets/display/browser/handler.py b/pyminer/widgets/display/browser/handler.py
new file mode 100644
index 0000000000000000000000000000000000000000..14cf330ddea4c9a0a480ef6694c2709e8f372d0e
--- /dev/null
+++ b/pyminer/widgets/display/browser/handler.py
@@ -0,0 +1,109 @@
+# Copyright (c) Jupyter Development Team, Spyder Project Contributors.
+# Distributed under the terms of the Modified BSD License.
+
+"""Entry point for server rendering notebooks for Spyder."""
+
+import os
+from jinja2 import FileSystemLoader
+from notebook.base.handlers import IPythonHandler, FileFindHandler
+from notebook.notebookapp import flags, NotebookApp
+from notebook.utils import url_path_join as ujoin
+from traitlets import Bool
+from notebook.services.kernels.kernelmanager import MappingKernelManager
+from jupyter_client.ioloop.manager import IOLoopKernelManager
+
+HERE = os.path.dirname(__file__)
+
+flags['dark'] = (
+ {'SpyderNotebookServer': {'dark_theme': True}},
+ 'Use dark theme when rendering notebooks'
+)
+
+
+class NotebookHandler(IPythonHandler):
+ """
+ Serve a notebook file from the filesystem in the notebook interface
+ """
+
+ def get(self):
+ """Get the main page for the application's interface."""
+ # Options set here can be read with PageConfig.getOption
+ # config_data = {
+ # # Use camelCase here, since that's what the lab components expect
+ # 'baseUrl': self.base_url,
+ # 'token': self.settings['token'],
+ # 'darkTheme': self.settings['dark_theme'],
+ # 'notebookPath': filename,
+ # 'frontendUrl': ujoin(self.base_url, 'static/'),
+ # # FIXME: Don't use a CDN here
+ # 'mathjaxUrl': 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/'
+ # '2.7.5/MathJax.js',
+ # 'mathjaxConfig': "TeX-AMS_CHTML-full,Safe"
+ # }
+ # return self.write(
+ # self.render_template(
+ # 'index.html',
+ # static=self.static_url,
+ # base_url=self.base_url,
+ # config_data=config_data
+ # )
+ # )
+ # self.session.send(stream, msg)
+ mgr: MappingKernelManager = self.kernel_manager
+ print(mgr.list_kernels())
+
+ for k in mgr._kernels:
+ ioloopmanager: IOLoopKernelManager = mgr._kernels[k]
+ print('AAAAAAAAAAAAA', ioloopmanager.kernel)
+ return self.write("aaaaaaaaaa")
+ # def get_templaer.load(self.settings['jinja2_ete(self, name):
+ # # loader = FileSystemLoader(HERE)
+ # # return loadnv'], name)
+
+
+class SpyderNotebookServer(NotebookApp):
+ """Server rendering notebooks in HTML and serving them over HTTP."""
+
+ flags = flags
+
+ dark_theme = Bool(
+ False, config=True,
+ help='Whether to use dark theme when rendering notebooks')
+
+ def init_webapp(self):
+ """Initialize tornado webapp and httpserver."""
+ self.tornado_settings['dark_theme'] = self.dark_theme
+
+ super().init_webapp()
+ print(self.base_url)
+ url = ujoin(self.base_url, r'/notebook/(.*)')
+ print(url)
+
+ default_handlers = [
+ (r"/pyminers", NotebookHandler),
+ # (ujoin(self.base_url, r"/pyminer/(.*)"), FileFindHandler,
+ # {'path': os.path.join(HERE, 'build')})
+ ]
+
+ self.web_app.add_handlers('.*', default_handlers)
+ # self.web_app.find_handler()
+
+def run():
+ import time
+ from IPython import get_ipython
+
+ while (1):
+ time.sleep(2)
+ _Jupyter = get_ipython()
+ print(_Jupyter)
+
+
+if __name__ == '__main__':
+ # import threading
+
+ # th = threading.Thread(target=run)
+ # th.setDaemon(True)
+ # th.start()
+
+ SpyderNotebookServer.launch_instance(
+ argv=['--port', '5000', '--no-browser', '--ip=\'*\'', '--NotebookApp.token=\'\''])
diff --git a/pyminer/widgets/display/dynamicgraph/__init__.py b/pyminer/widgets/display/dynamicgraph/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/widgets/display/dynamicgraph/base/__init__.py b/pyminer/widgets/display/dynamicgraph/base/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/widgets/display/dynamicgraph/base/basetimeseries.py b/pyminer/widgets/display/dynamicgraph/base/basetimeseries.py
new file mode 100644
index 0000000000000000000000000000000000000000..401cc33074dc201e279661f7a8d56233ba8ae3c6
--- /dev/null
+++ b/pyminer/widgets/display/dynamicgraph/base/basetimeseries.py
@@ -0,0 +1,210 @@
+import logging
+import sys
+import time
+
+from PySide2.QtCore import QTimer
+from PySide2.QtWidgets import QWidget, QApplication, QVBoxLayout, QPushButton
+import numpy as np
+from typing import List, Dict, Tuple
+from widgets.display.matplotlib.qt5agg import PMMatplotlibQt5Widget
+
+logger = logging.getLogger('display.basetimeseries')
+
+
+class PMBaseTimeSeriesWidget(QWidget):
+ def __init__(self, parent: 'QWidget' = None, recent_samples: int = 100, show_mode: str = '',
+ max_samples: int = 10000, sample_type: np.dtype = np.float):
+ """
+ 运用循环队进行数据刷新
+ :param parent:
+ :param recent_samples:
+ :param show_mode: "recently":最近的 recent_points个点,"all":显示全部
+ :param max_samples:最多采样数目。如果采样数量太多,就舍弃前面的数据。
+ """
+ super().__init__(parent=parent)
+ self.recent_sample_num = recent_samples
+ self.max_samples = max_samples
+ self.buffer_length = int(max_samples * 1.5)
+ self.bottom_pointers: Dict[str, int] = {}
+ self.top_pointers: Dict[str, int] = {}
+ self.time: Dict[str, np.ndarray] = {} # np.zeros(self.buffer_length, dtype=np.float)
+ self.data: Dict[str, np.ndarray] = {}
+ self.last_plot_time: float = 0
+ # self.init_plot()
+
+ def add_data(self, data_name, data_type):
+ self.data[data_name] = np.zeros(self.buffer_length, dtype=data_type)
+ self.time[data_name] = np.zeros(self.buffer_length, dtype=np.float)
+ self.top_pointers[data_name] = 0
+ self.bottom_pointers[data_name] = 0
+
+ def add_sample(self, data_name: str, value: float, sample_time: float = -1):
+ """
+ 增加一个采样点
+ :param data_name:
+ :param value:
+ :return:
+ """
+ if sample_time < 0:
+ sample_time = time.time()
+ top_pointer = self.top_pointers[data_name]
+
+ if top_pointer == self.buffer_length:
+ self.trim_data(data_name)
+
+ top_pointer = self.top_pointers[data_name]
+ self.data[data_name][top_pointer] = value
+ self.time[data_name][top_pointer] = sample_time
+ self.top_pointers[data_name] = self.top_pointers[data_name] + 1
+ self.refresh()
+
+ def trim_data(self, data_name: str):
+ self.data[data_name][:self.max_samples] = self.data[data_name][
+ self.buffer_length - self.max_samples:self.buffer_length]
+ self.data[data_name][self.max_samples:] = np.zeros(self.buffer_length - self.max_samples)
+ self.time[data_name][:self.max_samples] = self.time[data_name][
+ self.buffer_length - self.max_samples:self.buffer_length]
+ self.time[data_name][self.max_samples:] = np.zeros(self.buffer_length - self.max_samples)
+ self.top_pointers[data_name] = self.max_samples
+ # self.bottom_pointers[data_name] = self.max_samples
+
+ def on_length_exceed(self):
+ pass
+
+ def get_time_series(self, data_name: str) -> Tuple[np.ndarray, np.ndarray]:
+ data = self.data.get(data_name)
+ sample_time = self.time.get(data_name)
+ assert data is not None, 'no data named %s' % data_name
+ if self.top_pointers[data_name] - self.max_samples < 0:
+ return sample_time[:self.top_pointers[data_name]], data[:self.top_pointers[data_name]]
+ return sample_time[self.top_pointers[data_name] - self.max_samples:self.top_pointers[data_name]], \
+ data[self.top_pointers[data_name] - self.max_samples:self.top_pointers[data_name]]
+
+ def get_recent_data(self, data_name: str) -> Tuple[np.ndarray, np.ndarray]:
+ data = self.data.get(data_name)
+ sample_time = self.time.get(data_name)
+ assert data is not None, 'no data named %s' % data_name
+ if self.top_pointers[data_name] - self.recent_sample_num < 0:
+ return sample_time[:self.top_pointers[data_name]], \
+ data[:self.top_pointers[data_name]]
+ return sample_time[self.top_pointers[data_name] - self.recent_sample_num:self.top_pointers[data_name]], \
+ data[self.top_pointers[data_name] - self.recent_sample_num:self.top_pointers[data_name]]
+
+ def create_new_line(self):
+ pass
+
+ def refresh(self):
+ """
+ 'It is suggested that refresh rate should lower than 10 fps for Matplotlib widget.'
+ 刷新帧率通常不能超过每秒钟10帧——而且点数不太多的情况下,
+ :return:
+ """
+ self.clear()
+ for k in self.time.keys():
+ t, y = self.get_recent_data(k)
+ self.plot(t, y)
+
+ def init_plot(self):
+ pass
+
+ def plot(self, t, y):
+ pass
+
+ def clear(self):
+ pass
+
+
+class PMTimeSeriesMPLWidget(PMBaseTimeSeriesWidget):
+ def __init__(self, max_samples: int = 10000, recent_samples: int = 100):
+ super(PMTimeSeriesMPLWidget, self).__init__(max_samples=max_samples, recent_samples=recent_samples)
+
+ self.setLayout(QVBoxLayout())
+ self.plot_widget = PMMatplotlibQt5Widget(self)
+ self.layout().addWidget(self.plot_widget)
+ self.layout().addWidget(QPushButton('aaaa'))
+
+ self.ax = None
+
+ def plot(self, t, y):
+ ax = self.plot_widget.add_subplot(111)
+ ax.plot(t, y, 'r')
+ ax.set_xlabel('test_x_label')
+ self.plot_widget.draw()
+ self.plot_widget.canvas.flush_events()
+
+ def clear(self):
+ self.plot_widget.figure.clf()
+
+
+class PMTimeSeriesPGWidget(PMBaseTimeSeriesWidget):
+ def __init__(self, max_samples: int = 10000, recent_samples: int = 100):
+ super(PMTimeSeriesPGWidget, self).__init__(max_samples=max_samples, recent_samples=recent_samples)
+ import pyqtgraph
+ self.curves: Dict = {}
+ self.setLayout(QVBoxLayout())
+ self.plot_widget = pyqtgraph.plot() # PMMatplotlibQt5Widget(self)
+ self.layout().addWidget(self.plot_widget)
+ self.layout().addWidget(QPushButton('aaaa'))
+
+ self.ax = None
+
+ def add_data(self, data_name, data_type):
+ super().add_data(data_name, data_type)
+ self.curves[data_name] = self.plot_widget.plot()
+
+ # def plot(self, t, y):
+ # self.curves[].setData(y)
+ def refresh(self):
+ for k in self.time.keys():
+ t, y = self.get_recent_data(k)
+ self.curves[k].setData(t, y)
+
+ def clear(self):
+ pass
+
+
+def time_out():
+ t0 = time.time()
+ global i, graphics
+ i += 1
+ graphics.add_sample('a', value=np.random.randint(100))
+ t1 = time.time()
+ logger.debug(i, 'time elapsed :', t1 - t0)
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+
+
+ def mpl():
+ global i, graphics
+ graphics = PMTimeSeriesMPLWidget(max_samples=100, recent_samples=20)
+ graphics.show()
+ i = 0
+
+ graphics.add_data('a', np.float)
+
+ timer = QTimer()
+ timer.start(50)
+ timer.timeout.connect(time_out)
+
+
+ def pg():
+ global i, graphics
+ graphics = PMTimeSeriesPGWidget(max_samples=2000, recent_samples=1000)
+ graphics.show()
+ i = 0
+
+ graphics.add_data('a', np.float)
+
+
+ timer = QTimer()
+
+ timer.timeout.connect(time_out)
+
+ timer.start(1)
+ pg()
+ # timer.start(100)
+ # mpl()
+
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/display/dynamicgraph/mplplots/__init__.py b/pyminer/widgets/display/dynamicgraph/mplplots/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/widgets/display/dynamicgraph/pgexample.py b/pyminer/widgets/display/dynamicgraph/pgexample.py
new file mode 100644
index 0000000000000000000000000000000000000000..fd70829ae1d9bb628e4cddaa4009b7f3994538ba
--- /dev/null
+++ b/pyminer/widgets/display/dynamicgraph/pgexample.py
@@ -0,0 +1,6 @@
+import sys
+
+from pyqtgraph import examples
+
+if __name__ == '__main__':
+ examples.run()
diff --git a/pyminer/widgets/display/dynamicgraph/pgplots/__init__.py b/pyminer/widgets/display/dynamicgraph/pgplots/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/widgets/display/examples.py b/pyminer/widgets/display/examples.py
new file mode 100644
index 0000000000000000000000000000000000000000..ae8d24c99070eb04216ceaa065f7e46f69eba379
--- /dev/null
+++ b/pyminer/widgets/display/examples.py
@@ -0,0 +1,4 @@
+from pyqtgraph import examples
+
+if __name__ == '__main__':
+ examples.run()
diff --git a/pyminer/widgets/display/matplotlib/__init__.py b/pyminer/widgets/display/matplotlib/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/widgets/display/matplotlib/pmagg.py b/pyminer/widgets/display/matplotlib/pmagg.py
new file mode 100644
index 0000000000000000000000000000000000000000..2e665d7f0901d613b6170c2d1e81fb434fcdd4bc
--- /dev/null
+++ b/pyminer/widgets/display/matplotlib/pmagg.py
@@ -0,0 +1,36 @@
+import sys
+from PySide2 import QtWidgets
+import matplotlib.pyplot as plt
+import random
+sys.path.append(r'E:\Python\pyminer_bin\PyMiner\bin\pyminer2\extensions\packages\pmagg')
+import PMAgg
+import os
+
+
+class MainWindow(QtWidgets.QDialog):
+ def __init__(self, figure, config_path):
+ super().__init__()
+ layout = QtWidgets.QVBoxLayout()
+ mpl_app = PMAgg.Window(config_path)
+ # 只需要将mpl绘图产生的figure对象以及一个配置文件.cfg的路径传给PMAgg即可,配置文件可以留空。
+ mpl_app.get_canvas(figure)
+ layout.addWidget(mpl_app)
+ # 一个额外的按钮
+ self.button = QtWidgets.QPushButton('test')
+ layout.addWidget(self.button)
+ self.setLayout(layout)
+
+
+if __name__ == '__main__':
+ # matplotlib 绘图
+ fig = plt.figure()
+ ax = fig.add_subplot(111)
+ data = [random.random() for i in range(25)]
+ ax.plot(data, '*-')
+ # app
+ app = QtWidgets.QApplication(sys.argv)
+ config_path = os.path.join(r'E:\Python\pyminer_bin\PyMiner\bin\pyminer2\extensions\packages\pmagg', 'settings.cfg')
+ main = MainWindow(fig, config_path)
+ main.setWindowTitle('Simple PySide2 and PMAgg example')
+ main.show()
+ sys.exit(app.exec_())
\ No newline at end of file
diff --git a/pyminer/widgets/display/matplotlib/pyqtgraph/__init__.py b/pyminer/widgets/display/matplotlib/pyqtgraph/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/widgets/display/matplotlib/pyqtgraph/pyqtgraphwidget.py b/pyminer/widgets/display/matplotlib/pyqtgraph/pyqtgraphwidget.py
new file mode 100644
index 0000000000000000000000000000000000000000..318ff05f4fb8e2731716e7398fd98663ee5722d5
--- /dev/null
+++ b/pyminer/widgets/display/matplotlib/pyqtgraph/pyqtgraphwidget.py
@@ -0,0 +1,69 @@
+import sys
+import random
+import numpy as np
+import pyqtgraph as pg
+from PySide2.QtCore import QTimer
+from PySide2.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout
+
+
+class PMPyQtGraphWidget(QWidget):
+ def __init__(self, parent=None):
+ super(PMPyQtGraphWidget, self).__init__(parent)
+ self.resize(600, 600)
+ self.lines = []
+ # 1
+ pg.setConfigOptions(leftButtonPan=False)
+ pg.setConfigOption('background', 'w')
+ pg.setConfigOption('foreground', 'k')
+
+ # 2
+ # x = np.random.normal(size=1000)
+ # y = np.random.normal(size=1000)
+ # r_symbol = random.choice(['o', 's', 't', 't1', 't2', 't3', 'd', '+', 'x', 'p', 'h', 'star'])
+ # r_color = random.choice(['b', 'g', 'r', 'c', 'm', 'y', 'k', 'd', 'l', 's'])
+
+ # 3
+ self.pw = pg.PlotWidget(self,axisItems = {'bottom': pg.DateAxisItem()})
+ # self.plot_data = self.pw.plot(x, y, pen=None, symbol=r_symbol, symbolBrush=r_color)
+
+ # 4
+ # self.plot_btn = QPushButton('Replot', self)
+ # # self.plot_btn.clicked.connect(self.plot_slot)
+
+ self.v_layout = QVBoxLayout()
+ self.v_layout.addWidget(self.pw)
+ # self.v_layout.addWidget(self.plot_btn)
+ self.setLayout(self.v_layout)
+
+ def plot(self, x, ylist):
+ self.baseplot(x, ylist)
+ # self.timer2 = QTimer()
+ # self.timer2.singleShot(5000, lambda: self.baseplot(np.array(x)+2, ylist))
+ # self.baseplot(x, ylist)
+
+ def baseplot(self, x, ylist):
+ # print('baseplot!')
+ for line in self.lines:
+ line.clear()
+ for y in ylist:
+ plot_data = self.pw.plot(x, y, pen=None, symbol='+', symbolBrush='r')
+ self.lines.append(plot_data)
+
+ def draw(self):
+ pass
+
+ def clear(self):
+ pass
+ # def plot_slot(self):
+ # x = np.random.normal(size=1000)
+ # y = np.random.normal(size=1000)
+ # r_symbol = random.choice(['o', 's', 't', 't1', 't2', 't3', 'd', '+', 'x', 'p', 'h', 'star'])
+ # r_color = random.choice(['b', 'g', 'r', 'c', 'm', 'y', 'k', 'd', 'l', 's'])
+ # self.plot_data.setData(x, y, pen=None, symbol=r_symbol, symbolBrush=r_color)
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ demo = PMPyQtGraphWidget()
+ demo.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/display/matplotlib/qt5agg.py b/pyminer/widgets/display/matplotlib/qt5agg.py
new file mode 100644
index 0000000000000000000000000000000000000000..7c9ab22274bcf1515217fd945a1a0122dd753ee6
--- /dev/null
+++ b/pyminer/widgets/display/matplotlib/qt5agg.py
@@ -0,0 +1,50 @@
+from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
+import matplotlib.pyplot as plt
+from PySide2.QtWidgets import QVBoxLayout, QWidget
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from matplotlib.axes._subplots import Axes
+
+
+class PMMatplotlibQt5Widget(QWidget):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.figure = plt.figure(facecolor='#FFD7C4') # 可选参数,facecolor为背景颜色
+ self.canvas = FigureCanvasQTAgg(self.figure)
+ layout = QVBoxLayout()
+ layout.addWidget(self.canvas)
+ self.setLayout(layout)
+
+ def add_subplot(self, param) -> 'Axes':
+ return self.figure.add_subplot(param)
+
+ def draw(self) -> None:
+ self.canvas.draw()
+
+ def clear(self):
+ self.figure.clf()
+
+
+if __name__ == '__main__':
+ import sys
+ # from PySide2.QtWidgets import QApplication
+ from PySide2.QtWidgets import QApplication
+ from widgets.display import PMMatplotlibQt5Widget
+
+
+ def draw():
+ ax = pmqt5mplwgt.add_subplot(121)
+ ax2 = pmqt5mplwgt.add_subplot(122)
+ ax.plot([1, 2, 3])
+ ax.set_xlabel('test_x_label')
+ ax2.set_xlabel('test_2')
+ ax2.plot([1, 3, 1, 4, 15])
+ pmqt5mplwgt.draw()
+
+
+ app = QApplication(sys.argv)
+ pmqt5mplwgt = PMMatplotlibQt5Widget()
+ pmqt5mplwgt.show()
+ draw()
+ app.exec_()
diff --git a/pyminer/widgets/display/vtk/__init__.py b/pyminer/widgets/display/vtk/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/widgets/doc_figures/nested_lists_to_place_widgets.png b/pyminer/widgets/doc_figures/nested_lists_to_place_widgets.png
new file mode 100644
index 0000000000000000000000000000000000000000..a9247c0d3de4473f798da8a936b7de86ab842f27
Binary files /dev/null and b/pyminer/widgets/doc_figures/nested_lists_to_place_widgets.png differ
diff --git a/pyminer/widgets/doc_figures/pmflowarea_1.png b/pyminer/widgets/doc_figures/pmflowarea_1.png
new file mode 100644
index 0000000000000000000000000000000000000000..bcf6e2c4c08447d449e9de5d624646180b97fbcd
Binary files /dev/null and b/pyminer/widgets/doc_figures/pmflowarea_1.png differ
diff --git a/pyminer/widgets/doc_figures/pmflowarea_2.png b/pyminer/widgets/doc_figures/pmflowarea_2.png
new file mode 100644
index 0000000000000000000000000000000000000000..801eda3645d47987474ddb688d60d0ca75c792f0
Binary files /dev/null and b/pyminer/widgets/doc_figures/pmflowarea_2.png differ
diff --git a/pyminer/widgets/doc_figures/settings_panel.png b/pyminer/widgets/doc_figures/settings_panel.png
new file mode 100644
index 0000000000000000000000000000000000000000..b4c6efde1e420a860e2b110fda5f5eddf57077ec
Binary files /dev/null and b/pyminer/widgets/doc_figures/settings_panel.png differ
diff --git a/pyminer/widgets/docs/doc_figures/pmflowarea_2.png b/pyminer/widgets/docs/doc_figures/pmflowarea_2.png
new file mode 100644
index 0000000000000000000000000000000000000000..801eda3645d47987474ddb688d60d0ca75c792f0
Binary files /dev/null and b/pyminer/widgets/docs/doc_figures/pmflowarea_2.png differ
diff --git a/pyminer/widgets/docs/threading_and_tasking.md b/pyminer/widgets/docs/threading_and_tasking.md
new file mode 100644
index 0000000000000000000000000000000000000000..83c48fc188ef07ccef716aaa8e9cb1fb58195cda
--- /dev/null
+++ b/pyminer/widgets/docs/threading_and_tasking.md
@@ -0,0 +1,218 @@
+# 线程调用与后台任务执行器
+示例可见:`widgets/examples/utilities/threadings`文件夹
+
+>警告:
+>
+>- 在调用后台线程执行器PMGOneShot0hreadRunner\PMGLoopThreadRunner的时候,务必对执行器对象保持引用!否则执行器对象会被Python解释器垃圾回收,轻则任务无法完成,重则造成程序崩溃。
+>
+>- 注意:传入的callback函数不得在其中直接刷新UI(比如,调用文本框的setText方法),否则可能出现段错误(segmentation fault)。段错误会**立即导致界面崩溃**,且无法使用try...catch...或者cgitb等手段进行处理!
+>
+>注意:
+>
+>- 为了简单起见,本文档中的实例都是面向过程的。在示例中将执行器对象定义为全局变量,就是保持引用的一种方式。而当使用面向对象程序设计方式时,将其作为界面或者控件的属性,也不失为一种可行的解决方案。
+
+## 后台线程只执行一次`PMGOneShotThreadRunner`
+示例代码可以在`widgets/examples/utilities/threadings/singleshot.py`找到
+```python
+import sys
+import time
+
+from widgets import PMGOneShotThreadRunner # 导入单线程任务运行器
+from PySide2.QtWidgets import QTextEdit, QApplication, QWidget, QVBoxLayout, QPushButton
+
+
+def run(loop_times):
+ for i in range(loop_times):
+ print(i)
+ time.sleep(1)
+ return 'finished!!', 'aaaaaa', ['finished', 123]
+
+
+def single_shoot():
+ global oneshot, textedit
+ if oneshot is not None:
+ if oneshot.is_running(): # 如果后台线程已经在运行,那么就不要重新创建,否则可能造成程序崩溃。在实际程序中可以考虑加一个弹出框来进行提示。
+ return
+ oneshot = PMGOneShotThreadRunner(run, args=(3,))
+ oneshot.signal_finished.connect(lambda x: textedit.append('任务完成,函数返回值:' + repr(x)))
+
+
+oneshot = None
+app = QApplication(sys.argv)
+basewidget = QWidget()
+basewidget.setLayout(QVBoxLayout())
+
+textedit = QTextEdit()
+pushbutton = QPushButton('run')
+pushbutton.clicked.connect(single_shoot)
+basewidget.layout().addWidget(textedit)
+basewidget.layout().addWidget(pushbutton)
+basewidget.show()
+sys.exit(app.exec_())
+
+```
+运行时,点击弹出的窗体的`run`按钮,即可触发运行事件。等待3秒钟过后,即可显示下面的文字。多次点击,可以多次执行。
+
+
+
+
+
+### `PMGOneShotThreadRunner`类介绍
+
+#### 传入参数:
+- callback:传入函数对象。
+- args: 传入函数的参数,默认值为None。应当以元组形式依次传入。如果为None则不对函数传入参数。
+#### 信号
+signal_finished,需要连接到的槽函数有一个参数,代表传入函数对象的返回值。
+
+#### 方法
+is_running,返回线程是否在运行。
+
+#### 注意事项
+对于单步执行的子线程,`widgets`库**暂时不提供**强制停止未完成任务(或者出现错误而退出)的子线程的接口。用户需要保证子线程可以在一定时间后正确的退出。
+
+
+
+## 后台线程执行有限次`PMGLoopThreadRunner`
+后台线程执行有限次的例子可见`widgets/examples/utilities/threadings/loop_limited_times1.py`与
+`widgets/examples/utilities/threadings/loop_limited_times2.py`。
+首先,执行有限次的模型是这样的:执行有限次同一函数,但是每次执行时都要传入不同的参数。这样,我们只要传入一个函数,以及一个参数列表,
+列表长度就是循环的次数。返回的参数由`signal_step_finished`传回。
+代码看这里:(`widgets/examples/utilities/threadings/loop_limited_times1.py`)
+
+```python
+import sys
+import time
+from PySide2.QtWidgets import QTextEdit, QApplication
+from widgets import PMGLoopThreadRunner
+
+
+def run(i, j):
+ time.sleep(0.1)
+ return i + j
+
+
+def on_step_finished(step, result):
+ global text1
+ text1.append('传入每步不同的可迭代参数\nstep:%d,result:%s\n' % (step, repr(result)))
+
+
+def on_finished():
+ global text1
+ text1.append('所有任务完成!')
+
+
+app = QApplication(sys.argv)
+text1 = QTextEdit()
+text1.show()
+oneshot = PMGLoopThreadRunner(run, iter_args=[(i, i + 1) for i in range(36)])
+# 传入一个列表可迭代对象(当然也可以是其他迭代器。)作为参数。列表的长度就是循环的次数,列表的每一个元素代表每一步传入的参数。
+oneshot.signal_step_finished.connect(on_step_finished) # 每一步执行后的结果由signal_step_finished传回。多参数则会放进tuple里面。
+oneshot.signal_finished.connect(on_finished)
+
+sys.exit(app.exec_())
+```
+### `PMGLoopThreadRunner`类介绍
+#### 传入参数:
+- callback:传入函数对象。
+- iter_args: 传入函数的参数,默认值为None。应当以可迭代对象的形式传入,可迭代对象的每个元素都是元组。可以为None。
+**注意:只有当iter_args为None的时候,才会解析loop_times和step_args参数**!
+- loop_times:执行的次数。
+- step_args:当loop_times生效时每步执行传入的参数,类型应为元组。
+
+通过以上的示例我们可以看出,loop_times的功能完全可以被iter_args替代。但为了简洁方便,还是设计了这一方法。
+若要用loop_times配合step_args,则必须设置iter_args为None。
+使用loop_times配合step_args的示例详见`widgets/examples/utilities/threadings/loop_limited_times2.py`
+#### 信号
+signal_step_finished:传递两个参数,分别是步数(整数,从0开始)和单步执行的结果。
+signal_finished,意思后台线程执行完规定次数退出前发出的信号。它没有传递参数,在这点上与单步执行器不同。
+这是因为在结束时,他只是通知一下前台程序”任务已经完成,后台线程要退出了“;而程序每一步的执行结果,都已经在signal_step_finished中传过了。
+
+
+
+
+
+## 后台线程无限反复运行`PMGEndlessLoopThreadRunner`
+后台线程反复运行无数次的例子可见
+`widgets/examples/utilities/threadings/endlessloop.py`。
+首先,执行有限次的模型是这样的:执行有限次同一函数,但是每次执行时都要传入不同的参数。这样,我们只要传入一个函数,以及一个参数列表,
+列表长度就是循环的次数。返回的参数由`signal_step_finished`传回。
+### 例程
+```python
+import sys
+import time
+from PySide2.QtWidgets import QTextEdit, QApplication, QPushButton, QWidget, QVBoxLayout
+from widgets import PMGEndlessLoopThreadRunner
+
+
+def run(i, j):
+ time.sleep(0.1)
+ return i + j
+
+
+def on_step_finished(result):
+ global text1, stepcount
+ text1.append('step:%d,result:%s\n' % (stepcount,repr(result)))
+ stepcount += 1
+
+
+def on_finished():
+ global text1
+ text1.append('thread quit, all tasks completed!')
+
+
+def start_thread():
+ global endless_loop
+ if endless_loop is not None:
+ if endless_loop.is_running():
+ return
+ endless_loop = PMGEndlessLoopThreadRunner(run, args=(0, 0))
+ endless_loop.signal_step_finished.connect(on_step_finished)
+ endless_loop.signal_finished.connect(on_finished)
+
+def stop_thread():
+ global endless_loop
+ endless_loop.shut_down()
+
+stepcount = 0
+endless_loop:PMGEndlessLoopThreadRunner = None
+
+app = QApplication(sys.argv)
+basewidget = QWidget()
+basewidget.setLayout(QVBoxLayout())
+
+text1 = QTextEdit()
+pushbutton_run = QPushButton('Run')
+pushbutton_stop = QPushButton('Stop')
+pushbutton_run.clicked.connect(start_thread)
+pushbutton_stop.clicked.connect(stop_thread)
+basewidget.layout().addWidget(text1)
+basewidget.layout().addWidget(pushbutton_run)
+basewidget.layout().addWidget(pushbutton_stop)
+basewidget.show()
+sys.exit(app.exec_())
+```
+直接运行,则可以看到以下界面:
+
+
+在这个界面上点击`Run`即可运行。看到以下效果:
+
+
+在这个界面上点击`Stop`即可停止。看到以下效果:
+
+
+再次点击`Run`,可以继续运行。
+
+### `PMGEndlessLoopThreadRunner`类介绍
+
+#### 传入参数:
+- callback: 回调函数。函数每执行一次,信号`signal_step_finished`将发出,信号只有一个参数,也就是我们输入的callback的返回值。
+- args:参数
+
+#### 信号
+signal_step_finished:传递两个参数,分别是步数(整数,从0开始)和单步执行的结果。
+signal_finished,意思后台线程执行完规定次数退出前发出的信号。它没有传递参数,在这点上与单步执行器不同。
+这是因为在结束时,他只是通知一下前台程序”任务已经完成,后台线程要退出了“;而程序每一步的执行结果,都已经在signal_step_finished中传过了。
+
+#### 方法
+is_running,返回线程是否在运行。
\ No newline at end of file
diff --git a/pyminer/widgets/elements/__init__.py b/pyminer/widgets/elements/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..eb1e9220df93e6a16296121e23f1a8ee8b2e3d1e
--- /dev/null
+++ b/pyminer/widgets/elements/__init__.py
@@ -0,0 +1,2 @@
+from .toolbar import *
+from .dockobject import *
\ No newline at end of file
diff --git a/pyminer/widgets/elements/dockobject.py b/pyminer/widgets/elements/dockobject.py
new file mode 100644
index 0000000000000000000000000000000000000000..f5c5e5c0200eea5fa9114a5dfaf4978dbf9d1d1f
--- /dev/null
+++ b/pyminer/widgets/elements/dockobject.py
@@ -0,0 +1,62 @@
+from typing import Tuple
+
+from PySide2.QtWidgets import QWidget
+
+
+class PMDockObject(object):
+ """
+ 修改:
+ 原先,主窗口中的各个可停靠窗口,在点击右上角关闭按钮的时候会隐藏,可以在视图菜单中打开。
+ 但是当控件中有on_closed_action属性,且值为‘delete’的时候,控件就会被回收。
+ 为了实现控件的管理,控件需要继承PMDockObject,并且需要用多继承的方式。
+ 注意,凡是要和一些内置事件绑定的控件,都不要用delete。
+
+
+ from features.ui.generalwidgets import PMDockObject
+ 这个PMDockObject中定义了一些方法,作为补充。
+
+ class PMDockObject(object):
+ on_closed_action = 'hide' # 或者'delete'。
+
+ def raise_widget_to_visible(self, widget: 'QWidget'):
+ pass
+
+ def on_dock_widget_deleted(self):
+ pass
+
+ """
+ on_closed_action = 'hide' # 或者'delete'。
+
+ def is_temporary(self) -> bool:
+ """
+ 如果为True,相应的控件将在窗口关闭时删除,并且不会记忆其位置。如果为False,则相应的控件不会被删除,且其位置将被记忆。
+
+ 默认返回False。
+ """
+ return False
+
+ def raise_widget_to_visible(self, widget: 'QWidget'):
+ pass
+
+ def on_dock_widget_deleted(self):
+ pass
+
+ def get_split_portion_hint(self) -> Tuple[int, int]:
+ return (None, None)
+
+ def set_extension_lib(self, extension_lib):
+ """
+ 设置扩展库。
+ :param extension_lib:
+ :return:
+ """
+ self.extension_lib = extension_lib
+
+ def setup_ui(self):
+ pass
+
+ def bind_events(self):
+ pass
+
+ def get_widget_text(self) -> str:
+ return ''
diff --git a/pyminer/widgets/elements/toolbar.py b/pyminer/widgets/elements/toolbar.py
new file mode 100644
index 0000000000000000000000000000000000000000..32de6566f8d4b72a3aeabcb41346043a55bd00ad
--- /dev/null
+++ b/pyminer/widgets/elements/toolbar.py
@@ -0,0 +1,200 @@
+from typing import List, Callable, Optional
+
+from PySide2.QtCore import Qt, QSize
+from PySide2.QtGui import QIcon
+from PySide2.QtWidgets import QToolBar, QPushButton, QMenu, QToolButton, QAction, QWidget, QHBoxLayout
+
+
+class ActionWithMessage(QAction):
+ def __init__(self, text: str = '', icon: QIcon = None,
+ parent: QWidget = None, message: str = ''):
+ super().__init__(parent)
+ self.setText(text)
+ if icon is not None:
+ self.setIcon(icon)
+ self.message = message
+
+
+class TopToolBar(QToolBar):
+ def __init__(self):
+ super().__init__()
+ self.setFloatable(False)
+ self.setMovable(False)
+ self.buttonbar_widget = QWidget()
+ self.addWidget(self.buttonbar_widget)
+ self.buttonbar_widget.setLayout(QHBoxLayout())
+ self.buttonbar_widget.setContentsMargins(0, 0, 0, 0)
+ self.buttonbar_widget.layout().setContentsMargins(0, 0, 0, 0)
+ self.button_names = []
+
+ def add_button(self, name: str, text: str):
+ pbtn = QPushButton(text)
+ pbtn.setObjectName('pmtopToolbarButton')
+ pbtn.setProperty('stat', 'unselected')
+ self.buttonbar_widget.layout().addWidget(pbtn)
+ self.button_names.append(name)
+ return pbtn
+
+ def insert_button(self, name: str, text: str, insert_after: str):
+ pbtn = QPushButton(text)
+ pbtn.setObjectName('pmtopToolbarButton')
+ pbtn.setProperty('stat', 'unselected')
+
+ assert insert_after in self.button_names, f'{insert_after} do not in buttons: {self.button_names}'
+ index = self.button_names.index(insert_after)
+ self.button_names.insert(index + 1, name)
+ self.buttonbar_widget.layout().insertWidget(index + 1, pbtn)
+ return pbtn
+
+ def get_button(self, name: str):
+ return self.findChild(QPushButton, name)
+
+
+class TopToolBarRight(QToolBar):
+ def __init__(self):
+ super().__init__()
+ self.setFloatable(False)
+ self.setMovable(False)
+ self.setLayoutDirection(Qt.RightToLeft)
+ self.hide_button = QToolButton()
+ self.hide_button.setObjectName("hidebutton")
+ self.hide_button.setArrowType(Qt.UpArrow)
+ self.addWidget(self.hide_button)
+
+
+class PMGToolBar(QToolBar):
+ tab_button: QPushButton = None
+ _control_widget_dic = {}
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._control_widget_dic = {}
+
+ def get_toolbar_text(self) -> str:
+ return 'Toolbar'
+
+ def get_control_widget(self, widget_name: str) -> QPushButton:
+ w = self._control_widget_dic.get(widget_name)
+ if w is None:
+ raise Exception(
+ 'Toolbar has no widget named \'%s\'' %
+ widget_name)
+ return w
+
+ def bind_events(self):
+ pass
+
+ def add_tool_button(self, name: str, text: str = '', tooltip: str = '',
+ icon: QIcon = None, menu: QMenu = None):
+ tb = QToolButton()
+ tb.setPopupMode(QToolButton.InstantPopup)
+ tb.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
+ tb.setText(text)
+ tb.setToolTip(tooltip)
+ tb.setProperty('qssp', 'tooliconbtn')
+ if icon is not None:
+ pixmap = icon.pixmap(QSize(40, 40))
+ icon = QIcon(pixmap)
+ tb.setIcon(icon)
+
+ tb.setIconSize(QSize(40, 40))
+ if menu is not None:
+ tb.setMenu(menu)
+ self.addWidget(tb)
+ self._control_widget_dic[name] = tb
+ return tb
+
+ def add_height_occupation(self):
+ from widgets import PMPushButtonPane
+
+ pp = PMPushButtonPane()
+ button_list = pp.add_height_occu_buttons()
+ self.addWidget(pp)
+ return button_list
+
+ def add_buttons(self, button_num: int, names: List[str], texts: List[str], icons_path: List[str] = None) \
+ -> List['QPushButton']:
+ from widgets import PMPushButtonPane
+
+ pp = PMPushButtonPane()
+ button_list = pp.add_buttons(button_num, texts, icons_path)
+ for i, name in enumerate(names):
+ self._control_widget_dic[name] = button_list[i]
+ self.addWidget(pp)
+ return button_list
+
+ def add_widget(self, name: str, widget: 'QWidget'):
+ self._control_widget_dic[name] = widget
+ self.addWidget(widget)
+ return widget
+
+ def add_menu_to(self, button_name: str,
+ action_texts: List[str],
+ action_commands: List['Callable'],
+ action_icon: QIcon = None) -> Optional[QMenu]:
+ button = self.get_control_widget(button_name)
+ if button is not None:
+ menu = QMenu(self)
+ for text, cmd in zip(action_texts, action_commands):
+ a = QAction(text=text, parent=menu)
+ if action_icon is not None:
+ a.setIcon(action_icon)
+ menu.addAction(a)
+ a.triggered.connect(cmd)
+ button.setMenu(menu)
+ return menu
+ return None
+
+ def append_menu(self, button_name: str, action_text: str, action_command: 'Callable',
+ action_icon: QIcon = None) -> 'QAction':
+ button: 'QToolButton' = self.get_control_widget(button_name)
+ action = None
+ if button is not None:
+ menu = button.menu()
+ if menu is None:
+ menu = self.add_menu_to(button_name, [action_text], [action_command], action_icon=action_icon)
+ return menu.actions()[0]
+ else:
+ action = QAction(text=action_text, parent=menu)
+ if action_icon is not None:
+ action.setIcon(action_icon)
+ menu.addAction(action)
+ action.triggered.connect(action_command)
+
+ return action
+
+ def append_qmenu(self, button_name: str, menu_text: str, menu_icon: QIcon = None) -> QMenu:
+ button: 'QToolButton' = self.get_control_widget(button_name)
+ action = None
+ if button is not None:
+ menu = button.menu()
+ new_menu = QMenu(menu)
+ new_menu.setTitle(menu_text)
+ # menu.addMenu()
+ menu.addMenu(new_menu)
+ # if menu is None:
+ # self.add_menu_to(button_name, [action_text], [action_command], action_icon=action_icon)
+ # else:
+ # action = QAction(text=action_text, parent=menu)
+ # if action_icon is not None:
+ # action.setIcon(action_icon)
+ # menu.addAction(action)
+ # action.triggered.connect(action_command)
+
+ return new_menu
+
+ def add_menu_separator(self, button_name):
+ button: 'QToolButton' = self.get_control_widget(button_name)
+ action = None
+ if button is not None:
+ menu: QMenu = button.menu()
+ if menu is not None:
+ menu.addSeparator()
+
+ def insert_after(self) -> str:
+ """
+ 返回插入在某某后面
+ Returns:
+
+ """
+ return ''
diff --git a/pyminer/widgets/examples/__init__.py b/pyminer/widgets/examples/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/widgets/examples/utilities/__init__.py b/pyminer/widgets/examples/utilities/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/widgets/examples/utilities/examples.py b/pyminer/widgets/examples/utilities/examples.py
new file mode 100644
index 0000000000000000000000000000000000000000..60eade80aabc24b5b7154724620d3f5d55874ff4
--- /dev/null
+++ b/pyminer/widgets/examples/utilities/examples.py
@@ -0,0 +1,21 @@
+if __name__ == '__main__':
+ from widgets import BaseClient
+
+ import numpy as np
+
+ x = np.random.random(10) + np.linspace(1, 10, 10)
+ y = np.random.random(10) + np.linspace(1, 10, 10)
+
+ c = BaseClient()
+
+ print('set_var')
+ c.set_var('x', x)
+ c.set_var('y', y)
+ c.set_var('z', y)
+ c.set_var('w', y)
+ c.set_var('t', y)
+ c.set_var('y', y)
+ c.get_var('x')
+ print(c.get_all_vars())
+ print(c.get_all_var_names())
+ c.get_var('m') # 若没有m变量,就会报错
diff --git a/pyminer/widgets/examples/utilities/long_conn.py b/pyminer/widgets/examples/utilities/long_conn.py
new file mode 100644
index 0000000000000000000000000000000000000000..8a4dee6e7b6e5eccb0ae59aec7b83a5e1ee16cf9
--- /dev/null
+++ b/pyminer/widgets/examples/utilities/long_conn.py
@@ -0,0 +1,5 @@
+from widgets import GeneralClient
+import time
+__client = GeneralClient()
+while(1):
+ time.sleep(1)
\ No newline at end of file
diff --git a/pyminer/widgets/flowchart/__init__.py b/pyminer/widgets/flowchart/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..abea00de2956437c6fd80be19ffedf9616f19977
--- /dev/null
+++ b/pyminer/widgets/flowchart/__init__.py
@@ -0,0 +1,3 @@
+from .core import *
+from .dataprocesswidget import PMDataProcessFlowWidget
+from .simulationwidget import PMGSimulationWidget
diff --git a/pyminer/widgets/flowchart/core/__init__.py b/pyminer/widgets/flowchart/core/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..52eae64f3d12f2ad8fd2f1abac003f74f94fb4ff
--- /dev/null
+++ b/pyminer/widgets/flowchart/core/__init__.py
@@ -0,0 +1,2 @@
+from .flow_content import *
+from .flowchart_widget import PMFlowWidget
\ No newline at end of file
diff --git a/pyminer/widgets/flowchart/core/flow_content.py b/pyminer/widgets/flowchart/core/flow_content.py
new file mode 100644
index 0000000000000000000000000000000000000000..0629188c36c057b84206879271bc408139d4a167
--- /dev/null
+++ b/pyminer/widgets/flowchart/core/flow_content.py
@@ -0,0 +1,391 @@
+'''
+register contents
+manager.register('')
+'''
+import json
+import time
+import types
+
+from PySide2.QtCore import QObject
+from PySide2.QtWidgets import QApplication
+from PySide2.QtCore import Signal
+from typing import TYPE_CHECKING, Callable, List, Union, Tuple, Dict
+
+if TYPE_CHECKING:
+ from widgets.flowchart.core.flow_node import Node
+ from widgets.flowchart.core.flowchart_widget import PMGraphicsScene
+
+
+class FlowContentError():
+ def __init__(self, brief: str, detailed: str = ''):
+ self.brief: str = brief
+ if detailed == '':
+ detailed = brief
+ self.detailed: str = detailed
+
+
+class PMGBaseFlowContent(QObject):
+ signal_exec_started = Signal(str)
+ signal_exec_doing = Signal(str)
+ signal_exec_finished = Signal(str)
+ signal_error_occurs = Signal(FlowContentError)
+
+ def __init__(self):
+ super(PMGBaseFlowContent, self).__init__()
+ self.ports_changable: List[bool, bool] = [False, False]
+ self.input_args = []
+
+ self.results = []
+ self.node: 'Node' = None
+
+ def refresh_input_port_indices(self):
+ input_port_list = [p.get_connected_port()[0] for p in self.node.input_ports if len(p.get_connected_port()) > 0]
+ self.input_port_indices = []
+ try:
+ for p in input_port_list:
+ index = p.node.get_port_index(p)
+ self.input_port_indices.append([p, index])
+ except Exception as e:
+ import traceback
+ traceback.print_exc()
+ info = traceback.format_exc()
+ self.signal_error_occurs.emit(FlowContentError('Module:%s,Error:%s' % (self.node.text, str(e)),
+ info))
+ return
+
+ def format_param(self) -> str:
+ return ''
+
+ def format_text(self) -> str:
+ return ''
+
+ def set_params(self, params: Union[str]):
+ try:
+ if isinstance(params, str):
+ params = eval(params)
+
+ if isinstance(params, list):
+ self.params = params
+ for i in range(len(params)):
+ self.params[i] = self.params[i]
+ param_name = self.params[i][1]
+ param_obj = self.params[i][3]
+ self.vars[param_name] = param_obj
+ self.node.display_internal_values(self.format_param())
+ except:
+ import traceback
+ traceback.print_exc()
+
+ def get_settings_params(self) -> List:
+ return []
+
+ def _process(self, input_args: list = None):
+ if input_args is None:
+ input_args = []
+ try:
+ for p, index in self.input_port_indices:
+ if isinstance(p.node.content.results, types.GeneratorType):
+ input_args = [] # 对于迭代器场合,不需要任何输入值!
+ else:
+ val = p.node.content.results[index]
+ input_args.append(val)
+
+ except Exception as e:
+ import traceback
+ traceback.print_exc()
+ self.signal_error_occurs.emit(FlowContentError(str(e), traceback.format_exc()))
+ return
+ self.input_args = input_args
+ self.signal_exec_started.emit('started')
+ if not isinstance(input_args, list):
+ self.signal_error_occurs.emit(FlowContentError('input list is not instance of list'))
+ return None
+ t1 = time.time()
+ try:
+ result = self.process(*input_args)
+ except Exception as e:
+ import traceback
+ exc = traceback.format_exc()
+ self.signal_error_occurs.emit(
+ FlowContentError('Module:%s,Error:%s' % (self.node.text, str(e)),
+ 'Info:%s' % (exc)))
+ return None
+ # self.results = copy.deepcopy(result)
+ if isinstance(result, types.GeneratorType): # 如果发现返回值是一个迭代器,就执行迭代器的next方法。
+ step = 0
+ while 1:
+ try:
+ self.results = next(result)
+
+ next_content = self.get_next_content()
+ # self.signal_exec_finished.emit(
+ # 'finished\ninput value:%s\noutput value:%s' % (repr(input_list), repr(self.results)))
+ self.signal_exec_finished.emit('Iterating, %s' % repr(self.results))
+ QApplication.instance().processEvents() # 调用processEvents进行处理
+ if next_content is None:
+ return
+ else:
+ next_content._process()
+ except StopIteration:
+ self.signal_exec_finished.emit('finished!')
+ return
+
+ self.results = result
+ next_content = self.get_next_content()
+ # self.signal_exec_finished.emit(
+ # 'finished\ninput value:%s\noutput value:%s' % (repr(input_list), repr(self.results)))
+ self.signal_exec_finished.emit('finished')
+ if next_content is None:
+ return
+ else:
+ next_content._process()
+
+ def refresh(self):
+ pass
+
+ def dump(self):
+ return {'code': self.code, 'type': 'function', 'params': self.params, 'ports_changable': self.ports_changable}
+
+ def process(self, *args) -> List[object]:
+
+ return []
+
+ def get_next_content(self) -> 'FlowContentForFunction':
+ scene: 'PMGraphicsScene' = self.node.base_rect.scene()
+ scene.node_index_to_execute += 1
+ node_index = scene.node_index_to_execute
+ if node_index >= len(scene.call_id_list):
+ scene.node_index_to_execute = 0
+ return None
+ node_id = scene.call_id_list[node_index]
+ return scene.find_node(node_id).content
+
+ def settings_window_requested(self, parent):
+ """
+ 当设置窗口被请求时处理的函数。
+ 设置结束后,在节点表面上回显参数。需要调用format_params函数
+ Args:
+ parent: 父控件,若要弹出对话框就必须这样做。
+
+ Returns:
+
+ """
+ self.on_settings_requested(parent)
+ self.node.display_internal_values(self.format_param())
+
+ def on_settings_requested(self, parent):
+ pass
+
+
+class PMGFlowContent(PMGBaseFlowContent):
+ def __init__(self):
+ super(PMGFlowContent, self).__init__()
+ self.input_args_labels = ['input1']
+ self.output_ports_labels = ['output1', 'output2']
+ self.class_name = 'Demo Node'
+ self.text = '示例节点'
+ self.icon_path = ''
+ self.info = {}
+
+ def process(self, *args) -> List[object]:
+ print('hello world!!!')
+ return []
+
+ def dump(self):
+ return {'type': self.class_name, 'info': self.info}
+
+ def load_info(self, info: dict):
+ self.info = info
+
+ def format_text(self) -> str:
+ return self.text
+
+
+class FlowContentForFunction(PMGBaseFlowContent):
+
+ def __init__(self, node):
+ super(FlowContentForFunction, self).__init__()
+ self.node = node
+ self.node.content = self
+ self.code: str = None
+ self.params: List[Union[Tuple, int, float, str]] = []
+ self.refresh_func: Callable = None
+
+ self.func: Callable = None
+ self.input_port_indices: List[List] = []
+ self.vars: Dict[str, Union[int, str, float, object]] = {}
+
+ def set_function(self, function_def: str = None, func_name: str = None):
+ import time
+ t1 = time.time()
+ self.code: str = function_def
+ g = globals()
+ g.update(self.vars)
+ exec(self.code, g)
+ self.func: Callable = globals().get(func_name)
+ self.refresh_func: Callable = globals().get('refresh')
+ t2 = time.time()
+
+ def refresh_input_port_indices(self):
+ input_port_list = [p.get_connected_port()[0] for p in self.node.input_ports if len(p.get_connected_port()) > 0]
+ self.input_port_indices = []
+ try:
+ for p in input_port_list:
+ index = p.node.get_port_index(p)
+ self.input_port_indices.append([p, index])
+ except Exception as e:
+ import traceback
+ traceback.print_exc()
+ self.signal_error_occurs.emit(
+ FlowContentError('Module: %s,Exception:%s' %
+ (self.node.text, str(e)), traceback.format_exc()))
+ return
+
+ def get_settings_params(self):
+ return self.params.copy()
+
+ def update_settings(self, settings_dic: dict):
+ for i, tup in enumerate(self.params):
+ param_name = tup[1]
+ l = list(self.params[i])
+ l[3] = settings_dic[param_name]
+ self.params[i] = tuple(l)
+ self.vars[param_name] = settings_dic[param_name]
+ self.node.display_internal_values(self.format_param())
+
+ def _process(self, input_args: list = None):
+ if input_args is None:
+ input_args = []
+ try:
+ for p, index in self.input_port_indices:
+ val = p.node.content.results[index]
+ # val = copy.deepcopy(val)
+ input_args.append(val)
+
+ except Exception as e:
+ import traceback
+ traceback.print_exc()
+ self.signal_error_occurs.emit(FlowContentError('Module:%s,Error:%s' % (self.node.text, e),
+ traceback.format_exc()))
+ return
+ self.input_args = input_args
+ self.signal_exec_started.emit('started')
+ if not isinstance(input_args, list):
+ self.signal_error_occurs.emit(FlowContentError(
+ 'input list is not instance of list',
+ 'input list is not instance of list,but of type ' + repr(type(input_args))))
+ return None
+ t1 = time.time()
+ try:
+ result = self.process(*input_args)
+ except:
+ import traceback
+ exc = traceback.format_exc()
+ traceback.print_exc()
+ self.signal_error_occurs.emit('Module:%s\nInfo:%s' % (self.node.text, exc))
+ return None
+ # self.results = copy.deepcopy(result)
+
+ if isinstance(result, types.GeneratorType):
+ step = 0
+ while 1:
+ try:
+ self.results = next(result)
+
+ next_content = self.get_next_content()
+ # self.signal_exec_finished.emit(
+ # 'finished\ninput value:%s\noutput value:%s' % (repr(input_list), repr(self.results)))
+ self.signal_exec_finished.emit('finished')
+ if next_content is None:
+ return
+ else:
+ next_content._process()
+ except StopIteration:
+ return
+
+ self.results = result
+ next_content = self.get_next_content()
+ # self.signal_exec_finished.emit(
+ # 'finished\ninput value:%s\noutput value:%s' % (repr(input_list), repr(self.results)))
+ self.signal_exec_finished.emit('finished')
+ if next_content is None:
+ return
+ else:
+ next_content._process()
+
+ def dump(self):
+ return {'code': self.code, 'type': 'function', 'params': self.params, 'ports_changable': self.ports_changable}
+
+ def format_param(self) -> str:
+ keys = list(self.vars.keys())
+ if len(keys) == 1:
+ return str(self.vars[keys[0]])
+ else:
+ try:
+ return json.dumps(self.vars, indent=4)
+ except:
+ return str(self.vars)
+
+ def process(self, *args):
+ assert self.func is not None
+ globals().update(self.vars)
+ return self.func(*args)
+
+ def refresh(self):
+ if callable(self.refresh_func):
+ try:
+ self.refresh_func(self)
+ except:
+ import traceback
+ traceback.print_exc()
+
+
+class FlowContentEditableFunction(FlowContentForFunction):
+ def __init__(self, node, code: str = ''):
+ super().__init__(node)
+ self.ports_changable = [True, True]
+ self.input_args = []
+ self.results = []
+ self.node: 'Node' = node
+ self.node.content = self
+ if code == '':
+ code = """
+import time
+def function(x,y):
+ return y*2,x+2
+ """
+ self.code = code
+ self.set_function(code, 'function')
+
+ def get_settings_params(self) -> List[Union[Tuple, List]]:
+ return [('text_edit', 'code', 'Input Python Code', self.code, 'python')]
+
+ def update_settings(self, settings_dic: dict):
+ self.code = settings_dic['code']
+
+ def process(self, *args):
+ globals().update({'self': self})
+ # exec(self.code, globals())
+ # return function(*args)
+
+ return self.func(*args)
+
+ def dump(self):
+ return {'code': self.code, 'type': 'custom_function'}
+
+
+flowcontent_types = {'function': FlowContentForFunction,
+ 'custom_function': FlowContentEditableFunction}
+if __name__ == '__main__':
+ def process():
+ self = 123455
+ code = """
+def function():
+ print(123)
+ """
+ loc = locals()
+ exec(code)
+ print(loc['function'])
+
+
+ process()
diff --git a/pyminer/widgets/flowchart/core/flow_items.py b/pyminer/widgets/flowchart/core/flow_items.py
new file mode 100644
index 0000000000000000000000000000000000000000..a274c017343bb3e88f83caf55a53ca2be850fdcf
--- /dev/null
+++ b/pyminer/widgets/flowchart/core/flow_items.py
@@ -0,0 +1,685 @@
+"""
+绘制层次:
+
+"""
+from PySide2.QtCore import QLineF, QPointF, Qt, QRectF, Signal
+from PySide2.QtGui import QPolygonF, QPen, QPainterPath, QColor, QPainter, QBrush
+from PySide2.QtWidgets import QGraphicsLineItem, QGraphicsItem, QGraphicsSceneMouseEvent, QGraphicsObject, \
+ QStyleOptionGraphicsItem, QWidget, QGraphicsSceneHoverEvent, QGraphicsTextItem, QTextEdit
+
+from typing import TYPE_CHECKING, Tuple, List, Callable, Any
+
+if TYPE_CHECKING:
+ from .flowchart_widget import Node, PMGraphicsScene
+
+COLOR_NORMAL = QColor(212, 227, 242)
+COLOR_LINE_NORMAL = QColor(30, 30, 30)
+COLOR_HOVER = QColor(255, 200, 00)
+COLOR_HOVER_PORT = QColor(0, 0, 50)
+COLOR_HOVER_MID_POINT = QColor(0, 0, 200)
+COLOR_SELECTED = QColor(255, 255, 0)
+
+
+def round_position(point: QPointF, pixels=5):
+ x, y = point.x(), point.y()
+ x_cor, y_cor = round(x * 1.0 / pixels) * pixels, round(y * 1.0 / pixels) * pixels
+ return QPointF(x_cor, y_cor)
+
+
+class PMGSimpleSignal():
+ """
+ 简单的信号类
+ 此信号类接口类似于Signal,但父类不一定必须继承QObject。可以从其他地方发出,并且起到去耦合的作用。
+ """
+
+ def __init__(self, *object_types):
+ self.object_types = object_types
+ self.callbacks = []
+
+ def connect(self, callback: Callable):
+ self.callbacks.append(callback)
+
+ def emit(self, *args):
+ assert len(args) == len(self.object_types), '输入参数长度与定义时不等'
+ for arg, arg_type in zip(args, self.object_types):
+ assert isinstance(arg, arg_type), '参数%s类型与定义不同' % repr(arg)
+
+ for cb in self.callbacks:
+ cb(*args)
+
+ def disconnect(self, callback: Callable):
+ if callback in self.callbacks:
+ self.callbacks.remove(callback)
+
+
+class PMGGraphicsLineItem(QGraphicsLineItem):
+
+ def __init__(self, line: QLineF):
+ super(PMGGraphicsLineItem, self).__init__(line)
+
+ self.signal_hover_enter = PMGSimpleSignal(QGraphicsSceneHoverEvent)
+ self.signal_hover_leave = PMGSimpleSignal(QGraphicsSceneHoverEvent)
+ self.signal_mouse_pressd = PMGSimpleSignal(QGraphicsSceneMouseEvent)
+ self.signal_mouse_doubleclicked = PMGSimpleSignal(QGraphicsSceneMouseEvent, PMGGraphicsLineItem)
+ self.setAcceptHoverEvents(True)
+
+ def hoverEnterEvent(self, event: 'QGraphicsSceneHoverEvent') -> None:
+ self.signal_hover_enter.emit(event)
+
+ def hoverLeaveEvent(self, event: 'QGraphicsSceneHoverEvent') -> None:
+ self.signal_hover_leave.emit(event)
+
+ def mousePressEvent(self, event: 'QGraphicsSceneMouseEvent') -> None:
+ """
+ 这里不能进行继承。一旦进行了继承,似乎会让下面所有的东西都收到信号,从而无法选定。
+ :param event:
+ :return:
+ """
+ super(PMGGraphicsLineItem, self).mousePressEvent(event)
+ self.signal_mouse_pressd.emit(event)
+
+ def mouseDoubleClickEvent(self, event: 'QGraphicsSceneMouseEvent') -> None:
+ self.signal_mouse_doubleclicked.emit(event, self)
+
+ def paint(self, QPainter, QStyleOptionGraphicsItem, widget=None):
+ """
+ 屏蔽绘制事件!
+ Args:
+ QPainter:
+ QStyleOptionGraphicsItem:
+ widget:
+
+ Returns:
+
+ """
+ pass
+
+
+class CustomLine(QGraphicsLineItem):
+ """
+ CustomLine不是QObject,没有办法绑定信号,只能用回调函数的方式。
+ 之前有重绘事件的回调函数,现已删除,改为鼠标事件触发。
+
+ 这是一个line,其中起点是start_port,终点end_port,中继点为mid_points中从第1个元素开始依次向前。
+ 所以一共有起点+终点+len(mid_points)个节点,从而有len(mid_points)+1个线段。
+
+ 每一个线段背后都是一个QGraphicsLineItem。为何不是简单绘制?盖因一般的绘制没有办法捕获鼠标事件。
+ 如果使用QGraphicsLineItem,那么可以捕获键盘事件。
+
+ 当点击线段的时候会触发选择事件。首先,线段会调用QGraphicsScene(self.canvas)进行一个清除选择的操作————如果不按ctrl的话。
+ 然后设置本身状态为选择。
+
+ self.canvas.selectedItems()可获取当前被选中的部件。
+
+ 下一步任务:插入节点、删除节点。
+ """
+
+ def __init__(self, start_port: 'CustomPort', end_port: 'CustomPort', canvas: 'PMGraphicsScene' = None,
+ mid_points: 'List[CustomMidPoint]' = None):
+ super(CustomLine, self).__init__()
+ self.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable) # 拖动
+
+ self._refreshed = False
+ self.color = COLOR_LINE_NORMAL
+ self.start_port = start_port
+ self.end_port = end_port
+ if self not in start_port.connected_lines:
+ start_port.connected_lines.append(self)
+ if self not in end_port.connected_lines:
+ end_port.connected_lines.append(self)
+ self.line_items: List['QGraphicsLineItem'] = []
+ self.center_points = mid_points if (mid_points is not None) else []
+ for p in self.center_points:
+ canvas.addItem(p)
+ self.connect_midpoint_signals(p)
+ p.line = self
+ canvas.signal_clear_selection.connect(self.on_clear_selection)
+ self.refresh()
+ self.canvas = canvas
+ self.init_line_items()
+
+ def is_selected(self):
+ return self in self.canvas.selected_items
+
+ def get_opposite_port(self, port: 'CustomPort') -> 'CustomPort':
+ """
+
+ :param port:某个端口
+ :return: 对面的端口
+ """
+ if port is self.start_port:
+ return self.end_port
+ else:
+ return self.start_port
+
+ def remove_mid_point(self, mid_point: 'CustomMidPoint'):
+ """
+ 移除中间节点
+ :param mid_point:
+ :return:
+ """
+ index = self.center_points.index(mid_point)
+ point_to_remove = self.center_points.pop(index)
+ line_to_remove = self.line_items.pop(index)
+ self.canvas.removeItem(point_to_remove)
+ self.canvas.removeItem(line_to_remove)
+ self.canvas.removeItem(mid_point)
+ self.refresh_line_items()
+ self.update()
+ self.canvas.update_viewport()
+
+ def bind_line_item_signals(self, line_item: 'PMGGraphicsLineItem'):
+ """
+ 绑定线段的信号
+ :param line_item:
+ :return:
+ """
+ line_item.signal_mouse_doubleclicked.connect(self.on_line_item_double_clicked)
+ line_item.signal_mouse_pressd.connect(self.on_line_item_pressed)
+ line_item.signal_hover_enter.connect(self.on_line_item_hover_enter)
+ line_item.signal_hover_leave.connect(self.on_line_item_hover_leave)
+
+ def connect_midpoint_signals(self, mid_point: 'CustomMidPoint'):
+ mid_point.signal_double_clicked.connect(self.remove_mid_point)
+ mid_point.point_dragged.connect(self.refresh)
+
+ def add_mid_point(self, line_item: 'PMGGraphicsLineItem', pos: QPointF):
+ """
+ 增加中间节点
+ :param line_item:
+ :param pos:
+ :return:
+ """
+ index = self.line_items.index(line_item)
+ new_center_point = CustomMidPoint(pos, line=self)
+ self.canvas.addItem(new_center_point)
+ self.center_points.insert(index, new_center_point)
+ self.connect_midpoint_signals(new_center_point)
+ if index == 0:
+ last_pos = self.start_port.center_pos
+ else:
+ last_pos = self.center_points[index].center_pos
+ next_pos = new_center_point.center_pos
+ new_line_item = PMGGraphicsLineItem(QLineF(last_pos, next_pos))
+
+ # pen = QPen()
+ # pen.setColor(self.color)
+ # pen.setWidth(3)
+ # new_line_item.setPen(pen)
+ self.canvas.addItem(new_line_item)
+ self.line_items.insert(index, new_line_item)
+ self.bind_line_item_signals(new_line_item) # 绑定中间节点的信号。
+ print(len(self.line_items))
+ self.refresh_line_items()
+ self.update()
+ self.canvas.update_viewport()
+
+ def on_line_item_double_clicked(self, event: 'QGraphicsSceneMouseEvent', line_item: 'PMGGraphicsLineItem'):
+
+ self.add_mid_point(line_item, pos=event.scenePos())
+
+ def init_line_items(self):
+ """
+ 初始化线段
+ :return:
+ """
+ last_pos = self.start_port.center_pos
+ for p in self.center_points:
+ pos = p.center_pos
+ line_item = PMGGraphicsLineItem(QLineF(last_pos, pos))
+ self.bind_line_item_signals(line_item)
+
+ pen = QPen()
+ pen.setColor(self.color)
+ pen.setWidth(5)
+ line_item.setPen(pen)
+ self.canvas.addItem(line_item)
+ last_pos = pos
+ self.line_items.append(line_item)
+ line_item = PMGGraphicsLineItem(QLineF(last_pos, self.end_port.center_pos))
+ self.bind_line_item_signals(line_item)
+
+ self.canvas.addItem(line_item)
+ self.line_items.append(line_item)
+
+ def refresh_line_items(self):
+ """
+ 刷新背后的直线段。
+ :return:
+ """
+ last_pos = self.start_port.center_pos
+ if len(self.line_items) == 0:
+ return
+ for i, p in enumerate(self.center_points):
+ pos = p.center_pos
+ line = QLineF(last_pos, pos)
+ self.line_items[i].setLine(line)
+ last_pos = pos
+
+ line = QLineF(last_pos, self.end_port.center_pos)
+ self.line_items[-1].setLine(line)
+
+ def on_line_item_hover_enter(self, e: 'QGraphicsSceneHoverEvent'):
+ self.color = COLOR_HOVER
+ self.update()
+
+ def on_line_item_hover_leave(self, e: 'QGraphicsSceneHoverEvent'):
+ if not self.is_selected():
+ self.color = COLOR_LINE_NORMAL
+ self.update()
+
+ def on_line_item_pressed(self, e: 'QGraphicsSceneMouseEvent'):
+ """
+ 当线段被点击时触发的事件。
+ :param e:
+ :return:
+ """
+ if e.button() == Qt.LeftButton:
+ if not e.modifiers() == Qt.ControlModifier:
+ self.canvas.signal_clear_selection.emit()
+ self.canvas.select_item(self)
+ self.color = COLOR_SELECTED
+
+ def on_clear_selection(self):
+ """
+ 当清除选择时触发的事件。
+ :return:
+ """
+ self.color = COLOR_LINE_NORMAL
+ self.canvas.unselect_item(self)
+ # self.update()
+
+ def refresh(self):
+ """
+ 当刷新时触发的事件
+ :return:
+ """
+ self._refreshed = False
+ self.refresh_line_items()
+ self.update()
+ self._refreshed = True
+
+ def draw_arrow(self, QPainter, point_1: QPointF, point_2: QPointF) -> 'QPolygonF':
+ """
+ 绘制箭头。
+ :param QPainter:
+ :param point_1:
+ :param point_2:
+ :return:
+ """
+
+ line = QLineF(point_1, point_2)
+
+ v = line.unitVector()
+
+ v.setLength(20) # 改变单位向量的大小,实际就是改变箭头长度
+ v.translate(QPointF(int(line.dx() / 2), int(line.dy() / 2)))
+
+ n = v.normalVector() # 法向量
+ n.setLength(n.length() * 0.2) # 这里设定箭头的宽度
+ n2 = n.normalVector().normalVector() # 两次法向量运算以后,就得到一个反向的法向量
+ p1 = v.p2()
+ p2 = n.p2()
+ p3 = n2.p2()
+ # if PYSIDE2:
+ QPainter.drawPolygon([p1, p2, p3])
+ # else:
+ # QPainter.drawPolygon(p1, p2, p3)
+ return QPolygonF([p1, p2, p3, p1])
+
+ def paint(self, q_painter: 'QPainter', style_option_graphics_item: 'QStyleOptionGraphicsItem',
+ widget: 'QWidget' = None):
+
+ pen = QPen()
+ pen.setColor(self.color)
+ pen.setWidth(3)
+ pen.setJoinStyle(Qt.MiterJoin) # 让箭头变尖
+ q_painter.setPen(pen)
+
+ path = QPainterPath()
+
+ point1 = self.start_port
+ path.moveTo(self.start_port.center_pos)
+ # for i in self.line_items:
+ # i.setPen(QColor(255, 0, 0))
+ # i.update()
+ for p in self.center_points + [self.end_port]:
+ pen.setWidth(3)
+ q_painter.setPen(pen)
+ path.lineTo(p.center_pos)
+ q_painter.drawLine(QLineF(point1.center_pos, p.center_pos))
+ arrow = self.draw_arrow(q_painter, point1.center_pos, p.center_pos)
+ path.addPolygon(arrow) # 将箭头添加到连线上
+ point1 = p
+
+ def get_central_points_positions(self):
+ """
+ 获取所有中间节点的位置。
+ :return:
+ """
+ positions = []
+ for point in self.center_points:
+ positions.append((point.x(), point.y()))
+ return positions
+
+ def hoverEnterEvent(self, event: 'QGraphicsSceneHoverEvent') -> None:
+ """
+ 悬浮事件进入所触发的事件。
+ :param event:
+ :return:
+ """
+ self.color = COLOR_HOVER_PORT
+
+ self.update()
+
+ def on_delete(self):
+ """
+ 删除线对象
+ 首先从起始和结束的端口的连线中删除这个线;
+ 然后,从画布上移除自身所有的中间线段对象和中继点对象
+ 从画布上删除自身;
+ 从所有连线的列表中移除自身。
+ :return:
+ """
+ try:
+ self.start_port.connected_lines.remove(self)
+ except ValueError:
+ import traceback
+ traceback.print_exc()
+ try:
+ self.end_port.connected_lines.remove(self)
+ except ValueError:
+ import traceback
+ traceback.print_exc()
+ for line_item in self.line_items:
+ self.canvas.removeItem(line_item)
+ for mid_item in self.center_points:
+ self.canvas.removeItem(mid_item)
+ self.canvas.removeItem(self)
+ self.canvas.lines.remove(self)
+
+
+class CustomMidPoint(QGraphicsObject):
+ point_dragged = Signal(QGraphicsObject)
+ signal_double_clicked = Signal(object)
+
+ def __init__(self, pos: QPointF = None, line: 'CustomLine' = None):
+ super(CustomMidPoint, self).__init__()
+ self.setAcceptHoverEvents(True) # 接受鼠标悬停事件
+ self.relative_pos = (0, 0)
+ self.color = COLOR_NORMAL
+ self.size = (10, 10)
+ if pos is not None:
+ self.setPos(pos)
+ self.line = line
+ self.setZValue(1.0) # 设置层次。
+
+ def boundingRect(self):
+ return QRectF(0, 0, self.size[0], self.size[1])
+
+ @property
+ def center_pos(self):
+ return QPointF(self.x() + self.size[0] / 2, self.y() + self.size[1] / 2)
+
+ def mouseMoveEvent(self, event: 'QGraphicsSceneMouseEvent') -> None:
+ """
+ 鼠标拖动时触发的事件。
+ :param event:
+ :return:
+ """
+ mouse_x, mouse_y = event.scenePos().x(), event.scenePos().y()
+ self.setPos(round_position(QPointF(mouse_x, mouse_y)))
+ self.point_dragged.emit(self)
+
+ def paint(self, painter, styles, widget=None):
+ pen1 = QPen(Qt.SolidLine)
+ pen1.setColor(self.color)
+ painter.setPen(pen1)
+
+ brush1 = QBrush(Qt.SolidPattern)
+ brush1.setColor(self.color)
+ painter.setBrush(brush1)
+
+ painter.setRenderHint(QPainter.Antialiasing) # 反锯齿
+ painter.drawRoundedRect(self.boundingRect(), 10, 10)
+
+ def hoverEnterEvent(self, event: 'QGraphicsSceneHoverEvent') -> None:
+ self.color = COLOR_HOVER_MID_POINT
+ self.update()
+
+ def hoverLeaveEvent(self, event: 'QGraphicsSceneHoverEvent') -> None:
+ self.color = COLOR_NORMAL
+ self.update()
+
+ def mousePressEvent(self, evt: QGraphicsSceneMouseEvent):
+ if evt.button() == Qt.LeftButton:
+ pos = (evt.scenePos().x(), evt.scenePos().y())
+ self.relative_pos = (pos[0] - self.x(), pos[1] - self.y())
+
+ elif evt.button() == Qt.RightButton:
+ pass
+ elif evt.button() == Qt.MidButton:
+ pass
+
+ def paintEvent(self, QPaintEvent):
+ pen1 = QPen()
+ pen1.setColor(QColor(166, 66, 250))
+ painter = QPainter(self)
+ painter.setPen(pen1)
+ painter.begin(self)
+ painter.drawRoundedRect(self.boundingRect(), 10, 10) # 绘制函数
+ painter.end()
+
+ def mouseDoubleClickEvent(self, event: 'QGraphicsSceneMouseEvent') -> None:
+ self.signal_double_clicked.emit(self)
+ # self.line.remove_mid_point(self)
+
+
+class CustomPort(QGraphicsObject):
+ port_clicked = Signal(QGraphicsObject)
+
+ def __init__(self, port_id: str, text: str = 'port', content: Any = None, port_type='input'):
+ super(CustomPort, self).__init__()
+ self.setAcceptHoverEvents(True) # 接受鼠标悬停事件
+ self.relative_pos = (0, 0)
+ self.color = COLOR_NORMAL
+ self.id = port_id
+ self.text = text
+ self.size = (10, 10)
+ self.content = content
+ self.connected_lines = []
+ self.canvas = None
+ self.node: 'Node' = None
+
+ self.text_item = QGraphicsTextItem(parent=self)
+ self.port_type = port_type
+ if port_type == 'input':
+ self.text_item.setPos(10, -5)
+ else:
+ self.text_item.setPos(-30, -5)
+ self.text_item.setPlainText(self.text)
+
+ def set_text(self, text: str):
+ self.text_item.setPlainText(text)
+ self.text = text
+
+ def boundingRect(self):
+ return QRectF(0, 0, self.size[0], self.size[1])
+
+ @property
+ def center_pos(self):
+ return QPointF(self.x() + self.size[0] / 2, self.y() + self.size[1] / 2)
+
+ def paint(self, painter, styles, widget=None):
+ pen1 = QPen(Qt.SolidLine)
+ pen1.setColor(QColor(128, 128, 128))
+ painter.setPen(pen1)
+
+ brush1 = QBrush(Qt.SolidPattern)
+ brush1.setColor(self.color)
+ painter.setBrush(brush1)
+
+ painter.setRenderHint(QPainter.Antialiasing) # 反锯齿
+ painter.drawRoundedRect(self.boundingRect(), 10, 10)
+
+ def hoverEnterEvent(self, event: 'QGraphicsSceneHoverEvent') -> None:
+ self.color = COLOR_HOVER_PORT
+ self.update()
+
+ def hoverLeaveEvent(self, event: 'QGraphicsSceneHoverEvent') -> None:
+ self.color = COLOR_NORMAL
+ self.update()
+
+ def mousePressEvent(self, evt: QGraphicsSceneMouseEvent):
+ if evt.button() == Qt.LeftButton:
+ pos = (evt.scenePos().x(), evt.scenePos().y())
+ self.relative_pos = (pos[0] - self.x(), pos[1] - self.y())
+ self.port_clicked.emit(self)
+ elif evt.button() == Qt.RightButton:
+ pass
+ elif evt.button() == Qt.MidButton:
+ pass
+
+ def paintEvent(self, paint_event):
+ pen1 = QPen()
+ pen1.setColor(QColor(166, 66, 250))
+ painter = QPainter(self)
+ painter.setPen(pen1)
+ painter.begin(self)
+ painter.drawRoundedRect(self.boundingRect(), 10, 10) # 绘制函数
+ painter.end()
+
+ def get_pos(self) -> Tuple[int, int]:
+ pos = self.pos()
+ return pos.x(), pos.y()
+
+ def get_connected_lines(self) -> List['CustomLine']:
+ if len(self.connected_lines) == 0:
+ return []
+ else:
+ return self.connected_lines
+
+ def get_connected_port(self) -> List['CustomPort']:
+ """
+ 获取连接的节点
+ :return:
+ """
+ lines = self.get_connected_lines()
+ if len(lines) == 0:
+ return []
+ port_list = []
+ for l in lines:
+ port_list.append(l.get_opposite_port(self))
+ return port_list
+
+ def on_delete(self):
+ for line in self.connected_lines:
+ line.on_delete()
+ self.scene().removeItem(self)
+
+ def parse_id(self) -> Tuple[str, str, str]:
+ """
+ 解析id。id由三个元素构成,由冒号分割。
+ 如3:input:2,意思就是id3为3的节点中,id为2的输入端口。注意
+ :return:
+ """
+ node_id, type, port_id = self.id.split(':')
+ return node_id, type, port_id
+
+ def __repr__(self):
+ return super(CustomPort, self).__repr__() + 'id = ' + str(self.id)
+
+
+class CustomRect(QGraphicsItem):
+ def __init__(self, node: 'Node' = None):
+ super(CustomRect, self).__init__()
+ self.setFlags(QGraphicsItem.ItemIsSelectable) # 只有设置了可以选取才能被选中。
+ self.setAcceptHoverEvents(True) # 接受鼠标悬停事件
+ self.relative_pos = (0, 0)
+ self.color = COLOR_NORMAL
+ self.node = node
+
+ def boundingRect(self):
+ return QRectF(0, 0, 120, 120)
+
+ def paint(self, painter, styles, widget=None):
+ pen1 = QPen(Qt.SolidLine)
+ pen1.setColor(QColor(128, 128, 128))
+ painter.setPen(pen1)
+
+ brush1 = QBrush(Qt.SolidPattern)
+ brush1.setColor(self.color)
+ painter.setBrush(brush1)
+
+ painter.setRenderHint(QPainter.Antialiasing) # 反锯齿
+ painter.drawRoundedRect(self.boundingRect(), 10, 10)
+
+ def hoverEnterEvent(self, event: 'QGraphicsSceneHoverEvent') -> None:
+ self.color = COLOR_HOVER
+ self.update()
+
+ def hoverLeaveEvent(self, event: 'QGraphicsSceneHoverEvent') -> None:
+ self.color = COLOR_NORMAL
+ if self.isSelected():
+ self.color = COLOR_SELECTED
+ self.update()
+
+ def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None:
+ self.setPos(round_position(
+ QPointF(event.scenePos().x() - self.relative_pos[0],
+ event.scenePos().y() - self.relative_pos[1])))
+ self.node.refresh_pos()
+
+ def mouseDoubleClickEvent(self, event: 'QGraphicsSceneMouseEvent') -> None:
+ """
+ [!TODO]整理这里的代码,节点编辑这一块的代码非常乱!
+ :param event:
+ :return:
+ """
+ self.on_value_show_requested()
+ # self.on_edit_properties_requested()
+
+ def on_value_show_requested(self):
+ from PySide2.QtWidgets import QDialog, QVBoxLayout
+ val_dlg = QDialog()
+ val_dlg.setLayout(QVBoxLayout())
+ text_show = QTextEdit()
+ text = 'inputs:\n' + repr(self.node.content.input_args) + '\n' + 'results:\n' + repr(self.node.content.results)
+ text_show.setText(text)
+ val_dlg.layout().addWidget(text_show)
+ val_dlg.exec_()
+
+ def mousePressEvent(self, evt: QGraphicsSceneMouseEvent):
+ if evt.button() == Qt.LeftButton:
+ pos = (evt.scenePos().x(), evt.scenePos().y())
+ self.relative_pos = (pos[0] - self.x(), pos[1] - self.y())
+ if not evt.modifiers() == Qt.ControlModifier:
+ self.scene().signal_clear_selection.emit()
+ self.scene().select_item(self)
+ self.color = COLOR_SELECTED
+ elif evt.button() == Qt.RightButton:
+ pass
+ elif evt.button() == Qt.MidButton:
+ pass
+
+ def paintEvent(self, paintEvent):
+ pen1 = QPen()
+ pen1.setColor(self.color)
+ painter = QPainter(self)
+ painter.setPen(pen1)
+ painter.begin(self)
+ painter.drawRoundedRect(self.boundingRect(), 10, 10) # 绘制函数
+ painter.end()
+
+ def on_clear_selection(self):
+ """
+ 当清除选择时触发的事件。
+ :return:
+ """
+ self.color = COLOR_NORMAL
+ if self.scene() is not None:
+ self.scene().unselect_item(self)
+
+ def on_delete(self):
+ self.node.on_delete()
+ pass
diff --git a/pyminer/widgets/flowchart/core/flow_node.py b/pyminer/widgets/flowchart/core/flow_node.py
new file mode 100644
index 0000000000000000000000000000000000000000..86cd38c154db43a89127c61343f8d95ae70844d4
--- /dev/null
+++ b/pyminer/widgets/flowchart/core/flow_node.py
@@ -0,0 +1,438 @@
+import os
+import sys
+from typing import Tuple, List, Dict, Union, TYPE_CHECKING, Callable
+import json
+from PySide2.QtWidgets import QGraphicsTextItem, QGraphicsPixmapItem, QMessageBox
+from PySide2.QtCore import QObject, QPointF
+from PySide2.QtGui import QPixmap
+
+from widgets.flowchart.core.flow_items import CustomRect, CustomPort
+from widgets import get_parent_path, assert_in
+
+if TYPE_CHECKING:
+ from widgets.flowchart.core.flowchart_widget import PMGraphicsScene, PMFlowWidget
+from widgets.flowchart.core.flow_content import FlowContentEditableFunction, FlowContentForFunction, FlowContentError
+from widgets.flowchart.nodes.docparser import parse_doc
+
+
+class Node(QObject):
+ """
+ Node是一个节点的抽象
+ """
+
+ def __init__(self, canvas: 'PMGraphicsScene', node_id, text: str = '', input_ports: List[CustomPort] = None,
+ output_ports: List[CustomPort] = None, icon_path=r'', look: Dict[str, str] = None):
+ super(Node, self).__init__()
+ assert isinstance(look, dict)
+ self.look = look
+ direction = self.look.get('direction')
+ self.direction = 'E' if direction is None else direction # 输出端口的方向
+ assert_in(self.direction, ['E', 'W'])
+ self.look['direction'] = self.direction
+
+ self.id = node_id
+ self.text = text if text != '' else node_id
+ self.base_rect = CustomRect(self)
+
+ icon_path = os.path.join(get_parent_path(__file__), 'icons', 'logo.png') if icon_path == '' else icon_path
+ self.icon_path = icon_path
+ pix_map = QPixmap(icon_path)
+ self.pix_map_item = QGraphicsPixmapItem(pix_map, parent=self.base_rect)
+ self.pix_map_item.setPos(20, 20)
+
+ self.text_item = QGraphicsTextItem(parent=self.base_rect)
+
+ start_left = 50
+ self.text_item.setPos(start_left, 10)
+ self.text_item.setPlainText(self.text)
+
+ self.internal_value_text = QGraphicsTextItem(parent=self.base_rect)
+ self.internal_value_text.setPos(start_left, 30)
+ self.internal_value_text.setPlainText('')
+
+ self.status_text = QGraphicsTextItem(parent=self.base_rect)
+ self.status_text.setPos(start_left, 100)
+ self.status_text.setPlainText('wait')
+
+ self.input_ports = input_ports
+ self.output_ports = output_ports
+
+ self.input_ports_dic = {p.id: p for p in input_ports}
+ self.output_ports_dic = {p.id: p for p in output_ports}
+ self.canvas = canvas
+ # self.set_content(content)
+ self.setup()
+
+ def display_internal_values(self, val_str: str = ''):
+ self.internal_value_text.setPlainText(val_str)
+
+ def set_content(self, content: 'FlowContentEditableFunction' = None):
+ self.content: 'FlowContentEditableFunction' = content if content is not None else FlowContentEditableFunction(
+ self, '')
+ self.content.signal_exec_finished.connect(self.on_exec_finished)
+ self.content.signal_exec_started.connect(self.on_exec_started)
+ self.content.signal_error_occurs.connect(self.on_error_occurs)
+ self.display_internal_values(self.content.format_param())
+
+ def on_error_occurs(self, error: FlowContentError):
+ flow_widget: PMFlowWidget = self.canvas.flow_widget
+ flow_widget.on_error_occurs(error)
+
+ def on_exec_started(self, content):
+ """
+ 执行前进行的操作
+ :param content:
+ :return:
+ """
+ self.status_text.setPlainText(content)
+
+ def on_exec_finished(self, content: str):
+ """
+ 执行完成后进行的操作
+ :param content:
+ :return:
+ """
+ self.status_text.setPlainText(content)
+
+ #
+ def reset(self):
+ self.status_text.setPlainText('wait')
+
+ def get_port_index(self, port: CustomPort):
+ """
+ 获取端口的索引
+ :param port:
+ :return:
+ """
+ return self.input_ports.index(port) if port.port_type == 'input' else self.output_ports.index(port)
+
+ def set_icon(self, icon_path: str):
+ pass
+
+ def set_pos(self, x: int, y: int):
+ """
+ 设置位置,左上角角点
+ :param x:
+ :param y:
+ :return:
+ """
+ self.base_rect.setPos(x, y)
+ self.refresh_pos()
+
+ def get_pos(self) -> Tuple[int, int]:
+ """
+ 获取位置,左上角角点
+ :return:
+ """
+ pos = self.base_rect.pos()
+ return pos.x(), pos.y()
+
+ def refresh_pos(self):
+ """
+ 刷新位置。当节点被拖动的时候,此方法会被触发。
+ """
+ y = self.base_rect.y()
+ dy_input = self.base_rect.boundingRect().height() / (1 + len(self.input_ports))
+ dy_output = self.base_rect.boundingRect().height() / (1 + len(self.output_ports))
+ if self.direction == 'E':
+ x_input = self.base_rect.x() + 5
+ x_output = self.base_rect.x() + self.base_rect.boundingRect().width() - 15
+ elif self.direction == 'W':
+ x_input = self.base_rect.x() + self.base_rect.boundingRect().width() - 15
+ x_output = self.base_rect.x() + 5
+ else:
+ raise NotImplementedError
+ for i, p in enumerate(self.input_ports):
+ p.setPos(QPointF(x_input, y + int(dy_input * (1 + i))))
+ for i, p in enumerate(self.output_ports):
+ p.setPos(QPointF(x_output, y + int(dy_output * (1 + i))))
+ self.canvas.signal_item_dragged.emit('')
+
+ def setup(self):
+ self.base_rect.setPos(80, 80)
+ self.canvas.signal_clear_selection.connect(self.base_rect.on_clear_selection)
+ self.canvas.addItem(self.base_rect)
+ for p in self.input_ports + self.output_ports:
+ self.canvas.addItem(p)
+ p.port_clicked.connect(self.on_port_clicked)
+ p.node = self
+ self.refresh_pos()
+
+ def on_port_clicked(self, port: 'CustomPort'):
+
+ if self.canvas.drawing_lines:
+ if self.canvas.line_start_port is not port:
+ if not port.port_type == self.canvas.line_start_port.port_type:
+ if self.canvas.allow_multiple_input or (
+ (not self.canvas.allow_multiple_input) and len(port.connected_lines) == 0):
+ self.canvas.connect_port(port)
+
+ else:
+ if port.port_type == 'output':
+ self.canvas.drawing_lines = True
+ # if self.canvas.item
+
+ self.canvas.line_start_point = port.center_pos
+ self.canvas.line_start_port = port
+
+ def on_delete(self):
+ for port in self.input_ports + self.output_ports:
+ port.canvas = self.canvas
+ port.on_delete()
+ self.canvas.removeItem(self.base_rect)
+ self.canvas.nodes.remove(self)
+ self.deleteLater()
+
+ def __repr__(self):
+ s = super(Node, self).__repr__()
+ return s + repr(self.input_ports) + repr(self.output_ports)
+
+ def get_port(self, port_id: str) -> 'CustomPort':
+ for port in self.input_ports + self.output_ports:
+ if port_id == port.id:
+ return port
+ return None
+
+ def add_port(self, port: CustomPort):
+ """
+ 添加一个端口
+ :param port:
+ :return:
+ """
+
+ port_type = port.port_type
+ if port_type == 'input':
+ self.input_ports.append(port)
+ self.input_ports_dic[port.id] = port
+ elif port_type == 'output':
+ self.output_ports.append(port)
+ self.output_ports_dic[port.id] = port
+ else:
+ raise ValueError('port type invalid')
+ if port.node is None:
+ port.node = self
+ self.refresh_pos()
+ self.canvas.addItem(port)
+ port.port_clicked.connect(self.on_port_clicked)
+
+ return port
+
+ def remove_port(self, port: CustomPort):
+ """
+ 删除一个端口
+ :param port:
+ :return:
+ """
+
+ port_type = port.port_type
+ if port_type == 'input':
+ self.input_ports.remove(port)
+ self.input_ports_dic.pop(port.id)
+ elif port_type == 'output':
+ self.output_ports.remove(port)
+ self.output_ports_dic.pop(port.id)
+ else:
+ raise ValueError('port type invalid')
+
+ port.on_delete()
+ self.refresh_pos()
+
+ def change_property(self, property: Dict[str, Union[int, str]]):
+ """
+ 改变各个端口的文字、端口的数目以及文字。
+ :param property:
+ :return:
+ """
+ self.text = property['text']
+ self.text_item.setPlainText(self.text)
+ self.content.update_settings(property)
+ self.valid_port_ids = []
+ if property.get('inputs') is not None:
+ for input_id, input_text in zip(property['inputs'][0], property['inputs'][1]):
+ self.valid_port_ids.append(input_id)
+ p = self.get_port(input_id)
+ if p is None:
+ p = self.add_port(CustomPort(port_id=input_id, text=input_text, port_type='input'))
+ p.set_text(input_text)
+ for p in self.input_ports:
+ if p.id not in self.valid_port_ids:
+ self.remove_port(p)
+
+ if property.get('outputs') is not None:
+ for output_id, output_text in zip(property['outputs'][0], property['outputs'][1]):
+ self.valid_port_ids.append(output_id)
+ p = self.get_port(output_id)
+ if p is None:
+ p = self.add_port(CustomPort(port_id=output_id, text=output_text, port_type='output'))
+ p.set_text(output_text)
+
+ for p in self.output_ports:
+ if p.id not in self.valid_port_ids:
+ self.remove_port(p)
+
+ def change_ports_property(self, property: Dict[str, Union[int, str]]):
+ """
+ 改变各个端口的文字、端口的数目以及文字。
+ :param property:
+ :return:
+ """
+ self.text = property['text']
+ self.text_item.setPlainText(self.text)
+ self.valid_port_ids = []
+ if property.get('inputs') is not None:
+ for input_id, input_text in zip(property['inputs'][0], property['inputs'][1]):
+ self.valid_port_ids.append(input_id)
+ p = self.get_port(input_id)
+ if p is None:
+ p = self.add_port(CustomPort(port_id=input_id, text=input_text, port_type='input'))
+ p.set_text(input_text)
+ for p in self.input_ports:
+ if p.id not in self.valid_port_ids:
+ self.remove_port(p)
+
+ if property.get('outputs') is not None:
+ for output_id, output_text in zip(property['outputs'][0], property['outputs'][1]):
+ self.valid_port_ids.append(output_id)
+ p = self.get_port(output_id)
+ if p is None:
+ p = self.add_port(CustomPort(port_id=output_id, text=output_text, port_type='output'))
+ p.set_text(output_text)
+
+ for p in self.output_ports:
+ if p.id not in self.valid_port_ids:
+ self.remove_port(p)
+
+ def on_edit_ports(self):
+ from widgets import PMGPanel
+ from PySide2.QtWidgets import QDialog, QVBoxLayout
+ dlg = QDialog(self.base_rect.scene().flow_widget)
+ sp = PMGPanel()
+ p: 'CustomPort' = None
+ input_ids, output_ids = [], []
+ input_texts, output_texts = [], []
+ self._last_var = 0
+
+ def new_id_input():
+ node_id = self.id
+ max_val = 0
+ for p in self.input_ports:
+ n, t, p = p.parse_id()
+ if max_val < int(p):
+ max_val = int(p)
+ self._last_var += 1
+ return '%s:input:%d' % (node_id, max_val + self._last_var)
+
+ def new_id_output():
+ node_id = self.id
+ max_val = 0
+ for p in self.output_ports:
+ n, t, p = p.parse_id()
+ if max_val < int(p):
+ max_val = int(p)
+ self._last_var += 1
+ return '%s:output:%d' % (node_id, max_val + self._last_var)
+
+ for p in self.input_ports:
+ input_ids.append(p.id)
+ input_texts.append(p.text)
+
+ for p in self.output_ports:
+ output_ids.append(p.id)
+ output_texts.append(p.text)
+
+ views = []
+ # views += self.content.get_settings_params()
+ views += [('line_ctrl', 'text', 'Node Text', self.text),
+ ]
+ if self.content.ports_changable[0]:
+ views.append(('list_ctrl', 'inputs', 'Set Inputs', [input_ids, input_texts], new_id_input))
+ if self.content.ports_changable[1]:
+ views.append(('list_ctrl', 'outputs', 'Set Outputs', [output_ids, output_texts], new_id_output))
+ sp.set_items(views)
+ dlg.setLayout(QVBoxLayout())
+ dlg.layout().addWidget(sp)
+ dlg.exec_()
+
+ dic = sp.get_value()
+ self.change_ports_property(dic)
+
+ def on_edit_properties_requested(self):
+ """
+ Show Settings Panel.
+ If It was customized node,it will call the function of the content.
+ Or the default settings parameters will be got.
+ Returns:
+
+ """
+ from widgets import PMGPanel
+ from PySide2.QtWidgets import QDialog, QVBoxLayout
+
+ if isinstance(self.content, FlowContentForFunction):
+ dlg = QDialog(self.base_rect.scene().flow_widget)
+ sp = PMGPanel()
+ p: 'CustomPort' = None
+ input_ids, output_ids = [], []
+ input_texts, output_texts = [], []
+ self._last_var = 0
+
+ def new_id_input():
+ node_id = self.id
+ max_val = 0
+ for p in self.input_ports:
+ n, t, p = p.parse_id()
+ if max_val < int(p):
+ max_val = int(p)
+ self._last_var += 1
+ return '%s:input:%d' % (node_id, max_val + self._last_var)
+
+ def new_id_output():
+ node_id = self.id
+ max_val = 0
+ for p in self.output_ports:
+ n, t, p = p.parse_id()
+ if max_val < int(p):
+ max_val = int(p)
+ self._last_var += 1
+ return '%s:output:%d' % (node_id, max_val + self._last_var)
+
+ for p in self.input_ports:
+ input_ids.append(p.id)
+ input_texts.append(p.text)
+
+ for p in self.output_ports:
+ output_ids.append(p.id)
+ output_texts.append(p.text)
+
+ views = []
+ views += self.content.get_settings_params()
+ views += [('line_ctrl', 'text', 'Node Text', self.text),
+ ]
+ if self.content.ports_changable[0]:
+ views.append(('list_ctrl', 'inputs', 'Set Inputs', [input_ids, input_texts], new_id_input))
+ if self.content.ports_changable[1]:
+ views.append(('list_ctrl', 'outputs', 'Set Outputs', [output_ids, output_texts], new_id_output))
+ sp.set_items(views)
+ dlg.setLayout(QVBoxLayout())
+ dlg.layout().addWidget(sp)
+ dlg.exec_()
+
+ dic = sp.get_value()
+ self.change_property(dic)
+ else:
+ try:
+ self.content.settings_window_requested(self.canvas.flow_widget)
+ except Exception as e:
+ import traceback
+ exc = traceback.format_exc()
+ print(exc)
+ QMessageBox.warning(self.canvas.flow_widget, 'Error', str(e))
+
+ def invert(self):
+ self.direction = 'W' if self.direction == 'E' else 'E'
+ self.refresh_pos()
+ self.look['direction'] = self.direction
+
+
+def make_calculation_node(node_function: Callable) -> 'Node':
+ info = parse_doc(node_function)
+ content = FlowContentEditableFunction(code=0)
diff --git a/pyminer/widgets/flowchart/core/flowchart_scene.py b/pyminer/widgets/flowchart/core/flowchart_scene.py
new file mode 100644
index 0000000000000000000000000000000000000000..ed046024d7098e82a01f71d6209a84e1b0b8e9b1
--- /dev/null
+++ b/pyminer/widgets/flowchart/core/flowchart_scene.py
@@ -0,0 +1,368 @@
+"""
+node properties:
+id:str
+text:str
+icon:str(path of icon)
+ports:{}
+content:{}
+
+properties of contents:
+code:str
+type:str
+params:List[str]
+"""
+import json
+import logging
+import os
+import time
+from typing import List, Dict, TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from widgets import PMGFlowContent
+from widgets.flowchart.core.flow_content import FlowContentForFunction, flowcontent_types
+from widgets.flowchart.core.flow_items import CustomPort, CustomLine, CustomMidPoint, CustomRect
+from widgets.flowchart.core.flow_node import Node
+from widgets.utilities.uilogics.undomanager import UndoManager
+from PySide2.QtCore import QLineF, Qt, QPointF, Signal
+from PySide2.QtGui import QPen, QColor, QKeyEvent
+from PySide2.QtWidgets import QGraphicsView, \
+ QGraphicsScene, QGraphicsSceneMouseEvent, QMenu, QGraphicsSceneContextMenuEvent
+
+logger = logging.getLogger(__name__)
+COLOR_NORMAL = QColor(212, 227, 242)
+COLOR_HOVER = QColor(255, 200, 00)
+COLOR_HOVER_PORT = QColor(0, 0, 50)
+
+
+class PMGraphicsScene(QGraphicsScene):
+ signal_item_dragged = Signal(str) # 拖拽控件发生的事件。
+ signal_port_clicked = Signal(str) # 点击端口的事件
+ signal_clear_selection = Signal() # 清除选择的事件
+
+ def __init__(self, parent=None, graphics_view: 'QGraphicsView' = None, flow_widget: 'PMFlowWidget' = None,
+ allow_multiple_input=False):
+ super().__init__(parent)
+ self.undo_manager = UndoManager()
+ self.call_id_list: List = []
+ self.lines: List[CustomLine] = []
+ self.nodes: List['Node'] = []
+ self.selected_items = []
+ self.drawing_lines = False
+ self.line_start_port = None
+ self.line_start_point = None
+ self.line_end_port = None
+ self.node_index_to_execute = 0
+ self.line = self.addLine(0, 0, 1, 1, QPen())
+ self.graphics_view = graphics_view
+ self.flow_widget: 'PMFlowWidget' = flow_widget
+ self.allow_multiple_input = allow_multiple_input
+
+ def contextMenuEvent(self, event: 'QGraphicsSceneContextMenuEvent') -> None:
+ super(PMGraphicsScene, self).contextMenuEvent(event)
+ self.menu = QMenu()
+ node = self.get_rect_under_mouse().node
+
+ self.menu.addAction('Delete Selected').triggered.connect(lambda x: self.delete_selected_item())
+ print(node)
+ if node is not None:
+ self.menu.addAction('Invert').triggered.connect(lambda x: self.invert_node(node))
+ base_rect = self.get_rect_under_mouse()
+ if base_rect is not None:
+ self.menu.addAction('Edit').triggered.connect(lambda x: base_rect.node.on_edit_properties_requested())
+ self.menu.addAction('Edit Ports').triggered.connect(lambda x: base_rect.node.on_edit_ports())
+ self.menu.exec_(event.screenPos())
+
+ def keyPressEvent(self, event: 'QKeyEvent') -> None:
+ if event.key() == Qt.Key_Delete:
+ self.delete_selected_item()
+ elif event.key() == Qt.Key_Escape:
+ if self.drawing_lines:
+ self.drawing_lines = False
+ self.line.hide()
+
+ def mouseReleaseEvent(self, event: 'QGraphicsSceneMouseEvent') -> None:
+ super().mouseReleaseEvent(event)
+ self.update_viewport()
+
+ def mouseMoveEvent(self, event: 'QGraphicsSceneMouseEvent') -> None:
+ super(PMGraphicsScene, self).mouseMoveEvent(event)
+ if self.drawing_lines:
+ self.line.show()
+ self.line_end_point = event.scenePos()
+ self.line.setLine(QLineF(self.line_end_point, self.line_start_point))
+ self.sceneRect()
+ self.update_viewport()
+
+ def connect_port(self, end_port):
+ self.line.hide()
+ line = CustomLine(self.line_start_port, end_port, self)
+ self.lines.append(line)
+ self.addItem(line)
+ self.signal_item_dragged.connect(line.refresh)
+ self.drawing_lines = False
+ self.add_stat()
+
+ def find_port(self, port_id: int):
+ for n in self.nodes:
+ for p in n.input_ports + n.output_ports:
+ if p.id == port_id:
+ return p
+ return None
+
+ def find_node(self, node_id: str) -> Node:
+ for n in self.nodes:
+ if n.id == node_id:
+ return n
+ return None
+
+ def new_id(self) -> str:
+ max_id = 0
+ for n in self.nodes:
+ a = int(n.id)
+ if a > max_id:
+ max_id = a
+ new_id = max_id + 1
+ return str(new_id)
+
+ def add_node(self, node: 'Node' = None):
+ """
+ 添加节点
+ Args:
+ node:
+
+ Returns:
+
+ """
+ assert isinstance(node, Node)
+
+ self.nodes.append(node)
+ self.add_stat()
+
+ def get_rect_under_mouse(self) -> CustomRect:
+ # items =[]
+ for n in self.nodes:
+ if n.base_rect.isUnderMouse():
+ return n.base_rect
+ return None
+
+ def get_selected_base_rects(self) -> List[CustomRect]:
+ items = []
+ for selected_item in self.selected_items:
+ print(self.selected_items)
+ if isinstance(selected_item, CustomRect):
+ items.append(selected_item)
+ return items
+
+ def invert_node(self, node: Node):
+ if node is not None:
+ node.invert()
+ self.update_viewport()
+
+ def delete_selected_item(self):
+ """
+ 删除被选中的物品
+ :return:
+ """
+ self.add_stat()
+ for selected_item in self.selected_items:
+ if hasattr(selected_item, 'on_delete'):
+ selected_item.on_delete()
+ self.selected_items = []
+
+ def unselect_item(self, item):
+ if item in self.selected_items:
+ self.selected_items.remove(item)
+
+ def select_item(self, item):
+ if item not in self.selected_items:
+ self.selected_items.append(item)
+
+ def topo_sort(self, draw_graph=False):
+ """
+ 拓扑排序
+ :return:
+ """
+ lines = []
+ for l in self.lines:
+ start_id, end_id = l.start_port.node.id, l.end_port.node.id
+ lines.append((start_id, end_id))
+ import networkx
+
+ DG = networkx.DiGraph(lines)
+ if draw_graph:
+ networkx.draw_spring(DG, with_labels=True)
+ import matplotlib.pyplot as plt
+ plt.show()
+ return list(networkx.topological_sort(DG))
+
+ def load_flowchart(self, path=''):
+
+ file = './examples/flowchart_stat_demo.json' if path == '' else path
+ if not os.path.exists(file):
+ return
+
+ with open(file, 'r') as f:
+ text = f.read()
+ self.flowchart_from_json(text)
+
+ def add_stat(self):
+ self.undo_manager.push(self.flowchart_to_json())
+
+ def undo(self):
+ """
+ 重做
+ :return:
+ """
+ result = self.undo_manager.undo()
+ print('undo', result)
+ if result is not None:
+ self.reset_stat(result)
+
+ def redo(self):
+ """
+ 撤销
+ :return:
+ """
+ result = self.undo_manager.redo()
+ print(result)
+ if result is not None:
+ self.reset_stat(result)
+
+ def dump_flowchart(self, path=''):
+ t0 = time.time()
+ file = './examples/flowchart_stat_demo.json' if path == '' else path
+ json_str = self.flowchart_to_json()
+ with open(file, 'w') as f:
+ f.write(json_str)
+ t1 = time.time()
+ print('time elapsed for dumping flowchart', t1 - t0)
+
+ def flowchart_from_json(self, json_str):
+ t0 = time.time()
+ fc_info_dic: Dict[str, Dict] = json.loads(json_str)
+ nodes_dic = fc_info_dic['nodes']
+ connections: List = fc_info_dic['connections']
+
+ for k in nodes_dic.keys():
+ node_property = nodes_dic[k]
+ node_name = node_property['id']
+ node_pos = node_property['pos']
+ node_text = node_property['text']
+ node_icon_path = node_property['icon']
+ node_content = node_property['content']
+ node_look = node_property.get('look')
+ node_look = node_look if node_look is not None else {}
+ input_ports = []
+ for input_port_id in node_property['input_ports'].keys():
+ port_property = node_property['input_ports'][input_port_id]
+ port = CustomPort(port_id=input_port_id, text=port_property['text'], content=port_property['contents'],
+ port_type='input')
+ input_ports.append(port)
+ output_ports = []
+ for output_port_id in node_property['output_ports'].keys():
+ port_property = node_property['output_ports'][output_port_id]
+ port = CustomPort(port_id=output_port_id, text=port_property['text'], content=port_property['contents'],
+ port_type='output')
+ output_ports.append(port)
+
+ node = Node(self, node_name, text=node_text, input_ports=input_ports, output_ports=output_ports,
+ icon_path=node_icon_path, look=node_look)
+ content_type = flowcontent_types.get(node_content.get('type'))
+ if content_type is not None:
+ content: 'FlowContentForFunction' = content_type(node=node)
+ code = node_content['code']
+ content.set_function(code, 'function')
+ params = node_content.get('params')
+ content.set_params([] if params is None else params)
+ ports_changable = node_content.get('ports_changable')
+ ports_changable = ports_changable if ports_changable is not None else [False, False]
+ content.ports_changable = ports_changable
+ else:
+ content: 'PMGFlowContent' = self.flow_widget.node_manager.content_classes.get(
+ node_content.get('type'))()
+ content.node = node
+ content.class_name = node_content.get('type')
+ content.load_info(node_content.get('info'))
+ node.set_content(content)
+ node.set_pos(*node_pos)
+ self.nodes.append(node)
+ for line_property in connections:
+ start_id, end_id = line_property['start_id'], line_property['end_id']
+ start_port, end_port = self.find_port(start_id), self.find_port(end_id)
+ mid_positions = line_property['mid_positions']
+ mid_points = []
+ for pos in mid_positions:
+ mid_points.append(CustomMidPoint(pos=QPointF(*pos)))
+
+ line = CustomLine(canvas=self, start_port=start_port, end_port=end_port, mid_points=mid_points)
+ self.addItem(line)
+ self.signal_item_dragged.connect(line.refresh)
+ self.lines.append(line)
+ t1 = time.time()
+ print('time elapsed for loading flowchart:', t1 - t0)
+ # self.add_stat()
+
+ def reset_status(self):
+ self.drawing_lines = False
+ self.nodes = []
+ self.lines = []
+ self.selected_items = []
+ self.clear()
+ self.line_start_port = None
+ self.line_start_point = None
+ self.line_end_port = None
+ self.node_index_to_execute = 0
+ self.line = self.addLine(0, 0, 1, 1, QPen())
+
+ def reset_stat(self, json_str: str):
+ self.reset_status()
+ self.flowchart_from_json(json_str)
+
+ def flowchart_to_json(self) -> str:
+ fc_info = {}
+ connections = []
+ nodes_dic = {}
+ fc_info['nodes'] = nodes_dic
+ fc_info['connections'] = connections
+ for line in self.lines:
+ line_properties = {}
+ start_id = line.start_port.id
+ end_id = line.end_port.id
+ line_properties['start_id'] = start_id
+ line_properties['end_id'] = end_id
+ mid_positions = line.get_central_points_positions()
+ line_properties['mid_positions'] = mid_positions
+ connections.append(line_properties)
+ for node in self.nodes:
+ node_properties = {}
+ node_properties['text'] = node.text
+ node_properties['id'] = node.id
+ node_properties['pos'] = node.get_pos()
+ node_properties['icon'] = node.icon_path
+ node_properties['content'] = node.content.dump()
+ node_properties['look'] = node.look
+ input_ports_dic = {}
+ output_ports_dic = {}
+ for port in node.input_ports:
+ input_ports_dic[port.id] = {'id': port.id, 'pos': port.get_pos(), 'contents': {}, 'text': port.text}
+ for port in node.output_ports:
+ output_ports_dic[port.id] = {'id': port.id, 'pos': port.get_pos(), 'contents': {}, 'text': port.text}
+ node_properties['input_ports'] = input_ports_dic
+ node_properties['output_ports'] = output_ports_dic
+ nodes_dic[node.id] = node_properties
+ return json.dumps(fc_info, indent=4)
+
+ def reset(self):
+ self.clear()
+ self.call_id_list: List = []
+ self.lines: List[CustomLine] = []
+ self.nodes: List['Node'] = []
+ self.drawing_lines = False
+ self.line_start_port = None
+ self.line_start_point = None
+ self.line_end_port = None
+ self.node_index_to_execute = 0
+ self.line = self.addLine(0, 0, 1, 1, QPen())
+
+ def update_viewport(self):
+ self.graphics_view.viewport().update()
diff --git a/pyminer/widgets/flowchart/core/flowchart_widget.py b/pyminer/widgets/flowchart/core/flowchart_widget.py
new file mode 100644
index 0000000000000000000000000000000000000000..32f8b1c1db7198455951e5eb607c089202a2da2f
--- /dev/null
+++ b/pyminer/widgets/flowchart/core/flowchart_widget.py
@@ -0,0 +1,250 @@
+"""
+node properties:
+id:str
+text:str
+icon:str(path of icon)
+ports:{}
+content:{}
+
+properties of contents:
+code:str
+type:str
+params:List[str]
+"""
+import json
+import os
+import sys
+import time
+from typing import List, Dict
+
+from widgets.flowchart.core.flow_content import FlowContentForFunction, flowcontent_types, PMGFlowContent, \
+ FlowContentError
+
+from widgets.flowchart.core.flow_node import Node
+from widgets.flowchart.core.nodemanager import NodeManagerWidget
+from widgets.flowchart.core.flowchart_scene import PMGraphicsScene
+from widgets.widgets.basic.texts.statusreport import show_error
+from PySide2.QtCore import QSize, QCoreApplication, QLineF, Qt, QThread, Signal
+from PySide2.QtGui import QColor, QKeyEvent, QWheelEvent, QCloseEvent
+from PySide2.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QToolButton, QSpacerItem, QSizePolicy, QGraphicsView, \
+ QFrame, QApplication, QFileDialog, QMessageBox
+
+COLOR_NORMAL = QColor(212, 227, 242)
+COLOR_HOVER = QColor(255, 200, 00)
+COLOR_HOVER_PORT = QColor(0, 0, 50)
+
+
+class PMGraphicsView(QGraphicsView):
+ def wheelEvent(self, event: 'QWheelEvent') -> None:
+ """
+ 鼠标滚轮事件
+ :param event:
+ :return:
+ """
+ if event.modifiers() == Qt.ControlModifier:
+ if event.angleDelta().y() > 0:
+ self.scale(1.1, 1.1)
+ else:
+ self.scale(0.9, 0.9)
+
+
+class PMFlowWidget(QWidget):
+ def __init__(self, parent=None, path=''):
+ self._path = path
+ _translate = QCoreApplication.translate
+ super().__init__(parent)
+ # self.setup_ui()
+
+ def setup_ui(self):
+ self.setObjectName("tab_flow")
+ self.base_layout = QHBoxLayout(self)
+ self.verticalLayout_6 = QVBoxLayout()
+ self.base_layout.addLayout(self.verticalLayout_6)
+ self.verticalLayout_6.setContentsMargins(0, 0, 0, 0)
+ self.verticalLayout_6.setSpacing(0)
+ self.verticalLayout_6.setObjectName("verticalLayout_6")
+ self.widget_3 = QWidget(self)
+ self.widget_3.setMinimumSize(QSize(0, 30))
+ self.widget_3.setObjectName("widget_3")
+
+ # self.horizontal_layout_down = QHBoxLayout(self.widget_3)
+ self.horizontal_layout = QHBoxLayout(self.widget_3)
+ self.horizontal_layout.setContentsMargins(0, 0, 0, 0)
+ self.horizontal_layout.setSpacing(1)
+ self.horizontal_layout.setObjectName("horizontalLayout_6")
+
+ self.tool_button_run = QToolButton(self.widget_3)
+ self.tool_button_run.setText('Run_bg')
+ self.tool_button_run.setIconSize(QSize(25, 25))
+ self.tool_button_run.setObjectName("toolButton_4")
+
+ self.horizontal_layout.addWidget(self.tool_button_run)
+
+ self.tool_button_run_fg = QToolButton(self.widget_3)
+ self.tool_button_run_fg.setText('Run_fg')
+ self.horizontal_layout.addWidget(self.tool_button_run_fg)
+
+ self.tool_button_step = QToolButton(self.widget_3)
+ self.tool_button_step.setText('Step')
+ self.horizontal_layout.addWidget(self.tool_button_step)
+
+ self.tool_button_save = QToolButton(self.widget_3)
+ self.tool_button_save.setText('Save')
+ self.horizontal_layout.addWidget(self.tool_button_save)
+
+ self.tool_button_reset = QToolButton(self.widget_3)
+ self.tool_button_reset.setText('Reset')
+ self.horizontal_layout.addWidget(self.tool_button_reset)
+
+ self.tool_button_undo = QToolButton(self.widget_3)
+ self.tool_button_undo.setText('Undo')
+ self.horizontal_layout.addWidget(self.tool_button_undo)
+ # self.tool_button_undo.setEnabled(False)
+
+ self.tool_button_redo = QToolButton(self.widget_3)
+ self.tool_button_redo.setText('Redo')
+ self.horizontal_layout.addWidget(self.tool_button_redo)
+ # self.tool_button_redo.setEnabled(False)
+
+ spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+ self.horizontal_layout.addItem(spacerItem)
+ self.verticalLayout_6.addWidget(self.widget_3)
+
+ self.graphicsView = PMGraphicsView(self)
+
+ self.graphicsView.setFrameShape(QFrame.NoFrame)
+ self.graphicsView.setObjectName("graphicsView")
+ self.verticalLayout_6.addWidget(self.graphicsView)
+
+ self.scene = PMGraphicsScene(graphics_view=self.graphicsView, flow_widget=self)
+ self.scene.setSceneRect(-1000, -1000, 2000, 2000)
+
+ self.node_manager = NodeManagerWidget(scene=self.scene)
+ # self.node_manager.scene = self.scene
+ self.base_layout.addWidget(self.node_manager)
+ self.nodes: List[Node] = self.scene.nodes
+ self.lines = self.scene.lines
+
+ self.node_manager.register_node_content(PMGFlowContent, 'simple_calc', 'UserDefinedFunc')
+ self.load_nodes_library()
+ self.graphicsView.setScene(self.scene)
+
+ self.tool_button_run.clicked.connect(self.run)
+ self.tool_button_undo.clicked.connect(self.scene.undo)
+ self.tool_button_redo.clicked.connect(self.scene.redo)
+ # self.tool_button_open.clicked.connect(self.open)
+ self.tool_button_reset.clicked.connect(self.reset)
+ self.tool_button_save.clicked.connect(self.save)
+ self.tool_button_run_fg.clicked.connect(lambda: self.run_in_fg())
+ self.tool_button_step.clicked.connect(self.step)
+ if self._path != '':
+ self.scene.load_flowchart(self._path)
+
+ def load(self, path: str):
+ self._path = path
+ self.scene.load_flowchart(path)
+
+ def on_error_occurs(self, error: FlowContentError):
+ show_error(self, error.brief, error.detailed, self.tr('Error'))
+
+ def reset(self):
+ self.scene.reset_status()
+ self.scene.load_flowchart(self._path)
+
+ def open(self):
+ file_name, ext = QFileDialog.getOpenFileName(self, '选择文件', '', '流程图文件(*.pmcache *.json *.pmfc)')
+ if file_name == '':
+ return
+ try:
+ self._path = file_name
+ self.reset()
+ # self.load_flowchart(file_name)
+ self.setWindowTitle(os.path.basename(file_name))
+ except:
+ import traceback
+ traceback.print_exc()
+ pass
+
+ def save(self):
+ self.scene.dump_flowchart(self._path)
+
+ def pre_run(self):
+ call_id_list = self.scene.topo_sort()
+ for i in call_id_list:
+ self.scene.find_node(i).content.refresh_input_port_indices()
+ self.scene.find_node(i).content.refresh()
+
+ def step(self):
+ """
+ 要求必须是拓扑排序之后才可以用!
+ Returns:
+
+ """
+ self.run_fg_for_one_step()
+
+ def run_in_fg(self, input_args_list: List[object] = None) -> List[object]:
+ """
+ 前端直接进行数据处理,而非在后台线程执行。
+ 这样不能做耗时操作(因为会卡住界面),但是可以直接获取运行后的数据,并且结果相对简单一些。
+ :return:
+ """
+ self.pre_run()
+ if input_args_list is None:
+ input_args_list = []
+ for node in self.scene.nodes:
+ node.reset()
+ call_id_list = self.scene.topo_sort()
+ self.scene.call_id_list = call_id_list
+ return self.run_fg_for_one_step(input_args_list)
+
+ def run_fg_for_one_step(self, input_args_list: List[object] = None):
+ call_id_list = self.scene.call_id_list
+ self.scene.find_node(call_id_list[0]).content._process(input_args_list)
+
+ return self.scene.find_node(call_id_list[-1]).content.results
+
+ def run(self):
+ """
+ 运行代码
+ """
+ call_id_list = self.scene.topo_sort()
+ self.scene.call_id_list = call_id_list
+
+ thread = QThread()
+ for node in self.scene.nodes:
+ node.reset()
+ node.content.moveToThread(thread)
+ worker = self.scene.find_node(call_id_list[0]).content
+ worker.moveToThread(thread)
+ thread.started.connect(worker._process)
+ thread.start()
+ self.worker = worker
+ self.thread = thread
+
+ def closeEvent(self, a0: 'QCloseEvent') -> None:
+ """
+ 当文件名为示例文件的时候,不可自动存储,但是可以手动点击保存。
+ :param a0:
+ :return:
+ """
+ if not self._path.endswith('.json'):
+ self.scene.dump_flowchart(self._path)
+
+ def load_nodes_library(self):
+ from widgets.flowchart.nodes.simplecalc import Constant, Add, Mul
+ self.node_manager.register_node_content(Constant, 'simple_calc', 'Constant')
+ self.node_manager.register_node_content(Add, 'simple_calc', 'Add')
+ self.node_manager.register_node_content(Mul, 'simple_calc', 'Mul')
+
+
+if __name__ == '__main__':
+ from widgets.flowchart.core.flow_content import PMGFlowContent
+ import cgitb
+
+ cgitb.enable()
+ app = QApplication(sys.argv)
+ graphics = PMFlowWidget()
+ graphics.setup_ui()
+ graphics.load('flowchart_stat.pmcache')
+ graphics.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/flowchart/core/nodemanager.py b/pyminer/widgets/flowchart/core/nodemanager.py
new file mode 100644
index 0000000000000000000000000000000000000000..02c70c937023849776b794c70e9d51323915bc8f
--- /dev/null
+++ b/pyminer/widgets/flowchart/core/nodemanager.py
@@ -0,0 +1,283 @@
+"""
+This is node manager.
+
+"""
+import copy
+import json
+import os
+import sys
+from typing import List, Dict, Tuple, Union, TYPE_CHECKING, Type
+
+from widgets import PMGPanel
+from widgets.flowchart.core.flow_content import FlowContentForFunction
+from widgets.flowchart.core.flow_items import CustomPort
+from widgets.flowchart.core.flow_node import Node
+from PySide2.QtCore import Signal
+from PySide2.QtGui import QColor
+from PySide2.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QListWidget, QPushButton, QDialog, QToolBox, QTextEdit
+
+COLOR_NORMAL = QColor(212, 227, 242)
+COLOR_HOVER = QColor(255, 200, 00)
+COLOR_HOVER_PORT = QColor(0, 0, 50)
+
+if TYPE_CHECKING:
+ from widgets.flowchart.core.flowchart_widget import PMGraphicsScene
+ from widgets.flowchart.core.flow_content import PMGBaseFlowContent, PMGFlowContent
+
+
+class NodeManagerWidget(QWidget):
+ signal_new_node = Signal(Node)
+
+ def __init__(self, parent: QWidget = None, scene: 'PMGraphicsScene' = None):
+ super().__init__(parent)
+ self.content_classes: Dict[str, Type[PMGFlowContent]] = {}
+ self.text_to_class_name: Dict[str, str] = {}
+ self.groups = ['simple_calc', 'dataset', 'plot', 'linear_algebra', 'io']
+ self.trans_dic = {'simple_calc': '计算',
+ 'dataset': '数据集',
+ 'plot': '绘图',
+ 'logic': '逻辑',
+ 'linear_algebra': '线性代数',
+ 'io': '输入输出'
+ }
+ self.node_info_dic: Dict[str, Dict[str, Dict[str, object]]] = {key: dict() for key in self.groups}
+
+ self.setLayout(QVBoxLayout())
+ self.top_layout = QHBoxLayout()
+ self.layout().addLayout(self.top_layout)
+ # self.button_edit = QPushButton('Edit')
+ # self.button_add = QPushButton('Add')
+ # self.top_layout.addWidget(self.button_edit)
+ # self.top_layout.addWidget(self.button_add)
+ # self.button_edit.clicked.connect(self.on_edit)
+ # self.button_add.clicked.connect(self.on_add)
+
+ self.toolbox = QToolBox()
+ self.list_widgets: Dict[str, QListWidget] = {}
+ for text in self.groups:
+ list_widget = QListWidget()
+ list_widget.doubleClicked.connect(self.on_item_double_clicked)
+ self.layout().addWidget(self.toolbox)
+ self.toolbox.addItem(list_widget, self.trans_dic[text])
+ self.list_widgets[text] = list_widget
+ self.scene: 'PMGraphicsScene' = scene
+ self.setMinimumWidth(300)
+ self.load_nodes()
+
+ def on_add(self):
+ group = self.get_current_list_widget_group()
+ self.add_node_info(
+ {'text': 'untitled', 'inputs': ['input1'], 'outputs': ['output1'], 'code': '', 'params': [], 'icon': '',
+ 'group': group, 'ports_changeable': [False, False]})
+
+ def on_edit(self):
+ list_widget = self.toolbox.currentWidget()
+ curr_row = list_widget.currentRow()
+
+ if curr_row >= 0:
+ group_name = self.toolbox.itemText(self.toolbox.currentIndex())
+ curr_text = list_widget.currentItem().text()
+ dic = self.node_info_dic[group_name][curr_text]
+ edit_layout = QHBoxLayout()
+ input_widget = QTextEdit()
+ edit_layout.addWidget(input_widget)
+ check_button = QPushButton(text='check')
+ edit_layout.addWidget(check_button)
+ input_widget.setText(repr(dic['params']))
+ views = [
+ ('line_ctrl', 'text', 'Node Text', dic['text']),
+ ('check_ctrl', 'inputs_changeable', 'Input Ports Changeable', dic['ports_changeable'][0]),
+ ('check_ctrl', 'outputs_changeable', 'Output Ports Changeble', dic['ports_changeable'][1]),
+ ('editor_ctrl', 'code', 'Input Python Code', dic['code'], 'python'),
+ ('list_ctrl', 'inputs', 'Set Inputs', [[None] * len(dic['inputs']), dic['inputs']], lambda: None),
+ ('list_ctrl', 'outputs', 'Set Outputs', [[None] * len(dic['outputs']), dic['outputs']], lambda: None),
+ ('file_ctrl', 'icon', 'Set Icon', dic['icon']),
+ ('combo_ctrl', 'group', 'Group Name', dic['group'], self.groups)
+ ]
+ sp = PMGPanel(parent=None, views=views)
+ dialog = QDialog(self)
+
+ def verify():
+ try:
+ text = input_widget.document().toPlainText()
+ l = eval(text)
+ if isinstance(l, list):
+ dialog2 = QDialog(dialog)
+ sp2 = PMGPanel(parent=None, views=l)
+ dialog2.setLayout(QHBoxLayout())
+ dialog2.layout().addWidget(sp2)
+ dialog2.layout().addLayout(edit_layout)
+ dialog2.exec_()
+ except:
+ import traceback
+ traceback.print_exc()
+
+ check_button.clicked.connect(verify)
+ dialog.setLayout(QVBoxLayout())
+ dialog.layout().addWidget(sp)
+ dialog.layout().addLayout(edit_layout)
+ dialog.exec_()
+ dic = sp.get_value()
+ params = None
+ try:
+ params = eval(input_widget.document().toPlainText())
+ except:
+ import traceback
+ traceback.print_exc()
+ group = self.get_current_list_widget_group()
+ if isinstance(params, list):
+ self.node_info_dic[group][curr_text]['params'] = params
+ self.node_info_dic[group][curr_text]['text'] = dic['text']
+ self.node_info_dic[group][curr_text]['icon'] = dic['icon']
+ self.node_info_dic[group][curr_text]['code'] = dic['code']
+ self.node_info_dic[group][curr_text]['inputs'] = dic['inputs'][1]
+ self.node_info_dic[group][curr_text]['outputs'] = dic['outputs'][1]
+ self.node_info_dic[group][curr_text]['group'] = dic['group']
+ self.node_info_dic[group][curr_text]['ports_changeable'] = [dic['inputs_changeable'],
+ dic['outputs_changeable']]
+ list_widget.item(curr_row).setText(dic['text'])
+ self.save_node_templetes()
+ self.load_nodes()
+ else:
+ return
+
+ def add_node(self, text: str, inputs: List[str], outputs: List[str], ports_changeable: List[bool],
+ params: List[Union[List, Tuple]] = '', icon_path: str = '', func_str: str = ''):
+ """
+ 添加新的节点
+ """
+ node_id = self.scene.new_id()
+ input_ports = [CustomPort(node_id + ':input:%d' % int(i + 1), text=name, port_type='input') for i, name in
+ enumerate(inputs)]
+ output_ports = [CustomPort(node_id + ':output:%d' % int(i + 1), text=name, port_type='output') for i, name in
+ enumerate(outputs)]
+ node = Node(canvas=self.scene, node_id=node_id, text=text, input_ports=input_ports, output_ports=output_ports,
+ icon_path=icon_path)
+ content = FlowContentForFunction(node)
+ content.set_function(func_str, 'function')
+ content.set_params(params)
+ content.ports_changable = ports_changeable
+ node.set_content(content)
+ self.scene.add_node(node)
+
+ def on_item_double_clicked(self):
+ list_widget: QListWidget = self.toolbox.currentWidget()
+ group = self.get_current_list_widget_group()
+ curr_row = list_widget.currentRow()
+
+ if curr_row >= 0:
+ curr_text = list_widget.currentItem().text()
+ if curr_text in self.node_info_dic[group]:
+ node_info = self.node_info_dic[group][curr_text]
+ text: str = node_info.get('text')
+ inputs: List[str] = node_info.get('inputs')
+ outputs: List[str] = node_info.get('outputs')
+ code: str = node_info.get('code')
+ params: List = node_info.get('params')
+ icon_path: str = node_info.get('icon')
+ ports_changeable: List[bool] = node_info.get('ports_changeable')
+ params = copy.deepcopy(params) # 需要复制一份再传过去,否则会是一个对象。
+ self.add_node(text, inputs, outputs, ports_changeable, params, icon_path, code)
+ else:
+ content: PMGFlowContent = self.content_classes.get(self.text_to_class_name[curr_text])()
+
+ node_id = self.scene.new_id()
+ input_ports = [CustomPort(node_id + ':input:%d' % int(i + 1), text=name, port_type='input') for i, name
+ in enumerate(content.input_args_labels)]
+ output_ports = [CustomPort(node_id + ':output:%d' % int(i + 1), text=name, port_type='output') for
+ i, name in
+ enumerate(content.output_ports_labels)]
+ node = Node(canvas=self.scene, node_id=node_id, text=content.text, input_ports=input_ports,
+ output_ports=output_ports,
+ icon_path=content.icon_path,
+ look={})
+ content.node = node
+ # content.class_name = curr_text
+ node.set_content(content)
+ self.scene.add_node(node)
+ pass
+
+ def add_node_info(self, info_dic: Dict[str, object]):
+ """
+ add new infomation of node
+ """
+ group = self.get_current_list_widget_group()
+ text = info_dic.get('text')
+ self.get_current_list_widget().addItem(text)
+ self.node_info_dic[group][text](info_dic)
+
+ def get_current_list_widget(self) -> QListWidget:
+ return self.toolbox.currentWidget()
+
+ def get_current_list_widget_group(self) -> str:
+ text = self.toolbox.itemText(self.toolbox.currentIndex())
+ for k, v in self.trans_dic.items():
+ if text == v:
+ return k
+ return ''
+
+ def load_nodes(self):
+ """
+ 加载节点
+ 加载节点之后,可以
+ """
+ import pandas
+ self.node_info_dic = {key: dict() for key in self.groups}
+ return
+ df = pandas.read_csv(os.path.join(os.path.dirname(__file__), 'lib', 'test.csv'))
+ # for k in self.node_info_dic:
+ for i in range(df.shape[0]):
+ row = df.loc[i]
+ dic = {
+ 'text': row['text'] if not pandas.isna(row['text']) else '',
+ 'code': row['code'] if not pandas.isna(row['code']) else '',
+ 'inputs': json.loads(row['inputs']),
+ 'outputs': json.loads(row['outputs']),
+ 'params': json.loads(row['params'])
+ }
+ dic['icon'] = row['icon'] if not pandas.isna(row['icon']) else ''
+ dic['group'] = row['group'] if not pandas.isna(row.get('group')) else 'simple_calc'
+ ports_changeable = row.get('ports_changeable')
+ dic['ports_changeable'] = json.loads(ports_changeable) if ports_changeable is not None else [False, False]
+ self.node_info_dic[dic['group']][dic['text']] = dic
+
+ self.refresh_list()
+
+ def save_node_templetes(self):
+ return
+ import pandas
+
+ columns = ['text', 'inputs', 'outputs', 'ports_changeable', 'params', 'icon', 'group', 'code']
+ content = []
+ node_infos = []
+ for k in self.node_info_dic:
+ for k1 in self.node_info_dic[k]:
+ node_infos += [self.node_info_dic[k][k1]] # self.node_info_dic[k]
+
+ for node_info in node_infos:
+ text = node_info.get('text')
+ icon = node_info.get('icon')
+ inputs = json.dumps(node_info.get('inputs'))
+ outputs = json.dumps(node_info.get('outputs'))
+ ports_changeable = json.dumps(node_info.get('ports_changeable'))
+ params = json.dumps(node_info.get('params'))
+ code = node_info.get('code')
+ group = node_info.get('group')
+ content.append([text, inputs, outputs, ports_changeable, params, icon, group, code])
+ df = pandas.DataFrame(content, columns=columns)
+
+ df.to_csv(os.path.join(os.path.dirname(__file__), 'lib', 'test.csv'))
+
+ def refresh_list(self):
+ for k in self.node_info_dic:
+ node_infos = self.node_info_dic[k]
+ list_widget = self.list_widgets[k]
+ list_widget.clear()
+ for k1, info in node_infos.items():
+ list_widget.addItem(info['text'])
+
+ def register_node_content(self, content_class: Type['PMGFlowContent'], group_name: str, text: str = ''):
+ content = content_class()
+ self.list_widgets[group_name].addItem(content.text)
+ self.content_classes[content.class_name] = content_class
+ self.text_to_class_name[content.text] = content.class_name
diff --git a/pyminer/widgets/flowchart/core/utils.py b/pyminer/widgets/flowchart/core/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..b8a18278d9e4444e8247c1d2ca10f2cb74bc9f33
--- /dev/null
+++ b/pyminer/widgets/flowchart/core/utils.py
@@ -0,0 +1,13 @@
+from PySide2.QtCore import QPointF
+
+
+def round_position(point: QPointF, pixels=5):
+ """
+ 圆整位置。
+ :param point:
+ :param pixels:圆整的单位(最好是1,2,5,10,20,...)
+ :return:
+ """
+ x, y = point.x(), point.y()
+ x_cor, y_cor = round(x * 1.0 / pixels) * pixels, round(y * 1.0 / pixels) * pixels
+ return QPointF(x_cor, y_cor)
diff --git a/pyminer/widgets/flowchart/create_node_content_class.md b/pyminer/widgets/flowchart/create_node_content_class.md
new file mode 100644
index 0000000000000000000000000000000000000000..708ddba44ae2f9f39c9a56bf91f571aad4a2a894
--- /dev/null
+++ b/pyminer/widgets/flowchart/create_node_content_class.md
@@ -0,0 +1,86 @@
+# 使用面向对象方式进行节点创建
+## 简单示例解析
+1、在widgets/flowchart/nodes/simplecalc.py
+仿照现有的节点,编写一个现有的节点名为Mul.
+如果想要建立其他文件,可以在同一目录下建立。
+需要继承PMGFlowContent这个基类。
+我们参考Add这个类。
+不妨将Add类复制一遍之后,在其上加以修改。
+```python
+class Mul(PMGFlowContent):
+ def __init__(self):
+ super().__init__()
+ self.input_args_labels = ['in1', 'in2']
+ self.output_ports_labels = ['out']
+ self.class_name = 'Mul'
+ self.text = '乘积'
+ self.icon_path = ''
+
+ def process(self, *args) -> List[object]:
+ if len(args) > 1:
+ mul = args[0]
+ for a in args[1:]:
+ mul *= a
+ else:
+ raise ValueError
+ return [mul]
+```
+对于PMGFlowContent类有以下方式:
+- input_ports_labels:输入端口的文字列表。根据这个列表将自动生成输入端口,输入端口的数量与列表长度一致,文字从上至下依次为列表
+从前往后的各个项。
+- output_ports_labels:输出端口的文字列表,含义同输入端口文字列表。
+- class_name:节点的类。一般应当以英文明明
+- text:节点的文字,支持中文等utf-8编码的字符。
+- icon_path:图标的属性(尽量用绝对路径)
+
+注意事项:
+- `__init__(self)`方法除了`self`之外,不能有任何参数。
+
+
+2、在widgets/flowchart/core/flowchart_widget.py中,跳转到load_nodes_library(self)这个方法。
+```python
+def load_nodes_library(self):
+ from widgets.flowchart.nodes.simplecalc import Constant, Add
+ self.node_manager.register_node_content(Constant, 'simple_calc', 'Constant')
+ self.node_manager.register_node_content(Add, 'simple_calc', 'Add')
+
+```
+添加导入Mul,并且照葫芦画瓢,将代码改成:
+```python
+def load_nodes_library(self):
+ from widgets.flowchart.nodes.simplecalc import Constant, Add, Mul
+ self.node_manager.register_node_content(Constant, 'simple_calc', 'Constant')
+ self.node_manager.register_node_content(Add, 'simple_calc', 'Add')
+ self.node_manager.register_node_content(Mul, 'simple_calc', 'Mul')
+```
+`simple_calc`指的是简单计算这一分组。
+
+运行这个文件,即可看见新的节点`Mul`已被创建。双击右键即可插入。
+点击run_fg即可运行。但注意,至少要有两个模块和一条连接线。一个简单的示例如下图。
+双击节点,可以查看节点的输入值和输出值。
+
+
+3、点击设置弹出窗口效果
+参考simple_calc.py下的Constant类,重写其on_settings_requested(self, parent)方法即可。
+在这个节点上点击右键,即可弹出这个窗口。
+大概像这样:
+```python
+def on_settings_requested(self, parent):
+ dlg = QDialog(parent=parent)
+ dlg.exec_()
+```
+
+4、设置显示的文本内容
+参考Constant类,重写format_param(self)方法即可。大概如此:
+
+```python
+def format_params(self):
+ return "hello"
+```
+
+这样节点就会显示“hello”文本。
+
+5、设置图标(未做)
+
+6、设置存储信息
+节点的info属性会在保存时保存和加载。只要修改info属性即可。
diff --git a/pyminer/widgets/flowchart/dataprocesswidget.py b/pyminer/widgets/flowchart/dataprocesswidget.py
new file mode 100644
index 0000000000000000000000000000000000000000..9936238221395eb51b56b30562d8e6d353366afe
--- /dev/null
+++ b/pyminer/widgets/flowchart/dataprocesswidget.py
@@ -0,0 +1,209 @@
+"""
+node properties:
+id:str
+text:str
+icon:str(path of icon)
+ports:{}
+content:{}
+
+properties of contents:
+code:str
+type:str
+params:List[str]
+"""
+import json
+import os
+import sys
+import time
+from typing import List
+
+t0 = time.time()
+from widgets.flowchart.core.flowchart_widget import PMFlowWidget
+from widgets.flowchart.core.flow_node import Node
+from widgets.flowchart.core.nodemanager import NodeManagerWidget
+from widgets.flowchart.core.flowchart_scene import PMGraphicsScene
+from PySide2.QtCore import QSize, QCoreApplication, Qt
+from PySide2.QtGui import QColor, QWheelEvent
+from PySide2.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QToolButton, QSpacerItem, QSizePolicy, QGraphicsView, \
+ QFrame, QApplication, QFileDialog, QMessageBox
+
+COLOR_NORMAL = QColor(212, 227, 242)
+COLOR_HOVER = QColor(255, 200, 00)
+COLOR_HOVER_PORT = QColor(0, 0, 50)
+
+
+class PMGraphicsView(QGraphicsView):
+ def wheelEvent(self, event: 'QWheelEvent') -> None:
+ """
+ 鼠标滚轮事件
+ :param event:
+ :return:
+ """
+ if event.modifiers() == Qt.ControlModifier:
+ if event.angleDelta().y() > 0:
+ self.scale(1.1, 1.1)
+ else:
+ self.scale(0.9, 0.9)
+
+
+class PMDataProcessFlowWidget(PMFlowWidget):
+ def __init__(self, parent=None, path=''):
+ self._path = path
+ _translate = QCoreApplication.translate
+ super().__init__(parent)
+ self.setObjectName("tab_flow")
+ self.base_layout = QHBoxLayout(self)
+ self.verticalLayout_6 = QVBoxLayout()
+ self.base_layout.addLayout(self.verticalLayout_6)
+ self.verticalLayout_6.setContentsMargins(0, 0, 0, 0)
+ self.verticalLayout_6.setSpacing(0)
+ self.verticalLayout_6.setObjectName("verticalLayout_6")
+ self.widget_3 = QWidget(self)
+ self.widget_3.setMinimumSize(QSize(0, 30))
+ self.widget_3.setObjectName("widget_3")
+
+ self.horizontal_layout = QHBoxLayout(self.widget_3)
+ self.horizontal_layout.setContentsMargins(0, 0, 0, 0)
+ self.horizontal_layout.setSpacing(1)
+ self.horizontal_layout.setObjectName("horizontalLayout_6")
+
+ self.tool_button_run_fg = QToolButton(self.widget_3)
+ self.tool_button_run_fg.setText('Run')
+ self.horizontal_layout.addWidget(self.tool_button_run_fg)
+
+ self.tool_button_save = QToolButton(self.widget_3)
+ self.tool_button_save.setText('Save')
+ self.horizontal_layout.addWidget(self.tool_button_save)
+
+ self.tool_button_save_as = QToolButton(self.widget_3)
+ self.tool_button_save_as.setText('Save As')
+ self.horizontal_layout.addWidget(self.tool_button_save_as)
+
+ self.tool_button_open = QToolButton(self.widget_3)
+ self.tool_button_open.setText('Open')
+ self.horizontal_layout.addWidget(self.tool_button_open)
+
+ self.tool_button_reset = QToolButton(self.widget_3)
+ self.tool_button_reset.setText('Reset')
+ self.horizontal_layout.addWidget(self.tool_button_reset)
+
+ self.tool_button_undo = QToolButton(self.widget_3)
+ self.tool_button_undo.setText('Undo')
+ self.horizontal_layout.addWidget(self.tool_button_undo)
+ # self.tool_button_undo.setEnabled(False)
+
+ self.tool_button_redo = QToolButton(self.widget_3)
+ self.tool_button_redo.setText('Redo')
+ self.horizontal_layout.addWidget(self.tool_button_redo)
+ # self.tool_button_redo.setEnabled(False)
+
+ spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+ self.horizontal_layout.addItem(spacerItem)
+ self.verticalLayout_6.addWidget(self.widget_3)
+
+ self.graphicsView = PMGraphicsView(self)
+
+ self.graphicsView.setFrameShape(QFrame.NoFrame)
+ self.graphicsView.setObjectName("graphicsView")
+ self.verticalLayout_6.addWidget(self.graphicsView)
+
+ self.scene = PMGraphicsScene(graphics_view=self.graphicsView, flow_widget=self)
+ self.scene.setSceneRect(-1000, -1000, 2000, 2000)
+
+ self.node_manager = NodeManagerWidget(scene=self.scene)
+ # self.node_manager.scene = self.scene
+ self.base_layout.addWidget(self.node_manager)
+ self.nodes: List[Node] = self.scene.nodes
+ self.lines = self.scene.lines
+
+ self.load_nodes_library()
+ self.graphicsView.setScene(self.scene)
+
+ self.tool_button_undo.clicked.connect(self.scene.undo)
+ self.tool_button_redo.clicked.connect(self.scene.redo)
+ self.tool_button_open.clicked.connect(self.open)
+ self.tool_button_reset.clicked.connect(self.reset)
+ self.tool_button_save.clicked.connect(self.save)
+ self.tool_button_save_as.clicked.connect(self.saveas)
+ self.tool_button_run_fg.clicked.connect(lambda: self.run_in_fg())
+ if self._path != '':
+ self.scene.load_flowchart(self._path)
+
+ def load(self, path: str):
+ self._path = path
+ self.scene.load_flowchart(path)
+
+ def saveas(self):
+ """
+
+ Returns:
+ """
+ file_name, ext = QFileDialog.getSaveFileName(self, '选择文件', '', '流程图文件(*.pmfc)')
+ if file_name == '':
+ return
+ self._path = file_name
+ self.save()
+ self.setWindowTitle(os.path.basename(file_name))
+
+ def reset(self):
+ self.scene.reset_status()
+ self.scene.load_flowchart(self._path)
+ self.pre_run()
+
+
+
+ def run_in_fg(self, input_args_list: List[object] = None) -> List[object]:
+ """
+ 前端直接进行数据处理,而非在后台线程执行。
+ 这样不能做耗时操作(因为会卡住界面),但是可以直接获取运行后的数据,并且结果相对简单一些。
+ :return:
+ """
+ self.pre_run()
+ if input_args_list is None:
+ input_args_list = []
+ for node in self.scene.nodes:
+ node.reset()
+ call_id_list = self.scene.topo_sort()
+ self.scene.call_id_list = call_id_list
+ return self.run_fg_for_one_step(input_args_list)
+
+ def run_fg_for_one_step(self, input_args_list: List[object] = None):
+ call_id_list = self.scene.call_id_list
+ self.scene.find_node(call_id_list[0]).content._process(input_args_list)
+
+ return self.scene.find_node(call_id_list[-1]).content.results
+
+ def load_nodes_library(self):
+ from widgets.flowchart.nodes.simplecalc import Constant, Add, Mul
+ from widgets.flowchart.nodes.random import Random
+ from widgets.flowchart.nodes.plots import HistPlot
+ from widgets.flowchart.nodes.dfoperation import DataReplace
+ from widgets.flowchart.nodes.dataframeoperation import DropDuplicated
+ from widgets.flowchart.nodes.io import Iterator, ListDirs, PandasImport, PandasFileImport
+
+ self.node_manager.register_node_content(Constant, 'simple_calc')
+ self.node_manager.register_node_content(Add, 'simple_calc')
+ self.node_manager.register_node_content(Mul, 'simple_calc')
+ self.node_manager.register_node_content(Random, 'simple_calc')
+ self.node_manager.register_node_content(HistPlot, 'plot')
+ self.node_manager.register_node_content(DataReplace, 'dataset')
+ self.node_manager.register_node_content(DropDuplicated, 'dataset')
+ # self.node_manager.register_node_content(Iterator, 'simple_calc')
+ self.node_manager.register_node_content(ListDirs, 'io')
+ self.node_manager.register_node_content(PandasImport, 'io')
+ self.node_manager.register_node_content(PandasFileImport, 'io')
+
+ def closeEvent(self, e):
+ super().closeEvent(e)
+
+
+if __name__ == '__main__':
+ from widgets.flowchart.core.flow_content import PMGFlowContent
+ import cgitb
+
+ cgitb.enable()
+ app = QApplication(sys.argv)
+ graphics = PMDataProcessFlowWidget()
+ graphics.load('dataprocess.pmcache')
+ graphics.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/flowchart/doc_figures/before_run.png b/pyminer/widgets/flowchart/doc_figures/before_run.png
new file mode 100644
index 0000000000000000000000000000000000000000..47dffcb07cf65acaf79e09f7613a2db3839e4349
Binary files /dev/null and b/pyminer/widgets/flowchart/doc_figures/before_run.png differ
diff --git a/pyminer/widgets/flowchart/doc_figures/check_json.png b/pyminer/widgets/flowchart/doc_figures/check_json.png
new file mode 100644
index 0000000000000000000000000000000000000000..eb6c2fa3af3c1514be1e54ee061123cb9e07ff2c
Binary files /dev/null and b/pyminer/widgets/flowchart/doc_figures/check_json.png differ
diff --git a/pyminer/widgets/flowchart/doc_figures/click_edit_button.png b/pyminer/widgets/flowchart/doc_figures/click_edit_button.png
new file mode 100644
index 0000000000000000000000000000000000000000..29ccca60f497c205a190f37adf5e57f2c1017b3b
Binary files /dev/null and b/pyminer/widgets/flowchart/doc_figures/click_edit_button.png differ
diff --git a/pyminer/widgets/flowchart/doc_figures/click_right_top_add_button.png b/pyminer/widgets/flowchart/doc_figures/click_right_top_add_button.png
new file mode 100644
index 0000000000000000000000000000000000000000..51766abfeb055d33ba53c1155ec81eeb08f723a5
Binary files /dev/null and b/pyminer/widgets/flowchart/doc_figures/click_right_top_add_button.png differ
diff --git a/pyminer/widgets/flowchart/doc_figures/composition_structure.png b/pyminer/widgets/flowchart/doc_figures/composition_structure.png
new file mode 100644
index 0000000000000000000000000000000000000000..e5adbfeeaf515b543d74e8a484d5ee785818f765
Binary files /dev/null and b/pyminer/widgets/flowchart/doc_figures/composition_structure.png differ
diff --git a/pyminer/widgets/flowchart/doc_figures/configure_panel.png b/pyminer/widgets/flowchart/doc_figures/configure_panel.png
new file mode 100644
index 0000000000000000000000000000000000000000..841e3f28c5f78561b939496a198aace05ac9aed0
Binary files /dev/null and b/pyminer/widgets/flowchart/doc_figures/configure_panel.png differ
diff --git a/pyminer/widgets/flowchart/doc_figures/create.png b/pyminer/widgets/flowchart/doc_figures/create.png
new file mode 100644
index 0000000000000000000000000000000000000000..699fb754d43c5f9522890cb2e1734c38ee20c6b0
Binary files /dev/null and b/pyminer/widgets/flowchart/doc_figures/create.png differ
diff --git a/pyminer/widgets/flowchart/doc_figures/create_new_content_Mul.png b/pyminer/widgets/flowchart/doc_figures/create_new_content_Mul.png
new file mode 100644
index 0000000000000000000000000000000000000000..8cc0dc71b368ba88abe34f4c9d37a7d96e2d3c31
Binary files /dev/null and b/pyminer/widgets/flowchart/doc_figures/create_new_content_Mul.png differ
diff --git a/pyminer/widgets/flowchart/doc_figures/create_node.png b/pyminer/widgets/flowchart/doc_figures/create_node.png
new file mode 100644
index 0000000000000000000000000000000000000000..459c5c8160f70692cc66ba1fc2994f88e8d0e655
Binary files /dev/null and b/pyminer/widgets/flowchart/doc_figures/create_node.png differ
diff --git a/pyminer/widgets/flowchart/doc_figures/custom_node.png b/pyminer/widgets/flowchart/doc_figures/custom_node.png
new file mode 100644
index 0000000000000000000000000000000000000000..4baa3d11bc455270dad4ace4ef608b56122e763e
Binary files /dev/null and b/pyminer/widgets/flowchart/doc_figures/custom_node.png differ
diff --git a/pyminer/widgets/flowchart/doc_figures/edit_node.png b/pyminer/widgets/flowchart/doc_figures/edit_node.png
new file mode 100644
index 0000000000000000000000000000000000000000..b047ee097a6ac64b93ecdbeccc48bd274d30c10a
Binary files /dev/null and b/pyminer/widgets/flowchart/doc_figures/edit_node.png differ
diff --git a/pyminer/widgets/flowchart/doc_figures/edit_panel_meaning.png b/pyminer/widgets/flowchart/doc_figures/edit_panel_meaning.png
new file mode 100644
index 0000000000000000000000000000000000000000..b6573a39281a4c054181ef82a17d9e571ebe2317
Binary files /dev/null and b/pyminer/widgets/flowchart/doc_figures/edit_panel_meaning.png differ
diff --git a/pyminer/widgets/flowchart/doc_figures/popup_edit_panel.png b/pyminer/widgets/flowchart/doc_figures/popup_edit_panel.png
new file mode 100644
index 0000000000000000000000000000000000000000..2c0ff2e30df318e3332b8371f7da53941715c205
Binary files /dev/null and b/pyminer/widgets/flowchart/doc_figures/popup_edit_panel.png differ
diff --git a/pyminer/widgets/flowchart/doc_figures/sketch_after_edit.png b/pyminer/widgets/flowchart/doc_figures/sketch_after_edit.png
new file mode 100644
index 0000000000000000000000000000000000000000..4f91a16ed39ef77b326e62d40944814a7e7e0bfe
Binary files /dev/null and b/pyminer/widgets/flowchart/doc_figures/sketch_after_edit.png differ
diff --git a/pyminer/widgets/flowchart/icons/down.png b/pyminer/widgets/flowchart/icons/down.png
new file mode 100644
index 0000000000000000000000000000000000000000..d9d0cf6d0bc80115e7d9b5bd169def5a4582899b
Binary files /dev/null and b/pyminer/widgets/flowchart/icons/down.png differ
diff --git a/pyminer/widgets/flowchart/icons/logo.png b/pyminer/widgets/flowchart/icons/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..4b0a42323f9d02912c17717cc21d9d6bb6f5c613
Binary files /dev/null and b/pyminer/widgets/flowchart/icons/logo.png differ
diff --git a/pyminer/widgets/flowchart/nodes/__init__.py b/pyminer/widgets/flowchart/nodes/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/widgets/flowchart/nodes/dataframeoperation/__init__.py b/pyminer/widgets/flowchart/nodes/dataframeoperation/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..384473ff9b35e1385de56e69f74193e3f6e23173
--- /dev/null
+++ b/pyminer/widgets/flowchart/nodes/dataframeoperation/__init__.py
@@ -0,0 +1,2 @@
+from .dropduplicated import DropDuplicated
+ # self.results = copy.deepcopy(result)
\ No newline at end of file
diff --git a/pyminer/widgets/flowchart/nodes/dataframeoperation/dropduplicated.py b/pyminer/widgets/flowchart/nodes/dataframeoperation/dropduplicated.py
new file mode 100644
index 0000000000000000000000000000000000000000..5228533206bdf1698b777e5c523b073fee395c1a
--- /dev/null
+++ b/pyminer/widgets/flowchart/nodes/dataframeoperation/dropduplicated.py
@@ -0,0 +1,64 @@
+import sys
+from typing import List, Union, Tuple, Dict, TYPE_CHECKING, Callable
+from widgets import PMGFlowContent, PMGPanelDialog
+
+if TYPE_CHECKING:
+ import pandas as pd
+
+
+class DropDuplicated(PMGFlowContent):
+
+ def __init__(self):
+ super(DropDuplicated, self).__init__()
+ self.input_args_labels = ['in']
+ self.output_ports_labels = ['out']
+ self.class_name = 'DropDuplicated'
+ self.text = '去除重复值'
+ self.icon_path = ''
+ self.agg = None
+ self.info = {'regulations': []}
+
+ def process(self, *args) -> List:
+ """
+ 删除缺失值的方法
+ Args:
+ *args:
+
+ Returns:
+
+ """
+ import pandas as pd
+ assert isinstance(args[0], pd.DataFrame)
+ df: pd.DataFrame = args[0]
+ # if df.duplicated():
+ return [df.drop_duplicates()]
+
+ def on_settings_requested(self, parent):
+ """
+
+ Args:
+ parent:
+
+ Returns:
+
+ """
+ views = [['rules_ctrl', 'rules', '规则编辑',
+ [
+ {'name': 'input_col_name', 'text': '输入流字段', 'init': '$ALL'},
+ {'name': 'output_col_name', 'text': '输出流字段', 'init': ''},
+ {'name': 'str_to_replace', 'text': '待替换数据', 'init': ''},
+ {'name': 'replace_with', 'text': '替换为', 'init': ''},
+ {'name': 'regex', 'text': '使用正则表达式', 'init': False},
+ {'name': 'match_words', 'text': '全字匹配', 'init': True},
+ {'name': 'case_sensitive', 'text': '大小写敏感', 'init': False}
+ ],
+ ]
+ ]
+ dlg = PMGPanelDialog(parent=parent, views=views)
+
+ dlg.setMinimumSize(600, 480)
+
+ dlg.panel.set_value({'rules': self.info['regulations']})
+ dlg.exec_()
+
+ self.info['regulations'] = dlg.panel.get_value()['rules']
diff --git a/pyminer/widgets/flowchart/nodes/dataframeoperation/randomrowsample.py b/pyminer/widgets/flowchart/nodes/dataframeoperation/randomrowsample.py
new file mode 100644
index 0000000000000000000000000000000000000000..23523becf839b1a8d68dcaa4efe8f035aebb873e
--- /dev/null
+++ b/pyminer/widgets/flowchart/nodes/dataframeoperation/randomrowsample.py
@@ -0,0 +1,68 @@
+import sys
+from typing import List, Union, Tuple, Dict, TYPE_CHECKING, Callable
+from widgets import PMGFlowContent, PMGPanelDialog
+
+if TYPE_CHECKING:
+ import pandas as pd
+
+
+class RandomRowSample(PMGFlowContent):
+
+ def __init__(self):
+ super(RandomRowSample, self).__init__()
+ self.input_args_labels = ['输入']
+ self.output_ports_labels = ['取样'] # , '检验']
+ self.class_name = 'RandomRowSample'
+ self.text = '随机抽样'
+ self.icon_path = ''
+ self.info = {'sampling_rate': 0.2}
+
+ def process(self, *args) -> List:
+ """
+ 随机取样的方法
+ Args:
+ *args:
+
+ Returns:
+
+ """
+ import pandas as pd
+ assert isinstance(args[0], pd.DataFrame)
+ df: pd.DataFrame = args[0]
+ # if df.duplicated():
+ length = df.shape[0]
+ return [df.sample(n=int(self.info['sampling_rate'] * length))]
+
+ def on_settings_requested(self, parent):
+ """
+
+ Args:
+ parent:
+
+ Returns:
+
+ """
+ views = [('numberspin_ctrl', 'sampling_rate', '抽样比率', self.info['sampling_rate'], '', (0, 1), 0.0001)]
+ dlg = PMGPanelDialog(parent=parent, views=views)
+
+ dlg.setMinimumSize(600, 480)
+ dlg.exec_()
+
+ self.info['sampling_rate'] = dlg.panel.get_value()['sampling_rate']
+
+
+if __name__ == '__main__':
+ import sys
+
+ from PySide2.QtWidgets import QApplication
+
+ app = QApplication(sys.argv)
+
+ # r.load_info({'gen_array': False, 'size': (1, 2, 3), 'type': 'normal'})
+ # r.process()
+ info = {'sampling_rate': 0.2}
+ r = RandomRowSample()
+ r.load_info(info)
+ r.on_settings_requested(None)
+ print(r.info)
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/flowchart/nodes/dfoperation.py b/pyminer/widgets/flowchart/nodes/dfoperation.py
new file mode 100644
index 0000000000000000000000000000000000000000..d7d692fd0659527b238871eabd11e2ce26f14134
--- /dev/null
+++ b/pyminer/widgets/flowchart/nodes/dfoperation.py
@@ -0,0 +1,118 @@
+import sys
+from typing import List, Union, Tuple, Dict, TYPE_CHECKING, Callable
+from widgets import PMGFlowContent, PMGPanelDialog
+
+if TYPE_CHECKING:
+ import pandas as pd
+
+
+class DataReplace(PMGFlowContent):
+
+ def __init__(self):
+ super(DataReplace, self).__init__()
+ self.input_args_labels = ['in']
+ self.output_ports_labels = ['out']
+ self.class_name = 'DataReplace'
+ self.text = '数据替换'
+ self.icon_path = ''
+ self.agg = None
+ self.info = {'regulations': []}
+
+ def get_rule(self, match_words) -> Tuple[Callable, Callable]:
+ """
+ 抽象为:
+ 待替换的字符(origin),match,replacement,
+ Args:
+ match_words:
+
+ Returns:
+
+ """
+ if match_words:
+ return self.equals, lambda origin, match, replacement: replacement
+ else:
+ return self.contains, lambda origin, match, replacement: origin.replace(match, replacement)
+
+ def equals(self, origin: str, match: str) -> bool:
+ return origin == match
+
+ def contains(self, origin: str, match: str) -> bool:
+ if isinstance(origin, str):
+ return origin.find(match) != -1
+ return False
+
+ def process(self, *args) -> List:
+ """
+
+ Args:
+ *args:
+
+ Returns:
+
+ """
+ import pandas as pd
+ assert isinstance(args[0], pd.DataFrame)
+ df: pd.DataFrame = args[0]
+ regulations = self.info['regulations']
+
+ for regulation in regulations:
+ column_name = regulation['input_col_name']
+ match = regulation['str_to_replace']
+ replace_with = regulation['replace_with']
+ match_words = regulation['match_words']
+ match_rule, replace_rule = self.get_rule(match_words=match_words)
+ if column_name == '$ALL' or column_name == '':
+ for row in range(df.shape[0]):
+ for col in range(df.shape[1]):
+ if match_rule(df.iloc[row, col], match):
+ df.iloc[row, col] = replace_rule(df.iloc[row, col], match, replace_with)
+ else:
+ column = df[column_name]
+ # print(column,match)
+ for row in range(df.shape[0]):
+ print('row', row, column_name)
+ if match_rule(column[row], match):
+ df.loc[row, column_name] = replace_rule(column[row], match, replace_with)
+
+ return [df]
+
+ def check_data(self, data):
+ pass
+
+ def load_info(self, info: dict):
+ print('load info!!!!!!!!!!')
+ self.info = info
+ print(self.info)
+
+ def on_settings_requested(self, parent):
+ '''
+ headers = ['输入流字段', '输出流字段', '待替换数据', '替换为', '使用正则表达式', '全字匹配', '大小写敏感']
+ self.table_keys = ['input_col_name', 'output_col_name', 'str_to_replace', 'replace_with', 'match_words',
+ 'regex',
+ 'case_sensitive']
+ Args:
+ parent:
+
+ Returns:
+
+ '''
+ views = [['rules_ctrl', 'rules', '规则编辑',
+ [
+ {'name': 'input_col_name', 'text': '输入流字段', 'init': '$ALL'},
+ {'name': 'output_col_name', 'text': '输出流字段', 'init': ''},
+ {'name': 'str_to_replace', 'text': '待替换数据', 'init': ''},
+ {'name': 'replace_with', 'text': '替换为', 'init': ''},
+ {'name': 'regex', 'text': '使用正则表达式', 'init': False},
+ {'name': 'match_words', 'text': '全字匹配', 'init': True},
+ {'name': 'case_sensitive', 'text': '大小写敏感', 'init': False}
+ ],
+ ]
+ ]
+ dlg = PMGPanelDialog(parent=parent, views=views)
+
+ dlg.setMinimumSize(600, 480)
+
+ dlg.panel.set_value({'rules': self.info['regulations']})
+ dlg.exec_()
+
+ self.info['regulations'] = dlg.panel.get_value()['rules']
diff --git a/pyminer/widgets/flowchart/nodes/docparser.py b/pyminer/widgets/flowchart/nodes/docparser.py
new file mode 100644
index 0000000000000000000000000000000000000000..5cddb05ff43424b7c7fd1da6078bc2fa8ae39d7c
--- /dev/null
+++ b/pyminer/widgets/flowchart/nodes/docparser.py
@@ -0,0 +1,35 @@
+from typing import Callable
+import json
+
+
+def parse_doc(function: Callable):
+ doc: str = function.__doc__
+ row_iter = filter(lambda s: s != '', doc.split('\n'))
+ dic = {}
+ for row in row_iter:
+ split_list = row.split(':')
+ if len(split_list) == 2:
+ var_name, value_str = split_list
+ var_name = var_name.strip()
+ value_str = value_str.strip()
+ if var_name == 'input' or var_name == 'output':
+ assert value_str.startswith('[') and value_str.endswith(']')
+ try:
+ dic[var_name] = json.loads(value_str.strip())
+ except:
+ raise Exception(repr(value_str.strip()) + ' cannot be parsed as a list!')
+ else:
+ dic[var_name] = value_str
+ else:
+ continue
+ assert isinstance(dic.get('input'), list), '\'input\' is not defined.'
+ assert isinstance(dic.get('output'), list) is not None, '\'output\' is not defined.'
+ print(dic)
+
+
+if __name__ == '__main__':
+ from widgets.flowchart.nodes.reliabilities import relia_and
+
+ doc = relia_and.__doc__
+ print(doc)
+ parse_doc(relia_and)
diff --git a/pyminer/widgets/flowchart/nodes/io/__init__.py b/pyminer/widgets/flowchart/nodes/io/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..2502bf1c153de2312fb0ba8ed8891e92286b9014
--- /dev/null
+++ b/pyminer/widgets/flowchart/nodes/io/__init__.py
@@ -0,0 +1,3 @@
+from .iterator import Iterator
+from .listdir import ListDirs
+from .pdimport import PandasImport, PandasFileImport
diff --git a/pyminer/widgets/flowchart/nodes/io/iterator.py b/pyminer/widgets/flowchart/nodes/io/iterator.py
new file mode 100644
index 0000000000000000000000000000000000000000..aa60b772f1178257a4f60bd8dfb06eca355c27ef
--- /dev/null
+++ b/pyminer/widgets/flowchart/nodes/io/iterator.py
@@ -0,0 +1,68 @@
+"""
+有无办法通过迭代器方式递归实现流程图的迭代仿真?
+"""
+import sys
+import time
+from typing import List, Union, Tuple, Dict, TYPE_CHECKING, Callable
+from widgets import PMGFlowContent, PMGPanelDialog
+
+if TYPE_CHECKING:
+ import pandas as pd
+
+
+class Iterator(PMGFlowContent):
+
+ def __init__(self):
+ super(Iterator, self).__init__()
+ self.input_args_labels = []
+ self.output_ports_labels = ['取样'] # , '检验']
+ self.class_name = 'Iterator'
+ self.text = '迭代器'
+ self.icon_path = ''
+ self.info = {'sampling_rate': 0.2}
+
+ def process(self, *args) -> List:
+ """
+ 基础迭代器
+ Args:
+ *args:
+
+ Returns:
+
+ """
+ for i in range(10):
+ yield [i]
+
+ def on_settings_requested(self, parent):
+ """
+
+ Args:
+ parent:
+
+ Returns:
+
+ """
+ views = [('numberspin_ctrl', 'sampling_rate', '抽样比率', self.info['sampling_rate'], '', (0, 1), 0.0001)]
+ dlg = PMGPanelDialog(parent=parent, views=views)
+
+ dlg.setMinimumSize(600, 480)
+ dlg.exec_()
+
+ self.info['sampling_rate'] = dlg.panel.get_value()['sampling_rate']
+
+
+if __name__ == '__main__':
+ import sys
+
+ from PySide2.QtWidgets import QApplication
+
+ app = QApplication(sys.argv)
+
+ # r.load_info({'gen_array': False, 'size': (1, 2, 3), 'type': 'normal'})
+ # r.process()
+ info = {'sampling_rate': 0.2}
+ r = Iterator()
+ r.load_info(info)
+ r.on_settings_requested(None)
+ print(r.info)
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/flowchart/nodes/io/listdir.py b/pyminer/widgets/flowchart/nodes/io/listdir.py
new file mode 100644
index 0000000000000000000000000000000000000000..499153e3e1310cb0c471ca7dfac06e1e5cdda633
--- /dev/null
+++ b/pyminer/widgets/flowchart/nodes/io/listdir.py
@@ -0,0 +1,81 @@
+"""
+有无办法通过迭代器方式递归实现流程图的迭代仿真?
+"""
+import os
+import sys
+import time
+from typing import List, Union, Tuple, Dict, TYPE_CHECKING, Callable
+from widgets import PMGFlowContent, PMGPanelDialog
+
+if TYPE_CHECKING:
+ import pandas as pd
+
+
+class ListDirs(PMGFlowContent):
+
+ def __init__(self):
+ super(ListDirs, self).__init__()
+ self.input_args_labels = []
+ self.output_ports_labels = ['路径'] # , '检验']
+ self.class_name = 'ListDirs'
+ self.text = '文件列举'
+ self.icon_path = ''
+ self.info = {
+ 'ext_filter': '.csv',
+ 'dir': ''
+ }
+
+ def process(self, *args) -> List:
+ """
+ 基础迭代器
+ Args:
+ *args:
+
+ Returns:
+
+ """
+ ext = self.info['ext_filter']
+ files = [path for path in os.listdir(self.info['dir']) if os.path.splitext(path)[1] == ext]
+ for file_path in files:
+ path = os.path.normcase(os.path.join(self.info['dir'], file_path))
+ yield [path]
+
+ def on_settings_requested(self, parent):
+ """
+
+ Args:
+ parent:
+
+ Returns:
+
+ """
+ views = [
+ ('folder_ctrl', 'dir', '选取路径', self.info['dir']),
+ ('line_ctrl', 'ext_filter', '扩展名类型', self.info['ext_filter']),
+ ]
+ dlg = PMGPanelDialog(parent=parent, views=views)
+
+ dlg.setMinimumSize(600, 480)
+ dlg.exec_()
+
+ self.info = dlg.panel.get_value()
+
+
+if __name__ == '__main__':
+ import sys
+
+ from PySide2.QtWidgets import QApplication
+
+ app = QApplication(sys.argv)
+
+ # r.load_info({'gen_array': False, 'size': (1, 2, 3), 'type': 'normal'})
+ # r.process()
+ info = {
+ 'ext_filter': '.csv',
+ 'dir': '/home/'
+ }
+ r = ListDirs()
+ r.load_info(info)
+ r.on_settings_requested(None)
+ print(r.info)
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/flowchart/nodes/io/pdimport.py b/pyminer/widgets/flowchart/nodes/io/pdimport.py
new file mode 100644
index 0000000000000000000000000000000000000000..38a7d972645427c65b40c8398a6ed7fff893dfef
--- /dev/null
+++ b/pyminer/widgets/flowchart/nodes/io/pdimport.py
@@ -0,0 +1,194 @@
+"""
+有无办法通过迭代器方式递归实现流程图的迭代仿真?
+"""
+import os
+import sys
+import time
+from typing import List, Union, Tuple, Dict, TYPE_CHECKING, Callable
+from widgets import PMGFlowContent, PMGPanelDialog, ErrorReporter, PANEL_VIEW_CLASS
+
+if TYPE_CHECKING:
+ import pandas as pd
+
+
+def import_pandas(file: str, **kwargs):
+ if not isinstance(file, str):
+ raise ErrorReporter.create_type_error('File path', file, str)
+ if not os.path.exists(file):
+ raise ErrorReporter.create_file_not_found_error(file)
+
+ import pandas as pd
+
+ ext = os.path.splitext(file)[1]
+ kw = {'skiprows': kwargs['skiprows']}
+ if kwargs['limit_nrows'] == True:
+ kw['nrows'] = kwargs['nrows']
+ if kwargs['find_header_in_table'] == True:
+ kw['header'] = kwargs['header']
+ else:
+ kw['header'] = None
+ print(kw)
+ if ext == '.csv':
+ return [pd.read_csv(file, sep=kwargs['csv_sep'], **kw)]
+ elif ext == '.xls' or ext == '.xlsx':
+ return [pd.read_excel(file, **kw)]
+ else:
+ raise ValueError('Cannot Read file of this extension: \'%s\'' % ext)
+
+
+class BaseTableImport(PMGFlowContent):
+ def __init__(self):
+ super().__init__()
+ self.info = {
+ 'sampling_rate': 0.2, 'file_path': '',
+ 'csv_sep': ',',
+ 'find_header_in_table': True,
+ 'skiprows': 0,
+ 'limit_nrows': False,
+ 'nrows': -1,
+ 'header': 0
+ }
+
+ def format_settings(self, views: PANEL_VIEW_CLASS) -> PANEL_VIEW_CLASS:
+ info = self.info
+ views += [
+ [('combo_ctrl', 'csv_sep', 'CSV分隔符', info['csv_sep'], ['\t', ','], ['制表符(Tab,\\t)', '逗号(’,‘)'])],
+ ('numberspin_ctrl', 'skiprows', '跳过行数', info['skiprows'], '', (0, 100)),
+ [
+ ('check_ctrl', 'find_header_in_table', '从表中获取列名', info['find_header_in_table']),
+ ('numberspin_ctrl', 'header', '表头行数', info['header'], '', (0, 100)),
+ ], [
+ ('check_ctrl', 'limit_nrows', '限制最大行数', info['limit_nrows']),
+ ('numberspin_ctrl', 'nrows', '读取最大行数', info['nrows'], '', (0, 100000)),
+ ]
+ ]
+ return views
+
+ def process(self) -> List:
+ """
+ 基础迭代器
+ Args:
+ *args:
+
+ Returns:
+
+ """
+ return import_pandas(self.info['file_path'], **self.info)
+ # csv_sep=self.info['csv_sep'], skip_rows=self.info['skiprows'],
+ # first_row_as_header=self.info['find_header_in_table'], nrows=self.info['nrows'],limit_nrows = self.info['limit_nrows'] )
+
+
+class PandasFileImport(BaseTableImport):
+ def __init__(self):
+ super(PandasFileImport, self).__init__()
+ self.input_args_labels = []
+ self.output_ports_labels = ['数据集'] # , '检验']
+ self.class_name = 'PandasImport'
+ self.text = '读取Pandas数据集'
+ self.icon_path = ''
+
+ def on_settings_requested(self, parent):
+ """
+
+ Args:
+ parent:
+
+ Returns:
+
+ """
+ views = [
+ ('file_ctrl', 'file_path', '导入文件的路径', self.info['file_path']),
+ ]
+ views = self.format_settings(views)
+ dlg = PMGPanelDialog(parent=parent, views=views)
+ dlg.setMinimumSize(600, 480)
+ dlg.exec_()
+ self.info = dlg.panel.get_value()
+
+ def process(self) -> List:
+ """
+ 基础迭代器
+ Args:
+ *args:
+
+ Returns:
+
+ """
+ return import_pandas(self.info['file_path'], **self.info)
+
+
+class PandasImport(BaseTableImport):
+
+ def __init__(self):
+ super(PandasImport, self).__init__()
+ self.input_args_labels = ['路径']
+ self.output_ports_labels = ['数据集'] # , '检验']
+ self.class_name = 'PandasImport'
+ self.text = '从上游路径\n输入Pandas数据集'
+ self.icon_path = ''
+
+ def on_settings_requested(self, parent):
+ """
+
+ Args:
+ parent:
+
+ Returns:
+
+ """
+ views = self.format_settings([])
+
+ dlg = PMGPanelDialog(parent=parent, views=views)
+ dlg.setMinimumSize(600, 480)
+ dlg.exec_()
+ self.info = dlg.panel.get_value()
+
+ def process(self,path) -> List:
+ """
+ 被调用时执行的代码
+ Args:
+ *args:
+
+ Returns:
+
+ """
+ return import_pandas(path, **self.info)
+
+
+if __name__ == '__main__':
+ import sys
+
+ from PySide2.QtWidgets import QApplication
+
+ app = QApplication(sys.argv)
+
+ info = {'sampling_rate': 0.2}
+ pj = lambda *s: os.path.normcase(os.path.join(*s))
+ folder = pj(os.path.dirname(__file__), 'test_files')
+ r = PandasFileImport()
+ r.load_info({
+ 'sampling_rate': 0.2,
+ 'file_path': pj(folder, 'test.xlsx'),
+ 'csv_sep': '\t',
+ 'find_header_in_table': True,
+ 'skiprows': 0,
+ 'limit_nrows': True,
+ 'nrows': 3,
+ 'header': 0
+ })
+ print(r.process()[0])
+
+ r.load_info({
+ 'sampling_rate': 0.2,
+ 'file_path': pj(folder, 'test.csv'),
+ 'csv_sep': '\t',
+ 'find_header_in_table': False,
+ 'skiprows': 3,
+ 'limit_nrows': False,
+ 'nrows': -1,
+ 'header': 0
+ })
+
+ print(r.process()[0])
+
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/flowchart/nodes/plots.py b/pyminer/widgets/flowchart/nodes/plots.py
new file mode 100644
index 0000000000000000000000000000000000000000..1c6743310165102311ebc883e5b4f1f50697fe31
--- /dev/null
+++ b/pyminer/widgets/flowchart/nodes/plots.py
@@ -0,0 +1,100 @@
+import sys
+from typing import List, Union, Tuple
+from widgets import PMGFlowContent
+import numpy as np
+import matplotlib.pyplot as plt
+
+
+class LinePlot(PMGFlowContent):
+ """
+ 根据输入的数据,绘制一条直线。
+ 允许输入的数据类型:多个端口的直线。
+
+ """
+
+ def __init__(self):
+ super(LinePlot, self).__init__()
+ self.input_args_labels = ['in1']
+ self.output_ports_labels = []
+ self.class_name = 'LinePlot'
+ self.text = '折线图'
+ self.icon_path = ''
+ self.agg = None
+
+ # self.info = {'gen_array': False, 'size': (1, 2, 3),
+ # 'type': 'normal'} # 命名为self.info的变量会被自动保存,下一次会调用load_info方法进行读取。
+
+ def process(self, *args) -> List:
+ """
+
+ Args:
+ *args:
+
+ Returns:
+
+ """
+ from packages.pmagg import PMAgg
+ for arg in args:
+ pass
+ if self.agg is None:
+ self.agg = PMAgg.Window()
+
+ plt.plot([1, 2, 3, 4, 5])
+ fig = plt.gcf()
+ self.agg.get_canvas(fig)
+ self.agg.show()
+
+ return []
+
+ def check_data(self, data):
+ pass
+
+ def load_info(self, info: dict):
+ print('load info!!!!!!!!!!')
+ self.info = info
+ print(self.info)
+
+
+class HistPlot(PMGFlowContent):
+ """
+ 根据输入的数据,绘制一条直线。
+ 允许输入的数据类型:多个端口的直线。
+
+ """
+
+ def __init__(self):
+ super(HistPlot, self).__init__()
+ self.input_args_labels = ['in1']
+ self.output_ports_labels = []
+ self.class_name = 'HistPlot'
+ self.text = '条形图'
+ self.icon_path = ''
+ self.agg = None
+
+ def process(self, *args) -> List:
+ """
+
+ Args:
+ *args:
+
+ Returns:
+
+ """
+ from packages.pmagg import PMAgg
+ if self.agg is None:
+ self.agg = PMAgg.Window()
+
+ plt.hist(x=args, bins=20, color='steelblue', edgecolor='black')
+ fig = plt.gcf()
+ self.agg.get_canvas(fig)
+ self.agg.show()
+
+ return []
+
+ def check_data(self, data):
+ pass
+
+ def load_info(self, info: dict):
+ print('load info!!!!!!!!!!')
+ self.info = info
+ print(self.info)
diff --git a/pyminer/widgets/flowchart/nodes/random.py b/pyminer/widgets/flowchart/nodes/random.py
new file mode 100644
index 0000000000000000000000000000000000000000..7b0bcb17cb1f634a9faa468d9c10a089695ccca5
--- /dev/null
+++ b/pyminer/widgets/flowchart/nodes/random.py
@@ -0,0 +1,74 @@
+import sys
+from typing import List, Union, Tuple
+
+from PySide2.QtWidgets import QApplication, QDialog, QVBoxLayout, QLineEdit
+from widgets import PMGFlowContent, PMGPanelDialog
+import numpy as np
+
+
+class Random(PMGFlowContent):
+ """
+ 随机数生成的类
+ 可以切换生成的数据类型是整数/浮点还是矩阵。
+
+ """
+
+ def __init__(self):
+ super(Random, self).__init__()
+ self.input_args_labels = []
+ self.output_ports_labels = ['output1']
+ self.class_name = 'Random'
+ self.text = '随机数生成'
+ self.icon_path = ''
+
+ self.info = {'gen_array': False, 'size': (1, 2, 3),
+ 'type': 'normal'} # 命名为self.info的变量会被自动保存,下一次会调用load_info方法进行读取。
+
+ def process(self, *args) -> List[Union[int, float, np.ndarray]]:
+ """
+ 事件处理的方法。
+ Args:
+ *args:
+
+ Returns:
+
+ """
+ if self.info['gen_array']:
+ if self.info['type'] == 'uniform':
+ return [np.random.random(size=self.info['size'])]
+ elif self.info['type'] == 'normal':
+ return [np.random.randn(*self.info['size'])]
+ else:
+ if self.info['type'] == 'uniform':
+ return [np.random.random()]
+ elif self.info['type'] == 'normal':
+ return [np.random.randn()]
+
+ def on_settings_requested(self, parent):
+ """
+
+ Args:
+ parent:
+
+ Returns:
+
+ """
+ assert len(self.info.keys()) > 0, 'info is empty.there maybe no information stored.'
+ views = [
+ ('check_ctrl', 'gen_array', 'Generate Array', self.info['gen_array']),
+ ('eval_ctrl', 'size', 'Size', self.info['size'], 'safe'),
+ ('combo_ctrl', 'type', 'Type', self.info['type'], ['uniform', 'normal'], ['均匀分布', '正态分布'])
+ ]
+ dlg = PMGPanelDialog(parent=parent, views=views)
+ dlg.panel.set_param_changed_callback('gen_array',
+ lambda params: dlg.panel.get_ctrl('size').setEnabled(params['gen_array']))
+ dlg.exec_()
+ value = dlg.panel.get_value()
+ self.load_info(value)
+
+ def format_param(self) -> str:
+ return '模式:' + str(self.info['type'])
+
+ def load_info(self, info: dict):
+ self.info = info
+ print(self.info)
diff --git a/pyminer/widgets/flowchart/nodes/reliabilities.py b/pyminer/widgets/flowchart/nodes/reliabilities.py
new file mode 100644
index 0000000000000000000000000000000000000000..db15bd49038122ea5aeb6be997789aaf37e74773
--- /dev/null
+++ b/pyminer/widgets/flowchart/nodes/reliabilities.py
@@ -0,0 +1,19 @@
+def relia_or(*args):
+ """
+ input:["*"]
+ output:["out"]
+ text:"and"
+ """
+ pass
+
+
+def relia_and(*args):
+ """
+ input:["*"]
+ output:["out"]
+ text:"and"
+ """
+
+
+if __name__ == '__main__':
+ print(relia_and.__doc__)
diff --git a/pyminer/widgets/flowchart/nodes/simplecalc.py b/pyminer/widgets/flowchart/nodes/simplecalc.py
new file mode 100644
index 0000000000000000000000000000000000000000..dd3af3ebd8b91ad2878f43105810f08c2d634689
--- /dev/null
+++ b/pyminer/widgets/flowchart/nodes/simplecalc.py
@@ -0,0 +1,78 @@
+import sys
+from typing import List
+
+from PySide2.QtWidgets import QApplication, QDialog, QVBoxLayout, QLineEdit
+from widgets import PMFlowWidget, PMGFlowContent
+
+
+class ContentDialog(QDialog):
+ def __init__(self, parent=None, initial_value: float = 0):
+ super().__init__(parent)
+ self.setLayout(QVBoxLayout())
+ self.line_edit = QLineEdit()
+ self.layout().addWidget(self.line_edit)
+ self.line_edit.setText(str(initial_value))
+
+
+class Constant(PMGFlowContent):
+ def __init__(self):
+ super(Constant, self).__init__()
+ self.input_args_labels = []
+ self.output_ports_labels = ['output1']
+ self.class_name = 'Constant'
+ self.text = '常数'
+ self.icon_path = ''
+ self._value = 0
+
+ def process(self, *args) -> List[object]:
+ return [self._value]
+
+ def on_settings_requested(self, parent):
+ dlg = ContentDialog(parent)
+
+ dlg.exec_()
+ self._value = float(dlg.line_edit.text())
+ print(self._value)
+
+ def format_param(self) -> str:
+ return '值:' + str(self._value)
+
+
+class Add(PMGFlowContent):
+ def __init__(self):
+ super(Add, self).__init__()
+ self.input_args_labels = ['in1', 'in2']
+ self.output_ports_labels = ['out']
+ self.class_name = 'Add'
+ self.text = '求和'
+ self.icon_path = ''
+ self.ports_changable = [True, False]
+
+ def process(self, *args) -> List[object]:
+ if len(args) > 1:
+ sum = args[0]
+ for a in args[1:]:
+ sum += a
+ else:
+ raise ValueError
+ print(args[0], args[1])
+ return [sum]
+
+
+class Mul(PMGFlowContent):
+ def __init__(self):
+ super().__init__()
+ self.input_args_labels = ['in1', 'in2']
+ self.output_ports_labels = ['out']
+ self.class_name = 'Mul'
+ self.text = '乘积'
+ self.icon_path = ''
+
+ def process(self, *args) -> List[object]:
+ if len(args) > 1:
+ mul = args[0]
+ for a in args[1:]:
+ mul *= a
+ else:
+ raise ValueError
+ return [mul]
diff --git a/pyminer/widgets/flowchart/readme.md b/pyminer/widgets/flowchart/readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..0beb2ee14093190f12a4b2e2814799c5dbacf3c3
--- /dev/null
+++ b/pyminer/widgets/flowchart/readme.md
@@ -0,0 +1,136 @@
+# 流程图
+
+widgets.flowchart是流程图绘制子包。依赖于networkx和qtpy。
+
+简单的示例可以直接执行`widgets/flowchart/flowchart_widget.py`
+
+## 组成部分
+
+
+
+使用:
+
+```python
+from widgets import PMFlowWidget
+app = QApplication(sys.argv)
+graphics = PMFlowWidget(path=r'C:/Users/12957/Desktop/乡土中国.pmcache')
+graphics.show()
+sys.exit(app.exec_())
+```
+
+PMFlowWidget可以像普通的QWidget那样嵌套在任何位置。
+
+启动参数:
+
+parent:指定父控件。
+
+path:指定启动时加载的流程图。
+
+PMFlowWidget暂时不支持清空重绘。可以销毁当前控件再新建一个控件。
+
+## 操作
+
+### 预留节点
+
+很多节点是已经预留好的。比如求和、求积等。
+
+### 添加节点
+
+
+
+点击右键菜单的New,即可插入一个新的自定义节点。
+
+也可单击节点菜单插入新的节点。
+
+### 连线
+
+单击节点的一个出端口即可开始连线,连线**必须终止于不同节点的入端口**,否则无法连接。
+
+
+
+### 编辑节点
+
+双击节点即可编辑
+
+
+
+- Node Text:节点显示的文字信息
+- Set Inputs:输入节点端口编辑。点击`+`可以添加一个端口,点击`-`删除当前端口。双击选项可以编辑文字。
+- Set Outputs:输出节点编辑,和输入相同
+- Input Python Code:输入Python代码,是节点执行的内容。
+### 运行代码
+点击`Run`即可运行。节点代码执行后,会在此节点位置显示执行的输入输出结果。
+
+执行之前情况如下
+
+执行之后情况如下:
+
+图片:image-20201017130216358
+路径:C:/Users/12957/AppData/Roaming/Typora/typora-user-images/image-20201017130216358.png
+请检查此路径。
+
+### 集成到程序中并运行代码
+
+#### 在UI线程中直接运行代码
+
+参考widgets/flowchart/tests/continously_data_process.py文件中的内容
+可以这样做:
+
+获取流程图的对象
+
+```python
+graphics = PMFlowWidget(path='continously_data_process.json')
+graphics.show()
+result = graphics.run_in_fg([1])
+```
+`run_in_fg`方法适用于有明显的开始和结束节点的情况。
+
+在执行时,所有的节点将会进行**拓扑排序**。拓扑排序的目的,就是保证**节点的执行顺序符合用户连线的规定**。但是,当输入节点不唯一或输出节点不唯一时,
+(输入节点指的是“有出无进”的节点,而输出节点指的是“有进无出”的节点。)不能保证第一个执行的节点具体是哪个输入节点,或者
+最末一个输出节点是哪个输出节点——由此,就会出现错误。所以目前输入参数只支持输入和输出节点唯一的情况。
+
+`run_in_fg`的参数为一个列表(list),被传入第一个节点,然后以`*args`的方式展开为参数。返回值则是**最后一个节点的执行结果**。
+注意,第一个节点的参数和返回值都是list型,解包和打包操作在内部完成。
+
+[TODO!]之后需要支持用户指定输入和输出节点!
+
+`run_in_fg`方法可以让我们的流程图直接在UI线程之中处理计算任务,并且直接获取输出值。对于耗时不明显的操作,直接在前端中处理更加及时、准确、直观。当然,如果希望在运行过程中屏蔽用户对流程图的操作(比如说,程序执行过程中删除某个节点,可能导致灾难性的后果),那么使用这个方法应当也是可以的。
+
+当然,这对于UI线程而言,是本职工作之外的琐事。如果“琐事”消耗的时间太长,就会导致界面卡滞,严重影响用户体验。这样就需要在后台线程运行代码了。
+
+#### 在后台线程中运行代码
+
+[!TODO]接口已经留好,未完待续。。。
+
+#### 插入预定义节点
+
+在节点窗口中,点击“Add“可以创建一个自定义节点,双击列表项可以将节点插入到图形窗口中。点击‘‘Edit’’可以编辑当前选中的项。
+
+自己新建的自定义节点将被保存。
+
+### 自定义代码的规范
+
+- 输入参数数量与输入端口数目相同
+- 输出参数需要用列表包裹起来,列表长度与输出端口数目相同
+- 输入参数从左到右依次为输入端口从上到下的值,输出参数从左到右依次为输出参数从左到右的值。
+
+比如以下代码是规范的:
+
+```python
+import time
+def function(x):
+ return [x*2]
+```
+
+可以给函数添加默认值参数:
+
+```python
+import time
+def function(x=123):
+ return [x*2]
+```
+
+
+## 示例
+
+在widgets/flowchart/tests里面有一些示例可以供参考。
diff --git a/pyminer/widgets/flowchart/readme_arch.md b/pyminer/widgets/flowchart/readme_arch.md
new file mode 100644
index 0000000000000000000000000000000000000000..9662c7c2263cc84a741dc89fd635a1d9deef8ba9
--- /dev/null
+++ b/pyminer/widgets/flowchart/readme_arch.md
@@ -0,0 +1,29 @@
+# 流程图部分架构设计
+流程图由节点和连线组成。节点就是一个方框,连线就是连接不同方框之间的箭头。
+
+## 图形元素
+### 节点
+节点由底板图形、端口、图标以及若干文字对象组成。
+- 端口就是节点与其他节点相连的结构。端口分为出和入两种,连线只能从某个节点的“出”端口出发,指向其他节点的“入”端口。
+- 图标需要存储在icons文件夹下面。
+- 文字对象主要包括:节点的文字标记、端口文字标记、状态显示文字。
+
+当鼠标悬浮在节点上时,节点的底板图形会改变颜色;双击节点,即可弹出相关的设置界面。
+
+每一个节点都有内容`content`。`content`里面包含节点中运行的函数等。
+
+节点中的函数输入参数一一对应于端口,输出参数为一个列表,列表长度应当与输出端口相同,一一对应于输出端口。哪怕只有唯一一个输出参数,也应该用中括号括成列表的形式。
+
+节点有预定义好的节点,存储在pandas的dataframe里面。
+
+如果要继承节点,那么就继承content。节点的图形部分还是完全相同的。
+
+### 连线
+连线是由多个直线组成的折线。它的主要参数就是起点端口和终点端口。除了两端的端口之外,还有若干中继点。一个有n段的折线,包含n-1个中继点。
+
+第一段直线从起点端口出发,走向第一个中继点;后面的直线从前一个中继点到下一个中继点。最后一条直线的终点也就是连线的终点端口。中继节点存在列表中。
+
+双击任意一条连线,即可在相应位置插入一个中继点。插入操作同时也对中继节点列表以及连线列表进行了一次插入。插入之后,触发一次重绘事件。
+
+双击中继节点可以删除此中继节点。这个操作会弹出中继节点列表中的节点,同时也触发一次重绘事件。
+
diff --git a/pyminer/widgets/flowchart/simulationwidget.py b/pyminer/widgets/flowchart/simulationwidget.py
new file mode 100644
index 0000000000000000000000000000000000000000..c4eda466c7aa70f361457c24c2a3b490ff7c873e
--- /dev/null
+++ b/pyminer/widgets/flowchart/simulationwidget.py
@@ -0,0 +1,149 @@
+"""
+node properties:
+id:str
+text:str
+icon:str(path of icon)
+ports:{}
+content:{}
+
+properties of contents:
+code:str
+type:str
+params:List[str]
+"""
+import json
+import os
+import sys
+import time
+from typing import List, Dict
+
+from widgets.flowchart.core.flow_content import FlowContentForFunction, flowcontent_types, PMGFlowContent
+
+from widgets.flowchart.core.flow_node import Node
+from widgets.flowchart.core.nodemanager import NodeManagerWidget
+from widgets.flowchart.core.flowchart_scene import PMGraphicsScene
+from widgets.flowchart.core.flowchart_widget import PMGraphicsView
+from PySide2.QtCore import QSize, QCoreApplication, QLineF, Qt, QThread, Signal
+from PySide2.QtGui import QColor, QKeyEvent, QWheelEvent, QCloseEvent
+from PySide2.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QToolButton, QSpacerItem, QSizePolicy, QGraphicsView, \
+ QFrame, QApplication, QFileDialog, QMessageBox
+from widgets.flowchart.core import PMFlowWidget
+
+COLOR_NORMAL = QColor(212, 227, 242)
+COLOR_HOVER = QColor(255, 200, 00)
+COLOR_HOVER_PORT = QColor(0, 0, 50)
+
+
+class PMGSimulationWidget(PMFlowWidget):
+ def __init__(self, parent=None, path=''):
+ super().__init__(parent, path)
+ self._path = path
+ _translate = QCoreApplication.translate
+ self.setObjectName("tab_flow")
+ self.base_layout = QHBoxLayout(self)
+ self.verticalLayout_6 = QVBoxLayout()
+ self.base_layout.addLayout(self.verticalLayout_6)
+ self.verticalLayout_6.setContentsMargins(0, 0, 0, 0)
+ self.verticalLayout_6.setSpacing(0)
+ self.verticalLayout_6.setObjectName("verticalLayout_6")
+ self.widget_3 = QWidget(self)
+ self.widget_3.setMinimumSize(QSize(0, 30))
+ self.widget_3.setObjectName("widget_3")
+
+ # self.horizontal_layout_down = QHBoxLayout(self.widget_3)
+ self.horizontal_layout = QHBoxLayout(self.widget_3)
+ self.horizontal_layout.setContentsMargins(0, 0, 0, 0)
+ self.horizontal_layout.setSpacing(1)
+ self.horizontal_layout.setObjectName("horizontalLayout_6")
+
+ self.tool_button_run_fg = QToolButton(self.widget_3)
+ self.tool_button_run_fg.setText('Run_fg')
+ self.horizontal_layout.addWidget(self.tool_button_run_fg)
+
+ self.tool_button_save = QToolButton(self.widget_3)
+ self.tool_button_save.setText('Save')
+ self.horizontal_layout.addWidget(self.tool_button_save)
+
+ self.tool_button_reset = QToolButton(self.widget_3)
+ self.tool_button_reset.setText('Reset')
+ self.horizontal_layout.addWidget(self.tool_button_reset)
+
+ self.tool_button_undo = QToolButton(self.widget_3)
+ self.tool_button_undo.setText('Undo')
+ self.horizontal_layout.addWidget(self.tool_button_undo)
+
+ self.tool_button_redo = QToolButton(self.widget_3)
+ self.tool_button_redo.setText('Redo')
+ self.horizontal_layout.addWidget(self.tool_button_redo)
+
+ spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+ self.horizontal_layout.addItem(spacerItem)
+ self.verticalLayout_6.addWidget(self.widget_3)
+
+ self.graphicsView = PMGraphicsView(self)
+
+ self.graphicsView.setFrameShape(QFrame.NoFrame)
+ self.graphicsView.setObjectName("graphicsView")
+ self.verticalLayout_6.addWidget(self.graphicsView)
+
+ self.scene = PMGraphicsScene(graphics_view=self.graphicsView, flow_widget=self, allow_multiple_input=True)
+ self.scene.setSceneRect(-1000, -1000, 2000, 2000)
+
+ self.node_manager = NodeManagerWidget(scene=self.scene)
+ # self.node_manager.scene = self.scene
+ self.base_layout.addWidget(self.node_manager)
+ self.nodes: List[Node] = self.scene.nodes
+ self.lines = self.scene.lines
+
+ self.node_manager.register_node_content(PMGFlowContent, 'simple_calc', 'UserDefinedFunc')
+ self.load_nodes_library()
+ self.graphicsView.setScene(self.scene)
+
+ self.tool_button_undo.clicked.connect(self.scene.undo)
+ self.tool_button_redo.clicked.connect(self.scene.redo)
+ # self.tool_button_open.clicked.connect(self.open)
+ self.tool_button_reset.clicked.connect(self.reset)
+ self.tool_button_save.clicked.connect(self.save)
+ self.tool_button_run_fg.clicked.connect(lambda: self.run_in_fg())
+ if self._path != '':
+ self.scene.load_flowchart(self._path)
+
+ # def load_nodes_library(self):
+ # import widgets.flowchart.nodes.simulation as package_sim
+ # import inspect
+ # for class_name in dir(package_sim):
+ # cls = eval('package_sim.' + class_name)
+ # if inspect.isclass(cls) and issubclass(cls, package_sim.BaseLigralGenerator):
+ # self.node_manager.register_node_content(cls, 'simple_calc')
+
+ def run_in_fg(self, input_args_list: List[object] = None) -> List[object]:
+ """
+ 前端直接进行数据处理,而非在后台线程执行。
+ 这样不能做耗时操作(因为会卡住界面),但是可以直接获取运行后的数据,并且结果相对简单一些。
+ :return:
+ """
+ from widgets.flowchart.nodes.simulation import BaseLigralGenerator
+ j_list = []
+ for node in self.scene.nodes:
+ content: BaseLigralGenerator = node.content
+ j_list.append(content.generate_ligjson())
+ print(content.generate_ligjson())
+ text = json.dumps(j_list, indent=4)
+ print(text)
+ ligral_path = r'c:\users\12957\Desktop\ligral.exe'
+ file_path = os.path.join(os.path.dirname(__file__), 'temp.lig.json')
+ with open(file_path, 'w')as f:
+ f.write(text)
+ from widgets.utilities.platform import run_command_in_terminal_block
+ run_command_in_terminal_block('%s %s --json' % (ligral_path, file_path))
+
+
+if __name__ == '__main__':
+ import cgitb
+
+ cgitb.enable()
+ app = QApplication(sys.argv)
+ graphics = PMGSimulationWidget()
+ graphics.load('flowchart_stat.pmcache')
+ graphics.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/flowchart/tests/__init__.py b/pyminer/widgets/flowchart/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/widgets/flowchart/tests/continously_data_process.py b/pyminer/widgets/flowchart/tests/continously_data_process.py
new file mode 100644
index 0000000000000000000000000000000000000000..4b9b7d88a9867ee21d33fcdf08913d660ae6eba6
--- /dev/null
+++ b/pyminer/widgets/flowchart/tests/continously_data_process.py
@@ -0,0 +1,40 @@
+"""
+DO] add a table to store some informations about calculation.
+"""
+import sys
+import time
+
+from PySide2.QtCore import QTimer
+from PySide2.QtWidgets import QApplication
+
+from widgets.flowchart.core.flowchart_widget import PMFlowWidget
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ graphics = PMFlowWidget(path='continously_data_process.json')
+ graphics.show()
+ graphics.pre_run()
+ graphics.run_in_fg()
+ timer = QTimer()
+ timer.start(100)
+ i = 0
+
+
+ def run_fg():
+ """
+ 在前端直接运行代码
+ :return:
+ """
+ t0 = time.time()
+ global i,timer
+ i += 1
+ result = graphics.run_fg_for_one_step()
+ print('exec result:', result)
+ if i>100:
+ timer.stop()
+ t1 = time.time()
+ print('time elapsed %f'%(t1-t0))
+
+
+ timer.timeout.connect(run_fg)
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/flowchart/tests/database_import.py b/pyminer/widgets/flowchart/tests/database_import.py
new file mode 100644
index 0000000000000000000000000000000000000000..e61cfeff2c94854a2904ab5e4b9b48c8cb2785ea
--- /dev/null
+++ b/pyminer/widgets/flowchart/tests/database_import.py
@@ -0,0 +1,16 @@
+import sys
+import time
+
+from PySide2.QtCore import QTimer
+from PySide2.QtWidgets import QApplication
+
+from widgets.flowchart.core.flowchart_widget import PMFlowWidget
+import cgitb
+cgitb.enable()
+if __name__ == '__main__':
+
+ times = {True: 0, False: 1}
+ app = QApplication(sys.argv)
+ graphics = PMFlowWidget(path='database_import.json')
+ graphics.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/flowchart/tests/fault_tree.py b/pyminer/widgets/flowchart/tests/fault_tree.py
new file mode 100644
index 0000000000000000000000000000000000000000..3b086dddd7d702f941ffca4b5eb81baa5b94ca68
--- /dev/null
+++ b/pyminer/widgets/flowchart/tests/fault_tree.py
@@ -0,0 +1,46 @@
+import sys
+import time
+
+from PySide2.QtCore import QTimer
+from PySide2.QtWidgets import QApplication
+
+from widgets.flowchart.core.flowchart_widget import PMFlowWidget
+
+if __name__ == '__main__':
+ times = {True: 0, False: 1}
+ app = QApplication(sys.argv)
+ graphics = PMFlowWidget(path='fault_tree.json')
+ graphics.show()
+ graphics.pre_run()
+ graphics.run_in_fg()
+ timer = QTimer()
+ timer.start(1)
+ i = 0
+
+
+ def run_fg():
+ """
+ 在前端直接运行代码
+ :return:
+ """
+ t0 = time.time()
+ global i, timer
+ steps = 100
+ for j in range(steps):
+ i += 1
+ # result = graphics.run_in_fg([])
+
+ result = graphics.run_fg_for_one_step()
+ times[result[0]] += 1
+
+ print('exec result:', result)
+ if i > 10000:
+ timer.stop()
+ t1 = time.time()
+
+ print('Run Times:%d,Reliability is %f' % (i, times[True] / (times[True] + times[False])))
+ print('time elapsed %f' % (t1 - t0))
+
+
+ timer.timeout.connect(run_fg)
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/flowchart/tests/node_test.py b/pyminer/widgets/flowchart/tests/node_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..e5d30976a6facbbb6b7334d58a4b289ac0f338cc
--- /dev/null
+++ b/pyminer/widgets/flowchart/tests/node_test.py
@@ -0,0 +1,22 @@
+import sys
+from typing import List, Union
+
+from PySide2.QtWidgets import QApplication, QDialog, QVBoxLayout, QLineEdit
+# from widgets.flowchart.nodes.random import Random
+# from widgets.flowchart.nodes.plots import HistPlot
+from widgets.flowchart.nodes.dfoperation import DataReplace
+import numpy as np
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+
+ # r.load_info({'gen_array': False, 'size': (1, 2, 3), 'type': 'normal'})
+ # r.process()
+ info = {
+ 'regulations': [
+ {'input_col_name': 'aa', 'output_col_name': 'aa', 'str_to_replace': 'aa', 'replace_with': 'dddd',
+ 'match_words': True, 'regex': False, 'case_sensitive': False}]}
+ r = DataReplace()
+ r.load_info(info)
+ r.on_settings_requested(None)
+ sys.exit(app.exec_())
diff --git "a/pyminer/widgets/flowchart/\345\210\233\345\273\272\346\226\260\350\212\202\347\202\271(deprecated).md" "b/pyminer/widgets/flowchart/\345\210\233\345\273\272\346\226\260\350\212\202\347\202\271(deprecated).md"
new file mode 100644
index 0000000000000000000000000000000000000000..81c15d282782e63f8ff855b90ed3549033f60795
--- /dev/null
+++ "b/pyminer/widgets/flowchart/\345\210\233\345\273\272\346\226\260\350\212\202\347\202\271(deprecated).md"
@@ -0,0 +1,54 @@
+# 如何创建一个新的节点类
+
+
+## 添加新节点类
+点击Add按钮添加一个节点
+
+## 编辑
+选中新增的`untitled`项,并且点击Edit编辑。
+
+弹出编辑面板效果如下:
+
+编辑面板的含义如下:
+
+
+## 实例
+
+若要创建一个数据库连接节点,我们将其抽象为:
+
+- 节点文字:DBConn
+- 输入端口没有,输出端口一个,数量不得改变。输出端口命名为‘db’
+- 图标暂不设置,使用默认图标
+- 组名称为IO(可以点击下拉菜单切换组名称)
+
+- 设置信息:ip地址、端口、用户名、密码。因此需要根据widgets的方式编写设置面板的json。
+
+编写后的效果如下:
+
+
+其中,函数的输入只是一个实例。最后一栏的JSON全文如下:
+
+```json
+[
+ ['line_ctrl', 'user_name', 'User Name', '12000'],
+ ['line_ctrl', 'password', 'Password', 'abcdef'],
+ ['line_ctrl', 'ip', 'IP', '127.0.0.1'],
+ ['number_ctrl', 'port', 'Port', 12306, '', [0, 65535]]
+]
+```
+
+点击`Check`按钮可以检验输入框的效果,弹出的对话框将反映你写的json可否正确的被转换为界面元素。
+
+
+直接点击右上角叉号退出对话框即可,你的变更将被自动保存。对话框关闭的瞬间,所做的变更将被存储到磁盘中,无需担心数据丢失。
+
+## 新建节点
+
+双击“DBConn”列表项,即可插入一个DBConn节点。
+
+点击右键“Edit”,即可编辑它。
+
+弹出的设置面板效果如图:
+
+
+点击“x”号,信息也会自动保存。
\ No newline at end of file
diff --git a/pyminer/pmgwidgets/get_time_consuming_classes.py b/pyminer/widgets/get_time_consuming_classes.py
similarity index 61%
rename from pyminer/pmgwidgets/get_time_consuming_classes.py
rename to pyminer/widgets/get_time_consuming_classes.py
index 61009b6295d8fccf7153024a92af3392ec35b7e3..7e1d643c2ac5bc43627c3edb0b81bf7a296495a5 100644
--- a/pyminer/pmgwidgets/get_time_consuming_classes.py
+++ b/pyminer/widgets/get_time_consuming_classes.py
@@ -1,9 +1,9 @@
import typing
if typing.TYPE_CHECKING:
- import pmgwidgets.widgets.basic.others as others
+ import widgets.widgets.basic.others as others
def get_ipython_console_class() -> typing.Type['others.console.PMGIpythonConsole']:
- import pmgwidgets.widgets.basic.others as others
+ import widgets.widgets.basic.others as others
return others.import_console_class()
diff --git a/pyminer/widgets/utilities/__init__.py b/pyminer/widgets/utilities/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..129155fdced6bd22f67c8634a799fe448af1dbd1
--- /dev/null
+++ b/pyminer/widgets/utilities/__init__.py
@@ -0,0 +1,3 @@
+from .platform import *
+from .source import *
+from .uilogics import *
\ No newline at end of file
diff --git a/pyminer/widgets/utilities/network/__init__.py b/pyminer/widgets/utilities/network/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..57b892902e91d6123f7c0d4c68a595043ac994d5
--- /dev/null
+++ b/pyminer/widgets/utilities/network/__init__.py
@@ -0,0 +1,5 @@
+from .baseclient import BaseClient, get_style_sheet
+from .generalclient import GeneralClient
+from .qtclient import PMClient
+from .server import PMGServer
+from .util import timeit
diff --git a/pyminer/widgets/utilities/network/baseclient.py b/pyminer/widgets/utilities/network/baseclient.py
new file mode 100644
index 0000000000000000000000000000000000000000..b45de04a29b86c6af38067a7b4e28a2842ee2c8d
--- /dev/null
+++ b/pyminer/widgets/utilities/network/baseclient.py
@@ -0,0 +1,289 @@
+import base64
+import json
+import socket
+import sys
+import time
+from typing import List, Union, Dict, Any
+
+import cloudpickle
+
+from widgets.utilities.network.util import generate_client_payload, strip_byte_end
+
+
+def get_style_sheet() -> str:
+ return BaseClient().get_style_sheet()
+
+
+class BaseClient(object):
+ def __init__(self, name='Anonymous BaseClient'):
+ self.name = name
+
+ def init_socket(self, port: int = 12306):
+ host = 'localhost'
+ client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ client.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) # 在客户端开启心跳维护
+ client.connect((host, port))
+ return client
+
+ def get_var(self, data_name: str):
+ """
+ 从工作空间获取数据
+ :param data_name:
+ :return:
+ """
+ payload = self.generate_request_template()
+ payload['method'] = 'read_p'
+ payload['params'] = [data_name]
+
+ response_dic = self.send_dict_and_get_request_dict(payload)
+ if response_dic.get('message') == 'succeeded':
+ data_b64 = response_dic.get(data_name)
+ s = self.load_data_from_b64(data_b64)
+ return s
+ else:
+ raise ValueError(response_dic.get('message'))
+
+ def dump_data_from_b64(self, data: Any) -> str:
+ try:
+ data_dumped_bytes = cloudpickle.dumps(data)
+ return base64.b64encode(data_dumped_bytes).decode('ascii')
+ except:
+ return ''
+
+ def load_data_from_b64(self, data_b64: str) -> object:
+ try:
+ return cloudpickle.loads(base64.b64decode(data_b64))
+ except:
+ import traceback
+ traceback.print_exc()
+ return None
+
+ def delete_var(self, var_name: Union[str, List[str]], provider: str):
+ payload = self.generate_request_template()
+ payload['method'] = 'delete_variable'
+ payload['params'] = [var_name, provider]
+ response_dic = self.send_dict_and_get_request_dict(payload)
+ if response_dic['message'] == 'succeeded':
+ return response_dic['var_name']
+ else:
+ return []
+
+ def get_all_public_var_names(self):
+ """
+ 获取所有的可由外部获取的变量
+ :return:
+ """
+
+ payload = self.generate_request_template()
+ payload['method'] = 'get_all_public_variable_names'
+ payload['params'] = []
+ response_dic = self.send_dict_and_get_request_dict(payload)
+ if response_dic['message'] == 'succeeded':
+ return response_dic['var_names']
+ else:
+ return []
+
+ def get_all_var_names(self):
+ """
+ 获取所有的变量名称
+ :return:
+ """
+ payload = self.generate_request_template()
+ payload['method'] = 'get_all_variable_names'
+ payload['params'] = []
+ response_dic = self.send_dict_and_get_request_dict(payload)
+ if response_dic['message'] == 'succeeded':
+ return response_dic['var_names']
+ else:
+ return []
+
+ def set_var_dic(self, var_dic: dict, provider: str = 'server'):
+ """
+ 设置种类
+ :param var_dic:
+ :param provider:
+ :return:
+ """
+ t0 = time.time()
+ dic = {}
+ for k in var_dic.keys():
+ try:
+ b64 = self.dump_data_from_b64(var_dic[k])
+ dic[k] = b64
+ except:
+ import traceback
+ traceback.print_exc()
+ pass
+ dumped_data = self.dump_data_from_b64(dic)
+ payload = self.generate_request_template()
+ payload['method'] = 'write_var_dic'
+ payload['params'] = [dumped_data, provider]
+ t2 = time.time()
+ response_dic = self.send_dict_and_get_request_dict(payload)
+ t1 = time.time()
+ # print('set variable dict time elapsed %f s, with transfering consumed %f s.' % (t1 - t0, t1 - t2))
+ return response_dic
+
+ def get_all_vars(self):
+ # print('set variable dict time elapsed %f' % (t1 - t0))
+ payload = self.generate_request_template()
+ payload['method'] = 'get_var_dic'
+ payload['params'] = []
+ response_dic = self.send_dict_and_get_request_dict(payload)
+ if response_dic.get('var_dic') is not None:
+ b = response_dic.get('var_dic')
+ return self.load_data_from_b64(b)
+ else:
+ return response_dic
+
+ def get_vars(self, var_names: str):
+ payload = self.generate_request_template()
+ payload['method'] = 'get_vars'
+ payload['params'] = [var_names]
+ response_dic = self.send_dict_and_get_request_dict(payload)
+ if response_dic.get('var_dic') is not None:
+ b = response_dic.get('var_dic')
+ return self.load_data_from_b64(b)
+ else:
+ return response_dic
+ return
+
+ def get_all_var_names_of_type(self, type: str):
+ """
+ 获取一定类型的数据。
+ :param type:
+ :return:
+ """
+ assert type in ['string', 'array', 'table', 'numeric']
+ payload = self.generate_request_template()
+ payload['method'] = 'get_all_var_names_of_type'
+ payload['params'] = [type]
+ response_dic = self.send_dict_and_get_request_dict(payload)
+ if response_dic.get('message') == 'succeeded':
+ b = response_dic.get('var_names')
+ return self.load_data_from_b64(b)
+ else:
+ return []
+
+ def set_var(self, data_name: str, data: Any, provider: str = 'server'):
+ """
+ 向数据管理类中写入数据
+ Args:
+ data_name:
+ data:
+ provider:
+
+ Returns:
+
+ """
+ if sys.getsizeof(data) / (1024 * 1024) > 150:
+ raise MemoryError('Data \'%s\' size %f MB is larger than limit ( 150MB ) .' % (
+ data_name, sys.getsizeof(data) / 1024 / 1024))
+ dumped_data = self.dump_data_from_b64(data)
+ message = 'Data size after b64 encode %f MB is too large,it should less than 300MB after base64 encoded.\n ' % (
+ sys.getsizeof(dumped_data) / 1024 / 1024)
+
+ assert sys.getsizeof(
+ dumped_data) / 1024 / 1024 <= 300, MemoryError(message)
+
+ payload = self.generate_request_template()
+ payload['method'] = 'write_p'
+ payload['params'] = [data_name, dumped_data, provider]
+ response_dic = self.send_dict_and_get_request_dict(payload)
+ return response_dic
+
+ def get_settings(self) -> dict:
+ """
+ 获取主界面的设置项。
+ :return:
+ """
+ payload = self.generate_request_template()
+ payload['method'] = 'get_settings'
+ payload['params'] = []
+ result_dic = self.send_dict_and_get_request_dict(payload)
+ assert result_dic is not None
+ return result_dic
+
+ def set_settings_param(self, param_name: str, param_val: Any):
+ payload = self.generate_request_template()
+ payload['method'] = 'set_settings_param'
+ payload['params'] = [param_name, param_val]
+ result_dic = self.send_dict_and_get_request_dict(payload)
+ assert result_dic is not None
+ return result_dic
+
+ def get_style_sheet(self) -> str:
+ """
+ 获取主界面的样式表
+ :return:
+ """
+ payload = self.generate_request_template()
+ payload['method'] = 'get_style_sheet'
+ payload['params'] = []
+ result = self.send_dict_and_get_request_dict(payload).get('style_sheet')
+ assert result is not None
+ return result
+
+ def request_data(self, byte_data) -> bytes:
+ HOST = '127.0.0.1'
+ PORT = 12306
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 定义socket类型,网络通信,TCP
+ s.settimeout(5)
+ s.connect((HOST, PORT)) # 要连接的IP与端口
+ payload = self.generate_request_template()
+ payload['method'] = 'request_data'
+ s.send(json.dumps(payload).encode('utf-8'))
+ msg = s.recv(1024)
+ s.sendall(byte_data + b'PMEND') # 把命令发送给对端
+ recv_list = []
+ while (1):
+ b = s.recv(65536)
+ recv_list.append(b)
+ if b.endswith(b'PMEND'):
+ recv_list[-1] = strip_byte_end(recv_list[-1])
+ break
+ data = b''.join(recv_list) # 把接收的数据定义为变量
+
+ s.close() # 关闭连接
+ return data
+
+ def generate_request_template(self) -> Dict[str, Union[List, str]]:
+ template = generate_client_payload()
+ template['name'] = self.name
+ return template
+
+ def send_dict_and_get_request_dict(self, dict_to_send: Dict) -> Dict[str, Union[Dict, List, str, float, int]]:
+ try:
+ dict_byte = json.dumps(dict_to_send).encode('utf-8')
+ data_byte = self.request_data(dict_byte)
+ return json.loads(data_byte)
+ except TypeError:
+ import traceback
+ traceback.print_exc()
+ raise TypeError(
+ 'Cannot dump this object \'%s\' due to its type cannot be json serialized.' % repr(dict_to_send))
+ except json.decoder.JSONDecodeError:
+ import traceback
+ traceback.print_exc()
+ raise ValueError('Cannot decode this json: \'%s\'' % str(data_byte))
+ except ConnectionRefusedError:
+ raise ConnectionRefusedError('Connection Refused!')
+ except:
+ import traceback
+ traceback.print_exc()
+
+
+if __name__ == '__main__':
+ import numpy as np
+
+ dic = {
+ 'a': np.ndarray([200, 200]),
+ 'b': np.ndarray([200, 200]),
+ 'c': np.ndarray([200, 200]),
+ }
+ t0 = time.time()
+ bc = BaseClient()
+ a = bc.set_var_dic(dic)
+ # s = bc.get_all_var_names_of_type('table')
+ t1 = time.time()
+ print(t1 - t0)
diff --git a/pyminer/widgets/utilities/network/generalclient.py b/pyminer/widgets/utilities/network/generalclient.py
new file mode 100644
index 0000000000000000000000000000000000000000..d9655f3a71ea50ab62ba6ffa02c029df015b4a67
--- /dev/null
+++ b/pyminer/widgets/utilities/network/generalclient.py
@@ -0,0 +1,94 @@
+# coding=utf-8
+"""
+client端
+长连接,短连接,心跳
+"""
+import json
+import threading
+import zlib
+from typing import List
+
+import time
+
+from .util import timeit, receive
+from .baseclient import BaseClient
+
+
+def parse_splicing_packets(packet_bytes: bytes) -> List[bytes]:
+ return packet_bytes.split(b'PMEND')
+
+
+class GeneralClient(BaseClient):
+
+ def __init__(self, name: str = 'Anonymous GeneralClient'):
+ super().__init__(name)
+ self.client = self.init_socket(12306)
+ self.name = name
+ th = threading.Thread(target=self.run_event_loop)
+ th.setDaemon(True)
+ th.start()
+
+ def shut_down(self):
+ self.client.close()
+
+ def on_server_message_received(self, packet: bytes):
+ try:
+ dic = json.loads(packet)
+ msg = dic.get('message')
+ if msg == 'data_changed':
+ pass
+ except:
+ import traceback
+ traceback.print_exc()
+ pass
+
+ @timeit
+ def compress(self, byte_str):
+ return zlib.compress(byte_str)
+
+ def run_event_loop(self):
+ payload = {
+ "method": "start_long_connection",
+ "name": self.name,
+ "params": [],
+ "id": 0,
+ }
+ self.client.sendall(json.dumps(payload).encode('utf-8') + b'PMEND')
+ recv = receive(self.client)
+
+ message_dic = json.loads(recv)
+ if message_dic.get('message') == 'succeeded':
+ while True:
+ try:
+ recv = receive(self.client)
+ packets = parse_splicing_packets(recv)
+ for packet in packets:
+ self.on_server_message_received(packet)
+ except:
+ import traceback
+ traceback.print_exc()
+ break
+ else:
+ raise ConnectionError(message_dic.get('content'))
+
+
+if __name__ == '__main__':
+ import numpy as np
+
+ x = np.random.random(10) + np.linspace(1, 10, 10)
+ y = np.random.random(10) + np.linspace(1, 10, 10)
+ print(x)
+ c = GeneralClient()
+
+ print('set_var')
+ c.set_var('x', x)
+ c.set_var('y', y)
+ c.set_var('z', y)
+ c.set_var('w', y)
+ c.set_var('t', y)
+ c.set_var('y', y)
+ c.get_var('x')
+ print(c.get_all_vars())
+ print(c.get_all_var_names())
+ while (1):
+ time.sleep(1)
diff --git a/pyminer/widgets/utilities/network/qtclient.py b/pyminer/widgets/utilities/network/qtclient.py
new file mode 100644
index 0000000000000000000000000000000000000000..c177b3da36cd1ab06d47494fbaebf22a6afe59c9
--- /dev/null
+++ b/pyminer/widgets/utilities/network/qtclient.py
@@ -0,0 +1,125 @@
+# coding=utf-8
+__author__ = '侯展意'
+
+import json
+import logging
+import socket
+import sys
+import warnings
+import zlib
+from typing import List
+
+from PySide2.QtCore import QObject, QThread, Signal, QTimer
+from PySide2.QtWidgets import QApplication
+
+from .baseclient import BaseClient
+from .util import timeit, receive
+
+logger = logging.getLogger(__name__)
+
+
+def parse_splicing_packets(packet_bytes: bytes) -> List[bytes]:
+ return packet_bytes.split(b'PMEND')
+
+
+class RecvWork(QObject):
+ signal_received = Signal(bytes)
+
+ def __init__(self, sock: socket.socket, name: str):
+ super(RecvWork, self).__init__()
+ self.quit = False
+ self.socket = sock
+ self.name = name
+
+ def work(self):
+ payload = {
+ "method": "start_long_connection",
+ "name": self.name,
+ "id": 0,
+ }
+ self.socket.sendall(json.dumps(payload).encode('utf-8') + b'PMEND')
+ while True:
+ try:
+ recv = receive(self.socket)
+ assert recv != b''
+ packets = parse_splicing_packets(recv)
+ for p in packets:
+ self.signal_received.emit(p)
+ except AssertionError:
+ self.socket.close()
+ warnings.warn('Connection \'%s\' Closed!' % self.name)
+ break
+ except:
+ import traceback
+ traceback.print_exc()
+ break
+ self.thread().quit()
+
+ def close_socket(self):
+ self.socket.close()
+
+
+class PMClient(QObject, BaseClient):
+ signal_server_message_received = Signal(dict)
+ signal_data_changed = Signal(str)
+
+ def __init__(self, parent=None, name='Anonymous QtClient'):
+ super().__init__(parent)
+ self.name = name
+ self.client = self.init_socket(12306)
+ self.thread_recv = QThread()
+ self.worker_recv = RecvWork(self.client, self.name)
+ self.signal_received = self.worker_recv.signal_received
+ self.worker_recv.signal_received.connect(self.on_server_message_received)
+
+ self.worker_recv.moveToThread(self.thread_recv)
+ self.thread_recv.started.connect(self.worker_recv.work)
+ self.thread_recv.start()
+
+ def shut_down(self):
+ logger.info('client quit')
+ self.worker_recv.close_socket()
+ self.thread_recv.quit()
+ self.thread_recv.wait(500)
+
+ def on_server_message_received(self, packet: bytes):
+ try:
+ dic = json.loads(packet)
+ logger.info(dic)
+ msg = dic.get('message')
+ if msg == 'data_changed':
+ data_name = dic.get('data_name')
+ if data_name is not None:
+ self.signal_data_changed.emit(data_name)
+ self.signal_server_message_received.emit(dic)
+ except:
+ import traceback
+ traceback.print_exc()
+ pass
+
+ def send_bytes(self, packet: bytes):
+ self.client.sendall(packet)
+
+ @timeit
+ def compress(self, byte_str):
+ return zlib.compress(byte_str)
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ import numpy as np
+
+ x = np.random.random(10) + np.linspace(1, 10, 10)
+ y = np.random.random(10) + np.linspace(1, 10, 10)
+ print(x)
+ c = PMClient()
+ print('set_var')
+ c.set_var('x', x)
+ c.set_var('y', y)
+ c.get_var('x')
+ print(c.get_all_var_names())
+ timer = QTimer()
+ timer.start(10000)
+ timer.timeout.connect(lambda: c.get_var('x'))
+ c.shut_down()
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/utilities/network/server.py b/pyminer/widgets/utilities/network/server.py
new file mode 100644
index 0000000000000000000000000000000000000000..f69a0dc0e1196a7721abb277131890492b07594f
--- /dev/null
+++ b/pyminer/widgets/utilities/network/server.py
@@ -0,0 +1,215 @@
+"""
+基于qthread的服务器
+直接基于socket。
+
+"""
+
+import json
+import logging
+import socket
+import sys
+import time
+from typing import List, Tuple, Dict, Union
+
+from PySide2.QtCore import QObject, QThread, QTimer
+from PySide2.QtWidgets import QApplication
+
+from .util import receive, strip_byte_end
+
+logger = logging.getLogger(__name__)
+
+
+def parse_splicing_packets(packet_bytes: bytes) -> List[bytes]:
+ """
+ 处理粘包问题的函数
+ :param packet_bytes:
+ :return:
+ """
+ return packet_bytes.split(b'PMEND')
+
+
+def dict_to_byte_msg(dic: Dict) -> bytes:
+ b = (json.dumps(dic) + 'PMEND').encode('utf-8')
+ return b
+
+
+def generate_response_template() -> Dict[str, Union[str, int, Dict, List, float]]:
+ payload = {'message': 'succeeded', 'content': '', 'timestamp': time.time()}
+ return payload
+
+
+def send_dict(sock: socket.socket, dic: Dict):
+ sock.sendall(dict_to_byte_msg(dic))
+
+
+class LoopWork(QObject):
+ def __init__(self, server_obj: 'PMGServer', server_socket: socket.socket):
+ super().__init__()
+ self.server_socket = server_socket
+ self.server_obj = server_obj
+ self.threads = []
+
+ def work(self):
+ self.accept_client(self.server_socket)
+
+ def accept_client(self, server_socket):
+ """
+ 接收新连接.
+ 请求长连接的时候就把连接放到连接池中
+ 请求短链接的时候就直接处理。
+ TODO:一眼望去圈复杂度太大,需要重构!
+ """
+ while True:
+ try:
+ sock, _ = server_socket.accept() # 阻塞,等待客户端连接
+ # 加入连接池
+ conn_message = sock.recv(1024)
+ conn_message = strip_byte_end(conn_message)
+ try:
+ message_dic = json.loads(conn_message)
+ if message_dic.get('method') == 'start_long_connection':
+ name = message_dic.get('name')
+ if name is not None:
+ self.handle_long_connection(name, sock)
+ else:
+ response = generate_response_template()
+ response['message'] = 'failed'
+ response['content'] = 'socket name is None'
+ send_dict(sock, response)
+ logger.info('name is None, invalid request content!')
+ else: # 如果请求的不是一个长连接,就直接进行处理,
+
+ response = generate_response_template()
+ response['message'] = 'succeeded'
+ response['content'] = 'connection established'
+ send_dict(sock, response)
+ b = receive(sock)
+
+ if len(b) <= 1000:
+ message = str(b)
+ else:
+ message = str(b[:1000])
+
+ t0 = time.time()
+ self.handle_packet(sock, b)
+ t1 = time.time()
+ logger.info(f"Message handled!\nThe message was:{message}\nTime elapsed {t1 - t0:f} s\n")
+
+ except:
+ import traceback
+ traceback.print_exc()
+ logger.info('failed to decode json:%s' % str(conn_message))
+ response = generate_response_template()
+ response['message'] = 'failed'
+ response['content'] = 'invalid request json:\n%s' % str(conn_message)
+ send_dict(sock, response)
+ except:
+ import traceback
+ traceback.print_exc()
+ break
+
+ def handle_long_connection(self, name: str, sock: socket.socket):
+ """
+ 处理长连接请求
+ :return:
+ """
+ if name not in self.server_obj.long_conn_sockets.keys():
+ self.server_obj.long_conn_sockets[name] = sock
+ response = generate_response_template()
+ response['message'] = 'succeeded'
+ response['content'] = 'socket connected'
+ send_dict(sock, response)
+ else:
+ logger.info('socket named \'%s\' is already connected!' % name)
+ response = generate_response_template()
+ response['message'] = 'failed'
+ response['content'] = 'socket name \'%s\' already connected' % name
+ send_dict(sock, response)
+
+ def handle_packet(self, client: socket.socket, packet: bytes) -> None:
+ """
+ 处理数据包的方法
+ :param client: socket.socket,要求为tcp的端口。
+ :param packet: bytes
+ :return:
+ """
+ try:
+ try:
+ dic = json.loads(packet)
+ func = self.server_obj.dispatcher_dic[dic['method']]
+ args = dic['params']
+ vy = func(*args).encode('utf-8') + b'PMEND'
+ client.sendall(vy)
+ except json.decoder.JSONDecodeError:
+ vy = json.dumps({'message': 'failed'}).encode('utf-8') + b'PMEND'
+ client.sendall(vy)
+
+ except:
+ import traceback
+ traceback.print_exc()
+ client.close()
+
+
+def init_socket(address: Tuple[str, int]):
+ """
+ 初始化套接字
+ """
+ server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ server_socket.bind(address)
+ server_socket.listen(5)
+ logger.info("服务端已启动,等待客户端连接...")
+ return server_socket
+
+
+class PMGServer(QObject):
+ def __init__(self, address: Tuple[str, int], parent=None):
+ super().__init__(parent)
+ self.dispatcher_dic = {}
+ self.long_conn_sockets: Dict[str, socket.socket] = {}
+ self.socket = init_socket(address)
+ self.server_loop_thread = QThread()
+ self.loop_worker = LoopWork(self, self.socket)
+ self.loop_worker.moveToThread(self.server_loop_thread)
+
+ self.server_loop_thread.started.connect(self.loop_worker.work)
+ self.server_loop_thread.start()
+
+ def broadcast_message(self, message_dic: dict = None):
+ """
+ 广播信息
+ message:传递信息
+ # 'DATA_CHANGED'
+ # 'SHUT_DOWN'
+ :param message_dic:要发送的信息,应该是一个可以json序列化的字典。
+ :return:
+ """
+
+ if message_dic is None:
+ message_dic = {'name': 'broadcast', 'message': 'Are you alive?'}
+ ids = []
+ logger.info('broadcast message:' + repr(message_dic))
+ for k in self.long_conn_sockets.keys():
+ try:
+ self.long_conn_sockets[k].sendall(json.dumps(message_dic).encode('utf8') + b'PMEND')
+ except ConnectionResetError:
+ ids.append(k)
+ logger.info('Connection \'%s\' closed!' % k)
+ except:
+ import traceback
+ traceback.print_exc()
+ ids.append(k)
+ logger.info('died connections:' + repr(ids))
+ for not_used_socket_name in ids:
+ sock = self.long_conn_sockets.pop(not_used_socket_name)
+ sock.close()
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ ADDRESS = ('127.0.0.1', 12306) # 绑定地址
+ s = PMGServer(ADDRESS)
+
+ qtimer = QTimer()
+ qtimer.start(2000)
+ qtimer.timeout.connect(lambda: s.broadcast_message(None))
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/utilities/network/util.py b/pyminer/widgets/utilities/network/util.py
new file mode 100644
index 0000000000000000000000000000000000000000..7c9fface370a526d6482c121e26de4a91fc9fa74
--- /dev/null
+++ b/pyminer/widgets/utilities/network/util.py
@@ -0,0 +1,95 @@
+import json
+import time
+import socket
+import logging
+import warnings
+from typing import Dict, Union, List
+
+logger = logging.getLogger(__name__)
+
+
+def strip_byte_end(bytestream):
+ if bytestream.endswith(b'PMEND'):
+ return bytestream[: -5]
+ else:
+ return bytestream
+
+
+def generate_client_payload() -> Dict[str, Union[str, Dict, List]]:
+ """
+ 生成Client的消息模板
+ :return:
+ """
+ payload = {'timestamp': time.time(),
+ 'name': 'Anonymous Client',
+ 'contents': ''
+ }
+ return payload
+
+
+def generate_server_payload() -> Dict[str, Union[str, Dict, List]]:
+ """
+ 生成服务器消息模板
+ :return:
+ """
+ payload = {'message': 'succeeded', # 还有failed
+ 'timestamp': time.time()
+ }
+ return payload
+
+
+def send_dict(sock: socket.socket, dict_to_send: Dict) -> None:
+ """
+ 发送一个字典
+ :param sock:
+ :param dict_to_send:
+ :return:
+ """
+ content = (json.dumps(dict_to_send) + 'PMEND').encode('utf-8')
+ sock.sendall(content)
+
+
+def receive(socket_obj: socket.socket) -> bytes:
+ recv_list = []
+ empty_loops = 0
+ while (1):
+ try:
+ b = socket_obj.recv(1024)
+ if b == b'':
+ empty_loops += 1
+ if empty_loops > 1000:
+ raise ValueError('Too much empty values received!All received was:' + repr(recv_list))
+ else:
+ empty_loops = 0
+ recv_list.append(b)
+ if len(recv_list) >= 2:
+ if (recv_list[-2] + b).endswith(b'PMEND'):
+ recv_list[-2] = recv_list[-2] + recv_list[-1]
+ recv_list.pop()
+ recv_list[-1] = strip_byte_end(recv_list[-1])
+ break
+ else:
+ if b.endswith(b'PMEND'):
+ recv_list[-1] = strip_byte_end(recv_list[-1])
+ break
+
+
+ except ConnectionAbortedError:
+ warnings.warn('Connection terminated.')
+ return b''
+ except:
+ import traceback
+ traceback.print_exc()
+ return b''
+
+ return b''.join(recv_list)
+
+
+def timeit(func):
+ def wrapper(*args, **kwargs):
+ t0 = time.time()
+ r = func(*args, **kwargs)
+ logger.debug('time_elapsed', time.time() - t0)
+ return r
+
+ return wrapper
diff --git a/pyminer/widgets/utilities/platform/__init__.py b/pyminer/widgets/utilities/platform/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..ea56ec5738e0c181ea7e8fa515c442ca71b84fd0
--- /dev/null
+++ b/pyminer/widgets/utilities/platform/__init__.py
@@ -0,0 +1,11 @@
+from .commandutils import *
+from .fileutils import *
+from .filemanager import *
+from .openprocess import *
+from .translation import *
+from .pmdebug import *
+try:
+ from .filesyswatchdog import PMGFileSystemWatchdog
+except ModuleNotFoundError:
+ import warnings
+ warnings.warn("Module \'watchdog\' is not Installed so the class PMGFileSystemWatchdog cannot be imported.")
\ No newline at end of file
diff --git a/pyminer/widgets/utilities/platform/commandutils.py b/pyminer/widgets/utilities/platform/commandutils.py
new file mode 100644
index 0000000000000000000000000000000000000000..1166d5708e02d28e55b6fce39de97ac4cbebc8d9
--- /dev/null
+++ b/pyminer/widgets/utilities/platform/commandutils.py
@@ -0,0 +1,167 @@
+import os
+import platform
+import subprocess
+import sys
+from typing import Union, Optional
+
+
+def check_platform() -> str:
+ """
+ Check platform.
+ returns 'linux', 'windows' or 'osx'
+
+ Returns: platform name in lowercase, a str
+ """
+ system = platform.system()
+ plat = platform.platform(1, 1)
+ return system.lower()
+
+
+def run_command_in_terminal(cmd: str, close_mode: str = 'wait_key') -> None:
+ """
+ Run command in system terminal.
+ TODO: Adapt it to OSX and Linux system!!!
+
+ Args:
+ cmd: application command, which should be platform-crossing.
+ close_mode: What the terminal do when execution finished. There are a few options:['wait_key','auto','no'].
+
+ * 'wait_key' means the terminal closes when user pressed any key after execution.
+ * 'auto' means the terminal closes instantly after execution.
+ * 'no' means the terminal will keep shown until user manually close it.
+
+ Returns:
+
+ """
+ platform_name = check_platform()
+ if platform_name == 'windows':
+ close_action = {'auto': 'start cmd.exe /k \"%s &&exit \"',
+ 'no': 'start cmd.exe /k \"%s \"',
+ 'wait_key': 'start cmd.exe /k \"%s &&pause &&exit \"'
+ }
+ command = close_action[close_mode] % cmd
+ subprocess.Popen(command, shell=True)
+
+
+ elif platform_name == 'linux':
+ ret = os.system('which gnome-terminal')
+
+ if ret == 0:
+ close_action = {'auto': 'deepin-terminal -C \"%s\"',
+ 'no': 'deepin-terminal -C \"%s\" --keep-open',
+ 'wait_key': 'deepin-terminal -C \"%s\" --keep-open'
+ }
+ command = close_action[close_mode] % cmd
+ subprocess.Popen(command, shell=True)
+ else:
+ close_action = {'auto': 'gnome-terminal -x bash -c "%s;"',
+ 'no': 'gnome-terminal -x bash -c "%s; read"',
+ 'wait_key': 'gnome-terminal -x bash -c "%s; read"'
+ }
+ command = close_action[close_mode] % (cmd)
+ subprocess.Popen(command, shell=True)
+
+ else:
+ return
+
+
+def run_command_in_terminal_block(cmd: str, close_mode: str = 'wait_key') -> None:
+ """
+ Run command in a terminal window, and the running event will block current Program.
+ The programm will be continued after closing the command terminal.
+ TODO:Is this function threading-safe?
+
+ Args:
+ cmd: application command, which should be platform-crossing.
+ close_mode: What the terminal do when execution finished. There are a few options:['wait_key','auto','no'].
+
+ * 'wait_key' means the terminal closes when user pressed any key after execution.
+ * 'auto' means the terminal closes instantly after execution.
+ * 'no' means the terminal will keep shown until user manually close it.
+
+ Returns:
+ """
+ platform_name = check_platform()
+ if platform_name == 'windows':
+ close_action = {'auto': 'start cmd.exe /k \"%s &&exit \"',
+ 'no': 'start cmd.exe /k \"%s \"',
+ 'wait_key': 'start cmd.exe /k \"%s &&pause &&exit \"'
+ }
+ command = close_action[close_mode] % cmd
+ f = os.popen(command, 'r', 1)
+
+ elif platform_name == 'linux':
+ ret = os.system('which gnome-terminal')
+
+ if ret == 0:
+ close_action = {'auto': 'deepin-terminal -C \"%s\"',
+ 'no': 'deepin-terminal -C \"%s\" --keep-open',
+ 'wait_key': 'deepin-terminal -C \"%s\" --keep-open'
+ }
+ command = close_action[close_mode] % cmd
+ subprocess.Popen(command, shell=True)
+ else:
+ close_action = {'auto': 'gnome-terminal -x bash -c "%s;"',
+ 'no': 'gnome-terminal -x bash -c "%s; read"',
+ 'wait_key': 'gnome-terminal -x bash -c "%s; read"'
+ }
+ command = close_action[close_mode] % (cmd)
+ subprocess.Popen(command, shell=True)
+
+ else:
+ return
+
+
+def run_python_file_in_terminal(file_path, interpreter_path: str = None, close_mode: str = 'wait_key',
+ blocking=False) -> Optional[str]:
+ """
+ Run Python file in terminal
+ Args:
+ file_path:
+ interpreter_path:
+ close_mode:
+ blocking:
+
+ Returns:
+
+ """
+ if interpreter_path is None:
+ interpreter_path = sys.executable
+ if blocking:
+ return run_command_in_terminal_block('%s %s' % (interpreter_path, file_path), close_mode=close_mode)
+ else:
+ return run_command_in_terminal('%s %s' % (interpreter_path, file_path), close_mode=close_mode)
+
+
+def check_application(app_name):
+ os.system(app_name)
+
+
+def get_parent_path(path: str, storey: int = 1) -> str:
+ """
+ 获取文件或者文件夹的父路径。
+ Args:
+ path: original path(file or folder)
+ storey: If is 1,return parent path;if is 2,return the parent path's parent path
+
+ Returns:
+ """
+ for i in range(storey):
+ path = os.path.dirname(path)
+ return path
+
+
+if __name__ == '__main__':
+ run_python_file_in_terminal('./test/python_file_test.py', blocking=True)
+
+
+ def test_run_in_terminal():
+ import time
+ run_command_in_terminal('dir', close_mode='no')
+ time.sleep(1)
+ run_command_in_terminal('dir', close_mode='wait_key')
+ time.sleep(1)
+ run_command_in_terminal('dir', close_mode='auto')
+
+
+ test_run_in_terminal()
diff --git a/pyminer/widgets/utilities/platform/filemanager.py b/pyminer/widgets/utilities/platform/filemanager.py
new file mode 100644
index 0000000000000000000000000000000000000000..83f9200ff7646209bb75b95bf4252b615b00bad2
--- /dev/null
+++ b/pyminer/widgets/utilities/platform/filemanager.py
@@ -0,0 +1,26 @@
+import subprocess
+import os
+import platform
+
+
+def open_file_manager(path: str):
+ assert os.path.exists(path)
+ path = os.path.normcase(path) # windows的输入命令格式似乎需要注意一下!需要换成斜杠,否则会识别不出来的。
+ if platform.system().lower() == 'windows':
+ subprocess.Popen(['explorer.exe', path], shell=True)
+ elif platform.system().lower() == 'linux':
+ if os.system('which nautilus') == 0:
+ subprocess.Popen(['nautilus', path])
+ return
+ elif os.system('which dde-file-manager') == 0:
+ subprocess.Popen(['dde-file-manager', path])
+ return
+ else:
+ raise NotImplementedError('Cannot Detect system file manager!')
+ else:
+ raise NotImplementedError("This Platform is not supported now!")
+
+
+if __name__ == '__main__':
+ # open_file_manager(r'J:\Developing\pyminer_bin\PyMiner\bin\widgets\utilities\platform')
+ open_file_manager(r'/home/hzy/图片')
diff --git a/pyminer/widgets/utilities/platform/filesyswatchdog.py b/pyminer/widgets/utilities/platform/filesyswatchdog.py
new file mode 100644
index 0000000000000000000000000000000000000000..58835757bdbfd27ec1b8b6f66776c1bcb9465b15
--- /dev/null
+++ b/pyminer/widgets/utilities/platform/filesyswatchdog.py
@@ -0,0 +1,57 @@
+"""
+作者(Author):1295752786@qq.com
+文件系统看门狗-(Watchdog for filesystem)
+来源- (source)
+https://stackoverflow.com/questions/35874217/watchdog-pythons-library-how-to-send-signal-when-a-file-is-modified
+源代码为PyQt4,本人整理、移植到qtpy。(Originally code was in PyQt4 and I transplanted to PySide2.)
+"""
+
+from PySide2.QtCore import Signal, QThread, SignalInstance
+from watchdog.events import FileSystemEventHandler, FileModifiedEvent, FileMovedEvent, \
+ FileCreatedEvent, FileDeletedEvent
+from watchdog.observers import Observer
+
+
+class MyEventHandler(FileSystemEventHandler, QThread):
+ signal_file_modified: SignalInstance = Signal(str)
+ signal_file_created: SignalInstance = Signal(str)
+ signal_file_deleted: SignalInstance = Signal(str)
+ signal_file_moved: SignalInstance = Signal(str, str) # 仅当在本文件夹内才会触发,如果移动到了其他文件夹则触发删除信号
+
+ def on_deleted(self, event: FileDeletedEvent):
+ self.signal_file_deleted.emit(event.src_path)
+
+ def on_modified(self, event: FileModifiedEvent):
+ self.signal_file_modified.emit(event.src_path)
+
+ def on_created(self, event: FileCreatedEvent):
+ self.signal_file_created.emit(event.src_path)
+
+ def on_moved(self, event: FileMovedEvent):
+ self.signal_file_moved.emit(event.src_path, event.dest_path)
+
+
+class PMGFileSystemWatchdog(QThread):
+ """
+ Watch Dog 的一切操作都是在path这个文件夹下完成的,其余位置的文件变更并不会被监测到。
+ 因此,如果将一个文件移动出这个文件夹,触发的并不是“修改”信号,而是触发“删除”信号。
+
+ # TODO 目前仅支持监控一整个文件夹,需要添加监控单一文件的方法
+ """
+
+ def __init__(self, path):
+ super(PMGFileSystemWatchdog, self).__init__()
+
+ self.path = path
+ self.observer = Observer()
+ self.event_handler = MyEventHandler()
+ self.signal_file_modified: SignalInstance = self.event_handler.signal_file_modified
+ self.signal_file_created: SignalInstance = self.event_handler.signal_file_created
+ self.signal_file_deleted: SignalInstance = self.event_handler.signal_file_deleted
+ self.signal_file_moved: SignalInstance = self.event_handler.signal_file_moved
+ self.observer.schedule(self.event_handler, self.path, recursive=True)
+ self.observer.start()
+
+ def stop(self):
+ self.observer.stop()
+ self.deleteLater()
diff --git a/pyminer/widgets/utilities/platform/fileutils.py b/pyminer/widgets/utilities/platform/fileutils.py
new file mode 100644
index 0000000000000000000000000000000000000000..777d70a1d4177d5fbb404b63d8e914508869e32e
--- /dev/null
+++ b/pyminer/widgets/utilities/platform/fileutils.py
@@ -0,0 +1,134 @@
+import os
+
+import chardet
+
+
+def get_parent_path(path, layers=1):
+ """获取父级目录
+
+ Args:
+ path: 当前目录。
+ layers: 向上查找的层级数。
+
+ Returns:
+ 当前目录向上 ``layers`` 级的父目录。
+ """
+ assert isinstance(layers, int) and layers > 0
+ for i in range(layers):
+ path = os.path.dirname(path)
+ return path
+
+
+def load_json(path) -> dict:
+ """
+ 加载json,指定编码方式
+ :param path:
+ :return:
+ """
+ import json
+ with open(path, 'rb') as f:
+ byte_str = f.read()
+ encoding = chardet.detect(byte_str)['encoding']
+ return json.loads(byte_str.decode(encoding))
+
+
+def dump_json(dic: dict, path: str, indent=4) -> None:
+ """
+ 加载json,指定编码方式
+ :param path:
+ :return:
+ """
+ import json
+ with open(path, 'wb') as f:
+ byte_str = json.dumps(dic, indent=indent).encode('utf-8')
+ f.write(byte_str)
+
+
+def create_file_if_not_exist(abso_file_path: str, default_content: bytes = b''):
+ """
+ 在任意可能的路径创建一个文件,倘若文件不存在。
+ :param abso_file_path:
+ :param default_content:
+ :return:
+ """
+ dir_stack = []
+ path = abso_file_path
+ # assert os.path.isfile(abso_file_path), 'Path \'%s\' is not a file!' % abso_file_path
+ if os.path.isfile(abso_file_path):
+ return
+ while 1:
+ parent_path = os.path.dirname(path)
+ if not os.path.exists(parent_path):
+ dir_stack.append(parent_path)
+ path = parent_path
+ else:
+ break
+ dir_stack.reverse()
+ for path in dir_stack:
+ os.mkdir(path)
+ with open(abso_file_path, 'wb') as f:
+ f.write(default_content)
+ pass
+
+
+def move_to_trash(path: str) -> bool:
+ """
+ 将文件移动到回收站。成功返回True,失败返回False
+ :param path:绝对路径。
+ :return:
+ """
+ import platform
+ import send2trash
+ if platform.system() == "Windows":
+ path = path.replace('/', '\\')
+ try:
+ send2trash.send2trash(path)
+ return True
+ except:
+ import traceback
+ traceback.print_exc()
+ return False
+
+
+def rename_file(prev_absolute_path: str, new_absolute_path: str) -> bool:
+ """
+ 重命名文件或者文件夹
+ :param prev_absolute_path:之前的绝对路径名称
+ :param new_absolute_path: 之后的绝对路径名称
+ :return:
+ """
+ import os
+ try:
+ os.rename(prev_absolute_path, new_absolute_path)
+ return True
+ except:
+ import traceback
+ traceback.print_exc()
+ return False
+
+
+def copy_paste(source_path: str, target_path: str):
+ """
+
+ :param source_path: 源文件或文件夹
+ :param target_path: 目标文件或文件夹
+ :return:
+ """
+ import shutil, os
+ if os.path.isfile(source_path):
+ copy_func = shutil.copyfile
+ else:
+ copy_func = shutil.copytree
+
+ try:
+ copy_func(source_path, target_path)
+ except:
+ import traceback
+ traceback.print_exc()
+ return False
+ return True
+
+
+if __name__ == '__main__':
+ print(get_parent_path(os.path.dirname(__file__), 2))
+ print(load_json(r'/pyminer2/extensions/packages/code_editor/customized/settings.json'))
diff --git a/pyminer/widgets/utilities/platform/openprocess.py b/pyminer/widgets/utilities/platform/openprocess.py
new file mode 100644
index 0000000000000000000000000000000000000000..abb634e9f52839071af42f727ed1dd912a280f2f
--- /dev/null
+++ b/pyminer/widgets/utilities/platform/openprocess.py
@@ -0,0 +1,51 @@
+import queue
+import subprocess
+import threading
+import time
+import chardet
+from typing import List
+
+import packages.code_editor.utils.utils
+
+
+class PMGProcess():
+ def __init__(self, args: List[str]):
+ self.terminate = False
+ self.q = queue.Queue()
+ self.on_command_received = lambda cmd: None
+ self.on_error_received = lambda error: None
+ self.args = args
+ self.process = subprocess.Popen(self.args,
+ stdin=subprocess.PIPE,
+ shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ self.to = threading.Thread(
+ target=self.enqueue_stream, args=(
+ self.process.stdout, self.q, 1))
+ self.te = threading.Thread(
+ target=self.enqueue_stream, args=(
+ self.process.stderr, self.q, 2))
+ self.tp = threading.Thread(target=self.console_loop)
+ self.to.setDaemon(True)
+ self.te.setDaemon(True)
+ self.tp.setDaemon(True)
+ self.te.start()
+ self.to.start()
+ self.tp.start()
+
+ def enqueue_stream(self, stream, queue, type): # 将stderr或者stdout写入到队列q中。
+ for line in iter(stream.readline, b''):
+ if self.terminate:
+ break
+ encoding = chardet.detect(line)['encoding']
+ queue.put(str(type) + packages.code_editor.utils.utils.decode(encoding))
+ stream.close()
+
+ def console_loop(self): # 封装后的内容。
+ pass
+
+
+if __name__ == '__main__':
+ pmp = PMGProcess(['python', '-u', '-c', 'print(hello world!)'])
+ while (1):
+ time.sleep(2)
+ pass
diff --git a/pyminer/widgets/utilities/platform/pmdebug.py b/pyminer/widgets/utilities/platform/pmdebug.py
new file mode 100644
index 0000000000000000000000000000000000000000..463ce7bba43b345b51b31c05e4e32b16884a404b
--- /dev/null
+++ b/pyminer/widgets/utilities/platform/pmdebug.py
@@ -0,0 +1,37 @@
+import sys
+import pickle
+
+sys.path.append(r'/')
+
+
+def test1():
+ print(123)
+
+
+def test():
+ test1()
+ print(123456)
+
+
+def insight(name, var):
+ from pyminer_comm import set_var
+
+ if name not in ['globals', 'os'] and (not name.startswith('__')):
+ try:
+ print('set_var')
+ pickle.dumps(var)
+ set_var(name, var, 'debugger')
+ print('set_var')
+ except:
+ d = {}
+ import traceback
+ traceback.print_exc()
+ for k in dir(var):
+ try:
+ attr = getattr(var, k)
+ pickle.dumps(attr)
+ d[k] = attr
+ except:
+ d[k] = str(getattr(var, k))
+ print(name, '=', d)
+ set_var(name, d, 'debugger')
diff --git a/pyminer/widgets/utilities/platform/test/__init__.py b/pyminer/widgets/utilities/platform/test/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/widgets/utilities/platform/test/python_file_test.py b/pyminer/widgets/utilities/platform/test/python_file_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..fa461a65b9a7acff2e885659f911f82ab506ef89
--- /dev/null
+++ b/pyminer/widgets/utilities/platform/test/python_file_test.py
@@ -0,0 +1,10 @@
+import numpy as np
+import time
+
+i = 0
+while (1):
+ time.sleep(1)
+ print('this is a test python file!')
+ i += 1
+ if i > 10:
+ break
diff --git a/pyminer/widgets/utilities/platform/translation.py b/pyminer/widgets/utilities/platform/translation.py
new file mode 100644
index 0000000000000000000000000000000000000000..22690753db5a38cbe3543e36847b58e2c9ae30cd
--- /dev/null
+++ b/pyminer/widgets/utilities/platform/translation.py
@@ -0,0 +1,12 @@
+def add_translation_file(file_path: str):
+ from PySide2.QtWidgets import QApplication
+ from PySide2.QtCore import QTranslator
+ app = QApplication.instance()
+ if hasattr(app, 'trans'):
+ try:
+ tr = QTranslator()
+ path = file_path
+ tr.load(path)
+ app.installTranslator(tr)
+ except:
+ pass
diff --git a/pyminer/widgets/utilities/source/__init__.py b/pyminer/widgets/utilities/source/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..b6df99fe7afc272901b60400a6774a4e3bee655a
--- /dev/null
+++ b/pyminer/widgets/utilities/source/__init__.py
@@ -0,0 +1,3 @@
+from .iconutils import create_icon
+from .colorutils import color_tup2str, color_str2tup
+from .graphicsitemutils import PMGPlotCustomizer
diff --git a/pyminer/widgets/utilities/source/colorutils.py b/pyminer/widgets/utilities/source/colorutils.py
new file mode 100644
index 0000000000000000000000000000000000000000..344f820aff372f9abf43e03f5cc3ed2e0675a2c7
--- /dev/null
+++ b/pyminer/widgets/utilities/source/colorutils.py
@@ -0,0 +1,42 @@
+def convert(c):
+ v = ord(c)
+ if (48 <= v <= 57):
+ return v - 48
+ else:
+ return v - 87 # 返回a的值。
+
+
+def color_str2tup(value: str) -> tuple:
+ """
+ pos或者wh的输入都是tuple
+ """
+ value = value.lower()
+ c0 = convert(value[1])
+ c1 = convert(value[2])
+ c2 = convert(value[3])
+ c3 = convert(value[4])
+ c4 = convert(value[5])
+ c5 = convert(value[6])
+ a1 = c0 * 16 + c1
+ a2 = c2 * 16 + c3
+ a3 = c4 * 16 + c5
+ return (a1, a2, a3)
+
+
+def color_tup2str(value: tuple) -> str:
+ """
+ 问题修正
+ :param value:
+ :return:
+ """
+ if value is None:
+ return None
+ strcolor = '#'
+ for i in value:
+ strcolor += hex(int(i))[-2:].replace('x', '0')
+ return strcolor
+
+
+if __name__ == '__main__':
+ print(color_tup2str((0, 222, 155)))
+ print(color_str2tup('#00aaff'))
diff --git a/pyminer/widgets/utilities/source/graphicsitemutils.py b/pyminer/widgets/utilities/source/graphicsitemutils.py
new file mode 100644
index 0000000000000000000000000000000000000000..717e99b49de68b8a902d6d1fbef5adabe9e6f7c5
--- /dev/null
+++ b/pyminer/widgets/utilities/source/graphicsitemutils.py
@@ -0,0 +1,93 @@
+from typing import Union
+
+
+class PMGPlotCustomizer():
+ def __init__(self):
+ self._line_color = '#000000'
+ self._line_style = '--'
+ self._line_width = 2
+ self._border_color = '#000000'
+ self._border_width = 2
+ self._symbol = 't'
+ self._item_color = '#000000'
+ self._face_color = '#ffffff'
+ self._legend_face_color = '#ffffff'
+ self._symbols_dic = {}
+
+ @property
+ def border_color(self):
+ return self._border_color
+
+ @border_color.setter
+ def border_color(self, color: str):
+ self._border_color = color
+
+ @property
+ def border_width(self):
+ return self._border_width
+
+ @border_width.setter
+ def border_width(self, border_width: Union[int, float]):
+ self._border_width = border_width
+
+ @property
+ def face_color(self):
+ return self._face_color
+
+ @face_color.setter
+ def face_color(self, color: str):
+ self._face_color = color
+
+ @property
+ def item_color(self):
+ return self._item_color
+
+ @item_color.setter
+ def item_color(self, color: str):
+ self._item_color = color
+
+ @property
+ def line_color(self):
+ return self._line_color
+
+ @line_color.setter
+ def line_color(self, color: str):
+ self._line_color = color
+
+ @property
+ def line_style(self):
+ return self._line_style
+
+ @line_style.setter
+ def line_style(self, style: str):
+ self._line_style = style
+
+ @property
+ def line_width(self):
+ return self._line_width
+
+ @line_width.setter
+ def line_width(self, width: Union[int, float]):
+ assert isinstance(width, (int, float))
+ self._line_width = width
+
+ @property
+ def symbol(self):
+ return self._symbol
+
+ @symbol.setter
+ def symbol(self, symbol: str):
+ if symbol in self._symbols_dic.keys():
+ self._symbol = self._symbols_dic[symbol]
+ else:
+ raise ValueError(
+ 'Symbol \'%s\' is not allowed.\nAll allowed symbols are:%s' % (symbol,
+ list(self._symbols_dic.keys())))
+
+ @property
+ def legend_face_color(self):
+ return self._legend_face_color
+
+ @legend_face_color.setter
+ def legend_face_color(self, color):
+ self._legend_face_color = color
diff --git a/pyminer/widgets/utilities/source/iconutils.py b/pyminer/widgets/utilities/source/iconutils.py
new file mode 100644
index 0000000000000000000000000000000000000000..226acb2b3c8f4bf69c4b87d1a2bb3d33496d1d7d
--- /dev/null
+++ b/pyminer/widgets/utilities/source/iconutils.py
@@ -0,0 +1,16 @@
+from typing import Tuple
+
+from PySide2.QtGui import QIcon, QPixmap
+
+
+def create_icon(icon_path: str = ":/pyqt/source/images/New.png"):
+ icon = QIcon()
+ icon.addPixmap(QPixmap(icon_path), QIcon.Normal, QIcon.Off)
+ return icon
+
+
+def color_rgb_to_str(value: Tuple[int, ...]) -> str:
+ result = '#'
+ for i in value:
+ result += hex(int(i))[-2:].replace('x', '0')
+ return result
diff --git a/pyminer/widgets/utilities/source/translation.py b/pyminer/widgets/utilities/source/translation.py
new file mode 100644
index 0000000000000000000000000000000000000000..3b93cd20a434202c35f4a61d3017770452a5fc65
--- /dev/null
+++ b/pyminer/widgets/utilities/source/translation.py
@@ -0,0 +1,22 @@
+import os
+from PySide2.QtWidgets import QApplication
+from PySide2.QtCore import QLocale, QTranslator
+
+def create_translation(target_files: str):
+ import os
+ folder = os.path.dirname(target_files)
+ name = os.path.basename(target_files)
+ name_without_ext = os.path.splitext(name)
+ names = ''
+ for file_path in target_files:
+ name = os.path.basename(file_path)
+ names += name + ' '
+ os.system('cd %s && pylupdate5 -noobsolete %s -ts translations/%s.ts' % (folder, names, name_without_ext))
+
+def create_translator(path:str):
+
+ inner_app = QApplication.instance()
+ translator = QTranslator()
+ translator.load(path)
+ inner_app.installTranslator(translator)
+ return translator
\ No newline at end of file
diff --git a/pyminer/widgets/utilities/uilogics/__init__.py b/pyminer/widgets/utilities/uilogics/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..c3200698619c06159441702c3a05773c16c11bdc
--- /dev/null
+++ b/pyminer/widgets/utilities/uilogics/__init__.py
@@ -0,0 +1,5 @@
+from .tasks import *
+from .undomanager import *
+from .windowutils import *
+from .codechecking import *
+from .uidisplay import *
\ No newline at end of file
diff --git a/pyminer/widgets/utilities/uilogics/codechecking.py b/pyminer/widgets/utilities/uilogics/codechecking.py
new file mode 100644
index 0000000000000000000000000000000000000000..78248d2e55ce2ab63ba2e6a5db06edc43795aece
--- /dev/null
+++ b/pyminer/widgets/utilities/uilogics/codechecking.py
@@ -0,0 +1,72 @@
+from typing import Tuple, Union, Any, Type, Container
+
+ERROR_INVALID_TYPE = 'Parameter or Variable'
+TYPE_RANGE = Tuple[Union[int, float], Union[int, float]]
+
+
+def iter_isinstance(iter, elem_cls) -> bool:
+ """
+ 检查可迭代变量是否为iter_type指定的类型。
+ :param iter:
+ :param elem_cls:
+ :return:
+ """
+ for item in iter:
+ if not isinstance(item, elem_cls):
+ return False
+ return True
+
+
+def assert_in(elem: Any, container: Container):
+ """
+ 判断元素elem是否在container中。
+ Args:
+ elem:
+ container:
+
+ Returns:
+
+ """
+ assert elem in container, 'Element {0} not in Container {1}'.format(elem, container)
+
+
+def assert_not_in(elem, container: Container):
+ """
+ 判断元素elem是否在container中。
+ Args:
+ elem:
+ container:
+
+ Returns:
+
+ """
+ assert elem not in container, 'Element {0} in Container {1}'.format(elem, container)
+
+
+class ErrorReporter():
+ @staticmethod
+ def create_invalid_parameter_value_message(param_name: str, param_value: Any):
+ return 'Parameter \'%s\' value %s is not allowed.' % (param_name, str(param_value))
+
+ @staticmethod
+ def create_invalid_parameter_type_message(param_name, param_type, expected_type) -> Any:
+ return 'Parameter \'%s\' type %s is not allowed.Expected type is %s' % (
+ param_name, str(param_type), str(expected_type))
+
+ @staticmethod
+ def create_invalid_variable_value_message(variable_name, variable_value):
+ return 'Parameter \'%s\' value %s is not allowed.' % (variable_name, variable_value)
+
+ @staticmethod
+ def create_file_not_found_error(file: str):
+ if file != '':
+ return FileNotFoundError('File \'%s\' not found!' % file)
+ else:
+ return FileNotFoundError('File name is empty.')
+
+ @staticmethod
+ def create_type_error(name: str, value: Any, type_expected: Union[Type[Any], str]):
+ typ = type(value)
+ return TypeError(
+ 'Variable \'%s\' is expected to be an instance of %s, but actually it was an instance of %s' % (
+ name, str(type_expected), str(typ)))
diff --git a/pyminer/widgets/utilities/uilogics/drags.py b/pyminer/widgets/utilities/uilogics/drags.py
new file mode 100644
index 0000000000000000000000000000000000000000..26344070d4a9a5e52325a4261685c3aa73057a03
--- /dev/null
+++ b/pyminer/widgets/utilities/uilogics/drags.py
@@ -0,0 +1,39 @@
+import sys
+
+from PySide2.QtCore import QUrl
+from PySide2.QtGui import QDropEvent
+from PySide2.QtWidgets import QApplication, QTextBrowser
+
+
+class Demo(QTextBrowser): # 1
+ def __init__(self):
+ super(Demo, self).__init__()
+ self.setAcceptDrops(True) # 2
+
+ def dragEnterEvent(self, QDragEnterEvent): # 3
+ print('Drag Enter')
+ if QDragEnterEvent.mimeData().hasText():
+ QDragEnterEvent.acceptProposedAction()
+ print()
+
+ def dragMoveEvent(self, QDragMoveEvent): # 4
+ # print('Drag Move')
+ pass
+
+ def dragLeaveEvent(self, QDragLeaveEvent): # 5
+ # print('Drag Leave')
+ pass
+
+ def dropEvent(self, drop_event: QDropEvent): # 6
+ print('Drag Drop')
+ url: QUrl = None
+ urls = drop_event.mimeData().urls()
+ for url in urls:
+ print(url.toLocalFile())
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ demo = Demo()
+ demo.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/utilities/uilogics/tasks/__init__.py b/pyminer/widgets/utilities/uilogics/tasks/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..818d608b38a6b490486f8990d0b6e4d35ee18e4a
--- /dev/null
+++ b/pyminer/widgets/utilities/uilogics/tasks/__init__.py
@@ -0,0 +1,4 @@
+from .threads import *
+from .minimal_thread import PMGQThreadManager
+from .loop_background import *
+from .one_shot_background import *
\ No newline at end of file
diff --git a/pyminer/widgets/utilities/uilogics/tasks/loop_background.py b/pyminer/widgets/utilities/uilogics/tasks/loop_background.py
new file mode 100644
index 0000000000000000000000000000000000000000..cdaab7d88d239ee91f1017aa70807be5850ce85b
--- /dev/null
+++ b/pyminer/widgets/utilities/uilogics/tasks/loop_background.py
@@ -0,0 +1,168 @@
+# coding=utf-8
+__author__ = '侯展意'
+
+import logging
+import sys
+import time
+from typing import Callable, Tuple, Iterable
+# from collections import Iterable
+from PySide2.QtGui import QCloseEvent
+from PySide2.QtWidgets import QApplication
+from PySide2.QtCore import QObject, Signal
+
+logger = logging.getLogger(__name__)
+from widgets.utilities.uilogics.tasks.minimal_thread import PMGQThreadManager
+
+
+class PMGEndlessLoopWorker(QObject):
+ """
+ 在循环中工作。
+ 输入参数:单步工作函数;参数(可迭代对象)。
+ """
+ signal_step_finished = Signal(object)
+ signal_finished = Signal()
+
+ def __init__(self, work_fcn: Callable, args: Iterable = None):
+ super(PMGEndlessLoopWorker, self).__init__()
+ self.quit = False
+ self.args = args if args is not None else []
+ self.work_fcn: Callable = work_fcn
+
+ def work(self):
+ assert callable(self.work_fcn)
+
+ while (1):
+ step_ret = self.work_fcn(*self.args)
+ self.signal_step_finished.emit(step_ret)
+ if self.quit:
+ break
+ self.signal_finished.emit()
+
+ def on_exit(self):
+ self.quit = True
+
+
+class PMGLoopWorker(QObject):
+ signal_step_finished = Signal(int, object)
+ signal_finished = Signal()
+
+ def __init__(self, work_fcn: Callable, iter_args: Iterable = None, loop_times: int = 100,
+ step_args: Iterable = None):
+ """
+ 在循环中工作。
+ 第一种方法可以传入可迭代对象作为 iter_args 参数,循环次数就是 iter_args 的长度。此种情况下loop_times和step_args无效。
+ 第二种方法,当iter_args为None的时候,传入循环次数loop_times作为参数,每一步分别的参数由step_args传入。
+ 输入参数:单步工作函数;参数(可迭代对象)。
+ 注意:传入的callback函数不得在其中直接刷新UI(比如,调用文本框的setText方法),否则可能出现段错误。段错误会立即导致界面崩溃,且不可使用try...catch...或者cgitb等手段进行处理!
+ Args:
+ work_fcn:
+ iter_args:
+ loop_times:
+ step_args:
+
+ """
+ super(PMGLoopWorker, self).__init__()
+ self.quit = False
+
+ self.iter_args = iter_args if iter_args is not None else [([] if step_args is None else step_args) for i in
+ range(loop_times)]
+ self.work_fcn: Callable = work_fcn
+
+ def work(self):
+ assert callable(self.work_fcn)
+ assert self.iter_args is not None
+ for i, step_args in enumerate(self.iter_args):
+ step_ret = self.work_fcn(*step_args)
+ self.signal_step_finished.emit(i, step_ret)
+ self.signal_finished.emit()
+
+ def on_exit(self):
+ self.quit = True
+
+
+class PMGLoopThreadRunner(QObject):
+ signal_finished = Signal()
+ signal_step_finished = Signal(int, object)
+
+ def __init__(self, callback: Callable, iter_args: Iterable = None, loop_times: int = 100,
+ step_args: Iterable = None):
+ """
+ 连续执行有限多次回调函数callback。函数每执行一次,会发出含两个参数(参数依次为“当前步数”和“callback的返回值”)的信号`signal_step_finished`。
+ 第一种方法可以传入可迭代对象作为`iter_args`参数,循环次数就是 `iter_args` 的长度。此种情况下`loop_times`和`step_args`无效。
+ 第二种方法,当`iter_args`为`None`的时候,传入循环次数`loop_times`作为参数,此时每步的参数是相同的,均有参数`step_args`传入。
+
+ 如果函数有多个返回值,则信号`signal_step_finished`的参数为:<整数,元组>。第二个参数是一个元组,依次包含callback各个返回参数。
+ Args:
+ callback: 回调函数。函数每执行一次,信号`signal_step_finished`将发出。
+ iter_args:循环每一步需要输入的参数
+ loop_times:循环次数
+ step_args:所有步相同的参数
+ 注意:传入的callback函数不得在其中直接刷新UI(比如,调用文本框的setText方法),否则可能出现段错误。
+ 段错误会立即导致界面崩溃,且无法使用try...catch...或者cgitb等手段进行处理!
+
+ """
+ super().__init__()
+ self.worker = PMGLoopWorker(callback, iter_args, loop_times, step_args)
+ self.thread_mgr = PMGQThreadManager(worker=self.worker)
+ self.worker.signal_step_finished.connect(self.signal_step_finished.emit)
+ self.worker.signal_finished.connect(self.signal_finished.emit)
+
+
+class PMGEndlessLoopThreadRunner(QObject):
+ signal_finished = Signal()
+ signal_step_finished = Signal(object)
+
+ def __init__(self, callback: Callable, args: Iterable = None):
+ """
+ 连续执行无限多次回调函数callback。函数每执行一次,将发出参数为callback返回值的信号`signal_step_finished`。
+ 如果函数有多个返回值,则信号`signal_step_finished`的参数为一个元组,依次包含各个返回参数。
+ Args:
+ callback: 回调函数。函数每执行一次,信号`signal_step_finished`将发出,信号只有一个参数,也就是我们输入的callback的返回值。
+ args:
+ 注意:传入的callback函数不得在其中直接刷新UI(比如,调用文本框的setText方法),否则可能出现段错误。
+ 段错误会立即导致界面崩溃,且无法使用try...catch...或者cgitb等手段进行处理!
+
+ """
+ super().__init__()
+ self.worker = PMGEndlessLoopWorker(callback, args=args)
+ self.thread_mgr = PMGQThreadManager(worker=self.worker)
+ self.worker.signal_step_finished.connect(self.signal_step_finished.emit)
+ self.worker.signal_finished.connect(self.signal_finished.emit)
+
+ def is_running(self) -> bool:
+ return self.worker.thread().isRunning()
+
+ def shut_down(self):
+ self.thread_mgr.shut_down()
+
+
+if __name__ == '__main__':
+ from PySide2.QtWidgets import QTextEdit
+
+
+ def run(i, j):
+ time.sleep(0.1)
+ return i + j
+
+
+ class TextEdit(QTextEdit):
+ def __init__(self):
+ super(TextEdit, self).__init__()
+ self.oneshot = PMGLoopThreadRunner(run, iter_args=[(i, i + 1) for i in range(100)])
+ self.oneshot.signal_step_finished.connect(self.on_step_finished)
+ self.oneshot.signal_finished.connect(self.on_finished)
+
+ def on_step_finished(self, step, result):
+ self.append('step:%d,result:%s\n' % (step, repr(result)))
+
+ def on_finished(self):
+ self.append('finished!')
+
+ def closeEvent(self, a0: 'QCloseEvent') -> None:
+ super().closeEvent(a0)
+
+
+ app = QApplication(sys.argv)
+ textedit = TextEdit()
+ textedit.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/utilities/uilogics/tasks/minimal_thread.py b/pyminer/widgets/utilities/uilogics/tasks/minimal_thread.py
new file mode 100644
index 0000000000000000000000000000000000000000..6a3b18e0ec492da43d8ae8418eac2b67e53e2fc5
--- /dev/null
+++ b/pyminer/widgets/utilities/uilogics/tasks/minimal_thread.py
@@ -0,0 +1,127 @@
+# coding=utf-8
+__author__ = '侯展意'
+
+import logging
+import sys
+import time
+
+from PySide2.QtGui import QCloseEvent, QKeyEvent
+from PySide2.QtWidgets import QApplication
+from PySide2.QtCore import QObject, QThread, Signal, QMutex, QWaitCondition
+
+logger = logging.getLogger(__name__)
+
+
+class PMGQThreadManager(QObject):
+ signal_server_message_received = Signal(dict)
+ signal_data_changed = Signal(str)
+ signal_finished = Signal()
+
+ def __init__(self, parent=None, worker: QObject = None):
+ super().__init__(parent)
+ self.thread_recv = QThread()
+ self.worker_recv = worker
+ self.worker_recv.moveToThread(self.thread_recv)
+ self.thread_recv.started.connect(self.worker_recv.work)
+ self.thread_recv.start()
+ self.thread_recv.finished.connect(self.signal_finished.emit)
+
+ def shut_down(self):
+ """
+ 关闭线程,并且退出。
+ :return:
+ """
+ self.worker_recv.on_exit()
+ self.thread_recv.quit()
+ self.thread_recv.wait(500)
+
+
+if __name__ == '__main__':
+ from PySide2.QtWidgets import QTextEdit
+
+ i = 0
+ j = 0
+
+
+ def work(p):
+ global i, j
+ j += 1
+ mutex = QMutex()
+ print('start')
+ while (1):
+ mutex.lock()
+ # print(self.thread())
+ # self.wait_condition.wait(self.mutex)
+ # for j in range(3):
+ # print(j)
+ i += 1
+ p.signal_print.emit(repr(('thread:%d' % p.id, i)))
+ # print('thread:%d' % p.id, i, '\n')
+ p.thread().msleep(10)
+
+ # mutex.unlock()
+ if i>100:
+ break
+ # while(1):
+
+
+
+ class PMGThreadWorker(QObject):
+ """
+ 利用mutex加锁
+
+ """
+ signal_print = Signal(str)
+
+ def __init__(self, worker_id: int):
+ super(PMGThreadWorker, self).__init__()
+ self.quit = False
+ self.id = worker_id
+ self.mutex = QMutex()
+ self.wait_condition = QWaitCondition()
+
+ def work(self):
+ i = 0
+ while (1):
+ self.mutex.lock()
+ print(self.thread())
+ self.wait_condition.wait(self.mutex)
+ for i in range(3):
+ self.signal_print.emit(str(self.id) + ',' + str(i + 1))
+ i += 1
+ if self.quit:
+ break
+ self.mutex.unlock()
+
+ def on_exit(self):
+ self.quit = True
+ self.wait_condition.wakeAll()
+
+ def wake_all(self):
+ self.wait_condition.wakeAll()
+
+
+ class TextEdit(QTextEdit):
+ def __init__(self):
+ super(TextEdit, self).__init__()
+ self.worker = PMGThreadWorker(1)
+ self.thread_mgr = PMGQThreadManager(worker=self.worker)
+ self.worker.signal_print.connect(self.append)
+
+ self.worker2 = PMGThreadWorker(2)
+ self.thread_mgr2 = PMGQThreadManager(worker=self.worker2)
+ self.worker2.signal_print.connect(self.append)
+
+ def keyPressEvent(self, event: 'QKeyEvent') -> None:
+ super().keyPressEvent(event)
+ self.worker.wake_all()
+
+ def closeEvent(self, a0: 'QCloseEvent') -> None:
+ super().closeEvent(a0)
+ self.thread_mgr.shut_down()
+
+
+ app = QApplication(sys.argv)
+ textedit = TextEdit()
+ textedit.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/utilities/uilogics/tasks/one_shot_background.py b/pyminer/widgets/utilities/uilogics/tasks/one_shot_background.py
new file mode 100644
index 0000000000000000000000000000000000000000..79150e6999c5e3da70c9df42ad0a882f2e0b252a
--- /dev/null
+++ b/pyminer/widgets/utilities/uilogics/tasks/one_shot_background.py
@@ -0,0 +1,103 @@
+# coding=utf-8
+__author__ = '侯展意'
+
+import logging
+import sys
+import time
+from typing import Callable, Tuple
+
+from PySide2.QtGui import QCloseEvent
+from PySide2.QtWidgets import QApplication
+from PySide2.QtCore import QObject, Signal
+
+logger = logging.getLogger(__name__)
+from widgets.utilities.uilogics.tasks.minimal_thread import PMGQThreadManager
+
+
+class PMGOneShotWorker(QObject):
+ signal_finished = Signal(object)
+
+ def __init__(self, work_fcn: Callable, args: Tuple = None):
+ super(PMGOneShotWorker, self).__init__()
+ self.quit = False
+ self.args = args
+ self.work_fcn: Callable = work_fcn
+
+ def work(self):
+ assert callable(self.work_fcn)
+ if self.args is not None:
+ assert isinstance(self.args, (tuple, list))
+ ret = self.work_fcn(*self.args)
+ else:
+ ret = self.work_fcn()
+ self.signal_finished.emit(ret)
+
+ def on_exit(self):
+ pass
+
+
+class PMGOneShotThreadRunner(QObject):
+
+ signal_finished = Signal(object)
+
+ def __init__(self, callback: Callable, args=None):
+ """
+ Attension: Do not do UI operations in the callback.If callback contains operations for refresh UI, this method might
+ cause segmentation fault.
+
+ Args:
+ callback: 传入函数对象
+ args: 传入函数的参数,默认值为None。应当以元组形式依次传入。如果为None则不对函数传入参数。
+ """
+
+ super().__init__()
+ self.worker = PMGOneShotWorker(callback, args)
+ self.thread_mgr = PMGQThreadManager(worker=self.worker)
+ self.worker.signal_finished.connect(self.slot_signal_finished)
+
+ def slot_signal_finished(self, obj):
+ """
+ When the single shot finished,this slot method will instantly be called.
+
+
+ Args:
+ obj:
+
+ Returns:
+
+ """
+ self.signal_finished.emit(obj)
+ self.worker.thread().quit()
+ self.worker.thread().wait(500)
+
+ def is_running(self):
+ return self.worker.thread().isRunning()
+
+if __name__ == '__main__':
+ from PySide2.QtWidgets import QTextEdit
+
+
+ def run(loop_times):
+ for i in range(loop_times):
+ print(i)
+ time.sleep(1)
+ return 'finished!!', 'aaaaaa', ['finished', 123]
+
+
+ class TextEdit(QTextEdit):
+ def __init__(self):
+ super(TextEdit, self).__init__()
+ self.oneshot = PMGOneShotThreadRunner(run, args=(5,))
+ self.oneshot.signal_finished.connect(self.on_finished)
+
+ def on_finished(self, obj):
+ self.append(repr(obj))
+
+ def closeEvent(self, a0: 'QCloseEvent') -> None:
+ super().closeEvent(a0)
+
+
+ app = QApplication(sys.argv)
+ textedit = TextEdit()
+ textedit.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/utilities/uilogics/tasks/threads.py b/pyminer/widgets/utilities/uilogics/tasks/threads.py
new file mode 100644
index 0000000000000000000000000000000000000000..d9fedfacaf25ea45558ce5b0f0127ec23243f5b0
--- /dev/null
+++ b/pyminer/widgets/utilities/uilogics/tasks/threads.py
@@ -0,0 +1,108 @@
+# coding=utf-8
+__author__ = '侯展意'
+
+import logging
+import sys
+import time
+from typing import Callable
+
+logger = logging.getLogger(__name__)
+
+from PySide2.QtWidgets import QApplication
+from PySide2.QtCore import QObject, QThread, Signal
+
+
+class PMGThreadWorker(QObject):
+ def __init__(self):
+ super(PMGThreadWorker, self).__init__()
+ self.quit = False
+ self.work_loop_fcn = None
+ self.on_exit_fcn = None
+
+ def work(self):
+ while (1):
+ self.work_loop_fcn()
+ if self.quit:
+ break
+ # self.thread().quit()
+
+ def on_exit(self):
+ if callable(self.on_exit_fcn):
+ self.on_exit_fcn()
+ self.quit = True
+
+
+class PMQThreadObject(QObject):
+ signal_server_message_received = Signal(dict)
+ signal_data_changed = Signal(str)
+
+ def __init__(self, parent=None, worker: QObject = None):
+ super().__init__(parent)
+ self.thread = QThread()
+ self.worker = worker
+ self.worker.moveToThread(self.thread)
+ self.thread.started.connect(self.worker.work) # self.worker_recv.work)
+ self.thread.finished.connect(self.worker.deleteLater)
+ self.thread.finished.connect(self.thread.deleteLater)
+ self.thread.start()
+
+ def terminate(self):
+ logger.info('client quit')
+ self.worker.on_exit()
+
+ self.thread.quit()
+ self.thread.wait(500)
+
+
+class PMQThreadManager(QObject):
+ signal_server_message_received = Signal(dict)
+ signal_data_changed = Signal(str)
+
+ def __init__(self, parent=None, work_fcn: Callable = None, loop_fcn: Callable = None, exit_fcn: Callable = None):
+ super().__init__(parent)
+ self.thread_recv = QThread()
+ self.worker_recv = PMGThreadWorker()
+
+ if work_fcn is not None and loop_fcn is not None:
+ raise ValueError('work_fcn and loop_fcn cannot be both not None at the same time!')
+ if work_fcn is not None:
+ self.worker_recv.work = work_fcn
+ else:
+ self.worker_recv.work_loop_fcn = loop_fcn
+ self.worker_recv.moveToThread(self.thread_recv)
+ self.thread_recv.started.connect(self.worker_recv.work) # self.worker_recv.work)
+ self.worker_recv.on_exit_fcn = exit_fcn
+ self.thread_recv.start()
+
+ def shut_down(self):
+ logger.info('client quit')
+ self.worker_recv.on_exit()
+ # self.worker_recv.close_socket()
+ self.thread_recv.quit()
+ self.thread_recv.wait(500)
+
+
+if __name__ == '__main__':
+ from PySide2.QtWidgets import QTextEdit
+
+
+ def loop():
+ print(123)
+ time.sleep(0.4)
+
+
+ def exit():
+ print('thread exits!')
+
+ class TextEdit(QTextEdit):
+ def __init__(self):
+ super(TextEdit, self).__init__()
+ self.thread_mgr = PMQThreadManager()
+
+ app = QApplication(sys.argv)
+ textedit = QTextEdit()
+ textedit.show()
+ c = PMQThreadManager(loop_fcn=loop, exit_fcn=exit)
+ time.sleep(2)
+ c.shut_down()
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/utilities/uilogics/uidisplay.py b/pyminer/widgets/utilities/uilogics/uidisplay.py
new file mode 100644
index 0000000000000000000000000000000000000000..32fdcf4b45f5bc3bef3fcb0b39fd36c55642b851
--- /dev/null
+++ b/pyminer/widgets/utilities/uilogics/uidisplay.py
@@ -0,0 +1,27 @@
+# -*- coding:utf-8 -*-
+# @Time: 2021/3/29 10:55
+# @Author: Zhanyi Hou
+# @Email: 1295752786@qq.com
+# @File: uidisplay.py
+from PySide2.QtCore import QObject, QCoreApplication
+
+
+class TextDisplayUtil(QObject):
+ @staticmethod
+ def get_display_type(obj: object):
+ """
+ 获取显示的类型
+ :param obj:
+ :return:
+ """
+ _translate = QCoreApplication.translate
+ if type(obj) == type(True):
+ return _translate("TextDisplayUtil", "bool")
+ elif type(obj) == type(0):
+ return _translate("TextDisplayUtil", "integer")
+ elif type(obj) == type(""):
+ return _translate("TextDisplayUtil", "integer")
+ elif type(obj) == type(1.0):
+ return _translate("TextDisplayUtil", "float")
+ else:
+ raise TypeError("Unrecognized type for var %s" % (obj,))
diff --git a/pyminer/widgets/utilities/uilogics/undomanager.py b/pyminer/widgets/utilities/uilogics/undomanager.py
new file mode 100644
index 0000000000000000000000000000000000000000..b336a95ece6bf26a951ad7187d1a91fa6840130b
--- /dev/null
+++ b/pyminer/widgets/utilities/uilogics/undomanager.py
@@ -0,0 +1,65 @@
+"""
+用于撤销和重做的数据结构。
+
+"""
+
+from typing import Any
+
+
+class UndoManager:
+ """
+ 用于撤销和重做的管理类
+ """
+
+ def __init__(self, stack_size: int = 10):
+ self.pointer: int = 0
+ self.content = []
+ self.stack_size = stack_size
+
+ def push(self, obj: Any):
+ """
+ 压栈时指向栈顶,这里就是撤销时候的逻辑。
+ :param obj:
+ :return:
+ """
+ if self.pointer < len(self.content) - 1:
+ self.pointer += 1
+ self.content[self.pointer] = obj
+ else:
+ self.content.append(obj)
+ self.pointer = len(self.content) - 1
+ if len(self.content) > self.stack_size:
+ self.content.pop(0)
+
+ def undo(self) -> Any:
+ if 0 < self.pointer <= len(self.content) - 1:
+
+ obj = self.content[self.pointer]
+ self.pointer -= 1
+ return obj
+ else:
+ if len(self.content) > 0:
+ self.pointer = 0
+ return self.content[0]
+ else:
+ return None
+
+ def redo(self) -> Any:
+ if 0 <= self.pointer < len(self.content) - 1:
+ self.pointer += 1
+ return self.content[self.pointer]
+ else:
+ if len(self.content) > 0:
+ self.pointer = len(self.content) - 1
+ return self.content[self.pointer]
+ else:
+ return None
+
+ def last_value(self) -> Any:
+ try:
+ return self.content[self.pointer]
+ except:
+ return None
+
+ def __len__(self):
+ return len(self.content)
diff --git a/pyminer/widgets/utilities/uilogics/windowutils.py b/pyminer/widgets/utilities/uilogics/windowutils.py
new file mode 100644
index 0000000000000000000000000000000000000000..91cb0ed6a4f96674d365b213b389f5a68debb04b
--- /dev/null
+++ b/pyminer/widgets/utilities/uilogics/windowutils.py
@@ -0,0 +1,35 @@
+from PySide2.QtCore import Qt
+from PySide2.QtWidgets import QWidget, QDesktopWidget
+
+
+def in_unit_test():
+ """
+ 判断是否在单元测试中。
+ 方便控件单独测试。返回True的时候说明在单元测试;返回False的时候说明不在单元测试。
+ 当PyMiner主程序启动后,它会将这个函数用lambda:False这个匿名函数覆盖掉。
+
+ :return:
+ """
+ return True
+
+
+def center_window(window: QWidget):
+ screen = QDesktopWidget().screenGeometry()
+ size = window.geometry()
+ window.move(int((screen.width() - size.width()) / 2),
+ int((screen.height() - size.height()) / 2))
+
+
+def set_always_on_top(window: QWidget):
+ flags = window.windowFlags()
+ window.setWindowFlags(flags | Qt.WindowStaysOnTopHint) # 窗体总在最前端
+
+
+def set_minimizable(window: QWidget):
+ flags = window.windowFlags()
+ window.setWindowFlags(flags | Qt.WindowMinMaxButtonsHint)
+
+
+def set_closable(window: QWidget):
+ flags = window.windowFlags()
+ window.setWindowFlags(flags | Qt.WindowCloseButtonHint)
diff --git a/pyminer/widgets/widgets/__init__.py b/pyminer/widgets/widgets/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..72999c8c3cbc469ea3a501e29eee1613109cb4ae
--- /dev/null
+++ b/pyminer/widgets/widgets/__init__.py
@@ -0,0 +1,6 @@
+import time
+
+t0 = time.time()
+from .basic import *
+from .composited import *
+from .extended import *
diff --git a/pyminer/widgets/widgets/basic/__init__.py b/pyminer/widgets/widgets/basic/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..8f3340d85a56821d643954a02cc8781842b7fb65
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/__init__.py
@@ -0,0 +1,10 @@
+from .browsers import *
+from .buttons import *
+from .containers import *
+from .labels import *
+from .others import *
+from .plots import *
+from .tables import *
+from .texts import *
+from .trees import *
+from .dialogs import *
diff --git a/pyminer/widgets/widgets/basic/browsers/__init__.py b/pyminer/widgets/widgets/basic/browsers/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..42c47d01e6777909f57b1ab869f98e42dc107d54
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/browsers/__init__.py
@@ -0,0 +1,7 @@
+import warnings
+try:
+ from .browser import *
+except ImportError:
+ warnings.warn('QWebEngine cannot import, maybe it was not installed.')
+ import traceback
+ traceback.print_exc()
\ No newline at end of file
diff --git a/pyminer/widgets/widgets/basic/browsers/browser.py b/pyminer/widgets/widgets/basic/browsers/browser.py
new file mode 100644
index 0000000000000000000000000000000000000000..d52b7e86599bb8479f5b1051113504e8ceeaae1e
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/browsers/browser.py
@@ -0,0 +1,84 @@
+"""
+代码来源:
+https://www.cnblogs.com/taostaryu/p/9772492.html
+
+"""
+import sys
+from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout, QToolBar, QLineEdit
+from PySide2.QtCore import QUrl, Signal
+from PySide2.QtWebEngineWidgets import QWebEngineView
+
+
+class PMGWebBrowser(QWidget):
+ def __init__(self, parent=None, toolbar='standard'):
+ """
+
+ :param parent:
+ :param toolbar:多种选项:‘no’,‘standard’,'no_url_input','refresh_only'
+ """
+ super().__init__(parent)
+ self.webview = PMGWebEngineView()
+ self.setLayout(QVBoxLayout())
+ self.toolbar = QToolBar()
+ self.url_input = QLineEdit()
+ self.toolbar.addWidget(self.url_input)
+ self.toolbar.addAction('go').triggered.connect(lambda b: self.load_url())
+ back_action = self.toolbar.addAction('back')
+ back_action.triggered.connect(self.webview.back)
+
+ forward_action = self.toolbar.addAction('forward')
+ forward_action.triggered.connect(self.webview.forward)
+ self.layout().addWidget(self.toolbar)
+ if toolbar == 'no':
+ self.toolbar.hide()
+ elif toolbar == 'no_url_input':
+ self.url_input.hide()
+ elif toolbar == 'refresh_only':
+ self.url_input.hide()
+ back_action.setEnabled(False)
+ forward_action.setEnabled(True)
+
+ self.layout().addWidget(self.webview)
+ self.setWindowTitle('My Browser')
+ self.showMaximized()
+
+ # command:>
+ # jupyter notebook --port 5000 --no-browser --ip='*' --NotebookApp.token=''
+ # --NotebookApp.password='' c:\users\12957\
+
+ # self.webview.load(QUrl("http://127.0.0.1:5000/notebooks/desktop/Untitled.ipynb")) # 直接请求页面。
+ # self.webview.load(QUrl("E:\Python\pyminer_bin\PyMiner\bin\widgets\display\browser\show_formula.html")) # 直接请求页面。
+ # self.setCentralWidget(self.webview)
+
+ def load_url(self, url: str = ''):
+ if url == '':
+ url = self.url_input.text().strip()
+ else:
+ self.url_input.setText(url)
+ self.webview.load(QUrl(url))
+
+
+class PMGWebEngineView(QWebEngineView):
+ windowList = []
+ signal_new_window_created = Signal(PMGWebBrowser)
+ signal_load_url = Signal(str)
+
+ # 重写createwindow()
+ def createWindow(self, QWebEnginePage_WebWindowType):
+ new_window = PMGWebBrowser()
+ self.windowList.append(new_window) # 注:没有这句会崩溃!!!
+ self.signal_new_window_created.emit(new_window)
+ return new_window.webview
+
+ def load(self, request: QUrl) -> None:
+ print('load url', request, request.toString())
+ self.signal_load_url.emit(request.toString())
+ super(PMGWebEngineView, self).load(request)
+
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+
+ w = PMGWebBrowser()
+ w.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/widgets/basic/browsers/translations/qt_zh_CN.ts b/pyminer/widgets/widgets/basic/browsers/translations/qt_zh_CN.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ccc96b059fec006e6e2ba83468e68fec8c6a35c7
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/browsers/translations/qt_zh_CN.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/pyminer/widgets/widgets/basic/buttons/__init__.py b/pyminer/widgets/widgets/basic/buttons/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d7bc07af719b3ff8b546d6ce8ab3963ef41eb7e6
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/buttons/__init__.py
@@ -0,0 +1,2 @@
+from .buttonpane import *
+from .button import *
diff --git a/pyminer/widgets/widgets/basic/buttons/button/__init__.py b/pyminer/widgets/widgets/basic/buttons/button/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..edfb3b8657038d2588a939a7bea134863e783ece
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/buttons/button/__init__.py
@@ -0,0 +1 @@
+from .toolbutton import *
\ No newline at end of file
diff --git a/pyminer/widgets/widgets/basic/buttons/button/toolbutton.py b/pyminer/widgets/widgets/basic/buttons/button/toolbutton.py
new file mode 100644
index 0000000000000000000000000000000000000000..5d20e511d305ab09eaab0415e6e75ea9f58a696e
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/buttons/button/toolbutton.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python
+# -*- coding:utf-8 -*-
+from PySide2.QtCore import QEvent, QTimer
+from PySide2.QtGui import QColor
+from PySide2.QtWidgets import QToolButton, QGraphicsDropShadowEffect, QMenu, QWidget, QHBoxLayout
+
+
+class PMMenu(QMenu):
+ def __init__(self, tool_button: 'PMGToolButton' = None):
+ super().__init__()
+ self.tool_button = tool_button
+
+ def leaveEvent(self, a0: QEvent) -> None:
+ print('leave menu', self.underMouse(), self.tool_button.underMouse())
+
+ QTimer.singleShot(50, self.tool_button.hide_menu)
+
+
+
+class PMGToolButton(QToolButton):
+ shadow = QGraphicsDropShadowEffect() # 实例阴影
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.shadow.setColor(QColor(63, 72, 204)) # 设置阴影颜色
+ self.shadow.setOffset(0, 0) # 设置阴影方向
+ self.setMinimumWidth(60)
+ self.setMinimumHeight(40)
+ self.clicked.connect(self.set_btn_clicked_effect)
+ self.menu = PMMenu(tool_button=self)
+ self.setMenu(self.menu)
+ self.menu.addAction('新建行')
+ self.menu.addAction('新建列')
+ self.menu.addAction('删除行')
+ self.menu.addAction('删除列')
+
+ def set_btn_clicked_effect(self):
+ # 设置模糊度并为按钮添加阴影
+ self.shadow.setBlurRadius(20)
+ self.setGraphicsEffect(self.shadow)
+
+ def unset_btn_clicked_effect(self):
+ # 设置模糊度为0 间接取消阴影
+ self.shadow.setBlurRadius(0)
+
+ def show_menu(self):
+ if self.underMouse() or self.menu.underMouse():
+ self.menu.popup(self.mapToGlobal(self.pos()))
+ self.grabMouse()
+
+ def hide_menu(self):
+ if not (self.underMouse() or self.menu.underMouse()):
+ self.menu.hide()
+
+
+if __name__ == '__main__':
+ from PySide2.QtWidgets import QApplication
+ import sys
+
+ app = QApplication(sys.argv)
+ w = QWidget()
+ layout = QHBoxLayout()
+ w.setLayout(layout)
+ for i in range(3):
+ w1 = PMGToolButton()
+ layout.addWidget(w1)
+ w1.setText('aaa')
+ w.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/widgets/basic/buttons/buttonpane/__init__.py b/pyminer/widgets/widgets/basic/buttons/buttonpane/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..5b3cc9f23343fc46226eccb69b339aafe8b8adf5
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/buttons/buttonpane/__init__.py
@@ -0,0 +1 @@
+from .pushbuttonpane import *
diff --git a/pyminer/widgets/widgets/basic/buttons/buttonpane/pushbuttonpane.py b/pyminer/widgets/widgets/basic/buttons/buttonpane/pushbuttonpane.py
new file mode 100644
index 0000000000000000000000000000000000000000..730dd3222302f4844f02433897034f9a0e2149d5
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/buttons/buttonpane/pushbuttonpane.py
@@ -0,0 +1,78 @@
+"""
+pmpushbuttons是一个按钮组,专门负责向工具栏中插入按钮。
+其中PMPushButtonPane是按钮的载体,可以插入竖向排布的两个到三个按钮。
+作者:Zhanyi Hou
+"""
+from typing import List, Union
+
+from PySide2.QtCore import Qt
+from PySide2.QtGui import QIcon
+from PySide2.QtWidgets import QWidget, QVBoxLayout, QPushButton, QMenu, QLabel, QToolButton
+
+from widgets.utilities import create_icon
+
+
+class PMPushButtonPane(QWidget):
+ def __init__(self):
+ super().__init__()
+ self.layout = QVBoxLayout()
+ self.setLayout(self.layout)
+
+ def add_height_occu_buttons(self):
+ button_num = 3
+ btn_list = []
+ for i in range(button_num):
+ btn = QLabel()
+ btn.setText(' ')
+ btn.setProperty('qssp', 'occu')
+ btn_list.append(btn)
+ btn.setObjectName('space_occupation_button')
+ self.layout.addWidget(btn)
+ return btn_list
+
+ def add_buttons(self, button_num: int = 2, text: list = None, icon_path: List[str] = None,
+ menu: list = None) -> List[QPushButton]:
+ if text is None:
+ text = [''] * button_num
+ if icon_path is None:
+ icon_path = [None] * button_num
+ if menu is None:
+ menu = [None] * button_num
+ if len(text) != button_num or len(
+ icon_path) != button_num or len(menu) != button_num:
+ raise Exception('text,icon和menu参数都必须为长为2的可迭代对象。')
+ qssproperty = "minibutton3"
+ if button_num == 2:
+ qssproperty = "minibutton2"
+
+ btn_list = []
+ for i in range(button_num):
+ btn = self.add_button(text=text[i], icon=create_icon(icon_path[i]), menu=menu[i], qssproperty=qssproperty)
+ btn_list.append(btn)
+ btn.setObjectName('stacked_tool_button')
+ return btn_list
+
+ def add_button(self, text: str = '', icon: QIcon = None, menu: QMenu = None, qssproperty="minibutton3") \
+ -> Union[QToolButton, QPushButton]:
+ """
+ 添加按钮
+ 由于QToolButton不支持同时添加图标和下拉菜单箭头,所以不得不使用QPushButton。
+ [TODO!]QPushButton的样式仍然不好。
+ :param text:
+ :param icon:
+ :param menu:
+ :param qssproperty:
+ :return:
+ """
+ pbtn = QToolButton()
+ pbtn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
+ pbtn.setPopupMode(QToolButton.InstantPopup)
+ pbtn.setText(text)
+
+ if icon is not None:
+ pbtn.setIcon(icon)
+ if menu is not None:
+ pbtn.setMenu(menu)
+ pbtn.setProperty("qssp", qssproperty)
+ self.layout.addWidget(pbtn)
+ return pbtn
diff --git a/pyminer/widgets/widgets/basic/buttons/translations/qt_zh_CN.ts b/pyminer/widgets/widgets/basic/buttons/translations/qt_zh_CN.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ccc96b059fec006e6e2ba83468e68fec8c6a35c7
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/buttons/translations/qt_zh_CN.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/pyminer/widgets/widgets/basic/containers/PMTab.py b/pyminer/widgets/widgets/basic/containers/PMTab.py
new file mode 100644
index 0000000000000000000000000000000000000000..e6861f62b3a943128950794304b054d7717a6587
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/containers/PMTab.py
@@ -0,0 +1,22 @@
+from PySide2.QtWidgets import QTabWidget, QWidget
+
+
+class PMTabWidget(QTabWidget):
+ def setup_ui(self):
+ for tab_id in range(self.count()): # 遍历所有的tab
+ w = self.widget(tab_id)
+ if hasattr(w, 'setup_ui'):
+ self.widget(tab_id).setup_ui()
+
+ def addScrolledAreaTab(self, widget: QWidget, a1: str):
+ """
+ 添加使用QScrollArea包裹的Tab。
+ :param widget:
+ :param a1:
+ :return:
+ """
+ from widgets import PMScrollArea
+ scroll = PMScrollArea()
+ scroll.setWidget(widget)
+
+ super().addTab(scroll, a1)
diff --git a/pyminer/widgets/widgets/basic/containers/__init__.py b/pyminer/widgets/widgets/basic/containers/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..943814a8149a666c9e590bd66eb8b9a929ec3565
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/containers/__init__.py
@@ -0,0 +1,6 @@
+from .PMTab import PMTabWidget
+from .pmscrollarea import PMScrollArea
+from .flowarea import PMFlowArea
+from .flowlayout import PMFlowLayout, PMFlowLayoutWithGrid
+from .pmdockwidget import PMGDockWidget
+from .pmtoolbox import PMGToolBox
diff --git a/pyminer/widgets/widgets/basic/containers/flowarea.py b/pyminer/widgets/widgets/basic/containers/flowarea.py
new file mode 100644
index 0000000000000000000000000000000000000000..f8df49ed8fc0c8358656a0e87dad6052d0e3b84b
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/containers/flowarea.py
@@ -0,0 +1,93 @@
+"""
+集成了流式布局、按钮排布的窗口。
+"""
+from PySide2.QtWidgets import QScrollArea, QWidget, QToolButton, QVBoxLayout, QSpacerItem, QSizePolicy
+from PySide2.QtCore import Qt, QSize
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from PySide2.QtGui import QResizeEvent
+ from widgets import PMFlowLayout
+
+
+class PMFlowAreaWidget(QWidget):
+ def __init__(self):
+ super().__init__()
+ from widgets import PMFlowLayout
+
+ self.outer_layout = QVBoxLayout()
+
+ self.flow_layout = PMFlowLayout()
+ self.setMinimumWidth(100)
+ self.outer_layout.addLayout(self.flow_layout)
+ spacer_v = QSpacerItem(20, 20, QSizePolicy.Minimum,
+ QSizePolicy.Expanding)
+
+ self.outer_layout.addItem(spacer_v)
+ self.setLayout(self.outer_layout)
+
+ def add_widget(self, w: 'QWidget'):
+ self.flow_layout.add_widget(w)
+
+ def setup_ui(self):
+ if hasattr(self.widget(), 'setup_ui'):
+ self.widget().setup_ui()
+
+ def resizeEvent(self, a0: 'QResizeEvent') -> None:
+ super().resizeEvent(a0)
+ layout: 'PMFlowLayout' = self.flow_layout
+ layout.on_resize()
+
+
+class PMFlowArea(QScrollArea):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+
+ self.flow_widget = PMFlowAreaWidget()
+ self.widgets_list = self.flow_widget.flow_layout.widgets_list
+ self.setWidget(self.flow_widget)
+ self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
+ self.setWidgetResizable(True)
+
+ def set_layout_content_margins(
+ self, left: int, right: int, up: int, down: int):
+ self.flow_widget.flow_layout.setContentsMargins(left, right, up, down)
+
+ def add_tool_button(self, name: str, text: str, icon_path: str = ''):
+ from widgets import create_icon
+ b = QToolButton()
+ b.setText(text)
+ icon = create_icon(icon_path)
+ b.setIcon(icon)
+ b.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
+ b.setIconSize(QSize(40, 40))
+ b.setMaximumWidth(80)
+ b.setMinimumWidth(80)
+ b.setMinimumHeight(60)
+ b.setMaximumHeight(60)
+ self.add_widget(b)
+ return b
+
+ def add_widget(self, w: 'QWidget'):
+ self.widget().add_widget(w)
+ return w
+
+ def setup_ui(self):
+ if hasattr(self.widget(), 'setup_ui'):
+ self.widget().setup_ui()
+
+
+if __name__ == '__main__':
+ from PySide2.QtWidgets import QApplication, QPushButton
+ import sys
+
+ app = QApplication(sys.argv)
+ sa = PMFlowArea()
+ for i in range(10):
+ w = sa.add_widget(QPushButton('ad%d' % i))
+ w.setMaximumHeight(60)
+ w.setMinimumHeight(60)
+ w.setMinimumWidth(100)
+ w.setMaximumWidth(100)
+ sa.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/widgets/basic/containers/flowlayout.py b/pyminer/widgets/widgets/basic/containers/flowlayout.py
new file mode 100644
index 0000000000000000000000000000000000000000..dc1c2d7aa2d54da80a92aecba80845830416a327
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/containers/flowlayout.py
@@ -0,0 +1,113 @@
+from PySide2.QtWidgets import QGridLayout, QWidget, QSizePolicy
+
+
+class PMFlowLayoutWithGrid(QGridLayout):
+ """
+ 流式布局,继承自QGridLayout,以Grid的方式添加widget。
+ 主要作用是在保证兼容代码的基础上,做到流式布局。
+ """
+
+ def __init__(self, parent=None, column_width=100):
+ super().__init__(parent)
+
+ self.column_width = column_width
+ self.widgets_list = []
+
+ def addWidget(self, w: QWidget, row: int, column: int,
+ rowSpan: int, columnSpan: int) -> None:
+ """
+ 添加控件的方法。多了一个列表将所有的控件存储起来。当然,不允许重复添加。
+ :param w:
+ :param row:
+ :param column:
+ :param rowSpan:
+ :param columnSpan:
+ :return:
+ """
+ if w not in self.widgets_list:
+ self.widgets_list.append(w)
+ super().addWidget(w, row, column, rowSpan, columnSpan)
+
+ def on_resize(self):
+ """
+ 在界面放大缩小的时候,会将按钮重新排布。按照表格上从上到下、从左到右的顺序,就像下面这样:
+ 注意这个方法无法自动调用,只能依靠它的父控件。
+ 1 2 3 4
+ 5 6 7 8
+ 9
+ :return:
+ """
+ geometry = self.geometry()
+ cols = int(geometry.width() / self.column_width)
+
+ if cols == self.columnCount():
+ return
+ row = 0
+ col = 0
+ for i in range(len(self.widgets_list)):
+ w = self.widgets_list[i]
+ self.removeWidget(w)
+ self.addWidget(w, row, col, 1, 1)
+ col += 1
+ if col == cols:
+ row += 1
+ col = 0
+
+
+class PMFlowLayout(QGridLayout):
+ def __init__(self, parent=None, initial_columns=3, column_width=100):
+ super().__init__(parent)
+
+ self.column_width = column_width
+ self.widgets_list = []
+ self.occupy_widget = QWidget()
+ self.occupy_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ def add_widget(self, w: QWidget) -> None:
+ """
+ 添加控件的方法。多了一个列表将所有的控件存储起来。当然,不允许重复添加。
+ :param w:
+ :param row:
+ :param column:
+ :param rowSpan:
+ :param columnSpan:
+ :return:
+ """
+
+ if w not in self.widgets_list:
+ self.widgets_list.append(w)
+ items = len(self.widgets_list)
+ geometry = self.geometry()
+ cols = int(geometry.width() / self.column_width)
+ if cols == 0:
+ cols = 3
+ current_row = int(items / cols)
+ current_col = int(items % cols)
+ super().addWidget(w, current_row, current_col, 1, 1)
+
+ def on_resize(self):
+ """
+ 在界面放大缩小的时候,会将按钮重新排布。按照表格上从上到下、从左到右的顺序,就像下面这样:
+ 注意这个方法无法自动调用,只能依靠它的父控件。
+ 1 2 3 4
+ 5 6 7 8
+ 9
+ :return:
+ """
+ geometry = self.geometry()
+ cols = int(geometry.width() / self.column_width)
+
+ if cols == self.columnCount():
+ return
+ row = 0
+ col = 0
+ for i in range(len(self.widgets_list)):
+ w = self.widgets_list[i]
+ self.removeWidget(w)
+ self.addWidget(w, row, col, 1, 1)
+ col += 1
+ if col == cols:
+ row += 1
+ col = 0
+ self.removeWidget(self.occupy_widget)
+ self.addWidget(self.occupy_widget, row, col, 1, 1)
diff --git a/pyminer/widgets/widgets/basic/containers/pmdockwidget.py b/pyminer/widgets/widgets/basic/containers/pmdockwidget.py
new file mode 100644
index 0000000000000000000000000000000000000000..b09df36a34881afb18351648f6c174665541e2a6
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/containers/pmdockwidget.py
@@ -0,0 +1,21 @@
+from PySide2.QtWidgets import QDockWidget, QMainWindow
+
+
+class PMGDockWidget(QDockWidget):
+ def __init__(self, name, text='', parent: QMainWindow = None):
+ super().__init__(text, parent)
+ self.parent = parent
+ self.name = name
+
+ def keyPressEvent(self, a0) -> None:
+ super(PMGDockWidget, self).keyPressEvent(a0)
+ print(a0)
+
+ def raise_into_view(self):
+ """
+ 将控件提升到能直接看到的位置。特别适用于两个选项卡叠在一起的情况。
+ :return:
+ """
+ self.setVisible(True)
+ self.setFocus()
+ self.raise_()
diff --git a/pyminer/widgets/widgets/basic/containers/pmscrollarea.py b/pyminer/widgets/widgets/basic/containers/pmscrollarea.py
new file mode 100644
index 0000000000000000000000000000000000000000..9b787ce60ab17aa11177ac25b2576c2e769c7fe3
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/containers/pmscrollarea.py
@@ -0,0 +1,13 @@
+from PySide2.QtWidgets import QScrollArea
+from PySide2.QtCore import Qt
+
+
+class PMScrollArea(QScrollArea):
+ def __init__(self):
+ super().__init__()
+ self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
+ self.setWidgetResizable(True)
+
+ def setup_ui(self):
+ if hasattr(self.widget(), 'setup_ui'):
+ self.widget().setup_ui()
diff --git a/pyminer/widgets/widgets/basic/containers/pmtoolbox.py b/pyminer/widgets/widgets/basic/containers/pmtoolbox.py
new file mode 100644
index 0000000000000000000000000000000000000000..897490f9d2ad201462c238edfba9fc566a4fda0e
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/containers/pmtoolbox.py
@@ -0,0 +1,31 @@
+from PySide2.QtWidgets import QToolBox, QWidget
+from typing import Dict, Callable
+
+
+class PMGToolBox(QToolBox):
+ group_widgets: Dict[str, QWidget] = {}
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.button_num = 0
+ pass
+
+ def set_group_text(self, group_name: str, text: str):
+ gw = self.group_widgets.get(group_name)
+ if gw is not None:
+ self.setItemText(self.indexOf(gw), text)
+
+ def add_button(self, group_name: str, text: str, icon_path: str, action: Callable):
+ from widgets import PMFlowArea
+ if self.group_widgets.get(group_name) is None:
+ fa = PMFlowArea()
+ self.group_widgets[group_name] = fa
+ self.addItem(fa, group_name)
+ btn = fa.add_tool_button(name='button#%d' % self.button_num, text=text, icon_path=icon_path)
+ btn.clicked.connect(action)
+ self.button_num += 1
+ else:
+ fa: PMFlowArea = self.group_widgets[group_name]
+ btn = fa.add_tool_button(name='button#%d' % self.button_num, text=text, icon_path=icon_path)
+ btn.clicked.connect(action)
+ self.button_num += 1
diff --git a/pyminer/widgets/widgets/basic/containers/translations/qt_zh_CN.ts b/pyminer/widgets/widgets/basic/containers/translations/qt_zh_CN.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ccc96b059fec006e6e2ba83468e68fec8c6a35c7
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/containers/translations/qt_zh_CN.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/pyminer/widgets/widgets/basic/dialogs/__init__.py b/pyminer/widgets/widgets/basic/dialogs/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..90afc90939d1105f5890e3fd802d01251deb9354
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/dialogs/__init__.py
@@ -0,0 +1,6 @@
+# -*- coding:utf-8 -*-
+# @Time: 2021/2/10 10:12
+# @Author: Zhanyi Hou
+# @Email: 1295752786@qq.com
+# @File: __init__.py.py
+from .textdialog import TextShowDialog
\ No newline at end of file
diff --git a/pyminer/widgets/widgets/basic/dialogs/textdialog.py b/pyminer/widgets/widgets/basic/dialogs/textdialog.py
new file mode 100644
index 0000000000000000000000000000000000000000..3af4c2f445352d0b0b1e099f6424ff08fe75220a
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/dialogs/textdialog.py
@@ -0,0 +1,22 @@
+# -*- coding:utf-8 -*-
+# @Time: 2021/2/10 10:12
+# @Author: Zhanyi Hou
+# @Email: 1295752786@qq.com
+# @File: textdialog.py
+from PySide2.QtCore import QSize
+from PySide2.QtWidgets import QApplication, QDialog, QVBoxLayout, QTextBrowser
+
+
+class TextShowDialog(QDialog):
+ def __init__(self, title: str, parent=None):
+ super(TextShowDialog, self).__init__(parent=parent)
+ self.setLayout(QVBoxLayout())
+ self.setWindowTitle(title)
+ self.text_widget = QTextBrowser()
+ self.layout().addWidget(self.text_widget)
+
+ def set_markdown(self, markdown: str):
+ self.text_widget.setMarkdown(markdown)
+
+ def sizeHint(self) -> QSize:
+ return QSize(800, 600)
diff --git a/pyminer/widgets/widgets/basic/images/__init__.py b/pyminer/widgets/widgets/basic/images/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/widgets/widgets/basic/images/imageview.py b/pyminer/widgets/widgets/basic/images/imageview.py
new file mode 100644
index 0000000000000000000000000000000000000000..796bc0bacc63fa43a3d55ede477f55791f5100db
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/images/imageview.py
@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+"""
+This example demonstrates the use of ImageView, which is a high-level widget for
+displaying and analyzing 2D and 3D data. ImageView provides:
+
+ 1. A zoomable region (ViewBox) for displaying the image
+ 2. A combination histogram and gradient editor (HistogramLUTItem) for
+ controlling the visual appearance of the image
+ 3. A timeline for selecting the currently displayed frame (for 3D data only).
+ 4. Tools for very basic analysis of image data (see ROI and Norm buttons)
+
+"""
+## Add path to library (just for examples; you do not need this)
+
+
+from pyqtgraph.Qt import QtCore, QtGui
+import pyqtgraph as pg
+from PySide2.QtWidgets import QWidget, QVBoxLayout
+
+
+class PMGImageViewer(QWidget):
+ def __init__(self, parent=None):
+ super(PMGImageViewer, self).__init__(parent)
+ self.image_view = pg.ImageView()
+ self.setLayout(QVBoxLayout())
+ # QVBoxLayout.setC
+ self.layout().setContentsMargins(0, 0, 0, 0)
+ self.layout().addWidget(self.image_view)
+
+ def set_image(self, img, xvals=None):
+ self.image_view.setImage(img) # , xvals=xvals)
+
+ def set_color_map(self, colormap):
+ self.image_view.setColorMap(colormap=colormap)
+
+
+if __name__ == '__main__':
+ import numpy as np
+
+ # Interpret image data as row-major instead of col-major
+ pg.setConfigOptions(imageAxisOrder='row-major')
+
+ app = QtGui.QApplication([])
+
+ ## Create window with ImageView widget
+ # win = QtGui.QMainWindow()
+ # win.resize(800, 800)
+ imv = PMGImageViewer()
+ imv.show()
+ imv.setWindowTitle('image view!')
+ # win.setCentralWidget(imv)
+ # win.show()
+ # win.setWindowTitle('pyqtgraph example: ImageView')
+
+ ## Create random 3D data set with noisy signals
+ img = pg.gaussianFilter(np.random.normal(size=(200, 200)), (5, 5)) * 20 + 100
+ img = img[np.newaxis, :, :]
+ decay = np.exp(-np.linspace(0, 0.3, 100))[:, np.newaxis, np.newaxis]
+ data = np.random.normal(size=(100, 200, 200))
+ data += img * decay
+ data += 2
+
+ ## Add time-varying signal
+ sig = np.zeros(data.shape[0])
+ sig[30:] += np.exp(-np.linspace(1, 10, 70))
+ sig[40:] += np.exp(-np.linspace(1, 10, 60))
+ sig[70:] += np.exp(-np.linspace(1, 10, 30))
+
+ sig = sig[:, np.newaxis, np.newaxis] * 3
+ data[:, 50:60, 30:40] += sig
+ x = np.ones((1920, 1080, 3), dtype=np.uint8) + 50
+ # x[:, :, 0] = np.zeros((1920, 1080)) + 200
+ # x[:, :, 1] = 0
+ # x[:, :, 2] = 0
+ ## Display the data and assign each frame a time value from 1.0 to 3.0
+ print(x.shape,x,data,data.shape)
+ imv.set_image(data) # , xvals=np.linspace(1., 3., data.shape[0]))
+
+ ## Set a custom color map
+ colors = [
+ (0, 0, 0),
+ (45, 5, 61),
+ (84, 42, 55),
+ (150, 87, 60),
+ (208, 171, 141),
+ (255, 255, 255)
+ ]
+ # cmap = pg.ColorMap(pos=np.linspace(0.0, 1.0, 6), color=colors)
+ # imv.set_color_map(cmap)
+
+ ## Start Qt event loop unless running in interactive mode.
+ import sys
+
+ if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
+ QtGui.QApplication.instance().exec_()
diff --git a/pyminer/widgets/widgets/basic/images/imageviewitem.py b/pyminer/widgets/widgets/basic/images/imageviewitem.py
new file mode 100644
index 0000000000000000000000000000000000000000..a7edbadf8a44f580862f37cc3a09fa04a96acf8f
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/images/imageviewitem.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+"""
+Demonstrates very basic use of ImageItem to display image data inside a ViewBox.
+"""
+
+## Add path to library (just for examples; you do not need this)
+# from pyqtgraph.Qt import QtCore, QtGui
+import pyqtgraph as pg
+from PySide2.QtWidgets import QWidget, QVBoxLayout, QLabel, QScrollArea
+from PySide2.QtGui import QImage, QPixmap
+from PySide2.QtCore import Qt
+import time
+from typing import TYPE_CHECKING
+from PIL import Image
+
+if TYPE_CHECKING:
+ import numpy as np
+
+
+class PMGImageViewer(QWidget):
+ def __init__(self, parent=None):
+ """
+ 这是一个可以用多种方式和数据类型设置、获取图片的控件。
+ Args:
+ parent:
+ """
+ super(QWidget, self).__init__(parent)
+
+ self.setLayout(QVBoxLayout())
+ self.img_show = QLabel()
+ self.img_show.setAlignment(Qt.AlignTop | Qt.AlignLeft)
+ self.scroll_widget = QScrollArea()
+ self.layout().addWidget(self.scroll_widget)
+ self.scroll_widget.setWidget(self.img_show)
+
+ def set_image_qpixmap(self, pixmap):
+ self.img_show.setPixmap(pixmap)
+ self.img_show.setMinimumSize(pixmap.width() + 100, pixmap.height() + 100)
+
+ def set_image_array(self, arr: 'np.ndarray'):
+ from PIL import Image
+ image2 = Image.fromarray(arr)
+ self.set_image_qpixmap(image2.toqpixmap())
+
+ def open_image(self, image_path):
+ from PIL import Image
+ img = Image.open(image_path)
+ # img.topixmap()
+ self.set_image_qpixmap(img.toqpixmap())
+
+ def get_image_qpixmap(self) -> QPixmap:
+ return self.img_show.pixmap()
+
+ def get_image_array(self) -> 'np.ndarray':
+ from PIL import Image
+ img = Image.fromqpixmap(self.get_image_qpixmap())
+ return np.array(img)
+
+
+if __name__ == '__main__':
+
+ from pyqtgraph.Qt import QtCore, QtGui
+ import numpy as np
+ import pyqtgraph as pg
+ import pyqtgraph.ptime as ptime
+
+ app = QtGui.QApplication([])
+ # read_figure()
+
+ ## Create window with GraphicsView widget
+ win = PMGImageViewer()
+ win.showMaximized() ## show widget alone in its own window
+ win.setWindowTitle('pyqtgraph example: ImageItem')
+
+ # updateData()
+ image = Image.open(
+ r'C:\Users\12957\Documents\Developing\Python\PyMiner_dev_kit\bin\widgets\doc_figures\pmflowarea_1.png')
+ # image = np.array(image)
+ # image2 = Image.fromarray(image)
+ # win.set_image(image2.toqpixmap())
+ win.open_image(
+ r'C:\Users\12957\Documents\Developing\Python\PyMiner_dev_kit\bin\widgets\doc_figures\pmflowarea_1.png')
+ ## Start Qt event loop unless running in interactive mode.
+ import sys
+
+ if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
+ QtGui.QApplication.instance().exec_()
diff --git a/pyminer/widgets/widgets/basic/labels/__init__.py b/pyminer/widgets/widgets/basic/labels/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..da270d585d6032cfcfcbf7ee1eb8c22adf60f405
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/labels/__init__.py
@@ -0,0 +1 @@
+from .scrolllabel import *
\ No newline at end of file
diff --git a/pyminer/widgets/widgets/basic/labels/scrolllabel.py b/pyminer/widgets/widgets/basic/labels/scrolllabel.py
new file mode 100644
index 0000000000000000000000000000000000000000..270f194fd92c889242d57feffeae31623f1af010
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/labels/scrolllabel.py
@@ -0,0 +1,34 @@
+from PySide2.QtWidgets import QWidget, QVBoxLayout, QLabel, QScrollArea
+
+
+from PySide2.QtCore import Qt
+
+
+class PMScrollableLabel(QWidget):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ layout = QVBoxLayout()
+ self.scroll_area = QScrollArea()
+ self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
+ self.label = QLabel()
+ layout_label = QVBoxLayout()
+ layout_label.addWidget(self.label)
+ self.scroll_area.setLayout(layout_label)
+
+ layout.addWidget(self.scroll_area)
+
+ self.setLayout(layout)
+
+ self.setText = self.label.setText
+ self.setWordWrap = self.label.setWordWrap
+
+
+if __name__ == '__main__':
+ from PySide2.QtWidgets import QApplication
+ import sys
+ app = QApplication(sys.argv)
+ w = PMScrollableLabel()
+ w.setWordWrap(True)
+ w.setText('aaaaaaaaa ' * 10000)
+ w.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/widgets/basic/labels/translations/qt_zh_CN.ts b/pyminer/widgets/widgets/basic/labels/translations/qt_zh_CN.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ccc96b059fec006e6e2ba83468e68fec8c6a35c7
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/labels/translations/qt_zh_CN.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/pyminer/widgets/widgets/basic/lists/__init__.py b/pyminer/widgets/widgets/basic/lists/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/widgets/widgets/basic/lists/combobasic.py b/pyminer/widgets/widgets/basic/lists/combobasic.py
new file mode 100644
index 0000000000000000000000000000000000000000..a918a7f676f036b40e182478defef142d95aecd9
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/lists/combobasic.py
@@ -0,0 +1,62 @@
+import sys
+from PySide2.QtCore import *
+from PySide2.QtWidgets import *
+from PySide2.QtCore import *
+
+
+class ComboxDemo(QWidget):
+ def __init__(self, parent=None):
+ super(ComboxDemo, self).__init__(parent)
+ # 设置标题
+ self.setWindowTitle('ComBox例子')
+ # 设置初始界面大小
+ self.resize(300, 90)
+
+ # 垂直布局
+ layout = QVBoxLayout()
+ # 创建标签,默认空白
+ self.btn1 = QLabel('')
+
+ # 实例化QComBox对象
+ self.cb = QComboBox()
+ # 单个添加条目
+ self.cb.addItem('C')
+ self.cb.addItem('C++')
+ self.cb.addItem('Python')
+ # 多个添加条目
+ self.cb.addItems(['Java', 'C#', 'PHP'])
+ # 当下拉索引发生改变时发射信号触发绑定的事件
+ self.cb.mousePressEvent = self.cb_mouse_pressed
+ self.cb.currentIndexChanged.connect(self.selectionchange)
+
+ # 控件添加到布局中,设置布局
+ layout.addWidget(self.cb)
+ layout.addWidget(self.btn1)
+ self.setLayout(layout)
+
+ def cb_mouse_pressed(self, a0) -> None:
+ """
+ 触发时自动改变选项。
+ :param a0:
+ :return:
+ """
+ self.cb.addItems(['A'])
+ QComboBox.mousePressEvent(self.cb, a0)
+
+ def selectionchange(self, i):
+ # 标签用来显示选中的文本
+ # currentText():返回选中选项的文本
+ self.btn1.setText(self.cb.currentText())
+ print('Items in the list are:')
+ # 输出选项集合中每个选项的索引与对应的内容
+ # count():返回选项集合中的数目
+ for count in range(self.cb.count()):
+ print('Item' + str(count) + '=' + self.cb.itemText(count))
+ print('current index', i, 'selection changed', self.cb.currentText())
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ comboxDemo = ComboxDemo()
+ comboxDemo.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/widgets/basic/lists/translations/qt_zh_CN.ts b/pyminer/widgets/widgets/basic/lists/translations/qt_zh_CN.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ccc96b059fec006e6e2ba83468e68fec8c6a35c7
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/lists/translations/qt_zh_CN.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/pyminer/widgets/widgets/basic/others/ConsoleHistoryDialog.py b/pyminer/widgets/widgets/basic/others/ConsoleHistoryDialog.py
new file mode 100644
index 0000000000000000000000000000000000000000..f29581fd40a7539d9989e9cadd83cff8443c803c
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/others/ConsoleHistoryDialog.py
@@ -0,0 +1,97 @@
+import os
+
+from PySide2 import QtCore
+from PySide2.QtCore import QItemSelectionModel
+from PySide2.QtWidgets import QApplication, QDialog
+from .Ui_ConsoleHistoryDialog import Ui_ConsoleHistoryDialog
+
+
+class ConsoleHistoryDialog(QDialog, Ui_ConsoleHistoryDialog):
+ """
+ Class implementing the shell history dialog.
+ """
+
+ def __init__(self, console):
+ super().__init__()
+ self.setupUi(self)
+ self.__console = console
+
+ self.deleteButton.clicked.connect(self.on_deleteButton_clicked)
+ self.copyButton.clicked.connect(self.on_copyButton_clicked)
+ self.reloadButton.clicked.connect(self.on_reloadButton_clicked)
+ self.historyList.itemSelectionChanged.connect(
+ self.on_historyList_itemSelectionChanged)
+
+ self.reloadButton.click()
+
+ @QtCore.Slot(QtCore.QModelIndex)
+ def select(self, item):
+ print(item.data())
+
+ @QtCore.Slot()
+ def on_historyList_itemSelectionChanged(self):
+ """
+ Private slot to handle a change of the selection.
+ """
+ selected = len(self.historyList.selectedItems()) > 0
+ self.deleteButton.setEnabled(selected)
+ self.copyButton.setEnabled(selected)
+ self.executeButton.setEnabled(selected)
+
+ @QtCore.Slot()
+ def on_deleteButton_clicked(self):
+ """
+ Private slot to delete the selected entries from the history.
+ """
+ for itm in self.historyList.selectedItems():
+ ditm = self.historyList.takeItem(self.historyList.row(itm))
+ del ditm
+ self.historyList.scrollToItem(self.historyList.currentItem())
+ self.historyList.setFocus()
+
+ @QtCore.Slot()
+ def on_copyButton_clicked(self):
+ cmds = self.selected_cmds()
+ QApplication.clipboard().setText(cmds)
+
+ @QtCore.Slot()
+ def on_executeButton_clicked(self):
+ cmds = self.selected_cmds()
+ self.__console.hint_command(cmds)
+ self.__console.do_execute(cmds, True, '')
+ # reload the list because shell modified it
+ self.on_reloadButton_clicked()
+
+ @QtCore.Slot()
+ def on_reloadButton_clicked(self):
+ """
+ Private slot to reload the history.
+ """
+ history = self.__console.history_tail(0)
+
+ self.historyList.clear()
+ self.historyList.addItems(history)
+ self.historyList.setCurrentRow(
+ self.historyList.count() - 1,
+ QItemSelectionModel.SelectionFlag.Select)
+
+ self.historyList.scrollToItem(self.historyList.currentItem())
+
+ @QtCore.Slot(QtCore.QModelIndex)
+ def on_historyList_doubleClicked(self, item):
+ self.on_executeButton_clicked()
+
+ def get_history(self):
+ history = []
+ for index in range(self.historyList.count()):
+ history.append(self.historyList.item(index).text())
+ return history
+
+ def selected_cmds(self):
+ lines = []
+ for index in range(self.historyList.count()):
+ # selectedItems() doesn't seem to preserve the order
+ itm = self.historyList.item(index)
+ if itm.isSelected():
+ lines.append(itm.text())
+ return (os.linesep.join(lines) + os.linesep).rstrip(os.linesep)
diff --git a/pyminer/widgets/widgets/basic/others/ConsoleHistoryDialog.ui b/pyminer/widgets/widgets/basic/others/ConsoleHistoryDialog.ui
new file mode 100644
index 0000000000000000000000000000000000000000..59fb25071e01b00c6c25df2d5d9fc3a43f153b58
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/others/ConsoleHistoryDialog.ui
@@ -0,0 +1,158 @@
+
+ ShellHistoryDialog
+
+
+
+ 0
+ 0
+ 540
+ 506
+
+
+
+ Shell History
+
+
+ true
+
+
+ -
+
+
+
+ Monospace
+
+
+
+ QAbstractItemView::NoEditTriggers
+
+
+ true
+
+
+ QAbstractItemView::ExtendedSelection
+
+
+ true
+
+
+
+ -
+
+
-
+
+
+ false
+
+
+ Delete the selected entries
+
+
+ &Delete
+
+
+
+ -
+
+
+ false
+
+
+ Copy the selected entries to the current editor
+
+
+ C&opy
+
+
+
+ -
+
+
+ false
+
+
+ Execute the selected entries
+
+
+ &Execute
+
+
+
+ -
+
+
+ Reload the history
+
+
+ &Reload
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 72
+ 208
+
+
+
+
+
+
+ -
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+ historyList
+ deleteButton
+ copyButton
+ executeButton
+ reloadButton
+ buttonBox
+
+
+
+
+ buttonBox
+ accepted()
+ ShellHistoryDialog
+ accept()
+
+
+ 333
+ 487
+
+
+ 323
+ 505
+
+
+
+
+ buttonBox
+ rejected()
+ ShellHistoryDialog
+ reject()
+
+
+ 167
+ 490
+
+
+ 169
+ 504
+
+
+
+
+
diff --git a/pyminer/widgets/widgets/basic/others/Ui_ConsoleHistoryDialog.py b/pyminer/widgets/widgets/basic/others/Ui_ConsoleHistoryDialog.py
new file mode 100644
index 0000000000000000000000000000000000000000..e555d7aeaa3f82bbcf87ef1b1d224832f118e046
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/others/Ui_ConsoleHistoryDialog.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+# Form generated from reading UI file 'ConsoleHistoryDialog.ui'
+##
+# Created by: Qt User Interface Compiler version 6.1.0
+##
+# WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import *
+from PySide2.QtGui import *
+from PySide2.QtWidgets import *
+
+
+class Ui_ConsoleHistoryDialog(object):
+ def setupUi(self, ConsoleHistoryDialog):
+ if not ConsoleHistoryDialog.objectName():
+ ConsoleHistoryDialog.setObjectName(u"ConsoleHistoryDialog")
+ ConsoleHistoryDialog.resize(540, 506)
+ ConsoleHistoryDialog.setSizeGripEnabled(True)
+ self.gridLayout = QGridLayout(ConsoleHistoryDialog)
+ self.gridLayout.setObjectName(u"gridLayout")
+ self.historyList = QListWidget(ConsoleHistoryDialog)
+ self.historyList.setObjectName(u"historyList")
+ font = QFont()
+ font.setFamilies([u"Monospace"])
+ self.historyList.setFont(font)
+ self.historyList.setEditTriggers(QAbstractItemView.NoEditTriggers)
+ self.historyList.setAlternatingRowColors(True)
+ self.historyList.setSelectionMode(QAbstractItemView.ExtendedSelection)
+ self.historyList.setWordWrap(True)
+
+ self.gridLayout.addWidget(self.historyList, 0, 0, 1, 1)
+
+ self.verticalLayout = QVBoxLayout()
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.deleteButton = QPushButton(ConsoleHistoryDialog)
+ self.deleteButton.setObjectName(u"deleteButton")
+ self.deleteButton.setEnabled(False)
+
+ self.verticalLayout.addWidget(self.deleteButton)
+
+ self.copyButton = QPushButton(ConsoleHistoryDialog)
+ self.copyButton.setObjectName(u"copyButton")
+ self.copyButton.setEnabled(False)
+
+ self.verticalLayout.addWidget(self.copyButton)
+
+ self.executeButton = QPushButton(ConsoleHistoryDialog)
+ self.executeButton.setObjectName(u"executeButton")
+ self.executeButton.setEnabled(False)
+
+ self.verticalLayout.addWidget(self.executeButton)
+
+ self.reloadButton = QPushButton(ConsoleHistoryDialog)
+ self.reloadButton.setObjectName(u"reloadButton")
+
+ self.verticalLayout.addWidget(self.reloadButton)
+
+ self.verticalSpacer = QSpacerItem(
+ 72, 208, QSizePolicy.Minimum, QSizePolicy.Expanding)
+
+ self.verticalLayout.addItem(self.verticalSpacer)
+
+ self.gridLayout.addLayout(self.verticalLayout, 0, 1, 1, 1)
+
+ self.buttonBox = QDialogButtonBox(ConsoleHistoryDialog)
+ self.buttonBox.setObjectName(u"buttonBox")
+ self.buttonBox.setStandardButtons(
+ QDialogButtonBox.Cancel | QDialogButtonBox.Ok)
+
+ self.gridLayout.addWidget(self.buttonBox, 1, 0, 1, 2)
+
+ QWidget.setTabOrder(self.historyList, self.deleteButton)
+ QWidget.setTabOrder(self.deleteButton, self.copyButton)
+ QWidget.setTabOrder(self.copyButton, self.executeButton)
+ QWidget.setTabOrder(self.executeButton, self.reloadButton)
+ QWidget.setTabOrder(self.reloadButton, self.buttonBox)
+
+ self.retranslateUi(ConsoleHistoryDialog)
+ self.buttonBox.accepted.connect(ConsoleHistoryDialog.accept)
+ self.buttonBox.rejected.connect(ConsoleHistoryDialog.reject)
+
+ QMetaObject.connectSlotsByName(ConsoleHistoryDialog)
+ # setupUi
+
+ def retranslateUi(self, ConsoleHistoryDialog):
+ ConsoleHistoryDialog.setWindowTitle(QCoreApplication.translate(
+ "ConsoleHistoryDialog", u"Shell History", None))
+# if QT_CONFIG(tooltip)
+ self.deleteButton.setToolTip(QCoreApplication.translate(
+ "ConsoleHistoryDialog", u"Delete the selected entries", None))
+#endif // QT_CONFIG(tooltip)
+ self.deleteButton.setText(QCoreApplication.translate(
+ "ConsoleHistoryDialog", u"&Delete", None))
+# if QT_CONFIG(tooltip)
+ self.copyButton.setToolTip(QCoreApplication.translate(
+ "ConsoleHistoryDialog", u"Copy the selected entries to the current editor", None))
+#endif // QT_CONFIG(tooltip)
+ self.copyButton.setText(QCoreApplication.translate(
+ "ConsoleHistoryDialog", u"C&opy", None))
+# if QT_CONFIG(tooltip)
+ self.executeButton.setToolTip(QCoreApplication.translate(
+ "ConsoleHistoryDialog", u"Execute the selected entries", None))
+#endif // QT_CONFIG(tooltip)
+ self.executeButton.setText(QCoreApplication.translate(
+ "ConsoleHistoryDialog", u"&Execute", None))
+# if QT_CONFIG(tooltip)
+ self.reloadButton.setToolTip(QCoreApplication.translate(
+ "ConsoleHistoryDialog", u"Reload the history", None))
+#endif // QT_CONFIG(tooltip)
+ self.reloadButton.setText(QCoreApplication.translate(
+ "ConsoleHistoryDialog", u"&Reload", None))
+ # retranslateUi
diff --git a/pyminer/widgets/widgets/basic/others/__init__.py b/pyminer/widgets/widgets/basic/others/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..a9a35b6c8bfc24a47c29d9e469dae7a991acbdd1
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/others/__init__.py
@@ -0,0 +1,16 @@
+import typing
+from .gauge import *
+from .processconsole import PMGProcessConsoleWidget
+from .instantbootconsole import PMGInstantBootConsoleWidget
+if typing.TYPE_CHECKING:
+ import widgets.widgets.basic.others.console as console
+
+
+def import_console_class() -> typing.Type['console.PMGIpythonConsole']:
+ """
+ 导入这一步是耗时操作。为了节约widgets导入的时间,就使用了这种方式动态导入类。
+ Returns:
+
+ """
+ import widgets.widgets.basic.others.console as console
+ return console.PMGIpythonConsole
diff --git a/pyminer/pmgwidgets/widgets/basic/others/console.py b/pyminer/widgets/widgets/basic/others/console.py
similarity index 99%
rename from pyminer/pmgwidgets/widgets/basic/others/console.py
rename to pyminer/widgets/widgets/basic/others/console.py
index 15d074ac454cf8a97b918f1ea9ab5f697296a836..aeb3a18e9428d8e43032309e2aa32d1e6228a1aa 100644
--- a/pyminer/pmgwidgets/widgets/basic/others/console.py
+++ b/pyminer/widgets/widgets/basic/others/console.py
@@ -23,7 +23,7 @@ from qtconsole.manager import QtKernelManager
from qtconsole.rich_jupyter_widget import RichJupyterWidget
from qtconsole.styles import default_light_syntax_style, default_light_style_sheet
-from features.io import settings
+from lib.io import settings
default_dark_style_template = styles.default_template + """\
.in-prompt { color: #ff00ff; }
@@ -229,7 +229,7 @@ class PMGIpythonConsole(RichJupyterWidget):
自定义控制台开始的文字
Returns:
"""
- return 'Welcome To PMGWidgets Ipython Console!\n'
+ return 'Welcome To widgets Ipython Console!\n'
def closeEvent(self, event):
self.history = self.history_tail(0)
diff --git a/pyminer/widgets/widgets/basic/others/gauge.py b/pyminer/widgets/widgets/basic/others/gauge.py
new file mode 100644
index 0000000000000000000000000000000000000000..9554373233c95f2a5b53c872e8d40c0912674e4a
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/others/gauge.py
@@ -0,0 +1,223 @@
+import random
+import sys
+import math
+from typing import Tuple, Union, List
+
+from PySide2.QtCore import QTimer, Qt, QRectF, QPoint
+from PySide2.QtGui import QPainter, QLinearGradient, QFontMetricsF, QPen, QFontMetrics, QRadialGradient, QConicalGradient, \
+ QColor, QPolygon
+from PySide2.QtWidgets import QWidget, QLCDNumber, QApplication
+
+
+class PMGGauge(QWidget):
+ def __init__(self, val_range: Union[Tuple[Union[float, int], Union[float, int]],
+ List[Union[float, int]]] = None,
+ initial_value: float = None, title: str = 'Untitled',
+ warning_rate: float = 0.8):
+ super().__init__()
+ self._brush_color = Qt.black
+ if val_range is None:
+ val_range = (0, 100)
+ assert val_range[0] < val_range[1]
+ if initial_value is None:
+ initial_value = val_range[0]
+
+ self._warning_rate = warning_rate
+ self.setWindowTitle("GaugePanel")
+ self.setMinimumWidth(200)
+ self.setMinimumHeight(200)
+
+ self.lcd_display = QLCDNumber(self)
+ self.lcd_display.setDigitCount(4)
+ self.lcd_display.setMode(QLCDNumber.Dec)
+ self.lcd_display.setSegmentStyle(QLCDNumber.Flat)
+ self.lcd_display.setStyleSheet('border:2px solid green;color:green;background:silver')
+
+ self._start_angle = 120 # 以QPainter坐标方向为准,建议画个草图看看
+ self._end_angle = 60 # 以以QPainter坐标方向为准
+ self._scale_main_num = 10 # 主刻度数
+ self._scaleSubNum = 10 # 主刻度被分割份数
+ self._min_value = val_range[0]
+ self._max_value = val_range[1]
+ self._title = title
+ self._value = initial_value
+ self._min_radio = 1 # 缩小比例,用于计算刻度数字
+ self._decimals = 0 # 小数位数
+
+ def alert(self, alert_level: int):
+ """
+ 1:严重警报
+ 2:一般警报
+ 其他值为正常
+ :param alert_level:
+ :return:
+ """
+ if alert_level == 2:
+ self._brush_color = QColor(200, 100, 40)
+ elif alert_level == 1:
+ self._brush_color = QColor(220, 50, 30)
+ else:
+ self._brush_color = Qt.black
+ self.update()
+
+ def set_warning_rate(self, warning_rate: float):
+ assert 0 < warning_rate < 1
+ self._warning_rate = warning_rate
+
+ def set_min_max_value(self, min, max):
+ self._min_value = min
+ self._max_value = max
+
+ def set_title(self, title):
+ self._title = title
+
+ def set_value(self, value):
+ assert self._min_value <= value <= self._max_value, \
+ f'Value %s is not in range [{self._min_value},{self._max_value}]' % (repr(value))
+ self._value = value
+ self.update()
+
+ def set_min_radio(self, min_radio):
+ self._min_radio = min_radio
+
+ def set_decimals(self, decimals):
+ self._decimals = decimals
+
+ def paintEvent(self, event):
+ side = min(self.width(), self.height())
+
+ painter = QPainter(self)
+ painter.setRenderHint(QPainter.Antialiasing)
+ painter.translate(self.width() / 2, self.height() / 2) # painter坐标系原点移至widget中央
+ painter.scale(side / 200, side / 200) # 缩放painterwidget坐标系,使绘制的时钟位于widge中央,即钟表支持缩放
+
+ self.draw_panel(painter) # 画外框表盘
+ self.draw_scale_num(painter) # 画刻度数字
+ self.draw_scale_line(painter) # 画刻度线
+ self.drawTitle(painter) # 画标题备注
+ self.drawValue(painter) # 画数显
+ self.drawIndicator(painter) # 画指针
+
+ def draw_panel(self, p):
+ p.save()
+ radius = 100
+ lg = QLinearGradient(-radius, -radius, radius, radius)
+ lg.setColorAt(0, Qt.white)
+ lg.setColorAt(1, Qt.black)
+ p.setBrush(lg)
+ p.setPen(Qt.NoPen)
+ p.drawEllipse(-radius, -radius, radius * 2, radius * 2)
+
+ p.setBrush(self._brush_color)
+ p.drawEllipse(-92, -92, 92 * 2, 92 * 2)
+ p.restore()
+
+ def draw_scale_num(self, p):
+ sin = math.sin
+ cos = math.cos
+ p.save()
+ p.setPen(Qt.white)
+ start_rad = self._start_angle * (3.14 / 180)
+ step_rad = (360 - (self._start_angle - self._end_angle)) * (3.14 / 180) / self._scale_main_num
+
+ fm = QFontMetricsF(p.font())
+ for i in range(0, self._scale_main_num + 1):
+ sina = sin(start_rad + i * step_rad)
+ cosa = cos(start_rad + i * step_rad)
+
+ tmp_val = i * ((self._max_value - self._min_value) / self._scale_main_num) + self._min_value
+ tmp_val = tmp_val / self._min_radio
+ s = '{:.0f}'.format(tmp_val)
+ w = fm.size(Qt.TextSingleLine, s).width()
+ h = fm.size(Qt.TextSingleLine, s).height()
+ x = 80 * cosa - w / 2
+ y = 80 * sina - h / 2
+ p.drawText(QRectF(x, y, w, h), s)
+
+ p.restore()
+
+ def draw_scale_line(self, p):
+ p.save()
+ p.rotate(self._start_angle)
+ scale_nums = self._scale_main_num * self._scaleSubNum
+ angle_step = (360 - (self._start_angle - self._end_angle)) / scale_nums
+ p.setPen(Qt.white)
+
+ pen = QPen(Qt.white)
+ for i in range(0, scale_nums + 1):
+ if i >= self._warning_rate * scale_nums:
+ pen.setColor(Qt.red)
+
+ if i % self._scale_main_num == 0:
+ pen.setWidth(2)
+ p.setPen(pen)
+ p.drawLine(64, 0, 72, 0)
+ else:
+ pen.setWidth(1)
+ p.setPen(pen)
+ p.drawLine(67, 0, 72, 0)
+ p.rotate(angle_step)
+
+ p.restore()
+
+ def drawTitle(self, p):
+ p.save()
+ p.setPen(Qt.white)
+ fm = QFontMetrics(p.font())
+ w = fm.size(Qt.TextSingleLine, self._title).width()
+ p.drawText(int(-w / 2), -45, self._title)
+ p.restore()
+
+ def drawValue(self, p):
+ side = min(self.width(), self.height())
+ w, h = int(side / 2 * 0.4), int(side / 2 * 0.2)
+ x, y = int(self.width() / 2 - w / 2), int(self.height() / 2 + side / 2 * 0.55)
+ self.lcd_display.setGeometry(x, y, w, h)
+
+ ss = '{:.' + str(self._decimals) + 'f}'
+ self.lcd_display.display(ss.format(self._value))
+
+ def drawIndicator(self, p):
+ p.save()
+ polygon = QPolygon([QPoint(0, -2), QPoint(0, 2), QPoint(60, 0)])
+ deg_rotate = self._start_angle + (360 - (self._start_angle - self._end_angle)) / (
+ self._max_value - self._min_value) * (self._value - self._min_value)
+ # 画指针
+ p.rotate(deg_rotate)
+ halogd = QRadialGradient(0, 0, 60, 0, 0)
+ halogd.setColorAt(0, QColor(60, 60, 60))
+ halogd.setColorAt(1, QColor(160, 160, 160))
+ p.setPen(Qt.white)
+ p.setBrush(halogd)
+ p.drawConvexPolygon(polygon)
+ p.restore()
+
+ # 画中心点
+ p.save()
+ rad_gradient = QRadialGradient(0, 0, 10)
+ rad_gradient = QConicalGradient(0, 0, -90)
+ rad_gradient.setColorAt(0.0, Qt.darkGray)
+ rad_gradient.setColorAt(0.5, Qt.white)
+ rad_gradient.setColorAt(1.0, Qt.darkGray)
+ p.setPen(Qt.NoPen)
+ p.setBrush(rad_gradient)
+ p.drawEllipse(-5, -5, 10, 10)
+ p.restore()
+
+
+if __name__ == "__main__":
+ import cgitb
+
+ cgitb.enable()
+ app = QApplication(sys.argv)
+ gp = PMGGauge(val_range=(0, 100), initial_value=0, title='CPU占用%', warning_rate=0.75)
+ gp.show()
+
+ timer = QTimer()
+ timer.start(100)
+ timer.timeout.connect(lambda: gp.set_value(random.randint(0, 100)))
+
+ timer2 = QTimer()
+ timer2.start(1000)
+ timer2.timeout.connect(lambda: gp.alert(random.randint(0, 2)))
+ app.exec_()
diff --git a/pyminer/widgets/widgets/basic/others/instantbootconsole.py b/pyminer/widgets/widgets/basic/others/instantbootconsole.py
new file mode 100644
index 0000000000000000000000000000000000000000..920c8319bb59c7c9523f3380a2bcc342e9b5dc0b
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/others/instantbootconsole.py
@@ -0,0 +1,287 @@
+"""
+作者:侯展意
+有关QThread为什么行,为什么不行
+我也不知道啊...
+"""
+import os
+import time
+import sys
+import logging
+import socket
+
+from PySide2.QtCore import QThread, QObject, Signal, Qt
+from PySide2.QtGui import QCloseEvent, QKeyEvent, QIcon
+from PySide2.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QPushButton, QCheckBox, QTextEdit
+
+logger = logging.getLogger(__name__)
+
+from widgets.utilities.platform.openprocess import PMGProcess
+
+def connect():
+ address = ('127.0.0.1', 37789) # 服务端地址和端口
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ try:
+ s.connect(address) # 尝试连接服务端
+ except Exception:
+ print('[!] Server not found ot not open')
+class ProcessMonitorThread(QObject):
+ on_err = Signal(str)
+ on_out = Signal(str)
+ on_finished = Signal()
+
+ def __init__(self):
+ super().__init__()
+ self.process_terminated = False
+ self.args = None
+
+ def stop(self):
+ self.process.terminate = True
+ self.process_terminated = True
+
+ def run(self):
+ self.process = PMGProcess(self.args)
+ self.q = self.process.q
+ idleLoops = 0
+ while True:
+ if self.process_terminated == True:
+ return
+ if not self.q.empty():
+ line = self.q.get()
+ if line[0] == '1':
+ self.on_out.emit(line[1:])
+ else:
+ self.on_err.emit(line[1:])
+ sys.stdout.flush()
+ else:
+ time.sleep(0.01)
+ if idleLoops >= 5:
+ idleLoops = 0
+ if self.process.process.poll() is None:
+ pass
+ else:
+ self.on_finished.emit()
+ return
+ continue
+ idleLoops += 1
+
+
+class ProcessConsole(QTextEdit):
+ signal_stop_qthread = Signal()
+ signal_process_stopped = Signal()
+ signal_process_started = Signal()
+
+ insert_mode = ''
+
+ def __init__(self, args: list = None):
+ super().__init__()
+ self._is_running = False
+ self.auto_scroll = True
+ self.args = args #
+ self.setContentsMargins(20, 20, 0, 0)
+ self.monitor_thread: 'ProcessMonitorThread' = None
+ self.out_thread: 'QThread' = None
+
+ def set_args(self, args: list = None):
+ self.args = args
+
+ def is_running(self) -> bool:
+ """
+ 返回进程是否在运行
+ Returns:
+
+ """
+ if self.monitor_thread is not None:
+ process = self.get_subprocess()
+ if process is not None:
+ if process.poll() is None:
+ return True
+ return False
+
+ def start_process(self):
+ if not self.is_running():
+ self.out_thread = QThread(self)
+ self.monitor_thread = ProcessMonitorThread()
+ self.monitor_thread.args = self.args
+ self.monitor_thread.moveToThread(self.out_thread)
+
+ self.out_thread.started.connect(self.monitor_thread.run)
+ self.out_thread.start()
+
+ self.monitor_thread.on_out.connect(self.on_stdout)
+ self.monitor_thread.on_err.connect(self.on_stderr)
+
+ self.signal_stop_qthread.connect(self.monitor_thread.stop)
+
+ self.out_thread.finished.connect(self.out_thread.deleteLater)
+ self.out_thread.finished.connect(self.monitor_thread.deleteLater)
+
+ self.monitor_thread.on_finished.connect(self.terminate_process)
+
+ def on_stdout(self, text):
+ if self.insert_mode == 'error':
+ self.insertHtml('' + '========' + '
')
+ self.insert_mode = 'stdout'
+
+ self.insertPlainText(text)
+ if self.auto_scroll:
+ self.ensureCursorVisible()
+
+ def on_stderr(self, text):
+ self.insert_mode = 'error'
+ self.insertHtml('' + text + '
')
+ print(text)
+ if self.auto_scroll:
+ self.ensureCursorVisible()
+
+ def terminate_process(self):
+ if self.monitor_thread is not None:
+ self.monitor_thread.process_terminated = True
+ self.monitor_thread.process.process.terminate()
+ if self.out_thread.isRunning():
+ self.signal_stop_qthread.emit()
+ self.out_thread.quit()
+ self.out_thread.wait(500)
+ self.monitor_thread = None
+ self.out_thread = None
+ self.signal_process_stopped.emit()
+
+ def keyPressEvent(self, e: 'QKeyEvent'):
+ if e.key() == Qt.Key_Backspace or e.key() == Qt.Key_Delete:
+ return
+ print(e.key(), e.text())
+ if e.key() == Qt.Key_Return:
+ text = '\n'
+ else:
+ text = e.text()
+ if text != '' and self.monitor_thread is not None:
+ try:
+ print('sent:', text)
+ self.monitor_thread.process.process.stdin.write(text.encode('utf8'))
+ self.monitor_thread.process.process.stdin.flush()
+ except:
+ import traceback
+ traceback.print_exc()
+ super(ProcessConsole, self).keyPressEvent(e)
+
+ def get_subprocess(self):
+ """
+ 返回子进程
+ Returns:
+
+ """
+ if hasattr(self.monitor_thread, 'process'):
+ proc = self.monitor_thread.process
+ if hasattr(proc, 'process'):
+ return proc.process
+ return None
+
+
+class PMGInstantBootConsoleWidget(QWidget):
+ def __init__(self, file: str):
+ super().__init__()
+ self.hbox_layout = QHBoxLayout()
+ self.tool_widget = QWidget()
+ self.tool_widget.setLayout(QVBoxLayout())
+ script_path = os.path.join(os.path.dirname(__file__), 'scripts', 'instant_boot.py')
+ assert os.path.exists(script_path)
+ self.process_console = ProcessConsole(args=[sys.executable, '-u', script_path, file])
+ self.process_console.signal_process_stopped.connect(self.on_terminated)
+
+ self.button_to_start = QPushButton()
+ self.button_to_start.setToolTip(self.tr('Start'))
+ icon_path = os.path.dirname(__file__)
+ self.button_to_start.setIcon(QIcon(os.path.join(icon_path, 'source', 'run.png')))
+ self.tool_widget.layout().addWidget(self.button_to_start)
+ self.button_to_start.clicked.connect(self.start_process)
+
+ self.button_to_terminate = QPushButton()
+ self.button_to_terminate.setToolTip(self.tr('Terminate'))
+ self.button_to_terminate.setIcon(QIcon(os.path.join(icon_path, 'source', 'stop.png')))
+ self.tool_widget.layout().addWidget(self.button_to_terminate)
+ self.button_to_terminate.clicked.connect(self.terminate_and_restart_process)
+
+ self.button_to_clear = QPushButton()
+ self.button_to_clear.setToolTip(self.tr('Clear'))
+ self.tool_widget.layout().addWidget(self.button_to_clear)
+ self.button_to_clear.setIcon(QIcon(os.path.join(icon_path, 'source', 'clear.png')))
+ self.button_to_clear.clicked.connect(lambda: self.process_console.clear())
+
+ self.button_to_clear.setMaximumWidth(20)
+ self.button_to_start.setMaximumWidth(20)
+ self.button_to_terminate.setMaximumWidth(20)
+ self.autoscroll_checker = QCheckBox()
+ self.autoscroll_checker.setToolTip('autoscroll')
+
+ self.tool_widget.layout().addWidget(self.autoscroll_checker)
+ self.autoscroll_checker.stateChanged.connect(self.set_autoscroll)
+ self.autoscroll_checker.setChecked(True)
+ self.set_autoscroll()
+
+ self.hbox_layout.addWidget(self.tool_widget)
+ vbox = QVBoxLayout()
+ self.hbox_layout.addLayout(vbox)
+ vbox.addWidget(self.process_console)
+ # self.command_input = QLineEdit()
+ # vbox.addWidget()
+ self.setLayout(self.hbox_layout)
+
+ self.process_console.start_process()
+
+ def set_autoscroll(self):
+ print(self.autoscroll_checker.isChecked())
+ self.process_console.auto_scroll = self.autoscroll_checker.isChecked()
+
+ def on_terminated(self, close=False):
+ if not close:
+ self.process_console.start_process()
+ self.button_to_start.setEnabled(True)
+ self.button_to_terminate.setEnabled(False)
+
+ def on_started(self):
+ self.button_to_start.setEnabled(False)
+ self.button_to_terminate.setEnabled(True)
+
+ def start_process(self):
+ print('start this console!')
+ self.process_console.monitor_thread.process.process.stdin.write(b'run!\n\n')
+ self.process_console.monitor_thread.process.process.stdin.flush()
+ self.on_started()
+
+ def terminate_and_restart_process(self):
+ self.terminate_process()
+ self.on_terminated()
+
+ def terminate_process(self):
+ self.process_console.terminate_process()
+
+ def is_process_running(self) -> bool:
+ return self.process_console.is_running()
+
+ # def disconnect_events(self):
+
+
+ def closeEvent(self, a0: QCloseEvent) -> None:
+
+ if self.process_console.is_running():
+ self.process_console.signal_process_stopped.disconnect(self.on_terminated)
+ self.terminate_process()
+ super(PMGInstantBootConsoleWidget, self).closeEvent(a0)
+
+ def set_args(self, args: list):
+ self.process_console.set_args(args)
+
+
+if __name__ == '__main__':
+ import cgitb
+ import sys
+
+ cgitb.enable(format='text')
+ from PySide2.QtWidgets import QApplication, QTextEdit
+
+ app = QApplication(sys.argv)
+
+ w = PMGInstantBootConsoleWidget(r'C:\Users\12957\Documents\Developing\Python\PyMiner_dev_kit\bin'
+ r'\widgets\widgets\basic\others\scripts\test1_instant_boot.py')
+ w.show()
+
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/widgets/basic/others/processconsole.py b/pyminer/widgets/widgets/basic/others/processconsole.py
new file mode 100644
index 0000000000000000000000000000000000000000..17282f2fc21c671c08165c077031bd89b8aa5488
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/others/processconsole.py
@@ -0,0 +1,319 @@
+"""
+作者:侯展意
+有关QThread为什么行,为什么不行
+我也不知道啊...
+"""
+import os
+import re
+import time
+import sys
+import logging
+
+from PySide2.QtCore import QThread, QObject, Signal, Qt, QUrl
+from PySide2.QtGui import QCloseEvent, QKeyEvent, QIcon, QDesktopServices, QColor
+from PySide2.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QPushButton, QCheckBox, QTextEdit, QApplication, \
+ QMessageBox
+
+logger = logging.getLogger(__name__)
+
+from widgets.utilities.platform.openprocess import PMGProcess
+
+
+class ProcessMonitorThread(QObject):
+ on_err = Signal(str)
+ on_out = Signal(str)
+ on_finished = Signal()
+
+ def __init__(self):
+ super().__init__()
+ self.process_terminated = False
+ self.args = None
+
+ def stop(self):
+ self.process.terminate = True
+ self.process_terminated = True
+
+ def run(self):
+ try:
+ self.process = PMGProcess(self.args)
+ self.q = self.process.q
+ idleLoops = 0
+ while True:
+ if self.process_terminated == True:
+ return
+ if not self.q.empty():
+ line = self.q.get()
+ if line[0] == '1':
+ self.on_out.emit(line[1:])
+ else:
+ self.on_err.emit(line[1:])
+ sys.stdout.flush()
+ else:
+ time.sleep(0.01)
+ if idleLoops >= 5:
+ idleLoops = 0
+ if self.process.process.poll() is None:
+ pass
+ else:
+ self.on_finished.emit()
+ return
+ continue
+ idleLoops += 1
+ except:
+ import traceback
+ traceback.print_exc()
+
+
+class ProcessConsole(QTextEdit):
+ signal_stop_qthread = Signal()
+ signal_process_stopped = Signal()
+ signal_process_started = Signal()
+ signal_hyperlink_clicked = Signal(str)
+ signal_goto_file = Signal(str, int)
+ insert_mode = ''
+
+ def __init__(self, args: list = None):
+ super().__init__()
+ self.anchor = None
+ self._is_running = False
+ self.auto_scroll = True
+ self.args = args #
+ self.setContentsMargins(20, 20, 0, 0)
+ self.monitor_thread: 'ProcessMonitorThread' = None
+ self.out_thread: 'QThread' = None
+ self.signal_hyperlink_clicked.connect(self.on_hyperlink_clicked)
+
+ def set_args(self, args: list = None):
+ self.args = args
+
+ def is_running(self):
+ if self.monitor_thread is not None:
+ if self.monitor_thread.process.process.poll() is None:
+ return True
+ return False
+
+ def start_process(self):
+ if not self.is_running():
+ self.out_thread = QThread(self)
+ self.monitor_thread = ProcessMonitorThread()
+ self.monitor_thread.args = self.args
+ self.monitor_thread.moveToThread(self.out_thread)
+
+ self.out_thread.started.connect(self.monitor_thread.run)
+ self.out_thread.start()
+
+ self.monitor_thread.on_out.connect(self.on_stdout)
+ self.monitor_thread.on_err.connect(self.on_stderr)
+
+ self.signal_stop_qthread.connect(self.monitor_thread.stop)
+
+ self.out_thread.finished.connect(self.out_thread.deleteLater)
+ self.out_thread.finished.connect(self.monitor_thread.deleteLater)
+
+ self.monitor_thread.on_finished.connect(self.terminate_process)
+ self.clear()
+ self.insertHtml('' + ' '.join(self.args) + '
')
+ self.insertHtml('' + '' + '
')
+
+ def on_stdout(self, text):
+
+ if self.insert_mode == 'error':
+ self.insert_mode = 'stdout'
+
+ self.insertHtml(self.insertHtml('' + text + '
'))
+ if self.auto_scroll:
+ self.ensureCursorVisible()
+
+ def on_stderr(self, text):
+ self.insert_mode = 'error'
+ # result = re.search(r'(/|([a-zA-Z]:((\\)|/))).*:[0-9].*', text)
+ result = re.search(r'(/|([a-zA-Z]:((\\)|/))).* line [0-9].*,', text)
+ print(result)
+ if result is None:
+ self.insertHtml('' + text + '
')
+ else:
+ span = result.span()
+ self.insertHtml('' + text[:span[0]] + '
')
+ print(result.group())
+ self.insert_hyperlink(text[span[0]: span[1]])
+ # self.insertHtml('' + text[result[0]:result[1]] + '
')
+ self.insertHtml('' + text[span[1]:] + '
')
+ if self.auto_scroll:
+ self.ensureCursorVisible()
+
+ def terminate_process(self):
+ if self.monitor_thread is not None:
+ self.monitor_thread.process_terminated = True
+ self.monitor_thread.process.process.terminate()
+ if self.out_thread.isRunning():
+ self.signal_stop_qthread.emit()
+ self.out_thread.quit()
+ self.out_thread.wait(500)
+ self.monitor_thread = None
+ self.out_thread = None
+ self.signal_process_stopped.emit()
+
+ def keyPressEvent(self, e: 'QKeyEvent'):
+ if e.key() == Qt.Key_Backspace or e.key() == Qt.Key_Delete:
+ return
+ print(e.key(), e.text())
+ if e.key() == Qt.Key_Return:
+ text = '\n'
+ else:
+ text = e.text()
+ if text != '' and self.monitor_thread is not None:
+ try:
+ print('sent:', text)
+ self.monitor_thread.process.process.stdin.write(text.encode('utf8'))
+ self.monitor_thread.process.process.stdin.flush()
+ except:
+ import traceback
+ traceback.print_exc()
+ super(ProcessConsole, self).keyPressEvent(e)
+
+ def mousePressEvent(self, e):
+ super(ProcessConsole, self).mousePressEvent(e)
+ self.anchor = self.anchorAt(e.pos())
+ # if self.anchor:
+ # QApplication.setOverrideCursor(Qt.PointingHandCursor)
+
+ def mouseMoveEvent(self, e):
+ super(ProcessConsole, self).mouseMoveEvent(e)
+ self.anchor = self.anchorAt(e.pos())
+ if not self.anchor:
+ QApplication.setOverrideCursor(Qt.ArrowCursor)
+ else:
+ QApplication.setOverrideCursor(Qt.PointingHandCursor)
+
+ def mouseReleaseEvent(self, e):
+ super(ProcessConsole, self).mouseReleaseEvent(e)
+ if self.anchor:
+ # QDesktopServices.openUrl(QUrl(self.anchor))
+ # QApplication.setOverrideCursor(Qt.ArrowCursor)
+ self.signal_hyperlink_clicked.emit(self.anchor)
+ self.anchor = None
+
+ def insert_hyperlink(self, link: str, text=''):
+ cursor = self.textCursor()
+ fmt = cursor.charFormat()
+ fmt.setForeground(QColor('#0063c5'))
+ # address = 'http://example.com'
+ fmt.setAnchor(True)
+ fmt.setAnchorHref(link)
+ fmt.setToolTip(link)
+ if text == '':
+ cursor.insertText(link, fmt)
+ else:
+ cursor.insertText(text, fmt)
+
+ def on_hyperlink_clicked(self, hyperlink_text: str):
+ if re.search(r'(/|([a-zA-Z]:((\\)|/))).* line [0-9].*,', hyperlink_text) is not None:
+ try:
+ l = hyperlink_text.split('line')
+ assert len(l) == 2, l
+ for i in [0, 1]:
+ l[i] = l[i].strip(', \"\'')
+ file_path, row_str = l
+ row = int(row_str)
+ if not os.path.exists(file_path):
+ QMessageBox.warning(self, self.tr('Warning'), self.tr('文件%s不存在!' % file_path))
+ else:
+ self.signal_goto_file.emit(file_path, row)
+ logger.info('goto file %s, line %d' % (file_path, row))
+ except:
+ import traceback
+ traceback.print_exc()
+
+
+class PMGProcessConsoleWidget(QWidget):
+ signal_goto_file = Signal(str, int)
+
+ def __init__(self, args: list = None):
+ super().__init__()
+ self.hbox_layout = QHBoxLayout()
+ self.tool_widget = QWidget()
+ self.tool_widget.setLayout(QVBoxLayout())
+ self.process_console = ProcessConsole(args=args)
+ self.process_console.signal_goto_file.connect(self.signal_goto_file.emit)
+ self.process_console.signal_process_stopped.connect(self.on_terminated)
+
+ self.button_to_start = QPushButton()
+ self.button_to_start.setToolTip(self.tr('Start'))
+ icon_path = os.path.dirname(__file__)
+ self.button_to_start.setIcon(QIcon(os.path.join(icon_path, 'source', 'run.png')))
+ self.tool_widget.layout().addWidget(self.button_to_start)
+ self.button_to_start.clicked.connect(self.start_process)
+
+ self.button_to_terminate = QPushButton()
+ self.button_to_terminate.setToolTip(self.tr('Terminate'))
+ self.button_to_terminate.setIcon(QIcon(os.path.join(icon_path, 'source', 'stop.png')))
+ self.tool_widget.layout().addWidget(self.button_to_terminate)
+ self.button_to_terminate.clicked.connect(self.terminate_process)
+
+ self.button_to_clear = QPushButton()
+ self.button_to_clear.setToolTip(self.tr('Clear'))
+ self.tool_widget.layout().addWidget(self.button_to_clear)
+ self.button_to_clear.setIcon(QIcon(os.path.join(icon_path, 'source', 'clear.png')))
+ self.button_to_clear.clicked.connect(lambda: self.process_console.clear())
+
+ self.button_to_clear.setMaximumWidth(20)
+ self.button_to_start.setMaximumWidth(20)
+ self.button_to_terminate.setMaximumWidth(20)
+ self.autoscroll_checker = QCheckBox()
+ self.autoscroll_checker.setToolTip('autoscroll')
+
+ self.tool_widget.layout().addWidget(self.autoscroll_checker)
+ self.autoscroll_checker.stateChanged.connect(self.set_autoscroll)
+ self.autoscroll_checker.setChecked(True)
+ self.set_autoscroll()
+
+ self.hbox_layout.addWidget(self.tool_widget)
+ vbox = QVBoxLayout()
+ self.hbox_layout.addLayout(vbox)
+ vbox.addWidget(self.process_console)
+ # self.command_input = QLineEdit()
+ # vbox.addWidget()
+ self.setLayout(self.hbox_layout)
+
+ def set_autoscroll(self):
+ self.process_console.auto_scroll = self.autoscroll_checker.isChecked()
+
+ def on_terminated(self):
+ self.button_to_start.setEnabled(True)
+ self.button_to_terminate.setEnabled(False)
+
+ def on_started(self):
+ self.button_to_start.setEnabled(False)
+ self.button_to_terminate.setEnabled(True)
+
+ def start_process(self):
+ self.process_console.start_process()
+ self.on_started()
+
+ def terminate_process(self):
+ self.process_console.terminate_process()
+ self.on_terminated()
+
+ def is_process_running(self) -> bool:
+ return self.process_console.is_running()
+
+ def closeEvent(self, a0: QCloseEvent) -> None:
+ self.terminate_process()
+ super(PMGProcessConsoleWidget, self).closeEvent(a0)
+
+ def set_args(self, args: list):
+ self.process_console.set_args(args)
+
+
+if __name__ == '__main__':
+ import cgitb
+ import sys
+
+ cgitb.enable(format='text')
+
+ app = QApplication(sys.argv)
+ w = PMGProcessConsoleWidget(
+ [sys.executable, '-u', os.path.join(os.path.dirname(__file__), 'scripts', 'test2_open_app.py')])
+ w.show()
+
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/widgets/basic/others/source/clear.png b/pyminer/widgets/widgets/basic/others/source/clear.png
new file mode 100644
index 0000000000000000000000000000000000000000..97d20a373b8529370418907572e8ed143f25ebaa
Binary files /dev/null and b/pyminer/widgets/widgets/basic/others/source/clear.png differ
diff --git a/pyminer/widgets/widgets/basic/others/source/run.png b/pyminer/widgets/widgets/basic/others/source/run.png
new file mode 100644
index 0000000000000000000000000000000000000000..045fd3387941d0f305715b9097bcd2736c40bf1d
Binary files /dev/null and b/pyminer/widgets/widgets/basic/others/source/run.png differ
diff --git a/pyminer/widgets/widgets/basic/others/source/stop.png b/pyminer/widgets/widgets/basic/others/source/stop.png
new file mode 100644
index 0000000000000000000000000000000000000000..c2b9b96aa79a7d512887576d60c8454e24d8ef71
Binary files /dev/null and b/pyminer/widgets/widgets/basic/others/source/stop.png differ
diff --git a/pyminer/widgets/widgets/basic/others/translations/qt_zh_CN.ts b/pyminer/widgets/widgets/basic/others/translations/qt_zh_CN.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2fbbd02abd71a5ea5b4919f8694d525aac122ba0
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/others/translations/qt_zh_CN.ts
@@ -0,0 +1,69 @@
+
+
+
+ PMGIpythonConsole
+
+
+ Cut
+ 剪切
+
+
+
+ Copy
+ 复制
+
+
+
+ Copy(Raw Text)
+ 复制(原始文本)
+
+
+
+ Paste
+ 粘贴
+
+
+
+ Select All
+ 全选
+
+
+
+ Save as HTML/XML
+ 以HTML/XML存储
+
+
+
+ Print
+ 打印
+
+
+
+ Restart
+ 重启
+
+
+
+ Interrupt
+ 中断当前执行
+
+
+
+ PMGProcessConsoleWidget
+
+
+ Start
+ 启动
+
+
+
+ Terminate
+ 停止
+
+
+
+ Clear
+ 清除
+
+
+
diff --git a/pyminer/widgets/widgets/basic/plots/__init__.py b/pyminer/widgets/widgets/basic/plots/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..ecbf1e9f40cbf485c3dbc0c19ed63c5c88ef0bdb
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/plots/__init__.py
@@ -0,0 +1,4 @@
+from .bars import *
+from .lines import *
+from .matplotlib import *
+from .pyqtgraph import *
diff --git a/pyminer/widgets/widgets/basic/plots/bars/__init__.py b/pyminer/widgets/widgets/basic/plots/bars/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/widgets/widgets/basic/plots/bars/histogram.py b/pyminer/widgets/widgets/basic/plots/bars/histogram.py
new file mode 100644
index 0000000000000000000000000000000000000000..780fd7aef767e203023e4b62f91be72c2bcca3ae
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/plots/bars/histogram.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+"""
+In this example we draw two different kinds of histogram.
+"""
+import numpy as np
+import pyqtgraph as pg
+import sys
+from PySide2.QtWidgets import QApplication
+from widgets.widgets.basic.plots import PMGPyQtGraphWidget
+from widgets import color_str2tup
+
+
+class PMGBarWidget(PMGPyQtGraphWidget):
+ def __init__(self, parent=None):
+ super().__init__(parent=parent)
+ self.plot_widget = pg.PlotWidget(self)
+ self.v_layout.addWidget(self.plot_widget)
+
+ def plot(self, vals):
+ y, x = np.histogram(vals, bins=np.linspace(-3, 8, 40))
+ self.plot_widget.plot(x, y, stepMode=True, fillLevel=0, fillOutline=True,
+ pen=pg.mkPen(self.border_color, width = self.border_width),
+ brush=color_str2tup(self.item_color))
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ demo = PMGBarWidget()
+ demo.show()
+ demo.border_width = 3
+ demo.border_color = '#ffff00'
+ demo.plot(np.random.normal(size=500))
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/widgets/basic/plots/lines/__init__.py b/pyminer/widgets/widgets/basic/plots/lines/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..dd494875191901cc014d14703636ed95b10ab487
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/plots/lines/__init__.py
@@ -0,0 +1,6 @@
+import warnings
+
+try:
+ from .timeseries import *
+except ImportError as e:
+ warnings.warn(str(e))
diff --git a/pyminer/widgets/widgets/basic/plots/lines/timeseries.py b/pyminer/widgets/widgets/basic/plots/lines/timeseries.py
new file mode 100644
index 0000000000000000000000000000000000000000..de4852f7fe09013309017015b789a6de2d61584e
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/plots/lines/timeseries.py
@@ -0,0 +1,193 @@
+import time
+from random import randint
+
+from PySide2.QtGui import QBrush, QColor
+from PySide2.QtWidgets import QHBoxLayout, QLabel, QSpacerItem, QSizePolicy
+from typing import List, Union, Tuple
+import pyqtgraph as pg
+from PySide2.QtWidgets import QVBoxLayout, QWidget, QSpinBox
+from widgets.widgets.basic.plots.pyqtgraph.base.pgplot import PMGPyQtGraphWidget
+from widgets import color_str2tup, TYPE_RANGE
+from widgets import iter_isinstance
+
+
+def convert_time(time_stamp: Union[float, int]):
+ return time.strftime('%H:%M:%S', time.localtime(time_stamp))
+
+
+class TimeSeriesPGWidget(PMGPyQtGraphWidget):
+ """
+ 性能很好的监视面板,300个点每分钟刷新一次,可以保持
+ """
+
+ def __init__(self, parent=None, face_color=None, text_color=None):
+ super().__init__(parent, text_color=text_color)
+ self.face_color = face_color
+ self.text_color = text_color
+ self.plot_widget = pg.PlotWidget(self, axisItems={'bottom': pg.DateAxisItem()}, background=self.face_color)
+
+ self.legend = self.plot_widget.addLegend(brush=QBrush(QColor(255, 255, 255)), offset=(-10, 10),
+ labelTextColor=self.text_color, labelTextSize=8)
+ self.v_layout.addWidget(self.plot_widget)
+ # self.setMinimumSize(300, 200)
+ # self.setMaximumSize(450, 300)
+ self.xlabel: str = ''
+ self.ylabel: str = ''
+ self.threshold_range: TYPE_RANGE = None
+ self.x_range: Tuple[float, float] = None
+ self.y_range: Tuple[float, float] = None
+ self.title: str = ''
+ self.ticks = 5
+ self.show_data_num = 10
+ self.max_data = 200
+ self.timestamp_list = []
+ self.value_list = []
+ self.repaint_all = False
+
+ def show_time_series(self, timestamps: List[float], values: List[List[float]], tags=None):
+ self.clear()
+ for value_list in values:
+ if value_list is not None:
+ assert len(timestamps) == len(value_list)
+ if tags is not None:
+ assert len(tags) == len(values)
+ else:
+ tags = [i + 1 for i in range(len(values))]
+ self.plot_widget.plotItem.setTitle(self.title)
+ self.plot(timestamps, values, tags)
+
+ def gen_threshold_line(self, x):
+ return [self.threshold_range[0] for i in x], [self.threshold_range[1] for i in x]
+
+ def get_threshold_line_num(self) -> int:
+ s = 0
+ if self.threshold_range is None:
+ return s
+ if self.threshold_range[0] is not None:
+ s += 1
+ if self.threshold_range[1] is not None:
+ s += 1
+ return s
+
+ def plot(self, x, ylist, tags: List = None):
+ """
+ 绘图方法
+ :param x:
+ :param ylist:
+ :param tags: 标签
+ :return:
+ """
+ if (len(ylist) + self.get_threshold_line_num() != len(self.lines) and self.threshold_range is not None) or \
+ (len(ylist) != len(self.lines) and self.threshold_range is None):
+
+ if tags is None:
+ tags = [None] * len(ylist)
+
+ for line in self.lines:
+ line.clear()
+ self.lines = []
+ if self.y_range is not None:
+ self.plot_widget.setYRange(*self.y_range)
+ self.legend.clear()
+ # if self.threshold_range
+ if self.threshold_range is not None:
+ threshold_lower, threshold_upper = self.gen_threshold_line(x)
+ if self.threshold_range[0] is not None:
+ l1 = self.plot_widget.plot(x, threshold_lower, pen='#ff0000')
+ self.lines.append(l1)
+ if self.threshold_range[1] is not None:
+ l2 = self.plot_widget.plot(x, threshold_upper, pen='#ff0000')
+ self.lines.append(l2)
+ assert len(self._symbols) >= len(ylist), 'Too much lines for monitor!'
+ for i, y in enumerate(ylist):
+ plot_data = self.plot_widget.plot(x, y, **self._symbols[i], name=tags[i])
+ self.lines.append(plot_data)
+ self.legend.setBrush(QColor(*color_str2tup(self.legend_face_color), 100))
+ # TODO add opacity settings choices!
+ else:
+ if self.threshold_range is not None:
+
+ threshold_lower, threshold_upper = self.gen_threshold_line(x)
+ if self.threshold_range is not None:
+ threshold_lower, threshold_upper = self.gen_threshold_line(x)
+ if self.threshold_range[0] is not None and self.threshold_range[1] is not None:
+ self.lines[0].setData(x, threshold_lower)
+ self.lines[1].setData(x, threshold_upper)
+ else:
+ if self.threshold_range[1] is not None:
+ self.lines[0].setData(x, threshold_upper, pen='#ff0000')
+
+ if self.threshold_range[0] is not None:
+ self.lines[0].setData(x, threshold_lower, pen='#ff0000')
+
+ for i, y in enumerate(ylist):
+ self.lines[i + self.get_threshold_line_num()].setData(x, y)
+ else:
+ for i, y in enumerate(ylist):
+ self.lines[i].setData(x, y)
+
+
+class PMGTimeSeriesPlot(QWidget):
+ def __init__(self, parent: QWidget = None,
+ threshold_range: TYPE_RANGE = None, face_color=None, text_color=None):
+ super(PMGTimeSeriesPlot, self).__init__(parent)
+
+ self.threshold_range = threshold_range
+ layout = QVBoxLayout()
+ self.setLayout(layout)
+ layout.setContentsMargins(0, 0, 0, 0)
+ self.control_layout = QHBoxLayout()
+ self.control_layout.setContentsMargins(0, 0, 0, 0)
+
+ self.time_series = TimeSeriesPGWidget(face_color=face_color, text_color=text_color)
+ self.layout().addWidget(self.time_series)
+ self.layout().addLayout(self.control_layout)
+
+ def set_data(self, timestamps: List[float], values: List[List[float]], tags: List[str] = None):
+ self.time_series.show_time_series(timestamps, values, tags)
+
+ def insert_data(self, timestamp: float, values: List[float], tags: List[float]):
+ raise NotImplementedError
+ self.time_series.insert_data(timestamp, values)
+
+ def config_chart_text(self, title: str, xlabel: str, ylabel: str):
+ self.time_series.title = title
+ self.time_series.xlabel = xlabel
+ self.time_series.ylabel = ylabel
+
+ def alert(self, alert_level: int):
+ if alert_level == 1:
+ self.time_series.face_color = '#dc321e'
+ elif alert_level == 2:
+ self.time_series.face_color = '#c86428'
+ else:
+ self.time_series.face_color = '#ffffff'
+
+
+if __name__ == '__main__':
+ import sys
+ from PySide2.QtWidgets import QApplication
+ from PySide2.QtCore import QTimer
+
+ app = QApplication(sys.argv)
+ pmqt5mplwgt = PMGTimeSeriesPlot()
+ pmqt5mplwgt.time_series.line_color = '#ff0000'
+ pmqt5mplwgt.time_series.border_color = '#ff0000'
+ pmqt5mplwgt.time_series.symbol = '圆'
+ pmqt5mplwgt.time_series.y_range = (0, 100)
+ pmqt5mplwgt.time_series.threshold_line = 0.8 * 100
+ pmqt5mplwgt.config_chart_text(title='时间序列数据', xlabel='时间', ylabel='')
+ timer = QTimer()
+
+
+ def f():
+ n = 300
+ pmqt5mplwgt.set_data([i + 1 for i in range(n)],
+ [[randint(0, 30) for i in range(n)], [randint(0, 100) for i in range(n)]],
+ ['Cpu1', 'Cpu2'])
+
+
+ timer.timeout.connect(f)
+ timer.start(33)
+ pmqt5mplwgt.show()
+ app.exec_()
diff --git a/pyminer/widgets/widgets/basic/plots/matplotlib/__init__.py b/pyminer/widgets/widgets/basic/plots/matplotlib/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..aa854a4a2c15a4ec093ce994ec33c2a22327e05b
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/plots/matplotlib/__init__.py
@@ -0,0 +1,7 @@
+import warnings
+import os
+try:
+ import matplotlib
+ from .base import *
+except Exception as e:
+ warnings.warn(str(e))
diff --git a/pyminer/widgets/widgets/basic/plots/matplotlib/base/__init__.py b/pyminer/widgets/widgets/basic/plots/matplotlib/base/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..9fde31f5b0fdd9b9ab2f0a74256d7dbb7682685e
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/plots/matplotlib/base/__init__.py
@@ -0,0 +1 @@
+from .qt5aggplot import *
\ No newline at end of file
diff --git a/pyminer/widgets/widgets/basic/plots/matplotlib/base/pmaggplot.py b/pyminer/widgets/widgets/basic/plots/matplotlib/base/pmaggplot.py
new file mode 100644
index 0000000000000000000000000000000000000000..3fdb9194cd4d49c36d40ce0bcf0793d401b55e5c
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/plots/matplotlib/base/pmaggplot.py
@@ -0,0 +1,46 @@
+import sys
+from PySide2 import QtWidgets
+import matplotlib.pyplot as plt
+import random
+import os
+
+f = os.path.dirname
+s = __file__
+for i in range(7):
+ s = f(s)
+sys.path.append(os.path.join(s, 'pyminer2/extensions/packages/pmagg'))
+try:
+ import PMAgg
+except:
+ import traceback
+
+ traceback.print_exc()
+
+
+class MainWindow(QtWidgets.QDialog):
+ def __init__(self, figure, config_path):
+ super().__init__()
+ layout = QtWidgets.QVBoxLayout()
+ mpl_app = PMAgg.Window(config_path)
+ # 只需要将mpl绘图产生的figure对象以及一个配置文件.cfg的路径传给PMAgg即可,配置文件可以留空。
+ mpl_app.get_canvas(figure)
+ layout.addWidget(mpl_app)
+ # 一个额外的按钮
+ self.button = QtWidgets.QPushButton('test')
+ layout.addWidget(self.button)
+ self.setLayout(layout)
+
+
+if __name__ == '__main__':
+ # matplotlib 绘图
+ fig = plt.figure()
+ ax = fig.add_subplot(111)
+ data = [random.random() for i in range(25)]
+ ax.plot(data, '*-')
+ # app
+ app = QtWidgets.QApplication(sys.argv)
+ config_path = os.path.join(r'/pyminer2/extensions/packages/pmagg', 'settings.cfg')
+ main = MainWindow(fig, config_path)
+ main.setWindowTitle('Simple PySide2 and PMAgg example')
+ main.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/widgets/basic/plots/matplotlib/base/qt5aggplot.py b/pyminer/widgets/widgets/basic/plots/matplotlib/base/qt5aggplot.py
new file mode 100644
index 0000000000000000000000000000000000000000..7c9ab22274bcf1515217fd945a1a0122dd753ee6
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/plots/matplotlib/base/qt5aggplot.py
@@ -0,0 +1,50 @@
+from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
+import matplotlib.pyplot as plt
+from PySide2.QtWidgets import QVBoxLayout, QWidget
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from matplotlib.axes._subplots import Axes
+
+
+class PMMatplotlibQt5Widget(QWidget):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.figure = plt.figure(facecolor='#FFD7C4') # 可选参数,facecolor为背景颜色
+ self.canvas = FigureCanvasQTAgg(self.figure)
+ layout = QVBoxLayout()
+ layout.addWidget(self.canvas)
+ self.setLayout(layout)
+
+ def add_subplot(self, param) -> 'Axes':
+ return self.figure.add_subplot(param)
+
+ def draw(self) -> None:
+ self.canvas.draw()
+
+ def clear(self):
+ self.figure.clf()
+
+
+if __name__ == '__main__':
+ import sys
+ # from PySide2.QtWidgets import QApplication
+ from PySide2.QtWidgets import QApplication
+ from widgets.display import PMMatplotlibQt5Widget
+
+
+ def draw():
+ ax = pmqt5mplwgt.add_subplot(121)
+ ax2 = pmqt5mplwgt.add_subplot(122)
+ ax.plot([1, 2, 3])
+ ax.set_xlabel('test_x_label')
+ ax2.set_xlabel('test_2')
+ ax2.plot([1, 3, 1, 4, 15])
+ pmqt5mplwgt.draw()
+
+
+ app = QApplication(sys.argv)
+ pmqt5mplwgt = PMMatplotlibQt5Widget()
+ pmqt5mplwgt.show()
+ draw()
+ app.exec_()
diff --git a/pyminer/widgets/widgets/basic/plots/pyqtgraph/__init__.py b/pyminer/widgets/widgets/basic/plots/pyqtgraph/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..512fd95e44933cd938ee8136b2468242bcda08cc
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/plots/pyqtgraph/__init__.py
@@ -0,0 +1,7 @@
+import warnings
+
+try:
+ import pyqtgraph
+ from .base import *
+except Exception as e:
+ warnings.warn(str(e))
diff --git a/pyminer/widgets/widgets/basic/plots/pyqtgraph/base/__init__.py b/pyminer/widgets/widgets/basic/plots/pyqtgraph/base/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c16dae484763b434b194d5f354689425be0538e
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/plots/pyqtgraph/base/__init__.py
@@ -0,0 +1 @@
+from .pgplot import *
\ No newline at end of file
diff --git a/pyminer/widgets/widgets/basic/plots/pyqtgraph/base/pgplot.py b/pyminer/widgets/widgets/basic/plots/pyqtgraph/base/pgplot.py
new file mode 100644
index 0000000000000000000000000000000000000000..a8ad8608ee323e177432003b9ae85ae804770dee
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/plots/pyqtgraph/base/pgplot.py
@@ -0,0 +1,83 @@
+"""
+相关的元素设置
+
+线:
+颜色--line_color
+线型--line_style
+粗细--line_width
+
+图元item
+边缘线的颜色--border_color
+边缘线粗细--border_width
+形状--symbol
+颜色--item_color
+
+图窗颜色
+face_color
+
+['o', 's', 't', 't1', 't2', 't3', 'd', '+', 'x', 'p', 'h', 'star']
+ # r_color = random.choice(['b', 'g', 'r', 'c', 'm', 'y', 'k', 'd', 'l', 's'])
+"""
+import sys
+from typing import Union
+
+import pyqtgraph as pg
+from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout
+from widgets.utilities.source.graphicsitemutils import PMGPlotCustomizer
+
+symbols_dic = {'o': 'o', 's': 's', 't': 't', 't1': 't1', 't2': 't2', 't3': 't3',
+ 'd': 'd', '+': '+', 'x': 'x', 'p': 'p', 'h': 'h', 'star': 'star',
+ '圆': 'o', '方': 's', '正三角': 't1', '倒三角': 't', '五边形': 'p', '六边形': 'h', '星': 'star',
+ '五星': 'star', '菱形': 'd'}
+random_symbols = [
+ dict(pen=(0, 0, 200), symbolBrush=(0, 0, 200), symbolPen='w', symbol='o', symbolSize=8),
+ dict(pen=(0, 128, 0), symbolBrush=(0, 128, 0), symbolPen='w', symbol='t', symbolSize=8),
+ dict(pen=(19, 234, 201), symbolBrush=(19, 234, 201), symbolPen='w', symbol='t1', symbolSize=8),
+ dict(pen=(195, 46, 212), symbolBrush=(195, 46, 212), symbolPen='w', symbol='t2', symbolSize=8),
+ dict(pen=(250, 194, 5), symbolBrush=(250, 194, 5), symbolPen='w', symbol='t3', symbolSize=8),
+ dict(pen=(54, 55, 55), symbolBrush=(55, 55, 55), symbolPen='w', symbol='s', symbolSize=8),
+ dict(pen=(0, 114, 189), symbolBrush=(0, 114, 189), symbolPen='w', symbol='p', symbolSize=8),
+ dict(pen=(217, 83, 25), symbolBrush=(217, 83, 25), symbolPen='w', symbol='h', symbolSize=8),
+ dict(pen=(237, 177, 32), symbolBrush=(237, 177, 32), symbolPen='w', symbol='star', symbolSize=8),
+ dict(pen=(126, 47, 142), symbolBrush=(126, 47, 142), symbolPen='w', symbol='+', symbolSize=8),
+ dict(pen=(119, 172, 48), symbolBrush=(119, 172, 48), symbolPen='w', symbol='d', symbolSize=8),
+]
+
+
+class PMGPyQtGraphWidget(QWidget, PMGPlotCustomizer):
+ def __init__(self, parent=None, text_color=None):
+ super(PMGPyQtGraphWidget, self).__init__(parent)
+ self._symbols_dic = symbols_dic
+ self._symbols = random_symbols
+ self.resize(600, 600)
+ self.lines = []
+ pg.setConfigOptions(leftButtonPan=False)
+ text_color = 'k' if text_color is None else text_color
+ pg.setConfigOption('foreground', text_color)
+ self.plot_widget: pg.PlotWidget = None
+ self.v_layout = QVBoxLayout()
+ self.v_layout.setContentsMargins(0, 0, 0, 0)
+ self.setLayout(self.v_layout)
+
+ def plot(self, *args, **kwargs):
+ pass
+
+ def baseplot(self, x, ylist):
+ for line in self.lines:
+ line.clear()
+ for y in ylist:
+ plot_data = self.plot_widget.plot(x, y, pen=None, symbol='+', symbolBrush='r')
+ self.lines.append(plot_data)
+
+ def draw(self):
+ pass
+
+ def clear(self):
+ pass
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ demo = PMGPyQtGraphWidget()
+ demo.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/widgets/basic/plots/scatters/__init__.py b/pyminer/widgets/widgets/basic/plots/scatters/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/widgets/widgets/basic/plots/scatters/scatters.py b/pyminer/widgets/widgets/basic/plots/scatters/scatters.py
new file mode 100644
index 0000000000000000000000000000000000000000..bba7f60275ce4bcd98b3135fd9605d19721ee6c4
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/plots/scatters/scatters.py
@@ -0,0 +1,34 @@
+import pyqtgraph as pg
+import numpy as np
+from PySide2.QtWidgets import QApplication
+from widgets.widgets.basic.plots.pyqtgraph import PMGPyQtGraphWidget
+from widgets import color_str2tup
+import sys
+
+
+class PMGScatterPlot(PMGPyQtGraphWidget):
+ def __init__(self, parent=None):
+ super(PMGScatterPlot, self).__init__(parent)
+ self.plot_widget = pg.PlotWidget(self)
+ self.v_layout.addWidget(self.plot_widget)
+
+ def plot(self):
+ n = 300
+ self.s1 = pg.ScatterPlotItem(size=10,
+ pen=pg.mkPen(self.border_color,width = self.border_width),
+ brush=color_str2tup(self.item_color))
+ pos = np.random.normal(size=(2, n), scale=1e-5)
+ spots = [{'pos': pos[:, i], 'data': 1} for i in range(n)] + [{'pos': [0, 0], 'data': 1}]
+ print(spots)
+ self.s1.addPoints(spots)
+ self.plot_widget.addItem(self.s1)
+ print(self.plot_widget.getPlotItem())
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ pw = PMGScatterPlot()
+ pw.border_color = '#ff0000'
+ pw.show()
+ pw.plot()
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/widgets/basic/plots/translations/qt_zh_CN.ts b/pyminer/widgets/widgets/basic/plots/translations/qt_zh_CN.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ccc96b059fec006e6e2ba83468e68fec8c6a35c7
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/plots/translations/qt_zh_CN.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/pyminer/widgets/widgets/basic/quick/__init__.py b/pyminer/widgets/widgets/basic/quick/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/widgets/widgets/basic/quick/demo1.py b/pyminer/widgets/widgets/basic/quick/demo1.py
new file mode 100644
index 0000000000000000000000000000000000000000..99cba18fffb14bf593de8e97882fff0480f6f404
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/quick/demo1.py
@@ -0,0 +1,34 @@
+from PySide2.QtCore import QUrl, QObject, Slot
+
+from PySide2.QtGui import QGuiApplication
+
+from PySide2.QtQuick import QQuickView
+
+
+class MyClass(QObject):
+
+ @Slot(int, result=str) # 声明为槽,输入参数为int类型,返回值为str类型
+ def returnValue(self, value):
+ return str(value + 10)
+
+
+if __name__ == '__main__':
+ path = 'src/demo1.qml'
+
+ app = QGuiApplication([])
+
+ view = QQuickView()
+
+ con = MyClass()
+
+ context = view.rootContext()
+
+ context.setContextProperty("con", con)
+
+ view.engine().quit.connect(app.quit)
+
+ view.setSource(QUrl(path))
+
+ view.show()
+
+ app.exec_()
diff --git a/pyminer/widgets/widgets/basic/tables/__init__.py b/pyminer/widgets/widgets/basic/tables/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..7c90eb9d3bb63849c8179c96212b0f685a314e3f
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/tables/__init__.py
@@ -0,0 +1,2 @@
+from .tableviews import *
+from .tablewidgets import *
\ No newline at end of file
diff --git a/pyminer/widgets/widgets/basic/tables/help/help.md b/pyminer/widgets/widgets/basic/tables/help/help.md
new file mode 100644
index 0000000000000000000000000000000000000000..b8d270b8042a1c86615e100faefbf821c52c107d
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/tables/help/help.md
@@ -0,0 +1,44 @@
+# 数据查看与编辑 帮助 Help
+## 操作说明
+### 跳转到行:
+
+点击“跳转到行”按钮,或者快捷键`Ctrl+G`均可。
+
+但是注意,很多时候pandas的Index是从0开始的。因此行的索引为399时,实际的行号应该是400.
+
+
+# 高亮颜色说明
+### Pandas
+Pandas的数据类型是相当复杂的,不同的数据类型之间常常发生混淆。
+
+让我们考虑下面的情况:字符串`'2017-06-27'`和pandas的TimeStamp型数据`'2017-06-27'`,这两个数据其实是完全不同的。
+但是在数据集难以明显的区分出来。
+因此我们就用这样的高亮方式,方便区分数据集的数据类型。
+
+在PyMiner对Pandas数据的高亮方式中,可以很好很方便的区分这个问题。
+
+- 如果是字符串,就会用黄色背景这样显示:2017-06-27
+
+- 如果是时间戳类型,则会用绿色背景这样显示:2017-06-27
+
+高亮颜色的说明如下:
+- 数值型:
+
+ - 整数:123
+ - 浮点数:3.1415
+ - 复数:1+1j
+
+- 布尔型:
+True
+- 时间型:
+2017-07-20
+- 字符串型:黄色
+John Strauss
+- 其他类型:灰色
+ - None
+ - person(自定义数据类型)
+ - NaN
+ - NaT
+
+### Numpy
+目前没有高亮效果,未来的高亮效果计划以大小值为依据,配色方案参考pandas的方案。
\ No newline at end of file
diff --git a/pyminer/widgets/widgets/basic/tables/tableviews.py b/pyminer/widgets/widgets/basic/tables/tableviews.py
new file mode 100644
index 0000000000000000000000000000000000000000..be2c83ede199b73eacb11b879f5e101f8ef0b407
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/tables/tableviews.py
@@ -0,0 +1,680 @@
+"""
+这是一个利用QT的MVC架构进行数据查看的表格。这个表格十分适合大量数据的查看,1000*1000规模的数据集可以做到秒开。
+其中定义了若干类。可以直接显示pd.DataFrame,np.array和list的TableView。
+
+目前增加了切片索引查看功能和編輯功能。对dataframe而言,切片时可以编辑
+但是array在切片的时候编辑。
+
+作者:侯展意
+"""
+import os
+import sys
+
+import typing
+import logging
+from PySide2.QtGui import QColor
+from PySide2.QtWidgets import QSpacerItem, QComboBox, QSpinBox, QLabel
+from PySide2.QtWidgets import QTableView, QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton, \
+ QMessageBox, QInputDialog, QMenu, QDialog, QDialogButtonBox, QShortcut, QSizePolicy
+from PySide2.QtCore import QAbstractTableModel, QModelIndex, Signal, QLocale, QCoreApplication
+from PySide2.QtCore import Qt, QPoint
+from PySide2.QtGui import QContextMenuEvent, QKeyEvent, QKeySequence
+
+from widgets.utilities.source.translation import create_translator
+from widgets.widgets.basic.dialogs.textdialog import TextShowDialog
+
+if typing.TYPE_CHECKING:
+ import numpy as np
+
+logger = logging.getLogger(__name__)
+
+
+class InputValueDialog(QDialog):
+ UP = -1
+ DOWN = 1
+ signal_move_cursor = Signal(int)
+ signal_edit_finished = Signal(str)
+
+ def __init__(self, parent):
+ super(InputValueDialog, self).__init__(parent)
+ self.setLayout(QVBoxLayout())
+ self.edit = QLineEdit()
+ self.layout().addWidget(self.edit)
+ self.edit.returnPressed.connect(self.edit_finished)
+ self.button_box = QDialogButtonBox()
+ self.button_box.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+ self.layout().addWidget(self.button_box)
+ self.button_box.rejected.connect(self.close)
+ self.button_box.accepted.connect(self.edit_finished)
+
+ def edit_finished(self):
+ self.close()
+ self.signal_edit_finished.emit(self.edit.text())
+
+ def keyPressEvent(self, e: QKeyEvent):
+ if e.key() == Qt.Key_Up:
+ self.close()
+ self.signal_move_cursor.emit(self.UP)
+ e.accept()
+ elif e.key() == Qt.Key_Down:
+ self.close()
+ self.signal_move_cursor.emit(self.DOWN)
+ e.accept()
+
+ super(InputValueDialog, self).keyPressEvent(e)
+
+
+def to_decimal_str(cell_data: 'np.ndarray', decimals: int = 6):
+ import numpy as np
+ try:
+ rounded_data = np.around(cell_data, decimals)
+ return repr(rounded_data)
+ except:
+ return str(cell_data)
+
+
+def dataformat(val, decimals=6, sci=False):
+ """
+ 这只是暂时的strformat函数。如有可能,应当使用cython重写并且部署在动态链接库中,从而提升性能。
+ Args:
+ val:
+ decimals:
+ sci:
+
+ Returns:
+
+ """
+ global type_float_set
+ return to_decimal_str(val, decimals)
+
+
+class BaseAbstractTableModel(QAbstractTableModel):
+ @property
+ def default_slicing_statement(self):
+ raise NotImplementedError
+
+
+class TableModelForList(BaseAbstractTableModel):
+ """
+ 输入为list的table model
+ """
+
+ def __init__(self, data: list):
+ super(TableModelForList, self).__init__()
+ import numpy as np
+ self._data: np.ndarray = data
+
+ def data(self, index, role):
+ if role == Qt.DisplayRole:
+ return dataformat(self._data[index.row()][index.column()])
+
+ def rowCount(self, index):
+ return len(self._data)
+
+ def columnCount(self, index):
+ return len(self._data[0])
+
+
+class TableModelForNumpyArray(BaseAbstractTableModel):
+ """
+ 输入为pandas.DataFram的TableModel,用于在表格中显示数据。
+ """
+
+ def __init__(self, data):
+ super(TableModelForNumpyArray, self).__init__()
+ self._data = data
+ self.horizontal_start: int = 0
+ self.vertical_start: int = 0
+
+ def setData(self, index: 'QModelIndex', value: typing.Any = None, role='Qt.EditRole'):
+ """
+ # View中编辑后,View会调用这个方法修改Model中的数据
+ :param index:
+ :param value:
+ :param role:
+ :return:
+ """
+
+ if index.isValid() and 0 <= index.row() < self._data.shape[0] and value:
+ col = index.column()
+ row = index.row()
+
+ if len(self._data.shape) == 1: # 一维矩阵
+ self.beginResetModel()
+ self._data[row] = value
+ self.dirty = True
+ self.endResetModel()
+ return True
+ else:
+ if 0 <= col < self._data.shape[1]:
+ self.beginResetModel()
+ self._data[row, col] = value
+
+ self.dirty = True
+ self.endResetModel()
+ return True
+ return False
+
+ def data(self, index, role):
+ if role == Qt.DisplayRole:
+ if len(self._data.shape) >= 2:
+ value = self._data[index.row(), index.column()]
+ else:
+ value = self._data[index.column()]
+ return dataformat(value)
+
+ def rowCount(self, index):
+ if len(self._data.shape) >= 2:
+ return self._data.shape[0]
+ else:
+ return 1
+ # return self._data.shape[0]
+
+ def columnCount(self, index):
+ if len(self._data.shape) == 1:
+ return self._data.shape[0]
+ else:
+ return self._data.shape[1]
+
+ def headerData(self, section: int, orientation: Qt.Orientation, role: int = ...) -> typing.Any:
+ if role == Qt.DisplayRole:
+ if role == Qt.DisplayRole:
+ if orientation == Qt.Horizontal:
+ return str(self.horizontal_start + section)
+ if orientation == Qt.Vertical:
+ return str(self.vertical_start + section)
+
+ @property
+ def default_slicing_statement(self):
+ """
+ 默认切片数组
+ :return:
+ """
+ data_dim = len(self._data.shape)
+ if data_dim in (1, 2):
+
+ return '[%s]' % (':,' * data_dim).strip(',')
+ else:
+ return '[%s]' % (':,:,' + '0,' * (data_dim - 2)).strip(',')
+
+
+class TableModelForPandasDataframe(BaseAbstractTableModel):
+ """
+ 输入为pandas.DataFram的TableModel,用于在表格中显示数据。
+ """
+
+ def __init__(self, data, original_data):
+ super(TableModelForPandasDataframe, self).__init__()
+ self._data: 'pd.DataFrame' = data
+ self.original_data = original_data
+ self.colors = {'int': QColor(0, 0, 128, 100), 'bool': QColor(0, 200, 200, 100),
+ 'float': QColor(0, 64, 128, 100), 'str': QColor(200, 200, 0, 100),
+ 'timestamp': QColor(0, 200, 0, 100),
+ 'complex': QColor(100, 0, 128, 100)
+ }
+
+ def get_color(self, data):
+ import numpy as np
+ import pandas as pd
+ if isinstance(data, (np.bool_, bool)):
+ return self.colors['bool']
+ elif isinstance(data, (np.integer, int)):
+ return self.colors['int']
+ elif isinstance(data, (np.inexact, float)):
+ if isinstance(data, np.complex_):
+ return self.colors['complex']
+ return self.colors['float']
+ elif isinstance(data, (str)):
+ return self.colors['str']
+ elif isinstance(data, (pd.Timestamp)):
+ return self.colors['timestamp']
+ elif data == np.nan:
+ return QColor(0, 0, 50, 100)
+ elif data == pd.NaT:
+ return QColor(0, 50, 0, 100)
+
+ return QColor(0, 0, 0, 80)
+
+ def data(self, index, role):
+ if role == Qt.DisplayRole:
+ value = self._data.iloc[index.row(), index.column()]
+ return dataformat(value)
+ if role == Qt.BackgroundRole:
+ data = self._data.iloc[index.row(), index.column()]
+ return self.get_color(data)
+
+ def rowCount(self, index):
+ return self._data.shape[0]
+
+ def columnCount(self, index):
+ return self._data.shape[1]
+
+ def headerData(self, section: int, orientation: Qt.Orientation, role: int = ...) -> typing.Any:
+ if role == Qt.DisplayRole:
+ if role == Qt.DisplayRole:
+ if orientation == Qt.Horizontal:
+ return str(self._data.columns[section])
+ if orientation == Qt.Vertical:
+ return str(self._data.index[section])
+
+ @property
+ def default_slicing_statement(self):
+ """
+ 默认切片数组
+ :return:
+ """
+ data_dim = len(self._data.shape)
+ return '.iloc[%s]' % (':,' * data_dim).strip(',')
+
+ def setData(self, index, value=None, role=Qt.EditRole):
+ # 编辑后更新模型中的数据 View中编辑后,View会调用这个方法修改Model中的数据
+ if index.isValid() and 0 <= index.row() < self._data.shape[0] and value:
+ col = index.column()
+ row = index.row()
+ if 0 <= col < self._data.shape[1]:
+ self.beginResetModel()
+ col_label = self._data.columns[col]
+ row_label = self._data.index[row]
+ self.original_data.loc[row_label, col_label] = value
+ self._data.loc[row_label, col_label] = value
+ self.dirty = True
+ self.endResetModel()
+ return True
+ return False
+
+
+class PMTableView(QTableView):
+ """
+ 基类,用于显示数据。输入数据类型为列表。
+ """
+ INSERT_ROW = 0
+ DELETE_ROW = 1
+ INSERT_COLUMN = 2
+ DELETE_COLUMN = 3
+
+ signal_need_save = Signal(bool)
+
+ def __init__(self, data=None):
+ super().__init__()
+ self.translator = create_translator(
+ path=os.path.join(os.path.dirname(__file__), 'translations',
+ 'qt_{0}.qm'.format(QLocale.system().name()))) # translator
+ self.data = None
+ self.menu = QMenu()
+ self.action_insert_row = self.menu.addAction(QCoreApplication.translate('PMTableView', 'Insert Row'))
+ self.action_insert_row.triggered.connect(lambda: self.on_change_row_col(self.INSERT_ROW))
+ self.action_delete_row = self.menu.addAction(QCoreApplication.translate('PMTableView', 'Delete Row'))
+ self.action_delete_row.triggered.connect(lambda: self.on_change_row_col(self.DELETE_ROW))
+ self.action_insert_col = self.menu.addAction(QCoreApplication.translate('PMTableView', 'Insert Column'))
+ self.action_insert_col.triggered.connect(lambda: self.on_change_row_col(self.INSERT_COLUMN))
+ self.action_delete_col = self.menu.addAction(QCoreApplication.translate('PMTableView', 'Delete Column'))
+ self.action_delete_col.triggered.connect(lambda: self.on_change_row_col(self.DELETE_COLUMN))
+ # self.menu.addAction("aaaaaa")
+ if data is not None:
+ self.set_data(data)
+
+ def on_change_row_col(self, operation: int):
+ """
+ The slot for editting row or columns
+ Args:
+ operation:
+
+ Returns:
+
+ """
+ import pandas as pd
+ import numpy as np
+ pd_data: pd.DataFrame = self.model._data
+ current_index = self.currentIndex()
+ row, column = current_index.row(), current_index.column()
+ if operation == self.INSERT_ROW:
+ prev = pd_data.iloc[:row]
+ lat = pd_data.iloc[row:]
+ self.model._data = pd.concat([prev, pd.DataFrame([[]]), lat])
+ elif operation == self.DELETE_ROW:
+ prev = pd_data.iloc[:row]
+ lat = pd_data.iloc[row + 1:]
+ self.model._data = pd.concat([prev, lat])
+ elif operation == self.INSERT_COLUMN:
+ col_name: str = ''
+ col_name, _ = QInputDialog.getText(self, QCoreApplication.translate('PMTableView', 'Input Column Title'),
+ QCoreApplication.translate('PMTableView', 'Title'))
+ if _:
+ if col_name.isdigit():
+ col_name = int(col_name)
+ # if col_name in pd_data.columns:
+ # QMessageBox.warning(self, QCoreApplication.translate("PMTableView", "Warning"),
+ # QCoreApplication.translate("PMTableView",
+ # "Input value was integer, however the value will be converted to string." % col_name))
+ # return
+ try:
+ pd_data.insert(column, col_name, np.nan)
+ except ValueError:
+ QMessageBox.warning(self, QCoreApplication.translate("PMTableView", "Error"),
+ QCoreApplication.translate("PMTableView",
+ "Column name \'%s\', type\'%s\' duplicated with existing column!" % (
+ col_name, type(col_name))))
+
+ elif operation == self.DELETE_COLUMN:
+ # prev = pd_data.iloc[:row]
+ # lat = pd_data.iloc[row + 1:]
+ # self.model._data = pd.concat([prev, lat])
+ self.model._data = pd_data.drop(columns=[column], axis=0)
+ else:
+ raise NotImplementedError
+ self.model.layoutChanged.emit()
+ self.signal_need_save.emit(True)
+
+ def set_data(self, data):
+ self.data = data
+ self.show_data(data)
+
+ def get_data(self):
+ return self.model._data
+
+ def show_data(self, data):
+ """
+ data可能是self.data,也可能是self.data的一部分。
+ Args:
+ data:
+
+ Returns:
+
+ """
+ import pandas as pd
+ import numpy as np
+ if isinstance(data, pd.DataFrame):
+ self.model = TableModelForPandasDataframe(data, self.data)
+ elif isinstance(data, np.ndarray):
+ self.model = TableModelForNumpyArray(data)
+ self.menu.setEnabled(False)
+ elif isinstance(data, list):
+ self.model = TableModelForList(data)
+ self.menu.setEnabled(True)
+ else:
+ raise Exception("data type %s is not supported in PMTableView.\
+ \n Supported Types are: numpy.array,list and pandas.DataFrame." % type(data))
+ self.setModel(self.model)
+
+ def get_default_slicing_statement(self):
+ return self.model.default_slicing_statement
+
+ def mouseDoubleClickEvent(self, event: 'QMouseEvent') -> None:
+ """
+ Args:
+ event:
+
+ Returns:
+
+ """
+ super().mouseDoubleClickEvent(event)
+ self.show_edit_dialog(self.currentIndex().row(), self.currentIndex().column())
+
+ def keyPressEvent(self, event: QKeyEvent) -> None:
+ super(PMTableView, self).keyPressEvent(event)
+ if event.key() == Qt.Key_Return:
+ self.show_edit_dialog(self.currentIndex().row(), self.currentIndex().column())
+
+ def show_edit_dialog(self, row, col):
+ import pandas as pd
+ import numpy as np
+ data = self.model._data
+ if isinstance(data, (pd.DataFrame, np.ndarray)):
+ def on_edited(text):
+ from pandas import Timestamp, Period, Interval
+ try:
+ result = eval(text)
+ if isinstance(data, pd.DataFrame):
+ data.iloc[row, col] = result
+ elif isinstance(data, np.ndarray):
+ data[row, col] = result
+ self.signal_need_save.emit(True)
+ except:
+ import traceback
+ QMessageBox.warning(self, QCoreApplication.translate('PMTableView', 'Warning'),
+ traceback.format_exc())
+ return
+
+ def on_move_current_cell(direction: int):
+ target_row = row + direction
+ if 0 <= target_row < self.model.rowCount(col):
+ self.setCurrentIndex(self.model.index(target_row, col))
+ self.show_edit_dialog(target_row, col)
+
+ if isinstance(data, pd.DataFrame):
+ original_data = data.iloc[row, col]
+ elif isinstance(data, np.ndarray):
+ original_data = data[row, col]
+ else:
+ raise NotImplementedError
+
+ dlg = InputValueDialog(self)
+ dlg.setWindowTitle(QCoreApplication.translate('PMTableView', 'Input New Value'))
+ dlg.edit.setText(repr(original_data))
+ dlg.signal_edit_finished.connect(on_edited)
+ dlg.signal_move_cursor.connect(on_move_current_cell)
+ global_pos = self.mapToGlobal(
+ QPoint(self.columnViewportPosition(col) + 50, self.rowViewportPosition(row) + 50))
+ dlg.setGeometry(global_pos.x(), global_pos.y(), dlg.width(), dlg.height())
+ dlg.exec_()
+ # QInputDialog.getText(self, QCoreApplication.translate('PMTableView','Input New Value'), '', QLineEdit.Normal,
+ # text=repr(original_data))
+
+ def contextMenuEvent(self, event: QContextMenuEvent):
+ import pandas as pd
+ if isinstance(self.model._data, pd.DataFrame):
+ self.menu.exec_(event.globalPos())
+
+ def on_goto_index(self, row: int, col: int = 0):
+ import pandas as pd
+ import numpy as np
+
+ if isinstance(self.data, (pd.DataFrame, np.ndarray)):
+ assert 0 <= row <= self.model.rowCount(None)
+ self.setCurrentIndex(self.model.index(row, col))
+
+
+class PMGTableViewer(QWidget):
+ """
+ 一个含有QTableView的控件。
+ 有切片和保存两个按钮。点击Slice的时候可以切片查看,点击Save保存。
+ """
+ data_modified_signal = Signal()
+ signal_need_save = Signal(bool)
+
+ def __init__(self, parent=None, table_view: 'PMTableView' = None):
+ super().__init__(parent)
+
+ self.setLayout(QVBoxLayout())
+ self.top_layout = QHBoxLayout()
+ self.layout().addLayout(self.top_layout)
+ self.table_view = table_view
+ self.slice_input = QLineEdit()
+ self.help_button = QPushButton(QCoreApplication.translate('PMGTableViewer', '帮助'))
+ self.slice_refresh_button = QPushButton(QCoreApplication.translate('PMGTableViewer', '切片'))
+ self.save_change_button = QPushButton(QCoreApplication.translate('PMGTableViewer', '保存'))
+ self.goto_cell_button = QPushButton(QCoreApplication.translate('PMGTableViewer', '前往单元格'))
+
+ self.label_slice_axis2 = QLabel("高度索引:")
+
+ self.slice_axis2 = QSpinBox()
+ self.slice_axis2.setMinimum(0)
+ self.slice_axis2.setSingleStep(1)
+ self.slice_axis2.setMaximum(0)
+ self.slice_axis2.valueChanged.connect(self.on_axis2_value_changed)
+
+ self.save_change_button.clicked.connect(self.on_save)
+ self.slice_refresh_button.clicked.connect(self.slice)
+ self.help_button.clicked.connect(self.on_help)
+ self.goto_cell_button.clicked.connect(self.on_goto_cell)
+ self.slice_input.hide()
+ self.slice_refresh_button.hide()
+
+ self.table_view.signal_need_save.connect(self.signal_need_save.emit)
+ self.signal_need_save.connect(self.on_signal_need_save)
+
+ self.top_layout.addWidget(self.label_slice_axis2)
+ self.top_layout.addWidget(self.slice_axis2)
+
+ self.label_slice_axis2.hide()
+ self.slice_axis2.hide()
+
+ self.top_layout.addWidget(self.goto_cell_button)
+ self.top_layout.addWidget(self.save_change_button)
+ self.top_layout.addWidget(self.help_button)
+ self.top_layout.addWidget(self.slice_input)
+ self.top_layout.addWidget(self.slice_refresh_button)
+ self.top_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum))
+
+ if table_view is not None:
+ self.layout().addWidget(self.table_view)
+
+ self.shortcut_save = QShortcut(QKeySequence.Save, self.table_view, context=Qt.WidgetShortcut)
+ self.shortcut_save.activated.connect(self.on_save)
+
+ self.shortcut_goto = QShortcut(QKeySequence('Ctrl+G'), self.table_view, context=Qt.WidgetShortcut)
+ self.shortcut_goto.activated.connect(self.on_goto_cell)
+
+ def on_axis2_value_changed(self):
+ """
+ 当显示多维数组时,如果改变第三维的索引,就调用这个函数
+ Returns:
+
+ """
+ import numpy as np
+ if isinstance(self.table_view.data, np.ndarray):
+ if len(self.table_view.data.shape) > 2:
+ self.table_view.show_data(self.table_view.data[self.slice_axis2.value(), :, :])
+
+ def on_high_dimensional_array(self, high_dimensional: bool):
+ self.label_slice_axis2.setVisible(high_dimensional)
+ self.slice_axis2.setVisible(high_dimensional)
+
+ def on_help(self):
+ dlg = TextShowDialog(title=QCoreApplication.translate('PMGTableViewer', '帮助'))
+ with open(os.path.join(os.path.dirname(__file__), 'help', 'help.md'), 'r', encoding='utf8',
+ errors='replace') as f:
+ dlg.set_markdown(f.read())
+ dlg.exec_()
+
+ def on_signal_need_save(self, need_save: str):
+ title = self.windowTitle()
+ if need_save:
+ if not title.startswith('*'):
+ self.setWindowTitle('*' + title)
+ else:
+ if title.startswith('*'):
+ self.setWindowTitle(title.strip('*'))
+
+ def on_save(self):
+ self.signal_need_save.emit(False)
+ self.data_modified_signal.emit()
+
+ def set_data(self, data: typing.Any) -> bool:
+ """
+ set_data方法在初次调用时,设置其内部的data参数;
+ 当后面调用的时候,不会更改内部的data参数。
+ get_default_slicing_statement的意思是可以获取默认的切片索引。
+ 这是因为表格一般只能显示二维的数据,当数组维数超过二维的时候,就需要尽可能地利用切片进行显示了。
+ 比如对于四维np.array张量,返回的默认就是[:,:,0,0]。用户可以根据自己的需要进行切片。
+ :param data:
+ :return:
+ """
+ import numpy as np
+ if isinstance(data, np.ndarray):
+ if len(data.shape) > 3:
+ QMessageBox.warning(self, "提示", "目前只支持三维及以下数组的查看")
+ return False
+ elif len(data.shape) == 3:
+ self.slice_axis2.setMaximum(data.shape[0] - 1)
+ self.on_high_dimensional_array(len(data.shape) == 3)
+
+ if self.table_view is not None:
+ self.table_view.set_data(data)
+ self.slice_input.setText(self.table_view.get_default_slicing_statement())
+
+ if isinstance(data, np.ndarray):
+ if len(data.shape) > 2:
+ self.on_axis2_value_changed() # 如果维度小于3,则默认进行以下切片操作。
+ return True
+
+ def get_data(self):
+ return self.table_view.data
+
+ def slice(self):
+ """
+ 切片操作。同时屏蔽可能出现的非法字符。
+ 目前做不到对array数组进行索引。
+ :return:
+ """
+ data = self.table_view.data
+ text = self.slice_input.text().strip()
+ for char in text:
+ if not char in "[]:,.1234567890iloc":
+ QMessageBox.warning(self, QCoreApplication.translate('PMGTableViewer', 'Invalid Input'),
+ QCoreApplication.translate('PMGTableViewer',
+ "invalid character \"%s\" in slicing statement.") % char)
+ return
+ try:
+ data = eval('data' + text)
+ except Exception as exeption:
+
+ QMessageBox.warning(self, QCoreApplication.translate('PMGTableViewer', 'Invalid Input'),
+ QCoreApplication.translate('PMGTableViewer', str(exeption)))
+
+ self.table_view.show_data(data)
+
+ def closeEvent(self, a0: 'QCloseEvent') -> None:
+ super().closeEvent(a0)
+
+ def on_goto_cell(self):
+ if isinstance(self.table_view.model, (TableModelForPandasDataframe, TableModelForNumpyArray)):
+ min_row, max_row = 1, self.table_view.model.rowCount(None)
+ current_row = self.table_view.currentIndex().row() + 1
+ current_col = self.table_view.currentIndex().column() + 1
+ row, _ = QInputDialog.getInt(self, QCoreApplication.translate('PMGTableViewer', '输入行'),
+ QCoreApplication.translate('PMGTableViewer',
+ 'Target Row No.:({min}~{max})').format(min=min_row,
+ max=max_row),
+ current_row,
+ min_row, max_row, step=1)
+ if _:
+ self.table_view.on_goto_index(row - 1, 0)
+ else:
+ raise NotImplementedError
+
+
+if __name__ == '__main__':
+ import datetime
+ import numpy as np
+ import pandas as pd
+
+ app = QApplication(sys.argv)
+
+ table = PMGTableViewer(table_view=PMTableView())
+ data = np.arange(1, 17).reshape(2, 2, 4)
+ table.show()
+ ret = table.set_data(data)
+
+ table2 = PMGTableViewer(table_view=PMTableView())
+ data = np.arange(1, 17).reshape(2, 8)
+ table2.show()
+ ret = table2.set_data(data)
+
+ table3 = PMGTableViewer(table_view=PMTableView())
+ data = np.arange(1, 17)
+ table3.show()
+ ret = table3.set_data(data)
+
+ if not ret:
+ table.close()
+ else:
+ app.exec_()
+ # table.setWindowTitle('Pandas数据集 显示多种数据')
+
+ # data2 = pd.DataFrame(np.array([1 + 1j, 1 + 2j, 1 + 2.0j]))
+ # table2 = PMGTableViewer(table_view=PMTableView())
+ # # table2.show()
+ # table2.setWindowTitle('Pandas数据集复数显示')
+ # table2.set_data(data2)
+ # print(data.dtypes)
diff --git a/pyminer/widgets/widgets/basic/tables/tablewidgets.py b/pyminer/widgets/widgets/basic/tables/tablewidgets.py
new file mode 100644
index 0000000000000000000000000000000000000000..72a42a207a708d7a9b190cae1d5d56d8ee24a32b
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/tables/tablewidgets.py
@@ -0,0 +1,106 @@
+# 通用表格控件
+# 作者:侯展意
+# 带有加载数据集等等的功能,相对来讲比较方便。
+# 以PMG作为名字开头的控件,不依赖于主界面,只是会和主界面之间传递信号。
+
+import sys
+
+from PySide2.QtCore import Signal
+from PySide2.QtGui import QCloseEvent
+from PySide2.QtWidgets import QApplication, QTabWidget, QTableWidget, QTableWidgetItem, \
+ QAbstractItemView
+
+from typing import Sized, Iterable
+
+
+class PMGTableWidget(QTableWidget):
+ data_name: str = ''
+ data_shown = Signal(str)
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.setRowCount(1)
+ self.setColumnCount(1)
+ self.verticalHeader().setDefaultSectionSize(30)
+ self.verticalHeader().setMinimumWidth(30)
+ self.horizontalHeader().setMinimumWidth(30)
+
+ def closeEvent(self, a0: 'QCloseEvent') -> None:
+ super().closeEvent(a0)
+
+ @staticmethod
+ def check_data_can_be_displayed_by_table(data: 'Sized') -> bool:
+ try:
+ if not hasattr(data, '__len__'):
+ return True
+ max_cols = 0
+ for i, row_contents in enumerate(data):
+ if hasattr(row_contents, '__iter__'):
+ col_span = len(row_contents)
+ a = row_contents[i] # 尝试index第0项。
+ else:
+ col_span = 1
+ if col_span > max_cols:
+ max_cols = col_span
+
+ for row, row_content in enumerate(data):
+ if hasattr(row_content, '__iter__'):
+ for col, content in enumerate(row_content):
+ data[row][col]
+ else:
+ data[row]
+ return True
+
+ except:
+ return False
+
+ def set_data_2d(self, data: 'Iterable', rows: int = None,
+ columns: int = None):
+
+ if not hasattr(data, '__len__'):
+ item = QTableWidgetItem(str(data))
+ self.setItem(0, 0, item)
+ return
+ if rows is None or columns is None:
+ rows = len(data)
+ max_cols = 0
+ for row_contents in data:
+ if hasattr(row_contents, '__iter__'):
+ col_span = len(row_contents)
+ else:
+ col_span = 1
+ if col_span > max_cols:
+ max_cols = col_span
+ columns = max_cols
+ self.setColumnCount(columns)
+ self.setRowCount(rows)
+ self.setSelectionBehavior(QAbstractItemView.SelectRows)
+ for row, row_content in enumerate(data):
+ if hasattr(row_content, '__iter__'):
+ for col, content in enumerate(row_content):
+ item = QTableWidgetItem(str(data[row][col]))
+ self.setItem(row, col, item)
+ else:
+ item = QTableWidgetItem(str(data[row]))
+ self.setItem(row, 0, item)
+
+
+class PMGTableTabWidget(QTabWidget):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ list_to_display = ['hhhhhhhhhhhhhh',
+ ['a', 'v'],
+ [1, 2, 3, 4],
+ [3, 4, 5, 66, 7],
+ [123, '333', 'ffffffff']
+ ]
+ demo = PMGTableWidget()
+
+ demo.set_data_2d(list_to_display)
+
+ demo.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/widgets/basic/tables/translations/qt_zh_CN.ts b/pyminer/widgets/widgets/basic/tables/translations/qt_zh_CN.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f12b9f11e79c9d8de612129914840ed17ddb2489
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/tables/translations/qt_zh_CN.ts
@@ -0,0 +1,89 @@
+
+
+
+ PMGTableViewer
+
+
+ Slice
+ 切片
+
+
+
+ Save
+ 保存
+
+
+
+ Invalid Input
+ 无效输入
+
+
+
+ invalid character "%s" in slicing statement.
+ 输入的语句含有无效字符 "%s"。
+
+
+
+ Help
+ 帮助
+
+
+
+ Go To Cell
+ 前往单元格
+
+
+
+ Input Row
+ 输入跳转到的行
+
+
+
+ Target Row No.:({min}~{max})
+ 目标行号:(范围{min}~{max})
+
+
+
+ PMTableView
+
+
+ Insert Row
+ 插入行
+
+
+
+ Delete Row
+ 删除行
+
+
+
+ Insert Column
+ 插入列
+
+
+
+ Delete Column
+ 删除列
+
+
+
+ Input Column Title
+ 输入列名称
+
+
+
+ Title
+ 标题
+
+
+
+ Warning
+ 警告
+
+
+
+ Input New Value
+ 输入新值
+
+
+
diff --git a/pyminer/widgets/widgets/basic/texts/__init__.py b/pyminer/widgets/widgets/basic/texts/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..139597f9cb07c5d48bed18984ec4747f4b4f3438
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/texts/__init__.py
@@ -0,0 +1,2 @@
+
+
diff --git a/pyminer/widgets/widgets/basic/texts/statusreport/__init__.py b/pyminer/widgets/widgets/basic/texts/statusreport/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..3fbb7b8c18df9da9886757cc2757147bd455b94c
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/texts/statusreport/__init__.py
@@ -0,0 +1 @@
+from .errroreport import show_error, ReportWidget
diff --git a/pyminer/widgets/widgets/basic/texts/statusreport/errroreport.py b/pyminer/widgets/widgets/basic/texts/statusreport/errroreport.py
new file mode 100644
index 0000000000000000000000000000000000000000..17eb56ee1d64ee4faedfeb1c9ca8b979584672a9
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/texts/statusreport/errroreport.py
@@ -0,0 +1,31 @@
+from PySide2.QtWidgets import QWidget, QDialog, QVBoxLayout, QTextBrowser, QLabel, QPushButton
+
+
+class ReportWidget(QDialog):
+ def __init__(self, parent: QWidget = None):
+ super(ReportWidget, self).__init__(parent)
+ self.setLayout(QVBoxLayout())
+ self.label_brief = QLabel()
+ self.label_brief.setWordWrap(True)
+ self.layout().addWidget(self.label_brief)
+ self.detailed_info_show = QTextBrowser()
+ self.layout().addWidget(self.detailed_info_show)
+ self.ok_button = QPushButton()
+ self.ok_button.setText(self.tr('Ok'))
+ self.layout().addWidget(self.ok_button)
+
+ self.ok_button.clicked.connect(self.close)
+ self.setMinimumWidth(400)
+
+ def show_info(self, brief: str, detailed: str, title=''):
+ if title == '':
+ title = self.tr('Info')
+ self.label_brief.setText(brief)
+ self.detailed_info_show.setText(detailed)
+ self.setWindowTitle(title)
+
+
+def show_error(parent: QWidget, brief: str, detailed: str, title: str = ''):
+ rw = ReportWidget(parent)
+ rw.show_info(brief, detailed, title)
+ rw.exec_()
diff --git a/pyminer/widgets/widgets/basic/texts/webeditors/__init__.py b/pyminer/widgets/widgets/basic/texts/webeditors/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/widgets/widgets/basic/texts/webeditors/editor.py b/pyminer/widgets/widgets/basic/texts/webeditors/editor.py
new file mode 100644
index 0000000000000000000000000000000000000000..cb05f4efae820ed4f6baabcd375116c2043b3337
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/texts/webeditors/editor.py
@@ -0,0 +1,412 @@
+import os
+import shutil
+import sys
+
+sys.argv.append('--remote-debugging-port=10086')
+from time import time
+import json
+import qdarkstyle
+import qdarkstyle.style_rc # 导入pdarkstyle资源文件
+from PySide2 import QtCore
+from PySide2.QtCore import Qt, QPoint, QUrl, Signal, QTimer, QEventLoop
+from PySide2.QtWebChannel import QWebChannel
+from PySide2.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings
+from PySide2.QtWidgets import *
+from PySide2.QtCore import Slot
+
+strategy_path = os.path.join(os.getcwd(), 'strategy') # 用于存放编辑文件的文件夹
+editor_path = os.path.join(os.path.dirname(__file__), 'quant', 'python_editor', 'editor.htm')
+
+
+class WebEngineView(QWebEngineView):
+ customSignal = Signal(str, str)
+ saveSignal = Signal()
+ signal_open_file = Signal(str, str)
+ signal_request_text = Signal()
+ signal_text_got = Signal(str)
+ signal_save_as = Signal(str)
+ signal_set_autocomplete_apis = Signal(str)
+
+ def __init__(self, *args, **kwargs):
+ super(WebEngineView, self).__init__(*args, **kwargs)
+ self._untitled_id = 0
+ self.initSettings()
+ self.channel = QWebChannel(self)
+ # 把自身对象传递进去
+ self.channel.registerObject('Bridge', self)
+ # 设置交互接口
+ self.page().setWebChannel(self.channel)
+
+ # self.signal_set_autocomplete_apis.emit({"keywords": ["aaaaa", "bbbbbb"]})
+
+ @Slot(str, str)
+ def on_text_received(self, path, text):
+ print(text)
+ self.signal_text_got.emit(text)
+
+ # 注意pyqtSlot用于把该函数暴露给js可以调用
+ @Slot(str)
+ def print_from_js(self, text):
+ print('print from js', text)
+
+ @Slot(str, str)
+ def callFromJs(self, file, text):
+ print('call from js!')
+ try:
+ with open(file, mode='w', encoding='utf-8') as f:
+ f.write(text.replace('\r', ''))
+ f.close()
+ except Exception as e:
+ print(e)
+
+ @Slot(str, str)
+ def on_save(self, file_name: str, text: str):
+ if os.path.isabs(file_name):
+ pass
+ else:
+ file_name, ext = QFileDialog.getSaveFileName(self, "aaa", "/home/hzy/Desktop", "All Files(*)")
+ print(file_name, text)
+ with open(file_name, 'w') as f:
+ f.write(text)
+ self.signal_save_as.emit(file_name)
+
+ def open_file(self, file: str):
+ """
+ 打开文件
+ Args:
+ file:
+
+ Returns:
+
+ """
+ with open(file, 'r', encoding='utf-8') as f:
+ text = f.read()
+ self.signal_open_file.emit(file, text)
+
+ def new_file(self):
+ self._untitled_id += 1
+ self.signal_open_file.emit("Untitled-%d.py" % self._untitled_id, "")
+
+ def sendCustomSignal(self, file):
+ # 发送自定义信号
+ with open(file, 'r', encoding='utf-8') as f:
+ text = f.read()
+ self.customSignal.emit(file, text)
+
+ def sendSaveSignal(self):
+ self.saveSignal.emit()
+
+ # @Slot(str)
+ # @Slot(QUrl)
+ def load(self, url):
+ '''
+ eg: load("https://PySide2.com")
+ :param url: 网址
+ '''
+ return super(WebEngineView, self).load(QUrl(url))
+
+ def initSettings(self):
+ '''
+ eg: 初始化设置
+ '''
+ # 获取浏览器默认设置
+ settings = QWebEngineSettings.globalSettings()
+ # 设置默认编码utf8
+ settings.setDefaultTextEncoding("utf-8")
+ # 自动加载图片,默认开启
+ # settings.setAttribute(QWebEngineSettings.AutoLoadImages,True)
+ # 自动加载图标,默认开启
+ # settings.setAttribute(QWebEngineSettings.AutoLoadIconsForPage,True)
+ # 开启js,默认开启
+ # settings.setAttribute(QWebEngineSettings.JavascriptEnabled,True)
+ # js可以访问剪贴板
+ settings.setAttribute(
+ QWebEngineSettings.JavascriptCanAccessClipboard, True)
+ # js可以打开窗口,默认开启
+ # settings.setAttribute(QWebEngineSettings.JavascriptCanOpenWindows,True)
+ # 链接获取焦点时的状态,默认开启
+ # settings.setAttribute(QWebEngineSettings.LinksIncludedInFocusChain,True)
+ # 本地储存,默认开启
+ # settings.setAttribute(QWebEngineSettings.LocalStorageEnabled,True)
+ # 本地访问远程
+ settings.setAttribute(
+ QWebEngineSettings.LocalContentCanAccessRemoteUrls, True)
+ # 本地加载,默认开启
+ # settings.setAttribute(QWebEngineSettings.LocalContentCanAccessFileUrls,True)
+ # 监控负载要求跨站点脚本,默认关闭
+ # settings.setAttribute(QWebEngineSettings.XSSAuditingEnabled,False)
+ # 空间导航特性,默认关闭
+ # settings.setAttribute(QWebEngineSettings.SpatialNavigationEnabled,False)
+ # 支持平超链接属性,默认关闭
+ # settings.setAttribute(QWebEngineSettings.HyperlinkAuditingEnabled,False)
+ # 使用滚动动画,默认关闭
+ settings.setAttribute(QWebEngineSettings.ScrollAnimatorEnabled, True)
+ # 支持错误页面,默认启用
+ # settings.setAttribute(QWebEngineSettings.ErrorPageEnabled, True)
+ # 支持插件,默认关闭
+ settings.setAttribute(QWebEngineSettings.PluginsEnabled, True)
+ # 支持全屏应用程序,默认关闭
+ settings.setAttribute(
+ QWebEngineSettings.FullScreenSupportEnabled, True)
+ # 支持屏幕截屏,默认关闭
+ settings.setAttribute(QWebEngineSettings.ScreenCaptureEnabled, True)
+ # 支持html5 WebGl,默认开启
+ settings.setAttribute(QWebEngineSettings.WebGLEnabled, True)
+ # 支持2d绘制,默认开启
+ settings.setAttribute(
+ QWebEngineSettings.Accelerated2dCanvasEnabled, True)
+ # 支持图标触摸,默认关闭
+ settings.setAttribute(QWebEngineSettings.TouchIconsEnabled, True)
+
+
+class Editor(QWidget):
+ def __init__(self):
+ super().__init__()
+ self.mainLayout = QGridLayout() # 上方布局
+ self.bottomLayout = QGridLayout() # 下方布局
+ self.create_stragety_vbox()
+ self.create_content_vbox()
+ self.mainLayout = QGridLayout() # 主布局为垂直布局
+ self.mainLayout.setSpacing(5) # 主布局添加补白
+
+ self.mainLayout.addWidget(self.strategy_vbox, 0, 0, 1, 1)
+ self.mainLayout.addWidget(self.content_vbox, 0, 1, 1, 1)
+
+ self.mainLayout.setColumnStretch(0, 2)
+ self.mainLayout.setColumnStretch(1, 6)
+
+ self.setLayout(self.mainLayout)
+ self.setGeometry(200, 200, 1200, 800)
+ self.setWindowTitle('编辑器')
+ self.show()
+
+ # 策略信息
+ self.strategy_path = None
+
+ with open(r'qdark.qss', encoding='utf-8') as f:
+ self.setStyleSheet(f.read())
+
+ def create_stragety_vbox(self):
+ # 策略树
+ self.strategy_vbox = QGroupBox('策略')
+ self.strategy_layout = QHBoxLayout()
+ self.strategy_tree = QTreeView()
+ self.model = QFileSystemModel()
+ self.model.setRootPath(QtCore.QDir.rootPath())
+ self.strategy_tree.setModel(self.model)
+ self.strategy_tree.setEditTriggers(QAbstractItemView.NoEditTriggers)
+ self.strategy_tree.setRootIndex(self.model.index(r'/home/hzy/Desktop'))
+ # self.strategy_tree.setColumnCount(1)
+ # self.strategy_tree.setHeaderLabels(['策略'])
+ self.strategy_tree.setDragDropMode(QAbstractItemView.InternalMove)
+ self.model.setReadOnly(False)
+ self.strategy_tree.setHeaderHidden(True)
+ self.strategy_tree.hideColumn(1)
+ self.strategy_tree.hideColumn(2)
+ self.strategy_tree.hideColumn(3)
+
+ self.strategy_tree.setContextMenuPolicy(Qt.CustomContextMenu)
+ self.strategy_tree.customContextMenuRequested[QPoint].connect(self.strategy_tree_right_menu)
+
+ # for d in os.listdir(strategy_path):
+ # root = QTreeWidgetItem(self.strategy_tree)
+ # root.setText(0, d)
+ # for file in os.listdir(os.path.join(strategy_path, d)):
+ # child = QTreeWidgetItem(root)
+ # child.setText(0, file)
+ # self.list_strategy(strategy_path, self.strategy_tree)
+ self.strategy_tree.doubleClicked.connect(self.strategy_tree_clicked)
+ self.strategy_layout.addWidget(self.strategy_tree)
+ self.strategy_vbox.setLayout(self.strategy_layout)
+
+ # 策略右键菜单
+ def strategy_tree_right_menu(self, point):
+ self.strategy_tree.popMenu = QMenu()
+ self.strategy_tree.addType = QMenu(self.strategy_tree.popMenu)
+ self.strategy_tree.addType.setTitle('新建')
+ rename = QAction('重命名', self.strategy_tree)
+ delete = QAction('删除', self.strategy_tree)
+ add_strategy = QAction('新建策略')
+ add_group = QAction('新建分组')
+ refresh = QAction('刷新', self.strategy_tree)
+ self.strategy_tree.popMenu.addMenu(self.strategy_tree.addType)
+ self.strategy_tree.addType.addAction(add_strategy)
+ self.strategy_tree.addType.addAction(add_group)
+ self.strategy_tree.popMenu.addAction(rename)
+ self.strategy_tree.popMenu.addAction(delete)
+ self.strategy_tree.popMenu.addAction(refresh)
+
+ # 右键动作
+ action = self.strategy_tree.popMenu.exec_(self.strategy_tree.mapToGlobal(point))
+ if action == add_strategy:
+ index = self.strategy_tree.currentIndex()
+ model = index.model() # 请注意这里可以获得model的对象
+ item_path = model.filePath(index)
+ if item_path and os.path.isdir(item_path):
+ value = ''
+ while True:
+ value, ok = QInputDialog.getText(self, '新建文件', '策略名称', QLineEdit.Normal)
+ path = os.path.join(item_path, value + '.py')
+ if os.path.exists(path) and ok:
+ QMessageBox.warning(self, '提示', '策略名在选择的分组%s已经存在!!!' % value, QMessageBox.Yes)
+ elif not ok:
+ break
+ else:
+ with open(path, 'w', encoding='utf-8') as w:
+ pass
+ break
+ elif not os.path.isdir(item_path):
+ value = ''
+ while True:
+ value, ok = QInputDialog.getText(self, '新建文件', '策略名称', QLineEdit.Normal)
+ path = os.path.join(os.path.split(item_path)[1], value + '.py')
+ if os.path.exists(path) and ok:
+ QMessageBox.warning(self, '提示', '策略名在选择的分组%s已经存在!!!' % value, QMessageBox.Yes)
+ elif not ok:
+ break
+ else:
+ with open(path, 'w', encoding='utf-8') as w:
+ pass
+ break
+ else:
+ QMessageBox.warning(self, '提示', '请选择分组!!!', QMessageBox.Yes)
+
+ elif action == add_group:
+ value = ''
+ flag = self.strategy_tree.indexAt(point) # 判断鼠标点击位置标志位
+ while True:
+ if not flag.isValid(): # 鼠标点击位置不在目录树叶子上
+ item_path = strategy_path # 新建文件夹位置在根目录
+ else:
+ index = self.strategy_tree.currentIndex()
+ model = index.model() # 请注意这里可以获得model的对象
+ item_path = model.filePath(index)
+ value, ok = QInputDialog.getText(self, '新建文件夹', '分组名称', QLineEdit.Normal, value)
+ if os.path.isdir(item_path):
+ path = os.path.join(item_path, value)
+ else:
+ path = os.path.join(os.path.split(item_path)[0], value)
+ if os.path.exists(path) and ok:
+ QMessageBox.warning(self, '提示', '分组%s已经存在!!!' % value, QMessageBox.Yes)
+ elif not ok:
+ break
+ else:
+ os.mkdir(path)
+ break
+
+ elif action == refresh:
+ index = self.strategy_tree.currentIndex()
+ model = index.model() # 请注意这里可以获得model的对象
+ model.dataChanged.emit(index, index)
+ elif action == rename:
+ index = self.strategy_tree.currentIndex()
+ model = index.model() # 请注意这里可以获得model的对象
+ item_path = model.filePath(index)
+ if not os.path.isdir(item_path): # 修改策略名
+ value = ''
+ (file_path, filename) = os.path.split(item_path)
+ while True:
+ value, ok = QInputDialog.getText(self, '修改%s策略名' % filename, '策略名称', QLineEdit.Normal, value)
+ new_path = os.path.join(file_path, value + '.py')
+ if os.path.exists(new_path) and ok:
+ QMessageBox.warning(self, '提示', '策略名在此分组中%s已经存在!!!' % value, QMessageBox.Yes)
+ elif not ok:
+ break
+ else:
+ os.rename(item_path, new_path)
+ break
+ else:
+ value = ''
+ (dir_path, dir_name) = os.path.split(item_path)
+ while True:
+ value, ok = QInputDialog.getText(self, '修改%s文件夹' % dir_name, '分组名称', QLineEdit.Normal, value)
+ new_path = os.path.join(dir_path, value)
+ if os.path.exists(new_path) and ok:
+ QMessageBox.warning(self, '提示', '分组%s已经存在!!!' % value, QMessageBox.Yes)
+ elif not ok:
+ break
+ else:
+ os.rename(item_path, new_path)
+ break
+ elif action == delete:
+ index = self.strategy_tree.currentIndex()
+ model = index.model() # 请注意这里可以获得model的对象
+ item_path = model.filePath(index)
+ if item_path and os.path.isdir(item_path):
+ reply = QMessageBox.question(self, '提示', '确定删除分组及目录下的所有文件吗?', QMessageBox.Yes | QMessageBox.No)
+ if reply == QMessageBox.Yes:
+ shutil.rmtree(item_path)
+ elif item_path and not os.path.isdir(item_path):
+ reply = QMessageBox.question(self, '提示', '确定删除文件%s吗?' % item_path, QMessageBox.Yes | QMessageBox.No)
+ if reply == QMessageBox.Yes:
+ os.remove(item_path)
+ else:
+ pass
+ else:
+ pass
+
+ def create_content_vbox(self):
+ self.content_vbox = QGroupBox('内容')
+ self.content_layout = QGridLayout()
+ self.save_btn = QPushButton('保存')
+ self.run_btn = QPushButton('运行')
+ self.update_api = QPushButton('更新api')
+ self.refresh_btn = QPushButton('重新加载')
+ self.new_btn = QPushButton('新建')
+ self.refresh_btn.clicked.connect(
+ lambda: self.contentEdit.load(QUrl.fromLocalFile(os.path.abspath(editor_path))))
+ self.contentEdit = WebEngineView()
+ self.contentEdit.load(QUrl.fromLocalFile(os.path.abspath(editor_path)))
+ self.save_btn.setMaximumSize(80, 60)
+ self.run_btn.setMaximumSize(80, 60)
+ self.content_layout.addWidget(self.run_btn, 0, 1, 1, 1)
+ self.content_layout.addWidget(self.save_btn, 0, 2, 1, 1)
+ self.content_layout.addWidget(self.refresh_btn, 0, 3, 1, 1)
+ self.content_layout.addWidget(self.new_btn, 0, 4, 1, 1)
+ self.content_layout.addWidget(self.update_api, 0, 5, 1, 1)
+ self.content_layout.addWidget(self.contentEdit, 2, 0, 1, 5)
+ self.content_vbox.setLayout(self.content_layout)
+ self.save_btn.clicked.connect(self.emit_custom_signal)
+ self.new_btn.clicked.connect(lambda: self.contentEdit.new_file())
+ self.update_api.clicked.connect(
+ lambda: self.contentEdit.signal_set_autocomplete_apis.emit(
+ json.dumps({"python": {"keywords": {"mode": "add", "content": ["import", "def", "class"]}}})))
+
+ def emit_custom_signal(self):
+ self.contentEdit.sendSaveSignal()
+
+ def strategy_tree_clicked(self):
+ # 策略双击槽函数
+ index = self.strategy_tree.currentIndex()
+ model = index.model() # 请注意这里可以获得model的对象
+ item_path = model.filePath(index)
+ if not os.path.isdir(item_path):
+ self.contentEdit.open_file(item_path) # sendCustomSignal(item_path)
+ self.strategy_path = item_path
+
+ def get_text(self) -> str:
+ """
+ 获取文本内容
+ :return:
+ """
+ self.contentEdit.signal_request_text.emit()
+ self.loop = QEventLoop()
+ _text = ''
+
+ def f(text):
+ self.loop.quit()
+ _text = text
+
+ self.contentEdit.signal_text_got.connect(f)
+ self.loop.exec_()
+ return _text
+
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+ form = Editor()
+ form.show()
+ timer = QTimer()
+ timer.start(1000)
+ # timer.timeout.connect(lambda: print(form.get_text()))
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/widgets/basic/trees/__init__.py b/pyminer/widgets/widgets/basic/trees/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..47682075cde917463412b97af74552fff287161c
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/trees/__init__.py
@@ -0,0 +1,3 @@
+from .filetree import *
+from .jsontree import PMGJsonTree
+from .treecheck import *
\ No newline at end of file
diff --git a/pyminer/widgets/widgets/basic/trees/filetree.py b/pyminer/widgets/widgets/basic/trees/filetree.py
new file mode 100644
index 0000000000000000000000000000000000000000..86bdf6ec5c1e47d0a5eff8ddf05c01ffafd96807
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/trees/filetree.py
@@ -0,0 +1,564 @@
+import os
+from typing import List
+
+from PySide2.QtCore import Qt, QModelIndex, Signal, QLocale, QTranslator, QMimeData, QUrl
+from PySide2.QtGui import QCursor, QKeySequence, QClipboard
+from PySide2.QtWidgets import QTreeView, QFileSystemModel, QMenu, QApplication, QMessageBox, QInputDialog, \
+ QLineEdit, QDialog, QVBoxLayout, QDialogButtonBox, QPushButton, QHBoxLayout, QLabel, QShortcut, QCheckBox
+from widgets.widgets.basic.trees.treecheck import PMCheckTree
+from widgets.utilities.source.translation import create_translator
+from widgets.utilities.platform import open_file_manager
+
+
+class InputFilenameDialog(QDialog):
+ def __init__(self, parent=None, title: str = '', ext: str = ''):
+ super().__init__(parent)
+ layout = QVBoxLayout()
+ self.name_input = QLineEdit()
+ self.ok_button = QPushButton(self.tr('Ok'))
+ self.cancel_button = QPushButton(self.tr('Cancel'))
+ self.button_layout = QHBoxLayout()
+ self.button_layout.addWidget(self.ok_button)
+ self.button_layout.addWidget(self.cancel_button)
+ layout.addWidget(QLabel(title))
+ layout.addWidget(self.name_input)
+ layout.addLayout(self.button_layout)
+ initial_name = 'Untitled'
+ if ext != '':
+ ext = '.' + ext
+ self.name_input.setText(initial_name + ext)
+ self.name_input.setSelection(0, len(initial_name))
+ else:
+ self.name_input.setText(initial_name)
+ self.name_input.setSelection(0, len(initial_name))
+ self.ok_button.clicked.connect(self.on_ok)
+ self.cancel_button.clicked.connect(self.on_cancel)
+ self.status = False
+ self.setLayout(layout)
+
+ def on_ok(self):
+ self.status = True
+ self.close()
+
+ def on_cancel(self):
+ self.status = False
+ self.close()
+
+
+class PMFileSystemModel(QFileSystemModel):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ # self.setFilter(QDir.Dirs | QDir.AllDirs)
+
+ def headerData(self, p_int, qt_orientation, role=None):
+ if (p_int == 0) and (role == Qt.DisplayRole):
+ return self.tr('Name')
+ elif (p_int == 1) and (role == Qt.DisplayRole):
+ return self.tr('Size')
+ elif (p_int == 2) and (role == Qt.DisplayRole):
+ return self.tr('Type')
+ elif (p_int == 3) and (role == Qt.DisplayRole):
+ return self.tr('Last Modified')
+ else:
+ return super().headerData(p_int, qt_orientation, role)
+
+ def columnCount(self, parent: QModelIndex = ...) -> int:
+ return 1
+
+
+class PMGFilesTreeview(QTreeView):
+ """
+ 文件树
+ """
+ open_signal = Signal(str)
+ open_folder_signal = Signal(str)
+ new_file_signal = Signal(str)
+ new_folder_signal = Signal(str)
+ delete_file_signal = Signal(str)
+ rename_file_signal = Signal(str, str)
+
+ signal_ext_filter_adapt = Signal(bool)
+ signal_ext_filter_changed = Signal(dict)
+
+ def __init__(self, initial_dir: str = '', parent=None):
+ super().__init__(parent)
+ self.initial_dir = initial_dir
+ self.setup_ui()
+ self.bind_events()
+
+ self.filter_exts = True
+ self.exts_to_filter = {
+ 'Program Scripts': {'.pyx': True, '.py': True, '.c': True, '.pyi': True, '.dll': True,
+ '.h': True, '.cpp': True, '.ipynb': True, '.sh': True, '.cmd': True, '.bat': True
+ },
+ 'Documents': {'.txt': True, '.md': True, '.doc': True, '.docx': True, '.ppt': True, '.pptx': True,
+ '.html': True
+ },
+ 'Data Files': {'.csv': True, '.xls': True, '.xlsx': True, '.tab': True, '.dat': True, '.tsv': True,
+ '.sav': True, '.zsav': True, '.sas7bdat': True, '.pkl': True, '.json': True, '.mat': True,
+ '.pmjson': True, '.pmd': True},
+ 'Medias': {'.mp3': False, '.mp4': False, '.avi': False, '.wma': False, '.png': True, '.jpg': True,
+ '.svg': True},
+ 'Resources': {'.qm': True, '.ts': True}
+
+ }
+
+ def setup_ui(self):
+ """
+ 界面初始化
+ :return:
+ """
+
+ self.translator = create_translator(
+ path=os.path.join(os.path.dirname(__file__), 'translations',
+ 'qt_{0}.qm'.format(QLocale.system().name()))) # translator
+
+ self.setTabKeyNavigation(True)
+ self.setDragEnabled(True)
+ self.setDragDropOverwriteMode(True)
+ self.setAlternatingRowColors(False)
+ self.setUniformRowHeights(True)
+ self.setSortingEnabled(True)
+ self.setAnimated(True)
+ self.setAllColumnsShowFocus(False)
+ self.setWordWrap(False)
+ self.setHeaderHidden(False)
+ self.setObjectName("treeView_files")
+ self.header().setSortIndicatorShown(True)
+
+ self.model = PMFileSystemModel()
+ self.model.setRootPath(self.initial_dir)
+
+ self.setModel(self.model)
+ self.setRootIndex(self.model.index(self.initial_dir))
+ self.setAnimated(False)
+ self.setSortingEnabled(True) # 启用排序
+ self.header().setSortIndicatorShown(True) # 启用标题排序
+ self.setContextMenuPolicy(Qt.CustomContextMenu)
+ self.customContextMenuRequested.connect(self.show_context_menu)
+ self.init_context_menu()
+
+ def bind_events(self):
+ """
+ 回调、事件与信号初始化
+ :return:
+ """
+ self.doubleClicked.connect(lambda index: self.on_open())
+
+ self.openAction.triggered.connect(self.on_open)
+ self.importAction.triggered.connect(self.on_import)
+ self.renameAction.triggered.connect(self.on_rename)
+ self.deleteAction.triggered.connect(self.on_delete)
+
+ self.copyAction.triggered.connect(self.on_copy)
+ self.pasteAction.triggered.connect(self.on_paste)
+ self.filterAction.triggered.connect(self.show_ext_filter_selection_dialog)
+
+ self.copyPathAction.triggered.connect(self.copy_path)
+ self.new_file_action.triggered.connect(lambda: self.on_new_file(''))
+ self.new_python_file_action.triggered.connect(lambda: self.on_new_file('py'))
+ self.new_folder_action.triggered.connect(self.on_new_folder)
+ self.open_file_manager_action.triggered.connect(self.on_open_file_manager)
+
+ self.rename_shortcut.activated.connect(self.on_rename)
+ self.paste_shortcut.activated.connect(self.on_paste)
+ self.copy_shortcut.activated.connect(self.on_copy)
+ self.open_shortcut.activated.connect(self.on_open)
+ self.delete_shortcut.activated.connect(self.on_delete)
+ self.goto_parent_path_shortcut.activated.connect(self.slot_goto_parent_path)
+
+ self.customContextMenuRequested.connect(self.show_context_menu)
+
+ def init_context_menu(self):
+ """
+ 初始化右键菜单
+ :return:
+ """
+ self.contextMenu = QMenu(self)
+ self.openAction = self.contextMenu.addAction(self.tr('Open'))
+
+ self.importAction = self.contextMenu.addAction(self.tr('Import'))
+ self.importAction.setEnabled(False)
+
+ self.new_file_or_folder_menu = QMenu(self.tr('New..'))
+ self.contextMenu.addMenu(self.new_file_or_folder_menu)
+ self.new_file_action = self.new_file_or_folder_menu.addAction(self.tr('File..'))
+ self.new_python_file_action = self.new_file_or_folder_menu.addAction(self.tr('Python File'))
+ self.new_folder_action = self.new_file_or_folder_menu.addAction(self.tr('Folder'))
+ self.new_file_or_folder_menu.addSeparator()
+
+ self.copyAction = self.contextMenu.addAction(self.tr("Copy"))
+ self.pasteAction = self.contextMenu.addAction(self.tr("Paste"))
+ self.pasteAction.setEnabled(False)
+
+ self.renameAction = self.contextMenu.addAction(self.tr('Rename'))
+ self.deleteAction = self.contextMenu.addAction(self.tr('Delete'))
+
+ self.filterAction = self.contextMenu.addAction(self.tr('Filter'))
+ self.copyPathAction = self.contextMenu.addAction(self.tr('Copy Path'))
+
+ self.open_file_manager_action = self.contextMenu.addAction(self.tr('Open Explorer'))
+
+ self.renameAction.setShortcut(QKeySequence('F2'))
+ self.copyAction.setShortcut(QKeySequence('Ctrl+C'))
+ self.pasteAction.setShortcut(QKeySequence('Ctrl+V'))
+ self.deleteAction.setShortcut(QKeySequence('Delete'))
+
+ self.rename_shortcut = QShortcut(QKeySequence('F2'), self, context=Qt.WidgetShortcut)
+ self.copy_shortcut = QShortcut(QKeySequence.Copy, self, context=Qt.WidgetShortcut)
+ self.paste_shortcut = QShortcut(QKeySequence.Paste, self, context=Qt.WidgetShortcut)
+ self.delete_shortcut = QShortcut(QKeySequence('Delete'), self, context=Qt.WidgetShortcut)
+ self.open_shortcut = QShortcut(QKeySequence('Return'), self, context=Qt.WidgetShortcut)
+ self.goto_parent_path_shortcut = QShortcut(QKeySequence('Backspace'), self, context=Qt.WidgetShortcut)
+
+ def show_context_menu(self):
+ """
+ 显示上下文右键菜单
+ :return:
+ """
+ self.contextMenu.popup(QCursor.pos())
+ self.contextMenu.show()
+
+ def get_current_file_path(self):
+ """
+ 获取当前选中文件的路径。
+ 如果当前没有选中的文件,就返回根路径。
+ :return:
+ """
+ if len(self.selectedIndexes()) > 0:
+ index = self.currentIndex()
+ file_info = self.model.fileInfo(index)
+ return file_info.absoluteFilePath()
+ else:
+ return self.get_root_path()
+
+ def get_root_path(self):
+ """
+ 获取根路径
+ :return:
+ """
+ return self.model.rootPath()
+
+ def set_item_focus(self, file_path: str):
+ """
+ set item focus in TreeView
+ :param file_path: File or Dir
+ :return:
+ """
+ self.setCurrentIndex(self.model.index(file_path))
+
+ def on_open_file_manager(self):
+ path = self.get_current_file_path()
+ print(path)
+ if os.path.isdir(path):
+ open_file_manager(path)
+ else:
+ open_file_manager(os.path.dirname(path))
+
+ # if os.path.exists(new_folder_path):
+ # self.set_item_focus(new_folder_path) # 设置focus liugang 200923
+ # QMessageBox.critical(self, self.tr('Error'),
+ # self.tr('Folder %s already exists!' % name))
+ # return
+ # else:
+ # os.mkdir(new_folder_path)
+ # self.new_folder_signal[str].emit(new_folder_path)
+ # self.set_item_focus(new_folder_path) # 设置focus liugang 200923
+
+ def on_new_folder(self):
+ """
+ 新建文件夹时出发的回调
+ :return:
+ """
+ path = self.get_current_file_path()
+ name, stat = QInputDialog.getText(self, self.tr('Please Input folder name'), '', QLineEdit.Normal, '')
+ if name.find('.') != -1:
+ QMessageBox.critical(self, self.tr('Error'),
+ self.tr('Folder name %s is illeagal!' % name))
+ return
+ if stat:
+ if os.path.isdir(path):
+ new_folder_path = os.path.join(path, name)
+ else:
+ new_folder_path = os.path.join(os.path.dirname(path), name)
+
+ if os.path.exists(new_folder_path):
+ self.set_item_focus(new_folder_path) # 设置focus liugang 200923
+ QMessageBox.critical(self, self.tr('Error'),
+ self.tr('Folder %s already exists!' % name))
+ return
+ else:
+ os.mkdir(new_folder_path)
+ self.new_folder_signal[str].emit(new_folder_path)
+ self.set_item_focus(new_folder_path) # 设置focus liugang 200923
+
+ def on_new_file(self, ext: str = ''):
+ """
+ 新建文件时触发的回调
+ :return:
+ """
+ path = self.get_current_file_path()
+ dlg = InputFilenameDialog(parent=self, title=self.tr('Please input file name'), ext=ext)
+
+ dlg.exec_()
+ name = dlg.name_input.text()
+ stat = dlg.status
+ if stat:
+ if os.path.isdir(path):
+ new_file_path = os.path.join(path, name)
+ else:
+ new_file_path = os.path.join(os.path.dirname(path), name)
+
+ if os.path.exists(new_file_path):
+ self.set_item_focus(new_file_path) # 设置focus liugang 200923
+ QMessageBox.critical(self, self.tr('Error'),
+ self.tr('File %s already exists!' % name))
+ return
+ with open(new_file_path, 'wb') as f:
+ f.close()
+ self.new_file_signal[str].emit(new_file_path)
+
+ self.set_item_focus(new_file_path)
+ self.on_open() # 创建文件后打开 liugang 200923
+
+ def on_open(self):
+ """
+ 点击‘open’时候触发的回调, 等效的方式还有双击以及按下回车键。
+ :return:
+ """
+ path = self.get_current_file_path()
+ if os.path.isdir(path):
+ self.open_folder_signal.emit(path)
+ else:
+ self.open_signal[str].emit(path)
+
+ def on_import(self):
+ """
+
+ :return:
+ """
+ pass
+
+ def on_rename(self):
+ """
+ 点击’重命名‘时候的回调。
+ :return:
+ """
+ from widgets import rename_file
+ path = self.get_current_file_path()
+ basename = os.path.basename(path)
+ dir_name = os.path.dirname(path)
+ name, stat = QInputDialog.getText(self, self.tr('Please Input file name'), '', QLineEdit.Normal, basename)
+ if stat:
+ new_absolute_path = os.path.join(dir_name, name)
+ rename_result = rename_file(path, new_absolute_path)
+ if not rename_result:
+ QMessageBox.critical(self, self.tr('Error'),
+ self.tr('Unable to Rename this file.'))
+ else:
+ self.rename_file_signal[str, str].emit(path, new_absolute_path)
+
+ def on_delete(self):
+ """
+ 点击’删除‘时的回调
+ :return:
+ """
+ from widgets import move_to_trash
+ path = self.get_current_file_path()
+
+ moved_successful = move_to_trash(path)
+ if not moved_successful:
+ QMessageBox.critical(self, self.tr('Error'),
+ self.tr('Unable to Move this file to recycle bin.'))
+ else:
+ self.delete_file_signal[str].emit(path)
+
+ def on_copy(self):
+ """
+ copy file or dir , save path in pasteAction data.
+ :return:
+ """
+ path = self.get_current_file_path()
+ self.pasteAction.setEnabled(True)
+ self.pasteAction.setData(path)
+
+ data = QMimeData()
+ data.setUrls([QUrl.fromLocalFile(path)]) # 复制到系统剪贴板
+
+ clip = QApplication.clipboard()
+ clip.setMimeData(data)
+
+ def on_paste(self):
+ """
+ Paste file or dir in pasteAction data
+ :return:
+ """
+ from widgets import copy_paste
+ path = self.get_current_file_path()
+ target_dir_name = path if os.path.isdir(path) else os.path.dirname(path)
+ url: QUrl = None
+
+ mimedata = QApplication.clipboard().mimeData(mode=QClipboard.Clipboard)
+ print(mimedata)
+ urls: List[QUrl] = mimedata.urls()
+ for url in urls:
+ source_path = url.toLocalFile() # self.pasteAction.data()
+ # File
+ if os.path.isfile(source_path):
+ source_file_name = os.path.basename(source_path)
+ # if exist ,rename to copy_xxx
+ if os.path.isfile(os.path.join(target_dir_name, source_file_name)):
+ target_file_name = "copy_{0}".format(source_file_name)
+ else:
+ target_file_name = source_file_name
+ target_path = os.path.join(target_dir_name, target_file_name)
+ # Directory
+ else:
+ last_dir_name = os.path.split(source_path)[-1]
+ # if exist , rename dir copy_xxxx
+ if os.path.isdir(os.path.join(target_dir_name, last_dir_name)):
+ target_name = "copy_{0}".format(last_dir_name)
+ else:
+ target_name = last_dir_name
+ target_path = os.path.join(target_dir_name, target_name)
+
+ copy_succ = copy_paste(source_path, target_path)
+ if not copy_succ:
+ QMessageBox.critical(self, self.tr('Error'),
+ self.tr('Copy File or Directory Error.'))
+ else:
+ self.set_item_focus(target_path)
+
+ def show_ext_filter_selection_dialog(self):
+
+ self.dlg = QDialog(self)
+ self.dlg.setWindowTitle(self.tr('Extension Name To Show'))
+ self.dlg.setLayout(QVBoxLayout())
+ self.dlg.layout().addWidget(QLabel('过滤文件名'))
+ check_box = QCheckBox()
+ self.dlg.check_box = check_box
+ check_box.setChecked(self.filter_exts)
+ self.dlg.layout().addWidget(check_box)
+ check_box.stateChanged.connect(lambda stat: self.signal_ext_filter_adapt.emit(stat))
+ check_widget = PMCheckTree(data=self.exts_to_filter)
+ self.dlg.check_widget = check_widget
+ self.dlg.layout().addWidget(check_widget)
+ buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+
+ buttonBox.accepted.connect(self.on_ext_filter_changed)
+ buttonBox.rejected.connect(self.dlg.deleteLater)
+ # 清除选择功能不完善目前禁用
+ # button_clear = buttonBox.addButton(self.tr('Clear Filter'), QDialogButtonBox.ApplyRole)
+ # button_clear.clicked.connect(self.clear_ext_filter)
+ self.dlg.layout().addWidget(buttonBox)
+ self.dlg.exec_()
+
+ def on_ext_filter_changed(self):
+ """
+ 当扩展名过滤改变的时候。
+ :return:
+ """
+ self.exts_to_filter = self.dlg.check_widget.get_data()
+ self.filter_exts = self.dlg.check_box.isChecked()
+ self.update_ext_filter()
+ self.dlg.deleteLater()
+ self.signal_ext_filter_changed.emit(self.exts_to_filter)
+
+ def clear_ext_filter(self):
+ self.set_ext_filter(None)
+ self.dlg.deleteLater()
+
+ def update_ext_filter(self):
+ """
+ 刷新扩展名过滤。
+ :return:
+ """
+ ext_list = []
+ for key in self.exts_to_filter.keys():
+ for name in self.exts_to_filter[key].keys():
+ if self.exts_to_filter[key][name]:
+ ext_list.append('*' + name)
+ self.set_ext_filter(ext_list)
+
+ def set_ext_filter(self, ext_names: List[str]):
+ """
+ 文件名过滤
+ 例如要过滤出.py和.pyx文件,就是ext_names=['*.py','*.pyx']
+ discard功能不太完善,目前先禁用。
+ :param ext_names:
+ :return:
+ """
+ if ext_names is not None and self.filter_exts:
+ self.model.setNameFilterDisables(False)
+ self.model.setNameFilters(ext_names)
+ else:
+ self.model.setNameFilterDisables(True)
+ self.model.setNameFilters(["*"])
+
+ def slot_goto_parent_path(self):
+ """
+
+ Returns:
+
+ """
+ root = self.get_root_path()
+ parent = os.path.dirname(root)
+ if os.path.exists(parent):
+ pass
+ self.open_folder_signal.emit(parent)
+
+ def copy_path(self):
+ """
+ 复制当前文件的路径的回调
+ Returns:
+
+ """
+ path = self.get_current_file_path()
+ # data = QMimeData()
+ clipboard = QApplication.clipboard()
+ clipboard.setText(path)
+
+
+class Stack(object):
+
+ def __init__(self):
+ # 创建空列表实现栈
+ self.__list = []
+
+ def is_empty(self):
+ # 判断是否为空
+ return self.__list == []
+
+ def push(self, item):
+ # 压栈,添加元素
+ self.__list.append(item)
+
+ def pop(self):
+ # 弹栈,弹出最后压入栈的元素
+ if self.is_empty():
+ return
+ else:
+ return self.__list.pop()
+
+ def top(self):
+ # 取最后压入栈的元素
+ if self.is_empty():
+ return
+ else:
+ return self.__list[-1]
+
+ def __len__(self):
+ return len(self.__list)
+
+ def __str__(self):
+ return str(self.__list)
+
+
+if __name__ == '__main__':
+ import sys
+
+ app = QApplication(sys.argv)
+
+ tree = PMGFilesTreeview(os.path.join(os.path.expanduser('~'), 'Desktop', 'cloud'), None)
+ tree.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/widgets/basic/trees/jsontree.py b/pyminer/widgets/widgets/basic/trees/jsontree.py
new file mode 100644
index 0000000000000000000000000000000000000000..b2ea4e30a8de5ac23d528095802f7442bb29c276
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/trees/jsontree.py
@@ -0,0 +1,233 @@
+"""
+专门显示JSON的树状控件。
+"""
+import os
+import time
+from typing import Dict, Callable, Any
+
+from PySide2.QtWidgets import QTreeWidget, QTreeWidgetItem, QMenu
+from PySide2.QtCore import Signal, Qt, QTimer, QLocale
+from widgets.utilities.source.translation import create_translator
+
+
+class PMGJsonTree(QTreeWidget):
+ signal_show_data_value = Signal(str)
+ signal_data_saveas = Signal(str)
+ signal_data_open = Signal(str)
+
+ def __init__(self, parent=None):
+ super().__init__(parent=None)
+ self.select_data_callbacks = []
+ self.filter_all_upper_case = False
+ self.filter_all_callables = True
+ self.setup_ui()
+ self.expanding_stat: Dict[str, bool] = {}
+
+ def setup_ui(self):
+ self.translator = create_translator(
+ path=os.path.join(os.path.dirname(__file__), 'translations',
+ 'qt_{0}.qm'.format(QLocale.system().name()))) # translator
+ self.nodes: Dict[str, 'QTreeWidgetItem'] = {}
+ self.setColumnCount(2)
+ header_item = QTreeWidgetItem()
+ header_item.setText(0, self.tr('Name'))
+ self.setColumnWidth(0, 200)
+ header_item.setText(1, self.tr('Value'))
+ header_item.setTextAlignment(0, Qt.AlignCenter)
+ header_item.setTextAlignment(1, Qt.AlignCenter)
+ self.setHeaderItem(header_item)
+ self.auto_expand = False
+
+ self.itemClicked.connect(self.on_item_clicked)
+ self.itemDoubleClicked.connect(self.on_item_double_clicked)
+ self.customContextMenuRequested.connect(self.on_right_clicked)
+ self.setContextMenuPolicy(Qt.CustomContextMenu)
+
+ self.context_menu = QMenu()
+ show_action = self.context_menu.addAction(self.tr('View'))
+ save_action = self.context_menu.addAction(self.tr('Save as '))
+ cancel_action = self.context_menu.addAction(self.tr('Undo'))
+ redo_action = self.context_menu.addAction(self.tr('Redo'))
+ delete_action = self.context_menu.addAction(self.tr('Delete'))
+ show_action.triggered.connect(self.on_show_data_by_context_menu)
+
+ def memorize_expanding_stat(self):
+ for k, node in self.nodes.items():
+ self.expanding_stat[k] = node.isExpanded()
+
+ def on_item_clicked(self, item: QTreeWidgetItem, col: int) -> None:
+ """
+ 点击条目时的回调函数。会顺次执行self.select_data_callbacks,而self.select_data_callbacks
+ 是由插件的add_select_data_callback(self, callback: Callable)方式加入的。
+ """
+ if item.isExpanded():
+ self.collapseItem(item)
+ else:
+ self.expandItem(item)
+
+ if col == 0 and item.text(0) in self.nodes.keys():
+ for callback in self.select_data_callbacks:
+ callback(item.text(0))
+
+ def add_select_data_callback(self, callback: Callable):
+ """
+ 加入回调函数。
+ """
+
+ self.select_data_callbacks.append(callback)
+
+ def on_item_double_clicked(self, item: QTreeWidgetItem, col: int) -> None:
+ """
+ 双击条目的事件,触发数据显示。
+ """
+ if col == 0 and item.text(0) in self.nodes.keys():
+ return
+ # self.show_data(item.text(0))
+
+ def on_right_clicked(self, pos):
+ """
+ 右键点击某一条目的事件,此时会弹出菜单。
+ """
+ item = self.currentItem()
+ if item is not None and item.text(0) in self.nodes.keys():
+ self.context_menu.exec_(self.mapToGlobal(pos))
+
+ def on_show_data_by_context_menu(self, action: 'QAction'):
+ """
+ 右键菜单点击‘显示数据’所触发的事件。
+ """
+ item = self.currentItem()
+ if item is not None and item.text(0) in self.nodes.keys():
+ return
+
+ def autorepr(self, obj: Any, max_length=80) -> str:
+ """
+ 封装了repr方法,数据过长的时候,取较短的。
+ """
+ if isinstance(obj, str):
+ return obj
+ else:
+ return repr(obj).replace('\n', '').replace('\r', '')[:max_length]
+
+ def get_size(self, data: Any):
+ """
+ 获取数据的尺寸。
+ 有‘shape’这一属性的,size就等于data.shape
+ 有‘__len__’这个属性的,返回len(data)
+ 否则返回1.
+ """
+ size = 1
+ if hasattr(data, 'shape'):
+ size = data.shape
+ elif isinstance(data, str):
+ size = 1
+ elif hasattr(data, '__len__'):
+ try:
+ size = len(data)
+ except:
+ pass
+
+ return size
+
+ def set_data_dic(self, data_dic: Dict[str, object]) -> None:
+ """
+ 显示数据
+ :param data_dic:所有数据的字典{变量名:变量对象}
+ :return:
+ """
+ self.expanding_stat = {}
+ self.memorize_expanding_stat()
+ self.clear()
+ self.nodes = {}
+
+ for data_name in data_dic:
+
+ data = data_dic[data_name]
+ text = str(data_name)
+ size = self.get_size(data)
+ """
+ 如果变量不在变量列表中,就新建一个节点,并通过递归方式创建变量的各个属性。
+ """
+ child = QTreeWidgetItem(self)
+ child.item_id = text
+ self.nodes[child.item_id] = child
+ child.setText(0, text)
+ self.try_expand_item(child)
+ if isinstance(data, dict):
+ self.set_data_view(data, child)
+
+ def get_hier(self, item: QTreeWidgetItem) -> int:
+ i = 0
+ all_nodes = [self.nodes[k] for k in self.nodes.keys()]
+ while (1):
+ if item.parent() not in all_nodes:
+ i += 1
+ item = item.parent()
+ else:
+ return i
+
+ def set_data_view(self, data_dic: Dict[str, object], root: 'QTreeWidgetItem'):
+ """
+ 递归方式显示json。
+ 显示的时候要注意层级。
+ :param data_dic:
+ :param root:
+ :return:
+ """
+ for k in data_dic.keys():
+ if isinstance(data_dic[k], dict):
+ child = QTreeWidgetItem(root)
+ child.setText(0, str(k))
+ child.item_id = root.item_id + '-' + child.text(0)
+ self.nodes[child.item_id] = child
+ self.set_data_view(data_dic[k], child)
+ else:
+ child = QTreeWidgetItem(root)
+ child.setText(0, str(k))
+ child.item_id = root.item_id + '-' + child.text(0)
+ self.nodes[child.item_id] = child
+
+ child.setText(1, self.autorepr(data_dic[k]))
+ self.try_expand_item(child)
+
+ def try_expand_item(self, item: QTreeWidgetItem):
+ """
+ 如果状态为展开则展开,否则不展开
+ :param item:
+ :return:
+ """
+ expanding_stat = self.expanding_stat.get(item.item_id)
+ if expanding_stat is not None:
+ item.setExpanded(expanding_stat)
+ else:
+ item.setExpanded(False)
+
+
+if __name__ == '__main__':
+ from PySide2.QtWidgets import QApplication
+ import sys
+
+ app = QApplication(sys.argv)
+ sa = PMGJsonTree()
+ sa.show()
+ sa.setup_ui()
+ a = 123
+ data_dic = {
+ 'p%d' % i: {'type': str(type(123)),
+ 'value': 123,
+ 'attributes': {name: str(getattr(a, name)) for name in dir(a)}
+ } for i in range(100)}
+ data_dic['aaaaaa'] = 'aaaaa '
+
+
+ def f():
+ data_dic['p10']['value'] = time.time()
+ sa.set_data_dic(data_dic)
+
+
+ sa.set_data_dic(data_dic)
+ sa.memorize_expanding_stat()
+ timer = QTimer()
+ timer.start(1000)
+ timer.timeout.connect(f)
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/widgets/basic/trees/translations/qt_zh_CN.ts b/pyminer/widgets/widgets/basic/trees/translations/qt_zh_CN.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3c8de54fdb138325e9dadcad19a95e1dd4e3d6aa
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/trees/translations/qt_zh_CN.ts
@@ -0,0 +1,257 @@
+
+
+
+
+ InputFilenameDialog
+
+
+ Ok
+ 确定
+
+
+
+ Cancel
+ 取消
+
+
+
+ PMFileSystemModel
+
+
+ Name
+ 名称
+
+
+
+ Size
+ 大小
+
+
+
+ Type
+ 类型
+
+
+
+ Last Modified
+ 上次修改
+
+
+
+ PMGAttrTree
+
+
+ Name
+ 名称
+
+
+
+ Size
+ 大小
+
+
+
+ Value
+ 值
+
+
+
+ View
+ 查看
+
+
+
+ Save as
+ 保存为
+
+
+
+ Undo
+ 撤销
+
+
+
+ Redo
+ 重做
+
+
+
+ Delete
+ 删除
+
+
+
+ PMGFilesTreeview
+
+
+ Open
+ 打开
+
+
+
+ Import
+ 导入
+
+
+
+ New..
+ 新建..
+
+
+
+ Folder
+ 文件夹
+
+
+
+ Rename
+ 重命名
+
+
+
+ Delete
+ 删除
+
+
+
+ Please Input file name
+ 请输入文件名
+
+
+
+ Error
+ 错误
+
+
+
+ Folder name %s is illeagal!
+ 文件夹名称%s不合规范!
+
+
+
+ Folder %s already exists!
+ 文件夹%s 已经存在!
+
+
+
+ File %s already exists!
+ 文件%s已经存在!
+
+
+
+ Unable to Rename this file.
+ 无法重命名这个文件。
+
+
+
+ Unable to Move this file to recycle bin.
+ 无法将这个文件移动到回收站。
+
+
+
+ Copy
+ 复制
+
+
+
+ Paste
+ 粘贴
+
+
+
+ Copy File or Directory Error.
+ 无法复制文件或文件夹。
+
+
+
+ Filter
+ 过滤文件类型
+
+
+
+ Extension Name To Show
+ 显示的文件类型
+
+
+
+ File..
+ 文件..
+
+
+
+ Python File
+ Python文件
+
+
+
+ Copy Path
+ 复制路径
+
+
+
+ Open Explorer
+ 打开文件管理器
+
+
+
+ Please input file name
+ 请输入文件路径
+
+
+
+ PMGJsonTree
+
+
+ Name
+ 名称
+
+
+
+ Value
+ 值
+
+
+
+ View
+ 查看
+
+
+
+ Save as
+ 储存为
+
+
+
+ Undo
+ 撤销
+
+
+
+ Redo
+ 重做
+
+
+
+ Delete
+ 删除
+
+
+
+ app
+
+
+ Program Scripts
+ 程序脚本
+
+
+
+ Documents
+ 办公文件
+
+
+
+ Data Files
+ 数据文件
+
+
+
diff --git a/pyminer/widgets/widgets/basic/trees/treecheck.py b/pyminer/widgets/widgets/basic/trees/treecheck.py
new file mode 100644
index 0000000000000000000000000000000000000000..c4bb6ff273c8e00b4ddf3f9c34ad371c24263df9
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/trees/treecheck.py
@@ -0,0 +1,88 @@
+"""
+树状选择
+作者:侯展意
+"""
+import os
+from typing import Dict
+from PySide2.QtCore import Qt, QLocale
+from PySide2.QtWidgets import QTreeWidget, QTreeWidgetItem, QApplication, QTreeWidgetItemIterator
+import sys
+from widgets.utilities.source.translation import create_translator
+
+
+class PMCheckTree(QTreeWidget):
+ def __init__(self, parent=None, data: Dict = None):
+ super().__init__(parent)
+ self.translator = create_translator(
+ path=os.path.join(os.path.dirname(__file__), 'translations',
+ 'qt_{0}.qm'.format(QLocale.system().name()))) # translator
+ self.tree_data = data
+ self.set_data(data)
+ self.clicked.connect(self.on_item_clicked)
+ self.expandAll()
+
+ def set_data(self, data: Dict[str, Dict]):
+ self.tree_data = data
+ self.refresh_tree()
+
+ def refresh_tree(self):
+ data = self.tree_data
+ for k in data.keys():
+ parent = QTreeWidgetItem(self)
+ print(self.tr(k))
+ parent.setText(0, self.tr(k))
+ parent.setFlags(parent.flags()
+ | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
+ for x in data[k].keys():
+ child = QTreeWidgetItem(parent)
+ child.setFlags(child.flags() | Qt.ItemIsUserCheckable)
+ child.setText(0, x)
+ child.k = k
+ child.x = x
+ if data[k][x] == True:
+ child.setCheckState(0, Qt.Checked)
+ else:
+ child.setCheckState(0, Qt.Unchecked)
+
+ def on_item_clicked(self, index):
+
+ item = self.itemFromIndex(index)
+ if hasattr(item, 'x') and hasattr(item, 'k'):
+ if item.checkState(0) == Qt.Checked:
+ self.tree_data[item.k][item.x] = True
+ else:
+ self.tree_data[item.k][item.x] = False
+ print(self.tree_data)
+
+ def get_data(self):
+ iter = QTreeWidgetItemIterator(self)
+ while iter.value():
+ item = iter.value()
+ if hasattr(item, 'x') and hasattr(item, 'k'):
+ if item.checkState(0) == Qt.Checked:
+ self.tree_data[item.k][item.x] = True
+ else:
+ self.tree_data[item.k][item.x] = False
+ iter.__iadd__(1)
+ return self.tree_data
+
+ def closeEvent(self, a0: 'QCloseEvent') -> None:
+ pass
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ app.tr('Program Scripts')
+ app.tr('Documents')
+ app.tr('Data Files')
+ foods = {
+ 'Program Scripts': {'.pyx': True, '.py': True, '.c': True, '.pyi': True, '.dll': True,
+ '.h': True, '.cpp': True, '.ipynb': True},
+ 'Documents': {'.txt': True, '.md': True, '.doc': True, '.docx': True, '.ppt': True, '.pptx': True},
+ 'Data Files': {'.csv': True, '.xls': True, '.xlsx': True, '.tab': True, '.dat': True, '.tsv': True,
+ '.sav': True, '.zsav': True, '.sas7bdat': True}
+ }
+
+ tree = PMCheckTree(data=foods)
+ tree.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/widgets/basic/trees/varattrtree.py b/pyminer/widgets/widgets/basic/trees/varattrtree.py
new file mode 100644
index 0000000000000000000000000000000000000000..60c33ba3a06b0d5b5a06d479801e16d29f30f7f6
--- /dev/null
+++ b/pyminer/widgets/widgets/basic/trees/varattrtree.py
@@ -0,0 +1,218 @@
+import os
+from typing import Dict, TYPE_CHECKING, Callable, Any
+
+from PySide2.QtWidgets import QTreeWidget, QTreeWidgetItem, QWidget, QVBoxLayout, QPushButton, QHBoxLayout, QMenu
+from PySide2.QtCore import Signal, Qt, QLocale
+
+
+class PMGAttrTree(QTreeWidget):
+ signal_show_data_value = Signal(str)
+ signal_data_saveas = Signal(str)
+ signal_data_open = Signal(str)
+ select_data_callbacks = []
+ extension_lib: 'extension_lib' = None
+
+ def __init__(self, parent=None):
+ super().__init__(parent=None)
+
+ self.filter_all_upper_case = False
+ self.filter_all_callables = True
+ self.setup_ui()
+
+ # def is_item_legal(self, name: str, obj: object):
+ # return (not (callable(obj) and self.filter_all_callables)) and \
+ # (not (name.isupper() and self.filter_all_upper_case))
+
+ def setup_ui(self):
+ self.nodes: Dict[str, 'QTreeWidgetItem'] = {}
+ self.setColumnCount(3)
+ header_item = QTreeWidgetItem()
+ header_item.setText(0, self.tr('Name'))
+ self.setColumnWidth(0, 10 * 10)
+ header_item.setText(1, self.tr('Size'))
+ self.setColumnWidth(1, 10 * 10)
+ header_item.setText(2, self.tr('Value'))
+ header_item.setTextAlignment(0, Qt.AlignCenter)
+ header_item.setTextAlignment(1, Qt.AlignCenter)
+ header_item.setTextAlignment(2, Qt.AlignCenter)
+ self.setHeaderItem(header_item)
+ self.auto_expand = False
+
+ self.itemClicked.connect(self.on_item_clicked)
+ self.itemDoubleClicked.connect(self.on_item_double_clicked)
+ self.customContextMenuRequested.connect(self.on_right_clicked)
+ self.setContextMenuPolicy(Qt.CustomContextMenu)
+
+ self.context_menu = QMenu()
+ show_action = self.context_menu.addAction(self.tr('View'))
+ save_action = self.context_menu.addAction(self.tr('Save as '))
+ cancel_action = self.context_menu.addAction(self.tr('Undo'))
+ redo_action = self.context_menu.addAction(self.tr('Redo'))
+ delete_action = self.context_menu.addAction(self.tr('Delete'))
+ show_action.triggered.connect(self.on_show_data_by_context_menu)
+
+ def on_item_clicked(self, item: QTreeWidgetItem, col: int) -> None:
+ """
+ 点击条目时的回调函数。会顺次执行self.select_data_callbacks,而self.select_data_callbacks
+ 是由插件的add_select_data_callback(self, callback: Callable)方式加入的。
+ """
+ if item.isExpanded():
+ self.collapseItem(item)
+ else:
+ self.expandItem(item)
+
+ if col == 0 and item.text(0) in self.nodes.keys():
+ for callback in self.select_data_callbacks:
+ callback(item.text(0))
+
+ def add_select_data_callback(self, callback: Callable):
+ """
+ 加入回调函数。
+ """
+
+ self.select_data_callbacks.append(callback)
+
+ def on_item_double_clicked(self, item: QTreeWidgetItem, col: int) -> None:
+ """
+ 双击条目的事件,触发数据显示。
+ """
+ if col == 0 and item.text(0) in self.nodes.keys():
+ self.show_data(item.text(0))
+
+ def on_right_clicked(self, pos):
+ """
+ 右键点击某一条目的事件,此时会弹出菜单。
+ """
+ item = self.currentItem()
+ if item is not None and item.text(0) in self.nodes.keys():
+ self.context_menu.exec_(self.mapToGlobal(pos))
+
+ def on_show_data_by_context_menu(self, action: 'QAction'):
+ """
+ 右键菜单点击‘显示数据’所触发的事件。
+ """
+ item = self.currentItem()
+ if item is not None and item.text(0) in self.nodes.keys():
+ self.show_data(item.text(0))
+
+ def autorepr(self, obj: Any, max_length=80) -> str:
+ """
+ 封装了repr方法,数据过长的时候,取较短的。
+ """
+ return repr(obj).replace('\n', '').replace('\r', '')[:max_length]
+
+ def get_size(self, data: Any):
+ """
+ 获取数据的尺寸。
+ 有‘shape’这一属性的,size就等于data.shape
+ 有‘__len__’这个属性的,返回len(data)
+ 否则返回1.
+ """
+ size = 1
+ if hasattr(data, 'shape'):
+ size = data.shape
+ elif hasattr(data, '__len__'):
+ try:
+ size = len(data)
+ except:
+ pass
+ return size
+
+ def set_data_dic(self, data_dic: Dict[str, object]) -> None:
+ """
+ 显示数据
+ :param data_dic:所有数据的字典{变量名:变量对象}
+ :return:
+ """
+ for data_name in data_dic:
+
+ data = data_dic[data_name]
+ text = repr(data_name)
+ size = self.get_size(data)
+ data_str = f'<{size}>\t{self.autorepr(data)}'
+
+ if text in self.nodes.keys():
+ """
+ 如果变量在变量列表中,就刷新这个变量位置显示的文字信息,并通过递归方式创建变量的各个属性。
+ """
+ data_node = self.nodes[text]
+ data_node.setText(0, text)
+ data_node.setText(1, repr(size))
+ data_node.setText(2, self.autorepr(data))
+ data_node.takeChildren()
+ else:
+ """
+ 如果变量不在变量列表中,就新建一个节点,并通过递归方式创建变量的各个属性。
+ """
+ child = QTreeWidgetItem(self)
+ self.nodes[text] = child
+ child.setText(0, text)
+ child.setText(1, str(size))
+ child.setText(2, self.autorepr(data))
+
+ if isinstance(data, dict):
+ self.set_data_view(data, child)
+ if self.auto_expand:
+ self.expandItem(child)
+ else:
+ self.collapseItem(child)
+
+ def get_hier(self, item: QTreeWidgetItem) -> int:
+ i = 0
+ all_nodes = [self.nodes[k] for k in self.nodes.keys()]
+ while (1):
+ print(item.parent())
+ if item.parent() not in all_nodes:
+ i += 1
+ item = item.parent()
+ else:
+ return i
+
+ def set_data_view(self, data_dic: Dict[str, object], root: 'QTreeWidgetItem'):
+ """
+ 递归方式显示json。
+ 显示的时候要注意层级。
+ :param data_dic:
+ :param root:
+ :return:
+ """
+ for k in data_dic.keys():
+ print(k)
+ if type(data_dic[k]) == dict:
+ child = QTreeWidgetItem(root)
+ child.setText(0, repr(k))
+ self.set_data_view(data_dic[k], child)
+ elif not isinstance(data_dic[k], str):
+ print(k)
+ d = {attr_name: str(getattr(data_dic[k], attr_name)) for attr_name in dir(data_dic[k])}
+
+ child = QTreeWidgetItem(root)
+ child.setText(0, repr(k))
+ self.set_data_view(d, child)
+ else:
+ child = QTreeWidgetItem(root)
+ child.setText(0, repr(k))
+ child.setText(1, str(self.get_size(data_dic[k])))
+ child.setText(2, data_dic[k])
+
+
+if __name__ == '__main__':
+ from PySide2.QtWidgets import QApplication, QTableWidget
+ import sys
+
+ app = QApplication(sys.argv)
+ sa = PMGAttrTree()
+ sa.show()
+ sa.setup_ui()
+ data_dic = {'a': {'type': 'statespace',
+ 'A': {'type': 'timeseries', 'time': '[1,2,3]', 'mdata': '[3,2,1]'},
+ 'B': {'type': 'matrix', 'value': '[[2],[1]]', 'aaaa': {'a': 'aa', 0: {'a': 'a'}}},
+ 'C': {'type': 'matrix', 'value': '[[1,2]]', },
+ 'D': {'type': 'matrix', 'value': '[[0]]', },
+ 'row': ['left', 'x2'], 'column': ['column'], 'u': ['u'], 'sys': 'str'},
+ 'b': 100,
+ 55: {123: 456, 'ff': {3333: 555}},
+ 'sa': sa}
+ # sa.set_data_dic(data_dic)
+ sa.set_data_view(data_dic, sa)
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/widgets/composited/__init__.py b/pyminer/widgets/widgets/composited/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..5a23169cfd5081625ae885232aa70aa7c635443c
--- /dev/null
+++ b/pyminer/widgets/widgets/composited/__init__.py
@@ -0,0 +1,5 @@
+"""
+综合控件
+"""
+from .generalpanel import *
+from .fastui import *
\ No newline at end of file
diff --git a/pyminer/widgets/widgets/composited/buttonpanel.py b/pyminer/widgets/widgets/composited/buttonpanel.py
new file mode 100644
index 0000000000000000000000000000000000000000..3d6791744b1c2a912a400328bd81da24ac5d033f
--- /dev/null
+++ b/pyminer/widgets/widgets/composited/buttonpanel.py
@@ -0,0 +1,18 @@
+from PySide2.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton
+
+
+class PMGButtonPanel(QWidget):
+ def __init__(self, parent=None, layout_dir: str = 'v'):
+ super().__init__(parent=parent)
+ if layout_dir == 'v':
+ self.setLayout(QVBoxLayout())
+ else:
+ self.setLayout(QHBoxLayout())
+
+ def add_button(self, text: str, callback: str = None, icon: str = None) -> QPushButton:
+ b = QPushButton()
+ b.setText(text)
+ self.layout().addWidget(b)
+ if callback is not None:
+ b.clicked.connect(callback)
+ return b
\ No newline at end of file
diff --git a/pyminer/widgets/widgets/composited/fastui.py b/pyminer/widgets/widgets/composited/fastui.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ede79a6e6fe61b4c196a4afc098663049ca791d
--- /dev/null
+++ b/pyminer/widgets/widgets/composited/fastui.py
@@ -0,0 +1,273 @@
+"""
+这是定义基类的基础文件,里面是用代码生成方式来制作功能性面板的示例。比如
+可以参阅同一目录下的其他文件。这些面板默认样式为悬浮在最上方,即使面板弹出,也可以操作主界面。
+
+面板文件与PyMiner主程序完全解耦,启动PyMiner主程序后,可以直接运行此文件,或者fastui文件夹下的任何.py文件,
+以此调试面板。此时,需要确保PyMiner的工作空间中有pandas.DataFrame类型的数据。
+
+如datamerge.py(数据集合并)/ dropdata.py(去除缺失值)/ fillna(填充缺失值) 等。
+
+最底层基类是BaseOperationDialog,是一个QDialog对话框基类,里面定义了一系列的基础方法,比如变量获取等。它是一个完全空白的对话框。
+次底层基类是DFOperationDialog,是用于数据集操作的基类。它定义有一个变量选择下拉菜单self.combo_box、一个参数输入面板self.panel,以及一个带有
+四个按钮(预览、保存、关闭和帮助)的按钮面板。
+
+数据合并的面板继承于BaseOperationDialog,其余面板都继承于DFOperationDialog。这是因为数据合并面板需要多个选择数据的下拉框,用后者满足不了要求。
+
+有关底层基类中的相关方法,请移步相应类定义的位置。
+
+这些面板生成的函数,应当是一个有返回值的函数,参数为工作空间中的变量,或者是字符串、数字等。
+
+比如,工作空间中有函数a、b。数据合并面板希望生成一个横向合并a和b的函数。那么,最终生成的代码应该为:
+pd.concat([a,b],axis=0)
+
+这段函数有两种执行方式。一种是预览(preview),由preview按钮触发,调用基类的preview方法,直接执行pd.concat([a,b],axis=0);另一种是储存(store),
+由“Save to var“按钮触发,调用基类store方法,要求用户输入一个新的变量名,然后执行代码f = pd.concat([a,b],axis=0),(f为用户输入的变量名)。
+
+pd.concat函数有返回值。在点击“preview”
+
+@Time: 2021/2/8 12:54
+@Author: Zhanyi Hou
+@Email: 1295752786@qq.com
+@File: pmgui.py
+"""
+from abc import abstractmethod
+from typing import List
+
+from PySide2.QtCore import Qt, QCoreApplication, Signal
+from PySide2.QtWidgets import QDialog, QVBoxLayout, QApplication, QPushButton, QComboBox, QHBoxLayout, QLabel, \
+ QTextBrowser
+
+from pyminer_comm import get_var_names, run_command, call_interface
+from utils import input_identifier, bind_combo_with_workspace
+
+
+def kwargs_to_str(kwargs: dict) -> str:
+ """
+ 将字典参数转化为字符串的简易方法
+ Args:
+ kwargs:
+
+ Returns:
+
+ """
+ args_str = ''
+ for k, v in kwargs.items():
+ args_str += '{k}={v},'.format(k=k, v=repr(v))
+ return args_str
+
+
+class BaseOperationDialog(QDialog):
+ """
+ 最底层的功能面板基类。
+ 定义了代码生成的有关方法。
+
+ """
+ signal_update_combo = Signal()
+
+ def __init__(self):
+ from widgets.utilities import PMGOneShotThreadRunner
+ self.thrunner: PMGOneShotThreadRunner = None
+ super(BaseOperationDialog, self).__init__()
+
+ def store(self):
+ """
+ 执行存储到变量的命令。
+
+ Returns:
+
+ """
+ code = self.get_assignment_code()
+ if code != '':
+ run_command(command=code, hint_text=self.get_prompt_template() + code, hidden=False)
+
+ def preview(self) -> None:
+ """
+ 预览分析结果。
+ Returns:
+
+ """
+ code = self.get_value_code()
+ if code != '':
+ run_command(command=code, hint_text=self.get_prompt_template() + code, hidden=False)
+
+ def help(self) -> None:
+ """
+ 弹出帮助面板
+ Returns: None
+
+ """
+ dlg = QDialog()
+ dlg.setLayout(QVBoxLayout())
+ textBrowser = QTextBrowser(self)
+ dlg.layout().addWidget(textBrowser)
+ textBrowser.setMarkdown(self.get_help_content())
+ dlg.exec_()
+
+ def get_help_content(self) -> str:
+ """
+ 生成帮助内容
+
+ 为markdown格式
+
+ Notes:子类最好重写这个方法
+ Returns:
+
+ """
+ return """# 帮助\n\n暂未定义帮助"""
+
+ def get_prompt_template(self) -> str:
+ """
+ 获取提示模板
+
+ Notes:子类可以不重写这个方法
+ Returns:
+ """
+ return ''
+
+ def kwargs_to_str(self, kwargs: dict) -> str:
+ """
+ 将字典参数转化为字符串的简易方法
+ Args:
+ kwargs:
+
+ Returns:
+
+ """
+ args_str = ''
+ for k, v in kwargs.items():
+ args_str += '{k}={v},'.format(k=k, v=repr(v))
+ return args_str
+
+ @abstractmethod
+ def get_value_code(self) -> str:
+ """
+ 获取一段代码,这段代码应当有返回值,而不是赋值代码。
+ 例如, “a = pd.concat([c,d])”
+
+ Notes:子类必须重写这个方法。
+
+ Returns:
+ """
+ return ''
+
+ def get_assignment_code(self) -> str:
+ """
+ 获取赋值语句。实际上就是先让用户输入一个变量名,然后将这个变量名和赋值语句放在get_value_code方法
+ 生成的代码的左侧。
+
+ 倘若用户输入的变量名是“a”,get_value_code方法生成的代码为“pd.concat([c,d])”,那么这个方法生成的代码就是:
+ “a = pd.concat([c,d])”
+
+ 子类中通常无需重写这个方法
+
+ Returns: 合并好的代码
+ """
+ code = self.get_value_code()
+ identifier = input_identifier(parent=self, default_name=self.combo_box.currentText(), allow_existing_name=True)
+ if identifier != '':
+ code = identifier + ' = ' + code
+ return code
+ return ''
+
+ def get_selected_variables(self) -> List[str]:
+ """
+ 获取当前界面上选中的变量。
+ 当界面为PyMiner调用时,此方法不能在主进程中调用,否则会阻塞消息循环。
+ Returns: 当前界面上选中的变量,可能是一个列表。
+ """
+ return call_interface('workspace_inspector', 'get_selected_variables', {}, request_ret=True)
+
+ def showEvent(self, arg__1: 'QShowEvent') -> None:
+ from widgets.utilities import PMGOneShotThreadRunner
+ if self.thrunner is None or not self.thrunner.is_running():
+ self.thrunner = PMGOneShotThreadRunner(callback=self.get_selected_variables)
+ self.thrunner.signal_finished.connect(self.on_update_combo)
+
+
+class DFOperationDialog(BaseOperationDialog):
+ signal_update_combo = Signal()
+
+ def __init__(self):
+ from widgets.widgets.composited import PMGPanel
+ super(DFOperationDialog, self).__init__()
+ self.setLayout(QVBoxLayout())
+ self.combo_box = QComboBox()
+ bind_combo_with_workspace(self.combo_box)
+ self.panel = PMGPanel()
+ self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint)
+ self.button_layout = QHBoxLayout()
+
+ self.button_preview = QPushButton(QCoreApplication.translate('BaseDFOperationDialog', "预览"))
+ self.button_store = QPushButton(QCoreApplication.translate('BaseDFOperationDialog', "保存到变量"))
+ self.button_close = QPushButton(QCoreApplication.translate('BaseDFOperationDialog', "关闭"))
+ self.button_help = QPushButton(QCoreApplication.translate('BaseDFOperationDialog', "帮助"))
+
+ # self.button_box.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Help)
+ self.button_layout.addWidget(self.button_preview)
+ self.button_layout.addWidget(self.button_store)
+ self.button_layout.addWidget(self.button_help)
+ self.button_layout.addWidget(self.button_close)
+
+ self.button_preview.clicked.connect(self.preview) # 预览
+ self.button_store.clicked.connect(self.store) # 储存
+ self.button_close.clicked.connect(self.close) # 关闭对话框
+ self.button_help.clicked.connect(self.help) # 显示帮助
+
+ self.hint_label = QLabel(self.tr('选择变量'))
+ self.layout().addWidget(self.hint_label)
+ self.layout().addWidget(self.combo_box)
+ self.layout().addWidget(self.panel)
+ self.layout().addLayout(self.button_layout)
+
+ def on_update_combo(self, vars: List[str]):
+ self.combo_box.clear()
+ self.combo_box.addItems(get_var_names())
+ if len(vars) > 0:
+ self.combo_box.setCurrentText(vars[0])
+
+
+class FunctionGUIDialog(DFOperationDialog):
+ def __init__(self, dic):
+ super(FunctionGUIDialog, self).__init__()
+ self.func_name = dic["func_name"]
+ self.with_object = dic["with_object"]
+ if not self.with_object:
+ self.combo_box.hide()
+ self.setWindowTitle(dic["title"])
+ views = []
+ optional_names = []
+ for arg in dic["args"]:
+ views.append("-" * 60)
+ if arg["optional"]:
+ optional_names.append(arg["name"])
+ views.append(("check_ctrl", arg["name"] + "#enable", "", True))
+ arg["ctrl"]["name"] = arg["name"]
+ views.append(arg["ctrl"])
+
+ self.panel.set_items(views)
+ for op_name in optional_names:
+ self.panel.set_as_controller(op_name + "#enable", [op_name], True, )
+
+ def get_value_code(self) -> str:
+ values = self.panel.get_value_with_filter() # 只获取使能并且可见的控件的值
+ print(values,self.panel.widgets_dic)
+ values = {k: v for k, v in values.items() if k.isidentifier()}
+ varname = self.combo_box.currentText()
+ args_str = ''
+ for k, v in values.items():
+ args_str += '{k}={v},'.format(k=k, v=repr(v))
+ if not self.with_object:
+ code = '{func_name}({args})'.format(func_name=self.func_name, args=args_str)
+ else:
+ code = '{var_name}.{method_name}({args})'.format(var_name=varname,
+ method_name=self.func_name,
+ args=args_str)
+ print(code,values)
+ return code
+
+
+if __name__ == '__main__':
+ app = QApplication([])
+ md = DFOperationDialog()
+ md.show()
+ app.exec_()
diff --git a/pyminer/widgets/widgets/composited/generalpanel.py b/pyminer/widgets/widgets/composited/generalpanel.py
new file mode 100644
index 0000000000000000000000000000000000000000..06ebacfcc19d41920712280f46f24204a0cc2950
--- /dev/null
+++ b/pyminer/widgets/widgets/composited/generalpanel.py
@@ -0,0 +1,458 @@
+"""
+命名规范:
+
+PMG+控件类型+功能类型
+控件类型可以是一词或者两个词。
+
+相关设计思想来自ImagePy团队的SciWx中的normal控件,对接口做出相关调整,增加了json解码解包的部分。
+
+作者:侯展意
+qq:1295752786@qq.com
+"""
+
+import logging
+from typing import Any, List, Tuple, Dict, Callable, Union, Optional
+
+from PySide2.QtCore import Signal
+from PySide2.QtGui import QCloseEvent
+from PySide2.QtWidgets import QSpacerItem, QSizePolicy, QDialog, QFrame, QVBoxLayout, QHBoxLayout, QDialogButtonBox, \
+ QApplication, QLabel
+
+from widgets.widgets.extended import (BaseExtendedWidget, PMGCheckCtrl, PMGColorCtrl, PMGEvalCtrl, PMGComboCtrl,
+ PMGFileCtrl, PMGMultiTypeCtrl, PMGVariablesComboCtrl,
+ PMGKeyMapCtrl, PMGFolderCtrl, PMGLineCtrl, PMGNumberCtrl, PMGPasswordCtrl,
+ PMGListCtrl, PMGDateCtrl, PMGTimeCtrl, PMGDateTimeCtrl, PMGNumberSpinCtrl,
+ PMGTableShow, PMGLabelShow, PMGRuleCtrl)
+
+try:
+ from widgets.widgets.extended import PMGTimeSeriesShow
+except ImportError:
+ PMGTimeSeriesShow = None
+
+from widgets.utilities.uilogics.codechecking import iter_isinstance, ErrorReporter
+
+logger = logging.getLogger(__name__)
+views_dic = {}
+views_dic.update({'color_ctrl': PMGColorCtrl,
+ 'eval_ctrl': PMGEvalCtrl,
+ 'file_ctrl': PMGFileCtrl,
+ 'keymap_ctrl': PMGKeyMapCtrl,
+ 'folder_ctrl': PMGFolderCtrl,
+ 'line_ctrl': PMGLineCtrl,
+ 'number_ctrl': PMGNumberCtrl,
+ 'password_ctrl': PMGPasswordCtrl,
+ 'multitype_ctrl': PMGMultiTypeCtrl,
+ "vars_combo_ctrl": PMGVariablesComboCtrl})
+
+views_dic.update({'editor_ctrl': globals().get('PMGEditorCtrl'),
+ 'check_ctrl': PMGCheckCtrl,
+ 'combo_ctrl': PMGComboCtrl,
+ 'list_ctrl': PMGListCtrl,
+ 'time_ctrl': PMGTimeCtrl,
+ 'date_ctrl': PMGDateCtrl,
+ 'datetime_ctrl': PMGDateTimeCtrl,
+ 'numberspin_ctrl': PMGNumberSpinCtrl
+ })
+
+views_dic.update({'timeseries_show': PMGTimeSeriesShow,
+ 'label_show': PMGLabelShow,
+ 'table_show': PMGTableShow,
+ 'rules_ctrl': PMGRuleCtrl
+ })
+
+PANEL_VIEW_CLASS = List[Union[
+ Tuple[Any],
+ List[Any],
+ Dict[str, Any]
+]]
+
+
+class PMGPanel(QFrame):
+ widgets_dic: Dict[str, BaseExtendedWidget] = {}
+ signal_settings_changed = Signal(dict)
+
+ def __init__(self, parent=None, views: PANEL_VIEW_CLASS = None, layout_dir: str = 'v', with_spacer: bool = True,
+ with_aux_spacer=False,
+ spacing: int = 0,
+ margins: Union[int, Tuple[int, int, int, int]] = 0):
+ super(PMGPanel, self).__init__(parent)
+ self._initial_style_sheet = self.styleSheet()
+ self.layout_dir = layout_dir
+ self.with_spacer = with_spacer
+ self.with_aux_spacer = with_aux_spacer
+
+ if layout_dir == 'v':
+ self.setLayout(QVBoxLayout())
+ else:
+ self.setLayout(QHBoxLayout())
+
+ self.layout().setSpacing(spacing)
+ if isinstance(margins, tuple):
+ assert iter_isinstance(margins, int), \
+ ErrorReporter.create_invalid_parameter_type_message('margins', type(margins), int)
+ self.layout().setContentsMargins(*margins)
+ elif isinstance(margins, int):
+ margins = [margins] * 4
+ self.layout().setContentsMargins(*margins)
+ else:
+ raise TypeError(ErrorReporter.create_invalid_parameter_type_message('margins', type(margins),
+ 'int or Tuple[int,int,int,int]'))
+ self.layout()
+ self.set_items(views)
+
+ self._param_changd_callbacks: Dict[str, List[Callable]] = {}
+
+ def set_param_changed_callback(self, param: str, callback: Callable):
+ """
+
+ Args:
+ param:
+ callback:
+
+ Returns:
+
+ """
+ # assert param not in self._param_changd_callbacks.keys(), \
+ # repr(param) + ' is already in callback dict\'s keys: %s ' % (repr(self._param_changd_callbacks.keys()))
+ assert param in self.widgets_dic.keys(), 'widget with param \'%s\' is not found in this PMGPanel' % param
+ if param not in self._param_changd_callbacks.keys():
+ self._param_changd_callbacks[param] = [callback]
+ else:
+ self._param_changd_callbacks[param].append(callback)
+
+ def on_param_changed(self, param):
+ if param in self._param_changd_callbacks.keys():
+ for callback in self._param_changd_callbacks[param]:
+ callback(self.get_value())
+
+ def set_debug_mode(self, debug: bool):
+ """
+ :param debug:将debug设置为True,即可进入debug模式。该模式下,PMGPanel会用醒目(但是很丑)的颜色显示出其上的不同控件,方便布局调整。
+ :return:
+ """
+ if debug:
+ self._initial_style_sheet = self.styleSheet()
+ red = 100 + random.randint(0, 155)
+ self.setStyleSheet(
+ 'PMGPanel{background-color:#%s0000;}QLabel{background-color:yellow;}BaseExtendedWidget{background-color:green;}' % hex(
+ red)[2:])
+ else:
+ self.setStyleSheet(self._initial_style_sheet)
+
+ def emit_settings_changed_signal(self):
+ """
+ 发出设置已改变的信号
+ :return:
+ """
+ self.signal_settings_changed.emit(self.get_value())
+
+ def on_settings_changed(self, name):
+ self.signal_settings_changed.emit(self.get_value())
+ self.on_param_changed(name)
+
+ def add_widget(self, argument: Union[List, Tuple, Dict], layout: Union[QHBoxLayout, QVBoxLayout]) -> None:
+ """
+ 创建控件,并且将其添加到控件字典中。
+ :param argument:
+ :return:
+ """
+ if isinstance(argument, str):
+ layout.addWidget(QLabel(argument))
+ elif isinstance(argument, (tuple, list)):
+ name = argument[1]
+ try:
+ widget = views_dic[argument[0]](self.layout_dir, *argument[2:])
+ except:
+ import traceback
+ traceback.print_exc()
+ raise ValueError(repr(argument) + 'is invalid!')
+ if self.widgets_dic.get(name) is None:
+ self.widgets_dic[name] = widget
+ widget.signal_param_changed.connect(self.on_settings_changed)
+ widget.name = name
+ layout.addWidget(widget)
+ elif isinstance(argument, dict):
+ widget_type = views_dic.get(argument['type'])
+ assert widget_type is not None, repr(argument) + 'is of invalid widget type!!'
+ name = argument['name']
+ title = argument.get('title') if 'title' in argument.keys() else ''
+ initial_value = argument.get('init')
+ preserved_params = ['type', 'name', 'title', 'init']
+ kwargs = {k: v for k, v in argument.items() if k not in preserved_params}
+ widget = widget_type(self.layout_dir, title, initial_value, **kwargs)
+ if self.widgets_dic.get(name) is None:
+ self.widgets_dic[name] = widget
+ widget.name = name
+ widget.signal_param_changed.connect(self.on_settings_changed)
+ layout.addWidget(widget)
+ pass
+ else:
+ raise TypeError('Argument %s should be ')
+
+ def _set_items(self, views: PANEL_VIEW_CLASS = None):
+ """
+ Clear all items on the panel and then add items from argument 'views'.
+ Args:
+ views:
+
+ Returns:
+
+ """
+ if views is None:
+ return
+ self.widgets_dic: Dict[str, QWidget] = {}
+ self.layout().setContentsMargins(0, 0, 0, 0)
+ for v in views:
+ if isinstance(v, dict):
+ self.add_widget(v, self.layout())
+ elif isinstance(v[0], str):
+ self.add_widget(v, self.layout())
+
+ elif isinstance(v[0], (list, tuple, dict)):
+ sub_layout = None
+ if self.layout_dir == 'v':
+ sub_layout = QHBoxLayout()
+ self.layout().addLayout(sub_layout)
+ else:
+ sub_layout = QVBoxLayout()
+ self.layout().addLayout(sub_layout)
+ for subv in v:
+ self.add_widget(subv, sub_layout)
+ if self.with_aux_spacer:
+ if self.layout_dir == 'h':
+ sub_layout.addItem(QSpacerItem(20, 0, QSizePolicy.Minimum, QSizePolicy.Expanding))
+ else:
+ sub_layout.addItem(QSpacerItem(20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
+ if self.with_spacer:
+ if self.layout_dir == 'v':
+ self.layout().addItem(QSpacerItem(20, 0, QSizePolicy.Minimum, QSizePolicy.Expanding))
+ else:
+ self.layout().addItem(QSpacerItem(20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
+
+ def set_items(self, items: PANEL_VIEW_CLASS = None):
+ self.widgets_dic = {}
+ for i in range(self.layout().count()):
+ widget = self.layout().itemAt(i).widget()
+ layout = self.layout().itemAt(i).layout()
+ if widget is not None:
+ widget.deleteLater()
+ if layout is not None:
+ for j in range(layout.count()):
+ widget = layout.itemAt(j).widget()
+ if widget is not None:
+ widget.deleteLater()
+ layout.deleteLater()
+ self._set_items(items)
+
+ def get_ctrl(self, ctrl_name: str) -> 'BaseExtendedWidget':
+ return self.widgets_dic.get(ctrl_name)
+
+ def get_value(self):
+ result = {}
+ for k in self.widgets_dic:
+ result[k] = self.widgets_dic[k].get_value()
+ return result
+
+ def get_value_with_filter(self, filter: str = 'enabled_and_visible'):
+ assert filter in {'enabled', 'visible', 'enabled_and_visible'}
+ result = {}
+ for k in self.widgets_dic:
+ widget = self.widgets_dic[k]
+ if filter == 'enabled_and_visible' and widget.isEnabled() and widget.isVisible():
+ result[k] = widget.get_value()
+ elif (widget.isVisible() and filter == 'visible') or (widget.isEnabled() and filter == 'enabled'):
+ result[k] = widget.get_value()
+ return result
+
+ def set_as_controller(self, controller_param: str, target_params: List[str],
+ trigger_value: [Any, Callable[[Any], bool]],
+ action='enable'):
+ assert action in ['enable', 'disable', 'show', 'hide']
+ assert isinstance(target_params, list)
+ trigger_condition_func: Callable[[Any], bool] = None
+ if not callable(trigger_value):
+ trigger_condition_func = lambda obj: obj == trigger_value
+ else:
+ trigger_condition_func = trigger_value
+ _condition_func = lambda params: trigger_condition_func(params[controller_param])
+ for target_param in target_params:
+ assert target_param in self.widgets_dic.keys(), 'Widget %s does not exist!, all widgets: %s' % (
+ target_param, self.widgets_dic)
+
+ if action == 'enable' or action == 'show':
+ condition_func = lambda params: _condition_func(params)
+ else:
+ condition_func = lambda params: not _condition_func(params)
+
+ def callback(params):
+ for target_param in target_params:
+ if action == 'enable' or action == 'disable':
+ self.get_ctrl(target_param).setEnabled(condition_func(params))
+ else:
+ self.get_ctrl(target_param).setVisible(condition_func(params))
+
+ self.set_param_changed_callback(controller_param, callback)
+ callback(self.get_value()) # 设置时会被调用一次,以自动刷新界面。
+
+ def set_value(self, values: Dict[str, Union[int, str, List, float, Tuple]]):
+ """
+ 设置值。如果values中键对应的控件不存在,那么也不会报错,而是会忽略这一项。
+ :param values:字典。如果values中键对应的控件不存在,那么也不会报错,而是会忽略这一项。
+ :return:
+ """
+ for k in values.keys():
+ w = self.widgets_dic.get(k)
+ if w is not None:
+ w.set_value(values[k])
+
+ def closeEvent(self, a0: QCloseEvent) -> None:
+ super().closeEvent(a0)
+ self.deleteLater()
+ self.signal_settings_changed.emit(self.get_value())
+
+
+class PMGPanelDialog(QDialog):
+ """
+ 一个对话框,直接弹出PMGPanel。
+ TODO:要求如果有不合法的数值,就不可关闭。
+ """
+
+ def __init__(self, parent, views, with_spacer=False):
+ super().__init__(parent)
+ self.panel = PMGPanel(parent=self, views=views, with_spacer=with_spacer)
+ self.setLayout(QVBoxLayout())
+ self.layout().addWidget(self.panel)
+ button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+
+ button_box.accepted.connect(self.accept)
+ button_box.rejected.connect(self.reject)
+ self.layout().addWidget(button_box)
+ self.changes_accepted = False
+
+ def get_value(self) -> Dict[str, Any]:
+ return self.panel.get_value()
+
+ def set_value(self, values: Dict[str, Any]) -> None:
+ self.panel.set_value(values)
+
+ def accept(self):
+ if self.verify(self.get_value()):
+ self.changes_accepted = True
+ super().accept()
+
+ def verify(self, values) -> bool:
+ return True
+
+
+def is_standard_widget_name(widget_name: str) -> bool:
+ """
+ 返回字符串是否对应一个标准化的PMGPanel控件。
+ :return:
+ """
+ return views_dic.get(widget_name) is not None
+
+
+def parse_simplified_pmgjson(identifier, data, params) -> Optional[List[Union[int, str]]]:
+ """
+ 解析简化版的json数据!
+ :param identifier:
+ :param data:
+ :param params:
+ :return:
+ """
+ if len(params) > 0:
+ if is_standard_widget_name(params[0]):
+ return [params[0], identifier, 'Input Value', data] + params[1:]
+
+ if isinstance(data, bool):
+ return ['check_ctrl', identifier, 'Input Bool', data]
+
+ elif isinstance(data, (int, float)):
+
+ return ['numberspin_ctrl', identifier, 'Input Value', data] + params
+
+ elif isinstance(data, str):
+ return ['line_ctrl', identifier, 'Input String', data]
+
+
+if __name__ == '__main__':
+ import sys
+
+ app = QApplication(sys.argv)
+ # 类型;名称;显示的提示文字;初始值;//单位;范围
+ views1 = [('line_ctrl', 'name', 'What\'s your name?', 'hzy'),
+ {'type': 'line_ctrl', 'name': 'your_name', 'title': 'what is your name?', 'init': 'hzy'},
+ ('password_ctrl', 'password', 'What\'s your password?', '123456'),
+ ('file_ctrl', 'file_dir', 'File Name', '/home/hzy/Desktop/123.txt'),
+ # ('editor_ctrl', 'code', 'Input Python Code', 'print(123)', 'sql'),
+ ('number_ctrl', 'age', 'How old are you?', 88, 'years old', (0, 150)),
+ ('number_ctrl', 'height', 'How High could This Plane fly?', 12000, 'm', (10, 20000)),
+ ('check_ctrl', 'sport', 'do you like sport', True),
+ ('numberspin_ctrl', 'eyesight', '视力', 4.0, '度', (3.0, 5.5), 0.1),
+ ('numberspin_ctrl', 'apple_num', '苹果数量', 4, '个', (0, 10), 1), ]
+ views2 = [('combo_ctrl', 'plane_type', 'plane type', 'f22', ['f22', 'f18', 'j20', 'su57'],
+ ['f22战斗机', 'f18战斗轰炸机', 'j20战斗机', 'su57战斗机']),
+ ('color_ctrl', 'color', 'Which color do u like?', (0, 200, 0)),
+ ('list_ctrl', 'inputs', 'Set Inputs', [['1', '2', '3'], ['#1', '#2', '#3']], lambda: str(123)),
+ ('list_ctrl', 'inputs_2', 'Set Inputs', [[None, None, None], ['##1', '##2', '##3'], ], lambda: None),
+ ('date_ctrl', 'date', '设置时间', (1970, 7, 21)),
+ [
+ ('eval_ctrl', 'code_eval', 'Evaluate this code', 123 + 456, 'normal'),
+ ('eval_ctrl', 'code_eval2', 'Evaluate this code', (1, 2, 3), 'safe')
+ ],
+ # ('keymap_ctrl', 'key_map2', 'Key For Find', 'Ctrl+F'),
+ ]
+ sp = PMGPanel(views=views1, layout_dir='v')
+ sp.set_items(views1)
+ sp.signal_settings_changed.connect(lambda settings: print('views1-settings', settings))
+ # sp.show()
+
+ sp2 = PMGPanel(views=views2, layout_dir='v')
+ sp2.signal_settings_changed.connect(lambda settings: print('views2-settings', settings))
+ sp2.set_items(views2)
+ # sp2.show()
+
+ import random
+
+ views3 = \
+ [
+ # {'type': 'timeseries_show',
+ # 'name': 'cpu_occupation',
+ # 'title': 'CPU占用',
+ # 'init': {'timestamps': [i + 1 for i in range(10)],
+ # 'line1': {'tag': 'CPU利用率1',
+ # 'data': [random.randint(0, 100) for i in
+ # range(10)]},
+ # 'line2': {'tag': 'CPU利用率2',
+ # 'data': [random.randint(0, 100) for i in
+ # range(10)]}
+ # },
+ # 'y_range': (0, 100),
+ # 'xlabel': '时间',
+ # 'ylabel': '占用率',
+ # 'threshold_range': (0, 78),
+ # 'legend_face_color': "#00ff00"
+ # },
+ ("vars_combo_ctrl", "variables", "选择变量", ""),
+ ("multitype_ctrl", "multiple_types", "多种类型", [[None, None, None], ['诶诶诶', '嗷嗷喊', 'qq']], [{
+ "type_title": "字符串列表",
+ "ctrls": [
+ ("list_ctrl", "list_ctrl", 'input list of strings', [[None, None, None], ['##1', '##2', '##3']],
+ lambda: None),
+ ],
+ "on_ok": lambda values: values["list_ctrl"][1]},
+ {
+ "type_title": "字符串",
+ "ctrls": [
+ ("line_ctrl", "aaaa", 'input a string', "Please input a string"),
+ ],
+ "on_ok": None
+ }
+ ])
+ ]
+ sp3 = PMGPanel(views=views3, layout_dir='v')
+ sp3.signal_settings_changed.connect(lambda settings: print('views2-settings', settings))
+ sp3.set_items(views3)
+ sp3.show()
+
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/widgets/extended/__init__.py b/pyminer/widgets/widgets/extended/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..a3e91d89d98b53959d24865778e4d44bb27994d0
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/__init__.py
@@ -0,0 +1,13 @@
+from .base import *
+from .checkbuttons import *
+from .comboboxes import *
+from .entries import *
+from .labels import *
+from .lists import *
+from .others import *
+from .plots import *
+from .radiobuttons import *
+from .spins import *
+from .tables import *
+from .texts import *
+from .trees import *
diff --git a/pyminer/widgets/widgets/extended/base/__init__.py b/pyminer/widgets/widgets/extended/base/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..92290609b03fcb9364adf03eb7ce35a8060853a4
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/base/__init__.py
@@ -0,0 +1 @@
+from .baseextendedwidget import *
\ No newline at end of file
diff --git a/pyminer/widgets/widgets/extended/base/baseextendedwidget.py b/pyminer/widgets/widgets/extended/base/baseextendedwidget.py
new file mode 100644
index 0000000000000000000000000000000000000000..b5a974482def1bd62b8bd326e1b0f016f71a1e74
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/base/baseextendedwidget.py
@@ -0,0 +1,95 @@
+import os
+import string
+import sys
+import time
+from typing import List, Dict, Tuple, Union, Callable, Any
+from PySide2.QtCore import Signal
+from PySide2.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QFrame
+
+
+class BaseExtendedWidget(QFrame):
+ """
+ 基础参数控件的类型。所有的参数控件都在其上派生而来。
+ """
+ signal_param_changed = Signal(str)
+ NORMAL = 0
+ WARNING = 1
+ ERROR = 2
+
+ def __init__(self, layout_dir='v'):
+ super(BaseExtendedWidget, self).__init__()
+
+ self._initial_stylesheet = self.styleSheet()
+ # if layout_dir == 'v':
+ self.name = ''
+ self.central_layout = QVBoxLayout()
+ # else:
+ # self.central_layout = QHBoxLayout()
+ self.setLayout(self.central_layout)
+ self.central_layout.setContentsMargins(0, 0, 0, 0)
+
+ self.on_para_change = None
+ self.__app = None # SciApp。初始化控件的时候指定,并且在调用set_app的时候传入。
+
+ def para_changed(self):
+ if (self.on_para_change is not None) and (self.__app is not None):
+ self.on_para_change(self.__app)
+
+ def set_app(self, app):
+ """
+ 在sciwx中,需要指定SciApp。但是在PyMiner中目前还没有这种需求。
+ :param app:
+ :return:
+ """
+ self.__app = app
+
+ def is_key(self, event, type=''):
+ """
+ 'dir':判断方向键
+ 'alpha':判断是否为26个字母
+ 'hex':判断是否为十六进制数字或者字母
+ 'digit':判断是否为数字0~9
+ 'valid':包含数字、字母或者退格键。
+ """
+
+ type = type.lower()
+ if type == '':
+ return True
+ elif type.startswith('dir'):
+ return event.keysym.lower() in ('left', 'right', 'up', 'down')
+ elif type.startswith('alpha'):
+ return event.keysym in string.ascii_lowercase
+ elif type.startswith('hex'):
+ return event.keysym in string.hexdigits
+ elif type.startswith(('digit')):
+ return event.keysym in string.digits
+
+ def set_value(self, value: Any):
+ pass
+
+ def get_value(self):
+ pass
+
+ def set_params(self, *args, **kwargs):
+ pass
+
+ def alert(self, level: str, *args, **kwargs):
+ """
+ alert
+ Args:
+ level: str
+ 'normal'
+ 'warning'
+ 'error'
+
+ Returns:
+
+ """
+ if level == 'normal':
+ self.setStyleSheet(self._initial_stylesheet)
+ elif level == 'error':
+ self.setStyleSheet('QWidget{background-color:#ff0000;}')
+ elif level == 'warning':
+ self.setStyleSheet('QWidget{background-color:#ffff00;}')
+ else:
+ raise ValueError('alert not defined!!')
diff --git a/pyminer/widgets/widgets/extended/checkbuttons/__init__.py b/pyminer/widgets/widgets/extended/checkbuttons/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d95717ede7687f9c420e0578422aca5bf966a2ba
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/checkbuttons/__init__.py
@@ -0,0 +1 @@
+from .check import *
\ No newline at end of file
diff --git a/pyminer/widgets/widgets/extended/checkbuttons/check.py b/pyminer/widgets/widgets/extended/checkbuttons/check.py
new file mode 100644
index 0000000000000000000000000000000000000000..56b40fa71b916eb279f8cf336c237fa08d6caf51
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/checkbuttons/check.py
@@ -0,0 +1,30 @@
+from PySide2.QtWidgets import QLabel, QHBoxLayout, QCheckBox
+from widgets.widgets.extended.base.baseextendedwidget import BaseExtendedWidget
+
+
+class PMGCheckCtrl(BaseExtendedWidget):
+ """
+ bool, 'sport', 'do you like sport',True
+ """
+
+ def __init__(self, layout_dir: str, title: str, initial_value: bool):
+ super().__init__(layout_dir)
+
+ # layout = QHBoxLayout()
+ # layout.setContentsMargins(0, 0, 0, 0)
+ self.on_check_callback = None
+ check = QCheckBox(text=title)
+ check.stateChanged.connect(self.on_check)
+ # layout.addWidget(check)
+ self.check = check
+ self.central_layout.addWidget(check)
+ self.set_value(initial_value)
+
+ def get_value(self) -> bool:
+ return self.check.isChecked()
+
+ def set_value(self, value: bool):
+ self.check.setChecked(value)
+
+ def on_check(self):
+ self.signal_param_changed.emit(self.name)
diff --git a/pyminer/widgets/widgets/extended/comboboxes/__init__.py b/pyminer/widgets/widgets/extended/comboboxes/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..89123c2fb9244fea94042d03277a689c58e1489d
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/comboboxes/__init__.py
@@ -0,0 +1,2 @@
+from .combo import *
+from .variables_combo import PMGVariablesComboCtrl
\ No newline at end of file
diff --git a/pyminer/widgets/widgets/extended/comboboxes/combo.py b/pyminer/widgets/widgets/extended/comboboxes/combo.py
new file mode 100644
index 0000000000000000000000000000000000000000..6e4f06be8eb0660736720c0091df24ccfca60b3e
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/comboboxes/combo.py
@@ -0,0 +1,62 @@
+from typing import List, Any
+
+from PySide2.QtWidgets import QLabel, QHBoxLayout, QComboBox
+from widgets.widgets.extended.base.baseextendedwidget import BaseExtendedWidget
+
+
+class PMGComboCtrl(BaseExtendedWidget):
+ def __init__(self, layout_dir: str, title: str, initial_value: Any, choices: list, texts=None):
+ """
+ ComboBox control to select values
+ Args:
+ layout_dir:
+ title:
+ initial_value:
+ choices:
+ texts:
+ """
+ super().__init__(layout_dir)
+ self.choices = []
+ self.text_list = []
+
+ lab_title = QLabel(text=title)
+ layout = QHBoxLayout()
+ self.central_layout.addWidget(lab_title)
+ self.on_check_callback = None
+ check = QComboBox()
+
+ check.currentIndexChanged.connect(self.on_value_changed)
+
+ layout.addWidget(check)
+ self.central_layout.addLayout(layout)
+ self.check = check
+ self.set_choices(choices, texts)
+ self.set_value(initial_value)
+
+ def set_choices(self, choices: list, texts: list = None):
+ self.check.clear()
+ self.choices = choices
+ self.text_list = []
+ if texts is None:
+ for choice in choices:
+ self.text_list.append(str(choice))
+ else:
+ if len(texts) != len(choices):
+ raise Exception('Length of argument \'choices\'(len=%d) and \'texts\'(len=%d) are not same!' %
+ (len(choices), len(texts)))
+ else:
+ self.text_list = texts
+ self.check.addItems(self.text_list)
+
+ def on_value_changed(self):
+ self.signal_param_changed.emit(self.name)
+
+ def set_value(self, value: Any):
+ index = self.choices.index(value)
+ self.check.setCurrentIndex(index)
+
+ def get_value(self) -> Any:
+ try:
+ return self.choices[self.check.currentIndex()]
+ except:
+ return None
diff --git a/pyminer/widgets/widgets/extended/comboboxes/variables_combo.py b/pyminer/widgets/widgets/extended/comboboxes/variables_combo.py
new file mode 100644
index 0000000000000000000000000000000000000000..c0b301241812359e3f86ab75d245b51af17ae543
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/comboboxes/variables_combo.py
@@ -0,0 +1,28 @@
+from typing import Any
+
+from .combo import PMGComboCtrl
+
+
+class Var():
+ def __init__(self, name: str):
+ self.name = name
+
+ def __repr__(self):
+ return self.name
+
+
+class PMGVariablesComboCtrl(PMGComboCtrl):
+ def __init__(self, layout_dir: str, title: str, initial_value: Any = ""):
+ """
+ ComboBox control to select values
+ Args:
+ layout_dir:
+ title:
+ initial_value:
+ """
+ from utils import bind_panel_combo_ctrl_with_workspace
+ super().__init__(layout_dir, title, initial_value, [""], [""])
+ bind_panel_combo_ctrl_with_workspace(self)
+
+ def get_value(self) ->str:
+ return Var(super().get_value())
diff --git a/pyminer/widgets/widgets/extended/entries/__init__.py b/pyminer/widgets/widgets/extended/entries/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..886c5f38a1de15e6d8ab081528652cd424e16971
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/entries/__init__.py
@@ -0,0 +1,8 @@
+from .colorctrl import *
+from .evalctrl import *
+from .filectrl import *
+from .folderctrl import *
+from .keymappingctrl import *
+from .linectrl import *
+from .numctrl import *
+from .passwordctrl import *
diff --git a/pyminer/widgets/widgets/extended/entries/baseentryctrl.py b/pyminer/widgets/widgets/extended/entries/baseentryctrl.py
new file mode 100644
index 0000000000000000000000000000000000000000..88aafca2e61e06176bfb83593392b5df3bca3daf
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/entries/baseentryctrl.py
@@ -0,0 +1,14 @@
+from PySide2.QtWidgets import QLineEdit
+from ..base import BaseExtendedWidget
+
+
+class PMGBaseEntryCtrl(BaseExtendedWidget):
+ def __init__(self, layout_dir: str):
+ super().__init__(layout_dir)
+ self.ctrl: 'QLineEdit' = None
+
+ def set_ctrl_warning(self, warning: bool):
+ if warning == True:
+ self.ctrl.setStyleSheet("background-color:#ff0000;")
+ else:
+ self.ctrl.setStyleSheet("")
diff --git a/pyminer/widgets/widgets/extended/entries/colorctrl.py b/pyminer/widgets/widgets/extended/entries/colorctrl.py
new file mode 100644
index 0000000000000000000000000000000000000000..c35ac5addf2e45b2c219c7be12ff817d60f00684
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/entries/colorctrl.py
@@ -0,0 +1,91 @@
+from typing import Tuple
+
+from PySide2.QtGui import QColor
+from PySide2.QtWidgets import QLineEdit, QLabel, QHBoxLayout, QPushButton, QColorDialog
+from widgets.widgets.extended.base.baseextendedwidget import BaseExtendedWidget
+
+
+class PMGColorCtrl(BaseExtendedWidget):
+ def __init__(self, layout_dir: str, title: str, initial_value: str):
+ super().__init__(layout_dir)
+ self.on_check_callback = None
+ self.prefix = QLabel(text=title)
+
+ entryLayout = QHBoxLayout()
+
+ self.ctrl = QLineEdit()
+ self.ctrl.textChanged.connect(self.ontext)
+
+ self.color_button = QPushButton()
+ self.color_button.clicked.connect(self.oncolor)
+ self.central_layout.addWidget(self.prefix)
+ self.central_layout.addLayout(entryLayout)
+ entryLayout.addWidget(self.ctrl)
+ entryLayout.addWidget(self.color_button)
+ self.set_value(initial_value)
+
+ def ontext(self, event):
+ if self.get_value() is None:
+ self.color_button.setStyleSheet("background-color:#ff0000;")
+ self.ctrl.setStyleSheet("background-color:#ff0000;")
+ else:
+ self.ctrl.setStyleSheet('background-color:#ffffff;')
+ self.color_button.setStyleSheet("background-color:%s;" % self.colorTup2Str(self.get_value()))
+ self.para_changed()
+ self.ctrl.update()
+ if callable(self.on_check_callback):
+ self.on_check_callback()
+
+ def oncolor(self, event):
+ color = QColorDialog.getColor(initial=QColor(*self.get_value()))
+ self.set_value(self.colorStr2Tup(color.name()))
+ if callable(self.on_check_callback):
+ self.on_check_callback()
+
+ def set_value(self, color: Tuple = None):
+ if color is None:
+ color = (255, 255, 255)
+ strcolor = self.colorTup2Str(color)
+ self.color_button.setStyleSheet('background-color:%s;' % strcolor)
+ self.ctrl.clear()
+ self.ctrl.setText(strcolor)
+
+ def get_value(self):
+ rgb = self.ctrl.text().strip()
+ if len(rgb) != 7 or rgb[0] != '#':
+ return None
+ try:
+ int(rgb[1:], 16)
+ except:
+ import traceback
+ traceback.print_exc()
+ return None
+ return self.colorStr2Tup(rgb)
+
+ def colorStr2Tup(self, value: str) -> tuple: # pos或者wh的输入都是tuple
+ def convert(c):
+ v = ord(c)
+ if (48 <= v <= 57):
+ return v - 48
+ else:
+ return v - 87 # 返回a的值。
+
+ value = value.lower()
+ c0 = convert(value[1])
+ c1 = convert(value[2])
+ c2 = convert(value[3])
+ c3 = convert(value[4])
+ c4 = convert(value[5])
+ c5 = convert(value[6])
+ a1 = c0 * 16 + c1
+ a2 = c2 * 16 + c3
+ a3 = c4 * 16 + c5
+ return (a1, a2, a3)
+
+ def colorTup2Str(self, value: tuple) -> str:
+ if value is None:
+ return None
+ strcolor = '#'
+ for i in value:
+ strcolor += hex(int(i))[-2:].replace('x', '0')
+ return strcolor
diff --git a/pyminer/widgets/widgets/extended/entries/evalctrl.py b/pyminer/widgets/widgets/extended/entries/evalctrl.py
new file mode 100644
index 0000000000000000000000000000000000000000..3ef7cf506729299c531896cd6509cf5e5b6907cc
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/entries/evalctrl.py
@@ -0,0 +1,64 @@
+from typing import Any
+
+from PySide2.QtWidgets import QLineEdit, QLabel, QHBoxLayout, QPushButton, QMessageBox
+from widgets.widgets.extended.base.baseextendedwidget import BaseExtendedWidget
+
+
+class PMGEvalCtrl(BaseExtendedWidget):
+ """
+ type:
+ safe--can only input ',[](){}:1234567890.'
+ """
+
+ def __init__(self, layout_dir: str, title: str, initial_value: str, type='normal'):
+ super().__init__(layout_dir)
+ self.allowed_chars = set(' ,[](){}:1234567890.+-*/')
+ self.on_check_callback = None
+ self.prefix = QLabel(text=title)
+ self.type = type
+ entryLayout = QHBoxLayout()
+
+ self.ctrl = QLineEdit()
+
+ self.color_button = QPushButton()
+ self.color_button.clicked.connect(self.on_eval_test)
+
+ self.central_layout.addWidget(self.prefix)
+ self.central_layout.addLayout(entryLayout)
+ entryLayout.addWidget(self.ctrl)
+ entryLayout.addWidget(self.color_button)
+ self.set_value(initial_value)
+
+ def get_code(self) -> str:
+ text = self.ctrl.text()
+ if self.type == 'safe':
+ for char in text:
+ if char not in self.allowed_chars:
+ return None
+ return text
+
+ def set_value(self, obj: Any):
+ try:
+ self.ctrl.setText(repr(obj))
+ except:
+ import traceback
+ traceback.print_exc()
+
+ def get_value(self) -> object:
+ if self.get_code() is not None:
+ try:
+ return eval(self.ctrl.text())
+ except:
+ import traceback
+ traceback.print_exc()
+ return None
+ else:
+ return None
+
+ def on_eval_test(self):
+ """
+ 点击计算按钮,弹出对话框显示计算结果。
+ :return:
+ """
+ val = self.get_value()
+ QMessageBox.information(self, self.tr('Result'), repr(val), QMessageBox.Ok)
diff --git a/pyminer/widgets/widgets/extended/entries/filectrl.py b/pyminer/widgets/widgets/extended/entries/filectrl.py
new file mode 100644
index 0000000000000000000000000000000000000000..95e529372d9e55ed3a5b13869ee27ee1bdbd4ae2
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/entries/filectrl.py
@@ -0,0 +1,65 @@
+import os
+from PySide2.QtWidgets import QPushButton, QLineEdit, QLabel, QHBoxLayout, QFileDialog
+from widgets.widgets.extended.base.baseextendedwidget import BaseExtendedWidget
+
+
+class PMGFileCtrl(BaseExtendedWidget):
+ def __init__(self, layout_dir: str, title, initial_value: str = '', filt="All Files (*);;Text Files (*.txt)",
+ initial_dir: str = '', mode: str = 'open'):
+ super().__init__(layout_dir)
+ self.prefix = lab_title = QLabel(text=title)
+ path_layout = QHBoxLayout()
+ self.layout().addWidget(lab_title)
+ self.filter = filt
+
+ self.ctrl = QLineEdit()
+ self.mode = mode
+ self.current_dir = initial_dir
+ if os.path.exists(initial_value):
+ self.ctrl.setText(initial_value)
+ self.current_dir = os.path.dirname(self.current_dir)
+ else:
+ self.current_dir = initial_dir
+ path_layout.addWidget(self.ctrl)
+ self.file_choose_button = QPushButton('..')
+ self.file_choose_button.clicked.connect(self.select_file)
+ self.file_choose_button.setMaximumWidth(30)
+ path_layout.addWidget(self.file_choose_button)
+ self.central_layout.addLayout(path_layout)
+ self.ctrl.textChanged.connect(self.on_text_changed)
+
+ def select_file(self):
+ if self.mode == 'open':
+ name, ext = QFileDialog.getOpenFileName(self, self.tr("Select File"),
+ self.current_dir, # 起始路径
+ self.filter)
+ elif self.mode == 'save':
+ name, ext = QFileDialog.getSaveFileName(self, self.tr("Save File"), self.current_dir,
+ self.filter)
+ else:
+ raise ValueError
+ if name != '':
+ self.ctrl.setText(name)
+ self.current_dir = os.path.dirname(name)
+
+ def on_text_changed(self, event):
+ """
+
+ Args:
+ event:
+
+ Returns:
+
+ """
+ # self.para_changed()
+ path = self.ctrl.text().strip()
+ if self.mode == 'open' and os.path.isfile(path):
+ self.signal_param_changed.emit(self.name)
+ elif self.mode == 'save':
+ self.signal_param_changed.emit(self.name)
+
+ def set_value(self, value: str):
+ self.ctrl.setText(value)
+
+ def get_value(self) -> str:
+ return self.ctrl.text()
diff --git a/pyminer/widgets/widgets/extended/entries/folderctrl.py b/pyminer/widgets/widgets/extended/entries/folderctrl.py
new file mode 100644
index 0000000000000000000000000000000000000000..4714268f75c149ae5c14e584511ec0ef89528067
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/entries/folderctrl.py
@@ -0,0 +1,35 @@
+import os
+from PySide2.QtWidgets import QPushButton, QLineEdit, QLabel, QHBoxLayout, QFileDialog
+from widgets.widgets.extended.base.baseextendedwidget import BaseExtendedWidget
+
+
+class PMGFolderCtrl(BaseExtendedWidget):
+ def __init__(self, layout_dir: str, title, initial_value: str = '', filt=None):
+ super().__init__(layout_dir)
+ self.prefix = lab_title = QLabel(text=title)
+ path_layout = QHBoxLayout()
+ path_layout.addWidget(lab_title)
+
+ self.ctrl = QLineEdit()
+ self.ctrl.setText(initial_value)
+ path_layout.addWidget(self.ctrl)
+ self.file_choose_button = QPushButton('..')
+ self.file_choose_button.clicked.connect(self.select_file)
+ path_layout.addWidget(self.file_choose_button)
+ self.central_layout.addLayout(path_layout)
+
+ def select_file(self):
+ name = QFileDialog.getExistingDirectory(self, self.tr("Select File"),
+ os.path.dirname(self.get_value()), # 起始路径
+ )
+ if name != '':
+ self.ctrl.setText(name)
+
+ def ontext(self, event):
+ self.para_changed()
+
+ def set_value(self, value: str):
+ self.ctrl.setText(value)
+
+ def get_value(self) -> str:
+ return self.ctrl.text()
diff --git a/pyminer/widgets/widgets/extended/entries/funcctrl.py b/pyminer/widgets/widgets/extended/entries/funcctrl.py
new file mode 100644
index 0000000000000000000000000000000000000000..8ce2f868301f76a4e16e2fa2a99ae0fe59dbc860
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/entries/funcctrl.py
@@ -0,0 +1,101 @@
+import ast
+import astunparse
+from typing import Any, Tuple, List
+
+from PySide2.QtWidgets import QLineEdit, QLabel, QHBoxLayout, QPushButton, QMessageBox
+
+from widgets.widgets.extended.base.baseextendedwidget import BaseExtendedWidget
+
+
+class CodeVisitor(ast.NodeVisitor):
+ def __init__(self):
+ super(CodeVisitor, self).__init__()
+ self.preserved = {"pi", "e"}
+ self.called = set()
+ self.func_args = set()
+ self._names = set()
+
+ def visit_Name(self, node: ast.Name) -> Any:
+ self._names.add(node.id)
+
+
+ def visit_Call(self, node: ast.Call) -> Any:
+ self.generic_visit(node)
+ self.called.add(node.func.id)
+
+ def get_result(self) -> Tuple[List[str], List[str]]:
+ """
+
+ Returns: 定义的名称,以及调用的ID名称。
+
+ """
+ names = self._names.copy()
+ names.difference_update(self.preserved)
+ names.difference_update(self.called)
+ return list(names), list(self.called)
+
+
+# n = ast.parse("x*cos(v,sin(2*pi*x))")
+# print(CodeVisitor().visit(n))
+# print(cv := CodeVisitor())
+# cv.visit(n)
+# print(cv.get_result())
+# # # print(ast.get_source_segment("x*cos(v,sin(2*pi*x))",n))
+# # print(ast.dump(n))
+# # print(astunparse.unparse(n))
+
+
+class PMGFuncCtrl(BaseExtendedWidget):
+ """
+
+ 输入:一个有效的函数表达式。
+ 其中,里面的变量名会自动进行检测。
+
+ """
+
+ def __init__(self, layout_dir: str, title: str, initial_value: str):
+ super().__init__(layout_dir)
+ self.allowed_chars = set(' ,[](){}:1234567890.+-*/')
+ self.on_check_callback = None
+ self.prefix = QLabel(text=title)
+ self.type = type
+ entryLayout = QHBoxLayout()
+ self.ctrl = QLineEdit()
+ self.central_layout.addWidget(self.prefix)
+ self.central_layout.addLayout(entryLayout)
+ entryLayout.addWidget(self.ctrl)
+ self.set_value(initial_value)
+
+ def get_code(self) -> str:
+ text = self.ctrl.text()
+ if self.type == 'safe':
+ for char in text:
+ if char not in self.allowed_chars:
+ return None
+ return text
+
+ def set_value(self, obj: Any):
+ try:
+ self.ctrl.setText(repr(obj))
+ except:
+ import traceback
+ traceback.print_exc()
+
+ def get_value(self) -> object:
+ if self.get_code() is not None:
+ try:
+ return eval(self.ctrl.text())
+ except:
+ import traceback
+ traceback.print_exc()
+ return None
+ else:
+ return None
+
+ def on_eval_test(self):
+ """
+ 点击计算按钮,弹出对话框显示计算结果。
+ :return:
+ """
+ val = self.get_value()
+ QMessageBox.information(self, self.tr('Result'), repr(val), QMessageBox.Ok)
diff --git a/pyminer/widgets/widgets/extended/entries/keymappingctrl.py b/pyminer/widgets/widgets/extended/entries/keymappingctrl.py
new file mode 100644
index 0000000000000000000000000000000000000000..eb8d8dad83f9b131a6ee336c93faddef9b149324
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/entries/keymappingctrl.py
@@ -0,0 +1,46 @@
+from PySide2.QtCore import Qt
+from PySide2.QtGui import QKeyEvent, QKeySequence
+from PySide2.QtWidgets import QLineEdit, QLabel, QHBoxLayout
+from widgets.widgets.extended.base.baseextendedwidget import BaseExtendedWidget
+
+
+class PMGKeyMapCtrl(BaseExtendedWidget):
+ def __init__(self, layout_dir: str, title: str, initial_value: str):
+ super().__init__(layout_dir)
+ self.on_check_callback = None
+ # self.modifiers = {Qt.ControlModifier: Qt.CTRL, Qt.AltModifier: Qt.ALT, Qt.ShiftModifier: Qt.SHIFT,
+ # Qt.AltModifier | Qt.ControlModifier: Qt.ALT + Qt.CTRL,
+ # Qt.ControlModifier | Qt.ShiftModifier: Qt.SHIFT + Qt.CTRL
+ # }
+ self.modifiers = {}
+ self.prefix = QLabel(text=title)
+
+ entryLayout = QHBoxLayout()
+ entryLayout.setContentsMargins(0, 0, 0, 0)
+ self.ctrl = QLineEdit()
+ self.ctrl.keyPressEvent = self.key_press
+ # self.ctrl.textChanged.connect(self.ontext)
+
+ self.central_layout.addWidget(self.prefix)
+ self.central_layout.addLayout(entryLayout)
+ entryLayout.addWidget(self.ctrl)
+ self.set_value(initial_value)
+
+ def key_press(self, event: QKeyEvent):
+ k = self.modifiers.get(event.modifiers())
+ if k is not None:
+ key_seq = QKeySequence(k + event.key())
+ try:
+ text = key_seq.toString()
+ self.ctrl.setText(text)
+ except:
+ import traceback
+ traceback.print_exc()
+
+ def set_value(self, value: str):
+ # key_seq = QKeySequence(value)
+ # key_seq.keyBindings()
+ self.ctrl.setText(value)
+
+ def get_value(self):
+ return self.ctrl.text()
diff --git a/pyminer/widgets/widgets/extended/entries/linectrl.py b/pyminer/widgets/widgets/extended/entries/linectrl.py
new file mode 100644
index 0000000000000000000000000000000000000000..ebd749416e7ed9bf4e1496b64deca51a8c69d16a
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/entries/linectrl.py
@@ -0,0 +1,33 @@
+from PySide2.QtWidgets import QLineEdit, QLabel, QHBoxLayout
+from widgets.widgets.extended.base.baseextendedwidget import BaseExtendedWidget
+
+
+class PMGLineCtrl(BaseExtendedWidget):
+ def __init__(self, layout_dir: str, title: str, initial_value: str):
+ super().__init__(layout_dir)
+ self.on_check_callback = None
+
+ self.prefix = QLabel(text=title)
+
+ entryLayout = QHBoxLayout()
+ entryLayout.setContentsMargins(0, 0, 0, 0)
+ self.ctrl = QLineEdit()
+ self.ctrl.textChanged.connect(self.ontext)
+
+ self.central_layout.addWidget(self.prefix)
+ self.central_layout.addLayout(entryLayout)
+ entryLayout.addWidget(self.ctrl)
+ self.set_value(initial_value)
+
+ def param_changed(self, event):
+ pass
+
+ def ontext(self, event):
+ self.para_changed()
+
+ def set_value(self, text: str):
+ self.ctrl.clear()
+ self.ctrl.setText(text)
+
+ def get_value(self) -> str:
+ return self.ctrl.text()
diff --git a/pyminer/widgets/widgets/extended/entries/numctrl.py b/pyminer/widgets/widgets/extended/entries/numctrl.py
new file mode 100644
index 0000000000000000000000000000000000000000..2fea84d033110a954ae428ca487a4f7150d7cf79
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/entries/numctrl.py
@@ -0,0 +1,73 @@
+import math
+from typing import Tuple, Union
+
+from PySide2.QtWidgets import QLineEdit, QLabel, QHBoxLayout
+
+from .baseentryctrl import PMGBaseEntryCtrl
+
+
+class PMGNumberCtrl(PMGBaseEntryCtrl):
+ """
+ 用于输入数值。
+ """
+
+ def __init__(self, layout_dir: str, title: str, initial_value: Union[int, float], unit: str = '',
+ range: Tuple[Union[int, float], Union[int, float]] = None):
+ super().__init__(layout_dir=layout_dir)
+ self.on_check_callback = None
+ if range is None:
+ range = (float('-inf'), float('inf'))
+
+ if isinstance(initial_value, int) and isinstance(range[0], int) and isinstance(range[1], int):
+ self.num_type = int
+ else:
+ self.num_type = float
+
+ self.prefix = QLabel(text=title)
+ entryLayout = QHBoxLayout()
+ entryLayout.setContentsMargins(0, 0, 0, 0)
+
+ self.ctrl = QLineEdit()
+ self.ctrl.textChanged.connect(self.ontext)
+
+ self.postfix = QLabel(text=unit)
+
+ self.central_layout.addWidget(self.prefix)
+ self.central_layout.addLayout(entryLayout)
+ entryLayout.addWidget(self.ctrl)
+ entryLayout.addWidget(self.postfix)
+
+ self.min, self.max = range
+ self.accury = initial_value
+ self.set_value(initial_value)
+
+ def ontext(self, event):
+ if self.get_value() is None:
+ self.set_ctrl_warning(True)
+ else:
+ self.set_ctrl_warning(False)
+ self.para_changed()
+ self.ctrl.update()
+ if callable(self.on_check_callback):
+ self.on_check_callback()
+ self.signal_param_changed.emit(self.name)
+
+ def set_value(self, n):
+ self.ctrl.clear()
+ self.ctrl.setText(str(n))
+
+ def get_value(self):
+ text = self.ctrl.text()
+ try:
+ if text.lower() == "e":
+ return math.e
+ elif text.lower() == "pi":
+ return math.pi
+ num = self.num_type(text)
+ except ValueError:
+ import traceback
+ traceback.print_exc()
+ return None
+ if num < self.min or num > self.max:
+ return None
+ return num
diff --git a/pyminer/widgets/widgets/extended/entries/passwordctrl.py b/pyminer/widgets/widgets/extended/entries/passwordctrl.py
new file mode 100644
index 0000000000000000000000000000000000000000..4877cf110c36372758d438a46cab196617ebc776
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/entries/passwordctrl.py
@@ -0,0 +1,34 @@
+from PySide2.QtWidgets import QLineEdit, QLabel, QHBoxLayout
+from widgets.widgets.extended.base.baseextendedwidget import BaseExtendedWidget
+
+
+class PMGPasswordCtrl(BaseExtendedWidget):
+ def __init__(self, layout_dir: str, title: str, initial_value: str):
+ super().__init__(layout_dir)
+ self.on_check_callback = None
+
+ self.prefix = QLabel(text=title)
+
+ entryLayout = QHBoxLayout()
+ entryLayout.setContentsMargins(0, 0, 0, 0)
+ self.ctrl = QLineEdit()
+ self.ctrl.setEchoMode(QLineEdit.Password)
+ self.ctrl.textChanged.connect(self.ontext)
+
+ self.central_layout.addWidget(self.prefix)
+ self.central_layout.addLayout(entryLayout)
+ entryLayout.addWidget(self.ctrl)
+ self.set_value(initial_value)
+
+ def param_changed(self, event):
+ pass
+
+ def ontext(self, event):
+ self.para_changed()
+
+ def set_value(self, text: str):
+ self.ctrl.clear()
+ self.ctrl.setText(text)
+
+ def get_value(self) -> str:
+ return self.ctrl.text()
diff --git a/pyminer/widgets/widgets/extended/labels/__init__.py b/pyminer/widgets/widgets/extended/labels/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..b6650e547c7ce49b6772a5ceb65d6324f6ed0ff0
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/labels/__init__.py
@@ -0,0 +1 @@
+from .label import PMGLabelShow
\ No newline at end of file
diff --git a/pyminer/widgets/widgets/extended/labels/label.py b/pyminer/widgets/widgets/extended/labels/label.py
new file mode 100644
index 0000000000000000000000000000000000000000..0482aeba51008df3bb1c8c92e1eab3bfa763d18e
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/labels/label.py
@@ -0,0 +1,23 @@
+from PySide2.QtWidgets import QLabel, QVBoxLayout, QSpacerItem, QSizePolicy
+from ..base import BaseExtendedWidget
+
+
+class PMGLabelShow(BaseExtendedWidget):
+ def __init__(self, layout_dir: str, title: str, initial_value: str):
+ super().__init__(layout_dir=layout_dir)
+ self.on_check_callback = None
+ layout = QVBoxLayout()
+ self.prefix = QLabel(text=title)
+ self.ctrl = QLabel()
+
+ self.ctrl.setStyleSheet('QLabel{font-size:20px;}')
+ self.central_layout.addLayout(layout)
+ # layout.addWidget(QLabel(' '))
+ layout.addWidget(self.prefix)
+ layout.addWidget(self.ctrl)
+ # layout.addItem(QSpacerItem(20, 20, QSizePolicy.Ignored, QSizePolicy.Expanding))
+
+ self.set_value(initial_value)
+
+ def set_value(self, value: str):
+ self.ctrl.setText(value)
diff --git a/pyminer/widgets/widgets/extended/lists/__init__.py b/pyminer/widgets/widgets/extended/lists/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..69b1cc7865bee181a39ab80ad35b72719fa64690
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/lists/__init__.py
@@ -0,0 +1 @@
+from .listwgt import PMGListCtrl
diff --git a/pyminer/widgets/widgets/extended/lists/listwgt.py b/pyminer/widgets/widgets/extended/lists/listwgt.py
new file mode 100644
index 0000000000000000000000000000000000000000..4a933c7088b298bb4645ace916feeff0d8379dd9
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/lists/listwgt.py
@@ -0,0 +1,109 @@
+from typing import List, Callable
+
+from PySide2.QtCore import Qt, QStringListModel
+from PySide2.QtGui import QMouseEvent
+from PySide2.QtWidgets import QLabel, QHBoxLayout, QListWidget, QVBoxLayout, QPushButton, QLineEdit
+from PySide2.QtWidgets import QListWidgetItem, QCompleter
+
+from widgets.widgets.extended.base.baseextendedwidget import BaseExtendedWidget
+
+
+class PMGListCtrl(BaseExtendedWidget):
+ def __init__(self, layout_dir: str, title: str, initial_value: List[List[str]] = None,
+ new_id_func: Callable = None):
+ super().__init__(layout_dir)
+ self.choices = []
+ initial_value = initial_value if initial_value is not None else [[], []]
+ self.text_list = []
+ lab_title = QLabel(text=title)
+ layout = QHBoxLayout()
+ self.central_layout.addWidget(lab_title)
+ self.on_check_callback = None
+ self.list_widget = QListWidget()
+ self.list_widget.mouseDoubleClickEvent = self.on_listwidget_double_cicked
+ # if initial_value is not None:
+
+ self.set_value(initial_value)
+ layout_tools = QVBoxLayout()
+ self.button_add_item = QPushButton('+')
+ self.button_delete_item = QPushButton('-')
+ self.button_delete_item.clicked.connect(self.delete_row)
+ self.button_add_item.clicked.connect(self.add_row)
+ self.button_add_item.setMaximumWidth(20)
+ self.button_delete_item.setMaximumWidth(20)
+ layout_tools.addWidget(self.button_add_item)
+ layout_tools.addWidget(self.button_delete_item)
+ layout.addLayout(layout_tools)
+ layout.addWidget(self.list_widget)
+ self.central_layout.addLayout(layout)
+ self.data = initial_value
+ self.new_id_func = new_id_func
+
+ self.text_edit = QLineEdit(parent=self.list_widget)
+ self.text_edit.setWindowFlags(self.text_edit.windowFlags() | Qt.Dialog | Qt.FramelessWindowHint)
+ self.text_edit.hide()
+ self.completer = QCompleter()
+ self.text_edit.setCompleter(self.completer)
+ self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion)
+
+ def set_completions(self, completions: List[str]):
+ """
+ 设置补全内容
+ Args:
+ completions:
+
+ Returns:
+
+ """
+ self.completer.setModel(QStringListModel(completions))
+
+ def new_id(self):
+ if callable(self.new_id_func):
+ return self.new_id_func()
+ else:
+ return None
+
+ def add_row(self):
+ self.data = self.get_value()
+ self.data[0].append(self.new_id())
+ self.data[1].append('Unnamed')
+ self.list_widget.addItem(QListWidgetItem('Unnamed'))
+
+ def delete_row(self):
+ index = self.list_widget.currentIndex().row()
+ self.data[0].pop(index)
+ self.data[1].pop(index)
+ self.list_widget.takeItem(index)
+
+ def on_listwidget_double_cicked(self, evt: QMouseEvent):
+ print('edit', evt)
+ pos = evt.globalPos()
+ current_item: QListWidgetItem = self.list_widget.currentItem()
+
+ def set_value():
+ current_item.setText(self.text_edit.text())
+ self.text_edit.hide()
+ self.text_edit.returnPressed.disconnect(set_value)
+
+ item: QListWidgetItem = self.list_widget.currentItem()
+
+ self.text_edit.setGeometry(pos.x(), pos.y(), 200, 20)
+ self.text_edit.returnPressed.connect(set_value)
+ self.text_edit.show()
+ # self.list_widget.editItem(item)
+
+ def get_value(self):
+ text = []
+ for i in range(self.list_widget.count()):
+ text.append(self.list_widget.item(i).text())
+ self.data[1] = text
+ assert len(self.data[1]) == len(self.data[0]), repr(self.data)
+ return self.data
+
+ def set_value(self, data: List[List[str]]):
+ assert isinstance(data, list), data
+ self.list_widget.clear()
+ self.list_widget.addItems(data[1])
+ self.data = data
+ for index in range(self.list_widget.count()):
+ item: QListWidgetItem = self.list_widget.item(index)
diff --git a/pyminer/widgets/widgets/extended/others/__init__.py b/pyminer/widgets/widgets/extended/others/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..975bf08bafcf825b3e7be598e1a5ef4817a70f29
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/others/__init__.py
@@ -0,0 +1 @@
+from .multitypeparaminput import PMGMultiTypeCtrl
\ No newline at end of file
diff --git a/pyminer/widgets/widgets/extended/others/monitors/__init__.py b/pyminer/widgets/widgets/extended/others/monitors/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/widgets/widgets/extended/others/multitypeparaminput.py b/pyminer/widgets/widgets/extended/others/multitypeparaminput.py
new file mode 100644
index 0000000000000000000000000000000000000000..ea049d191365cc7ceeb0c7ea514fc9704bde2def
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/others/multitypeparaminput.py
@@ -0,0 +1,103 @@
+from typing import List, Any, Dict
+
+from PySide2.QtWidgets import QApplication, QRadioButton, QLayout
+from PySide2.QtWidgets import QLabel
+
+from widgets.widgets.extended.base.baseextendedwidget import BaseExtendedWidget
+
+
+class PMGMultiTypeCtrl(BaseExtendedWidget):
+ def __init__(self, layout_dir: str, title: str, initial_value: Any=None, types: List[Dict]=None):
+ super().__init__(layout_dir)
+ from widgets.widgets.composited.generalpanel import PMGPanel
+ self.on_check_callback = None
+
+ self.prefix = QLabel(text=title)
+
+ self.central_layout.addWidget(self.prefix)
+
+ self.radio_buttons: List[QRadioButton] = []
+ self.ctrls = types
+ radiobtn_texts = [self.ctrls[i]["type_title"] for i in range(len(self.ctrls))]
+ for i, ctrl in enumerate(self.ctrls):
+ radio_button = QRadioButton(radiobtn_texts[i])
+ radio_button.toggled.connect(self.on_type_changed)
+ self.radio_buttons.append(radio_button)
+ self.central_layout.addWidget(radio_button)
+
+ self.sub_panel = PMGPanel()
+ self.central_layout.addWidget(self.sub_panel)
+
+ self.set_value(initial_value)
+
+ def on_type_changed(self, event):
+
+ index = self.get_type_index()
+ self.sub_panel.set_items([self.ctrls[index]["ctrls"]])
+
+ # print(self.get_value())
+ # self.setFixedSize(0)
+
+ def on_param_changed(self, event):
+ print(self.get_value())
+
+ def ontext(self, event):
+ self.para_changed()
+
+ def set_type_index(self, index: int):
+ self.radio_buttons[index].setChecked(True)
+
+ def get_type_index(self) -> int:
+ for i, btn in enumerate(self.radio_buttons):
+ if btn.isChecked():
+ return i
+
+ def set_value(self, value: Any):
+ for i, ctrl in enumerate(self.ctrls):
+ try:
+ self.set_type_index(i)
+ keys = list(self.sub_panel.widgets_dic.keys())
+ assert len(keys) == 1
+ print(i, ctrl, {keys[0]: value})
+ self.sub_panel.set_value({keys[0]: value})
+ break
+ except:
+ import traceback
+ traceback.print_exc()
+
+ def get_value(self) -> Any:
+ values = self.sub_panel.get_value()
+ fcn = self.ctrls[self.get_type_index()].get("on_ok")
+ if fcn is None:
+ if len(list(values.keys())) == 1:
+ return values[list(values.keys())[0]]
+ else:
+ return values
+ else:
+ return fcn(values)
+
+
+if __name__ == '__main__':
+ app = QApplication()
+ types = [{
+ "type_title": "字符串列表",
+ "ctrls": [
+ ("list_ctrl", "list_ctrl", 'input list of strings',),
+ ],
+ "on_ok": lambda values: values["list_ctrl"][1]
+ },
+ {
+ "type_title": "字符串",
+ "ctrls": [
+ ("line_ctrl", "aaaa", 'input a string', "Please input a string"),
+ ],
+ "on_ok": None
+ }
+ ]
+
+ e = PMGMultiTypeCtrl("v", "aaaaaa", [[None, None, None], ["aaaaaa", "aaaaaa", "aaaaaa"]], types)
+
+ # e.set_value([[None, None, None], ["aaaaaa", "aaaaaa", "aaaaaa"]])
+ e.show()
+
+ app.exec_()
diff --git a/pyminer/widgets/widgets/extended/plots/__init__.py b/pyminer/widgets/widgets/extended/plots/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..06b6e4b8e2432c3a9c6c8fbfc58fc688a53f7604
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/plots/__init__.py
@@ -0,0 +1,5 @@
+try:
+ from .lines import *
+except ImportError as e:
+ import warnings
+ warnings.warn(str(e))
\ No newline at end of file
diff --git a/pyminer/widgets/widgets/extended/plots/lines/__init__.py b/pyminer/widgets/widgets/extended/plots/lines/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..fb0d359c008d978e5a13360939dd4cedc1244d01
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/plots/lines/__init__.py
@@ -0,0 +1 @@
+from .timeseries import *
\ No newline at end of file
diff --git a/pyminer/widgets/widgets/extended/plots/lines/timeseries.py b/pyminer/widgets/widgets/extended/plots/lines/timeseries.py
new file mode 100644
index 0000000000000000000000000000000000000000..4df429f130d4ad91a41a3188fa0da363890b9706
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/plots/lines/timeseries.py
@@ -0,0 +1,64 @@
+from typing import List, Dict, Tuple
+
+import pyqtgraph as pg
+from PySide2.QtWidgets import QHBoxLayout
+from widgets.widgets.extended.base import BaseExtendedWidget
+from widgets import iter_isinstance, TYPE_RANGE
+
+
+class PMGTimeSeriesShow(BaseExtendedWidget):
+ def __init__(self, layout_dir: str, title: str, initial_value: Dict[str, List[float]],
+ y_range: Tuple[int, int] = None, threshold_range: TYPE_RANGE = None,
+ xlabel: str = '', ylabel: str = '',
+ face_color: str = '#ffffff', legend_face_color: str = '#dddddd', x_range: Tuple[int, int] = None,
+ text_color: str = 'k'):
+ super().__init__(layout_dir=layout_dir)
+ from widgets import PMGTimeSeriesPlot
+ entryLayout = QHBoxLayout()
+ entryLayout.setContentsMargins(0, 0, 0, 0)
+
+ self.ctrl = PMGTimeSeriesPlot(parent=None, threshold_range=threshold_range, face_color=face_color,
+ text_color=text_color)
+ self.ctrl.time_series.xlabel = xlabel
+ self.ctrl.time_series.ylabel = ylabel
+ self.ctrl.time_series.title = title
+ self.ctrl.time_series.x_range = x_range
+ self.ctrl.time_series.y_range = y_range
+ self.ctrl.time_series.threshold_range = threshold_range
+ self.ctrl.time_series.face_color = face_color
+ pg.setConfigOption('background', face_color)
+ self.ctrl.time_series.legend_face_color = legend_face_color
+ self.central_layout.addLayout(entryLayout)
+ entryLayout.addWidget(self.ctrl)
+
+ self.accury = initial_value
+ if initial_value is not None:
+ self.set_value(initial_value)
+
+ def set_value(self, values: Dict[str, List[float]]):
+ """
+ {'timestamps':[...],
+ 'info1':{name:'unnamed','data':[...]}
+ }
+ :param values:
+ :return:
+ """
+ timestamps = values['timestamps']
+ values_list = []
+ tags_list = []
+ length = len(timestamps)
+ for k in values:
+ if k != 'timestamps':
+ assert len(values[k]['data']) == length, 'timestamps and datas may not be of same length!'
+ assert values[k].get('data') is not None
+ assert values[k].get('tag') is not None
+ values_list.append(values[k]['data'])
+ tags_list.append(values[k]['tag'])
+ self.ctrl.set_data(timestamps, values=values_list, tags=tags_list)
+
+ def alert(self, alert_level: int):
+ self.ctrl.alert(alert_level)
+
+ def set_threshold_y(self, threshold: Tuple[int, int]):
+ self.ctrl.time_series.y_range = threshold
+ self.ctrl.time_series.threshold_range = threshold
diff --git a/pyminer/widgets/widgets/extended/radiobuttons/__init__.py b/pyminer/widgets/widgets/extended/radiobuttons/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/widgets/widgets/extended/radiobuttons/radiobuttonctrl.py b/pyminer/widgets/widgets/extended/radiobuttons/radiobuttonctrl.py
new file mode 100644
index 0000000000000000000000000000000000000000..f6159ff2bf682806a1fc070b2016e872e8dfb00c
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/radiobuttons/radiobuttonctrl.py
@@ -0,0 +1,63 @@
+import sys
+from typing import List
+
+from PySide2.QtWidgets import *
+from PySide2.QtWidgets import QApplication
+from PySide2.QtWidgets import QLabel
+
+from widgets.widgets.extended.base.baseextendedwidget import BaseExtendedWidget
+
+
+class PMGRadioCtrl(BaseExtendedWidget):
+ def __init__(self, layout_dir: str, title: str, initial_value: str,
+ choices: list, texts=None):
+ super().__init__(layout_dir)
+ if texts is not None:
+ assert len(choices) == len(texts)
+ else:
+ texts = [str(c) for c in choices]
+ self.on_check_callback = None
+
+ self.prefix = QLabel(text=title)
+
+ # entryLayout = QHBoxLayout()
+ # entryLayout.setContentsMargins(0, 0, 0, 0)
+ # self.ctrl = QLineEdit()
+ # self.ctrl.textChanged.connect(self.ontext)
+
+ # self.central_layout.addWidget(self.prefix)
+ # self.central_layout.addLayout(entryLayout)
+ # entryLayout.addWidget(self.ctrl)
+ self.radio_buttons: List[QRadioButton] = []
+ self.choices = choices
+ self.texts = texts
+ for text in texts:
+ radio_button = QRadioButton(text)
+ radio_button.toggled.connect(self.on_param_changed)
+ self.radio_buttons.append(radio_button)
+ self.central_layout.addWidget(radio_button)
+
+ self.set_value(initial_value)
+
+ def on_param_changed(self, event):
+ if event:
+ self.para_changed()
+
+ def set_value(self, value: str):
+ for i, c in enumerate(self.choices):
+ if c == value:
+ self.radio_buttons[i].setChecked(True)
+
+ def get_value(self) -> str:
+ for i, btn in enumerate(self.radio_buttons):
+ if btn.isChecked():
+ return self.choices[i]
+ raise ValueError(self.choices)
+
+
+if __name__ == '__main__':
+ app = QApplication()
+ radioDemo = PMGRadioCtrl("v", "Radio Demo", "aaa", ["aaa", "bbb", "ccc"], ["啊啊啊", "波波波", "呲呲呲"])
+ radioDemo.set_value("bbb")
+ radioDemo.show()
+ sys.exit(app.exec_())
diff --git a/pyminer/widgets/widgets/extended/spins/__init__.py b/pyminer/widgets/widgets/extended/spins/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..f6a43a7fd2ca1d214df3d508ffa907752b22e4c2
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/spins/__init__.py
@@ -0,0 +1,2 @@
+from .datetime import *
+from .numberspin import *
\ No newline at end of file
diff --git a/pyminer/widgets/widgets/extended/spins/datetime.py b/pyminer/widgets/widgets/extended/spins/datetime.py
new file mode 100644
index 0000000000000000000000000000000000000000..4d96e8f88fda3a061bd31a9663137de3bfdece47
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/spins/datetime.py
@@ -0,0 +1,114 @@
+import time
+from typing import Union, Tuple
+
+from PySide2.QtCore import QCalendar, QDate
+from PySide2.QtWidgets import QLabel, QHBoxLayout, QDateEdit
+from widgets.widgets.extended.base.baseextendedwidget import BaseExtendedWidget
+
+
+class PMGDateCtrl(BaseExtendedWidget):
+ def __init__(self, layout_dir: str, title, initial_date):
+ super().__init__(layout_dir)
+ self.prefix = lab_title = QLabel(text=title)
+ path_layout = QHBoxLayout()
+ path_layout.addWidget(lab_title)
+
+ self.ctrl = QDateEdit()
+ path_layout.addWidget(self.ctrl)
+
+ calendar_widget = QCalendar()
+ self.ctrl.setCalendar(calendar_widget)
+
+ self.central_layout.addLayout(path_layout)
+ self.set_value(initial_date)
+
+ def set_value(self, value: Union[Tuple[int, int, int], float, int]):
+ if isinstance(value, tuple):
+ assert len(value) == 3
+ date = QDate(*value)
+ elif isinstance(value, (float, int)):
+ loc_time = time.localtime(value)
+ print(loc_time)
+ date = QDate(loc_time.tm_year, loc_time.tm_mon, loc_time.tm_mday)
+ else:
+ raise ValueError("value is not allowed", value)
+ self.ctrl.setDate(date)
+
+ def get_value(self) -> float:
+ """
+ 计算值
+ :return:
+ """
+ return time.mktime(self.ctrl.date().toPyDate().timetuple())
+
+
+class PMGDateTimeCtrl(BaseExtendedWidget):
+ def __init__(self, layout_dir: str, title, initial_date):
+ super().__init__(layout_dir)
+ self.prefix = lab_title = QLabel(text=title)
+ path_layout = QHBoxLayout()
+ path_layout.addWidget(lab_title)
+
+ self.ctrl = QDateEdit()
+ path_layout.addWidget(self.ctrl)
+
+ calendar_widget = QCalendar()
+ self.ctrl.setCalendar(calendar_widget)
+
+ self.central_layout.addLayout(path_layout)
+ self.set_value(initial_date)
+
+ def set_value(self, value: Union[Tuple[int, int, int], float, int]):
+ if isinstance(value, tuple):
+ assert len(value) == 3
+ date = QDate(*value)
+ elif isinstance(value, (float, int)):
+ loc_time = time.localtime(value)
+ print(loc_time)
+ date = QDate(loc_time.tm_year, loc_time.tm_mon, loc_time.tm_mday)
+ else:
+ raise ValueError("value is not allowed", value)
+ self.ctrl.setDate(date)
+
+ def get_value(self) -> float:
+ """
+ 计算值
+ :return:
+ """
+ return time.mktime(self.ctrl.date().toPyDate().timetuple())
+
+
+class PMGTimeCtrl(BaseExtendedWidget):
+ def __init__(self, layout_dir: str, title, initial_date):
+ super().__init__(layout_dir)
+ self.prefix = lab_title = QLabel(text=title)
+ path_layout = QHBoxLayout()
+ path_layout.addWidget(lab_title)
+
+ self.ctrl = QDateEdit()
+ path_layout.addWidget(self.ctrl)
+
+ calendar_widget = QCalendar()
+ self.ctrl.setCalendar(calendar_widget)
+
+ self.central_layout.addLayout(path_layout)
+ self.set_value(initial_date)
+
+ def set_value(self, value: Union[Tuple[int, int, int], float, int]):
+ if isinstance(value, tuple):
+ assert len(value) == 3
+ date = QDate(*value)
+ elif isinstance(value, (float, int)):
+ loc_time = time.localtime(value)
+ print(loc_time)
+ date = QDate(loc_time.tm_year, loc_time.tm_mon, loc_time.tm_mday)
+ else:
+ raise ValueError("value is not allowed", value)
+ self.ctrl.setDate(date)
+
+ def get_value(self) -> float:
+ """
+ 计算值
+ :return:
+ """
+ return time.mktime(self.ctrl.date().toPyDate().timetuple())
diff --git a/pyminer/widgets/widgets/extended/spins/numberspin.py b/pyminer/widgets/widgets/extended/spins/numberspin.py
new file mode 100644
index 0000000000000000000000000000000000000000..61ae4829addcde565f71ad7a923221128676ed39
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/spins/numberspin.py
@@ -0,0 +1,53 @@
+from typing import Union, Tuple
+
+from PySide2.QtWidgets import QLabel, QHBoxLayout, QSpinBox, QDoubleSpinBox
+from widgets.widgets.extended.base.baseextendedwidget import BaseExtendedWidget
+
+
+class PMGNumberSpinCtrl(BaseExtendedWidget):
+ """
+ 利用spinbox的控制面板,当最大值、最小值、初始值和步长均为整数的时候,类型为整数;、反之只要有任意一个是float,
+ 类型就是浮点数了。
+ """
+
+ def __init__(self, layout_dir: str, title: str, initial_value: Union[int, float], unit: str = '',
+ val_range: Tuple[Union[float, int], Union[float, int]] = (None, None),
+ step: int = 1):
+ super().__init__(layout_dir)
+ self.on_check_callback = None
+
+ self.prefix = QLabel(text=title)
+ entryLayout = QHBoxLayout()
+ entryLayout.setContentsMargins(0, 0, 0, 0)
+
+ self.min, self.max = val_range
+ self.step = step
+ if isinstance(self.min, int) and isinstance(self.max, int) and isinstance(self.step, int) \
+ and isinstance(initial_value, int):
+ self.ctrl = QSpinBox()
+ else:
+ self.ctrl = QDoubleSpinBox()
+ self.ctrl.valueChanged.connect(self.on_value_changed)
+ self.postfix = QLabel(text=unit)
+
+ # self.central_layout.addWidget(self.prefix)
+ self.central_layout.addLayout(entryLayout)
+ entryLayout.addWidget(self.prefix)
+ entryLayout.addWidget(self.ctrl)
+ entryLayout.addWidget(self.postfix)
+ if self.min is not None:
+ self.ctrl.setMinimum(self.min)
+ if self.max is not None:
+ self.ctrl.setMaximum(self.max)
+ self.ctrl.setSingleStep(step)
+ self.accury = initial_value
+ self.set_value(initial_value)
+
+ def set_value(self, value: Union[float, int]) -> None:
+ self.ctrl.setValue(value)
+
+ def get_value(self) -> Union[int, float]:
+ return self.ctrl.value()
+
+ def on_value_changed(self):
+ self.signal_param_changed.emit(self.name)
diff --git a/pyminer/widgets/widgets/extended/tables/__init__.py b/pyminer/widgets/widgets/extended/tables/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..c962dad5cacb61bc048ab075266364e042e6df21
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/tables/__init__.py
@@ -0,0 +1,2 @@
+from .tableshow import *
+from .rulesctrl import PMGRuleCtrl
diff --git a/pyminer/widgets/widgets/extended/tables/rulesctrl.py b/pyminer/widgets/widgets/extended/tables/rulesctrl.py
new file mode 100644
index 0000000000000000000000000000000000000000..e8f89242021387199826fa3d28f1335962fcf00a
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/tables/rulesctrl.py
@@ -0,0 +1,124 @@
+"""
+规则编辑面板
+"""
+import sys
+from typing import List, Union, Tuple, Dict, TYPE_CHECKING, Callable, Optional
+
+from PySide2.QtWidgets import QWidget, QVBoxLayout, QTableWidget, QHBoxLayout, QPushButton, QTableWidgetItem, QHeaderView
+if TYPE_CHECKING:
+ import pandas as pd
+ from widgets import PANEL_VIEW_CLASS
+if __name__ == '__main__':
+ from widgets.widgets.extended.base import BaseExtendedWidget
+else:
+ from ..base import BaseExtendedWidget
+
+
+
+def parse_simplified_expression(identifier, title, data) -> Optional[List[Union[int, str, float, bool]]]:
+ """
+ 解析简化版的json数据!
+ :param identifier:
+ :param data:
+ :param params:
+ :return:
+ """
+
+ if isinstance(data, bool):
+ return ['check_ctrl', identifier, title, data]
+
+ elif isinstance(data, (int, float)):
+
+ return ['numberspin_ctrl', identifier, title, data]
+
+ elif isinstance(data, str):
+ return ['line_ctrl', identifier, title, data]
+
+ else:
+ raise ValueError('cannot parse' + repr(data))
+
+
+class PMGRuleCtrl(BaseExtendedWidget):
+ """
+ rules:
+ {'name':'regex',
+ 'text':'匹配正则表达式',
+ 'init':False
+ }
+ """
+
+ def __init__(self, layout_dir='v', title='', rules: List[Dict[str, Union[bool, int, float, str]]] = None):
+ super().__init__(layout_dir)
+ self.table_h_headers = []
+ self.table_keys = []
+ self.initial_values = []
+ for rule in rules:
+ self.table_h_headers.append(rule['text'])
+ self.table_keys.append(rule['name'])
+ self.initial_values.append(rule['init'])
+ self.regulations_table = QTableWidget(0, len(self.table_h_headers))
+ self.regulations_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+ self.regulations_table.setHorizontalHeaderLabels(self.table_h_headers)
+
+ self.layout().addWidget(self.regulations_table)
+ self.set_layout = QHBoxLayout()
+ self.layout().addLayout(self.set_layout)
+ self.button_add = QPushButton('Add')
+ self.button_remove = QPushButton('Remove')
+ self.set_layout.addWidget(self.button_add)
+ self.set_layout.addWidget(self.button_remove)
+ self.button_add.clicked.connect(self.add_regulation)
+ self.button_remove.clicked.connect(self.remove_regulation)
+
+ def load_regulations(self, regulations: List[Dict[str, Union[int, str, float, bool]]]):
+ row_count = len(regulations)
+ self.regulations_table.setRowCount(row_count)
+ for i, regulation in enumerate(regulations):
+ l = [regulation[k] for k in self.table_keys]
+ for j, obj in enumerate(l):
+ item = QTableWidgetItem()
+ item.setData(0, obj)
+ self.regulations_table.setItem(i, j, item)
+
+ def add_regulation(self):
+ rc = self.regulations_table.rowCount()
+ self.regulations_table.setRowCount(rc + 1)
+ for i, obj in enumerate(self.initial_values):
+ item = QTableWidgetItem()
+ item.setData(0, obj)
+ self.regulations_table.setItem(rc, i, item)
+
+ def remove_regulation(self):
+ self.regulations_table.removeRow(self.regulations_table.currentRow())
+
+ def get_value(self) -> List[Dict]:
+ l = []
+ for i in range(self.regulations_table.rowCount()):
+ dic = {}
+ for j in range(self.regulations_table.columnCount()):
+ item = self.regulations_table.item(i, j)
+ dic[self.table_keys[j]] = item.data(0)
+ l.append(dic)
+ return l
+
+ def set_value(self, value):
+ self.load_regulations(value)
+
+
+if __name__ == '__main__':
+ from PySide2.QtWidgets import QApplication
+
+ app = QApplication([])
+ rc = PMGRuleCtrl(rules=[
+ {'name': 'property1', 'text': '属性1', 'init': '字符串属性'},
+ {'name': 'property2', 'text': '属性2', 'init': True},
+ {'name': 'property3', 'text': '属性3', 'init': False},
+ {'name': 'property4', 'text': '属性4', 'init': 0},
+ ])
+ rc.show()
+ rc.set_value([
+ {'property1': 'aaa', 'property2': False, 'property3': True, 'property4': 1},
+ {'property1': 'whatif', 'property2': False, 'property3': False, 'property4': 12}
+ ])
+ print(rc.get_value())
+ app.exec_()
diff --git a/pyminer/widgets/widgets/extended/tables/tableshow.py b/pyminer/widgets/widgets/extended/tables/tableshow.py
new file mode 100644
index 0000000000000000000000000000000000000000..6377098e4ea8a56cce596f722bc2149fc5a5721d
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/tables/tableshow.py
@@ -0,0 +1,119 @@
+from typing import List, Union
+
+from PySide2.QtCore import Qt
+from PySide2.QtGui import QBrush, QColor
+from PySide2.QtWidgets import QHBoxLayout, QTableWidget, QTableWidgetItem, QHeaderView
+from ..base import BaseExtendedWidget
+from widgets.utilities import color_str2tup
+
+
+class PMGTableShow(BaseExtendedWidget):
+ default_bg = QTableWidgetItem().background()
+ default_fg = QTableWidgetItem().foreground()
+
+ def __init__(self, layout_dir: str, title: List[str],
+ initial_value: List[List[Union[int, float, str]]],
+ size_restricted=False, header_adaption_h=False, header_adaption_v=False,
+ background_color: List[List[Union[str]]] = None,
+ foreground_color: List[List[Union[str]]] = None):
+ super().__init__(layout_dir=layout_dir)
+
+ self.maximum_rows = 100
+ self.size_restricted = size_restricted
+ self.header_adaption_h = header_adaption_h
+ self.header_adaption_v = header_adaption_v
+ self.background_color = background_color if background_color is not None else ''
+ self.foreground_color = foreground_color if foreground_color is not None else ''
+ self.char_width = 15
+ self.on_check_callback = None
+ self.title_list = title
+ entryLayout = QHBoxLayout()
+ entryLayout.setContentsMargins(0, 0, 0, 0)
+
+ self.ctrl = QTableWidget()
+ self.ctrl.verticalHeader().setVisible(False)
+ self.set_params(size_restricted, header_adaption_h, header_adaption_v)
+ self.ctrl.setColumnCount(len(title))
+
+ self.ctrl.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
+ for i, text in enumerate(title):
+ self.ctrl.setColumnWidth(i, len(text) * self.char_width + 10)
+ self.ctrl.setHorizontalHeaderItem(i, QTableWidgetItem(text))
+
+ self.central_layout.addLayout(entryLayout)
+ entryLayout.addWidget(self.ctrl)
+
+ if initial_value is not None:
+ for sublist in initial_value:
+ assert len(sublist) == len(title), \
+ 'title is not as long as sublist,%s,%s' % (repr(title), sublist)
+ self.ctrl.setRowCount(len(initial_value))
+ self.set_value(initial_value)
+
+ def set_params(self, size_restricted=False, header_adaption_h=False, header_adaption_v=False):
+ self.size_restricted = size_restricted
+ self.header_adaption_h = header_adaption_h
+ self.header_adaption_v = header_adaption_v
+ if header_adaption_h:
+ self.ctrl.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+ if header_adaption_v:
+ self.ctrl.verticalHeader().setSectionResizeMode(QHeaderView.Stretch)
+
+ def check_data(self, value: List[List[Union[int, float, str]]]):
+ for sublist in value:
+ assert len(sublist) == len(self.title_list),\
+ '%s,%s' % (repr(sublist), repr(self.title_list))
+
+ def set_value(self, value: List[List[Union[int, float, str]]]):
+ self.check_data(value)
+ self.ctrl.setRowCount(len(value))
+ cols = len(value[0])
+ if isinstance(self.foreground_color, str):
+ fg = [[self.foreground_color for i in range(cols)] for j in range(len(value))]
+
+ else:
+ fg = self.foreground_color
+ if isinstance(self.background_color, str):
+ bg = [[self.background_color for i in range(cols)] for j in range(len(value))]
+ else:
+ bg = self.background_color
+ for row, row_list in enumerate(value):
+ for col, content in enumerate(row_list):
+ if len(str(content)) * self.char_width > self.ctrl.columnWidth(col):
+ self.ctrl.setColumnWidth(col, len(str(content)) * self.char_width + 10)
+ table_item = QTableWidgetItem(str(content))
+ table_item.setTextAlignment(Qt.AlignCenter)
+ # 字体颜色(红色)
+ if fg[row][col] == '':
+ table_item.setForeground(self.default_fg)
+ else:
+ table_item.setForeground(QBrush(QColor(*color_str2tup(fg[row][col]))))
+
+ # 背景颜色(红色)
+ if bg[row][col] == '':
+ table_item.setBackground(self.default_bg)
+ else:
+ table_item.setBackground(QBrush(QColor(*color_str2tup(bg[row][col]))))
+ self.ctrl.setItem(row, col, table_item)
+
+ if self.size_restricted:
+ if self.header_adaption_h:
+ self.ctrl.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+ scrollbar_area_width = 0
+ else:
+ self.ctrl.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
+ scrollbar_area_width = 10
+ self.ctrl.setMaximumHeight((self.ctrl.rowCount() + 1) * 30 + scrollbar_area_width)
+ self.setMaximumHeight((self.ctrl.rowCount() + 1) * 30 + scrollbar_area_width)
+
+ def alert(self, alert_level: int):
+ self.ctrl.alert(alert_level)
+
+ def add_row(self, row: List):
+ assert len(row) == self.ctrl.columnCount()
+ rc = self.ctrl.rowCount()
+ self.ctrl.setRowCount(rc + 1)
+ for i, val in enumerate(row):
+ self.ctrl.setItem(rc, i, QTableWidgetItem(str(val)))
+ if self.ctrl.rowCount() > self.maximum_rows:
+ self.ctrl.removeRow(0)
diff --git a/pyminer/widgets/widgets/extended/texts/__init__.py b/pyminer/widgets/widgets/extended/texts/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..6fa30ac8fb94b8222c1b496e36fead9f43a20113
--- /dev/null
+++ b/pyminer/widgets/widgets/extended/texts/__init__.py
@@ -0,0 +1,2 @@
+from .htmlshow import *
+from .markdownshow import *
\ No newline at end of file
diff --git a/pyminer/widgets/widgets/extended/texts/htmlshow.py b/pyminer/widgets/widgets/extended/texts/htmlshow.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/widgets/widgets/extended/texts/markdownshow.py b/pyminer/widgets/widgets/extended/texts/markdownshow.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pyminer/widgets/widgets/extended/trees/__init__.py b/pyminer/widgets/widgets/extended/trees/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391