Compare commits
531 Commits
1.1.9
...
release-v1
| Author | SHA1 | Date | |
|---|---|---|---|
| e4bb8cfa17 | |||
|
|
98ee99168b | ||
|
|
4d065afe98 | ||
|
|
2c3416fde5 | ||
|
|
51749cf81b | ||
|
|
9037223584 | ||
|
|
3aea4f319a | ||
|
|
cae04fe2a0 | ||
|
|
6a5298b0fa | ||
|
|
6b5449a6d5 | ||
|
|
31a736c6b8 | ||
|
|
950b15880e | ||
|
|
694cd2a448 | ||
|
|
5659fc8591 | ||
|
|
fb486a87ba | ||
|
|
5fef22d0db | ||
|
|
7e745797f9 | ||
| 2486b9bbb5 | |||
| 92b47af415 | |||
| 36ac4c87f1 | |||
| 96fa1434fa | |||
| 11c833bb54 | |||
| 32f8a0c189 | |||
| db004a23b0 | |||
| 9c3473a166 | |||
| 70dc245d1e | |||
| 8cd73016c5 | |||
| cf453a012c | |||
| b624d11c7d | |||
| eb7a599e03 | |||
| dbc2c9270d | |||
| e47ffd017c | |||
| 2b0ffd1a22 | |||
| b9fdb14bcd | |||
| 7d4123ba50 | |||
| 464532b509 | |||
| c13f846b99 | |||
| b895d0c234 | |||
| 60dad5389b | |||
| d6a9319cd7 | |||
| 956e1cc64f | |||
| df3737d23e | |||
| 84753f8fdd | |||
| 49682f8cd5 | |||
| d4220b2acc | |||
| 0f8cc95e40 | |||
| cac5a365f5 | |||
| a3f77bc6fe | |||
| d4f02c84c7 | |||
| b5fe6c8451 | |||
| 2b0dce4640 | |||
| 0402d31630 | |||
| c9c6152df2 | |||
| 8e2d7c53de | |||
|
|
4fdefea1bd | ||
|
|
0d191ef104 | ||
|
|
559387eecc | ||
|
|
0a94a9f7d1 | ||
|
|
c036127f36 | ||
|
|
eb3559115e | ||
|
|
e469b8ba89 | ||
|
|
6920774e64 | ||
|
|
b45c22c275 | ||
|
|
69f0381167 | ||
|
|
c8ea5622e0 | ||
|
|
e85340ba06 | ||
|
|
6fee8fe4ee | ||
|
|
48589365b7 | ||
|
|
249333b961 | ||
|
|
e75fa0e3da | ||
|
|
e58ab2e3dc | ||
|
|
0e3b7dcaab | ||
|
|
ac015b4679 | ||
|
|
9b6ec0734e | ||
|
|
6e2889453c | ||
| 9e93d537a6 | |||
| 1870bf6b90 | |||
| 8c135f968a | |||
| eca42274c0 | |||
|
|
11e1f8c607 | ||
|
|
ac150126d8 | ||
| 034f895b25 | |||
|
|
e8a02a176d | ||
|
|
dbdec091e1 | ||
|
|
86729c12aa | ||
|
|
9d765f2f5c | ||
|
|
d9aaa17551 | ||
|
|
fc0eec2932 | ||
| 654ddf8c83 | |||
| 8cf24ef669 | |||
| 5a5a9ba319 | |||
| 03324543a3 | |||
| 6053b23fce | |||
| 7d7e50c25d | |||
| f773ccb67c | |||
| edb9bab320 | |||
|
|
761c2cca8b | ||
| 70d97cd6e9 | |||
| 8f639ce292 | |||
| 6dff6baa8e | |||
|
|
7736971546 | ||
|
|
fe32898fef | ||
|
|
3034568047 | ||
| f01f466cef | |||
| 80712f59d7 | |||
| 52672dcda8 | |||
| 52c8f13355 | |||
| b39e633efb | |||
|
|
962a6bd1e9 | ||
|
|
b99db6f5d9 | ||
| c87c0db667 | |||
|
|
f25d40463c | ||
|
|
901f7f6910 | ||
|
|
ad73d3a71c | ||
| 305b10768b | |||
| 0df7e85d3c | |||
| 292463e445 | |||
|
|
79dc83b2c1 | ||
|
|
778bb5bb62 | ||
|
|
981fa41f25 | ||
| 325819f92e | |||
| 87c90072f8 | |||
| 70a9538e2c | |||
| 10cd8e374e | |||
| 1c9839c925 | |||
| 2b3eb0d474 | |||
|
|
c69fc58fb4 | ||
|
|
8608ad6e75 | ||
| 9e239a9756 | |||
| 09a7ec5adf | |||
|
|
f02332332e | ||
|
|
f9812e2d63 | ||
|
|
32b20809f9 | ||
|
|
429fb3cc07 | ||
|
|
f6063568bb | ||
|
|
b542a9322b | ||
|
|
8a94fb67ec | ||
| 4f20a7c3d0 | |||
| 1ff182c58c | |||
| c52f2b44f5 | |||
|
|
50941aedd8 | ||
|
|
8a3404b5d7 | ||
|
|
ab947ce163 | ||
|
|
3d54349bdc | ||
|
|
566e08eb11 | ||
|
|
49ea34ac7a | ||
|
|
82bd720d0e | ||
|
|
c03e1d8474 | ||
|
|
292d136c10 | ||
|
|
b4936bbbda | ||
|
|
8d914fa2d0 | ||
|
|
239d69ddc3 | ||
| 89a0ce87b9 | |||
| 66f30eccf9 | |||
| a2cb2406a1 | |||
|
|
a9f6f6936d | ||
|
|
7a7af09614 | ||
|
|
a0bf563e08 | ||
|
|
9711e7b2f1 | ||
|
|
3d69d8ac0e | ||
|
|
35551de78b | ||
| 6d03104990 | |||
| c63066e26e | |||
| c547a29b46 | |||
| c7c27d785a | |||
| 7a330ec3f3 | |||
| 02165abde2 | |||
| e4d5d8a129 | |||
| ddafba2f01 | |||
| a16b957730 | |||
| f99ed4e706 | |||
| 34a42310ab | |||
| 1b2c08473b | |||
|
|
003fd36e9a | ||
|
|
b02ab2ac99 | ||
| c385b9429f | |||
| 23407cfa63 | |||
| 430e9fb690 | |||
| 47da48ba20 | |||
| 03f663deba | |||
|
|
c537fa5046 | ||
|
|
4a6b43d594 | ||
| c5d8e80e19 | |||
|
|
14353f8a02 | ||
|
|
7ce8879e69 | ||
|
|
f61d308a90 | ||
|
|
0f7103c643 | ||
|
|
bf5284e6de | ||
|
|
c084fe34cb | ||
|
|
e505f2a6ce | ||
| 685f9256e2 | |||
|
|
0fffcb6828 | ||
|
|
ea82f9b177 | ||
|
|
3d88d7d2b4 | ||
|
|
7af369c1c7 | ||
|
|
97fbdd0d14 | ||
| 5d75c79477 | |||
| 15be8c44f2 | |||
| b66a5d9647 | |||
| b4e38e866c | |||
|
|
a353b4f283 | ||
|
|
5963084649 | ||
| 834770159c | |||
| 518ec4d12c | |||
| 5e35ba94c7 | |||
|
|
6b16395bbd | ||
| a5c31a81ec | |||
|
|
367ef836e4 | ||
|
|
5aa7229097 | ||
|
|
d4120994b5 | ||
|
|
dc71382e71 | ||
| ed835f9158 | |||
|
|
0f2eee7d5c | ||
|
|
13f2f6703d | ||
|
|
d2565799ef | ||
| b5b5cb0606 | |||
| 7bf7065ca7 | |||
| efe7747cb3 | |||
| dfd8a18e00 | |||
| 95d87102b1 | |||
| 1aa84a16c0 | |||
| 2f18b49410 | |||
| adc2ba7d54 | |||
| 24ecfafa80 | |||
|
|
dbe5d83ff6 | ||
| bf5b652ff3 | |||
|
|
8339d71bc0 | ||
|
|
ba67161862 | ||
| 4da14c06cc | |||
|
|
3ab4c4f77e | ||
| 529236e212 | |||
| 745e14a96e | |||
| 86819258fa | |||
|
|
2d538c7867 | ||
|
|
a6a56cd8b2 | ||
| 7e1884ce6a | |||
| 237f836d4b | |||
| fb1c7d030e | |||
| 6398b2f7fe | |||
|
|
399ef925a5 | ||
|
|
7c1a313023 | ||
|
|
26977a0876 | ||
| ad35950edf | |||
| 2965a9283d | |||
| e561439b4d | |||
| 425c4ce6bd | |||
|
|
98010e4411 | ||
|
|
9f4c734b40 | ||
| 5202fc200e | |||
| 88eee51941 | |||
| afbbdc650c | |||
| b3c39137d5 | |||
| ee393300f9 | |||
| 9a865aa81e | |||
| f445c321ba | |||
| 28d3da172a | |||
| 24503f8a9c | |||
| d3326a1ed7 | |||
| d18af26ec3 | |||
|
|
9303b023f2 | ||
| 850e5b509f | |||
|
|
2d9f964163 | ||
|
|
8605aaf726 | ||
|
|
bd3bff528c | ||
|
|
32689ddbb3 | ||
|
|
46d859ef30 | ||
| 87d00a29fb | |||
| b261bc22d9 | |||
| a11d17e5fe | |||
|
|
28db9f2950 | ||
|
|
0af9dd3dbd | ||
| 194d71cf91 | |||
|
|
967ad9343c | ||
|
|
df7140ff34 | ||
| a06a89a0e4 | |||
| 6767679715 | |||
| 8138cf021e | |||
| dad4597c22 | |||
| 95344ca299 | |||
| 0ea829f2c7 | |||
| 2154aef937 | |||
|
|
a637b30d6f | ||
|
|
38797e36fd | ||
|
|
a606dbc188 | ||
|
|
efd6e9880e | ||
| 5be9a5543c | |||
|
|
26bb88422d | ||
| c7e55abab2 | |||
|
|
f5c2510ab4 | ||
|
|
c60bfdd598 | ||
|
|
3449808666 | ||
|
|
b3c107eeb6 | ||
|
|
fd60cbd9d8 | ||
| 7afdeeaf5d | |||
|
|
12b3959d5a | ||
| a619406005 | |||
| 1e752c3ba9 | |||
| b3923c3cfe | |||
| be1baff33a | |||
| 9cdf93a913 | |||
|
|
da92b918e4 | ||
| d1a19a75fa | |||
| b8c98328e3 | |||
| 57e6152cbe | |||
| 5b8dab62fa | |||
|
|
40e17cbe8c | ||
|
|
8da72722f0 | ||
| 9f80e17558 | |||
|
|
dd096bf448 | ||
| a378f6d6f1 | |||
| 0c27ea4323 | |||
| 5cd4cb85c1 | |||
| 731ced20aa | |||
| 6059ff20ba | |||
| 3b8e31a7ae | |||
| 850f92ccb4 | |||
| 635fec362a | |||
| 3b15692870 | |||
| 3da865f292 | |||
| 97c4863a32 | |||
|
|
eb6084ca26 | ||
| 6035919fb5 | |||
|
|
8cbea747bf | ||
| d772d98f64 | |||
|
|
f1b8187edd | ||
|
|
f04e1a4899 | ||
| 4f59dda32e | |||
| bc26c69bb8 | |||
| 3ef7326bbf | |||
| 3e04aa09da | |||
|
|
6e012e3855 | ||
|
|
c18941c0ef | ||
| 71dfbb76d4 | |||
| ffaa91ca9a | |||
| f7543c2a64 | |||
|
|
facb9c886e | ||
|
|
730d2492d9 | ||
|
|
03570f5cd1 | ||
|
|
29c3a59b34 | ||
| 6a856a7a6d | |||
|
|
2ffefb6c7b | ||
| e418d8b239 | |||
| 7ff5233b9c | |||
| c5224b9ca7 | |||
| e7fd2213e7 | |||
| 105a7fff15 | |||
|
|
cee299b7c2 | ||
|
|
83c81ee667 | ||
|
|
8d0bf22ad7 | ||
| 4abe8b7a35 | |||
| 3e6d984e58 | |||
|
|
f9a9cb99f8 | ||
|
|
d995b9c8af | ||
|
|
4e18118a24 | ||
|
|
d883a581f6 | ||
|
|
03c2363b1e | ||
|
|
3aec1f93cd | ||
| 60e4eb3f22 | |||
| dec9193469 | |||
| 3bd945a2e6 | |||
| 3671354b5d | |||
| 45b26c6de1 | |||
|
|
757a0e233b | ||
|
|
10181fb0d1 | ||
|
|
6fa60fe02e | ||
|
|
8853866742 | ||
|
|
3f7dbd7cd5 | ||
|
|
c78a251084 | ||
|
|
ba5172d78e | ||
|
|
895fae18f4 | ||
|
|
3a8b939cbe | ||
|
|
4db5107d81 | ||
|
|
e1a058d891 | ||
| c3b78c9ebd | |||
| a316425ab0 | |||
| 3add6372ab | |||
| 5b5fdaadd7 | |||
| 442c7ebee6 | |||
| dc7ead7d8d | |||
| 69922ea846 | |||
| 43da503da9 | |||
|
|
6164698dd7 | ||
|
|
c1fe25e2bb | ||
| 8eb8b7bf27 | |||
|
|
317e486f85 | ||
| eb785eec45 | |||
|
|
5cda1ddf87 | ||
|
|
8f4448bac2 | ||
|
|
422bb59161 | ||
|
|
a1d6446f06 | ||
|
|
ca73febbf7 | ||
|
|
27104812da | ||
|
|
1162c1147a | ||
|
|
c9a361b8ce | ||
|
|
f1a423fd5f | ||
| 3747df5272 | |||
| 24bb39ee63 | |||
| 7ea8630f2c | |||
|
|
c81517b96b | ||
|
|
53d96ecffc | ||
|
|
787da7f1be | ||
|
|
718c24fd6c | ||
|
|
6ff1af20dc | ||
|
|
feb364eae0 | ||
|
|
50aea7774e | ||
|
|
8ed04be941 | ||
|
|
3bc147e4a2 | ||
|
|
13767ad1cc | ||
|
|
92fd8f3dbd | ||
|
|
ea594c4048 | ||
|
|
a9dd2e21c5 | ||
|
|
2eb71bece8 | ||
|
|
59c8b8f5ed | ||
|
|
6679d6e04d | ||
|
|
2f78ab6632 | ||
| 667222ce91 | |||
|
|
ab2030a0e2 | ||
|
|
20bf5e75f4 | ||
| 815e2b01a8 | |||
|
|
1849f01b0e | ||
|
|
d26827075b | ||
| bd840fa160 | |||
|
|
d31772b3d5 | ||
| abb3633172 | |||
|
|
8c589d6133 | ||
| c7d36438e1 | |||
| 0a4fb7150d | |||
|
|
7ab6793179 | ||
|
|
5ad48be1cd | ||
|
|
c68992fd00 | ||
|
|
785600c857 | ||
| 2f17aa5090 | |||
|
|
f5fdb925ea | ||
|
|
1bd9e17b60 | ||
|
|
631c7b82f2 | ||
|
|
eb7eb2552c | ||
| a95a65e6d5 | |||
| 240abc1e91 | |||
|
|
fbc1d020d9 | ||
|
|
8c533b7a6f | ||
|
|
017156b0eb | ||
|
|
3daba03cbb | ||
|
|
f108f95426 | ||
|
|
92d0339146 | ||
| f950b42450 | |||
| 4d942625cb | |||
| 597062153a | |||
|
|
8a931725bf | ||
|
|
c678fda573 | ||
| 13382d2cd4 | |||
| cda1f95b75 | |||
|
|
f7505f6063 | ||
| c4b998100e | |||
|
|
97d8776a5e | ||
| 810ba4c447 | |||
|
|
ce9280134e | ||
|
|
60f4f90f48 | ||
|
|
c3408b9d3f | ||
| 076e497171 | |||
| 014186b050 | |||
| 80872b6511 | |||
| 2efb7d33a7 | |||
| 362e63d781 | |||
|
|
834d5ac708 | ||
|
|
8e4c35db29 | ||
| 5aea9e96a9 | |||
|
|
b1ca13404a | ||
|
|
73830bb306 | ||
|
|
849161d237 | ||
|
|
2410f6e213 | ||
| 58cd858643 | |||
| 27f1593844 | |||
| 2e72676dec | |||
| ec1bb66f1e | |||
| ee78f2bc60 | |||
|
|
f0731aa259 | ||
|
|
1cd41017b2 | ||
|
|
2c6c88d62c | ||
|
|
74ffd70c3a | ||
|
|
b9bb72ac64 | ||
|
|
7b9e4f4aef | ||
|
|
60d4de6832 | ||
|
|
641081b0a5 | ||
| 3c7c35d50b | |||
|
|
65f8a50dd9 | ||
| 09b4f3c64b | |||
|
|
4bf14ed939 | ||
|
|
79e368dc17 | ||
|
|
b1662b717a | ||
| f1811216dd | |||
| e9ebcfc649 | |||
|
|
d89c39ea0b | ||
| d4d7cdfc68 | |||
| 200c090887 | |||
| 6391df41a2 | |||
|
|
308fe8db1c | ||
| a767c9978d | |||
|
|
08005c73c5 | ||
| 013c47d81e | |||
| 45d9f4b4fc | |||
|
|
877976f1d7 | ||
|
|
a391ed05e0 | ||
| b5afb513db | |||
|
|
9808d49377 | ||
| 7138ef02a1 | |||
| f5cca69c50 | |||
| af14b6f946 | |||
| cbcdc120b7 | |||
|
|
87ce23ce12 | ||
|
|
871da1b9c2 | ||
|
|
7cf9ba2eac | ||
| 87e48b9130 | |||
|
|
2bda1d8097 | ||
| 020ee7008e | |||
|
|
4506b0c310 | ||
| 2a3b32503f | |||
| b2718c1932 | |||
| 9617ee5046 | |||
|
|
aebcdd9646 | ||
|
|
b8b649d097 | ||
|
|
12b59555b6 | ||
|
|
b2676f861f | ||
|
|
0216a3bfcb | ||
| c014a72ace | |||
| a49c31586a | |||
|
|
2ea22fc5a2 | ||
| 500057e926 | |||
| 18799d0c2f | |||
| 73b6bcf7fc | |||
| 25767b48fb | |||
| 5c608d332f |
56
.gitee/ISSUE_TEMPLATE/bug.yml
Normal file
56
.gitee/ISSUE_TEMPLATE/bug.yml
Normal file
@ -0,0 +1,56 @@
|
||||
name: Bug 反馈
|
||||
description: 当你在代码中发现了一个 Bug,导致应用崩溃或抛出异常,或者有一个组件存在问题,或者某些地方看起来不对劲。
|
||||
title: "[Bug]: "
|
||||
labels: [ "bug" ]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢对项目的支持与关注。在提出问题之前,请确保你已查看相关开发或使用文档:
|
||||
- https://gitee.com/jcnc-org/docs/blob/master/zh-cn/doc/doc-jnotepad/doc-jnotepad.md
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: 这个问题是否已经存在?
|
||||
options:
|
||||
- label: 我已经搜索过现有的问题 (https://gitee.com/organizations/jcnc-org/issues)
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 如何复现
|
||||
description: 请详细告诉我们如何复现你遇到的问题,如涉及代码,可提供一个最小代码示例,并使用反引号```附上它
|
||||
placeholder: |
|
||||
操作系统:
|
||||
复现步骤:
|
||||
1. ...
|
||||
2. ...
|
||||
3. ...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 预期结果
|
||||
description: 请告诉我们你预期会发生什么。
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 实际结果
|
||||
description: 请告诉我们实际发生了什么。
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 截图或视频
|
||||
description: 如果可以的话,上传任何关于 bug 的截图。
|
||||
value: |
|
||||
[在这里上传图片]
|
||||
- type: dropdown
|
||||
id: version
|
||||
attributes:
|
||||
label: 版本
|
||||
description: 你当前正在使用我们软件的哪个版本/分支?
|
||||
options:
|
||||
- V1.1.14(最新开发版)
|
||||
- V1.1.13(最新发行版)
|
||||
validations:
|
||||
required: true
|
||||
5
.gitee/ISSUE_TEMPLATE/config.yml
Normal file
5
.gitee/ISSUE_TEMPLATE/config.yml
Normal file
@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: true
|
||||
#contact_links:
|
||||
# - name: Gitee 帮助中心
|
||||
# url: https://help.gitee.com/
|
||||
# about: 提供 Git 使用指南、教程、Gitee.com 平台基本功能使用、介绍和常见问题解答
|
||||
43
.gitee/ISSUE_TEMPLATE/feature.yml
Normal file
43
.gitee/ISSUE_TEMPLATE/feature.yml
Normal file
@ -0,0 +1,43 @@
|
||||
name: 功能建议
|
||||
description: 对本项目提出一个功能建议
|
||||
title: "[功能建议]: "
|
||||
labels: [ "feature" ]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢提出功能建议,我们将仔细考虑!
|
||||
- type: textarea
|
||||
id: related-problem
|
||||
attributes:
|
||||
label: 你的功能建议是否和某个问题相关?
|
||||
description: 清晰并简洁地描述问题是什么,例如,当我...时,我总是感到困扰。
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: desired-solution
|
||||
attributes:
|
||||
label: 你希望看到什么解决方案?
|
||||
description: 清晰并简洁地描述你希望发生的事情。
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: 你考虑过哪些替代方案?
|
||||
description: 清晰并简洁地描述你考虑过的任何替代解决方案或功能。
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: 你有其他上下文或截图吗?
|
||||
description: 在此处添加有关功能请求的任何其他上下文或截图。
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: 意向参与贡献
|
||||
options:
|
||||
- label: 我有意向参与具体功能的开发实现并将代码贡献回到上游社区
|
||||
required: false
|
||||
12
.gitee/ISSUE_TEMPLATE/refactor.yml
Normal file
12
.gitee/ISSUE_TEMPLATE/refactor.yml
Normal file
@ -0,0 +1,12 @@
|
||||
name: 重构
|
||||
description: 对本项目提出一个功能建议
|
||||
title: "[重构]: "
|
||||
labels: [ "refactor" ]
|
||||
body:
|
||||
- type: textarea
|
||||
id: related-problem
|
||||
attributes:
|
||||
label: 重构目的是什么?
|
||||
description: 清晰并简洁地描述重构是什么,例如,减少文件选择器重复创建代码。
|
||||
validations:
|
||||
required: false
|
||||
28
.gitignore
vendored
28
.gitignore
vendored
@ -2,15 +2,21 @@ target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
test/
|
||||
### IntelliJ IDEA ###
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
.idea/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
### 此处忽略了json与xml后缀
|
||||
*.xml
|
||||
*.json
|
||||
|
||||
### 此处排除证书目录
|
||||
certificate/
|
||||
|
||||
### 此处排除项目文件
|
||||
.jnotepad/
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
@ -38,3 +44,15 @@ build/
|
||||
.DS_Store
|
||||
/JNotepad/
|
||||
/src/main/JNotepad.java
|
||||
/.idea/
|
||||
!.idea/codeStyles/
|
||||
!.idea/fileTemplates/
|
||||
!.idea/encodings.xml
|
||||
/project.txt
|
||||
logs/
|
||||
/ch_language_pack.txt
|
||||
/en_language_pack.txt
|
||||
/jnotepadConfig.json
|
||||
/qodana.yaml
|
||||
.mvn/
|
||||
/main.c
|
||||
8
.idea/.gitignore
generated
vendored
8
.idea/.gitignore
generated
vendored
@ -1,8 +0,0 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
24
.idea/artifacts/JNotepad_jar.xml
generated
24
.idea/artifacts/JNotepad_jar.xml
generated
@ -1,24 +0,0 @@
|
||||
<component name="ArtifactManager">
|
||||
<artifact type="jar" name="JNotepad:jar">
|
||||
<output-path>$PROJECT_DIR$/out/artifacts/JNotepad_jar</output-path>
|
||||
<root id="archive" name="JNotepad.jar">
|
||||
<element id="module-output" name="JNotepad" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/openjfx/javafx-graphics/17.0.1/javafx-graphics-17.0.1-win.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/openjfx/javafx-fxml/17.0.1/javafx-fxml-17.0.1-win.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/openjfx/javafx-fxml/17.0.1/javafx-fxml-17.0.1.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/openjfx/javafx-base/17.0.1/javafx-base-17.0.1-win.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/openjfx/javafx-controls/17.0.1/javafx-controls-17.0.1-win.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/openjfx/javafx-base/17.0.1/javafx-base-17.0.1.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/openjfx/javafx-controls/17.0.1/javafx-controls-17.0.1.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/openjfx/javafx-graphics/17.0.1/javafx-graphics-17.0.1.jar" path-in-jar="/" />
|
||||
<element id="library" level="project" name="Maven: org.openjfx:javafx-controls:17.0.1" />
|
||||
<element id="library" level="project" name="Maven: org.openjfx:javafx-controls:win:17.0.1" />
|
||||
<element id="library" level="project" name="Maven: org.openjfx:javafx-graphics:17.0.1" />
|
||||
<element id="library" level="project" name="Maven: org.openjfx:javafx-graphics:win:17.0.1" />
|
||||
<element id="library" level="project" name="Maven: org.openjfx:javafx-base:17.0.1" />
|
||||
<element id="library" level="project" name="Maven: org.openjfx:javafx-base:win:17.0.1" />
|
||||
<element id="library" level="project" name="Maven: org.openjfx:javafx-fxml:17.0.1" />
|
||||
<element id="library" level="project" name="Maven: org.openjfx:javafx-fxml:win:17.0.1" />
|
||||
</root>
|
||||
</artifact>
|
||||
</component>
|
||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
6
.idea/encodings.xml
generated
6
.idea/encodings.xml
generated
@ -1,11 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/11.txt" charset="GBK" />
|
||||
<file url="file://$PROJECT_DIR$/aaa.txt" charset="GBK" />
|
||||
<file url="file://$PROJECT_DIR$/init.bat" charset="US-ASCII" />
|
||||
<component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/新建文本.txt" charset="GBK" />
|
||||
</component>
|
||||
</project>
|
||||
7
.idea/fileTemplates/code/JavaDoc Class.java
generated
Normal file
7
.idea/fileTemplates/code/JavaDoc Class.java
generated
Normal file
@ -0,0 +1,7 @@
|
||||
#foreach($param in $RECORD_COMPONENTS)
|
||||
* @param $param
|
||||
#end
|
||||
#foreach($param in $TYPE_PARAMS)
|
||||
* @param <$param>
|
||||
#end
|
||||
* @author $USER
|
||||
13
.idea/misc.xml
generated
13
.idea/misc.xml
generated
@ -1,13 +0,0 @@
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="openjdk-17.0.2" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
124
.idea/uiDesigner.xml
generated
124
.idea/uiDesigner.xml
generated
@ -1,124 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Palette2">
|
||||
<group name="Swing">
|
||||
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
|
||||
</item>
|
||||
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
|
||||
</item>
|
||||
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
|
||||
</item>
|
||||
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
|
||||
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
|
||||
</item>
|
||||
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
|
||||
<initial-values>
|
||||
<property name="text" value="Button" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||
<initial-values>
|
||||
<property name="text" value="RadioButton" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||
<initial-values>
|
||||
<property name="text" value="CheckBox" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
|
||||
<initial-values>
|
||||
<property name="text" value="Label" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||
<preferred-size width="150" height="-1" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||
<preferred-size width="150" height="-1" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||
<preferred-size width="150" height="-1" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||
<preferred-size width="200" height="200" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||
<preferred-size width="200" height="200" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
|
||||
</item>
|
||||
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
|
||||
<preferred-size width="-1" height="20" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
|
||||
</item>
|
||||
</group>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
BIN
.mvn/wrapper/maven-wrapper.jar
vendored
BIN
.mvn/wrapper/maven-wrapper.jar
vendored
Binary file not shown.
2
.mvn/wrapper/maven-wrapper.properties
vendored
2
.mvn/wrapper/maven-wrapper.properties
vendored
@ -1,2 +0,0 @@
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.5/apache-maven-3.8.5-bin.zip
|
||||
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
|
||||
124
README.md
124
README.md
@ -1,11 +1,50 @@
|
||||
# JNotepad
|
||||
<p align="center">
|
||||
<img src="src/main/resources/jcnc/app/svg/icon.svg" alt="JNotepad Icon">
|
||||
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">JNotepad</h1>
|
||||
<h4 align="center" style="margin: 30px 0 30px; font-weight: bold;">JavaFX开发,插件驱动,创造无限可能</h4>
|
||||
|
||||

|
||||

|
||||

|
||||
<p align="center">
|
||||
<a href='https://gitee.com/jcnc-org/JNotepad/stargazers'><img
|
||||
src='https://gitee.com/jcnc-org/JNotepad/badge/star.svg?theme=dark' alt='star'>
|
||||
</a>
|
||||
<a href='https://gitee.com/jcnc-org/JNotepad/members'><img
|
||||
src='https://gitee.com/jcnc-org/JNotepad/badge/fork.svg?theme=dark' alt='fork'>
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://gitee.com/jcnc-org/JNotepad/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/badge/%20license-GPL--3.0%20-blue" alt="">
|
||||
</a>
|
||||
<a href="https://gitee.com/jcnc-org/JNotepad/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/badge/version-v1.1.13-blue" alt="">
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://gitee.com/jcnc-org/JNotepad/releases">
|
||||
<img src="https://img.shields.io/badge/Windows-Passing-49%2C198%2C84.svg?style=falt&logo=Windows" alt="">
|
||||
</a>
|
||||
<a href="https://gitee.com/jcnc-org/JNotepad/releases">
|
||||
<img src="https://img.shields.io/badge/Ubuntu-Passing-49%2C198%2C84.svg?style=falt&logo=Ubuntu" alt="">
|
||||
</a>
|
||||
<a href="https://gitee.com/jcnc-org/JNotepad/releases">
|
||||
<img src="https://img.shields.io/badge/MacOS-Passing-49%2C198%2C84.svg?style=falt&logo=Apple" alt="">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
JNotepad(Java Notepad)是一款简约而强大的跨平台文本编辑器,旨在提供用户友好的界面和丰富的功能。无论你是在Linux、Windows还是macOS系统上使用,JNotepad都能满足你对文本编辑和查看的需求。 JNotepad使用Java语言编写,并基于JavaFX框架开发,具有良好的可扩展性和稳定性。
|
||||
[jnotepad-official-plugins]:https://gitee.com/jcnc-org/jnotepad-official-plugins
|
||||
[jcnc-docs]:https://gitee.com/jcnc-org/docs
|
||||
|
||||
|
||||
| 序号 | 相关仓库 | 链接地址 |
|
||||
|:---: | :---------------: | :-----------------------------------:|
|
||||
|1 | JNotepad插件仓库 | [点击访问][jnotepad-official-plugins] |
|
||||
|2 | JCNC文档仓库 | [点击访问][jcnc-docs] |
|
||||
|
||||
JNotepad(Java Notepad)
|
||||
是一款简约而强大的跨平台文本编辑器,旨在提供用户友好的界面和丰富的功能以及插件化使用。无论你是在Linux、Windows还是macOS系统上使用,JNotepad都能满足你对文本编辑和查看的需求。
|
||||
JNotepad使用Java语言编写,并基于JavaFX框架开发,具有良好的可扩展性和稳定性。
|
||||
|
||||
## 功能介绍
|
||||
|
||||
- 文本编辑和查看:JNotepad提供了完善的文本编辑和查看功能,使你能够轻松创建、编辑和浏览各种类型的文本文件。
|
||||
@ -16,65 +55,86 @@ JNotepad(Java Notepad)是一款简约而强大的跨平台文本编辑器,旨
|
||||
|
||||
- 基于Java:JNotepad使用Java语言编写,并基于JavaFX框架开发,具有良好的可扩展性和稳定性。
|
||||
|
||||
|
||||
## 安装教程
|
||||
|
||||
1. Windows 平台,可以直接使用我编译好的可执行程序或自己编译
|
||||
1. Windows 平台,可以直接使用编译的可执行程序或自己编译
|
||||
|
||||
[gitee-download]: https://gitee.com/jcnc-org/JNotepad/releases
|
||||
|
||||
[java-download]: https://www.oracle.com/cn/java/technologies/downloads/
|
||||
[qq-url]: http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=zOfwWb1lcle68cbEdJCjSIp3Itx0nEC0&authKey=bOsZFT9OVYZpZQbS6IYO4onBQoeBorF5nanMEi1G%2FgPbzmUkOweXBo9qB0G34R5K&noverify=0&group_code=386279455
|
||||
|
||||
|
||||
[docs-url]: https://gitee.com/jcnc-org/docs
|
||||
|
||||
- [下载][gitee-download]
|
||||
|
||||
2. Linux/MacOS 平台,查看入门指南
|
||||
|
||||
|
||||
## 入门指南
|
||||
|
||||
要使用 JNotepad,请按照以下步骤进行:
|
||||
|
||||
1. 下载并安装 Java(如果尚未安装)。
|
||||
|
||||
- [下载][gitee-download]
|
||||
|
||||
2. 克隆或下载 JNotepad 项目。
|
||||
|
||||
<pre><code>git clone https://gitee.com/jcnc-org/JNotepad.git</code></pre>
|
||||
|
||||
3. 在您偏好的 Java IDE 中打开项目。
|
||||
|
||||
## 使用方法
|
||||
|
||||
1. 运行 `JNotepad` 类以启动应用程序。
|
||||
2. 主窗口将显示菜单栏、标签区域和状态栏。
|
||||
2. 主窗口将显示菜单栏、标签区域和状态栏等。
|
||||
3. 使用菜单栏执行各种操作:
|
||||
- `文件 > 新建`:创建一个带有空白文本区域的新标签。
|
||||
- `文件 > 打开`:打开现有文本文件进行编辑。
|
||||
- `文件 > 保存`:将当前活动标签的内容保存到关联文件中。
|
||||
- `文件 > 另存为`:将当前活动标签的内容保存为新文件。
|
||||
4. 在每个标签的文本区域中编辑内容。
|
||||
5. 状态栏将显示有关光标位置和文本统计信息的信息。
|
||||
|
||||
- `文件 > 新建`:创建一个带有空白文本区域的新标签。
|
||||
- `文件 > 打开`:打开现有文本文件进行编辑。
|
||||
- `文件 > 保存`:将当前活动标签的内容保存到关联文件中。
|
||||
- `文件 > 另存为`:将当前活动标签的内容保存为新文件。
|
||||
- `文件 > 重命名`:将当前活动标签的内容重命名。
|
||||
|
||||
|
||||
- `设置 > 自动换行`:打开当前文本自动换行。
|
||||
- `设置 > 打开配置文件`:打开JNotepad的配置文件实现配置快捷键和其他功能。
|
||||
- `设置 > 窗口置顶`:将程序主仓库置顶。
|
||||
- `设置 > 语言`:切换语言。
|
||||
|
||||
|
||||
- `插件 > 增加插件`:管理插件系统。
|
||||
|
||||
## 依赖项
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
<version>17.0.1</version>
|
||||
</dependency>
|
||||
<dependencies>
|
||||
## 软件运行截图
|
||||
POM文件中的全部依赖项:
|
||||
|
||||
| 组ID | 工件ID | 版本 | 功能描述 |
|
||||
|----------------------------|----------------------------|--------|--------------------------------------------------------------|
|
||||
| org.kordamp.ikonli | ikonli-javafx | 12.3.1 | 提供JavaFX应用程序中的图标集成。 |
|
||||
| org.kordamp.ikonli | ikonli-antdesignicons-pack | 12.3.1 | 包含Ant Design图标集的Ikonli图标包。 |
|
||||
| io.github.mkpaz | atlantafx-base | 2.0.1 | 提供Atlantafx库的基本功能。 |
|
||||
| org.openjfx | javafx-fxml | 20.0.2 | JavaFX的FXML模块,用于构建用户界面。 |
|
||||
| org.junit.jupiter | junit-jupiter-api | 5.9.2 | 用于JUnit 5测试框架的API。 |
|
||||
| com.fasterxml.jackson.core | jackson-databind | 2.15.2 | 用于JSON数据的序列化和反序列化。 |
|
||||
| org.slf4j | slf4j-api | 2.0.7 | 简单日志门面,用于处理日志记录。 |
|
||||
| ch.qos.logback | logback-core | 1.4.11 | Logback的核心组件,用于日志记录。 |
|
||||
| ch.qos.logback | logback-classic | 1.4.11 | Logback的经典模块,提供日志记录功能。 |
|
||||
| com.ibm.icu | icu4j | 73.2 | ICU(International Components for Unicode)库,用于处理Unicode字符和文本。 |
|
||||
|
||||
## 软件运行截图
|
||||
|
||||
- Windows 平台
|
||||

|
||||
|
||||
- MacOS 平台
|
||||

|
||||

|
||||
|
||||
### 参与贡献
|
||||
- Linux 平台
|
||||

|
||||
|
||||
# 参与贡献
|
||||
|
||||
1. Fork 本仓库
|
||||
1. 加入JCNC社区
|
||||
1. [加入QQ群:386279455][qq-url]
|
||||
1. 新建分支
|
||||
1. 提交代码
|
||||
1. 新建 Pull Request
|
||||
2. [阅读JCNC开发者文档][docs-url]
|
||||
3. 加入QQ群:386279455
|
||||
4. 联系微信:xuxiaolankaka 加入群聊
|
||||
@ -1,8 +0,0 @@
|
||||
jpackage `
|
||||
--name JNotepad `
|
||||
--type app-image `
|
||||
-m org.jcnc.jnotepad/org.jcnc.jnotepad.LunchApp `
|
||||
--runtime-image .\target\JNotepad\ `
|
||||
--icon src/main/resources/img/icon.ico `
|
||||
--app-version 1.1.9 `
|
||||
--vendor "JCNC"
|
||||
BIN
docs/images/codeTemplate.png
Normal file
BIN
docs/images/codeTemplate.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 117 KiB |
247
docs/代码规范.md
Normal file
247
docs/代码规范.md
Normal file
@ -0,0 +1,247 @@
|
||||
# 代码规范
|
||||
|
||||
## 1. 排版规则
|
||||
|
||||
### 1.1 编码
|
||||
|
||||
**规 则:IDE的text file encoding设置为UTF-8。**
|
||||
**规 则:IDE中文件的换行符使用Unix格式,不要使用windows格式。**
|
||||
|
||||
**补 充:**
|
||||
本项目中`.idea/encodings.xml`已定义src、resource目录为UTF-8编码,禁止修改。
|
||||
|
||||
### 1.2 页宽
|
||||
|
||||
**规 则:编辑器列宽应设置为120**
|
||||
**说 明:**
|
||||
当一行代码的长度过长时,为了阅读方便,应该换行展示。但因为每个人的字体设置,屏幕大小有所不同,因此需要统一列宽为120。
|
||||
**补 充:**
|
||||
IDEA默认列宽为120。本项目的.idea/codeStyles目录中应用的即为IDEA默认配置。
|
||||
|
||||
### 1.3 换行
|
||||
|
||||
**规 则:第二行相对第一行缩进4个空格,从第三行开始,不再缩进,而是与第二行保持同级。**
|
||||
**规 则:运算符、方法调用的点符号与下文一起换行。**
|
||||
**规 则:在多个参数超长时,逗号后换行。**
|
||||
**示 例:**
|
||||
|
||||
```java
|
||||
//符合规范
|
||||
StringBuffer sb=new StringBuffer();
|
||||
//超过100个字符的时候,换行缩进4个空格,并且方法钱的点符号一起换行
|
||||
sb.append("wan").append("fang")...
|
||||
.append("shu")...
|
||||
.append("ju")...
|
||||
.append("gu");
|
||||
//在逗号后换行
|
||||
method(arg1,arg2,arg3,...argx,
|
||||
argx1)
|
||||
```
|
||||
|
||||
### 1.4 缩进
|
||||
|
||||
**规 则:程序块需要采用缩进风格编写,缩进为4个空格。**
|
||||
**说 明:** 不同的编辑工具会导致Tab字符的宽度不统一,在编码时应注意其可能造成的问题。
|
||||
**补 充:**
|
||||
本项目的.idea/codeStyles目录中应用的即为IDEA默认配置。
|
||||
|
||||
### 1.5 空行
|
||||
|
||||
**规则:独立的程序块与变量声明之间加空行分割**
|
||||
**示 例:**
|
||||
|
||||
```java
|
||||
//不符合规范
|
||||
if(log.getLevel()<LogConfig.getRecordLevel()){
|
||||
return;
|
||||
}
|
||||
LogWriter writer;
|
||||
int index;
|
||||
```
|
||||
|
||||
```java
|
||||
//符合规范
|
||||
if(log.getLevel()<LogConfig.getRecordLevel()){
|
||||
return;
|
||||
}
|
||||
|
||||
LogWriter writer;
|
||||
int index;
|
||||
```
|
||||
|
||||
### 1.6 大括号
|
||||
|
||||
**规 则:使用大括号(即使是可选的)**
|
||||
**说 明:** 大括号与if, else, for, do, while即使只有一条语句或是空,也应该把大括号写上。
|
||||
**示 例:**
|
||||
|
||||
```java
|
||||
//不符合规范的
|
||||
if(writeToFile)
|
||||
writeFileThread.interrupt();
|
||||
```
|
||||
|
||||
```java
|
||||
//符合规范的
|
||||
if(writeToFile){
|
||||
writeFileThread.interrupt();
|
||||
}
|
||||
```
|
||||
|
||||
**规 则:对于非空块和块状结构,大括号遵循Kernighan和Ritchie风格(紧凑风格)。**
|
||||
**说 明:**
|
||||
|
||||
* 左大括号前不换行
|
||||
* 左大括号后换行
|
||||
* 右大括号前换行
|
||||
* 如果右大括号是一个语句、函数体或类的终止,则右大括号后换行; 否则不换行。例如,如果右大括号后面是else或逗号,则不换行
|
||||
|
||||
**示 例:**
|
||||
|
||||
```java
|
||||
//不符合规范
|
||||
if(isOk)
|
||||
{
|
||||
someThing();
|
||||
}
|
||||
else
|
||||
{
|
||||
otherThing();
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
//符合规范
|
||||
if(isOk){
|
||||
someThing();
|
||||
}else{
|
||||
otherThing();
|
||||
}
|
||||
```
|
||||
|
||||
**补 充:**
|
||||
Kernighan和Ritchie风格指的是,Kernighan和Ritchie的《C Programming
|
||||
Language》一书中约定的“大括号放在同一行”规则。这种编码风格比较紧凑,也是Java官方和Google等诸多主流公司遵循的编码风格。
|
||||
|
||||
**建 议:空块可以简洁的写成{},除非它是多块语句的一部分。**
|
||||
**说 明:** 一个空的块状结构里什么也不包含,大括号可以简洁地写成{},不需要换行。例外:如果它是一个多块语句的一部分(if/else 或
|
||||
try/catch/finally) ,即使大括号内没内容,右大括号也要换行。
|
||||
**示 例:**
|
||||
|
||||
```java
|
||||
void doNothing(){}
|
||||
```
|
||||
|
||||
### 1.7 小括号
|
||||
|
||||
**规 则:用小括号来限定计算优先级**
|
||||
**说 明:** 没有理由假设代码的阅读者能够清晰的记住整个Java运算符优先级表。
|
||||
###2.8 空格
|
||||
**规 则:函数参数在“,”后需要加空格。**
|
||||
**规 则:各种双目操作符,比如“=”,“<”等,前后都要加空格。**
|
||||
**规 则:if, while等关键字后面需要有空格。**
|
||||
**补 充:** 基本上Eclipse的自动格式化功能,就能保证这些空格的正确使用。
|
||||
|
||||
### 1.8 TODO
|
||||
|
||||
**规 则:TODO用于任务标记,要避免出现无用的TODO标记**
|
||||
**说 明:** 自动生成代码中如果有TODO标记,请根据需要编写注释或删除TODO标记,避免产生无用的TODO,影响他人跟踪任务。
|
||||
|
||||
### 1.9 import
|
||||
|
||||
**规 则:import不要使用通配符**
|
||||
**说 明:** 不要出现类似这样的import语句:import java.util.*。使用通配符会造成歧义,及不可预期的冲突或bug。程序应保持清晰、易懂、无歧义。
|
||||
|
||||
**规 则:import不要引用不需要的包**
|
||||
|
||||
### 1.10 变量
|
||||
|
||||
**规 则:每次只声明一个变量**
|
||||
**说 明:** 不要使用组合声明(int a,b;),减少歧义性,增强可读性
|
||||
|
||||
**规 则:需要时才声明,并尽快进行初始化**
|
||||
|
||||
**规 则:使用非C风格的数组声明**
|
||||
**说 明:** 使用String[] args,而非String args[]
|
||||
|
||||
**建 议:谨慎的使用公共变量**
|
||||
**说 明:** 公共变量是增大模块间耦合的原因之一,应减少没必要的公共变量以降低模块间的耦合度。
|
||||
|
||||
## 2 命名规范
|
||||
|
||||
**规 则:命名应尽可能做到见名知意,力求语义表达清晰完整。**
|
||||
**规 则:代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。**
|
||||
**规 则:代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。**
|
||||
|
||||
### 2.1 package
|
||||
|
||||
**规 则:包名均采用小写**
|
||||
**建 议:包名一般为一个单词**
|
||||
|
||||
**规 则:内部包名规则一般为com.部门名称/站点域名.产品名/项目名.模块名,通用包放在com.wanfangdata下**
|
||||
**说 明:** 由于历史遗留原因,部分内部包名格式为:部门名称.产品名/项目名.模块名。原则上,新建的包不要用此命名规则,但暂时也不修改此规则的包名
|
||||
|
||||
### 2.2 Class
|
||||
|
||||
**规 则:类的命名应该都是名词**
|
||||
**规 则:类名第一个字母要为大写,其他每个单词的第一个字母为大写(UpperCamelCase)**
|
||||
**规 则:类名要用完整的单词,除非是被公认的单词缩写**
|
||||
**规 则:抽象类命名应以Abstract为开头**
|
||||
**规 则:异常类命名使用Exception结尾**
|
||||
**规 则:测试类命名以它要测试的类名称开始,以Test结尾**
|
||||
|
||||
**建 议:如果使用了设计模式,建议在类(接口)命名中体现出具体的设计模式**
|
||||
**建 议:枚举类名以Enum后缀结束,美剧成员名称需要全部大写,单词间用下划线分隔。**
|
||||
**示 例:**
|
||||
|
||||
```java
|
||||
public class BalanceLimitAccountHandler;
|
||||
|
||||
public interface SqlSessionFactory;
|
||||
|
||||
public class LoginProxy;
|
||||
```
|
||||
|
||||
### 2.3 Interface
|
||||
|
||||
**规 则:接口名的命名应该是名词或形容词**
|
||||
**规 则:接口名应以大写的I开头,其他单词第一个字母都要大写(UpperCamelCase)**
|
||||
**规 则:接口名要用完整的单词,除非是被公认的单词缩写**
|
||||
**建 议:接口类中的方法和属性不要加任何修饰符好(public也不要加)。**
|
||||
|
||||
### 2.4 方法命名
|
||||
|
||||
**规 则:方法名应以动词或惯用短语描述**
|
||||
**规 则:方法名第一个字母都要小写,其他每个单词第一个字母都要大写(lowerCamelCase)**
|
||||
|
||||
**建 议:方法名中不要加入对象名字,可能会带来误解。**
|
||||
**说 明:** 因为对象本身已经包含在调用语句中了
|
||||
|
||||
### 2.5 变量
|
||||
|
||||
**规 则:应避免用单个字符命名变量**
|
||||
**规 则:代码中严禁使用拼音与英文混合的方式,优先使用英文而非拼音**
|
||||
|
||||
**规 则:参数名第一个字母使用小写,其他每个单词第一个字母大写(lowerCamelCase)**
|
||||
|
||||
**规 则:常量名应该为名词或名词短语**
|
||||
**规 则:常量名每一个字母都应为大写,单词之间用“_”分开。**
|
||||
**规 则:不允许出现任何魔法值(未经定义的常量)直接出现在代码中。**
|
||||
**建 议:不要使用一个常量类维护所有常量,应该按常量功能进行归类,分开维护。**
|
||||
|
||||
**规 则:long或者Long初始赋值时,必须使用大写的L,不能是小写的l,小写容易跟数字1混淆,造成误解。**
|
||||
|
||||
**规 则:局部变量名第一个字母使用小写,其他每个单词第一个字母大写(lowerCamelCase)。**
|
||||
**规 则:局部变量名可以更宽松的使用缩写,但除了临时变量和循环变量应避免单字符命名。**
|
||||
|
||||
**规 则:POJO类中布尔类型的变量,都不要加is,否则部分框架解析会引起序列化错误。(参考阿里java规范)**
|
||||
**说 明:**
|
||||
定义为基本数据类型Boolean isSuccess;的属性,它的方法也是isSuccess(),RPC框架在反向解析的时候,“以为”对应的属性名称是success,导致属性获取不到,进而抛出异常。
|
||||
|
||||
## 注释规范
|
||||
|
||||
见目录: .idea/fileTemplates/code
|
||||
|
||||
如需增加新的注释模板,进入IDEA设置->Editor->File and Code Templates,可进行修改。
|
||||
⚠️注意:确保schema选择的是project。
|
||||

|
||||
80
docs/开发指南.md
Normal file
80
docs/开发指南.md
Normal file
@ -0,0 +1,80 @@
|
||||
# 1. 开发流程
|
||||
|
||||
基于**AoneFlow**开发流程,具体请阅读:[在阿里,我们如何管理代码分支?](https://developer.aliyun.com/article/573549)
|
||||
|
||||
## 1.1 IDEA IDE版
|
||||
|
||||
### 步骤一 Fork JCNC/JNotepad或同步JCNC/JNotepad到个人仓库
|
||||
|
||||
#### 首次开发,进行Fork操作
|
||||
|
||||

|
||||
|
||||
#### 非首次开发,进行同步操作
|
||||
|
||||

|
||||
|
||||
### 步骤二 clone个人仓库或fetch
|
||||
|
||||
#### 首次开发,clone个人仓库
|
||||
|
||||

|
||||
|
||||
#### 非首次开发,执行fetch
|
||||
|
||||

|
||||
|
||||
### 步骤三 从remote下的master分支创建本地开发分支。
|
||||
|
||||
**特殊情况:开发依赖release分支已提交内容,或是对已提交内容进行修改,那么需要从release分支上进行拉取。**
|
||||

|
||||

|
||||
|
||||
分支命名规则见:1.3节
|
||||
|
||||
### 步骤四 推送本地分支到远程
|
||||
|
||||
### 步骤五 发起Pull Request(PR)
|
||||
|
||||

|
||||
|
||||
**注意:目标分支选择预期要发布的release分支**
|
||||
|
||||
## 1.2 GIT命令行版本
|
||||
|
||||
步骤一、步骤二、步骤五同1.1操作
|
||||
|
||||
```shell
|
||||
# 步骤三
|
||||
git fetch
|
||||
git checkout origin/master && git checkout -b feature-demo
|
||||
# 步骤四
|
||||
git push origin feature-demo
|
||||
```
|
||||
|
||||
## 1.3 分支命名规则
|
||||
|
||||
| issue类别 | 分支名格式 | 示例 |
|
||||
|------------|------------------|-----------------|
|
||||
| 功能/优化/文档修改 | feature-issue编号 | feature-I7W9LX |
|
||||
| bug fix | fix-issue编号 | fix-I7W9LX |
|
||||
| 代码重构 | refactor-issue编号 | refactor-I7W9LX |
|
||||
|
||||
# 2. IDEA插件配置
|
||||
|
||||
* 安装Resource Bundle插件
|
||||

|
||||
* 安装成功后,打开i18n.properties,可以看到Resource Bundle tab
|
||||

|
||||
|
||||
# Q&A
|
||||
|
||||
Q: 本地开发时,主仓库合并了新代码,如何处理?
|
||||
A: 继续完成本地开发,发起PR时再解决冲突。
|
||||
|
||||
Q: 解决冲突步骤是什么?
|
||||
A: 一般按如下步骤。
|
||||
|
||||
1. 先同步主仓库
|
||||
2. 本地仓库进行fetch
|
||||
3. 本地开发分支merge/pull/rebase更新的release分支
|
||||
44
libs/README.md
Normal file
44
libs/README.md
Normal file
@ -0,0 +1,44 @@
|
||||
# 解决jlink error指南
|
||||
|
||||
## 现象
|
||||
|
||||
jlink时,如果出现如下错误,参考本文档
|
||||
|
||||
```
|
||||
"automatic module cannot be used with jlink"
|
||||
```
|
||||
|
||||
## 解决方法:
|
||||
|
||||
1. 为jar生成module-info.class
|
||||
|
||||
```shell
|
||||
jdeps --ignore-missing-deps --module-path <jar_dir_path> --add-modules <module_name --generate-module-info <out_dir_path> <jar_path>
|
||||
javac --patch-module <module_name>=<jar_path> <module-info.java>
|
||||
jar uf <jar_path> -C <module_name> <module-info.class>
|
||||
```
|
||||
|
||||
以本次icu4j为例,先将依赖的jar包copy到libs目录,然后执行:
|
||||
|
||||
```shell
|
||||
jdeps --ignore-missing-deps --module-path libs --add-modules com.ibm.icu --generate-module-info libs/tmpOut libs/icu4j-73.2.jar
|
||||
javac --patch-module com.ibm.icu=libs/icu4j-73.2.jar libs/tmpOut/com.ibm.icu/module-info.java
|
||||
jar uf libs/icu4j-73.2.jar -C libs/tmpOut/com.ibm.icu module-info.class
|
||||
```
|
||||
|
||||
2. pom中添加依赖
|
||||
|
||||
```xml
|
||||
|
||||
<dependency>
|
||||
<groupId>com.ibm.icu</groupId>
|
||||
<artifactId>icu4j</artifactId>
|
||||
<version>73.2</version>
|
||||
<scope>system</scope>
|
||||
<systemPath>${project.basedir}/libs/icu4j-73.2.jar</systemPath>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
1. [java_jlink_automatic_module_cannot_be_used_with_jlink](https://tacosteemers.com/articles/java_jlink_automatic_module_cannot_be_used_with_jlink.html)
|
||||
BIN
libs/commonmark-0.21.0.jar
Normal file
BIN
libs/commonmark-0.21.0.jar
Normal file
Binary file not shown.
BIN
libs/icu4j-73.2.jar
Normal file
BIN
libs/icu4j-73.2.jar
Normal file
Binary file not shown.
BIN
libs/richtextfx-fat-0.11.1.jar
Normal file
BIN
libs/richtextfx-fat-0.11.1.jar
Normal file
Binary file not shown.
126
pom.xml
126
pom.xml
@ -1,21 +1,59 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.jcnc</groupId>
|
||||
<artifactId>JNotepad</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<version>1.0.14-alpha</version>
|
||||
<name>JNotepad</name>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<junit.version>5.9.2</junit.version>
|
||||
<javafx.version>20.0.2</javafx.version>
|
||||
</properties>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<!-- https://mvnrepository.com/artifact/org.fxmisc.richtext/richtextfx -->
|
||||
<!--JavaFX 的富文本区域-->
|
||||
<dependency>
|
||||
<groupId>org.fxmisc.richtext</groupId>
|
||||
<artifactId>richtextfx</artifactId>
|
||||
<version>0.11.1</version>
|
||||
<scope>system</scope>
|
||||
<systemPath>${project.basedir}/libs/richtextfx-fat-0.11.1.jar</systemPath>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.commonmark/commonmark -->
|
||||
<dependency>
|
||||
<groupId>org.commonmark</groupId>
|
||||
<artifactId>commonmark</artifactId>
|
||||
<version>0.21.0</version>
|
||||
<scope>system</scope>
|
||||
<systemPath>${project.basedir}/libs/commonmark-0.21.0.jar</systemPath>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-web</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!--图标库主依赖-->
|
||||
<dependency>
|
||||
<groupId>org.kordamp.ikonli</groupId>
|
||||
<artifactId>ikonli-javafx</artifactId>
|
||||
<version>12.3.1</version>
|
||||
</dependency>
|
||||
<!--蚂蚁图标库-->
|
||||
<dependency>
|
||||
<groupId>org.kordamp.ikonli</groupId>
|
||||
<artifactId>ikonli-antdesignicons-pack</artifactId>
|
||||
<version>12.3.1</version>
|
||||
</dependency>
|
||||
<!--亚特兰大fx主题-->
|
||||
<dependency>
|
||||
<groupId>io.github.mkpaz</groupId>
|
||||
<artifactId>atlantafx-base</artifactId>
|
||||
@ -24,7 +62,12 @@
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
<version>17.0.1</version>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
@ -32,11 +75,42 @@
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!--Json依赖-->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.15.2</version>
|
||||
</dependency>
|
||||
<!--log-->
|
||||
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>2.0.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-core</artifactId>
|
||||
<version>1.4.11</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.4.11</version>
|
||||
</dependency>
|
||||
<!--国际化依赖-->
|
||||
<dependency>
|
||||
<groupId>com.ibm.icu</groupId>
|
||||
<artifactId>icu4j</artifactId>
|
||||
<version>73.2</version>
|
||||
<scope>system</scope>
|
||||
<systemPath>${project.basedir}/libs/icu4j-73.2.jar</systemPath>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
@ -55,9 +129,8 @@
|
||||
<!-- Default configuration for running with: mvn clean javafx:run -->
|
||||
<id>default-cli</id>
|
||||
<configuration>
|
||||
<mainClass>org.jcnc.jnotepad/org.jcnc.jnotepad.LunchApp</mainClass>
|
||||
<mainClass>org.jcnc.jnotepad/org.jcnc.jnotepad.JnotepadApp</mainClass>
|
||||
<launcher>JNotepad</launcher>
|
||||
<jlinkZipName>JNotepad</jlinkZipName>
|
||||
<jlinkImageName>JNotepad</jlinkImageName>
|
||||
<noManPages>true</noManPages>
|
||||
<stripDebug>true</stripDebug>
|
||||
@ -67,6 +140,41 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.gluonhq</groupId>
|
||||
<artifactId>gluonfx-maven-plugin</artifactId>
|
||||
<version>1.0.19</version>
|
||||
<configuration>
|
||||
<mainClass>org.jcnc.jnotepad/org.jcnc.jnotepad.JnotepadApp</mainClass>
|
||||
<reflectionList>
|
||||
org.jcnc.jnotepad.app.config.UserConfig,org.jcnc.jnotepad.app.config.UserConfig$ShortcutKey
|
||||
</reflectionList>
|
||||
<bundlesList>
|
||||
i18n/i18n
|
||||
</bundlesList>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.4</version>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer
|
||||
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
|
||||
</transformers>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
</project>
|
||||
BIN
screenshot/Linux-1.png
Normal file
BIN
screenshot/Linux-1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 222 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 80 KiB |
BIN
screenshot/MacOS-1.png
Normal file
BIN
screenshot/MacOS-1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 740 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 81 KiB |
@ -1,13 +1,53 @@
|
||||
module org.jcnc.jnotepad {
|
||||
requires javafx.controls;
|
||||
// 不知道为什么,不加这个,日志框架在打包后的程序不起作用,会报错
|
||||
// Exception in thread "JavaFX Application Thread" java.lang.NoClassDefFoundError: javax/naming/NamingException
|
||||
// 但我打开源代码,他们的模块的确有包含这个,java.naming,这个没懂,我干脆自己导入
|
||||
requires java.naming;
|
||||
requires atlantafx.base;
|
||||
requires com.fasterxml.jackson.core;
|
||||
requires com.fasterxml.jackson.databind;
|
||||
requires com.fasterxml.jackson.annotation;
|
||||
requires org.slf4j;
|
||||
requires ch.qos.logback.core;
|
||||
requires ch.qos.logback.classic;
|
||||
requires com.ibm.icu;
|
||||
requires org.kordamp.ikonli.core;
|
||||
requires org.kordamp.ikonli.javafx;
|
||||
requires org.kordamp.ikonli.antdesignicons;
|
||||
requires richtextfx.fat;
|
||||
requires java.desktop;
|
||||
requires org.commonmark;
|
||||
requires javafx.web;
|
||||
|
||||
exports org.jcnc.jnotepad;
|
||||
exports org.jcnc.jnotepad.tool;
|
||||
exports org.jcnc.jnotepad.Interface;
|
||||
exports org.jcnc.jnotepad.controller.event.handler;
|
||||
exports org.jcnc.jnotepad.model.enums;
|
||||
exports org.jcnc.jnotepad.app.config;
|
||||
exports org.jcnc.jnotepad.app.i18n;
|
||||
exports org.jcnc.jnotepad.app.common.constants;
|
||||
exports org.jcnc.jnotepad.controller.config;
|
||||
exports org.jcnc.jnotepad.controller.manager;
|
||||
exports org.jcnc.jnotepad.view.init;
|
||||
exports org.jcnc.jnotepad.view.manager;
|
||||
exports org.jcnc.jnotepad.constants;
|
||||
exports org.jcnc.jnotepad.controller.i18n;
|
||||
exports org.jcnc.jnotepad.controller.event.handler.toolbar;
|
||||
exports org.jcnc.jnotepad.controller.event.handler.menuitem;
|
||||
exports org.jcnc.jnotepad.ui.component.module.interfaces;
|
||||
opens org.jcnc.jnotepad.app.config;
|
||||
exports org.jcnc.jnotepad.controller.plugin.interfaces;
|
||||
exports org.jcnc.jnotepad.ui.views.root.bottom.function;
|
||||
|
||||
exports org.jcnc.jnotepad.ui.component.module;
|
||||
exports org.jcnc.jnotepad.model.entity;
|
||||
exports org.jcnc.jnotepad.ui.views.root.bottom;
|
||||
exports org.jcnc.jnotepad.ui.views.root.bottom.status;
|
||||
exports org.jcnc.jnotepad.api.core.views.sidebar.bottom;
|
||||
exports org.jcnc.jnotepad.api.core.controller.config;
|
||||
exports org.jcnc.jnotepad.ui.component.module.base;
|
||||
exports org.jcnc.jnotepad.ui.component.stage.setting;
|
||||
exports org.jcnc.jnotepad.ui.component.module.vbox;
|
||||
exports org.jcnc.jnotepad.ui.component.module.hbox;
|
||||
exports org.jcnc.jnotepad.ui.component.stage.topmenu.help;
|
||||
exports org.jcnc.jnotepad.ui.component.stage.topmenu.plugin;
|
||||
exports org.jcnc.jnotepad.ui.component.module.vbox.components;
|
||||
exports org.jcnc.jnotepad.ui.views.root.center.main.center.tab;
|
||||
|
||||
}
|
||||
@ -1,113 +0,0 @@
|
||||
package org.jcnc.jnotepad.Interface;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.TextArea;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 控制器接口类
|
||||
*/
|
||||
public interface ControllerInterface {
|
||||
|
||||
/**
|
||||
* 打开关联文件并创建 TextArea
|
||||
*
|
||||
* @param rawParameters 原始参数列表
|
||||
* @return 创建的 TextArea
|
||||
*/
|
||||
TextArea openAssociatedFileAndCreateTextArea(List<String> rawParameters);
|
||||
|
||||
/**
|
||||
* 获取换行符处理事件处理程序
|
||||
*
|
||||
* @param textArea 文本区域
|
||||
* @return 换行符处理事件处理程序
|
||||
*/
|
||||
EventHandler<ActionEvent> getLineFeedEventHandler(TextArea textArea);
|
||||
|
||||
/**
|
||||
* 获取新建文件处理事件处理程序
|
||||
*
|
||||
* @param textArea 文本区域
|
||||
* @return 新建文件处理事件处理程序
|
||||
*/
|
||||
EventHandler<ActionEvent> getNewFileEventHandler(TextArea textArea);
|
||||
|
||||
/**
|
||||
* 获取打开文件处理事件处理程序
|
||||
*
|
||||
* @return 打开文件处理事件处理程序
|
||||
*/
|
||||
EventHandler<ActionEvent> getOpenFileEventHandler();
|
||||
|
||||
/**
|
||||
* 获取另存为文件处理事件处理程序
|
||||
*
|
||||
* @return 另存为文件处理事件处理程序
|
||||
*/
|
||||
EventHandler<ActionEvent> getSaveAsFileEventHandler();
|
||||
|
||||
|
||||
/**
|
||||
* 自动保存
|
||||
*
|
||||
* @param textArea 文本区域
|
||||
*/
|
||||
void autoSave(TextArea textArea);
|
||||
|
||||
/**
|
||||
* 更新状态标签
|
||||
*
|
||||
* @param textArea 文本区域
|
||||
*/
|
||||
void updateStatusLabel(TextArea textArea);
|
||||
|
||||
/**
|
||||
* 打开关联文件
|
||||
*
|
||||
* @param filePath 文件路径
|
||||
*/
|
||||
void openAssociatedFile(String filePath);
|
||||
|
||||
/**
|
||||
* 获取文件内容
|
||||
*
|
||||
* @param file 文件
|
||||
*/
|
||||
void getText(File file);
|
||||
|
||||
/**
|
||||
* 更新编码标签
|
||||
*
|
||||
* @param text 编码标签文本
|
||||
*/
|
||||
void upDateEncodingLabel(String text);
|
||||
|
||||
/**
|
||||
* 获取光标所在行号
|
||||
*
|
||||
* @param caretPosition 光标位置
|
||||
* @param text 文本内容
|
||||
* @return 行号
|
||||
*/
|
||||
int getRow(int caretPosition, String text);
|
||||
|
||||
/**
|
||||
* 获取光标所在列号
|
||||
*
|
||||
* @param caretPosition 光标位置
|
||||
* @param text 文本内容
|
||||
* @return 列号
|
||||
*/
|
||||
int getColumn(int caretPosition, String text);
|
||||
|
||||
/**
|
||||
* 初始化 TabPane
|
||||
*/
|
||||
void initTabPane();
|
||||
|
||||
void updateUIWithNewTextArea(TextArea textArea);
|
||||
}
|
||||
55
src/main/java/org/jcnc/jnotepad/JnotepadApp.java
Normal file
55
src/main/java/org/jcnc/jnotepad/JnotepadApp.java
Normal file
@ -0,0 +1,55 @@
|
||||
package org.jcnc.jnotepad;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.stage.Stage;
|
||||
import org.jcnc.jnotepad.app.manager.ApplicationManager;
|
||||
import org.jcnc.jnotepad.app.utils.LoggerUtil;
|
||||
|
||||
|
||||
/**
|
||||
* 启动程序类
|
||||
*
|
||||
* <p>该类用于启动 JNotepad 记事本应用程序。</p>
|
||||
*
|
||||
* @author 许轲
|
||||
*/
|
||||
public class JnotepadApp extends Application {
|
||||
private static final ApplicationManager APPLICATION_MANAGER = ApplicationManager.getInstance();
|
||||
|
||||
/**
|
||||
* 应用程序的入口点,启动 JavaFX 应用程序。
|
||||
*
|
||||
* @param args 命令行参数
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
// 获取当前启动位置a
|
||||
String currentWorkingDirectory = System.getProperty("user.dir");
|
||||
LoggerUtil.getLogger(this.getClass()).info("当前启动位置:{}", currentWorkingDirectory);
|
||||
// 设置参数
|
||||
APPLICATION_MANAGER.setApplication(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) {
|
||||
APPLICATION_MANAGER.setPrimaryStage(primaryStage);
|
||||
// 加载应用程序资源
|
||||
APPLICATION_MANAGER.loadAppResources();
|
||||
// 加载应用程序缓存
|
||||
APPLICATION_MANAGER.loadAppCache();
|
||||
// 初始化应用程序
|
||||
APPLICATION_MANAGER.initializeApp();
|
||||
// 初始化默认操作
|
||||
APPLICATION_MANAGER.executeDefaultAction();
|
||||
primaryStage.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
APPLICATION_MANAGER.operationBeforeStopping();
|
||||
}
|
||||
}
|
||||
@ -1,81 +0,0 @@
|
||||
package org.jcnc.jnotepad;
|
||||
|
||||
import atlantafx.base.theme.PrimerLight;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.stage.Stage;
|
||||
import org.jcnc.jnotepad.constants.Constants;
|
||||
import org.jcnc.jnotepad.controller.manager.Controller;
|
||||
import org.jcnc.jnotepad.view.manager.ViewManager;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import static org.jcnc.jnotepad.view.init.View.initItem;
|
||||
import static org.jcnc.jnotepad.view.init.View.initTabPane;
|
||||
|
||||
public class LunchApp extends Application {
|
||||
private static final ExecutorService threadPool = Executors.newCachedThreadPool();
|
||||
public static boolean isRelevance = true;
|
||||
|
||||
Controller controller = new Controller();
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) {
|
||||
|
||||
|
||||
Pane root = new Pane();
|
||||
|
||||
double width = Constants.SCREEN_WIDTH;
|
||||
double length = Constants.SCREEN_LENGTH;
|
||||
String name = Constants.APP_NAME;
|
||||
String icon = Constants.APP_ICON;
|
||||
|
||||
Scene scene = new Scene(root, width, length);
|
||||
|
||||
Application.setUserAgentStylesheet(new PrimerLight().getUserAgentStylesheet());
|
||||
|
||||
primaryStage.setTitle(name);
|
||||
primaryStage.setWidth(width);
|
||||
primaryStage.setHeight(length);
|
||||
primaryStage.setScene(scene);
|
||||
primaryStage.getIcons().add(new Image(Objects.requireNonNull(getClass().getResource(icon)).toString()));
|
||||
primaryStage.show();
|
||||
|
||||
ViewManager viewManager = ViewManager.getInstance(scene);
|
||||
viewManager.initScreen(scene);
|
||||
|
||||
// 初始化菜单项和标签栏
|
||||
initItem();
|
||||
initTabPane();
|
||||
|
||||
if (isRelevance) {
|
||||
// 使用线程池加载关联文件并创建文本区域
|
||||
List<String> rawParameters = getParameters().getRaw();
|
||||
threadPool.execute(() -> {
|
||||
TextArea textArea = controller.openAssociatedFileAndCreateTextArea(rawParameters);
|
||||
if (!Objects.isNull(textArea)) {
|
||||
Platform.runLater(() -> controller.updateUIWithNewTextArea(textArea));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
// 关闭线程池
|
||||
threadPool.shutdownNow();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
package org.jcnc.jnotepad.api.core.component.stage;
|
||||
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
/**
|
||||
* 抽象窗格舞台。
|
||||
* <p>
|
||||
* 该类是一个抽象的窗格舞台,用于创建自定义的JavaFX窗口。
|
||||
* </p>
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public abstract class AbstractPaneStage extends BorderPane {
|
||||
private final Stage stage = new Stage();
|
||||
|
||||
/**
|
||||
* 获取舞台图标。
|
||||
*
|
||||
* @return 舞台图标
|
||||
*/
|
||||
protected abstract Image getStageIcon();
|
||||
|
||||
/**
|
||||
* 获取舞台标题。
|
||||
*
|
||||
* @return 舞台标题
|
||||
*/
|
||||
protected abstract String getStageTitle();
|
||||
|
||||
/**
|
||||
* 获取自定义舞台场景。
|
||||
*
|
||||
* @return 舞台场景
|
||||
*/
|
||||
protected abstract Scene getCustomizationScene();
|
||||
|
||||
/**
|
||||
* 初始化方法。
|
||||
* <p>
|
||||
* 在此方法中,您可以进行与窗口相关的初始化操作。
|
||||
* </p>
|
||||
*/
|
||||
protected abstract void initialize();
|
||||
|
||||
/**
|
||||
* 自定义启动方法。
|
||||
*
|
||||
* @param stage 自定义舞台
|
||||
*/
|
||||
public abstract void run(Stage stage);
|
||||
|
||||
/**
|
||||
* 启动方法。
|
||||
* <p>
|
||||
* 该方法设置窗口的图标、标题、场景,并将窗口设置为模态对话框,然后显示窗口。
|
||||
* </p>
|
||||
*/
|
||||
public void run() {
|
||||
stage.getIcons().add(getStageIcon());
|
||||
stage.setTitle(getStageTitle());
|
||||
stage.setScene(getCustomizationScene());
|
||||
// 设置为模态
|
||||
stage.initModality(Modality.APPLICATION_MODAL);
|
||||
stage.show();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,135 @@
|
||||
package org.jcnc.jnotepad.api.core.controller.config;
|
||||
|
||||
import org.jcnc.jnotepad.api.core.controller.interfaces.ConfigController;
|
||||
import org.jcnc.jnotepad.app.utils.JsonUtil;
|
||||
import org.jcnc.jnotepad.app.utils.LoggerUtil;
|
||||
import org.jcnc.jnotepad.app.utils.PopUpUtil;
|
||||
import org.jcnc.jnotepad.controller.exception.AppException;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* 抽象基本配置文件控制器类。
|
||||
* <p>
|
||||
* 该类是基本配置文件控制器的抽象实现,提供了加载、持久化配置文件以及其他相关方法。
|
||||
* </p>
|
||||
*
|
||||
* @param <T> 配置文件类型
|
||||
* @author gewuyou
|
||||
*/
|
||||
public abstract class BaseConfigController<T> implements ConfigController<T> {
|
||||
|
||||
protected static final String ROOT_CONFIG_DIR = "config";
|
||||
|
||||
protected static final String SYSTEM_CONFIG_DIR = "system";
|
||||
private final Logger logger = LoggerUtil.getLogger(getClass());
|
||||
protected T config;
|
||||
|
||||
/**
|
||||
* 获取配置文件Class类。
|
||||
*
|
||||
* @return 配置文件Class类
|
||||
*/
|
||||
protected abstract Class<T> getConfigClass();
|
||||
|
||||
/**
|
||||
* 获取配置文件名称。
|
||||
*
|
||||
* @return 配置文件名称
|
||||
*/
|
||||
protected abstract String getConfigName();
|
||||
|
||||
/**
|
||||
* 获取配置文件文件夹路径。
|
||||
*
|
||||
* @return 配置文件夹路径
|
||||
*/
|
||||
protected abstract String getConfigDir();
|
||||
|
||||
/**
|
||||
* 获取配置文件对象。
|
||||
*
|
||||
* @return 配置文件对象
|
||||
*/
|
||||
public T getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载配置文件内容。
|
||||
*/
|
||||
@Override
|
||||
public void loadConfig() {
|
||||
createConfigIfNotExists();
|
||||
// 存在则加载
|
||||
try {
|
||||
logger.info("正在加载配置文件: {}...", getConfigClass());
|
||||
String configContent = Files.readString(getConfigPath());
|
||||
config = JsonUtil.OBJECT_MAPPER.readValue(configContent, getConfigClass());
|
||||
} catch (IOException e) {
|
||||
logger.error("加载配置文件错误", e);
|
||||
PopUpUtil.errorAlert("错误", "读写错误", "加载配置文件错误!", null, null);
|
||||
throw new AppException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置文件持久化。
|
||||
*/
|
||||
@Override
|
||||
public void writeConfig() {
|
||||
createConfigIfNotExists();
|
||||
writeConfig(getConfig());
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置文件持久化。
|
||||
*
|
||||
* @param config 配置文件对象
|
||||
*/
|
||||
@Override
|
||||
public void writeConfig(T config) {
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(getConfigPath().toString()))) {
|
||||
if (config == null) {
|
||||
config = generateDefaultConfig();
|
||||
}
|
||||
writer.write(JsonUtil.toJsonString(config));
|
||||
} catch (Exception e) {
|
||||
logger.error("", e);
|
||||
PopUpUtil.errorAlert("错误", "读写错误", "配置文件读写错误!", null, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果配置文件不存在则创建。
|
||||
*/
|
||||
@Override
|
||||
public void createConfigIfNotExists() {
|
||||
Path configPath = getConfigPath();
|
||||
if (configPath.toFile().exists()) {
|
||||
return;
|
||||
}
|
||||
File directory = new File(getConfigDir());
|
||||
if (!directory.exists()) {
|
||||
directory.mkdirs();
|
||||
}
|
||||
writeConfig(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置文件路径。
|
||||
*
|
||||
* @return 配置文件路径
|
||||
*/
|
||||
@Override
|
||||
public Path getConfigPath() {
|
||||
return Paths.get(getConfigDir(), getConfigName());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
package org.jcnc.jnotepad.api.core.controller.interfaces;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* 配置文件控制器接口
|
||||
* <p>
|
||||
* 该接口定义了配置文件相关的操作,包括加载、持久化、创建和获取配置文件路径等。
|
||||
* </p>
|
||||
*
|
||||
* @param <T> 配置文件类型
|
||||
* @author gewuyou
|
||||
*/
|
||||
public interface ConfigController<T> {
|
||||
/**
|
||||
* 加载配置文件内容
|
||||
* <p>
|
||||
* 从配置文件中加载配置信息。
|
||||
* </p>
|
||||
*/
|
||||
void loadConfig();
|
||||
|
||||
/**
|
||||
* 配置文件持久化
|
||||
* <p>
|
||||
* 将配置信息持久化到配置文件中。
|
||||
* </p>
|
||||
*/
|
||||
void writeConfig();
|
||||
|
||||
/**
|
||||
* 配置文件持久化
|
||||
* <p>
|
||||
* 将指定的配置对象持久化到配置文件中。
|
||||
* </p>
|
||||
*
|
||||
* @param config 配置文件对象
|
||||
*/
|
||||
void writeConfig(T config);
|
||||
|
||||
/**
|
||||
* 如果配置文件不存在则创建
|
||||
* <p>
|
||||
* 在需要的情况下创建配置文件,如果配置文件已存在,则不执行任何操作。
|
||||
* </p>
|
||||
*/
|
||||
void createConfigIfNotExists();
|
||||
|
||||
/**
|
||||
* 创建配置文件实体
|
||||
* <p>
|
||||
* 生成默认的配置文件实体对象,用于后续的序列化操作。
|
||||
* </p>
|
||||
*
|
||||
* @return 默认的配置文件实体
|
||||
* @apiNote 返回默认的配置文件实体用于序列化 JSON 数据。
|
||||
*/
|
||||
T generateDefaultConfig();
|
||||
|
||||
/**
|
||||
* 获取配置文件路径
|
||||
* <p>
|
||||
* 返回配置文件的路径。
|
||||
* </p>
|
||||
*
|
||||
* @return 配置文件路径
|
||||
*/
|
||||
Path getConfigPath();
|
||||
}
|
||||
@ -0,0 +1,118 @@
|
||||
package org.jcnc.jnotepad.api.core.manager;
|
||||
|
||||
import org.jcnc.jnotepad.model.entity.Cache;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 抽象缓存管理类
|
||||
*
|
||||
* <p>
|
||||
* 该类是缓存管理的抽象基类,用于管理不同类型的缓存。
|
||||
* </p>
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public abstract class AbstractCacheManager {
|
||||
/**
|
||||
* 缓存集合
|
||||
*/
|
||||
protected Map<String, Cache> caches;
|
||||
|
||||
/**
|
||||
* 获取全局命名空间
|
||||
*
|
||||
* @return 全局命名空间
|
||||
*/
|
||||
public abstract String getGlobalNamespace();
|
||||
|
||||
/**
|
||||
* 创建缓存类
|
||||
*
|
||||
* @param group 缓存组
|
||||
* @param name 缓存名称
|
||||
* @param cacheData 缓存数据
|
||||
* @param expirationTime 过期时间
|
||||
* @return 缓存类
|
||||
* @apiNote 这个方法,只需通过自定义的缓存管理类调用此方法,无需每次指定相同地命名空间
|
||||
*/
|
||||
public Cache createCache(String group, String name, Object cacheData, Long expirationTime) {
|
||||
return new Cache(getGlobalNamespace(), group, name, cacheData, expirationTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存集合
|
||||
*
|
||||
* @return 缓存集合
|
||||
*/
|
||||
public Map<String, Cache> getCaches() {
|
||||
return caches;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置缓存集合
|
||||
*
|
||||
* @param caches 缓存集合
|
||||
*/
|
||||
public void setCaches(Map<String, Cache> caches) {
|
||||
this.caches = caches;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加缓存
|
||||
*
|
||||
* @param cache 缓存
|
||||
*/
|
||||
public void addCache(Cache cache) {
|
||||
String cacheKey = cache.getCacheKey();
|
||||
// 如果集合中已存在该缓存,则更新读写时间
|
||||
if (caches.containsKey(cacheKey)) {
|
||||
cache.setLastReadOrWriteTime(System.currentTimeMillis());
|
||||
}
|
||||
caches.put(cacheKey, cache);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存类
|
||||
*
|
||||
* @param cacheKey 缓存key
|
||||
* @return 缓存类
|
||||
*/
|
||||
public Cache getCache(String cacheKey) {
|
||||
if (caches == null || caches.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (caches.containsKey(cacheKey)) {
|
||||
Cache cache = caches.get(cacheKey);
|
||||
cache.setLastReadOrWriteTime(System.currentTimeMillis());
|
||||
return cache;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存类
|
||||
*
|
||||
* @param group 组
|
||||
* @param name 缓存名
|
||||
* @return 缓存类
|
||||
*/
|
||||
public Cache getCache(String group, String name) {
|
||||
return getCache(Cache.getCacheKey(getGlobalNamespace(), group, name));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存数据
|
||||
*
|
||||
* @param group 组
|
||||
* @param name 缓存名
|
||||
* @return 缓存类
|
||||
*/
|
||||
public Object getCacheData(String group, String name) {
|
||||
Cache cache = getCache(group, name);
|
||||
if (cache == null) {
|
||||
return null;
|
||||
}
|
||||
return cache.getCacheData();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package org.jcnc.jnotepad.api.core.views.manager;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 抽象管理类
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public abstract class AbstractManager<T> {
|
||||
|
||||
/**
|
||||
* 获取节点列表
|
||||
*
|
||||
* @return 节点列表
|
||||
*/
|
||||
public abstract List<T> getNodeList();
|
||||
|
||||
public void registerNode(T node) {
|
||||
getNodeList().add(node);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
package org.jcnc.jnotepad.api.core.views.manager.builder;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.Button;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 下方状态栏按钮建造者
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class BottomStatusBoxButtonBuilder {
|
||||
private Button button;
|
||||
|
||||
private FontIcon fontIcon;
|
||||
|
||||
private EventHandler<ActionEvent> eventHandler;
|
||||
|
||||
public BottomStatusBoxButtonBuilder() {
|
||||
}
|
||||
|
||||
public BottomStatusBoxButtonBuilder(Button button) {
|
||||
this.button = button;
|
||||
}
|
||||
|
||||
public BottomStatusBoxButtonBuilder setFontIcon(FontIcon fontIcon) {
|
||||
this.fontIcon = fontIcon;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BottomStatusBoxButtonBuilder setEventHandler(EventHandler<ActionEvent> eventHandler) {
|
||||
this.eventHandler = eventHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Button build() {
|
||||
Optional<Button> container = Optional.ofNullable(button);
|
||||
button = container.orElseGet(Button::new);
|
||||
button.setGraphic(fontIcon);
|
||||
button.setOnAction(eventHandler);
|
||||
return button;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
package org.jcnc.jnotepad.api.core.views.manager.builder;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.image.ImageView;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 侧边栏按钮建造者
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class SideBarButtonBuilder {
|
||||
private Button button;
|
||||
private ImageView imageView;
|
||||
private EventHandler<ActionEvent> eventHandler;
|
||||
|
||||
public Button build() {
|
||||
Optional<Button> container = Optional.ofNullable(button);
|
||||
button = container.orElseGet(Button::new);
|
||||
button.setGraphic(imageView);
|
||||
button.setOnAction(eventHandler);
|
||||
return button;
|
||||
}
|
||||
|
||||
public SideBarButtonBuilder setImageView(ImageView imageView) {
|
||||
this.imageView = imageView;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SideBarButtonBuilder setButtonEssentialAttribute(Double relativelyPrefWidth, Double relativelyPrefHeight) {
|
||||
Optional<Double> container = Optional.ofNullable(relativelyPrefHeight);
|
||||
button.setPrefWidth(imageView.getFitWidth() + container.orElse(20D));
|
||||
container = Optional.ofNullable(relativelyPrefWidth);
|
||||
button.setPrefHeight(imageView.getFitHeight() + container.orElse(20D));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置ImageView属性
|
||||
*
|
||||
* @param fitWidth 适合宽度
|
||||
* @param fitHeight 适合高度
|
||||
* @param preserveRatio 保持比例
|
||||
* @param scaleX X轴比例
|
||||
* @param scaleY Y轴比例
|
||||
* @return 建造者对象
|
||||
*/
|
||||
public SideBarButtonBuilder setImageViewEssentialAttribute(Double fitWidth, Double fitHeight, boolean preserveRatio, Double scaleX, Double scaleY) {
|
||||
Optional<Double> container = Optional.ofNullable(fitWidth);
|
||||
imageView.setFitWidth(container.orElse(10D));
|
||||
container = Optional.ofNullable(fitHeight);
|
||||
imageView.setFitHeight(container.orElse(10D));
|
||||
imageView.setPreserveRatio(preserveRatio);
|
||||
container = Optional.ofNullable(scaleX);
|
||||
imageView.setScaleX(container.orElse(2.5));
|
||||
container = Optional.ofNullable(scaleY);
|
||||
imageView.setScaleY(container.orElse(2.5));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public SideBarButtonBuilder setEventHandler(EventHandler<ActionEvent> eventHandler) {
|
||||
this.eventHandler = eventHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SideBarButtonBuilder setButton(Button button) {
|
||||
this.button = button;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
package org.jcnc.jnotepad.api.core.views.menu;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.Menu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import org.jcnc.jnotepad.app.i18n.UiResourceBundle;
|
||||
import org.jcnc.jnotepad.ui.views.root.top.menubar.TopMenuBar;
|
||||
|
||||
/**
|
||||
* 抽象基础菜单类
|
||||
*
|
||||
* <p>
|
||||
* 此抽象类用于创建基础菜单,包括菜单项的注册和初始化。
|
||||
* </p>
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public abstract class AbstractBaseMenu extends AbstractMenu<Menu> {
|
||||
protected final TopMenuBar topMenuBar = TopMenuBar.getInstance();
|
||||
|
||||
/**
|
||||
* 获取菜单名称
|
||||
*
|
||||
* @return 菜单名称
|
||||
*/
|
||||
public abstract String getMenuName();
|
||||
|
||||
/**
|
||||
* 获取菜单项
|
||||
*
|
||||
* @return 菜单项集合
|
||||
*/
|
||||
@Override
|
||||
protected ObservableList<MenuItem> getItems() {
|
||||
return getMenu().getItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化菜单栏
|
||||
*/
|
||||
@Override
|
||||
public void initMenu() {
|
||||
registerMenu();
|
||||
Menu menu = getMenu();
|
||||
// 菜单名称国际化
|
||||
UiResourceBundle.bindStringProperty(menu.textProperty(), getMenuName());
|
||||
// 初始化菜单项
|
||||
initMenuItems(getMenuItems());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,119 @@
|
||||
package org.jcnc.jnotepad.api.core.views.menu;
|
||||
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.CheckMenuItem;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.RadioMenuItem;
|
||||
import org.jcnc.jnotepad.app.i18n.UiResourceBundle;
|
||||
import org.jcnc.jnotepad.app.utils.LoggerUtil;
|
||||
import org.jcnc.jnotepad.controller.config.UserConfigController;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 抽象菜单类
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public abstract class AbstractMenu<T> {
|
||||
protected Logger logger = LoggerUtil.getLogger(this.getClass());
|
||||
UserConfigController userConfigController = UserConfigController.getInstance();
|
||||
|
||||
/**
|
||||
* 获取菜单
|
||||
*
|
||||
* @return 菜单
|
||||
*/
|
||||
public abstract T getMenu();
|
||||
|
||||
/**
|
||||
* 获取菜单项集合
|
||||
*
|
||||
* @return 菜单项集合
|
||||
*/
|
||||
public abstract Map<String, MenuItem> getMenuItems();
|
||||
|
||||
/**
|
||||
* 注册菜单
|
||||
*/
|
||||
protected abstract void registerMenu();
|
||||
|
||||
/**
|
||||
* 初始化菜单
|
||||
*/
|
||||
protected abstract void initMenu();
|
||||
|
||||
/**
|
||||
* 获取菜单项
|
||||
*
|
||||
* @return 菜单项集合
|
||||
*/
|
||||
protected abstract ObservableList<MenuItem> getItems();
|
||||
|
||||
|
||||
/**
|
||||
* 注册菜单项
|
||||
*
|
||||
* @param menuItem 菜单项
|
||||
* @param menuItemName 菜单项名称
|
||||
* @param userData 用户数据,用来存放必要的数据,比如按钮菜单项名称
|
||||
* @param eventHandler 事件处理器
|
||||
*/
|
||||
public void registerMenuItem(MenuItem menuItem, String menuItemName, Object userData, EventHandler<ActionEvent> eventHandler) {
|
||||
getMenuItems().put(menuItemName, menuItem);
|
||||
menuItem.setUserData(userData);
|
||||
menuItem.setOnAction(eventHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册检查菜单项
|
||||
*
|
||||
* @param checkMenuItem 检查菜单项
|
||||
* @param menuItemName 菜单项名称
|
||||
* @param userData 用户数据,用来存放必要的数据,比如按钮菜单项名称
|
||||
* @param listener 监听器
|
||||
*/
|
||||
public void registerMenuItem(CheckMenuItem checkMenuItem, String menuItemName, Object userData, ChangeListener<Boolean> listener) {
|
||||
getMenuItems().put(menuItemName, checkMenuItem);
|
||||
checkMenuItem.setUserData(userData);
|
||||
checkMenuItem.selectedProperty().addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册单选菜单项
|
||||
*
|
||||
* @param radioMenuItem 单选菜单项
|
||||
* @param menuItemName 菜单项名称
|
||||
* @param userData 用户数据,用来存放必要的数据
|
||||
* @param eventHandler 事件处理器
|
||||
*/
|
||||
public void registerRadioMenuItem(Map<String, RadioMenuItem> radioMenuItems, RadioMenuItem radioMenuItem, String menuItemName, Object userData, EventHandler<ActionEvent> eventHandler) {
|
||||
radioMenuItems.put(menuItemName, radioMenuItem);
|
||||
radioMenuItem.setUserData(userData);
|
||||
radioMenuItem.setOnAction(eventHandler);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 初始化菜单项
|
||||
*
|
||||
* @param menuItems 菜单项集合
|
||||
*/
|
||||
protected void initMenuItems(Map<String, MenuItem> menuItems) {
|
||||
logger.info("初始化菜单项!");
|
||||
Map<String, MenuItem> menuItemMap = new HashMap<>(16);
|
||||
menuItems.forEach((key, value) -> {
|
||||
UiResourceBundle.bindStringProperty(value.textProperty(), key);
|
||||
menuItemMap.put((String) value.getUserData(), value);
|
||||
getItems().add(value);
|
||||
});
|
||||
userConfigController.getMenuItems().add(menuItemMap);
|
||||
userConfigController.initShortcutKeys(menuItemMap);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,230 @@
|
||||
package org.jcnc.jnotepad.api.core.views.menu.builder;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.*;
|
||||
import org.jcnc.jnotepad.app.i18n.UiResourceBundle;
|
||||
import org.jcnc.jnotepad.controller.config.UserConfigController;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 抽象菜单建造者类
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public abstract class AbstractMenuBuilder<B, T> {
|
||||
/**
|
||||
* 上下文菜单项
|
||||
*/
|
||||
protected final Map<String, MenuItem> menuItems = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Get subclass builder
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
protected abstract B getBuilder();
|
||||
|
||||
/**
|
||||
* 获取菜单
|
||||
*
|
||||
* @return 菜单
|
||||
*/
|
||||
protected abstract T getMenu();
|
||||
|
||||
/**
|
||||
* Retrieves the items of the menu.
|
||||
*
|
||||
* @return an ObservableList of MenuItems
|
||||
*/
|
||||
protected abstract ObservableList<MenuItem> getItems();
|
||||
|
||||
/**
|
||||
* 添加菜单项
|
||||
*
|
||||
* @param label 菜单项名称
|
||||
* @param eventHandler 事件
|
||||
* @return 建造者
|
||||
*/
|
||||
public B addMenuItem(String label, EventHandler<ActionEvent> eventHandler) {
|
||||
MenuItem menuItem = new MenuItem(label);
|
||||
menuItem.setOnAction(eventHandler);
|
||||
menuItems.put(label, menuItem);
|
||||
getItems().add(menuItem);
|
||||
return getBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加菜单项
|
||||
*
|
||||
* @param label 菜单项名称
|
||||
* @param eventHandler 事件
|
||||
* @param visible 是否可见
|
||||
* @return 建造者
|
||||
*/
|
||||
public B addMenuItem(String label, EventHandler<ActionEvent> eventHandler, BooleanProperty visible) {
|
||||
MenuItem menuItem = new MenuItem(label);
|
||||
menuItem.setOnAction(eventHandler);
|
||||
menuItem.setVisible(visible.get());
|
||||
visible.addListener((observable, oldValue, newValue) -> menuItem.setVisible(Boolean.TRUE.equals(newValue)));
|
||||
menuItems.put(label, menuItem);
|
||||
getItems().add(menuItem);
|
||||
return getBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加菜单项
|
||||
*
|
||||
* @param label 菜单项名称
|
||||
* @param eventHandler 事件
|
||||
* @param visible 是否可见
|
||||
* @return 建造者
|
||||
*/
|
||||
public B addMenuItem(String label, EventHandler<ActionEvent> eventHandler, boolean visible) {
|
||||
MenuItem menuItem = new MenuItem(label);
|
||||
menuItem.setOnAction(eventHandler);
|
||||
menuItem.setVisible(visible);
|
||||
menuItems.put(label, menuItem);
|
||||
getItems().add(menuItem);
|
||||
return getBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加单选菜单项
|
||||
*
|
||||
* @param label 菜单项名称
|
||||
* @param eventHandler 事件
|
||||
* @return 建造者
|
||||
*/
|
||||
public B addRadioMenuItem(String label, EventHandler<ActionEvent> eventHandler) {
|
||||
RadioMenuItem menuItem = new RadioMenuItem(label);
|
||||
menuItem.setOnAction(eventHandler);
|
||||
menuItems.put(label, menuItem);
|
||||
getItems().add(menuItem);
|
||||
return getBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加复选菜单项
|
||||
*
|
||||
* @param label 菜单项名称
|
||||
* @param eventHandler 事件
|
||||
* @return 建造者
|
||||
*/
|
||||
public B addCheckMenuItem(String label, EventHandler<ActionEvent> eventHandler) {
|
||||
CheckMenuItem menuItem = new CheckMenuItem(label);
|
||||
menuItem.setOnAction(eventHandler);
|
||||
menuItems.put(label, menuItem);
|
||||
getItems().add(menuItem);
|
||||
return getBuilder();
|
||||
}
|
||||
|
||||
|
||||
public B addCheckMenuItem(CheckMenuItem checkMenuItem, EventHandler<ActionEvent> eventHandler) {
|
||||
checkMenuItem.setOnAction(eventHandler);
|
||||
menuItems.put(checkMenuItem.getText(), checkMenuItem);
|
||||
getItems().add(checkMenuItem);
|
||||
return getBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加菜单
|
||||
*
|
||||
* @param menu 菜单
|
||||
* @return 建造者
|
||||
*/
|
||||
public B addMenu(Menu menu) {
|
||||
menuItems.put(menu.getText(), menu);
|
||||
getItems().add(menu);
|
||||
return getBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加菜单
|
||||
*
|
||||
* @param menu 菜单
|
||||
* @param visible 是否隐藏
|
||||
* @return 建造者
|
||||
*/
|
||||
public B addMenu(Menu menu, BooleanProperty visible) {
|
||||
menu.setVisible(visible.get());
|
||||
visible.addListener((observable, oldValue, newValue) -> menu.setVisible(Boolean.TRUE.equals(newValue)));
|
||||
menuItems.put(menu.getText(), menu);
|
||||
getItems().add(menu);
|
||||
return getBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加菜单
|
||||
*
|
||||
* @param menu 菜单
|
||||
* @param visible 是否隐藏
|
||||
* @return 建造者
|
||||
*/
|
||||
public B addMenu(Menu menu, boolean visible) {
|
||||
menu.setVisible(visible);
|
||||
menuItems.put(menu.getText(), menu);
|
||||
getItems().add(menu);
|
||||
return getBuilder();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加分割线
|
||||
*
|
||||
* @return 建造者
|
||||
*/
|
||||
public B addSeparatorMenuItem() {
|
||||
getItems().add(new SeparatorMenuItem());
|
||||
return getBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加分割线
|
||||
*
|
||||
* @param visible 是否可见
|
||||
* @return 建造者
|
||||
*/
|
||||
public B addSeparatorMenuItem(BooleanProperty visible) {
|
||||
SeparatorMenuItem separatorMenuItem = new SeparatorMenuItem();
|
||||
separatorMenuItem.setVisible(visible.get());
|
||||
visible.addListener((observable, oldValue, newValue) -> separatorMenuItem.setVisible(Boolean.TRUE.equals(newValue)));
|
||||
getItems().add(separatorMenuItem);
|
||||
return getBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加分割线
|
||||
*
|
||||
* @param visible 是否可见
|
||||
* @return 建造者
|
||||
*/
|
||||
public B addSeparatorMenuItem(boolean visible) {
|
||||
SeparatorMenuItem separatorMenuItem = new SeparatorMenuItem();
|
||||
separatorMenuItem.setVisible(visible);
|
||||
getItems().add(separatorMenuItem);
|
||||
return getBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build menu
|
||||
*
|
||||
* @return menu
|
||||
*/
|
||||
public T build() {
|
||||
UserConfigController userConfigController = UserConfigController.getInstance();
|
||||
Map<String, MenuItem> menuItemMap = new HashMap<>(16);
|
||||
menuItems.forEach((key, value) -> {
|
||||
UiResourceBundle.bindStringProperty(value.textProperty(), key);
|
||||
menuItemMap.put((String) value.getUserData(), value);
|
||||
});
|
||||
userConfigController.getMenuItems().add(menuItemMap);
|
||||
userConfigController.initShortcutKeys(menuItemMap);
|
||||
return getMenu();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
package org.jcnc.jnotepad.api.core.views.menu.builder;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
|
||||
/**
|
||||
* 上下文菜单建造者类
|
||||
*
|
||||
* <p>
|
||||
* 此类用于构建上下文菜单对象,可以添加菜单项、单选菜单项、复选菜单项以及分割线等。
|
||||
* </p>
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class ContextMenuBuilder extends AbstractMenuBuilder<ContextMenuBuilder, ContextMenu> {
|
||||
|
||||
private final ContextMenu contextMenu;
|
||||
|
||||
public ContextMenuBuilder() {
|
||||
contextMenu = new ContextMenu();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取子类的建造者实例
|
||||
*
|
||||
* @return 建造者实例
|
||||
*/
|
||||
@Override
|
||||
protected ContextMenuBuilder getBuilder() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单
|
||||
*
|
||||
* @return 菜单
|
||||
*/
|
||||
@Override
|
||||
protected ContextMenu getMenu() {
|
||||
return contextMenu;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上下文菜单的菜单项列表
|
||||
*
|
||||
* @return 菜单项列表
|
||||
*/
|
||||
@Override
|
||||
protected ObservableList<MenuItem> getItems() {
|
||||
return contextMenu.getItems();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
package org.jcnc.jnotepad.api.core.views.menu.builder;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.Menu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
|
||||
/**
|
||||
* 菜单建造者类
|
||||
*
|
||||
* <p>
|
||||
* 此类用于构建菜单对象,可以添加菜单项、单选菜单项、复选菜单项以及分割线等。
|
||||
* </p>
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class MenuBuilder extends AbstractMenuBuilder<MenuBuilder, Menu> {
|
||||
|
||||
private final Menu menu;
|
||||
|
||||
|
||||
/**
|
||||
* 构造菜单建造者
|
||||
*
|
||||
* @param label 菜单的标签
|
||||
*/
|
||||
public MenuBuilder(String label) {
|
||||
menu = new Menu(label);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取子类的建造者实例
|
||||
*
|
||||
* @return 建造者实例
|
||||
*/
|
||||
@Override
|
||||
protected MenuBuilder getBuilder() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单
|
||||
*
|
||||
* @return 菜单
|
||||
*/
|
||||
@Override
|
||||
protected Menu getMenu() {
|
||||
return menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单的菜单项列表
|
||||
*
|
||||
* @return 菜单项列表
|
||||
*/
|
||||
@Override
|
||||
protected ObservableList<MenuItem> getItems() {
|
||||
return menu.getItems();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
package org.jcnc.jnotepad.api.core.views.sidebar.bottom;
|
||||
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Menu;
|
||||
import javafx.scene.control.MenuBar;
|
||||
import org.jcnc.jnotepad.ui.views.root.bottom.function.FunctionBox;
|
||||
|
||||
/**
|
||||
* 子功能栏抽象类
|
||||
*
|
||||
* <p>
|
||||
* 此抽象类用于构建一个基本的子功能栏,包括功能按钮的初始化和添加到功能栏中。
|
||||
* </p>
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public abstract class AbstractFunctionChildrenBox {
|
||||
protected final FunctionBox functionBox;
|
||||
|
||||
protected final MenuBar menuBar;
|
||||
|
||||
protected final Label label = new Label(getFunctionName());
|
||||
|
||||
protected Menu menu = new Menu();
|
||||
|
||||
/**
|
||||
* 构造子功能栏抽象类
|
||||
*/
|
||||
protected AbstractFunctionChildrenBox() {
|
||||
functionBox = FunctionBox.getInstance();
|
||||
menuBar = FunctionBox.getMenuBar();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化子功能栏,包括将功能按钮添加到菜单栏中
|
||||
*/
|
||||
public void initialize() {
|
||||
menu.setGraphic(label);
|
||||
menuBar.getMenus().add(menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取功能按钮名称
|
||||
*
|
||||
* @return 功能按钮名称
|
||||
*/
|
||||
protected abstract String getFunctionName();
|
||||
}
|
||||
1
src/main/java/org/jcnc/jnotepad/app/aReadme.md
Normal file
1
src/main/java/org/jcnc/jnotepad/app/aReadme.md
Normal file
@ -0,0 +1 @@
|
||||
app 目录存放应用程序配置、应用程序缓存、通用常量和国际化文件。
|
||||
1
src/main/java/org/jcnc/jnotepad/app/common/aReadme.md
Normal file
1
src/main/java/org/jcnc/jnotepad/app/common/aReadme.md
Normal file
@ -0,0 +1 @@
|
||||
common 目录存放应用程序的通用组卷
|
||||
@ -0,0 +1,69 @@
|
||||
package org.jcnc.jnotepad.app.common.constants;
|
||||
|
||||
import org.jcnc.jnotepad.app.i18n.UiResourceBundle;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 应用常量类,用于存放应用程序中的常量值。
|
||||
*
|
||||
* <p>
|
||||
* 该类包含了应用程序的一些常用常量,如版本号、作者、软件名称、初始宽度和高度、Logo地址等。
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 还包括了与标签页相关的正则表达式模式,以及默认的属性和程序文件目录等常量。
|
||||
* </p>
|
||||
*
|
||||
* @author luke
|
||||
*/
|
||||
public class AppConstants {
|
||||
/**
|
||||
* 版本号
|
||||
*/
|
||||
public static final String VERSION = "1.0.14-alpha";
|
||||
/**
|
||||
* 作者
|
||||
*/
|
||||
public static final String AUTHOR = "JCNC";
|
||||
/**
|
||||
* 软件名称
|
||||
*/
|
||||
public static final String APP_NAME = "JNotepad";
|
||||
/**
|
||||
* 初始宽度
|
||||
*/
|
||||
public static final double SCREEN_WIDTH = 1050;
|
||||
/**
|
||||
* 初始高度
|
||||
*/
|
||||
public static final double SCREEN_LENGTH = 750;
|
||||
/**
|
||||
* logo地址
|
||||
*/
|
||||
public static final String APP_ICON = "/jcnc/app/images/appIcons/icon.png";
|
||||
|
||||
/**
|
||||
* 默认标签页的正则
|
||||
*
|
||||
* <p>
|
||||
* 用于匹配默认标签页名称的正则表达式,格式为"New File"后跟数字。
|
||||
* </p>
|
||||
*/
|
||||
public static final Pattern TABNAME_PATTERN = Pattern.compile("^" + Pattern.quote(UiResourceBundle.getContent(TextConstants.NEW_FILE)) + "\\d+$");
|
||||
|
||||
/**
|
||||
* 默认属性
|
||||
*/
|
||||
public static final String DEFAULT_PROPERTY = "user.home";
|
||||
/**
|
||||
* 程序文件目录
|
||||
*/
|
||||
public static final String PROGRAM_FILE_DIRECTORY = ".jnotepad";
|
||||
|
||||
/**
|
||||
* 私有构造函数,防止该类被实例化。
|
||||
*/
|
||||
private AppConstants() {
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package org.jcnc.jnotepad.app.common.constants;
|
||||
|
||||
/**
|
||||
* SplitPane常量类
|
||||
*
|
||||
* <p>用于记录SplitPane中子组件的索引</p>
|
||||
*
|
||||
* @author cccqyu
|
||||
*/
|
||||
public class SplitPaneItemConstants {
|
||||
|
||||
/**
|
||||
* rootSplitPane中的上部分隔栏索引。
|
||||
*/
|
||||
public static final int ROOT_SPLIT_PANE_TOP_SPLIT_PANE = 0;
|
||||
|
||||
/**
|
||||
* rootSplitPane中的底部指令框索引。
|
||||
*/
|
||||
public static final int ROOT_SPLIT_PANE_CMD_BOX = 1;
|
||||
|
||||
/**
|
||||
* rootSplitPane中的上部面板的左侧索引。
|
||||
*/
|
||||
public static final int TOP_SPLIT_PANE_DIRECTORY_SIDEBAR_PANE = 0;
|
||||
|
||||
/**
|
||||
* rootSplitPane中的上部面板的右侧索引。
|
||||
*/
|
||||
public static final int TOP_SPLIT_PANE_CENTER_TAB_PANE = 1;
|
||||
|
||||
}
|
||||
@ -0,0 +1,264 @@
|
||||
package org.jcnc.jnotepad.app.common.constants;
|
||||
|
||||
/**
|
||||
* 文本常量类,包含多处使用的文本常量。
|
||||
*
|
||||
* <p>如果只有一个类使用常量,请在该类中使用 <code>private static final</code> 声明。</p>
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class TextConstants {
|
||||
|
||||
/**
|
||||
* 标题文本常量
|
||||
*/
|
||||
public static final String TITLE = "title";
|
||||
|
||||
/**
|
||||
* 保存文本常量
|
||||
*/
|
||||
public static final String SAVE = "SAVE";
|
||||
|
||||
/**
|
||||
* 文件文本常量
|
||||
*/
|
||||
public static final String FILE = "FILE";
|
||||
/**
|
||||
* 文件夹
|
||||
*/
|
||||
public static final String FOLDER = "FOLDER";
|
||||
|
||||
/**
|
||||
* 构建文本常量
|
||||
*/
|
||||
public static final String BUILD = "BUILD";
|
||||
|
||||
/**
|
||||
* 终端文本常量
|
||||
*/
|
||||
public static final String TERMINAL = "TERMINAL";
|
||||
|
||||
/**
|
||||
* 运行文本常量
|
||||
*/
|
||||
public static final String RUN = "RUN";
|
||||
|
||||
/**
|
||||
* 调试文本常量
|
||||
*/
|
||||
public static final String DE_BUG = "DE_BUG";
|
||||
|
||||
/**
|
||||
* 新建文本常量
|
||||
*/
|
||||
public static final String NEW = "NEW";
|
||||
|
||||
/**
|
||||
* 打开文本常量
|
||||
*/
|
||||
public static final String OPEN = "OPEN";
|
||||
|
||||
/**
|
||||
* 打开目录文本常量
|
||||
*/
|
||||
public static final String OPEN_DIRECTORY = "OPEN_DIRECTORY";
|
||||
|
||||
/**
|
||||
* 另存为文本常量
|
||||
*/
|
||||
public static final String SAVE_AS = "SAVE_AS";
|
||||
|
||||
/**
|
||||
* 重命名文本常量
|
||||
*/
|
||||
public static final String RENAME = "RENAME";
|
||||
|
||||
/**
|
||||
* 设置文本常量
|
||||
*/
|
||||
public static final String SET = "SET";
|
||||
|
||||
/**
|
||||
* 帮助文本常量
|
||||
*/
|
||||
public static final String HELP = "HELP";
|
||||
|
||||
/**
|
||||
* 自动换行文本常量
|
||||
*/
|
||||
public static final String WORD_WRAP = "WORD_WRAP";
|
||||
|
||||
/**
|
||||
* 插件文本常量
|
||||
*/
|
||||
public static final String PLUGIN = "PLUGIN";
|
||||
|
||||
/**
|
||||
* 管理插件文本常量
|
||||
*/
|
||||
public static final String MANAGER_PLUGIN = "MANAGER_PLUGIN";
|
||||
|
||||
/**
|
||||
* 关于文本常量
|
||||
*/
|
||||
public static final String ABOUT = "ABOUT";
|
||||
|
||||
/**
|
||||
* 开发者文本常量
|
||||
*/
|
||||
public static final String DEVELOPER = "DEVELOPER";
|
||||
|
||||
/**
|
||||
* 统计文本常量
|
||||
*/
|
||||
public static final String STATISTICS = "STATISTICS";
|
||||
|
||||
/**
|
||||
* 打开配置文件文本常量
|
||||
*/
|
||||
public static final String OPEN_CONFIGURATION_FILE = "OPEN_CONFIGURATION_FILE";
|
||||
|
||||
/**
|
||||
* 顶部文本常量
|
||||
*/
|
||||
public static final String TOP = "TOP";
|
||||
|
||||
/**
|
||||
* 语言文本常量
|
||||
*/
|
||||
public static final String LANGUAGE = "LANGUAGE";
|
||||
|
||||
/**
|
||||
* 中文文本常量
|
||||
*/
|
||||
public static final String UPPER_CHINESE = "CHINESE";
|
||||
|
||||
/**
|
||||
* 英文文本常量
|
||||
*/
|
||||
public static final String UPPER_ENGLISH = "ENGLISH";
|
||||
|
||||
/**
|
||||
* 新建文件文本常量
|
||||
*/
|
||||
public static final String NEW_FILE = "NEW_FILE";
|
||||
|
||||
/**
|
||||
* 新建文件夹
|
||||
*/
|
||||
public static final String NEW_DIRECTORY = "NEW_DIRECTORY";
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
public static final String DELETE = "DELETE";
|
||||
|
||||
/**
|
||||
* 行文本常量
|
||||
*/
|
||||
public static final String ROW = "ROW";
|
||||
|
||||
/**
|
||||
* 列文本常量
|
||||
*/
|
||||
public static final String COLUMN = "COLUMN";
|
||||
|
||||
/**
|
||||
* 字数统计文本常量
|
||||
*/
|
||||
public static final String WORD_COUNT = "WORD_COUNT";
|
||||
|
||||
/**
|
||||
* 编码文本常量
|
||||
*/
|
||||
public static final String ENCODE = "ENCODE";
|
||||
|
||||
/**
|
||||
* 英文小写文本常量
|
||||
*/
|
||||
public static final String ENGLISH = "english";
|
||||
|
||||
/**
|
||||
* 中文小写文本常量
|
||||
*/
|
||||
public static final String CHINESE = "chinese";
|
||||
|
||||
/**
|
||||
* 关闭
|
||||
*/
|
||||
public static final String CLOSE = "CLOSE";
|
||||
|
||||
/**
|
||||
* 关闭其他标签页
|
||||
*/
|
||||
public static final String CLOSE_OTHER_TABS = "CLOSE_OTHER_TABS";
|
||||
|
||||
/**
|
||||
* 关闭所有标签页
|
||||
*/
|
||||
public static final String CLOSE_ALL_TABS = "CLOSE_ALL_TABS";
|
||||
|
||||
/**
|
||||
* 关闭左侧标签
|
||||
*/
|
||||
public static final String CLOSE_LEFT_TABS = "CLOSE_LEFT_TABS";
|
||||
|
||||
/**
|
||||
* 关闭右侧标签
|
||||
*/
|
||||
public static final String CLOSE_RIGHT_TABS = "CLOSE_RIGHT_TABS";
|
||||
|
||||
/**
|
||||
* 复制
|
||||
*/
|
||||
public static final String COPY = "COPY";
|
||||
|
||||
/**
|
||||
* 粘贴
|
||||
*/
|
||||
public static final String PASTE = "PASTE";
|
||||
|
||||
/**
|
||||
* 剪切
|
||||
*/
|
||||
public static final String SHEAR = "SHEAR";
|
||||
|
||||
/**
|
||||
* 文件名
|
||||
*/
|
||||
public static final String FILE_NAME = "FILE_NAME";
|
||||
|
||||
/**
|
||||
* 文件路径
|
||||
*/
|
||||
public static final String FILE_PATH = "FILE_PATH";
|
||||
|
||||
|
||||
/**
|
||||
* 所在文件夹
|
||||
*/
|
||||
public static final String FOLDER_PATH = "FOLDER_PATH";
|
||||
/**
|
||||
* 固定标签页
|
||||
*/
|
||||
public static final String FIXED_TAB = "FIXED_TAB";
|
||||
/**
|
||||
* 只读
|
||||
*/
|
||||
public static final String READ_ONLY = "READ_ONLY";
|
||||
|
||||
public static final String SEPARATOR = "separator";
|
||||
|
||||
/**
|
||||
* 打开于
|
||||
*/
|
||||
public static final String OPEN_ON = "OPEN_ON";
|
||||
/**
|
||||
* 资源管理器
|
||||
*/
|
||||
public static final String EXPLORER = "EXPLORER";
|
||||
|
||||
private TextConstants() {
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package org.jcnc.jnotepad.app.common.manager;
|
||||
|
||||
import org.jcnc.jnotepad.api.core.manager.AbstractCacheManager;
|
||||
|
||||
/**
|
||||
* 应用程序缓存管理类。
|
||||
* <p>
|
||||
* 该类用于管理应用程序中的缓存数据。
|
||||
* </p>
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class ApplicationCacheManager extends AbstractCacheManager {
|
||||
|
||||
private static final ApplicationCacheManager INSTANCE = new ApplicationCacheManager();
|
||||
|
||||
private ApplicationCacheManager() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 ApplicationCacheManager 的实例。
|
||||
*
|
||||
* @return ApplicationCacheManager 实例
|
||||
*/
|
||||
public static ApplicationCacheManager getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全局命名空间。
|
||||
*
|
||||
* @return 全局命名空间字符串
|
||||
*/
|
||||
@Override
|
||||
public String getGlobalNamespace() {
|
||||
return "jcnc";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,91 @@
|
||||
package org.jcnc.jnotepad.app.common.manager;
|
||||
|
||||
import org.jcnc.jnotepad.app.utils.LoggerUtil;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* 线程池管理类。
|
||||
* <p>
|
||||
* 该类用于管理应用程序中的线程池,提供了异步操作的执行环境。
|
||||
* </p>
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class ThreadPoolManager {
|
||||
private static final Logger logger = LoggerUtil.getLogger(ThreadPoolManager.class);
|
||||
/**
|
||||
* 核心线程数
|
||||
*/
|
||||
private static final int CORE_POOL_SIZE = 2;
|
||||
/**
|
||||
* 最大线程数
|
||||
*/
|
||||
private static final int MAXIMUM_POOL_SIZE = 4;
|
||||
/**
|
||||
* 空闲线程回收时间间隔
|
||||
*/
|
||||
private static final Long KEEP_ALIVE_TIME = 3L;
|
||||
/**
|
||||
* 空闲线程回收时间间隔单位
|
||||
*/
|
||||
private static final TimeUnit TIME_UNIT = TimeUnit.SECONDS;
|
||||
/**
|
||||
* 提交任务的队列<br>
|
||||
* 注:<br>
|
||||
* ArrayBlockingQueue:有界带缓冲阻塞队列<br>
|
||||
* SynchronousQueue:无缓冲阻塞队列<br>
|
||||
* LinkedBlockingQueue:无界带缓冲阻塞队列
|
||||
*/
|
||||
private static final BlockingQueue<Runnable> BLOCKING_QUEUE = new LinkedBlockingQueue<>(4);
|
||||
/**
|
||||
* 当前运行线程数
|
||||
*/
|
||||
private static final AtomicInteger THREAD_COUNT = new AtomicInteger(1);
|
||||
/**
|
||||
* 线程工厂
|
||||
*/
|
||||
private static final ThreadFactory THREAD_FACTORY = r -> {
|
||||
Thread thread = new Thread(r);
|
||||
thread.setName("JNotepad-ThreadPool-Thread-" + THREAD_COUNT.getAndIncrement());
|
||||
thread.setDaemon(true);
|
||||
// 设置异常处理器
|
||||
thread.setUncaughtExceptionHandler((t, e) -> logger.error("线程执行异常!", e));
|
||||
return thread;
|
||||
};
|
||||
/**
|
||||
* 拒绝处理任务时的策略
|
||||
*/
|
||||
private static final RejectedExecutionHandler HANDLER = new ThreadPoolExecutor.AbortPolicy();
|
||||
/**
|
||||
* 线程池
|
||||
*/
|
||||
private static final ThreadPoolExecutor THREAD_POOL = new ThreadPoolExecutor(
|
||||
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_TIME, TIME_UNIT, BLOCKING_QUEUE, THREAD_FACTORY, HANDLER);
|
||||
|
||||
private ThreadPoolManager() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前运行线程数自减。
|
||||
* <p>
|
||||
* 当你创建任务时,务必在最后执行一次该方法。
|
||||
* </p>
|
||||
*
|
||||
* @see ThreadPoolManager
|
||||
*/
|
||||
public static void threadContSelfSubtracting() {
|
||||
THREAD_COUNT.decrementAndGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取线程池实例。
|
||||
*
|
||||
* @return 线程池实例
|
||||
*/
|
||||
public static ExecutorService getThreadPool() {
|
||||
return THREAD_POOL;
|
||||
}
|
||||
}
|
||||
108
src/main/java/org/jcnc/jnotepad/app/config/AppConfig.java
Normal file
108
src/main/java/org/jcnc/jnotepad/app/config/AppConfig.java
Normal file
@ -0,0 +1,108 @@
|
||||
package org.jcnc.jnotepad.app.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.jcnc.jnotepad.app.common.constants.AppConstants.DEFAULT_PROPERTY;
|
||||
import static org.jcnc.jnotepad.app.common.constants.AppConstants.PROGRAM_FILE_DIRECTORY;
|
||||
|
||||
/**
|
||||
* 应用程序配置文件
|
||||
*
|
||||
* <p>
|
||||
* 此类用于存储应用程序的配置信息,包括程序根路径、排除的文件夹和文件等。
|
||||
* </p>
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class AppConfig {
|
||||
/**
|
||||
* 排除的文件夹
|
||||
*/
|
||||
@JsonIgnore
|
||||
private final Set<File> ignoreFolder;
|
||||
/**
|
||||
* 排除的文件
|
||||
*/
|
||||
@JsonIgnore
|
||||
private final Set<File> ignoreFile;
|
||||
/**
|
||||
* 程序根路径
|
||||
*/
|
||||
private String rootPath;
|
||||
/**
|
||||
* 上次的程序根路径
|
||||
*/
|
||||
@JsonIgnore
|
||||
private String lastRootPath;
|
||||
|
||||
/**
|
||||
* 构造应用程序配置对象
|
||||
*/
|
||||
public AppConfig() {
|
||||
ignoreFolder = Set.of(
|
||||
new File(Paths.get(System.getProperty(DEFAULT_PROPERTY), PROGRAM_FILE_DIRECTORY, "system").toString()),
|
||||
new File(Paths.get(System.getProperty(DEFAULT_PROPERTY), PROGRAM_FILE_DIRECTORY, "logs").toString())
|
||||
);
|
||||
ignoreFile = Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取程序根路径
|
||||
*
|
||||
* @return 程序根路径
|
||||
*/
|
||||
public String getRootPath() {
|
||||
return Optional.ofNullable(rootPath).orElse(System.getProperty(DEFAULT_PROPERTY));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置程序根路径
|
||||
*
|
||||
* @param rootPath 程序根路径
|
||||
*/
|
||||
public void setRootPath(String rootPath) {
|
||||
this.rootPath = rootPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上次的程序根路径
|
||||
*
|
||||
* @return 上次的程序根路径
|
||||
*/
|
||||
public String getLastRootPath() {
|
||||
return lastRootPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置上次的程序根路径
|
||||
*
|
||||
* @param lastRootPath 上次的程序根路径
|
||||
*/
|
||||
public void setLastRootPath(String lastRootPath) {
|
||||
this.lastRootPath = lastRootPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取排除的文件夹集合
|
||||
*
|
||||
* @return 排除的文件夹集合
|
||||
*/
|
||||
public Set<File> getIgnoreFolder() {
|
||||
return ignoreFolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取排除的文件集合
|
||||
*
|
||||
* @return 排除的文件集合
|
||||
*/
|
||||
public Set<File> getIgnoreFile() {
|
||||
return ignoreFile;
|
||||
}
|
||||
}
|
||||
36
src/main/java/org/jcnc/jnotepad/app/config/PluginConfig.java
Normal file
36
src/main/java/org/jcnc/jnotepad/app/config/PluginConfig.java
Normal file
@ -0,0 +1,36 @@
|
||||
package org.jcnc.jnotepad.app.config;
|
||||
|
||||
import org.jcnc.jnotepad.model.entity.PluginDescriptor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 插件配置文件
|
||||
*
|
||||
* <p>
|
||||
* 此类用于存储插件的配置信息,包括插件描述符的列表。
|
||||
* </p>
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class PluginConfig {
|
||||
private List<PluginDescriptor> plugins;
|
||||
|
||||
/**
|
||||
* 获取插件描述符列表
|
||||
*
|
||||
* @return 插件描述符列表
|
||||
*/
|
||||
public List<PluginDescriptor> getPlugins() {
|
||||
return plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置插件描述符列表
|
||||
*
|
||||
* @param plugins 插件描述符列表
|
||||
*/
|
||||
public void setPlugins(List<PluginDescriptor> plugins) {
|
||||
this.plugins = plugins;
|
||||
}
|
||||
}
|
||||
77
src/main/java/org/jcnc/jnotepad/app/config/UserConfig.java
Normal file
77
src/main/java/org/jcnc/jnotepad/app/config/UserConfig.java
Normal file
@ -0,0 +1,77 @@
|
||||
package org.jcnc.jnotepad.app.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import org.jcnc.jnotepad.model.entity.ShortcutKey;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户配置文件类
|
||||
*
|
||||
* <p>
|
||||
* 此类用于存储用户的配置信息,包括语言设置、文本自动换行设置和快捷键配置。
|
||||
* </p>
|
||||
*
|
||||
* @author luke
|
||||
*/
|
||||
public class UserConfig {
|
||||
|
||||
private String language;
|
||||
@JsonIgnore
|
||||
private boolean textWrap;
|
||||
private List<ShortcutKey> shortcutKey;
|
||||
|
||||
/**
|
||||
* 获取语言设置
|
||||
*
|
||||
* @return 语言设置
|
||||
*/
|
||||
public String getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置语言设置
|
||||
*
|
||||
* @param language 语言设置
|
||||
*/
|
||||
public void setLanguage(String language) {
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取快捷键配置列表
|
||||
*
|
||||
* @return 快捷键配置列表
|
||||
*/
|
||||
public List<ShortcutKey> getShortcutKey() {
|
||||
return shortcutKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置快捷键配置列表
|
||||
*
|
||||
* @param shortcutKey 快捷键配置列表
|
||||
*/
|
||||
public void setShortcutKey(List<ShortcutKey> shortcutKey) {
|
||||
this.shortcutKey = shortcutKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文本自动换行设置
|
||||
*
|
||||
* @return 是否启用文本自动换行
|
||||
*/
|
||||
public boolean isTextWrap() {
|
||||
return textWrap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文本自动换行设置
|
||||
*
|
||||
* @param textWrap 是否启用文本自动换行
|
||||
*/
|
||||
public void setTextWrap(boolean textWrap) {
|
||||
this.textWrap = textWrap;
|
||||
}
|
||||
}
|
||||
136
src/main/java/org/jcnc/jnotepad/app/i18n/UiResourceBundle.java
Normal file
136
src/main/java/org/jcnc/jnotepad/app/i18n/UiResourceBundle.java
Normal file
@ -0,0 +1,136 @@
|
||||
package org.jcnc.jnotepad.app.i18n;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
/**
|
||||
* UI资源绑定,用于加载语言文件。
|
||||
*
|
||||
* <p>
|
||||
* 此类用于加载和管理UI资源文件,支持国际化和多语言功能。可以通过绑定StringProperty和键值对应的内容,以及获取当前资源文件的内容。
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 该类是一个单例类,通过getInstance方法获取实例。
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 使用方法示例:
|
||||
* <code>
|
||||
* UiResourceBundle.bindStringProperty(stringProperty, "key");
|
||||
* String content = UiResourceBundle.getContent("key");
|
||||
* </code>
|
||||
* </p>
|
||||
*
|
||||
* @author songdragon
|
||||
*/
|
||||
public class UiResourceBundle {
|
||||
|
||||
private static final UiResourceBundle INSTANCE = new UiResourceBundle();
|
||||
/**
|
||||
* resource目录下的i18n/i18nXXX.properties
|
||||
*/
|
||||
private static final String BASENAME = "jcnc/app/i18n/i18n";
|
||||
/**
|
||||
* 资源文件的观察者绑定。
|
||||
*/
|
||||
private final ObjectProperty<ResourceBundle> resources = new SimpleObjectProperty<>();
|
||||
/**
|
||||
* 当前语言
|
||||
*/
|
||||
private Locale currentLocale;
|
||||
|
||||
private UiResourceBundle() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取UiResourceBundle的单例实例
|
||||
*
|
||||
* @return UiResourceBundle的单例实例
|
||||
*/
|
||||
public static UiResourceBundle getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具方法:绑定StringProperty和Key对应的内容
|
||||
*
|
||||
* @param stringProperty 字符串属性
|
||||
* @param key 键值
|
||||
*/
|
||||
public static void bindStringProperty(StringProperty stringProperty, String key) {
|
||||
if (stringProperty == null) {
|
||||
return;
|
||||
}
|
||||
stringProperty.bind(getInstance().getStringBinding(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前资源中的key值
|
||||
*
|
||||
* @param key 资源所对应键
|
||||
* @return 当前键所对应的值
|
||||
*/
|
||||
public static String getContent(String key) {
|
||||
return INSTANCE.getResources().getString(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前资源文件
|
||||
*
|
||||
* @return 资源文件
|
||||
*/
|
||||
public ObjectProperty<ResourceBundle> resourcesProperty() {
|
||||
return resources;
|
||||
}
|
||||
|
||||
public final ResourceBundle getResources() {
|
||||
return resourcesProperty().get();
|
||||
}
|
||||
|
||||
public final void setResources(ResourceBundle resources) {
|
||||
resourcesProperty().set(resources);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置当前local
|
||||
*
|
||||
* @param toLocal 要设置的新的Locale
|
||||
*/
|
||||
public final void resetLocal(Locale toLocal) {
|
||||
if (this.currentLocale == toLocal) {
|
||||
return;
|
||||
}
|
||||
this.currentLocale = toLocal;
|
||||
ResourceBundle resourceBundle = ResourceBundle.getBundle(BASENAME, currentLocale);
|
||||
this.setResources(resourceBundle);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取key对应的绑定属性内容
|
||||
*
|
||||
* @param key key
|
||||
* @return key对应的内容
|
||||
*/
|
||||
public StringBinding getStringBinding(String key) {
|
||||
return Bindings.createStringBinding(() -> getResources().getString(key), resourcesProperty());
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册资源变更监听器
|
||||
*
|
||||
* @param listener 变更监听器
|
||||
*/
|
||||
public void addListener(ChangeListener<? super ResourceBundle> listener) {
|
||||
this.resources.addListener(listener);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,328 @@
|
||||
package org.jcnc.jnotepad.app.manager;
|
||||
|
||||
import atlantafx.base.theme.PrimerLight;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
import org.jcnc.jnotepad.JnotepadApp;
|
||||
import org.jcnc.jnotepad.app.common.constants.AppConstants;
|
||||
import org.jcnc.jnotepad.app.common.constants.TextConstants;
|
||||
import org.jcnc.jnotepad.app.common.manager.ThreadPoolManager;
|
||||
import org.jcnc.jnotepad.app.config.AppConfig;
|
||||
import org.jcnc.jnotepad.app.i18n.UiResourceBundle;
|
||||
import org.jcnc.jnotepad.app.utils.FileUtil;
|
||||
import org.jcnc.jnotepad.app.utils.LoggerUtil;
|
||||
import org.jcnc.jnotepad.app.utils.UiUtil;
|
||||
import org.jcnc.jnotepad.controller.ResourceController;
|
||||
import org.jcnc.jnotepad.controller.cache.CacheController;
|
||||
import org.jcnc.jnotepad.controller.config.AppConfigController;
|
||||
import org.jcnc.jnotepad.controller.config.PluginConfigController;
|
||||
import org.jcnc.jnotepad.controller.exception.AppException;
|
||||
import org.jcnc.jnotepad.controller.manager.Controller;
|
||||
import org.jcnc.jnotepad.controller.plugin.manager.PluginManager;
|
||||
import org.jcnc.jnotepad.ui.views.manager.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import static org.jcnc.jnotepad.app.common.constants.AppConstants.DEFAULT_PROPERTY;
|
||||
import static org.jcnc.jnotepad.app.common.constants.AppConstants.PROGRAM_FILE_DIRECTORY;
|
||||
|
||||
/**
|
||||
* 应用程序管理类
|
||||
*
|
||||
* <p>
|
||||
* 此类负责管理应用程序的生命周期和操作。它包括初始化应用程序、执行默认操作、加载缓存、加载资源、迁移程序根文件夹、停止前操作等功能。
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 该类是一个单例类,通过getInstance方法获取实例。
|
||||
* </p>
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class ApplicationManager {
|
||||
private static final ApplicationManager INSTANCE = new ApplicationManager();
|
||||
/**
|
||||
* 线程池
|
||||
*/
|
||||
private final ExecutorService threadPool = ThreadPoolManager.getThreadPool();
|
||||
private Pane root = new Pane();
|
||||
private Scene scene;
|
||||
private Stage primaryStage;
|
||||
private Application application;
|
||||
|
||||
private ApplicationManager() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取ApplicationManager的单例实例
|
||||
*
|
||||
* @return ApplicationManager的单例实例
|
||||
*/
|
||||
public static ApplicationManager getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化应用程序
|
||||
*
|
||||
* <p>
|
||||
* 此方法用于初始化应用程序的各个组件,包括设置应用程序主题、初始化UI组件、初始化插件、初始化顶部菜单栏、初始化侧边工具栏、初始化下方状态栏、初始化标签页布局等。
|
||||
* </p>
|
||||
*/
|
||||
public void initializeApp() {
|
||||
// 设置应用程序主题
|
||||
Application.setUserAgentStylesheet(new PrimerLight().getUserAgentStylesheet());
|
||||
// 初始化scene
|
||||
initScene();
|
||||
// 初始化插件
|
||||
PluginManager.getInstance().initPlugins();
|
||||
// 初始化顶部菜单栏
|
||||
TopMenuBarManager.getInstance().initTopMenuBar();
|
||||
// 初始化侧边工具栏
|
||||
SidebarToolBarManager.getInstance().initSidebarToolBar();
|
||||
// 初始化下方状态栏
|
||||
BottomStatusBoxManager.getInstance().initStatusBox();
|
||||
// 初始标签页布局组件
|
||||
CenterTabPaneManager.getInstance().initCenterTabPane();
|
||||
// 初始化应用布局
|
||||
initAppLayout();
|
||||
// 初始化primaryStage
|
||||
initPrimaryStage();
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行默认操作
|
||||
*
|
||||
* <p>
|
||||
* 此方法用于执行应用程序的默认操作,例如根据参数打开关联文件并创建文本区域,加载已打开的文件夹等。
|
||||
* </p>
|
||||
*/
|
||||
public void executeDefaultAction() {
|
||||
// 使用加载关联文件并创建文本区域
|
||||
List<String> rawParameters = application.getParameters().getRaw();
|
||||
Controller.getInstance().openAssociatedFileAndCreateTextArea(rawParameters);
|
||||
// 加载已打开的文件夹
|
||||
DirectorySidebarManager.getInstance().expandTheOpenFileTree();
|
||||
}
|
||||
|
||||
private void initScene() {
|
||||
// 初始化scene
|
||||
double width = AppConstants.SCREEN_WIDTH;
|
||||
double length = AppConstants.SCREEN_LENGTH;
|
||||
scene = new Scene(root, width, length);
|
||||
scene.getStylesheets().add(Objects.requireNonNull(application.getClass().getResource("/jcnc/app/css/styles.css")).toExternalForm());
|
||||
}
|
||||
|
||||
private void initPrimaryStage() {
|
||||
primaryStage.setScene(scene);
|
||||
primaryStage.setWidth(scene.getWidth());
|
||||
primaryStage.setHeight(scene.getHeight());
|
||||
primaryStage.getIcons().add(UiUtil.getAppIcon());
|
||||
primaryStage.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (Boolean.TRUE.equals(newValue)) {
|
||||
CenterTabPaneManager instance = CenterTabPaneManager.getInstance();
|
||||
instance.checkFileTabStatus(instance.getSelected());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载缓存
|
||||
*
|
||||
* <p>
|
||||
* 此方法用于加载应用程序的缓存。
|
||||
* </p>
|
||||
*/
|
||||
public void loadAppCache() {
|
||||
// 加载缓存
|
||||
CacheController.getInstance().loadCaches();
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载资源
|
||||
*
|
||||
* <p>
|
||||
* 此方法用于加载应用程序的资源,包括加载资源文件和绑定快捷键。
|
||||
* </p>
|
||||
*/
|
||||
public void loadAppResources() {
|
||||
// 加载资源
|
||||
ResourceController.getInstance().loadResources();
|
||||
// 绑定快捷键
|
||||
UiResourceBundle.bindStringProperty(primaryStage.titleProperty(), TextConstants.TITLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 迁移程序根文件夹
|
||||
*
|
||||
* <p>
|
||||
* 此方法用于迁移应用程序的根文件夹,将根文件夹从之前的位置迁移到新的位置。
|
||||
* </p>
|
||||
*/
|
||||
public void migrateFileRootFolder() {
|
||||
AppConfig config = AppConfigController.getInstance().getConfig();
|
||||
String lastRootPath = config.getLastRootPath();
|
||||
if (lastRootPath == null) {
|
||||
return;
|
||||
}
|
||||
// 获取源文件夹
|
||||
File sourceFolder = new File(lastRootPath, PROGRAM_FILE_DIRECTORY);
|
||||
// 获取目标文件夹
|
||||
File targetFolder = new File(config.getRootPath(), PROGRAM_FILE_DIRECTORY);
|
||||
// 设置忽略文件夹
|
||||
Set<File> ignoredFolders = config.getIgnoreFolder();
|
||||
// 设置忽略文件
|
||||
Set<File> ignoredFiles = config.getIgnoreFile();
|
||||
// 移动文件夹
|
||||
FileUtil.migrateFolder(sourceFolder, targetFolder, ignoredFolders, ignoredFiles);
|
||||
// 删除.jnotepad
|
||||
if (!sourceFolder.equals(new File(Paths.get(System.getProperty(DEFAULT_PROPERTY), PROGRAM_FILE_DIRECTORY).toString()))) {
|
||||
try {
|
||||
Files.delete(sourceFolder.toPath());
|
||||
} catch (IOException e) {
|
||||
throw new AppException(e);
|
||||
}
|
||||
}
|
||||
// 保存新配置
|
||||
AppConfigController.getInstance().writeConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止前操作
|
||||
*
|
||||
* <p>
|
||||
* 在停止应用程序之前,执行一系列操作,包括刷新插件配置、销毁插件、保存已打开的文件标签页、将缓存写入本地、迁移程序根文件夹、关闭线程池等。
|
||||
* </p>
|
||||
*/
|
||||
public void operationBeforeStopping() {
|
||||
PluginConfigController pluginConfigController = PluginConfigController.getInstance();
|
||||
// 刷新插件配置文件
|
||||
pluginConfigController.getConfig().setPlugins(PluginManager.getInstance().getPluginDescriptors());
|
||||
pluginConfigController.writeConfig();
|
||||
|
||||
// 销毁插件可能申请的资源
|
||||
PluginManager.getInstance().destroyPlugins();
|
||||
// 保存已打开的文件标签页
|
||||
CenterTabPaneManager.getInstance().saveOpenFileTabs();
|
||||
// 将缓存写入本地
|
||||
CacheController.getInstance().writeCaches();
|
||||
// 迁移文件夹
|
||||
migrateFileRootFolder();
|
||||
// 关闭线程池
|
||||
threadPool.shutdownNow();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前窗口
|
||||
*
|
||||
* @return 当前窗口
|
||||
*/
|
||||
public Window getWindow() {
|
||||
return scene.getWindow();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前窗口的场景
|
||||
*
|
||||
* @return 当前窗口的场景
|
||||
*/
|
||||
public Scene getScene() {
|
||||
return scene;
|
||||
}
|
||||
|
||||
public void setScene(Scene scene) {
|
||||
this.scene = scene;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载程序布局
|
||||
*
|
||||
* <p>
|
||||
* 此方法用于加载应用程序的布局,包括根布局容器、底部根侧边栏垂直布局、主界面边界布局、顶部边界面板、右侧边栏垂直布局、根布局等组件。
|
||||
* </p>
|
||||
*/
|
||||
public void initAppLayout() {
|
||||
// 加载根布局容器
|
||||
RootManager rootManager = RootManager.getInstance(scene);
|
||||
rootManager.initScreen(scene);
|
||||
// 初始化底部根侧边栏垂直布局
|
||||
RootBottomSideBarVerticalBoxManager.getInstance().initSidebarVerticalBox();
|
||||
// 初始化主界面边界布局
|
||||
MainBorderPaneManager.getInstance().initMainBorderPane();
|
||||
// 初始化顶部边界面板
|
||||
RootTopBorderPaneManager.getInstance().initRootBorderPane();
|
||||
// 初始化右侧边栏垂直布局
|
||||
RootRightSideBarVerticalBoxManager.getInstance().initRootRightSideBarVerticalBox();
|
||||
// 初始化根布局
|
||||
RootBorderPaneManager.getInstance().initRootBorderPane();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重启应用程序
|
||||
*
|
||||
* <p>
|
||||
* 此方法用于重启当前的Java应用程序。
|
||||
* </p>
|
||||
*/
|
||||
public void restart() {
|
||||
try {
|
||||
// 获取当前Java应用程序的命令
|
||||
String javaCommand = System.getProperty("java.home") + "/bin/java";
|
||||
String mainClass = JnotepadApp.class.getName();
|
||||
|
||||
// 构建新进程来重新启动应用程序
|
||||
ProcessBuilder builder = new ProcessBuilder(javaCommand, "-cp", System.getProperty("java.class.path"), mainClass);
|
||||
builder.start();
|
||||
// 关闭当前应用程序
|
||||
stop();
|
||||
} catch (IOException e) {
|
||||
LoggerUtil.getLogger("正在重启当前应用程序".getClass());
|
||||
}
|
||||
}
|
||||
|
||||
public Pane getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
public void setRoot(Pane root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
public Application getApplication() {
|
||||
return application;
|
||||
}
|
||||
|
||||
public void setApplication(Application application) {
|
||||
this.application = application;
|
||||
}
|
||||
|
||||
public Stage getPrimaryStage() {
|
||||
return primaryStage;
|
||||
}
|
||||
|
||||
public void setPrimaryStage(Stage primaryStage) {
|
||||
this.primaryStage = primaryStage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止应用程序
|
||||
*
|
||||
* <p>
|
||||
* 此方法用于停止应用程序。
|
||||
* </p>
|
||||
*/
|
||||
public void stop() {
|
||||
Platform.exit();
|
||||
}
|
||||
}
|
||||
42
src/main/java/org/jcnc/jnotepad/app/utils/ClipboardUtil.java
Normal file
42
src/main/java/org/jcnc/jnotepad/app/utils/ClipboardUtil.java
Normal file
@ -0,0 +1,42 @@
|
||||
package org.jcnc.jnotepad.app.utils;
|
||||
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.ClipboardContent;
|
||||
|
||||
/**
|
||||
* 剪切板工具
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class ClipboardUtil {
|
||||
/**
|
||||
* 系统剪切板对象
|
||||
*/
|
||||
private static final Clipboard CLIPBOARD = Clipboard.getSystemClipboard();
|
||||
|
||||
private ClipboardUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the provided text to the system clipboard.
|
||||
*
|
||||
* @param text the text to be written to the clipboard
|
||||
*/
|
||||
public static void writeTextToClipboard(String text) {
|
||||
ClipboardContent content = new ClipboardContent();
|
||||
content.putString(text);
|
||||
CLIPBOARD.setContent(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads text from the clipboard.
|
||||
*
|
||||
* @return the text read from the clipboard
|
||||
*/
|
||||
public static String readTextFromClipboard() {
|
||||
String text = CLIPBOARD.getString();
|
||||
LoggerUtil.getLogger(ClipboardUtil.class).info("剪切板内容:{}", text);
|
||||
return text;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
package org.jcnc.jnotepad.app.utils;
|
||||
|
||||
import com.ibm.icu.text.CharsetDetector;
|
||||
import com.ibm.icu.text.CharsetMatch;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* 编码检测工具类
|
||||
*
|
||||
* <p>该工具类用于检测文本文件的编码类型。</p>
|
||||
*
|
||||
* @author 许轲
|
||||
*/
|
||||
public class EncodingDetector {
|
||||
/**
|
||||
* 编码侦测概率阈值,50%
|
||||
*/
|
||||
public static final int THRESHOLD_CONFIDENCE = 50;
|
||||
private static final Logger LOG = LoggerUtil.getLogger(EncodingDetector.class);
|
||||
|
||||
private EncodingDetector() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测文本编码。
|
||||
*
|
||||
* @param file 要检测的文件
|
||||
* @return 字符串表示的编码,如果检测失败则返回 "UNKNOWN"
|
||||
*/
|
||||
public static String detectEncoding(File file) {
|
||||
CharsetDetector charsetDetector = new CharsetDetector();
|
||||
try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file.getPath()))) {
|
||||
charsetDetector.setText(inputStream);
|
||||
CharsetMatch[] matchList = charsetDetector.detectAll();
|
||||
if (matchList == null || matchList.length == 0) {
|
||||
return null;
|
||||
}
|
||||
CharsetMatch maxConfidence = matchList[0];
|
||||
if (maxConfidence.getConfidence() < THRESHOLD_CONFIDENCE) {
|
||||
return null;
|
||||
}
|
||||
for (int i = 1; i < matchList.length; i++) {
|
||||
CharsetMatch match = matchList[i];
|
||||
LOG.debug("{} : {}", match.getName(), match.getConfidence());
|
||||
if (match.getConfidence() >= THRESHOLD_CONFIDENCE && match.getConfidence() >= maxConfidence.getConfidence()) {
|
||||
maxConfidence = match;
|
||||
} else {
|
||||
return maxConfidence.getName();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测文本编码。
|
||||
*
|
||||
* @param file 文件
|
||||
* @return Charset编码
|
||||
*/
|
||||
public static Charset detectEncodingCharset(File file) {
|
||||
String charset = detectEncoding(file);
|
||||
try {
|
||||
// 断言charset != null
|
||||
assert charset != null;
|
||||
return Charset.forName(charset);
|
||||
} catch (Exception e) {
|
||||
return Charset.defaultCharset();
|
||||
}
|
||||
}
|
||||
}
|
||||
434
src/main/java/org/jcnc/jnotepad/app/utils/FileUtil.java
Normal file
434
src/main/java/org/jcnc/jnotepad/app/utils/FileUtil.java
Normal file
@ -0,0 +1,434 @@
|
||||
package org.jcnc.jnotepad.app.utils;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.image.ImageView;
|
||||
import org.jcnc.jnotepad.controller.event.handler.menuitem.OpenFile;
|
||||
import org.jcnc.jnotepad.controller.exception.AppException;
|
||||
import org.jcnc.jnotepad.model.entity.DirFileModel;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
import static org.kordamp.ikonli.antdesignicons.AntDesignIconsFilled.*;
|
||||
|
||||
/**
|
||||
* 文件工具
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class FileUtil {
|
||||
private static final MessageDigest MESSAGE_DIGEST_SHA_256;
|
||||
private static final int BUFFER_SIZE = 8192;
|
||||
private static final Logger logger = LoggerUtil.getLogger(FileUtil.class);
|
||||
|
||||
private static final String WINDOWS = "win";
|
||||
|
||||
private static final String MAC = "mac";
|
||||
|
||||
private static final String PATH = "path";
|
||||
|
||||
static {
|
||||
try {
|
||||
MESSAGE_DIGEST_SHA_256 = MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AppException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private FileUtil() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字节数组转换为String类型哈希值
|
||||
*
|
||||
* @param bytes 字节数组
|
||||
* @return 哈希值
|
||||
*/
|
||||
private static String bytes2HashCode(byte[] bytes) {
|
||||
StringBuilder hashString = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
hashString.append(String.format("%02x", b));
|
||||
}
|
||||
return hashString.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取本地文件Sha256哈希值字符串
|
||||
*
|
||||
* @param file 本地文件
|
||||
* @return 本地文件Sha256哈希值
|
||||
*/
|
||||
public static String getLocalFileSha256HashString(File file) {
|
||||
try (
|
||||
// 获取文件输入流
|
||||
FileInputStream fileInputStream = new FileInputStream(file);
|
||||
// 获取字节流通道
|
||||
FileChannel channel = fileInputStream.getChannel()
|
||||
) {
|
||||
// 设置8k缓冲区
|
||||
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
|
||||
while (channel.read(buffer) != -1) {
|
||||
buffer.flip();
|
||||
MESSAGE_DIGEST_SHA_256.update(buffer);
|
||||
buffer.clear();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AppException(e);
|
||||
}
|
||||
return bytes2HashCode(MESSAGE_DIGEST_SHA_256.digest());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本地文件Sha256哈希值字符串
|
||||
*
|
||||
* @param pathStr 本地文件路径字符串
|
||||
* @return 本地文件Sha256哈希值
|
||||
*/
|
||||
public static String getLocalFileSha256HashString(String pathStr) {
|
||||
return getLocalFileSha256HashString(new File(pathStr));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本地文件Sha256哈希值字符串
|
||||
*
|
||||
* @param path 本地文件路径
|
||||
* @return 本地文件Sha256哈希值
|
||||
*/
|
||||
public static String getLocalFileSha256HashString(Path path) {
|
||||
return getLocalFileSha256HashString(path.toFile());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件中的文本内容。
|
||||
*
|
||||
* @param file 文件对象
|
||||
* @return 文本内容
|
||||
*/
|
||||
public static String getFileText(File file) {
|
||||
return getFileText(file, EncodingDetector.detectEncodingCharset(file));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取文件中的文本内容。
|
||||
*
|
||||
* @param file 文件对象
|
||||
* @param encoding 文件编码
|
||||
* @return 文本内容
|
||||
*/
|
||||
public static String getFileText(File file, Charset encoding) {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(file, encoding))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (!stringBuilder.isEmpty()) {
|
||||
stringBuilder.append("\n");
|
||||
}
|
||||
stringBuilder.append(line);
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
LoggerUtil.getLogger(OpenFile.class).info("已忽视IO异常!");
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将文件夹转为DirFileModel
|
||||
*
|
||||
* @param file 文件
|
||||
* @return DirFileModel 存储文件夹与文件关系的实体类
|
||||
*/
|
||||
public static DirFileModel getDirFileModel(File file) {
|
||||
if (!file.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
DirFileModel dirFileModel = new DirFileModel(
|
||||
file.getAbsolutePath(),
|
||||
file.getName(), new ArrayList<>(),
|
||||
new FontIcon(FOLDER),
|
||||
new FontIcon(FOLDER_OPEN));
|
||||
|
||||
File[] files = file.listFiles();
|
||||
if (files != null) {
|
||||
for (File f : files) {
|
||||
if (f.isDirectory()) {
|
||||
DirFileModel childDirFileModel = getDirFileModel(f);
|
||||
dirFileModel.getChildFile().add(childDirFileModel);
|
||||
} else {
|
||||
// 在此监测文件后缀,设置对应的图标
|
||||
dirFileModel.getChildFile().add(new DirFileModel(
|
||||
f.getAbsolutePath(), f.getName(), null,
|
||||
getIconCorrespondingToFileName(f.getName()),
|
||||
null));
|
||||
}
|
||||
}
|
||||
}
|
||||
return dirFileModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a DirFileModel object based on the given dirFileModels map.
|
||||
*
|
||||
* @param dirFileModels a map containing the dirFileModels data
|
||||
* @return the DirFileModel object
|
||||
*/
|
||||
public static DirFileModel getDirFileModel(Map<String, Object> dirFileModels) {
|
||||
if (Objects.isNull(dirFileModels) || dirFileModels.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
File rootDir = new File((String) dirFileModels.get(PATH));
|
||||
DirFileModel dirFileModel = new DirFileModel(
|
||||
rootDir.getAbsolutePath(),
|
||||
rootDir.getName(), new ArrayList<>(),
|
||||
new FontIcon(FOLDER),
|
||||
new FontIcon(FOLDER_OPEN), (Boolean) dirFileModels.get("open"));
|
||||
Optional<Object> o = Optional.ofNullable(dirFileModels.get("childFile"));
|
||||
if (o.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
List<Map<String, Object>> childFile = (List<Map<String, Object>>) o.get();
|
||||
File[] files = rootDir.listFiles();
|
||||
if (files == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (File f : files) {
|
||||
if (f.isDirectory()) {
|
||||
Optional<Map<String, Object>> first = childFile
|
||||
.stream()
|
||||
.filter(map -> map.get(PATH).equals(f.getAbsolutePath())).findFirst();
|
||||
DirFileModel childDirFileModel;
|
||||
if (first.isPresent()) {
|
||||
childDirFileModel = getDirFileModel(first.get());
|
||||
} else {
|
||||
childDirFileModel = getDirFileModel(f);
|
||||
}
|
||||
dirFileModel.getChildFile().add(childDirFileModel);
|
||||
} else {
|
||||
// 在此监测文件后缀,设置对应的图标
|
||||
dirFileModel.getChildFile().add(new DirFileModel(
|
||||
f.getAbsolutePath(), f.getName(), null,
|
||||
getIconCorrespondingToFileName(f.getName()),
|
||||
null));
|
||||
}
|
||||
}
|
||||
return dirFileModel;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 文件夹迁移
|
||||
*
|
||||
* @param sourceFolder 源文件夹
|
||||
* @param targetFolder 目标文件夹
|
||||
* @since 2023/10/5 12:18
|
||||
*/
|
||||
|
||||
public static void migrateFolder(File sourceFolder, File targetFolder) {
|
||||
// 创建目标文件夹
|
||||
targetFolder.mkdirs();
|
||||
|
||||
// 获取源文件夹中的所有文件和文件夹
|
||||
File[] files = sourceFolder.listFiles();
|
||||
|
||||
if (files != null) {
|
||||
// 遍历源文件夹中的每个文件和文件夹
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
// 如果是文件夹,递归调用自身进行迁移
|
||||
migrateFolder(file, new File(targetFolder, file.getName()));
|
||||
} else {
|
||||
// 如果是文件,将文件复制到目标文件夹中
|
||||
Path sourceFilePath = file.toPath();
|
||||
Path targetFilePath = new File(targetFolder, file.getName()).toPath();
|
||||
try {
|
||||
Files.copy(sourceFilePath, targetFilePath, StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
throw new AppException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 迁移文件夹
|
||||
*
|
||||
* @param sourceFolder 源文件夹
|
||||
* @param targetFolder 目标文件夹
|
||||
* @param ignoredFolders 忽略的文件夹集合
|
||||
* @param ignoredFiles 忽略的文件集合
|
||||
* @since 2023/10/5 13:58
|
||||
*/
|
||||
public static void migrateFolder(File sourceFolder, File targetFolder, Set<File> ignoredFolders, Set<File> ignoredFiles) {
|
||||
// 创建目标文件夹
|
||||
targetFolder.mkdir();
|
||||
// 获取源文件夹中的所有文件和文件夹
|
||||
File[] files = sourceFolder.listFiles();
|
||||
if (files != null) {
|
||||
// 遍历源文件夹中的每个文件和文件夹
|
||||
for (File file : files) {
|
||||
// 如果是文件夹且不是忽略的文件夹,递归调用自身进行迁移
|
||||
if (file.isDirectory() && !ignoredFolders.contains(file)) {
|
||||
migrateFolder(targetFolder, ignoredFolders, ignoredFiles, file);
|
||||
continue;
|
||||
}
|
||||
// 如果是文件且不是忽略的文件,将文件复制到目标文件夹中
|
||||
if (!file.isDirectory() && !ignoredFiles.contains(file)) {
|
||||
migrateFile(targetFolder, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 迁移文件
|
||||
*
|
||||
* @param targetFolder 目标文件夹
|
||||
* @param file 文件
|
||||
*/
|
||||
public static void migrateFile(File targetFolder, File file) {
|
||||
Path sourceFilePath = file.toPath();
|
||||
Path targetFilePath = new File(targetFolder, file.getName()).toPath();
|
||||
try {
|
||||
Files.copy(sourceFilePath, targetFilePath, StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
throw new AppException(e);
|
||||
}
|
||||
// 删除源文件
|
||||
try {
|
||||
Files.delete(file.toPath());
|
||||
} catch (IOException e) {
|
||||
throw new AppException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 迁移文件夹
|
||||
*
|
||||
* @param targetFolder 目标文件夹
|
||||
* @param ignoredFolders 忽略的文件夹集合
|
||||
* @param ignoredFiles 忽略的文件集合
|
||||
* @param file 文件
|
||||
*/
|
||||
private static void migrateFolder(File targetFolder, Set<File> ignoredFolders, Set<File> ignoredFiles, File file) {
|
||||
migrateFolder(file, new File(targetFolder, file.getName()), ignoredFolders, ignoredFiles);
|
||||
// 调用完毕删除当前目录
|
||||
try {
|
||||
Files.deleteIfExists(file.toPath());
|
||||
} catch (IOException e) {
|
||||
throw new AppException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the file explorer to the specified file or its parent directory.
|
||||
*
|
||||
* @param file the file or directory to open in the file explorer
|
||||
*/
|
||||
public static void openExplorer(File file) {
|
||||
try { // 判断传入的是文件还是文件夹
|
||||
if (file.isDirectory()) {
|
||||
Desktop.getDesktop().open(file);
|
||||
}
|
||||
// 如果是文件则打开所在文件夹
|
||||
else {
|
||||
Desktop.getDesktop().open(file.getParentFile());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AppException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the icon corresponding to the given file name.
|
||||
*
|
||||
* @param fileName the file name
|
||||
* @return the corresponding icon for the file extension
|
||||
*/
|
||||
public static Node getIconCorrespondingToFileName(String fileName) {
|
||||
// 在此根据文件缀名获取对应的图标
|
||||
String fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1);
|
||||
Node orDefault = UiUtil.getIconMap().getOrDefault(fileExtension, FontIcon.of(FILE_UNKNOWN));
|
||||
if (orDefault instanceof FontIcon fontIcon) {
|
||||
return new FontIcon(fontIcon.getIconLiteral());
|
||||
}
|
||||
if (orDefault instanceof ImageView imageView) {
|
||||
return new ImageView(imageView.getImage());
|
||||
}
|
||||
return orDefault;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a terminal in the specified folder.
|
||||
*
|
||||
* @param folder the folder in which to open the terminal
|
||||
*/
|
||||
public static void openTerminal(File folder) {
|
||||
if (!folder.exists()) {
|
||||
return;
|
||||
}
|
||||
if (folder.isFile()) {
|
||||
folder = folder.getParentFile();
|
||||
}
|
||||
String os = System.getProperty("os.name").toLowerCase();
|
||||
ProcessBuilder processBuilder = getProcessBuilder(folder, os);
|
||||
try {
|
||||
processBuilder.start();
|
||||
} catch (IOException e) {
|
||||
PopUpUtil.errorAlert("打开失败", "打开于终端失败", "错误原因:" + e.getMessage(), null, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ProcessBuilder object based on the provided folder and operating system.
|
||||
*
|
||||
* @param folder the folder to set as the working directory for the ProcessBuilder object
|
||||
* @param os the operating system to determine the appropriate command for the ProcessBuilder object
|
||||
* @return a ProcessBuilder object with the correct command for the specified operating system
|
||||
*/
|
||||
private static ProcessBuilder getProcessBuilder(File folder, String os) {
|
||||
ProcessBuilder processBuilder;
|
||||
if (os.contains(WINDOWS)) {
|
||||
// Windows系统
|
||||
processBuilder = new ProcessBuilder("cmd.exe", "/c", "start", "cmd.exe", "/k", "cd", folder.getAbsolutePath());
|
||||
} else if (os.contains(MAC)) {
|
||||
// macOS系统
|
||||
processBuilder = new ProcessBuilder("open", "-a", "Terminal", folder.getAbsolutePath());
|
||||
} else {
|
||||
// Linux或其他系统
|
||||
processBuilder = new ProcessBuilder("xdg-open", folder.getAbsolutePath());
|
||||
}
|
||||
return processBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a file at the specified path.
|
||||
*
|
||||
* @param path The path to the file to be created.
|
||||
*/
|
||||
public static void createFile(Path path) {
|
||||
try {
|
||||
Files.createFile(path);
|
||||
} catch (IOException e) {
|
||||
logger.error("创建文件失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
81
src/main/java/org/jcnc/jnotepad/app/utils/JsonUtil.java
Normal file
81
src/main/java/org/jcnc/jnotepad/app/utils/JsonUtil.java
Normal file
@ -0,0 +1,81 @@
|
||||
package org.jcnc.jnotepad.app.utils;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.core.util.DefaultIndenter;
|
||||
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import org.jcnc.jnotepad.controller.exception.AppException;
|
||||
|
||||
import static com.fasterxml.jackson.core.util.DefaultIndenter.SYS_LF;
|
||||
|
||||
/**
|
||||
* Jackson 解析器的外观类,主要提供 ObjectMapper 对象。
|
||||
*
|
||||
* <p>该类用于封装 Jackson 对象映射工具的配置和操作。</p>
|
||||
*
|
||||
* @author songdragon
|
||||
*/
|
||||
public class JsonUtil {
|
||||
public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
static {
|
||||
OBJECT_MAPPER.enable(SerializationFeature.INDENT_OUTPUT);
|
||||
DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();
|
||||
DefaultIndenter di = new DefaultIndenter(" ", SYS_LF);
|
||||
prettyPrinter.indentArraysWith(di);
|
||||
prettyPrinter.indentObjectsWith(di);
|
||||
OBJECT_MAPPER.setDefaultPrettyPrinter(prettyPrinter);
|
||||
}
|
||||
|
||||
private JsonUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象转换为 JSON 字符串。
|
||||
*
|
||||
* @param o 要转换的对象
|
||||
* @return 对象的 JSON 表示
|
||||
* @throws AppException 如果转换失败
|
||||
*/
|
||||
public static String toJsonString(Object o) {
|
||||
try {
|
||||
return OBJECT_MAPPER.writeValueAsString(o);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new AppException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将json字符串解析成对象
|
||||
*
|
||||
* @param json json字符串
|
||||
* @param clazz 对象类型
|
||||
* @return 对象
|
||||
* @throws AppException 如果解析失败
|
||||
*/
|
||||
public static <T> T fromJsonString(String json, Class<T> clazz) {
|
||||
try {
|
||||
return OBJECT_MAPPER.readValue(json, clazz);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new AppException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将json字符串解析成对象
|
||||
*
|
||||
* @param json json字符串
|
||||
* @param valueTypeRef 值 类型 引用
|
||||
* @return 对象
|
||||
* @throws AppException 如果解析失败
|
||||
*/
|
||||
public static <T> T fromJsonString(String json, TypeReference<T> valueTypeRef) {
|
||||
try {
|
||||
return OBJECT_MAPPER.readValue(json, valueTypeRef);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new AppException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/main/java/org/jcnc/jnotepad/app/utils/LoggerUtil.java
Normal file
29
src/main/java/org/jcnc/jnotepad/app/utils/LoggerUtil.java
Normal file
@ -0,0 +1,29 @@
|
||||
package org.jcnc.jnotepad.app.utils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* 日志工具
|
||||
*
|
||||
* <p>注意:如果在JavaFX项目中需要调用日志,请使用Platform.runLater()来调用。</p>
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class LoggerUtil {
|
||||
private LoggerUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取日志类。
|
||||
*
|
||||
* @param currentClass 所要记录的类
|
||||
* @return org.slf4j.Logger 日志对象
|
||||
*
|
||||
* <p>传入当前需要记录的类,返回记录该类的日志类。</p>
|
||||
* <p>建议一个类调用超过两次这个方法时,应当将该日志类变成成员对象,而不是多次调用。</p>
|
||||
*/
|
||||
public static Logger getLogger(Class<?> currentClass) {
|
||||
return LoggerFactory.getLogger(currentClass);
|
||||
}
|
||||
}
|
||||
102
src/main/java/org/jcnc/jnotepad/app/utils/NotificationUtil.java
Normal file
102
src/main/java/org/jcnc/jnotepad/app/utils/NotificationUtil.java
Normal file
@ -0,0 +1,102 @@
|
||||
package org.jcnc.jnotepad.app.utils;
|
||||
|
||||
import atlantafx.base.controls.Notification;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jcnc.jnotepad.ui.views.manager.RootManager;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* 通知实用程序
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class NotificationUtil {
|
||||
private static final RootManager ROOT_MANAGER = RootManager.getInstance();
|
||||
private static final StackPane ROOT_STACK_PANE = ROOT_MANAGER.getRootStackPane();
|
||||
|
||||
private NotificationUtil() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a custom notification with the given message and icon, applying the specified styles.
|
||||
*
|
||||
* @param message the message to display in the notification
|
||||
* @param icon the icon to display in the notification
|
||||
* @param styles additional styles to apply to the notification
|
||||
*/
|
||||
public static void customNotification(String message, FontIcon icon, String... styles) {
|
||||
Notification notification = new Notification(message, icon);
|
||||
Collections.addAll(notification.getStyleClass(), styles);
|
||||
RootManager.addNotificationToStackPane(ROOT_STACK_PANE, notification, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a success notification with the given message.
|
||||
*
|
||||
* @param message the message to be displayed in the notification
|
||||
*/
|
||||
public static void successNotification(String message) {
|
||||
Notification notification = new Notification(message, UiUtil.getSuccessIcon());
|
||||
setStyleClass(notification, Styles.SUCCESS);
|
||||
RootManager.addNotificationToStackPane(ROOT_STACK_PANE, notification, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an info notification with the given message.
|
||||
*
|
||||
* @param message the message to display in the notification
|
||||
*/
|
||||
public static void infoNotification(String message) {
|
||||
Notification notification = new Notification(message, UiUtil.getInfoIcon());
|
||||
setStyleClass(notification, Styles.ACCENT);
|
||||
RootManager.addNotificationToStackPane(ROOT_STACK_PANE, notification, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an error notification with the given message and displays it on the root stack pane.
|
||||
*
|
||||
* @param message the error message to be displayed
|
||||
*/
|
||||
public static void errorNotification(String message) {
|
||||
Notification notification = new Notification(message, UiUtil.getErrorIcon());
|
||||
setStyleClass(notification, Styles.DANGER);
|
||||
RootManager.addNotificationToStackPane(ROOT_STACK_PANE, notification, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a warning notification with the given message and displays it on the root stack pane.
|
||||
*
|
||||
* @param message the warning message to be displayed
|
||||
*/
|
||||
public static void warningNotification(String message) {
|
||||
Notification notification = new Notification(message, UiUtil.getWarningIcon());
|
||||
setStyleClass(notification, Styles.WARNING);
|
||||
RootManager.addNotificationToStackPane(ROOT_STACK_PANE, notification, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a question notification with the given message and displays it on the root stack pane.
|
||||
*
|
||||
* @param message the question message to be displayed
|
||||
*/
|
||||
public static void questionNotification(String message) {
|
||||
Notification notification = new Notification(message, UiUtil.getQuestionIcon());
|
||||
setStyleClass(notification, Styles.ACCENT);
|
||||
RootManager.addNotificationToStackPane(ROOT_STACK_PANE, notification, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the style class of the given notification.
|
||||
*
|
||||
* @param notification The notification object to set the style class for.
|
||||
* @param styleClass The style class to add to the notification.
|
||||
*/
|
||||
private static void setStyleClass(Notification notification, String styleClass) {
|
||||
notification.getStyleClass().add(Styles.ELEVATED_1);
|
||||
notification.getStyleClass().add(styleClass);
|
||||
}
|
||||
}
|
||||
162
src/main/java/org/jcnc/jnotepad/app/utils/PopUpUtil.java
Normal file
162
src/main/java/org/jcnc/jnotepad/app/utils/PopUpUtil.java
Normal file
@ -0,0 +1,162 @@
|
||||
package org.jcnc.jnotepad.app.utils;
|
||||
|
||||
import org.jcnc.jnotepad.model.enums.DialogType;
|
||||
import org.jcnc.jnotepad.ui.component.stage.dialog.AppDialogBuilder;
|
||||
import org.jcnc.jnotepad.ui.component.stage.dialog.interfaces.DialogButtonAction;
|
||||
|
||||
/**
|
||||
* 弹窗工具类
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class PopUpUtil {
|
||||
|
||||
private PopUpUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置错误弹窗
|
||||
*
|
||||
* @param title 弹窗标题
|
||||
* @param headerText 头文本
|
||||
* @param message 消息文本
|
||||
* @param leftBtnAction 左侧按钮点击事件
|
||||
* @param rightBtnAction 右侧按钮点击事件
|
||||
* @apiNote
|
||||
* @since 2023/9/3 11:54
|
||||
*/
|
||||
public static void errorAlert(
|
||||
String title, String headerText, String message,
|
||||
DialogButtonAction leftBtnAction, DialogButtonAction rightBtnAction) {
|
||||
getCustomDialog()
|
||||
.setDialogType(DialogType.ERROR)
|
||||
.setTitle(title)
|
||||
.setHeaderText(headerText)
|
||||
.setCustomText(message)
|
||||
.setLeftBtnAction(leftBtnAction)
|
||||
.setRightBtnAction(rightBtnAction)
|
||||
.build().showAndWait();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置信息弹窗
|
||||
*
|
||||
* @param title 弹窗标题
|
||||
* @param headerText 头文本
|
||||
* @param message 消息文本
|
||||
* @param leftBtnAction 左侧按钮点击事件
|
||||
* @param rightBtnAction 右侧按钮点击事件
|
||||
* @apiNote
|
||||
* @since 2023/9/3 11:54
|
||||
*/
|
||||
public static void infoAlert(
|
||||
String title, String headerText, String message,
|
||||
DialogButtonAction leftBtnAction, DialogButtonAction rightBtnAction) {
|
||||
getCustomDialog()
|
||||
.setDialogType(DialogType.INFO)
|
||||
.setTitle(title)
|
||||
.setHeaderText(headerText)
|
||||
.setCustomText(message)
|
||||
.setLeftBtnAction(leftBtnAction)
|
||||
.setRightBtnAction(rightBtnAction)
|
||||
.build().showAndWait();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置警告弹窗
|
||||
*
|
||||
* @param title 弹窗标题
|
||||
* @param headerText 头文本
|
||||
* @param message 消息文本
|
||||
* @param leftBtnAction 左侧按钮点击事件
|
||||
* @param rightBtnAction 右侧按钮点击事件
|
||||
* @apiNote
|
||||
* @since 2023/9/3 11:54
|
||||
*/
|
||||
public static void warningAlert(
|
||||
String title, String headerText, String message,
|
||||
DialogButtonAction leftBtnAction, DialogButtonAction rightBtnAction) {
|
||||
getCustomDialog()
|
||||
.setDialogType(DialogType.WARNING)
|
||||
.setTitle(title)
|
||||
.setHeaderText(headerText)
|
||||
.setCustomText(message)
|
||||
.setLeftBtnAction(leftBtnAction)
|
||||
.setRightBtnAction(rightBtnAction)
|
||||
.build().showAndWait();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置疑问弹窗
|
||||
*
|
||||
* @param title 弹窗标题
|
||||
* @param headerText 头文本
|
||||
* @param message 消息文本
|
||||
* @param leftBtnAction 左侧按钮点击事件
|
||||
* @param rightBtnAction 右侧按钮点击事件
|
||||
* @apiNote
|
||||
* @since 2023/9/3 11:54
|
||||
*/
|
||||
public static void questionAlert(
|
||||
String title, String headerText, String message,
|
||||
DialogButtonAction leftBtnAction, DialogButtonAction rightBtnAction) {
|
||||
getCustomDialog()
|
||||
.setDialogType(DialogType.QUESTION)
|
||||
.setTitle(title)
|
||||
.setHeaderText(headerText)
|
||||
.setCustomText(message)
|
||||
.setLeftBtnAction(leftBtnAction)
|
||||
.setRightBtnAction(rightBtnAction)
|
||||
.build().showAndWait();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置疑问弹窗
|
||||
*
|
||||
* @param title 弹窗标题
|
||||
* @param headerText 头文本
|
||||
* @param message 消息文本
|
||||
* @param leftBtnAction 左侧按钮点击事件
|
||||
* @param rightBtnAction 右侧按钮点击事件
|
||||
* @apiNote
|
||||
* @since 2023/9/3 11:54
|
||||
*/
|
||||
public static void questionAlert(
|
||||
String title, String headerText, String message,
|
||||
DialogButtonAction leftBtnAction, DialogButtonAction rightBtnAction, String leftBtnText, String rightBtnText) {
|
||||
getCustomDialog()
|
||||
.setDialogType(DialogType.QUESTION)
|
||||
.setTitle(title)
|
||||
.setHeaderText(headerText)
|
||||
.setCustomText(message)
|
||||
.setLeftBtnAction(leftBtnAction)
|
||||
.setRightBtnAction(rightBtnAction)
|
||||
.setLeftBtnText(leftBtnText)
|
||||
.setRightBtnText(rightBtnText)
|
||||
.build().showAndWait();
|
||||
}
|
||||
|
||||
public static void successAlert(
|
||||
String title, String headerText, String message,
|
||||
DialogButtonAction leftBtnAction, DialogButtonAction rightBtnAction) {
|
||||
getCustomDialog()
|
||||
.setDialogType(DialogType.SUCCESS)
|
||||
.setTitle(title)
|
||||
.setHeaderText(headerText)
|
||||
.setCustomText(message)
|
||||
.setLeftBtnAction(leftBtnAction)
|
||||
.setRightBtnAction(rightBtnAction)
|
||||
.build().showAndWait();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取自定义弹窗
|
||||
*
|
||||
* @apiNote 使用此方法会返回原始的应用对话框建造者类,以实现自定义弹窗
|
||||
* @since 2023/9/3 11:54
|
||||
*/
|
||||
public static AppDialogBuilder getCustomDialog() {
|
||||
return new AppDialogBuilder();
|
||||
}
|
||||
}
|
||||
70
src/main/java/org/jcnc/jnotepad/app/utils/ResourceUtil.java
Normal file
70
src/main/java/org/jcnc/jnotepad/app/utils/ResourceUtil.java
Normal file
@ -0,0 +1,70 @@
|
||||
package org.jcnc.jnotepad.app.utils;
|
||||
|
||||
import org.jcnc.jnotepad.JnotepadApp;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 资源工具
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class ResourceUtil {
|
||||
public static final String MODULE_DIR = "/jcnc/app/";
|
||||
|
||||
private ResourceUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an input stream for the specified resource.
|
||||
*
|
||||
* @param resource the path to the resource
|
||||
* @return the input stream for the resource
|
||||
*/
|
||||
public static InputStream getResourceAsStream(String resource) {
|
||||
String path = resolve(resource);
|
||||
return Objects.requireNonNull(
|
||||
JnotepadApp.class.getResourceAsStream(resolve(path)),
|
||||
"Resource not found: " + path
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the resource with the specified path.
|
||||
*
|
||||
* @param resource the path of the resource to retrieve
|
||||
* @return the URI of the retrieved resource
|
||||
*/
|
||||
public static URI getResource(String resource) {
|
||||
String path = resolve(resource);
|
||||
URL url = Objects.requireNonNull(JnotepadApp.class.getResource(resolve(path)), "Resource not found: " + path);
|
||||
return URI.create(url.toExternalForm());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a resource path by checking if it starts with a "/". If it does,
|
||||
* the resource path is returned as is. If it doesn't, the resource path is
|
||||
* concatenated with the module directory path.
|
||||
*
|
||||
* @param resource the resource path to be resolved
|
||||
* @param moduleDir the module directory path
|
||||
* @return the resolved resource path
|
||||
*/
|
||||
public static String resolve(String resource, String moduleDir) {
|
||||
Objects.requireNonNull(resource);
|
||||
return resource.startsWith("/") ? resource : moduleDir + resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the given resource using the default module directory.
|
||||
*
|
||||
* @param resource the resource to resolve
|
||||
* @return the resolved resource
|
||||
*/
|
||||
public static String resolve(String resource) {
|
||||
return resolve(resource, MODULE_DIR);
|
||||
}
|
||||
}
|
||||
338
src/main/java/org/jcnc/jnotepad/app/utils/TabUtil.java
Normal file
338
src/main/java/org/jcnc/jnotepad/app/utils/TabUtil.java
Normal file
@ -0,0 +1,338 @@
|
||||
package org.jcnc.jnotepad.app.utils;
|
||||
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jcnc.jnotepad.app.common.constants.AppConstants;
|
||||
import org.jcnc.jnotepad.app.common.constants.TextConstants;
|
||||
import org.jcnc.jnotepad.app.common.manager.ApplicationCacheManager;
|
||||
import org.jcnc.jnotepad.app.i18n.UiResourceBundle;
|
||||
import org.jcnc.jnotepad.controller.config.UserConfigController;
|
||||
import org.jcnc.jnotepad.controller.event.handler.menuitem.OpenFile;
|
||||
import org.jcnc.jnotepad.controller.i18n.LocalizationController;
|
||||
import org.jcnc.jnotepad.model.entity.Cache;
|
||||
import org.jcnc.jnotepad.model.enums.CacheExpirationTime;
|
||||
import org.jcnc.jnotepad.ui.component.module.TextCodeArea;
|
||||
import org.jcnc.jnotepad.ui.component.stage.dialog.factory.impl.BasicFileChooserFactory;
|
||||
import org.jcnc.jnotepad.ui.views.manager.BottomStatusBoxManager;
|
||||
import org.jcnc.jnotepad.ui.views.manager.CenterTabPaneManager;
|
||||
import org.jcnc.jnotepad.ui.views.root.center.main.center.tab.CenterTab;
|
||||
import org.jcnc.jnotepad.ui.views.root.center.main.center.tab.CenterTabPane;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import static org.jcnc.jnotepad.app.common.constants.TextConstants.NEW_FILE;
|
||||
import static org.jcnc.jnotepad.app.utils.FileUtil.getFileText;
|
||||
import static org.jcnc.jnotepad.controller.config.UserConfigController.CONFIG_NAME;
|
||||
|
||||
/**
|
||||
* 标签页工具
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class TabUtil {
|
||||
private static final ApplicationCacheManager CACHE_MANAGER = ApplicationCacheManager.getInstance();
|
||||
private static final Logger logger = LoggerUtil.getLogger(TabUtil.class);
|
||||
|
||||
private TabUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存文件标签页
|
||||
*/
|
||||
public static void saveFile(CenterTab tab) {
|
||||
if (tab == null) {
|
||||
return;
|
||||
}
|
||||
// 如果打开的是非关联文件,则调用另存为方法
|
||||
if (!tab.getRelevanceProperty()) {
|
||||
logger.info("当前保存文件为非关联打开文件,调用另存为方法");
|
||||
saveAsFile(tab);
|
||||
} else {
|
||||
logger.info("当前保存文件为关联打开文件,调用自动保存方法");
|
||||
// 调用tab保存方法
|
||||
tab.saveSelectedFileTab();
|
||||
// 如果该文件是配置文件,则刷新快捷键
|
||||
if (CONFIG_NAME.equals(tab.getText())) {
|
||||
// 重新加载语言包和快捷键
|
||||
UserConfigController.getInstance().loadConfig();
|
||||
UserConfigController.getInstance().initAllShortcutKeys();
|
||||
LocalizationController.initLocal();
|
||||
logger.info("已刷新语言包!");
|
||||
logger.info("已刷新快捷键!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 另存为
|
||||
*
|
||||
* @apiNote 将当前选中的标签页进行另存为弹出窗口式的保存。
|
||||
* @see LoggerUtil
|
||||
*/
|
||||
public static void saveAsFile(CenterTab tab) {
|
||||
if (tab == null) {
|
||||
return;
|
||||
}
|
||||
Cache cache = CACHE_MANAGER.getCache("folder", "saveFile");
|
||||
File file = BasicFileChooserFactory.getInstance().createFileChooser(
|
||||
UiResourceBundle.getContent(TextConstants.SAVE_AS),
|
||||
tab.getText(),
|
||||
cache == null ? null : new File((String) cache.getCacheData()),
|
||||
new FileChooser.ExtensionFilter("All types", "*.*"))
|
||||
.showSaveDialog(UiUtil.getAppWindow());
|
||||
if (file != null) {
|
||||
if (cache == null) {
|
||||
CACHE_MANAGER.addCache(
|
||||
CACHE_MANAGER.createCache("folder", "saveFile", file.getParent(),
|
||||
CacheExpirationTime.NEVER_EXPIRES.getValue()));
|
||||
} else {
|
||||
cache.setCacheData(file.getParent());
|
||||
CACHE_MANAGER.addCache(cache);
|
||||
}
|
||||
logger.info("正在保存文件: {}", file.getName());
|
||||
tab.save(file);
|
||||
// 将保存后的文件设置为关联文件
|
||||
tab.setRelevanceProperty(true);
|
||||
// 更新标签页上的文件名
|
||||
tab.setText(file.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重命名
|
||||
*/
|
||||
public static void rename(CenterTab tab) {
|
||||
if (tab == null || tab.getText().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// 判断当前是否为关联文件
|
||||
if (tab.getRelevanceProperty()) {
|
||||
// 重命名关联文件
|
||||
handleRenameRelevanceFile(tab);
|
||||
}
|
||||
// 如果当前不是关联文件则重命名标签页
|
||||
else {
|
||||
handleRenameTab(tab);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重命名标签页。
|
||||
*
|
||||
* @param tab 标签页组件
|
||||
*/
|
||||
private static void handleRenameTab(CenterTab tab) {
|
||||
// 临时记录标签页名称
|
||||
String tempName = tab.getText();
|
||||
TextField textField = new TextField(tempName);
|
||||
textField.getStyleClass().add("tab-title-editable");
|
||||
// 清空标签页名称
|
||||
tab.setText("");
|
||||
// 监听 Enter 键,完成编辑
|
||||
textField.setOnKeyPressed(event -> {
|
||||
if (event.getCode() == KeyCode.ENTER) {
|
||||
String newTabName = textField.getText();
|
||||
// 检查是否存在相同名称的标签页
|
||||
if (tabNameExists(newTabName)) {
|
||||
|
||||
// 显示弹窗并提示用户更换名称
|
||||
showDuplicateNameAlert(newTabName);
|
||||
|
||||
// 恢复原始名称
|
||||
tab.setText(tempName);
|
||||
|
||||
} else {
|
||||
tab.setText(newTabName);
|
||||
// 可选:移除 TextField 的图形
|
||||
tab.setGraphic(null);
|
||||
// 可选:恢复标签页的关闭按钮
|
||||
tab.setClosable(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 监听失去焦点事件,完成编辑
|
||||
textField.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
String newTabName = textField.getText();
|
||||
// 检查是否存在相同名称的标签页
|
||||
if (tabNameExists(newTabName)) {
|
||||
// 恢复原始名称
|
||||
tab.setText(tempName);
|
||||
|
||||
}
|
||||
if (Boolean.FALSE.equals(newValue)) {
|
||||
tab.setText(newTabName);
|
||||
// 可选:移除 TextField 的图形
|
||||
tab.setGraphic(null);
|
||||
// 可选:恢复标签页的关闭按钮
|
||||
tab.setClosable(true);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
tab.setClosable(false);
|
||||
// 设置 TextField 作为标签页的图形
|
||||
tab.setGraphic(textField);
|
||||
// 默认获取焦点并选中所有文字
|
||||
textField.requestFocus();
|
||||
textField.selectAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否存在具有相同名称的标签页。
|
||||
*
|
||||
* @param newTabName 要检查的新标签页名称
|
||||
* @return 如果存在具有相同名称的标签页,则返回 true;否则返回 false
|
||||
*/
|
||||
private static boolean tabNameExists(String newTabName) {
|
||||
CenterTabPane tabPane = CenterTabPane.getInstance();
|
||||
return tabPane.getTabs().stream()
|
||||
.anyMatch(tab -> tab.getText().equals(newTabName));
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示警告弹窗,提示用户更换重复的名称。
|
||||
*/
|
||||
private static void showDuplicateNameAlert(String newTabName) {
|
||||
PopUpUtil.errorAlert(
|
||||
"重命名错误",
|
||||
"\" " + newTabName + "\" 和已有标签页名字重复",
|
||||
"请再次重命名",
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重命名关联文件。
|
||||
*
|
||||
* @param tab 标签页组件
|
||||
*/
|
||||
private static void handleRenameRelevanceFile(CenterTab tab) {
|
||||
// 获取原始文件对象
|
||||
File file = (File) tab.getUserData();
|
||||
|
||||
// 获取应用窗口并绑定
|
||||
File newFile = BasicFileChooserFactory.getInstance()
|
||||
.createFileChooser(
|
||||
UiResourceBundle.getContent(TextConstants.RENAME),
|
||||
tab.getText(),
|
||||
new File(file.getParent()),
|
||||
new FileChooser.ExtensionFilter("All types", "*.*"))
|
||||
.showSaveDialog(UiUtil.getAppWindow());
|
||||
if (newFile != null) {
|
||||
boolean rename = file.renameTo(newFile);
|
||||
// 设置文件数据
|
||||
tab.setUserData(newFile);
|
||||
if (rename) {
|
||||
tab.setText(newFile.getName());
|
||||
logger.info("文件重命名成功");
|
||||
} else {
|
||||
logger.debug("文件重命名失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加新的文件标签页。
|
||||
*/
|
||||
public static void addNewFileTab() {
|
||||
// 创建一个新的文本编辑区
|
||||
TextCodeArea textArea = new TextCodeArea();
|
||||
// 创建标签页
|
||||
CenterTab centerTab = new CenterTab(
|
||||
generateDefaultName(),
|
||||
textArea);
|
||||
// 将Tab页添加到TabPane中
|
||||
CenterTabPaneManager.getInstance().addNewTab(centerTab);
|
||||
// 更新编码信息
|
||||
BottomStatusBoxManager.getInstance().updateEncodingLabel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the default name for a new tab.
|
||||
*
|
||||
* @return The default name for a new tab.
|
||||
*/
|
||||
private static String generateDefaultName() {
|
||||
// 设定初始索引
|
||||
int index = 1;
|
||||
StringBuilder tabTitle = new StringBuilder();
|
||||
String content = UiResourceBundle.getContent(NEW_FILE);
|
||||
// 获取当前默认创建标签页集合
|
||||
List<Tab> tabs = CenterTabPane.getInstance()
|
||||
.getTabs()
|
||||
.stream()
|
||||
// 排除不属于默认创建的标签页
|
||||
.filter(tab -> AppConstants.TABNAME_PATTERN.matcher(tab.getText()).matches())
|
||||
// 对默认创建的标签页进行排序
|
||||
.sorted(Comparator.comparing(tab -> {
|
||||
String tabText = tab.getText();
|
||||
// 提取数字部分
|
||||
String numberPart = tabText.substring(content.length());
|
||||
// 解析为数字
|
||||
return Integer.parseInt(numberPart);
|
||||
}))
|
||||
// 转为List集合
|
||||
.toList();
|
||||
// 构建初始标签页名称
|
||||
tabTitle.append(content).append(index);
|
||||
for (Tab tab : tabs) {
|
||||
if (tab.getText().contentEquals(tabTitle)) {
|
||||
tabTitle.setLength(0);
|
||||
tabTitle.append(content).append(++index);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return tabTitle.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开文件到选项卡
|
||||
*
|
||||
* @param file 文件对象
|
||||
*/
|
||||
public static void openFileToTab(File file) {
|
||||
// 获取标签页集合
|
||||
CenterTabPane centerTabPane = CenterTabPane.getInstance();
|
||||
// 遍历标签页,查找匹配的标签页
|
||||
for (Tab tab : centerTabPane.getTabs()) {
|
||||
// 获取绑定的文件
|
||||
File tabFile = (File) tab.getUserData();
|
||||
if (tabFile == null) {
|
||||
continue;
|
||||
}
|
||||
if (file.getPath().equals((tabFile).getPath())) {
|
||||
// 找到匹配的标签页,设置为选中状态并跳转
|
||||
centerTabPane.getSelectionModel().select(tab);
|
||||
return;
|
||||
}
|
||||
}
|
||||
getText(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取文本文件的内容。
|
||||
*
|
||||
* @param file 文件对象
|
||||
*/
|
||||
public static void getText(File file) {
|
||||
TextCodeArea textCodeArea = new TextCodeArea();
|
||||
// 检测文件编码
|
||||
Charset encoding = EncodingDetector.detectEncodingCharset(file);
|
||||
String fileText = getFileText(file, encoding);
|
||||
LoggerUtil.getLogger(OpenFile.class).info("已调用读取文件功能");
|
||||
textCodeArea.appendText(fileText);
|
||||
// 设置当前标签页关联本地文件 设置标签页关联文件
|
||||
CenterTab tab = new CenterTab(file.getName(), textCodeArea, encoding, file, true);
|
||||
// 设置关联文件最后的修改时间
|
||||
tab.setLastModifiedTimeOfAssociatedFile(file.lastModified());
|
||||
CenterTabPaneManager.getInstance().addNewTab(tab);
|
||||
}
|
||||
}
|
||||
222
src/main/java/org/jcnc/jnotepad/app/utils/UiUtil.java
Normal file
222
src/main/java/org/jcnc/jnotepad/app/utils/UiUtil.java
Normal file
@ -0,0 +1,222 @@
|
||||
package org.jcnc.jnotepad.app.utils;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.stage.Window;
|
||||
import org.jcnc.jnotepad.app.common.constants.AppConstants;
|
||||
import org.jcnc.jnotepad.app.manager.ApplicationManager;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.kordamp.ikonli.antdesignicons.AntDesignIconsFilled.*;
|
||||
|
||||
/**
|
||||
* UI工具
|
||||
*
|
||||
* <p>封装了项目中需要引入的UI组件</p>
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class UiUtil {
|
||||
/**
|
||||
* 应用程序图标
|
||||
*/
|
||||
private static final Image APP_ICON = new Image(Objects.requireNonNull(UiUtil.class.getResource(AppConstants.APP_ICON)).toString());
|
||||
|
||||
/**
|
||||
* 错误图标
|
||||
*/
|
||||
private static final FontIcon ERROR_ICON = FontIcon.of(EXCLAMATION_CIRCLE);
|
||||
|
||||
/**
|
||||
* 信息图标
|
||||
*/
|
||||
private static final FontIcon INFO_ICON = FontIcon.of(INFO_CIRCLE);
|
||||
/**
|
||||
* 警告图标
|
||||
*/
|
||||
private static final FontIcon WARNING_ICON = FontIcon.of(WARNING);
|
||||
/**
|
||||
* 问题图标
|
||||
*/
|
||||
private static final FontIcon QUESTION_ICON = FontIcon.of(QUESTION_CIRCLE);
|
||||
|
||||
private static final FontIcon SUCCESS_ICON = FontIcon.of(CHECK_CIRCLE);
|
||||
/**
|
||||
* 图标集合
|
||||
*/
|
||||
private static final Map<String, Node> ICON_MAP = new HashMap<>(32);
|
||||
|
||||
static {
|
||||
// 暂时设置颜色
|
||||
ERROR_ICON.getStyleClass().addAll(Styles.DANGER);
|
||||
INFO_ICON.getStyleClass().addAll(Styles.ACCENT);
|
||||
QUESTION_ICON.getStyleClass().addAll(Styles.ACCENT);
|
||||
WARNING_ICON.getStyleClass().addAll(Styles.WARNING);
|
||||
SUCCESS_ICON.getStyleClass().addAll(Styles.SUCCESS);
|
||||
ICON_MAP.put("css", fileIconByPng("css"));
|
||||
ICON_MAP.put("doc", fileIconByPng("doc"));
|
||||
ICON_MAP.put("dll", fileIconByPng("dll"));
|
||||
ICON_MAP.put("exe", fileIconByPng("exe"));
|
||||
ICON_MAP.put("gif", fileIconByPng("gif"));
|
||||
ICON_MAP.put("gitignore", fileIconByPng("git"));
|
||||
ICON_MAP.put("html", fileIconByPng("html"));
|
||||
ICON_MAP.put("json", fileIconByPng("json"));
|
||||
ICON_MAP.put("md", fileIconByPng("markdown"));
|
||||
ICON_MAP.put("pdf", FontIcon.of(FILE_PDF));
|
||||
ICON_MAP.put("ppt", FontIcon.of(FILE_PPT));
|
||||
ICON_MAP.put("png", fileIconByPng("png"));
|
||||
ICON_MAP.put("sql", fileIconByPng("database"));
|
||||
ICON_MAP.put("svg", fileIconByPng("svg"));
|
||||
ICON_MAP.put("txt", FontIcon.of(FILE_TEXT));
|
||||
ICON_MAP.put("xls", FontIcon.of(FILE_EXCEL));
|
||||
ICON_MAP.put("xml", fileIconByPng("xml"));
|
||||
// 编程语言
|
||||
ICON_MAP.put("bat", fileIconByPng("bat"));
|
||||
ICON_MAP.put("c", fileIconByPng("c"));
|
||||
ICON_MAP.put("cs", fileIconByPng("csharp"));
|
||||
ICON_MAP.put("cpp", fileIconByPng("cplusplus"));
|
||||
ICON_MAP.put("go", fileIconByPng("golang"));
|
||||
ICON_MAP.put("js", fileIconByPng("js"));
|
||||
ICON_MAP.put("java", fileIconByPng("java"));
|
||||
ICON_MAP.put("kt", fileIconByPng("kotlin"));
|
||||
ICON_MAP.put("lua", fileIconByPng("lua"));
|
||||
ICON_MAP.put("py", fileIconByPng("python"));
|
||||
ICON_MAP.put("php", fileIconByPng("php"));
|
||||
ICON_MAP.put("rb", fileIconByPng("ruby"));
|
||||
ICON_MAP.put("sh", fileIconByPng("sh"));
|
||||
|
||||
|
||||
}
|
||||
|
||||
private UiUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取应用程序图标。
|
||||
*
|
||||
* @return javafx.scene.image.Image 应用程序图标对象
|
||||
*/
|
||||
public static Image getAppIcon() {
|
||||
return APP_ICON;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the information icon.
|
||||
*
|
||||
* @return the information icon
|
||||
*/
|
||||
|
||||
public static FontIcon getInfoIcon() {
|
||||
return INFO_ICON;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the error icon.
|
||||
*
|
||||
* @return the error icon
|
||||
*/
|
||||
public static FontIcon getErrorIcon() {
|
||||
return ERROR_ICON;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the warning icon.
|
||||
*
|
||||
* @return the warning icon
|
||||
*/
|
||||
public static FontIcon getWarningIcon() {
|
||||
return WARNING_ICON;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the question icon.
|
||||
*
|
||||
* @return the question icon
|
||||
*/
|
||||
public static FontIcon getQuestionIcon() {
|
||||
return QUESTION_ICON;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the success icon.
|
||||
*
|
||||
* @return the success icon as a FontIcon object
|
||||
*/
|
||||
public static FontIcon getSuccessIcon() {
|
||||
return SUCCESS_ICON;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取应用窗口。
|
||||
*
|
||||
* @return javafx.stage.Window 应用窗口对象
|
||||
* @apiNote JnotepadApp.getWindow()
|
||||
*/
|
||||
public static Window getAppWindow() {
|
||||
return ApplicationManager.getInstance().getWindow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an ImageView with the specified module directory, name, and format.
|
||||
*
|
||||
* @param moduleDir the directory where the module is located
|
||||
* @param name the name of the icon
|
||||
* @param format the format of the icon
|
||||
* @return the generated ImageView
|
||||
*/
|
||||
public static ImageView icon(String moduleDir, String name, String format) {
|
||||
return icon(moduleDir + name + format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an ImageView object with the image specified by the given path.
|
||||
*
|
||||
* @param path the path to the image file
|
||||
* @return the ImageView object with the specified image
|
||||
*/
|
||||
public static ImageView icon(String path) {
|
||||
var img = new Image(ResourceUtil.getResourceAsStream(path));
|
||||
return new ImageView(img);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an ImageView based on a PNG file.
|
||||
*
|
||||
* @param moduleDir the directory of the module
|
||||
* @param name the name of the PNG file
|
||||
* @return the generated ImageView
|
||||
*/
|
||||
public static ImageView iconByPng(String moduleDir, String name) {
|
||||
return icon(moduleDir + name + ".png");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an ImageView object for a file icon based on the given PNG name.
|
||||
*
|
||||
* @param name the name of the PNG file for the file icon
|
||||
* @return the ImageView object representing the file icon
|
||||
*/
|
||||
public static ImageView fileIconByPng(String name) {
|
||||
return iconByPng("images/fileIcons/", name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an ImageView object for a file icon based on the given PNG name.
|
||||
*
|
||||
* @param name the name of the PNG file for the file icon
|
||||
* @return the ImageView object representing the file icon
|
||||
*/
|
||||
public static ImageView sidebarIconByPng(String name) {
|
||||
return iconByPng("images/sidebarIcons/", name);
|
||||
}
|
||||
|
||||
public static Map<String, Node> getIconMap() {
|
||||
return ICON_MAP;
|
||||
}
|
||||
}
|
||||
1
src/main/java/org/jcnc/jnotepad/app/utils/aReadme.md
Normal file
1
src/main/java/org/jcnc/jnotepad/app/utils/aReadme.md
Normal file
@ -0,0 +1 @@
|
||||
util 存放通用的实用工具代码。
|
||||
@ -1,12 +0,0 @@
|
||||
package org.jcnc.jnotepad.constants;
|
||||
|
||||
/**
|
||||
* Constants持有所有共享信息的全局变量
|
||||
*/
|
||||
|
||||
public class Constants {
|
||||
public static final double SCREEN_WIDTH = 800; //宽度
|
||||
public static final double SCREEN_LENGTH = 600; //高度
|
||||
public static final String APP_NAME = "JNotepad"; //名字
|
||||
public static final String APP_ICON = "/img/icon.png"; //logo地址
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package org.jcnc.jnotepad.controller;
|
||||
|
||||
import org.jcnc.jnotepad.controller.i18n.LocalizationController;
|
||||
import org.jcnc.jnotepad.controller.plugin.PluginLoader;
|
||||
|
||||
/**
|
||||
* 资源控制器
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class ResourceController {
|
||||
private static final ResourceController INSTANCE = new ResourceController();
|
||||
|
||||
private ResourceController() {
|
||||
}
|
||||
|
||||
|
||||
public static ResourceController getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public void loadResources() {
|
||||
// 1. 加载语言
|
||||
LocalizationController.initLocal();
|
||||
// 2. 加载插件
|
||||
PluginLoader.getInstance().loadPlugins();
|
||||
}
|
||||
}
|
||||
1
src/main/java/org/jcnc/jnotepad/controller/aReadme.md
Normal file
1
src/main/java/org/jcnc/jnotepad/controller/aReadme.md
Normal file
@ -0,0 +1 @@
|
||||
controller 存放控制器相关的代码,包括事件处理、异常处理等。
|
||||
243
src/main/java/org/jcnc/jnotepad/controller/cache/CacheController.java
vendored
Normal file
243
src/main/java/org/jcnc/jnotepad/controller/cache/CacheController.java
vendored
Normal file
@ -0,0 +1,243 @@
|
||||
package org.jcnc.jnotepad.controller.cache;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.jcnc.jnotepad.app.common.manager.ApplicationCacheManager;
|
||||
import org.jcnc.jnotepad.app.utils.JsonUtil;
|
||||
import org.jcnc.jnotepad.app.utils.LoggerUtil;
|
||||
import org.jcnc.jnotepad.controller.config.AppConfigController;
|
||||
import org.jcnc.jnotepad.model.entity.Cache;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 缓存控制器
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class CacheController {
|
||||
|
||||
private static final ApplicationCacheManager APPLICATION_CACHE_MANAGER = ApplicationCacheManager.getInstance();
|
||||
private static final CacheController INSTANCE = new CacheController();
|
||||
Logger logger = LoggerUtil.getLogger(this.getClass());
|
||||
private String cacheDir;
|
||||
|
||||
private CacheController() {
|
||||
cacheDir = Paths.get(AppConfigController.getInstance().getConfig().getRootPath(), ".jnotepad", "caches").toString();
|
||||
}
|
||||
|
||||
public static CacheController getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public void loadCaches() {
|
||||
// 如果本地没有缓存的话,就创建缓存
|
||||
if (createCachesIfNotExists()) {
|
||||
return;
|
||||
}
|
||||
// 检查并获取缓存根目录
|
||||
File cacheFileDir = createCacheRootIfNotExist();
|
||||
// 获取缓存命名空间
|
||||
String[] namespaces = cacheFileDir.list();
|
||||
// 如果缓存根目录下为空,则创建缓存
|
||||
if (Objects.requireNonNull(namespaces).length == 0) {
|
||||
APPLICATION_CACHE_MANAGER.setCaches(new HashMap<>(16));
|
||||
return;
|
||||
}
|
||||
Map<String, Cache> caches = new HashMap<>(16);
|
||||
setCaches(namespaces, cacheFileDir, caches);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the caches for the given namespaces.
|
||||
*
|
||||
* @param namespaces an array of namespace names
|
||||
* @param cacheFileDir the directory where the cache files are stored
|
||||
* @param caches a map of caches to be set
|
||||
*/
|
||||
private void setCaches(String[] namespaces, File cacheFileDir, Map<String, Cache> caches) {
|
||||
for (String namespace : namespaces) {
|
||||
// 获取命名空间对应的文件夹
|
||||
File namespaceDir = new File(cacheFileDir, namespace);
|
||||
// 获取缓存组对应的文件名称列表
|
||||
String[] groupNames = namespaceDir.list();
|
||||
// 如果命名空间文件夹下没有文件则删除该文件夹
|
||||
if (cleanEmptyFileOrFolder(namespaceDir)) {
|
||||
continue;
|
||||
}
|
||||
for (String groupName : Objects.requireNonNull(groupNames)) {
|
||||
// 获取缓存组对应的文件
|
||||
File groupFile = new File(namespaceDir, groupName);
|
||||
// 清理空文件
|
||||
if (cleanEmptyFileOrFolder(groupFile)) {
|
||||
continue;
|
||||
}
|
||||
// 获取缓存
|
||||
try {
|
||||
String cacheJson = Files.readString(groupFile.toPath());
|
||||
// 获取缓存集合
|
||||
Map<String, Cache> cacheMap = JsonUtil.fromJsonString(cacheJson, new TypeReference<>() {
|
||||
});
|
||||
// 设置缓存
|
||||
cacheMap.forEach((k, v) -> setUpCache(namespace, groupName, k, v, caches));
|
||||
} catch (IOException e) {
|
||||
logger.error("读取缓存文件出错!", e);
|
||||
try {
|
||||
Files.delete(cacheFileDir.toPath());
|
||||
} catch (IOException ignore) {
|
||||
logger.error("删除失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
// 设置缓存
|
||||
APPLICATION_CACHE_MANAGER.setCaches(caches);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置缓存
|
||||
*
|
||||
* @param namespace 命名空间
|
||||
* @param groupName 缓存组
|
||||
* @param k 缓存名称
|
||||
* @param v 缓存类
|
||||
* @param caches 缓存集合
|
||||
*/
|
||||
private void setUpCache(String namespace, String groupName, String k, Cache v, Map<String, Cache> caches) {
|
||||
// 判断缓存是否过期,没有过期才加载进内存
|
||||
if (v.getLastReadOrWriteTime() + v.getExpirationTime() > System.currentTimeMillis() || v.getExpirationTime() < 0) {
|
||||
v.setNamespace(namespace);
|
||||
v.setGroup(groupName);
|
||||
v.setName(k);
|
||||
caches.put(v.getCacheKey(), v);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理空文件或空文件夹并返回结果
|
||||
*
|
||||
* @param fileOrFolder 文件或文件夹
|
||||
* @return 是否清理
|
||||
*/
|
||||
private boolean cleanEmptyFileOrFolder(File fileOrFolder) {
|
||||
try {
|
||||
if (fileOrFolder.isFile() && fileOrFolder.length() == 0) {
|
||||
Files.delete(fileOrFolder.toPath());
|
||||
logger.info("删除缓存文件:{}", fileOrFolder);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (fileOrFolder.isDirectory() && Objects.requireNonNull(fileOrFolder.list()).length == 0) {
|
||||
Files.delete(fileOrFolder.toPath());
|
||||
logger.info("删除缓存文件夹:{}", fileOrFolder);
|
||||
return true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("清理缓存文件或文件夹出错!", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写缓存(writeCache)
|
||||
*/
|
||||
public void writeCaches() {
|
||||
writeCaches(APPLICATION_CACHE_MANAGER.getCaches());
|
||||
}
|
||||
|
||||
/**
|
||||
* 写缓存
|
||||
*
|
||||
* @param caches 缓存集合
|
||||
*/
|
||||
public void writeCaches(Map<String, Cache> caches) {
|
||||
// 检查并获取缓存根目录
|
||||
File cacheFileDir = createCacheRootIfNotExist();
|
||||
Map<File, Map<String, Cache>> fileMap = new HashMap<>(16);
|
||||
// 生成缓存
|
||||
caches.forEach((key, value) -> {
|
||||
// 判断当前命名空间对应目录是否创建
|
||||
File namespaceDir = new File(cacheFileDir, value.getNamespace());
|
||||
if (!namespaceDir.exists()) {
|
||||
namespaceDir.mkdirs();
|
||||
}
|
||||
// 判断当前组文件是否创建
|
||||
File groupFile = new File(namespaceDir, value.getGroup());
|
||||
if (!groupFile.exists()) {
|
||||
try {
|
||||
boolean flag = groupFile.createNewFile();
|
||||
if (!flag) {
|
||||
logger.error("创建缓存文件失败:{}", groupFile);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("创建缓存文件失败!", e);
|
||||
}
|
||||
}
|
||||
fileMap.computeIfAbsent(groupFile, k -> new HashMap<>(16));
|
||||
// 设置需要写入的数据
|
||||
fileMap.get(groupFile).put(value.getName(), value);
|
||||
});
|
||||
Set<File> fileSet = fileMap.keySet();
|
||||
// 清空原来的缓存
|
||||
fileSet.forEach(file -> {
|
||||
try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
|
||||
fileOutputStream.write(new byte[0]);
|
||||
} catch (IOException e) {
|
||||
logger.error("清空缓存文件失败!", e);
|
||||
}
|
||||
});
|
||||
// 写入缓存
|
||||
for (Map.Entry<File, Map<String, Cache>> entry : fileMap.entrySet()) {
|
||||
try (FileWriter writer = new FileWriter(entry.getKey(), true)) {
|
||||
writer.write(JsonUtil.toJsonString(entry.getValue()));
|
||||
} catch (IOException e) {
|
||||
logger.error("写入缓存文件失败!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果不存在,则创建缓存根目录
|
||||
*
|
||||
* @return 缓存根目录
|
||||
*/
|
||||
private File createCacheRootIfNotExist() {
|
||||
File cacheFileDir = new File(cacheDir);
|
||||
if (!cacheFileDir.exists()) {
|
||||
cacheFileDir.mkdirs();
|
||||
}
|
||||
return cacheFileDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果本地没有缓存则创建缓存
|
||||
*
|
||||
* @return 是否创建成功
|
||||
*/
|
||||
private boolean createCachesIfNotExists() {
|
||||
File cacheFileDir = createCacheRootIfNotExist();
|
||||
if (Objects.requireNonNull(cacheFileDir.list()).length == 0) {
|
||||
APPLICATION_CACHE_MANAGER.setCaches(new HashMap<>(16));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getCacheDir() {
|
||||
return cacheDir;
|
||||
}
|
||||
|
||||
public void setCacheDir(String cacheDir) {
|
||||
this.cacheDir = cacheDir;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
package org.jcnc.jnotepad.controller.config;
|
||||
|
||||
import org.jcnc.jnotepad.api.core.controller.config.BaseConfigController;
|
||||
import org.jcnc.jnotepad.app.config.AppConfig;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import static org.jcnc.jnotepad.app.common.constants.AppConstants.DEFAULT_PROPERTY;
|
||||
import static org.jcnc.jnotepad.app.common.constants.AppConstants.PROGRAM_FILE_DIRECTORY;
|
||||
|
||||
/**
|
||||
* 应用程序配置文件控制器
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class AppConfigController extends BaseConfigController<AppConfig> {
|
||||
|
||||
/**
|
||||
* 配置文件名
|
||||
*/
|
||||
public static final String CONFIG_NAME = "JNotepadConfig.json";
|
||||
private static final AppConfigController INSTANCE = new AppConfigController();
|
||||
private final String configDir;
|
||||
|
||||
public AppConfigController() {
|
||||
configDir = Paths.get(System.getProperty(DEFAULT_PROPERTY), PROGRAM_FILE_DIRECTORY, SYSTEM_CONFIG_DIR).toString();
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
public static AppConfigController getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置文件Class类
|
||||
*
|
||||
* @return 配置文件Class类
|
||||
*/
|
||||
@Override
|
||||
protected Class<AppConfig> getConfigClass() {
|
||||
return AppConfig.class;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取配置文件名称
|
||||
*
|
||||
* @return 配置文件名称
|
||||
*/
|
||||
@Override
|
||||
protected String getConfigName() {
|
||||
return CONFIG_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置文件文件夹路径
|
||||
*
|
||||
* @return 配置文件夹路径
|
||||
*/
|
||||
@Override
|
||||
protected String getConfigDir() {
|
||||
return configDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建配置文件实体
|
||||
*
|
||||
* @return 默认的配置文件实体
|
||||
* @apiNote 返回默认的配置文件实体用于序列化json
|
||||
*/
|
||||
@Override
|
||||
public AppConfig generateDefaultConfig() {
|
||||
AppConfig config = new AppConfig();
|
||||
config.setRootPath(System.getProperty(DEFAULT_PROPERTY));
|
||||
return config;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,102 @@
|
||||
package org.jcnc.jnotepad.controller.config;
|
||||
|
||||
import org.jcnc.jnotepad.api.core.controller.config.BaseConfigController;
|
||||
import org.jcnc.jnotepad.app.config.PluginConfig;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static org.jcnc.jnotepad.app.common.constants.AppConstants.PROGRAM_FILE_DIRECTORY;
|
||||
|
||||
/**
|
||||
* 插件控制器
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class PluginConfigController extends BaseConfigController<PluginConfig> {
|
||||
/**
|
||||
* 插件配置文件
|
||||
*/
|
||||
public static final String CONFIG_NAME = "pluginConfig.json";
|
||||
private static final PluginConfigController INSTANCE = new PluginConfigController();
|
||||
|
||||
private String configDir;
|
||||
private String pluginsDir;
|
||||
|
||||
private PluginConfigController() {
|
||||
String rootPath = AppConfigController.getInstance().getConfig().getRootPath();
|
||||
configDir = Paths.get(rootPath, PROGRAM_FILE_DIRECTORY, ROOT_CONFIG_DIR).toString();
|
||||
setPluginsDir(Paths.get(rootPath, PROGRAM_FILE_DIRECTORY, "plugins").toString());
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
public static PluginConfigController getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置文件Class类
|
||||
*
|
||||
* @return 配置文件Class类
|
||||
*/
|
||||
@Override
|
||||
protected Class<PluginConfig> getConfigClass() {
|
||||
return PluginConfig.class;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取配置文件名称
|
||||
*
|
||||
* @return 配置文件名称
|
||||
*/
|
||||
@Override
|
||||
protected String getConfigName() {
|
||||
return CONFIG_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置文件文件夹路径
|
||||
*
|
||||
* @return 配置文件夹路径
|
||||
*/
|
||||
@Override
|
||||
protected String getConfigDir() {
|
||||
return configDir;
|
||||
}
|
||||
|
||||
public void setConfigDir(String configDir) {
|
||||
this.configDir = configDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建配置文件实体
|
||||
*
|
||||
* @return 默认的配置文件实体
|
||||
* @apiNote 返回默认的配置文件实体用于序列化json
|
||||
*/
|
||||
@Override
|
||||
public PluginConfig generateDefaultConfig() {
|
||||
PluginConfig pluginConfig = new PluginConfig();
|
||||
pluginConfig.setPlugins(new ArrayList<>());
|
||||
return pluginConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件路径
|
||||
*
|
||||
* @return 插件路径
|
||||
*/
|
||||
public Path getPlungsPath() {
|
||||
return Paths.get(getPluginsDir());
|
||||
}
|
||||
|
||||
public String getPluginsDir() {
|
||||
return pluginsDir;
|
||||
}
|
||||
|
||||
public void setPluginsDir(String pluginsDir) {
|
||||
this.pluginsDir = pluginsDir;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,206 @@
|
||||
package org.jcnc.jnotepad.controller.config;
|
||||
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import org.jcnc.jnotepad.api.core.controller.config.BaseConfigController;
|
||||
import org.jcnc.jnotepad.app.config.UserConfig;
|
||||
import org.jcnc.jnotepad.app.utils.LoggerUtil;
|
||||
import org.jcnc.jnotepad.model.entity.ShortcutKey;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jcnc.jnotepad.app.common.constants.AppConstants.PROGRAM_FILE_DIRECTORY;
|
||||
import static org.jcnc.jnotepad.app.common.constants.TextConstants.CHINESE;
|
||||
|
||||
/**
|
||||
* 应用程序配置控制器
|
||||
*
|
||||
* <p>该类负责管理应用程序的配置文件,包括加载、持久化和更新配置信息等操作。</p>
|
||||
*
|
||||
* @author songdragon
|
||||
*/
|
||||
public class UserConfigController extends BaseConfigController<UserConfig> {
|
||||
/**
|
||||
* 配置文件名
|
||||
*/
|
||||
public static final String CONFIG_NAME = "userConfig.json";
|
||||
private static final String CTRL_N = "ctrl+n";
|
||||
private static final String CTRL_O = "ctrl+o";
|
||||
private static final String CTRL_S = "ctrl+s";
|
||||
private static final String CTRL_ALT_S = "ctrl+alt+s";
|
||||
private static final String ALT_S = "alt+s";
|
||||
private static final UserConfigController INSTANCE = new UserConfigController();
|
||||
private final List<Map<String, MenuItem>> menuItems = new ArrayList<>();
|
||||
Logger logger = LoggerUtil.getLogger(this.getClass());
|
||||
private String configDir;
|
||||
|
||||
private UserConfigController() {
|
||||
configDir = Paths.get(AppConfigController.getInstance().getConfig().getRootPath(), PROGRAM_FILE_DIRECTORY, ROOT_CONFIG_DIR).toString();
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取 UserConfigController 的实例。
|
||||
*
|
||||
* @return UserConfigController 的实例
|
||||
*/
|
||||
public static UserConfigController getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 ShortcutKey 对象。
|
||||
*
|
||||
* @param buttonName 按钮名称
|
||||
* @param shortcutKeyValue 快捷键值
|
||||
* @return ShortcutKey 对象
|
||||
*/
|
||||
private static ShortcutKey createShortcutKey(String buttonName, String shortcutKeyValue) {
|
||||
ShortcutKey shortcutKey = new ShortcutKey();
|
||||
shortcutKey.setButtonName(buttonName);
|
||||
shortcutKey.setShortcutKeyValue(shortcutKeyValue);
|
||||
return shortcutKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置文件Class类
|
||||
*
|
||||
* @return 配置文件Class类
|
||||
*/
|
||||
@Override
|
||||
protected Class<UserConfig> getConfigClass() {
|
||||
return UserConfig.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置文件名称
|
||||
*
|
||||
* @return 配置文件名称
|
||||
*/
|
||||
@Override
|
||||
protected String getConfigName() {
|
||||
return CONFIG_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置文件文件夹路径
|
||||
*
|
||||
* @return 配置文件夹路径
|
||||
*/
|
||||
@Override
|
||||
protected String getConfigDir() {
|
||||
return configDir;
|
||||
}
|
||||
|
||||
public void setConfigDir(String configDir) {
|
||||
this.configDir = configDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建配置文件实体
|
||||
*
|
||||
* @return 默认的配置文件实体
|
||||
* @apiNote 返回默认的配置文件实体用于序列化json
|
||||
*/
|
||||
@Override
|
||||
public UserConfig generateDefaultConfig() {
|
||||
UserConfig config = new UserConfig();
|
||||
config.setLanguage(CHINESE);
|
||||
config.setTextWrap(false);
|
||||
|
||||
List<ShortcutKey> shortcutKeys = new ArrayList<>();
|
||||
shortcutKeys.add(createShortcutKey("newItem", CTRL_N));
|
||||
shortcutKeys.add(createShortcutKey("openItem", CTRL_O));
|
||||
shortcutKeys.add(createShortcutKey("saveItem", CTRL_S));
|
||||
shortcutKeys.add(createShortcutKey("saveAsItem", CTRL_ALT_S));
|
||||
shortcutKeys.add(createShortcutKey("lineFeedItem", ""));
|
||||
shortcutKeys.add(createShortcutKey("openConfigItem", ALT_S));
|
||||
shortcutKeys.add(createShortcutKey("pluginManager", ""));
|
||||
shortcutKeys.add(createShortcutKey("countItem", ""));
|
||||
shortcutKeys.add(createShortcutKey("aboutItem", ""));
|
||||
|
||||
config.setShortcutKey(shortcutKeys);
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取自动换行设置,默认自动换行。
|
||||
*
|
||||
* @return true,自动换行;false,不自动换行
|
||||
*/
|
||||
public boolean getAutoLineConfig() {
|
||||
return getConfig().isTextWrap();
|
||||
}
|
||||
|
||||
public void setAutoLineConfig(boolean isAutoLine) {
|
||||
getConfig().setTextWrap(isAutoLine);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置文件中的语言设置。
|
||||
*
|
||||
* @param language 更新后的语言设置
|
||||
*/
|
||||
public void updateLanguage(String language) {
|
||||
if (getLanguage().equals(language)) {
|
||||
return;
|
||||
}
|
||||
getConfig().setLanguage(language);
|
||||
writeConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的语言设置。
|
||||
*
|
||||
* @return 语言设置
|
||||
*/
|
||||
public String getLanguage() {
|
||||
return getConfig().getLanguage();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取快捷键设置。
|
||||
*
|
||||
* @return 快捷键设置列表
|
||||
*/
|
||||
public List<ShortcutKey> getShortcutKey() {
|
||||
return getConfig().getShortcutKey();
|
||||
}
|
||||
|
||||
public void initAllShortcutKeys() {
|
||||
menuItems.forEach(this::initShortcutKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化快捷键
|
||||
*/
|
||||
public void initShortcutKeys(Map<String, MenuItem> menuItemMap) {
|
||||
List<MenuItem> itemsToUnbind = new ArrayList<>();
|
||||
List<ShortcutKey> shortcutKeyConfigs = getShortcutKey();
|
||||
for (ShortcutKey shortcutKey : shortcutKeyConfigs) {
|
||||
// 保证json的key必须和变量名一致
|
||||
MenuItem menuItem = menuItemMap.get(shortcutKey.getButtonName());
|
||||
String shortKeyValue = shortcutKey.getShortcutKeyValue();
|
||||
if ("".equals(shortKeyValue) && menuItem != null) {
|
||||
itemsToUnbind.add(menuItem);
|
||||
continue;
|
||||
}
|
||||
if (menuItem != null) {
|
||||
logger.info("功能名称:{}->快捷键:{}", menuItem.getText(), shortKeyValue);
|
||||
// 动态添加快捷键
|
||||
menuItem.setAccelerator(KeyCombination.keyCombination(shortKeyValue));
|
||||
}
|
||||
}
|
||||
// 解绑需要解绑的快捷键
|
||||
itemsToUnbind.forEach(menuItem -> menuItem.setAccelerator(null));
|
||||
}
|
||||
|
||||
public List<Map<String, MenuItem>> getMenuItems() {
|
||||
return menuItems;
|
||||
}
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
package org.jcnc.jnotepad.controller.event.handler;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.TextArea;
|
||||
|
||||
/**
|
||||
* 换行程序。
|
||||
* <p>
|
||||
* 用于在文本区域中插入一个换行符。
|
||||
*/
|
||||
public class LineFeed implements EventHandler<ActionEvent> {
|
||||
private final TextArea textArea;
|
||||
|
||||
/**
|
||||
* 构造函数,初始化 LineFeed 对象。
|
||||
* @param textArea 要操作的文本区域
|
||||
*/
|
||||
public LineFeed(TextArea textArea) {
|
||||
this.textArea = textArea;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理事件的方法,将一个换行符插入到文本区域的末尾。
|
||||
* @param event 触发的事件对象
|
||||
*/
|
||||
@Override
|
||||
public void handle(ActionEvent event) {
|
||||
String text = textArea.getText();
|
||||
textArea.setText(text + "\n");
|
||||
}
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
package org.jcnc.jnotepad.controller.event.handler;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TextArea;
|
||||
import org.jcnc.jnotepad.controller.manager.Controller;
|
||||
import org.jcnc.jnotepad.view.manager.ViewManager;
|
||||
|
||||
import static org.jcnc.jnotepad.view.manager.ViewManager.tabPane;
|
||||
|
||||
/**
|
||||
* 新建文件事件的事件处理程序。
|
||||
* <p>
|
||||
* 当用户选择新建文件时候,将创建一个新的文本编辑区,并在Tab页中显示。
|
||||
*/
|
||||
public class NewFile implements EventHandler<ActionEvent> {
|
||||
/**
|
||||
* 处理新建文件事件。
|
||||
*
|
||||
* @param event 事件对象
|
||||
*/
|
||||
@Override
|
||||
public void handle(ActionEvent event) {
|
||||
// 获取控制器
|
||||
Controller controller = new Controller();
|
||||
|
||||
// 创建一个新的文本编辑区
|
||||
TextArea textArea = new TextArea();
|
||||
|
||||
// 创建一个新的Tab页
|
||||
Tab tab = new Tab("新建文本 " + ++ViewManager.tabIndex);
|
||||
tab.setContent(textArea);
|
||||
|
||||
// 将Tab页添加到TabPane中
|
||||
tabPane.getTabs().add(tab);
|
||||
|
||||
// 将新建的Tab页设置为选中状态
|
||||
tabPane.getSelectionModel().select(tab);
|
||||
|
||||
// 更新状态标签
|
||||
controller.updateStatusLabel(textArea);
|
||||
|
||||
// 更新编码信息
|
||||
controller.upDateEncodingLabel(textArea.getText());
|
||||
}
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
package org.jcnc.jnotepad.controller.event.handler;
|
||||
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jcnc.jnotepad.controller.manager.Controller;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static org.jcnc.jnotepad.view.manager.ViewManager.tabPane;
|
||||
|
||||
/**
|
||||
* 打开文件的事件处理程序。
|
||||
* <p>
|
||||
* 当用户选择打开文件时,将创建一个新的文本编辑区,并在Tab页中显示。
|
||||
*/
|
||||
public class OpenFile implements EventHandler<ActionEvent> {
|
||||
/**
|
||||
* 处理打开文件事件。
|
||||
*
|
||||
* @param event 事件对象
|
||||
*/
|
||||
@Override
|
||||
public void handle(ActionEvent event) {
|
||||
// 获取控制器
|
||||
Controller controller = new Controller();
|
||||
// 创建文件选择器
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
// 显示文件选择对话框,并获取选中的文件
|
||||
File file = fileChooser.showOpenDialog(null);
|
||||
if (file != null) {
|
||||
// 创建打开文件的任务
|
||||
Task<Void> openFileTask = new Task<>() {
|
||||
@Override
|
||||
protected Void call() {
|
||||
// 调用控制器的getText方法,读取文件内容
|
||||
controller.getText(file);
|
||||
// 更新编码标签
|
||||
controller.upDateEncodingLabel(((TextArea) tabPane.getSelectionModel().getSelectedItem().getContent()).getText());
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// 设置任务成功完成时的处理逻辑
|
||||
openFileTask.setOnSucceeded(e -> {
|
||||
// 处理成功的逻辑
|
||||
});
|
||||
|
||||
// 设置任务失败时的处理逻辑
|
||||
openFileTask.setOnFailed(e -> {
|
||||
// 处理失败的逻辑
|
||||
});
|
||||
|
||||
// 创建并启动线程执行任务
|
||||
Thread thread = new Thread(openFileTask);
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,54 +0,0 @@
|
||||
package org.jcnc.jnotepad.controller.event.handler;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jcnc.jnotepad.controller.manager.Controller;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.jcnc.jnotepad.view.manager.ViewManager.tabPane;
|
||||
|
||||
/**
|
||||
* 保存文件事件处理器。
|
||||
* <p>
|
||||
* 当用户选择保存文件时,当用户选择另存为文件菜单或按钮时,
|
||||
* 会弹出一个保存文件对话框,用户选择保存位置和文件名后,
|
||||
* 将当前文本编辑区的内容保存到指定文件中,
|
||||
* 并更新Tab页上的文件名和UserData。
|
||||
*/
|
||||
public class SaveAsFile implements EventHandler<ActionEvent> {
|
||||
/**
|
||||
* 处理保存文件事件。
|
||||
*
|
||||
* @param event 事件对象
|
||||
*/
|
||||
@Override
|
||||
public void handle(ActionEvent event) {
|
||||
Tab selectedTab = tabPane.getSelectionModel().getSelectedItem();
|
||||
if (selectedTab != null) {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setInitialFileName("新建文本");
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("文本文档", "*.txt"));
|
||||
File file = fileChooser.showSaveDialog(null);
|
||||
if (file != null) {
|
||||
try {
|
||||
BufferedWriter writer = new BufferedWriter(new FileWriter(file));
|
||||
TextArea textArea = (TextArea) selectedTab.getContent(); // 获取当前Tab页的文本编辑区
|
||||
String text = textArea.getText();
|
||||
writer.write(text); // 写入文件内容
|
||||
writer.flush();
|
||||
writer.close();
|
||||
selectedTab.setText(file.getName()); // 更新Tab页标签上的文件名
|
||||
selectedTab.setUserData(file); // 将文件对象保存到Tab页的UserData中
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
package org.jcnc.jnotepad.controller.event.handler.menuitem;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
|
||||
import static org.jcnc.jnotepad.app.utils.TabUtil.addNewFileTab;
|
||||
|
||||
/**
|
||||
* 新建文件事件的事件处理程序。
|
||||
*
|
||||
* <p>当用户选择新建文件时,将创建一个新的文本编辑区,并在Tab页中显示。</p>
|
||||
*
|
||||
* @author 许轲
|
||||
*/
|
||||
public class NewFile implements EventHandler<ActionEvent> {
|
||||
/**
|
||||
* 处理新建文件事件。
|
||||
*
|
||||
* @param event 事件对象
|
||||
*/
|
||||
@Override
|
||||
public void handle(ActionEvent event) {
|
||||
addNewFileTab();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
package org.jcnc.jnotepad.controller.event.handler.menuitem;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import org.jcnc.jnotepad.app.utils.LoggerUtil;
|
||||
import org.jcnc.jnotepad.controller.config.UserConfigController;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static org.jcnc.jnotepad.app.utils.TabUtil.openFileToTab;
|
||||
|
||||
/**
|
||||
* 打开配置文件事件处理程序。
|
||||
*
|
||||
* <p>该事件处理程序用于打开配置文件。</p>
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class OpenConfig extends OpenFile {
|
||||
|
||||
/**
|
||||
* 处理打开配置文件事件。
|
||||
*
|
||||
* @param actionEvent 事件对象
|
||||
*/
|
||||
@Override
|
||||
public void handle(ActionEvent actionEvent) {
|
||||
// 显示文件选择对话框,并获取配置文件
|
||||
File file = UserConfigController.getInstance().getConfigPath().toFile();
|
||||
LoggerUtil.getLogger(this.getClass()).info("已调用打开配置文件功能, {}", file);
|
||||
// 创建打开文件的任务并启动线程执行任务
|
||||
openFileToTab(file);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
package org.jcnc.jnotepad.controller.event.handler.menuitem;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jcnc.jnotepad.app.common.constants.TextConstants;
|
||||
import org.jcnc.jnotepad.app.common.manager.ApplicationCacheManager;
|
||||
import org.jcnc.jnotepad.app.i18n.UiResourceBundle;
|
||||
import org.jcnc.jnotepad.app.utils.UiUtil;
|
||||
import org.jcnc.jnotepad.model.entity.Cache;
|
||||
import org.jcnc.jnotepad.model.enums.CacheExpirationTime;
|
||||
import org.jcnc.jnotepad.ui.component.stage.dialog.factory.impl.BasicFileChooserFactory;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static org.jcnc.jnotepad.app.utils.TabUtil.openFileToTab;
|
||||
|
||||
|
||||
/**
|
||||
* 打开文件的事件处理程序。
|
||||
* <p>
|
||||
* 当用户选择打开文件时,将创建一个新的文本编辑区,并在Tab页中显示。
|
||||
*
|
||||
* @author 许轲
|
||||
*/
|
||||
public class OpenFile implements EventHandler<ActionEvent> {
|
||||
private static final ApplicationCacheManager CACHE_MANAGER = ApplicationCacheManager.getInstance();
|
||||
|
||||
/**
|
||||
* 处理打开文件事件。
|
||||
*
|
||||
* @param event 事件对象
|
||||
*/
|
||||
@Override
|
||||
public void handle(ActionEvent event) {
|
||||
// 获取缓存
|
||||
Cache cache = CACHE_MANAGER.getCache("folder", "openFile");
|
||||
// 显示文件选择对话框,并获取选中的文件
|
||||
File file = BasicFileChooserFactory.getInstance().createFileChooser(
|
||||
UiResourceBundle.getContent(TextConstants.OPEN),
|
||||
null,
|
||||
cache == null ? null : new File((String) cache.getCacheData()),
|
||||
new FileChooser.ExtensionFilter("All types", "*.*"))
|
||||
.showOpenDialog(UiUtil.getAppWindow());
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
// 设置缓存
|
||||
if (cache == null) {
|
||||
CACHE_MANAGER.addCache(CACHE_MANAGER.createCache("folder", "openFile", file.getParent(), CacheExpirationTime.NEVER_EXPIRES.getValue()));
|
||||
} else {
|
||||
cache.setCacheData(file.getParent());
|
||||
CACHE_MANAGER.addCache(cache);
|
||||
}
|
||||
openFileToTab(file);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package org.jcnc.jnotepad.controller.event.handler.menuitem;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import org.jcnc.jnotepad.ui.views.manager.CenterTabPaneManager;
|
||||
|
||||
import static org.jcnc.jnotepad.app.utils.TabUtil.rename;
|
||||
|
||||
/**
|
||||
* 重命名文件事件处理器。
|
||||
* <p>
|
||||
* 当用户选择重命名文件时,如果当前标签页关联文件,则重命名关联文件;
|
||||
* 否则,重命名标签页。
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class RenameFile implements EventHandler<ActionEvent> {
|
||||
@Override
|
||||
public void handle(ActionEvent actionEvent) {
|
||||
rename(CenterTabPaneManager.getInstance().getSelected());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package org.jcnc.jnotepad.controller.event.handler.menuitem;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import org.jcnc.jnotepad.app.utils.LoggerUtil;
|
||||
import org.jcnc.jnotepad.ui.views.manager.CenterTabPaneManager;
|
||||
|
||||
import static org.jcnc.jnotepad.app.utils.TabUtil.saveAsFile;
|
||||
|
||||
/**
|
||||
* 保存文件事件处理器。
|
||||
* <p>
|
||||
* 当用户选择保存文件时,当用户选择另存为文件菜单或按钮时,
|
||||
* 会弹出一个保存文件对话框,用户选择保存位置和文件名后,
|
||||
* 将当前文本编辑区的内容保存到指定文件中,
|
||||
* 并更新Tab页上的文件名和UserData。
|
||||
*
|
||||
* @author 许轲
|
||||
*/
|
||||
public class SaveAsFile extends SaveFile {
|
||||
/**
|
||||
* 处理保存文件事件。
|
||||
*
|
||||
* @param event 事件对象
|
||||
*/
|
||||
@Override
|
||||
public void handle(ActionEvent event) {
|
||||
LoggerUtil.getLogger(SaveAsFile.class).info("已调用另存为功能");
|
||||
saveAsFile(CenterTabPaneManager.getInstance().getSelected());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package org.jcnc.jnotepad.controller.event.handler.menuitem;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import org.jcnc.jnotepad.ui.views.manager.CenterTabPaneManager;
|
||||
|
||||
import static org.jcnc.jnotepad.app.utils.TabUtil.saveFile;
|
||||
|
||||
/**
|
||||
* 保存文件事件处理程序。
|
||||
* <p>
|
||||
* 当用户选择保存文件时,如果当前标签页是关联文件,则自动保存;
|
||||
* 否则,调用另存为方法。
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class SaveFile implements EventHandler<ActionEvent> {
|
||||
|
||||
/**
|
||||
* 处理保存文件事件。
|
||||
*
|
||||
* @param actionEvent 事件对象
|
||||
* @apiNote 当用户选择保存文件时,如果当前标签页是关联文件,则自动保存;
|
||||
* 否则,调用另存为方法。
|
||||
*/
|
||||
@Override
|
||||
public void handle(ActionEvent actionEvent) {
|
||||
// 保存当前选中的标签页
|
||||
saveFile(CenterTabPaneManager.getInstance().getSelected());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package org.jcnc.jnotepad.controller.event.handler.toolbar;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import org.jcnc.jnotepad.ui.views.manager.DirectorySidebarManager;
|
||||
|
||||
|
||||
/**
|
||||
* 文件树按钮
|
||||
*
|
||||
* <p>文件树按钮事件的事件处理程序。</p>
|
||||
*
|
||||
* @author cccqyu
|
||||
*/
|
||||
public class DirTreeBtn implements EventHandler<ActionEvent> {
|
||||
|
||||
private static final DirectorySidebarManager DIRECTORY_SIDEBAR_MANAGER = DirectorySidebarManager.getInstance();
|
||||
|
||||
@Override
|
||||
public void handle(ActionEvent actionEvent) {
|
||||
DIRECTORY_SIDEBAR_MANAGER.controlShow();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
package org.jcnc.jnotepad.controller.event.handler.toolbar;
|
||||
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import org.jcnc.jnotepad.app.common.constants.TextConstants;
|
||||
import org.jcnc.jnotepad.app.common.manager.ApplicationCacheManager;
|
||||
import org.jcnc.jnotepad.app.i18n.UiResourceBundle;
|
||||
import org.jcnc.jnotepad.app.utils.FileUtil;
|
||||
import org.jcnc.jnotepad.app.utils.UiUtil;
|
||||
import org.jcnc.jnotepad.model.entity.Cache;
|
||||
import org.jcnc.jnotepad.model.entity.DirFileModel;
|
||||
import org.jcnc.jnotepad.model.enums.CacheExpirationTime;
|
||||
import org.jcnc.jnotepad.ui.component.stage.dialog.factory.impl.BasicDirectoryChooserFactory;
|
||||
import org.jcnc.jnotepad.ui.views.manager.DirectorySidebarManager;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* 打开文件夹處理器
|
||||
*
|
||||
* <p>当用户选择打开文件夹时,将创建一个左侧树型结构目录。</p>
|
||||
*
|
||||
* @author cccqyu
|
||||
*/
|
||||
public class OpenDirectory implements EventHandler<ActionEvent> {
|
||||
|
||||
public static final String GROUP = "directory";
|
||||
private static final ApplicationCacheManager CACHE_MANAGER = ApplicationCacheManager.getInstance();
|
||||
private static final DirectorySidebarManager DIRECTORY_SIDEBAR_MANAGER = DirectorySidebarManager.getInstance();
|
||||
|
||||
@Override
|
||||
public void handle(ActionEvent actionEvent) {
|
||||
// 获取缓存
|
||||
Cache cache = CACHE_MANAGER.getCache(GROUP, "openDirectory");
|
||||
|
||||
// 显示文件选择对话框,并获取选中的文件
|
||||
File file = BasicDirectoryChooserFactory.getInstance().createDirectoryChooser(
|
||||
UiResourceBundle.getContent(TextConstants.OPEN),
|
||||
cache == null ? null : new File((String) cache.getCacheData())
|
||||
)
|
||||
.showDialog(UiUtil.getAppWindow());
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
// 设置缓存
|
||||
if (cache == null) {
|
||||
CACHE_MANAGER.addCache(CACHE_MANAGER.createCache(GROUP, "openDirectory", file.getAbsolutePath(), CacheExpirationTime.NEVER_EXPIRES.getValue()));
|
||||
} else {
|
||||
cache.setCacheData(file.getParent());
|
||||
CACHE_MANAGER.addCache(cache);
|
||||
}
|
||||
flushDirSidebar(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes the directory sidebar with the given file.
|
||||
*
|
||||
* @param file the file to be converted into an entity class
|
||||
*/
|
||||
public void flushDirSidebar(File file) {
|
||||
// 将文件转为实体类
|
||||
DirFileModel dirFileModel = FileUtil.getDirFileModel(file);
|
||||
// 打开侧边栏
|
||||
DIRECTORY_SIDEBAR_MANAGER.controlShow(true);
|
||||
// 设置文件树功能
|
||||
DIRECTORY_SIDEBAR_MANAGER.setTreeView(dirFileModel);
|
||||
// 缓存已打开的文件夹
|
||||
CACHE_MANAGER.addCache(CACHE_MANAGER.createCache(GROUP, "folderThatWasOpened", dirFileModel, CacheExpirationTime.NEVER_EXPIRES.getValue()));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package org.jcnc.jnotepad.controller.event.handler.toolbar;
|
||||
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import org.jcnc.jnotepad.ui.views.manager.BuildPanelManager;
|
||||
|
||||
/**
|
||||
* 终端处理器
|
||||
*
|
||||
* @author cccqyu
|
||||
*/
|
||||
public class RunBtn implements EventHandler<ActionEvent> {
|
||||
|
||||
private static final BuildPanelManager BUILD_PANEL_MANAGER = BuildPanelManager.getInstance();
|
||||
|
||||
@Override
|
||||
public void handle(ActionEvent event) {
|
||||
BUILD_PANEL_MANAGER.controlShow();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
package org.jcnc.jnotepad.controller.event.handler.toolbar;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import org.jcnc.jnotepad.ui.component.stage.setting.SetStage;
|
||||
|
||||
/**
|
||||
* 设置按钮事件的事件处理程序。
|
||||
*
|
||||
* <p>当用户点击设置按钮时,将打开设置窗口。</p>
|
||||
*
|
||||
* @author 许轲
|
||||
*/
|
||||
public class SetBtn implements EventHandler<ActionEvent> {
|
||||
/**
|
||||
* 标志变量,跟踪Stage是否已创建
|
||||
*/
|
||||
private boolean isStageCreated = false;
|
||||
|
||||
/**
|
||||
* 打开设置窗口处理事件。
|
||||
*
|
||||
* @param event 事件对象
|
||||
*/
|
||||
@Override
|
||||
public void handle(ActionEvent event) {
|
||||
if (!isStageCreated) {
|
||||
SetStage.getInstance().openSetStage();
|
||||
// 设置标志变量为true,表示Stage已创建
|
||||
isStageCreated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
package org.jcnc.jnotepad.controller.exception;
|
||||
|
||||
/**
|
||||
* 应用异常类,用于处理应用程序中的异常情况。
|
||||
*
|
||||
* <p>应用异常是一个运行时异常,通常用于捕获和处理应用程序中的不可预料的错误和异常情况。</p>
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class AppException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* 构造一个应用异常对象。
|
||||
*
|
||||
* @param message 异常消息
|
||||
*/
|
||||
public AppException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造一个应用异常对象。
|
||||
*
|
||||
* @param cause 异常的原因
|
||||
*/
|
||||
public AppException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,96 @@
|
||||
package org.jcnc.jnotepad.controller.i18n;
|
||||
|
||||
import org.jcnc.jnotepad.JnotepadApp;
|
||||
import org.jcnc.jnotepad.app.i18n.UiResourceBundle;
|
||||
import org.jcnc.jnotepad.controller.config.UserConfigController;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jcnc.jnotepad.app.common.constants.TextConstants.CHINESE;
|
||||
import static org.jcnc.jnotepad.app.common.constants.TextConstants.ENGLISH;
|
||||
|
||||
/**
|
||||
* 本地化控制器
|
||||
*
|
||||
* <p>该类负责处理应用程序的本地化配置,包括语言和区域设置。</p>
|
||||
*
|
||||
* @author gewuyou
|
||||
* @see JnotepadApp
|
||||
*/
|
||||
public class LocalizationController {
|
||||
private static final LocalizationController LOCALIZATION_CONFIG = new LocalizationController();
|
||||
|
||||
private static final Map<String, Locale> SUPPORT_LOCALES;
|
||||
private static final Map<Locale, String> SUPPORT_LANGUAGES;
|
||||
|
||||
static {
|
||||
Locale.setDefault(Locale.ENGLISH);
|
||||
SUPPORT_LOCALES = new LinkedHashMap<>();
|
||||
SUPPORT_LOCALES.put(CHINESE, Locale.CHINESE);
|
||||
SUPPORT_LOCALES.put(ENGLISH, Locale.ENGLISH);
|
||||
|
||||
SUPPORT_LANGUAGES = new HashMap<>();
|
||||
SUPPORT_LANGUAGES.put(Locale.CHINESE, CHINESE);
|
||||
SUPPORT_LANGUAGES.put(Locale.ENGLISH, ENGLISH);
|
||||
}
|
||||
|
||||
private final UserConfigController userConfigController;
|
||||
|
||||
private LocalizationController() {
|
||||
this.userConfigController = UserConfigController.getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前语言配置
|
||||
*
|
||||
* @return 当前语言的Locale对象
|
||||
*/
|
||||
public static Locale getCurrentLocal() {
|
||||
return Locale.getDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前语言配置
|
||||
*
|
||||
* @param locale 当前语言的Locale对象
|
||||
*/
|
||||
public static void setCurrentLocal(Locale locale) {
|
||||
if (locale != null && locale.equals(getCurrentLocal())) {
|
||||
// 要更新的语言与当前语言一致,则不执行
|
||||
return;
|
||||
}
|
||||
if (locale == null) {
|
||||
locale = SUPPORT_LOCALES.get(LOCALIZATION_CONFIG.getLanguage());
|
||||
}
|
||||
if (locale == null) {
|
||||
locale = getCurrentLocal();
|
||||
}
|
||||
Locale.setDefault(locale);
|
||||
|
||||
UiResourceBundle.getInstance().resetLocal(getCurrentLocal());
|
||||
LOCALIZATION_CONFIG.setLanguage(SUPPORT_LANGUAGES.get(locale));
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化语言配置
|
||||
*/
|
||||
public static void initLocal() {
|
||||
setCurrentLocal(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前语言配置
|
||||
*
|
||||
* @return appConfig中的当前语言配置
|
||||
*/
|
||||
public String getLanguage() {
|
||||
return userConfigController.getLanguage();
|
||||
}
|
||||
|
||||
private void setLanguage(String language) {
|
||||
userConfigController.updateLanguage(language);
|
||||
}
|
||||
}
|
||||
@ -1,125 +1,59 @@
|
||||
package org.jcnc.jnotepad.controller.manager;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TextArea;
|
||||
import org.jcnc.jnotepad.Interface.ControllerInterface;
|
||||
import org.jcnc.jnotepad.LunchApp;
|
||||
import org.jcnc.jnotepad.controller.event.handler.LineFeed;
|
||||
import org.jcnc.jnotepad.controller.event.handler.NewFile;
|
||||
import org.jcnc.jnotepad.controller.event.handler.OpenFile;
|
||||
import org.jcnc.jnotepad.controller.event.handler.SaveAsFile;
|
||||
import org.jcnc.jnotepad.tool.EncodingDetector;
|
||||
import org.jcnc.jnotepad.view.manager.ViewManager;
|
||||
import org.jcnc.jnotepad.app.common.manager.ApplicationCacheManager;
|
||||
import org.jcnc.jnotepad.ui.component.module.interfaces.ControllerAble;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jcnc.jnotepad.app.utils.TabUtil.addNewFileTab;
|
||||
import static org.jcnc.jnotepad.app.utils.TabUtil.openFileToTab;
|
||||
|
||||
/**
|
||||
* 控制器类,实现ControllerInterface接口,用于管理文本编辑器的各种操作和事件处理。
|
||||
* 包括打开关联文件、创建文本区域、处理行分隔、新建文件、打开文件、自动保存等功能。
|
||||
* 控制器类,实现 ControllerAble 接口,用于管理文本编辑器的各种操作和事件处理。
|
||||
*
|
||||
* @author 许轲
|
||||
*/
|
||||
public class Controller implements ControllerInterface {
|
||||
public class Controller implements ControllerAble<List<String>> {
|
||||
private static final ApplicationCacheManager CACHE_MANAGER = ApplicationCacheManager.getInstance();
|
||||
|
||||
private static final Controller INSTANCE = new Controller();
|
||||
|
||||
private Controller() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Controller 的唯一实例。
|
||||
*
|
||||
* @return Controller 的实例
|
||||
*/
|
||||
public static Controller getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开关联文件并创建文本区域。
|
||||
*
|
||||
* @param rawParameters 原始参数列表
|
||||
* @return 创建的文本区域
|
||||
*/
|
||||
@Override
|
||||
public TextArea openAssociatedFileAndCreateTextArea(List<String> rawParameters) {
|
||||
public void openAssociatedFileAndCreateTextArea(List<String> rawParameters) {
|
||||
// 获取上次打开的页面
|
||||
Optional<Object> cacheData = Optional.ofNullable(CACHE_MANAGER.getCacheData("tabs", "centerTabs"));
|
||||
// 判空
|
||||
List<String> fileTab = (List<String>) cacheData.orElse(Collections.emptyList());
|
||||
// 打开上次打开的标签页
|
||||
fileTab.forEach(filePath -> openFileToTab(new File(filePath)));
|
||||
if (!rawParameters.isEmpty()) {
|
||||
String filePath = rawParameters.get(0);
|
||||
openAssociatedFile(filePath);
|
||||
return null;
|
||||
} else {
|
||||
TextArea textArea = createNewTextArea();
|
||||
configureTextArea(textArea);
|
||||
return textArea;
|
||||
return;
|
||||
}
|
||||
if (fileTab.isEmpty()) {
|
||||
addNewFileTab();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取行分隔事件处理程序。
|
||||
*
|
||||
* @param textArea 文本区域
|
||||
* @return 行分隔事件处理程序
|
||||
*/
|
||||
@Override
|
||||
public EventHandler<ActionEvent> getLineFeedEventHandler(TextArea textArea) {
|
||||
return new LineFeed(textArea);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取新建文件事件处理程序。
|
||||
*
|
||||
* @param textArea 文本区域
|
||||
* @return 新建文件事件处理程序
|
||||
*/
|
||||
@Override
|
||||
public EventHandler<ActionEvent> getNewFileEventHandler(TextArea textArea) {
|
||||
return new NewFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取打开文件事件处理程序。
|
||||
*
|
||||
* @return 打开文件事件处理程序
|
||||
*/
|
||||
@Override
|
||||
public EventHandler<ActionEvent> getOpenFileEventHandler() {
|
||||
return new OpenFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动保存文本内容。
|
||||
*
|
||||
* @param textArea 文本区域
|
||||
*/
|
||||
@Override
|
||||
public void autoSave(TextArea textArea) {
|
||||
textArea.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
Tab tab = ViewManager.tabPane.getSelectionModel().getSelectedItem();
|
||||
if (tab != null) {
|
||||
File file = (File) tab.getUserData();
|
||||
if (file != null) {
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
|
||||
writer.write(newValue);
|
||||
System.out.println("正在自动保存---");
|
||||
} catch (IOException ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取另存为文件事件处理程序。
|
||||
*
|
||||
* @return 另存为文件事件处理程序
|
||||
*/
|
||||
@Override
|
||||
public EventHandler<ActionEvent> getSaveAsFileEventHandler() {
|
||||
return new SaveAsFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新状态标签。
|
||||
*
|
||||
* @param textArea 文本区域
|
||||
*/
|
||||
@Override
|
||||
public void updateStatusLabel(TextArea textArea) {
|
||||
int caretPosition = textArea.getCaretPosition();
|
||||
int row = getRow(caretPosition, textArea.getText());
|
||||
int column = getColumn(caretPosition, textArea.getText());
|
||||
int length = textArea.getLength();
|
||||
ViewManager.statusLabel.setText("行: " + row + " \t列: " + column + " \t字数: " + length);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -131,184 +65,7 @@ public class Controller implements ControllerInterface {
|
||||
public void openAssociatedFile(String filePath) {
|
||||
File file = new File(filePath);
|
||||
if (file.exists() && file.isFile()) {
|
||||
LunchApp.isRelevance = false;
|
||||
openFile(file);
|
||||
openFileToTab(file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取文本文件的内容。
|
||||
*
|
||||
* @param file 文件对象
|
||||
*/
|
||||
@Override
|
||||
public void getText(File file) {
|
||||
TextArea textArea = createNewTextArea();
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
|
||||
StringBuilder textBuilder = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
textBuilder.append(line).append("\n");
|
||||
}
|
||||
String text = textBuilder.toString();
|
||||
|
||||
Platform.runLater(() -> {
|
||||
textArea.setText(text);
|
||||
|
||||
Tab tab = createNewTab(file.getName(), textArea);
|
||||
tab.setUserData(file);
|
||||
ViewManager.tabPane.getTabs().add(tab);
|
||||
ViewManager.tabPane.getSelectionModel().select(tab);
|
||||
updateStatusLabel(textArea);
|
||||
|
||||
autoSave(textArea);
|
||||
});
|
||||
} catch (IOException ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新编码标签。
|
||||
*
|
||||
* @param text 文本内容
|
||||
*/
|
||||
@Override
|
||||
public void upDateEncodingLabel(String text) {
|
||||
String encoding = EncodingDetector.detectEncoding(text);
|
||||
ViewManager.enCodingLabel.setText("\t编码: " + encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取光标所在行号。
|
||||
*
|
||||
* @param caretPosition 光标位置
|
||||
* @param text 文本内容
|
||||
* @return 光标所在行号
|
||||
*/
|
||||
@Override
|
||||
public int getRow(int caretPosition, String text) {
|
||||
caretPosition = Math.min(caretPosition, text.length());
|
||||
String substring = text.substring(0, caretPosition);
|
||||
int count = 0;
|
||||
for (char c : substring.toCharArray()) {
|
||||
if (c == '\n') {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取光标所在列号。
|
||||
*
|
||||
* @param caretPosition 光标位置
|
||||
* @param text 文本内容
|
||||
* @return 光标所在列号
|
||||
*/
|
||||
@Override
|
||||
public int getColumn(int caretPosition, String text) {
|
||||
return caretPosition - text.lastIndexOf("\n", caretPosition - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化标签面板。
|
||||
*/
|
||||
@Override
|
||||
public void initTabPane() {
|
||||
Controller controller = new Controller();
|
||||
|
||||
ViewManager.tabPane.getSelectionModel().selectedItemProperty().addListener((observable, oldTab, newTab) -> {
|
||||
if (newTab != null) {
|
||||
// 获取新选定的标签页并关联的文本区域
|
||||
TextArea textArea = (TextArea) newTab.getContent();
|
||||
|
||||
// 更新状态标签
|
||||
controller.updateStatusLabel(textArea);
|
||||
|
||||
// 监听文本光标位置的变化,更新状态标签
|
||||
textArea.caretPositionProperty().addListener((caretObservable, oldPosition, newPosition) -> controller.updateStatusLabel(textArea));
|
||||
|
||||
// 更新编码标签
|
||||
controller.upDateEncodingLabel(textArea.getText());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUIWithNewTextArea(TextArea textArea) {
|
||||
Tab tab = new Tab("新建文件 " + (++ViewManager.tabIndex));
|
||||
tab.setContent(textArea);
|
||||
ViewManager.tabPane.getTabs().add(tab);
|
||||
ViewManager.tabPane.getSelectionModel().select(tab);
|
||||
updateStatusLabel(textArea);
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置文本区域。
|
||||
*
|
||||
* @param textArea 文本区域
|
||||
*/
|
||||
private void configureTextArea(TextArea textArea) {
|
||||
textArea.setWrapText(true);
|
||||
upDateEncodingLabel(textArea.getText());
|
||||
updateStatusLabel(textArea);
|
||||
|
||||
textArea.textProperty().addListener((observable, oldValue, newValue) -> updateStatusLabel(textArea));
|
||||
|
||||
autoSave(textArea);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新的文本区域。
|
||||
*
|
||||
* @return 新的文本区域
|
||||
*/
|
||||
private TextArea createNewTextArea() {
|
||||
return new TextArea();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新的标签页。
|
||||
*
|
||||
* @param tabName 标签名
|
||||
* @param textArea 文本区域
|
||||
* @return 新的标签页
|
||||
*/
|
||||
private Tab createNewTab(String tabName, TextArea textArea) {
|
||||
Tab tab = new Tab(tabName);
|
||||
tab.setContent(textArea);
|
||||
tab.setUserData(null);
|
||||
return tab;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建打开文件的任务。
|
||||
*
|
||||
* @param file 文件对象
|
||||
* @return 打开文件的任务
|
||||
*/
|
||||
private Task<Void> createOpenFileTask(File file) {
|
||||
TextArea textArea = createNewTextArea();
|
||||
return new Task<>() {
|
||||
@Override
|
||||
protected Void call() {
|
||||
getText(file);
|
||||
upDateEncodingLabel(textArea.getText());
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开文件。
|
||||
*
|
||||
* @param file 文件对象
|
||||
*/
|
||||
private void openFile(File file) {
|
||||
Task<Void> openFileTask = createOpenFileTask(file);
|
||||
Thread thread = new Thread(openFileTask);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,285 @@
|
||||
package org.jcnc.jnotepad.controller.plugin;
|
||||
|
||||
import org.jcnc.jnotepad.app.common.manager.ThreadPoolManager;
|
||||
import org.jcnc.jnotepad.app.utils.JsonUtil;
|
||||
import org.jcnc.jnotepad.app.utils.LoggerUtil;
|
||||
import org.jcnc.jnotepad.controller.config.PluginConfigController;
|
||||
import org.jcnc.jnotepad.controller.exception.AppException;
|
||||
import org.jcnc.jnotepad.controller.plugin.interfaces.Plugin;
|
||||
import org.jcnc.jnotepad.controller.plugin.manager.PluginManager;
|
||||
import org.jcnc.jnotepad.model.entity.PluginDescriptor;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
/**
|
||||
* 插件加载类
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class PluginLoader {
|
||||
private static final PluginLoader INSTANCE = new PluginLoader();
|
||||
|
||||
private static final ExecutorService THREAD_POOL = ThreadPoolManager.getThreadPool();
|
||||
Logger logger = LoggerUtil.getLogger(this.getClass());
|
||||
|
||||
/**
|
||||
* 从插件jar包中读取 json 文件到 PluginDescriptor 类
|
||||
*
|
||||
* @param pluginJar jar 包
|
||||
*/
|
||||
public static PluginDescriptor readPlugin(File pluginJar) throws IOException {
|
||||
InputStream is;
|
||||
StringBuilder sb;
|
||||
try (JarFile jarFile = new JarFile(pluginJar)) {
|
||||
ZipEntry zipEntry = jarFile.getEntry("META-INF/plugin.json");
|
||||
is = jarFile.getInputStream(zipEntry);
|
||||
|
||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
|
||||
String temp;
|
||||
sb = new StringBuilder();
|
||||
while ((temp = br.readLine()) != null) {
|
||||
sb.append(temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
return JsonUtil.OBJECT_MAPPER.readValue(sb.toString(), PluginDescriptor.class);
|
||||
}
|
||||
|
||||
public static PluginLoader getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查插件
|
||||
*
|
||||
* @param configPluginDescriptors 配置文件插件信息
|
||||
* @param pluginDescriptor 插件信息类
|
||||
* @param pluginDescriptors 插件信息集合
|
||||
* @return boolean 是否检查通过
|
||||
* @apiNote
|
||||
* @since 2023/9/16 14:04
|
||||
*/
|
||||
private static boolean checkPlugin(List<PluginDescriptor> configPluginDescriptors, PluginDescriptor pluginDescriptor, List<PluginDescriptor> pluginDescriptors) {
|
||||
// 如果应用程序配置文件中没有该插件则默认禁用
|
||||
if (pluginDoesNotExist(pluginDescriptor, configPluginDescriptors)) {
|
||||
disabledByDefault(configPluginDescriptors, pluginDescriptor, pluginDescriptors);
|
||||
THREAD_POOL.submit(() -> {
|
||||
PluginConfigController.getInstance().writeConfig();
|
||||
ThreadPoolManager.threadContSelfSubtracting();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
// 如果应用程序配置文件中该插件禁用则不加载
|
||||
for (PluginDescriptor configPluginDescriptor : configPluginDescriptors) {
|
||||
if (disableDoNotLoad(pluginDescriptor, pluginDescriptors, configPluginDescriptor)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// 判断该插件是否已经加载
|
||||
return loaded(pluginDescriptor, pluginDescriptors);
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件不存在
|
||||
*
|
||||
* @param pluginDescriptor 插件描述类
|
||||
* @param configPluginDescriptors 配置文件插件信息集合
|
||||
* @return boolean 插件不存在
|
||||
* @apiNote
|
||||
* @since 2023/9/19 19:16
|
||||
*/
|
||||
private static boolean pluginDoesNotExist(PluginDescriptor pluginDescriptor, List<PluginDescriptor> configPluginDescriptors) {
|
||||
for (PluginDescriptor configPluginDescriptor : configPluginDescriptors) {
|
||||
if (configPluginDescriptor.getId().equals(pluginDescriptor.getId())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean loaded(PluginDescriptor pluginDescriptor, List<PluginDescriptor> pluginDescriptors) {
|
||||
Iterator<PluginDescriptor> iterator = pluginDescriptors.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
PluginDescriptor plugin = iterator.next();
|
||||
if (plugin.getId().equals(pluginDescriptor.getId())) {
|
||||
if (plugin.getVersion().equals(pluginDescriptor.getVersion())) {
|
||||
return true;
|
||||
}
|
||||
// 如果当前插件版本更低则更新
|
||||
if (plugin.getVersion().compareTo(pluginDescriptor.getVersion()) < 0) {
|
||||
// 删除当前的插件
|
||||
iterator.remove();
|
||||
} else {
|
||||
throw new AppException("当前加载的插件版本低于本地版本!");
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果插件被禁用则不加载
|
||||
*
|
||||
* @param pluginDescriptor 插件描述类
|
||||
* @param pluginDescriptors 插件描述类集合
|
||||
* @param configPluginDescriptor 配置文件插件信息
|
||||
* @return boolean
|
||||
* @apiNote
|
||||
* @since 2023/9/19 18:45
|
||||
*/
|
||||
private static boolean disableDoNotLoad(PluginDescriptor pluginDescriptor, List<PluginDescriptor> pluginDescriptors, PluginDescriptor configPluginDescriptor) {
|
||||
if (configPluginDescriptor.getId().equals(pluginDescriptor.getId()) && !configPluginDescriptor.isEnabled()) {
|
||||
pluginDescriptor.setEnabled(false);
|
||||
pluginDescriptors.add(pluginDescriptor);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认禁用
|
||||
*
|
||||
* @param configPluginDescriptors 配置文件插件信息
|
||||
* @param pluginDescriptor 插件描述类
|
||||
* @param pluginDescriptors 插件描述类集合
|
||||
* @apiNote
|
||||
* @since 2023/9/19 18:48
|
||||
*/
|
||||
private static void disabledByDefault(List<PluginDescriptor> configPluginDescriptors, PluginDescriptor pluginDescriptor, List<PluginDescriptor> pluginDescriptors) {
|
||||
pluginDescriptor.setEnabled(false);
|
||||
pluginDescriptors.add(pluginDescriptor);
|
||||
configPluginDescriptors.add(pluginDescriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载插件
|
||||
*
|
||||
* @param pluginFilePath 插件文件的路径
|
||||
*/
|
||||
public void loadPluginByPath(String pluginFilePath) {
|
||||
File file = new File(pluginFilePath);
|
||||
loadPluginByFile(file, PluginConfigController.getInstance().getConfig().getPlugins());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件加载插件
|
||||
*
|
||||
* @param pluginJar 插件jar包
|
||||
* @param configPluginDescriptors 配置文件插件信息集合
|
||||
* @apiNote
|
||||
* @since 2023/9/16 14:05
|
||||
*/
|
||||
public void loadPluginByFile(File pluginJar, List<PluginDescriptor> configPluginDescriptors) {
|
||||
PluginManager pluginManager = PluginManager.getInstance();
|
||||
Map<String, List<String>> categories = pluginManager.getLoadedPluginsByCategory();
|
||||
List<PluginDescriptor> pluginDescriptors = pluginManager.getPluginDescriptors();
|
||||
if (pluginJar.exists() && pluginJar.isFile()) {
|
||||
try {
|
||||
PluginDescriptor pluginDescriptor = readPlugin(pluginJar);
|
||||
// 检查插件状态
|
||||
if (checkPlugin(configPluginDescriptors, pluginDescriptor, pluginDescriptors)) {
|
||||
return;
|
||||
}
|
||||
pluginDescriptor.setEnabled(true);
|
||||
pluginDescriptors.add(pluginDescriptor);
|
||||
// 创建URLClassLoader以加载Jar文件中的类
|
||||
Class<?> pluginClass = loaderJarFileClass(pluginJar, pluginDescriptor);
|
||||
if (pluginClass == null) {
|
||||
return;
|
||||
}
|
||||
Plugin plugin;
|
||||
plugin = (Plugin) pluginClass.getDeclaredConstructor().newInstance();
|
||||
pluginDescriptor.setPlugin(plugin);
|
||||
categories.computeIfAbsent(pluginDescriptor.getCategory(), k -> new ArrayList<>()).add(pluginDescriptor.getName());
|
||||
} catch (IOException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
|
||||
throw new AppException(e);
|
||||
} catch (ClassNotFoundException e) {
|
||||
logger.error("无法找到对应的插件类!", e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
logger.error("插件类中没有找到指定方法!", e);
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.info("PluginDescriptor file not found");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载类中的class文件并返回插件主类
|
||||
*
|
||||
* @param pluginJar 插件jar包
|
||||
* @param pluginDescriptor 插件描述
|
||||
* @return java.lang.Class<?> 插件主类
|
||||
* @apiNote
|
||||
* @since 2023/9/19 14:00
|
||||
*/
|
||||
private Class<?> loaderJarFileClass(File pluginJar, PluginDescriptor pluginDescriptor) throws IOException, ClassNotFoundException {
|
||||
Class<?> pluginClass;
|
||||
try (
|
||||
URLClassLoader classLoader = new URLClassLoader(new URL[]{pluginJar.toURI().toURL()});
|
||||
JarFile jar = new JarFile(pluginJar)
|
||||
) {
|
||||
// 加载插件所需的依赖类
|
||||
Enumeration<JarEntry> entries = jar.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry entry = entries.nextElement();
|
||||
if (entry.getName().endsWith(".class")) {
|
||||
String className = entry.getName().replace("/", ".").replace(".class", "");
|
||||
if (!pluginDescriptor.getMainClass().equals(className) && !"module-info".equals(className)) {
|
||||
classLoader.loadClass(className);
|
||||
}
|
||||
}
|
||||
}
|
||||
pluginClass = classLoader.loadClass(pluginDescriptor.getMainClass());
|
||||
}
|
||||
logger.info("已加载插件:{}", pluginDescriptor.getName());
|
||||
return pluginClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* 装载插件
|
||||
*
|
||||
* @since 2023/9/15 21:39
|
||||
*/
|
||||
public void loadPlugins() {
|
||||
// 扫描并装载插件
|
||||
scanLoadPlugins(PluginConfigController.getInstance().getPlungsPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描插件
|
||||
*
|
||||
* @param pluginsPath 插件路径
|
||||
* @apiNote 扫描所有插件,更新配置文件中的插件信息
|
||||
* @since 2023/9/16 0:21
|
||||
*/
|
||||
|
||||
private void scanLoadPlugins(Path pluginsPath) {
|
||||
if (!Files.isDirectory(pluginsPath)) {
|
||||
try {
|
||||
Files.createDirectory(pluginsPath);
|
||||
} catch (IOException e) {
|
||||
throw new AppException("这不是一个有效的路径!");
|
||||
}
|
||||
}
|
||||
// 获取插件加载器
|
||||
PluginLoader pluginLoader = PluginLoader.getInstance();
|
||||
try (Stream<Path> pathStream = Files.walk(pluginsPath)) {
|
||||
pathStream.filter(path -> path.toString().endsWith(".jar")).forEach(path -> pluginLoader.loadPluginByPath(path.toString()));
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,118 @@
|
||||
package org.jcnc.jnotepad.controller.plugin;
|
||||
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import org.jcnc.jnotepad.app.utils.LoggerUtil;
|
||||
import org.jcnc.jnotepad.app.utils.PopUpUtil;
|
||||
import org.jcnc.jnotepad.app.utils.UiUtil;
|
||||
import org.jcnc.jnotepad.controller.plugin.manager.PluginManager;
|
||||
import org.jcnc.jnotepad.ui.component.stage.dialog.factory.impl.BasicFileChooserFactory;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 插件管理界面
|
||||
* <p>
|
||||
* 用于演示插件加载和执行的界面。
|
||||
*
|
||||
* @author luke gewuyou
|
||||
*/
|
||||
public class PluginManagerInterface {
|
||||
private static final PluginManagerInterface INSTANCE = new PluginManagerInterface();
|
||||
Logger logger = LoggerUtil.getLogger(this.getClass());
|
||||
|
||||
public static PluginManagerInterface getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动插件演示界面
|
||||
*
|
||||
* @param primaryStage JavaFX的主舞台
|
||||
*/
|
||||
public void start(Stage primaryStage) {
|
||||
PluginManager pluginManager = PluginManager.getInstance();
|
||||
|
||||
FileChooser fileChooser = BasicFileChooserFactory.getInstance().createFileChooser(
|
||||
"选择插件",
|
||||
null,
|
||||
null,
|
||||
new FileChooser.ExtensionFilter("JAR Files", "*.jar")
|
||||
);
|
||||
Button loadButton = createLoadButton(primaryStage, fileChooser, pluginManager);
|
||||
|
||||
Button executeButton = new Button("执行插件");
|
||||
executeButton.setOnAction(event -> pluginManager.executePlugins());
|
||||
|
||||
VBox root = new VBox(10, loadButton, executeButton);
|
||||
Scene scene = new Scene(root, 300, 200);
|
||||
primaryStage.getIcons().add(UiUtil.getAppIcon());
|
||||
primaryStage.setScene(scene);
|
||||
primaryStage.setTitle("插件演示");
|
||||
primaryStage.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示已加载插件的信息
|
||||
*
|
||||
* @param primaryStage JavaFX的主舞台
|
||||
* @param pluginManager 插件管理器
|
||||
*/
|
||||
private void displayPluginInfo(Stage primaryStage, PluginManager pluginManager) {
|
||||
Map<String, List<String>> loadedPluginsByCategory = pluginManager.getLoadedPluginsByCategory();
|
||||
VBox infoBox = new VBox();
|
||||
|
||||
loadedPluginsByCategory.forEach((key, pluginNames) -> {
|
||||
Label categoryLabel = new Label("类别: " + key);
|
||||
VBox categoryInfoBox = new VBox();
|
||||
for (String pluginName : pluginNames) {
|
||||
Label pluginLabel = new Label("插件名称: " + pluginName);
|
||||
categoryInfoBox.getChildren().add(pluginLabel);
|
||||
}
|
||||
infoBox.getChildren().addAll(categoryLabel, categoryInfoBox);
|
||||
});
|
||||
Scene infoScene = new Scene(infoBox, 400, 300);
|
||||
Stage infoStage = new Stage();
|
||||
infoStage.setScene(infoScene);
|
||||
infoStage.setTitle("已加载的插件");
|
||||
infoStage.initOwner(primaryStage);
|
||||
infoStage.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建加载插件的按钮
|
||||
*
|
||||
* @param primaryStage JavaFX的主舞台
|
||||
* @param fileChooser 文件选择器
|
||||
* @param pluginManager 插件管理器
|
||||
* @return 加载插件的按钮
|
||||
*/
|
||||
private Button createLoadButton(Stage primaryStage, FileChooser fileChooser, PluginManager pluginManager) {
|
||||
Button loadButton = new Button("加载插件");
|
||||
loadButton.setOnAction(event -> {
|
||||
try {
|
||||
File selectedFile = fileChooser.showOpenDialog(primaryStage);
|
||||
if (selectedFile != null) {
|
||||
String pluginFilePath = selectedFile.getAbsolutePath();
|
||||
PluginLoader.getInstance().loadPluginByPath(pluginFilePath);
|
||||
|
||||
// 更新插件信息显示
|
||||
displayPluginInfo(primaryStage, pluginManager);
|
||||
} else {
|
||||
PopUpUtil.infoAlert(null, null, "未找到插件!", null, null);
|
||||
logger.info("未找到插件!");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("加载插件失败!", e);
|
||||
}
|
||||
});
|
||||
return loadButton;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
plugin 存放插件相关的代码,包括插件接口和管理器。
|
||||
@ -0,0 +1,27 @@
|
||||
package org.jcnc.jnotepad.controller.plugin.interfaces;
|
||||
|
||||
|
||||
/**
|
||||
* 插件接口
|
||||
* <p>
|
||||
* 描述插件的基本功能。
|
||||
*
|
||||
* @author luke gewuyou
|
||||
*/
|
||||
public interface Plugin {
|
||||
|
||||
/**
|
||||
* 初始化插件
|
||||
*/
|
||||
void initialize();
|
||||
|
||||
/**
|
||||
* 执行插件的逻辑
|
||||
*/
|
||||
void execute();
|
||||
|
||||
/**
|
||||
* 销毁资源
|
||||
*/
|
||||
void destroyed();
|
||||
}
|
||||
@ -0,0 +1,222 @@
|
||||
package org.jcnc.jnotepad.controller.plugin.manager;
|
||||
|
||||
import org.jcnc.jnotepad.app.common.manager.ThreadPoolManager;
|
||||
import org.jcnc.jnotepad.app.manager.ApplicationManager;
|
||||
import org.jcnc.jnotepad.app.utils.LoggerUtil;
|
||||
import org.jcnc.jnotepad.app.utils.PopUpUtil;
|
||||
import org.jcnc.jnotepad.controller.config.PluginConfigController;
|
||||
import org.jcnc.jnotepad.model.entity.PluginDescriptor;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jcnc.jnotepad.controller.plugin.PluginLoader.readPlugin;
|
||||
|
||||
/**
|
||||
* 插件管理器
|
||||
* <p>
|
||||
* 该类用于管理插件的加载和执行。
|
||||
* 插件可以通过加载外部JAR文件中的类来扩展应用程序的功能。
|
||||
*
|
||||
* @author luke
|
||||
*/
|
||||
public class PluginManager {
|
||||
private static final PluginManager INSTANCE = new PluginManager();
|
||||
/**
|
||||
* 插件类别
|
||||
*/
|
||||
private final Map<String, List<String>> categories = new HashMap<>();
|
||||
Logger logger = LoggerUtil.getLogger(this.getClass());
|
||||
/**
|
||||
* 插件信息
|
||||
*/
|
||||
private List<PluginDescriptor> pluginDescriptors = new ArrayList<>();
|
||||
/**
|
||||
* 插件信息临时集合
|
||||
*/
|
||||
private List<PluginDescriptor> temporaryPluginDescriptors;
|
||||
|
||||
private PluginManager() {
|
||||
|
||||
}
|
||||
|
||||
public static PluginManager getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化插件临时集合
|
||||
*/
|
||||
public void initializeTemporaryPluginDescriptors() {
|
||||
temporaryPluginDescriptors = new ArrayList<>(pluginDescriptors.size());
|
||||
pluginDescriptors.forEach(pluginDescriptor -> temporaryPluginDescriptors.add(new PluginDescriptor(pluginDescriptor)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 卸载插件
|
||||
*
|
||||
* @param pluginDescriptor 插件信息类
|
||||
* @since 2023/9/11 12:28
|
||||
*/
|
||||
public void unloadPlugin(PluginDescriptor pluginDescriptor) {
|
||||
// 删除集合中的插件信息
|
||||
ThreadPoolManager.getThreadPool().submit(() -> {
|
||||
// 移除插件管理类中的插件描述类
|
||||
pluginDescriptors.remove(pluginDescriptor);
|
||||
// 移除插件配置文件类中的插件描述类
|
||||
PluginConfigController instance = PluginConfigController.getInstance();
|
||||
instance.getConfig().getPlugins().remove(pluginDescriptor);
|
||||
|
||||
// 刷新配置
|
||||
instance.writeConfig();
|
||||
// 删除本地插件jar包
|
||||
Path plungsPath = instance.getPlungsPath();
|
||||
try (Stream<Path> pathStream = Files.walk(plungsPath)) {
|
||||
pathStream.filter(path -> path.toString().endsWith(".jar")).forEach(path -> {
|
||||
try {
|
||||
File pluginJar = new File(path.toString());
|
||||
PluginDescriptor temp = readPlugin(pluginJar);
|
||||
if (temp.getId().equals(pluginDescriptor.getId())) {
|
||||
Files.delete(pluginJar.toPath());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
}
|
||||
ThreadPoolManager.threadContSelfSubtracting();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用插件
|
||||
*
|
||||
* @param pluginDescriptor 需要禁用的某个插件的插件类
|
||||
* @apiNote
|
||||
* @since 2023/9/11 12:34
|
||||
*/
|
||||
public void disablePlugIn(PluginDescriptor pluginDescriptor) {
|
||||
pluginDescriptor.setEnabled(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化所有启用的插件
|
||||
*/
|
||||
public void initPlugins() {
|
||||
for (PluginDescriptor pluginDescriptor : pluginDescriptors) {
|
||||
if (pluginDescriptor.isEnabled()) {
|
||||
pluginDescriptor.getPlugin().initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行插件
|
||||
*
|
||||
* @param pluginDescriptor 需要执行的插件的信息类
|
||||
* @apiNote
|
||||
* @since 2023/9/16 14:58
|
||||
*/
|
||||
public void executePlugin(PluginDescriptor pluginDescriptor) {
|
||||
pluginDescriptor.getPlugin().execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行加载的插件
|
||||
*
|
||||
* @deprecated 待删除
|
||||
*/
|
||||
public void executePlugins() {
|
||||
for (PluginDescriptor pluginDescriptor : pluginDescriptors) {
|
||||
if (pluginDescriptor.isEnabled()) {
|
||||
pluginDescriptor.getPlugin().execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁插件可能申请的资源
|
||||
*/
|
||||
public void destroyPlugins() {
|
||||
for (PluginDescriptor pluginDescriptor : pluginDescriptors) {
|
||||
if (pluginDescriptor.isEnabled() && pluginDescriptor.getPlugin() != null) {
|
||||
pluginDescriptor.getPlugin().destroyed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取按类别分类的已加载插件
|
||||
*
|
||||
* @return 插件类别映射
|
||||
*/
|
||||
public Map<String, List<String>> getLoadedPluginsByCategory() {
|
||||
return categories;
|
||||
}
|
||||
|
||||
public List<PluginDescriptor> getPluginDescriptors() {
|
||||
return pluginDescriptors;
|
||||
}
|
||||
|
||||
|
||||
public List<PluginDescriptor> getTemporaryPluginDescriptors() {
|
||||
return temporaryPluginDescriptors;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用插件
|
||||
*
|
||||
* @param pluginDescriptor 插件信息类
|
||||
*/
|
||||
public void enablePlugIn(PluginDescriptor pluginDescriptor) {
|
||||
pluginDescriptor.setEnabled(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存插件设置并退出
|
||||
*/
|
||||
public void saveAndExitSettings() {
|
||||
settingsChange();
|
||||
clearTemporarySettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置更改
|
||||
*/
|
||||
private void settingsChange() {
|
||||
boolean equals = temporaryPluginDescriptors.equals(pluginDescriptors);
|
||||
if (!equals) {
|
||||
pluginDescriptors = temporaryPluginDescriptors;
|
||||
PopUpUtil.questionAlert("更改", "程序与插件更新", "请重启程序以应用插件中的更改!",
|
||||
appDialog -> {
|
||||
appDialog.close();
|
||||
// 执行重启操作
|
||||
ApplicationManager.getInstance().restart();
|
||||
}, null, "重启", "以后再说");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存插件设置但不退出
|
||||
*/
|
||||
public void saveNotExitSettings() {
|
||||
settingsChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除插件临时设置
|
||||
*/
|
||||
public void clearTemporarySettings() {
|
||||
temporaryPluginDescriptors = null;
|
||||
}
|
||||
}
|
||||
1
src/main/java/org/jcnc/jnotepad/model/aReadme.md
Normal file
1
src/main/java/org/jcnc/jnotepad/model/aReadme.md
Normal file
@ -0,0 +1 @@
|
||||
model 存放模型相关的代码,包括实体类和枚举。
|
||||
123
src/main/java/org/jcnc/jnotepad/model/entity/Cache.java
Normal file
123
src/main/java/org/jcnc/jnotepad/model/entity/Cache.java
Normal file
@ -0,0 +1,123 @@
|
||||
package org.jcnc.jnotepad.model.entity;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
/**
|
||||
* 缓存类
|
||||
*
|
||||
* @author gewuyou
|
||||
*/
|
||||
public class Cache {
|
||||
/**
|
||||
* 命名空间
|
||||
*/
|
||||
@JsonIgnore
|
||||
private String namespace;
|
||||
/**
|
||||
* 组
|
||||
*/
|
||||
@JsonIgnore
|
||||
private String group;
|
||||
/**
|
||||
* 缓存名称
|
||||
*/
|
||||
@JsonIgnore
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 缓存数据
|
||||
*/
|
||||
private Object cacheData;
|
||||
/**
|
||||
* 过期时间<br/>如果过期时间为负数则永不过期
|
||||
*/
|
||||
private Long expirationTime;
|
||||
/**
|
||||
* 上次读或写时间
|
||||
*/
|
||||
private Long lastReadOrWriteTime;
|
||||
|
||||
public Cache() {
|
||||
|
||||
}
|
||||
|
||||
public Cache(String namespace, String group, String name, Object cacheData, Long expirationTime) {
|
||||
this.namespace = namespace;
|
||||
this.group = group;
|
||||
this.name = name;
|
||||
this.cacheData = cacheData;
|
||||
this.expirationTime = expirationTime;
|
||||
this.lastReadOrWriteTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成缓存key
|
||||
*
|
||||
* @param namespace 命名空间
|
||||
* @param group 组
|
||||
* @param name 缓存名称
|
||||
* @return 缓存key
|
||||
*/
|
||||
public static String getCacheKey(String namespace, String group, String name) {
|
||||
return namespace + "." + group + "." + name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存key
|
||||
*
|
||||
* @return key
|
||||
*/
|
||||
@JsonIgnore
|
||||
public String getCacheKey() {
|
||||
return getCacheKey(namespace, group, name);
|
||||
}
|
||||
|
||||
public String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
public void setNamespace(String namespace) {
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
public String getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
public void setGroup(String group) {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Object getCacheData() {
|
||||
return cacheData;
|
||||
}
|
||||
|
||||
public void setCacheData(Object cacheData) {
|
||||
this.cacheData = cacheData;
|
||||
}
|
||||
|
||||
public Long getExpirationTime() {
|
||||
return expirationTime;
|
||||
}
|
||||
|
||||
public void setExpirationTime(Long expirationTime) {
|
||||
this.expirationTime = expirationTime;
|
||||
}
|
||||
|
||||
public Long getLastReadOrWriteTime() {
|
||||
return lastReadOrWriteTime;
|
||||
}
|
||||
|
||||
public void setLastReadOrWriteTime(Long lastReadOrWriteTime) {
|
||||
this.lastReadOrWriteTime = lastReadOrWriteTime;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user