Compare commits
846 commits
4.60.0-dot
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
a0d3e7ce1f | ||
|
aae672824c | ||
|
117f40917d | ||
|
bf4630ec65 | ||
|
2bda3e1950 | ||
|
a2cd9fb702 | ||
|
634eb9c652 | ||
|
05939b2725 | ||
|
3f96ecf9ae | ||
|
148f548565 | ||
|
e949083fda | ||
|
93358382b4 | ||
|
e59310d5fc | ||
|
2b8941fad9 | ||
|
cb39c58f98 | ||
|
51f43ce318 | ||
|
85f3f472f7 | ||
|
e1f2d7f2d8 | ||
|
bed18263e6 | ||
|
104cd14f35 | ||
|
1ee5a7f9d2 | ||
|
6699f514a0 | ||
|
0f16031512 | ||
|
cb324f35fc | ||
|
f78e4913a2 | ||
|
9b63e2e661 | ||
|
29127a73ee | ||
|
746b79d61a | ||
|
5131c537f5 | ||
|
79e9b6198e | ||
|
fe31a0a7ed | ||
|
57b72ba938 | ||
|
7f485c5f78 | ||
|
03ed1948e2 | ||
|
3776730709 | ||
|
77ba1eb26f | ||
|
ddebd7a5c6 | ||
|
5f8735d1f3 | ||
|
41e137e797 | ||
|
2470d10afc | ||
|
dee0c81b58 | ||
|
4504ea77c3 | ||
|
ce32fe3814 | ||
|
48c7b673c1 | ||
|
ed128a36f5 | ||
|
ee972660c0 | ||
|
3907e7636e | ||
|
9cf9258835 | ||
|
0459adac69 | ||
|
cf3345303d | ||
|
5761be84d8 | ||
|
0b6eb6d5b3 | ||
|
4a5e2316e0 | ||
|
77ea65b8de | ||
|
0f32107272 | ||
|
393050c1ae | ||
|
31e8c51fba | ||
|
2ece7d4ad5 | ||
|
c34d28bcd4 | ||
|
1d3126f884 | ||
|
81638809c8 | ||
|
c51d670956 | ||
|
db4fe52ec8 | ||
|
f292d757de | ||
|
a39e180d13 | ||
|
854bdccdd3 | ||
|
73a1b085ee | ||
|
5035d24f2c | ||
|
3c3312f5a2 | ||
|
aaf391508c | ||
|
76e4071239 | ||
|
2ce3c7f75e | ||
|
415f55c3a7 | ||
|
3de64e7d99 | ||
|
e41be1c157 | ||
|
d97777b262 | ||
|
884d49a14f | ||
|
05d22968ae | ||
|
891b0a5b36 | ||
|
0fd1e011a1 | ||
|
130d5492f0 | ||
|
c11e950415 | ||
|
549330d0aa | ||
|
654c2d2930 | ||
|
8004b258a9 | ||
|
7f22061c3f | ||
|
4a883851e6 | ||
|
d3372dda27 | ||
|
0ff0efa3c6 | ||
|
8b16f3183c | ||
|
eeb95398e0 | ||
|
b62648aa23 | ||
|
79a3b85e73 | ||
|
e5d0c3d471 | ||
|
935285f715 | ||
|
955a7cba07 | ||
|
e7b380ee7e | ||
|
ca66e38c99 | ||
|
d368af0547 | ||
|
3952e1a438 | ||
|
440a34888f | ||
|
003a03dfeb | ||
|
f3a7e10ce0 | ||
|
db1f02af2f | ||
|
fe41f3cd1f | ||
|
d7c24da2bf | ||
|
2e6b74f0f1 | ||
|
74098a440b | ||
|
43c49380a2 | ||
|
def60b9ba8 | ||
|
7cf4c102fe | ||
|
ee794099bf | ||
|
cb0da5887d | ||
|
fe7fbcbc44 | ||
|
fb5fdbdd5c | ||
|
041c5ba4e6 | ||
|
71c50d40b4 | ||
|
5efbff1cb8 | ||
|
a78498fcca | ||
|
f06fcdc0a6 | ||
|
982cb755ba | ||
|
8b4e19a0cd | ||
|
8729971855 | ||
|
cdba184ed8 | ||
|
ad3465d9ba | ||
|
0bcd49a1b7 | ||
|
f1cd369302 | ||
|
ad76b5deeb | ||
|
635baca3cd | ||
|
74c1f7b337 | ||
|
5052aece6a | ||
|
4fe61e42d2 | ||
|
5b784253f5 | ||
|
15e56bd47d | ||
|
f97de3468b | ||
|
e902230468 | ||
|
d8d5227240 | ||
|
2eed0afaeb | ||
|
a10d9d3430 | ||
|
07f772c680 | ||
|
fcd03f4b6f | ||
|
a22884e2e3 | ||
|
d72a7672c1 | ||
|
3507b2708b | ||
|
cd7967fe4a | ||
|
4d02f3a515 | ||
|
3e0ade9797 | ||
|
3b7a17f6b6 | ||
|
27e073b03c | ||
|
d447633ec3 | ||
|
15af7b7d06 | ||
|
c24d8b6b87 | ||
|
f0eab8c1bd | ||
|
8bd030f1a1 | ||
|
34b935a196 | ||
|
efa236369f | ||
|
af9794b2c2 | ||
|
8e6fa8100a | ||
|
4a5f2d2907 | ||
|
8f74aa5c8d | ||
|
ba0e5fed78 | ||
|
3275279f9c | ||
|
0cf29cc20f | ||
|
fdbdd18120 | ||
|
73c692ebcc | ||
|
5ae9d3c88a | ||
|
ad67fe233e | ||
|
83bdad20f8 | ||
|
1ae844e7fc | ||
|
2de8770897 | ||
|
2cc5b70aed | ||
|
2a4831ac63 | ||
|
86afe5ae57 | ||
|
6b9cb23b6d | ||
|
c4e37eff04 | ||
|
62eb1f5781 | ||
|
98f383bffb | ||
|
25ed11c581 | ||
|
57528fdb5c | ||
|
6aabac7637 | ||
|
c8da36c9e3 | ||
|
3a7d72354a | ||
|
f07d658e87 | ||
|
c88bd1f2ec | ||
|
a3936a60a9 | ||
|
83745112a6 | ||
|
c963e86d3e | ||
|
8f76e053f0 | ||
|
282559db46 | ||
|
185d0997bb | ||
|
a138621f9a | ||
|
e34564fb38 | ||
|
a059d92c8e | ||
|
e3211b18ab | ||
|
dc84814eb2 | ||
|
199901cc4c | ||
|
800decc53f | ||
|
e047ffe684 | ||
|
83bb20ff3d | ||
|
517262690f | ||
|
d84cd10c04 | ||
|
f3270e00ec | ||
|
336d3baeb9 | ||
|
7da029ebf8 | ||
|
85e11e88b7 | ||
|
078fc3b1d4 | ||
|
71d1c850f8 | ||
|
e764cc07b8 | ||
|
2af2d59c4d | ||
|
16aea9c37f | ||
|
5b0f6950a3 | ||
|
81abea7164 | ||
|
de383504e0 | ||
|
2d9a91911b | ||
|
cef6e8e0cd | ||
|
58e2e936a3 | ||
|
de3beca51a | ||
|
84024bc302 | ||
|
2492b2353d | ||
|
6762f9c395 | ||
|
7e88e04cee | ||
|
0e9442353c | ||
|
59cbf63116 | ||
|
ed8d867e76 | ||
|
4ca5cf084a | ||
|
edbc55262b | ||
|
805727fed9 | ||
|
8effae33f4 | ||
|
6a64f8cfd2 | ||
|
6b20da8c59 | ||
|
529c285489 | ||
|
ad78b3b1ac | ||
|
81c16c6b5f | ||
|
d03347d39c | ||
|
b216a25480 | ||
|
81127fae93 | ||
|
d0c558f5c8 | ||
|
73fa593c25 | ||
|
d9e42ed2e7 | ||
|
ca0e825e88 | ||
|
2821880b3c | ||
|
3ca7703fcd | ||
|
c8d01725f1 | ||
|
7fdee8f91b | ||
|
268686635f | ||
|
0c77b57659 | ||
|
a309549dbb | ||
|
54d6d68a03 | ||
|
54d8f698d7 | ||
|
79b6b74f78 | ||
|
02f3ae25be | ||
|
c3a6db921f | ||
|
cc08622d06 | ||
|
73a6629045 | ||
|
5f312f3ae9 | ||
|
86976f50c3 | ||
|
a964716404 | ||
|
ca26ac8f37 | ||
|
62fec93502 | ||
|
b6aa5b5406 | ||
|
92d77a5ba8 | ||
|
ee54470f9a | ||
|
9a55191b08 | ||
|
0782708507 | ||
|
4180f59c4c | ||
|
493530dc14 | ||
|
747ff76d08 | ||
|
8c30a5fd72 | ||
|
560ab62e88 | ||
|
7bdabeb998 | ||
|
cf27988cbb | ||
|
8357653c81 | ||
|
6e4be7bb60 | ||
|
de58236c7a | ||
|
e7a6465a08 | ||
|
05d9773737 | ||
|
051a0a1952 | ||
|
d39a13d355 | ||
|
9d0041a73b | ||
|
20edf9f858 | ||
|
8af1193f0c | ||
|
b3a5e6dfbf | ||
|
3c3fc1f7ac | ||
|
5711c9f9de | ||
|
18a044f162 | ||
|
eef70d2aaf | ||
|
3b258ba639 | ||
|
9d1e4a8c93 | ||
|
99fd18335e | ||
|
17f8d76a60 | ||
|
8b61ab6f11 | ||
|
5accdda30f | ||
|
1c4574908b | ||
|
70085de109 | ||
|
27566df25a | ||
|
e2083920ee | ||
|
d117aabecd | ||
|
f8f05796aa | ||
|
d7b16be284 | ||
|
98140066fc | ||
|
fac982124d | ||
|
5b20cc1cee | ||
|
a410f19659 | ||
|
f869052f8e | ||
|
ab993d49ba | ||
|
331774cb5f | ||
|
4e760967cd | ||
|
67863949af | ||
|
96be8aea51 | ||
|
eeeb7ac9ba | ||
|
a97468db15 | ||
|
471f6d1ec4 | ||
|
e597fd0a90 | ||
|
40c6d737e3 | ||
|
eeb9eb9070 | ||
|
7862cf7110 | ||
|
5a3fda96e7 | ||
|
2c6c726962 | ||
|
9f5db18951 | ||
|
cde0fa74af | ||
|
ed34934872 | ||
|
4cd7b03c9b | ||
|
cb1dd38250 | ||
|
fda1ca8e20 | ||
|
a85b8d46b0 | ||
|
7402936eab | ||
|
6c00c133b8 | ||
|
155738a19d | ||
|
3b9176cbb1 | ||
|
77b0f906eb | ||
|
dad366884d | ||
|
a8221cad50 | ||
|
bd7b6d894c | ||
|
265ce998ef | ||
|
878e94c10a | ||
|
30af9fc52c | ||
|
37ab4d70ac | ||
|
4f192944c8 | ||
|
32acd89856 | ||
|
33edf9ad64 | ||
|
5133225298 | ||
|
be17021dcf | ||
|
aa8478f124 | ||
|
5adb01ce25 | ||
|
e9c3750f1e | ||
|
1f8270c4c6 | ||
|
f69322799d | ||
|
42335d486f | ||
|
95fd3c53a2 | ||
|
52052a8b9d | ||
|
14e8cbae3d | ||
|
6aeb8a94a1 | ||
|
5722f1ab09 | ||
|
b886e7d7a1 | ||
|
99743b9347 | ||
|
ad7b83cf44 | ||
|
b1b11ab819 | ||
|
b85128f752 | ||
|
4b9cbd240a | ||
|
3d6229dad5 | ||
|
8b23740fca | ||
|
c3bab8a351 | ||
|
28df2058ed | ||
|
d9f73b7ead | ||
|
97d7515a0e | ||
|
241e9d2f8a | ||
|
891bc93317 | ||
|
e620bdb22c | ||
|
0178e9b14f | ||
|
091900fb9b | ||
|
1f6a96cf99 | ||
|
570e20b398 | ||
|
2d019612fe | ||
|
9a606c0a7a | ||
|
bb4cbc8e85 | ||
|
02f8fc44bf | ||
|
4a43ca9ff3 | ||
|
25349c3724 | ||
|
e21d222e2e | ||
|
e21896fce3 | ||
|
5195fee765 | ||
|
9f10f7f5e0 | ||
|
bb62b9b7ad | ||
|
c9500acc3b | ||
|
68b5272bb6 | ||
|
7850d16642 | ||
|
4e24b582f1 | ||
|
be497ae8ff | ||
|
443cbf8420 | ||
|
3de2b4aa56 | ||
|
744ea8eccd | ||
|
8d5a32ff2c | ||
|
b406df0d6c | ||
|
4c27fb2cbd | ||
|
27618d1393 | ||
|
e9a2285288 | ||
|
f1bf250742 | ||
|
23717fdf42 | ||
|
c6c77ccf01 | ||
|
07ab15770a | ||
|
b9761311f6 | ||
|
a480f7a717 | ||
|
3915830985 | ||
|
807b1e6bf2 | ||
|
1127106d75 | ||
|
9e4a5e4bd6 | ||
|
2a470045f2 | ||
|
af97d49f91 | ||
|
ce5ccb0df6 | ||
|
70ba9b7569 | ||
|
9d7d07e9f3 | ||
|
6514982490 | ||
|
d0e8617abf | ||
|
185fa370db | ||
|
94c561a3bc | ||
|
4c3fd1977d | ||
|
7c9a66f7c0 | ||
|
1b7bc347b3 | ||
|
9c63abb8c5 | ||
|
9fb0d87181 | ||
|
591d05ce88 | ||
|
2482dff2d6 | ||
|
6d41b8395c | ||
|
4ebc727679 | ||
|
d4c8fc4850 | ||
|
92bac182bd | ||
|
32078421c6 | ||
|
aaa6d0e79d | ||
|
5d70b94e47 | ||
|
696a181129 | ||
|
bd0d18b380 | ||
|
86a09c2fe0 | ||
|
e7fe7b4cbf | ||
|
bcc799ce4a | ||
|
2222b00bd1 | ||
|
0e2ddaf7fd | ||
|
cd9b5e4b93 | ||
|
5fe61b7499 | ||
|
961f7ba137 | ||
|
8227dc60d1 | ||
|
686937e8f9 | ||
|
b2817f8608 | ||
|
7745cffbfb | ||
|
c940a9c8b4 | ||
|
2b4d2067a5 | ||
|
d8ac92ab1e | ||
|
0e0e5f8135 | ||
|
ab49e2eb5a | ||
|
065163fc73 | ||
|
860b258748 | ||
|
73cc4de08b | ||
|
da0b84be09 | ||
|
142e3cba53 | ||
|
678453d23d | ||
|
d06d59c0f1 | ||
|
091740af51 | ||
|
7ae9e0c2e7 | ||
|
0370306fe6 | ||
|
cdcbbd5cc0 | ||
|
9c48aefecf | ||
|
953992a962 | ||
|
5ed9fd4888 | ||
|
888e393886 | ||
|
231ff526b7 | ||
|
74ae30d8a1 | ||
|
f363bf97c1 | ||
|
4244c7bca0 | ||
|
a16a7d477d | ||
|
86ba815e0f | ||
|
b5bb47d17b | ||
|
537998b48b | ||
|
b1d1847c7b | ||
|
cd277b5540 | ||
|
217a6468f1 | ||
|
d1571a3dd9 | ||
|
4b24b7b3c1 | ||
|
f8c461d011 | ||
|
33159bc552 | ||
|
856d110996 | ||
|
cee4a417c7 | ||
|
72afe193a7 | ||
|
ec758db906 | ||
|
4d4d31ee72 | ||
|
1a22c3f103 | ||
|
b91ef6510d | ||
|
258e0dcb35 | ||
|
b90dd922e7 | ||
|
d522aa91eb | ||
|
8124dbe5c8 | ||
|
13d2e3422a | ||
|
05e336a2a5 | ||
|
b840a5b741 | ||
|
70697281ef | ||
|
2f1ffee12e | ||
|
19a41b9b38 | ||
|
2170b4a19f | ||
|
9ba3c8042e | ||
|
fb61584c0b | ||
|
f24d4b585c | ||
|
9805f004c2 | ||
|
c64022522e | ||
|
b6f55ad830 | ||
|
669b739696 | ||
|
8ec1ca1fb9 | ||
|
9af024cd82 | ||
|
da047a0c99 | ||
|
5be4ac34a9 | ||
|
2539e2cbb0 | ||
|
e9a9f299fc | ||
|
f1bcd8fe49 | ||
|
9a31a75be1 | ||
|
4f1429cc97 | ||
|
6c480dcf1c | ||
|
3e6cd243e3 | ||
|
35fabbaa09 | ||
|
8ae79ac25c | ||
|
f89e652d56 | ||
|
bae0152eaa | ||
|
f37b826586 | ||
|
8a405a2eaa | ||
|
a80a9436dc | ||
|
adc4709f34 | ||
|
9497746ab1 | ||
|
16c8e685c7 | ||
|
86cec41af4 | ||
|
80d2d463f8 | ||
|
8cefdad2d6 | ||
|
9b409083ee | ||
|
3cf96acf03 | ||
|
30d4241a34 | ||
|
2bd702f4f8 | ||
|
e0e1ba855f | ||
|
2364090661 | ||
|
1135563ee2 | ||
|
559333fcc2 | ||
|
e7744edb45 | ||
|
c3f937b294 | ||
|
9648b9034c | ||
|
2049d258f3 | ||
|
2ecb45481b | ||
|
af372a27a2 | ||
|
9a27c36234 | ||
|
45e55b43f6 | ||
|
0861d66339 | ||
|
dbf8183070 | ||
|
43702eff5f | ||
|
f7056a327c | ||
|
add16aa4ee | ||
|
8a68674026 | ||
|
332042d7d0 | ||
|
433d71421e | ||
|
7510d18518 | ||
|
0bd7b8e74a | ||
|
4b971f7d0a | ||
|
8cbd4a355a | ||
|
8c53d1c400 | ||
|
c0e952c930 | ||
|
1e3e721f36 | ||
|
9692dd1ea2 | ||
|
6eb14b7408 | ||
|
1c2aa9dd77 | ||
|
768e73a71c | ||
|
bba37766a7 | ||
|
95f5c643b0 | ||
|
3fc99031fd | ||
|
244cff57a4 | ||
|
6dd947b9cc | ||
|
8ef45d6643 | ||
|
f272da19b8 | ||
|
f466284325 | ||
|
bc81040fe0 | ||
|
790c811161 | ||
|
0afa04f1ea | ||
|
f71aa97c51 | ||
|
c7b2cfcc45 | ||
|
29ef2b4b25 | ||
|
463e121634 | ||
|
6bf2426c55 | ||
|
2fb75ffae4 | ||
|
540cfa9e1c | ||
|
37231510f6 | ||
|
85be5ae0a9 | ||
|
c9b273c934 | ||
|
4227cc3c91 | ||
|
209680701f | ||
|
59cf979031 | ||
|
fb7854c254 | ||
|
f02c2c6f02 | ||
|
70c8f26c9c | ||
|
c64c82c254 | ||
|
4260a4661f | ||
|
2db64b8367 | ||
|
226d4a39ad | ||
|
0a26d35faf | ||
|
43aaed1fd9 | ||
|
1dfb36f84a | ||
|
b28fe017b5 | ||
|
2a9fc313bc | ||
|
f90ce637aa | ||
|
af7aa7ab9f | ||
|
2dba726a7a | ||
|
b38ad064d3 | ||
|
fde83bb0cb | ||
|
a837c27ddd | ||
|
f405e9488c | ||
|
ccde400f29 | ||
|
6438e573df | ||
|
b81e406663 | ||
|
5d960c3dc8 | ||
|
c1fac73612 | ||
|
f883868828 | ||
|
9540947b48 | ||
|
7e0f042d9f | ||
|
5e938d0792 | ||
|
85e18deee9 | ||
|
bc9fa18855 | ||
|
35e5e23dbe | ||
|
ece20996a3 | ||
|
23b69a923c | ||
|
186ddb8722 | ||
|
672ed9755b | ||
|
c9cdf53176 | ||
|
31f4a9b558 | ||
|
bf9d1214f0 | ||
|
9681968974 | ||
|
d039a27418 | ||
|
e1dbdeecf6 | ||
|
e60f243503 | ||
|
b3a1ec1804 | ||
|
4fd596cd74 | ||
|
a8c4001e40 | ||
|
019c8bb930 | ||
|
d055bc6e43 | ||
|
2c79871bef | ||
|
1a228d708e | ||
|
99fac15540 | ||
|
177470e009 | ||
|
5ba4a5b099 | ||
|
47980d6923 | ||
|
8c315807ab | ||
|
499a019aac | ||
|
9e85401d53 | ||
|
15e3b5d55b | ||
|
c008805e97 | ||
|
7f6890c85c | ||
|
ba8b71ea25 | ||
|
9b59796dd5 | ||
|
16df165603 | ||
|
dcc18abb51 | ||
|
9db1b63c01 | ||
|
c2d72ee260 | ||
|
1dbbc39340 | ||
|
2dacb0c11f | ||
|
5ebdda0e42 | ||
|
a66883f4f4 | ||
|
7381699d8c | ||
|
1ab67bcfc0 | ||
|
fd8c01a828 | ||
|
b3ea8e4568 | ||
|
7b739eca08 | ||
|
b7b9005dcd | ||
|
a26a9c5854 | ||
|
77d9c2f4b1 | ||
|
70fc4a6382 | ||
|
a55c2d2efe | ||
|
86bab67f83 | ||
|
e41f88df93 | ||
|
f8188f68ab | ||
|
c6e13e5ddb | ||
|
314478e3ca | ||
|
71d3885cea | ||
|
dfb2ee72b1 | ||
|
9f40a47d30 | ||
|
d9993df5e9 | ||
|
0574702876 | ||
|
ee05d5b129 | ||
|
05fd8ca891 | ||
|
0ed90a1dfd | ||
|
43ea63bb7c | ||
|
be574eeb83 | ||
|
533ab49c90 | ||
|
63d15e9903 | ||
|
3113cc6d36 | ||
|
0e8680ce78 | ||
|
73d65ffdc1 | ||
|
371a883edc | ||
|
2f706252a0 | ||
|
0b8ca5257f | ||
|
3f76d680e1 | ||
|
b65b451c60 | ||
|
4591fb7ac8 | ||
|
62caa3b06e | ||
|
8835067a0a | ||
|
5569ea7ec2 | ||
|
03922d77b6 | ||
|
bf2f17e1d9 | ||
|
a526b282cd | ||
|
0b803ecd90 | ||
|
28235c3c85 | ||
|
384bc816c6 | ||
|
679af95b0e | ||
|
8a5d7631f8 | ||
|
194c745e24 | ||
|
6a49a1e68e | ||
|
f04c3729a2 | ||
|
3fa3b52634 | ||
|
c0e2d8ab98 | ||
|
47e7cfaa9a | ||
|
eafb9200f5 | ||
|
413cda0e58 | ||
|
0f3db9e612 | ||
|
c37147df8c | ||
|
77b87a4ab9 | ||
|
6e5b8a5385 | ||
|
6b42448a9d | ||
|
0b911c451c | ||
|
09cabec59b | ||
|
a3d4bc9261 | ||
|
3411eed910 | ||
|
581b5b6bc7 | ||
|
c076bc71c4 | ||
|
aae8a54e2c | ||
|
f1f60b03a1 | ||
|
0ecf5e3a86 | ||
|
dba12e59f2 | ||
|
074fc76936 | ||
|
ab5d97965b | ||
|
8bb44eadf5 | ||
|
01958bfc07 | ||
|
2b2b2212ec | ||
|
a65f6c7a1d | ||
|
f3e7c9558c | ||
|
d2ef85154a | ||
|
50e34b3348 | ||
|
578f52b433 | ||
|
ecd94860ad | ||
|
e71a18f207 | ||
|
b3f8f065d6 | ||
|
06b8a97dc5 | ||
|
64c3c38c0b | ||
|
b717694e9c | ||
|
85a6a18c25 | ||
|
b8c97d43f6 | ||
|
05e44e6aa5 | ||
|
5dd1e48f04 | ||
|
f837e05dc6 | ||
|
dc799034fa | ||
|
5d44e439ed | ||
|
a979e1691e | ||
|
76e7f1b39b | ||
|
71fc4144bf | ||
|
d6a772a9e8 | ||
|
4ff123583c | ||
|
176812c9da | ||
|
8e84b218d5 | ||
|
4b768d72c6 | ||
|
3531560b2a | ||
|
ae1fd61041 | ||
|
a2c824eaf1 | ||
|
c82747e8d4 | ||
|
62a3e3b867 | ||
|
48a95eeb2e | ||
|
ef6c82e164 | ||
|
fefd3949cf | ||
|
6e4b1aa5d4 | ||
|
0e0a03cdc0 | ||
|
fa0d3e1bc7 | ||
|
0a5f252a8d | ||
|
428d4d005c | ||
|
5be7bfde0f | ||
|
2d15591cd0 | ||
|
3e5ae43886 | ||
|
f7c31ab1cf | ||
|
cf408bbd98 | ||
|
48c5cc836e | ||
|
967a086605 | ||
|
06b1b57f2f | ||
|
55998c7c34 | ||
|
831a7bf72e | ||
|
c502543038 | ||
|
5cc855ba6a | ||
|
708eaeae91 | ||
|
6d163bb085 | ||
|
f3a745bb63 | ||
|
8e999500ba | ||
|
4bf7932f76 | ||
|
4948e4f502 | ||
|
aa09d8c2a1 | ||
|
39e729c03b | ||
|
eddbe0843c | ||
|
20924d97f9 | ||
|
76af6444f8 | ||
|
3bc17e75a8 | ||
|
834c286d54 | ||
|
11319709e1 | ||
|
313da2d5d5 | ||
|
051fa57d15 | ||
|
638cd0f97e | ||
|
a52db075d9 | ||
|
fc42b0d45c | ||
|
ad5b82654f | ||
|
1140e01230 | ||
|
18d656b0bd | ||
|
78fabef418 | ||
|
01d6561d35 | ||
|
f9c244dffe | ||
|
dbdefb3c10 | ||
|
96a4e9a983 | ||
|
1b4ad34987 | ||
|
8f5b4793c2 | ||
|
070fe55107 | ||
|
495dd3988d | ||
|
24bde45954 | ||
|
544e38be13 | ||
|
4d0d90eb18 | ||
|
84cf2611c0 | ||
|
16402d8b46 | ||
|
1f5fb16bc4 | ||
|
6af5235283 | ||
|
9f87cdbb25 | ||
|
22d8b7bcd4 | ||
|
8a8ae3df15 | ||
|
e8794f1c5e | ||
|
562eb86fbc | ||
|
847430a509 | ||
|
b85ad8e12a | ||
|
c99896edba | ||
|
48bb8fe24c | ||
|
ccd08b8e0a | ||
|
0ecc48f4f7 | ||
|
41e9e2121b | ||
|
6ad6d6583f | ||
|
151c85a52c | ||
|
303c3c0ad2 | ||
|
0f6dc9de90 | ||
|
a35687ac51 | ||
|
5fa5adc73e | ||
|
6267f27f5b | ||
|
00397d411f | ||
|
02fb5e7341 | ||
|
6096a5eeea | ||
|
04c6f88ecb | ||
|
e67527b2a4 | ||
|
7a74089157 | ||
|
05f24d565a | ||
|
5ee22c0568 |
|
@ -60,11 +60,13 @@ Anything else you think would be helpful?
|
||||||
These items may solve your problem. Please check those you've done by changing - [ ] to - [X]
|
These items may solve your problem. Please check those you've done by changing - [ ] to - [X]
|
||||||
|
|
||||||
- [ ] Searched main docs for your problem www.PySimpleGUI.org
|
- [ ] Searched main docs for your problem www.PySimpleGUI.org
|
||||||
- [ ] Looked for Demo Programs that are similar to your goal Demos.PySimpleGUI.org
|
- [ ] Looked for Demo Programs that are similar to your goal. It is recommend you use the Demo Browser! Demos.PySimpleGUI.org
|
||||||
|
- [ ] None of your GUI code was generated by an AI algorithm like GPT
|
||||||
- [ ] If not tkinter - looked for Demo Programs for specific port
|
- [ ] If not tkinter - looked for Demo Programs for specific port
|
||||||
- [ ] For non tkinter - Looked at readme for your specific port if not PySimpleGUI (Qt, WX, Remi)
|
- [ ] For non tkinter - Looked at readme for your specific port if not PySimpleGUI (Qt, WX, Remi)
|
||||||
- [ ] Run your program outside of your debugger (from a command line)
|
- [ ] Run your program outside of your debugger (from a command line)
|
||||||
- [ ] Searched through Issues (open and closed) to see if already reported Issues.PySimpleGUI.org
|
- [ ] Searched through Issues (open and closed) to see if already reported Issues.PySimpleGUI.org
|
||||||
|
- [ ] Have upgraded to the latest release of PySimpleGUI on PyPI (lastest official version)
|
||||||
- [ ] Tried using the PySimpleGUI.py file on GitHub. Your problem may have already been fixed but not released
|
- [ ] Tried using the PySimpleGUI.py file on GitHub. Your problem may have already been fixed but not released
|
||||||
|
|
||||||
#### Detailed Description
|
#### Detailed Description
|
||||||
|
|
Before Width: | Height: | Size: 184 KiB |
|
@ -1,233 +0,0 @@
|
||||||
import PySimpleGUI as sg
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import chess
|
|
||||||
import chess.pgn
|
|
||||||
import copy
|
|
||||||
import chess.uci
|
|
||||||
|
|
||||||
CHESS_PATH = '.' # path to the chess pieces
|
|
||||||
|
|
||||||
BLANK = 0 # piece names
|
|
||||||
PAWNB = 1
|
|
||||||
KNIGHTB = 2
|
|
||||||
BISHOPB = 3
|
|
||||||
ROOKB = 4
|
|
||||||
KINGB = 5
|
|
||||||
QUEENB = 6
|
|
||||||
PAWNW = 7
|
|
||||||
KNIGHTW = 8
|
|
||||||
BISHOPW = 9
|
|
||||||
ROOKW = 10
|
|
||||||
KINGW = 11
|
|
||||||
QUEENW = 12
|
|
||||||
|
|
||||||
initial_board = [[ROOKB, KNIGHTB, BISHOPB, QUEENB, KINGB, BISHOPB, KNIGHTB, ROOKB],
|
|
||||||
[PAWNB, ] * 8,
|
|
||||||
[BLANK, ] * 8,
|
|
||||||
[BLANK, ] * 8,
|
|
||||||
[BLANK, ] * 8,
|
|
||||||
[BLANK, ] * 8,
|
|
||||||
[PAWNW, ] * 8,
|
|
||||||
[ROOKW, KNIGHTW, BISHOPW, QUEENW, KINGW, BISHOPW, KNIGHTW, ROOKW]]
|
|
||||||
|
|
||||||
blank = os.path.join(CHESS_PATH, 'blank.png')
|
|
||||||
bishopB = os.path.join(CHESS_PATH, 'nbishopb.png')
|
|
||||||
bishopW = os.path.join(CHESS_PATH, 'nbishopw.png')
|
|
||||||
pawnB = os.path.join(CHESS_PATH, 'npawnb.png')
|
|
||||||
pawnW = os.path.join(CHESS_PATH, 'npawnw.png')
|
|
||||||
knightB = os.path.join(CHESS_PATH, 'nknightb.png')
|
|
||||||
knightW = os.path.join(CHESS_PATH, 'nknightw.png')
|
|
||||||
rookB = os.path.join(CHESS_PATH, 'nrookb.png')
|
|
||||||
rookW = os.path.join(CHESS_PATH, 'nrookw.png')
|
|
||||||
queenB = os.path.join(CHESS_PATH, 'nqueenb.png')
|
|
||||||
queenW = os.path.join(CHESS_PATH, 'nqueenw.png')
|
|
||||||
kingB = os.path.join(CHESS_PATH, 'nkingb.png')
|
|
||||||
kingW = os.path.join(CHESS_PATH, 'nkingw.png')
|
|
||||||
|
|
||||||
images = {BISHOPB: bishopB, BISHOPW: bishopW, PAWNB: pawnB, PAWNW: pawnW, KNIGHTB: knightB, KNIGHTW: knightW,
|
|
||||||
ROOKB: rookB, ROOKW: rookW, KINGB: kingB, KINGW: kingW, QUEENB: queenB, QUEENW: queenW, BLANK: blank}
|
|
||||||
|
|
||||||
|
|
||||||
def open_pgn_file(filename):
|
|
||||||
pgn = open(filename)
|
|
||||||
first_game = chess.pgn.read_game(pgn)
|
|
||||||
moves = [move for move in first_game.main_line()]
|
|
||||||
return moves
|
|
||||||
|
|
||||||
|
|
||||||
def render_square(image, key, location):
|
|
||||||
if (location[0] + location[1]) % 2:
|
|
||||||
color = '#B58863'
|
|
||||||
else:
|
|
||||||
color = '#F0D9B5'
|
|
||||||
return sg.RButton('', image_filename=image, size=(1, 1), button_color=('white', color), pad=(0, 0), key=key)
|
|
||||||
|
|
||||||
|
|
||||||
def redraw_board(window, board):
|
|
||||||
for i in range(8):
|
|
||||||
for j in range(8):
|
|
||||||
color = '#B58863' if (i + j) % 2 else '#F0D9B5'
|
|
||||||
piece_image = images[board[i][j]]
|
|
||||||
elem = window.FindElement(key=(i, j))
|
|
||||||
elem.Update(button_color=('white', color),
|
|
||||||
image_filename=piece_image, )
|
|
||||||
|
|
||||||
|
|
||||||
def PlayGame():
|
|
||||||
menu_def = [['&File', ['&Open PGN File', 'E&xit']],
|
|
||||||
['&Help', '&About...'], ]
|
|
||||||
|
|
||||||
# sg.SetOptions(margins=(0,0))
|
|
||||||
sg.ChangeLookAndFeel('GreenTan')
|
|
||||||
# create initial board setup
|
|
||||||
psg_board = copy.deepcopy(initial_board)
|
|
||||||
# the main board display layout
|
|
||||||
board_layout = [[sg.T(' ')] + [sg.T('{}'.format(a), pad=((23, 27), 0), font='Any 13') for a in 'abcdefgh']]
|
|
||||||
# loop though board and create buttons with images
|
|
||||||
for i in range(8):
|
|
||||||
row = [sg.T(str(8 - i) + ' ', font='Any 13')]
|
|
||||||
for j in range(8):
|
|
||||||
piece_image = images[psg_board[i][j]]
|
|
||||||
row.append(render_square(piece_image, key=(i, j), location=(i, j)))
|
|
||||||
row.append(sg.T(str(8 - i) + ' ', font='Any 13'))
|
|
||||||
board_layout.append(row)
|
|
||||||
# add the labels across bottom of board
|
|
||||||
board_layout.append([sg.T(' ')] + [sg.T('{}'.format(a), pad=((23, 27), 0), font='Any 13') for a in 'abcdefgh'])
|
|
||||||
|
|
||||||
# setup the controls on the right side of screen
|
|
||||||
openings = (
|
|
||||||
'Any', 'Defense', 'Attack', 'Trap', 'Gambit', 'Counter', 'Sicillian', 'English', 'French', 'Queen\'s openings',
|
|
||||||
'King\'s Openings', 'Indian Openings')
|
|
||||||
|
|
||||||
board_controls = [[sg.RButton('New Game', key='New Game'), sg.RButton('Draw')],
|
|
||||||
[sg.RButton('Resign Game'), sg.RButton('Set FEN')],
|
|
||||||
[sg.RButton('Player Odds'), sg.RButton('Training')],
|
|
||||||
[sg.Drop(openings), sg.Text('Opening/Style')],
|
|
||||||
[sg.CBox('Play As White', key='_white_')],
|
|
||||||
[sg.Drop([2, 3, 4, 5, 6, 7, 8, 9, 10], size=(3, 1), key='_level_'), sg.Text('Difficulty Level')],
|
|
||||||
[sg.Text('Move List')],
|
|
||||||
[sg.Multiline([], do_not_clear=True, autoscroll=True, size=(15, 10), key='_movelist_')],
|
|
||||||
]
|
|
||||||
|
|
||||||
# layouts for the tabs
|
|
||||||
controls_layout = [[sg.Text('Performance Parameters', font='_ 20')],
|
|
||||||
[sg.T('Put stuff like AI engine tuning parms on this tab')]]
|
|
||||||
|
|
||||||
statistics_layout = [[sg.Text('Statistics', font=('_ 20'))],
|
|
||||||
[sg.T('Game statistics go here?')]]
|
|
||||||
|
|
||||||
board_tab = [[sg.Column(board_layout)]]
|
|
||||||
|
|
||||||
# the main window layout
|
|
||||||
layout = [[sg.Menu(menu_def, tearoff=False)],
|
|
||||||
[sg.TabGroup([[sg.Tab('Board', board_tab),
|
|
||||||
sg.Tab('Controls', controls_layout),
|
|
||||||
sg.Tab('Statistics', statistics_layout)]], title_color='red'),
|
|
||||||
sg.Column(board_controls)],
|
|
||||||
[sg.Text('Click anywhere on board for next move', font='_ 14')]]
|
|
||||||
|
|
||||||
window = sg.Window('Chess',
|
|
||||||
default_button_element_size=(12, 1),
|
|
||||||
auto_size_buttons=False,
|
|
||||||
icon='kingb.ico').Layout(layout)
|
|
||||||
|
|
||||||
filename = sg.PopupGetFile('\n'.join(('To begin, set location of AI EXE file',
|
|
||||||
'If you have not done so already, download the engine',
|
|
||||||
'Download the StockFish Chess engine at: https://stockfishchess.org/download/')),
|
|
||||||
file_types=(('Chess AI Engine EXE File', '*.exe'),))
|
|
||||||
if filename is None:
|
|
||||||
sys.exit()
|
|
||||||
engine = chess.uci.popen_engine(filename)
|
|
||||||
engine.uci()
|
|
||||||
info_handler = chess.uci.InfoHandler()
|
|
||||||
engine.info_handlers.append(info_handler)
|
|
||||||
|
|
||||||
board = chess.Board()
|
|
||||||
move_count = 1
|
|
||||||
move_state = move_from = move_to = 0
|
|
||||||
# ---===--- Loop taking in user input --- #
|
|
||||||
while not board.is_game_over():
|
|
||||||
|
|
||||||
if board.turn == chess.WHITE:
|
|
||||||
engine.position(board)
|
|
||||||
|
|
||||||
# human_player(board)
|
|
||||||
move_state = 0
|
|
||||||
while True:
|
|
||||||
button, value = window.Read()
|
|
||||||
if button in (None, 'Exit'):
|
|
||||||
exit()
|
|
||||||
if button == 'New Game':
|
|
||||||
sg.Popup('You have to restart the program to start a new game... sorry....')
|
|
||||||
break
|
|
||||||
psg_board = copy.deepcopy(initial_board)
|
|
||||||
redraw_board(window, psg_board)
|
|
||||||
move_state = 0
|
|
||||||
break
|
|
||||||
level = value['_level_']
|
|
||||||
if type(button) is tuple:
|
|
||||||
if move_state == 0:
|
|
||||||
move_from = button
|
|
||||||
row, col = move_from
|
|
||||||
piece = psg_board[row][col] # get the move-from piece
|
|
||||||
button_square = window.FindElement(key=(row, col))
|
|
||||||
button_square.Update(button_color=('white', 'red'))
|
|
||||||
move_state = 1
|
|
||||||
elif move_state == 1:
|
|
||||||
move_to = button
|
|
||||||
row, col = move_to
|
|
||||||
if move_to == move_from: # cancelled move
|
|
||||||
color = '#B58863' if (row + col) % 2 else '#F0D9B5'
|
|
||||||
button_square.Update(button_color=('white', color))
|
|
||||||
move_state = 0
|
|
||||||
continue
|
|
||||||
|
|
||||||
picked_move = '{}{}{}{}'.format('abcdefgh'[move_from[1]], 8 - move_from[0],
|
|
||||||
'abcdefgh'[move_to[1]], 8 - move_to[0])
|
|
||||||
|
|
||||||
if picked_move in [str(move) for move in board.legal_moves]:
|
|
||||||
board.push(chess.Move.from_uci(picked_move))
|
|
||||||
else:
|
|
||||||
print('Illegal move')
|
|
||||||
move_state = 0
|
|
||||||
color = '#B58863' if (move_from[0] + move_from[1]) % 2 else '#F0D9B5'
|
|
||||||
button_square.Update(button_color=('white', color))
|
|
||||||
continue
|
|
||||||
|
|
||||||
psg_board[move_from[0]][move_from[1]] = BLANK # place blank where piece was
|
|
||||||
psg_board[row][col] = piece # place piece in the move-to square
|
|
||||||
redraw_board(window, psg_board)
|
|
||||||
move_count += 1
|
|
||||||
|
|
||||||
window.FindElement('_movelist_').Update(picked_move + '\n', append=True)
|
|
||||||
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
engine.position(board)
|
|
||||||
best_move = engine.go(searchmoves=board.legal_moves, depth=level, movetime=(level * 100)).bestmove
|
|
||||||
move_str = str(best_move)
|
|
||||||
from_col = ord(move_str[0]) - ord('a')
|
|
||||||
from_row = 8 - int(move_str[1])
|
|
||||||
to_col = ord(move_str[2]) - ord('a')
|
|
||||||
to_row = 8 - int(move_str[3])
|
|
||||||
|
|
||||||
window.FindElement('_movelist_').Update(move_str + '\n', append=True)
|
|
||||||
|
|
||||||
piece = psg_board[from_row][from_col]
|
|
||||||
psg_board[from_row][from_col] = BLANK
|
|
||||||
psg_board[to_row][to_col] = piece
|
|
||||||
redraw_board(window, psg_board)
|
|
||||||
|
|
||||||
board.push(best_move)
|
|
||||||
move_count += 1
|
|
||||||
sg.Popup('Game over!', 'Thank you for playing')
|
|
||||||
|
|
||||||
|
|
||||||
# Download the StockFish Chess engine at: https://stockfishchess.org/download/
|
|
||||||
# engine = chess.uci.popen_engine(r'E:\DownloadsE\stockfish-9-win\Windows\stockfish_9_x64.exe')
|
|
||||||
# engine.uci()
|
|
||||||
# info_handler = chess.uci.InfoHandler()
|
|
||||||
# engine.info_handlers.append(info_handler)
|
|
||||||
# level = 2
|
|
||||||
PlayGame()
|
|
|
@ -1,160 +0,0 @@
|
||||||
import PySimpleGUI as sg
|
|
||||||
import os
|
|
||||||
import chess
|
|
||||||
import chess.pgn
|
|
||||||
import copy
|
|
||||||
import time
|
|
||||||
|
|
||||||
button_names = ('close', 'cookbook', 'cpu', 'github', 'pysimplegui', 'run', 'storage', 'timer')
|
|
||||||
|
|
||||||
CHESS_PATH = '.' # path to the chess pieces
|
|
||||||
|
|
||||||
BLANK = 0 # piece names
|
|
||||||
PAWNB = 1
|
|
||||||
KNIGHTB = 2
|
|
||||||
BISHOPB = 3
|
|
||||||
ROOKB = 4
|
|
||||||
KINGB = 5
|
|
||||||
QUEENB = 6
|
|
||||||
PAWNW = 7
|
|
||||||
KNIGHTW = 8
|
|
||||||
BISHOPW = 9
|
|
||||||
ROOKW = 10
|
|
||||||
KINGW = 11
|
|
||||||
QUEENW = 12
|
|
||||||
|
|
||||||
initial_board = [[ROOKB, KNIGHTB, BISHOPB, KINGB, QUEENB, BISHOPB, KNIGHTB, ROOKB ],
|
|
||||||
[PAWNB,]*8,
|
|
||||||
[BLANK,]*8,
|
|
||||||
[BLANK,]*8,
|
|
||||||
[BLANK,]*8,
|
|
||||||
[BLANK,]*8,
|
|
||||||
[PAWNW,]*8,
|
|
||||||
[ROOKW, KNIGHTW, BISHOPW, KINGW, QUEENW, BISHOPW, KNIGHTW, ROOKW]]
|
|
||||||
|
|
||||||
blank = os.path.join(CHESS_PATH, 'blank.png')
|
|
||||||
bishopB = os.path.join(CHESS_PATH, 'nbishopb.png')
|
|
||||||
bishopW = os.path.join(CHESS_PATH, 'nbishopw.png')
|
|
||||||
pawnB = os.path.join(CHESS_PATH, 'npawnb.png')
|
|
||||||
pawnW = os.path.join(CHESS_PATH, 'npawnw.png')
|
|
||||||
knightB = os.path.join(CHESS_PATH, 'nknightb.png')
|
|
||||||
knightW = os.path.join(CHESS_PATH, 'nknightw.png')
|
|
||||||
rookB = os.path.join(CHESS_PATH, 'nrookb.png')
|
|
||||||
rookW = os.path.join(CHESS_PATH, 'nrookw.png')
|
|
||||||
queenB = os.path.join(CHESS_PATH, 'nqueenB.png')
|
|
||||||
queenW = os.path.join(CHESS_PATH, 'nqueenW.png')
|
|
||||||
kingB = os.path.join(CHESS_PATH, 'nkingb.png')
|
|
||||||
kingW = os.path.join(CHESS_PATH, 'nkingw.png')
|
|
||||||
|
|
||||||
images = {BISHOPB: bishopB, BISHOPW: bishopW, PAWNB: pawnB, PAWNW: pawnW, KNIGHTB: knightB, KNIGHTW: knightW,
|
|
||||||
ROOKB: rookB, ROOKW: rookW, KINGB: kingB, KINGW: kingW, QUEENB: queenB, QUEENW: queenW, BLANK: blank}
|
|
||||||
|
|
||||||
def open_pgn_file(filename):
|
|
||||||
pgn = open(filename)
|
|
||||||
first_game = chess.pgn.read_game(pgn)
|
|
||||||
moves = [move for move in first_game.main_line()]
|
|
||||||
return moves
|
|
||||||
|
|
||||||
def render_square(image, key, location):
|
|
||||||
if (location[0] + location[1]) % 2:
|
|
||||||
color = '#B58863'
|
|
||||||
else:
|
|
||||||
color = '#F0D9B5'
|
|
||||||
return sg.RButton('', image_filename=image, size=(1, 1), button_color=('white', color), pad=(0, 0), key=key)
|
|
||||||
|
|
||||||
def redraw_board(window, board):
|
|
||||||
for i in range(8):
|
|
||||||
for j in range(8):
|
|
||||||
color = '#B58863' if (i+j) % 2 else '#F0D9B5'
|
|
||||||
piece_image = images[board[i][j]]
|
|
||||||
elem = window.FindElement(key=(i,j))
|
|
||||||
elem.Update(button_color = ('white', color),
|
|
||||||
image_filename=piece_image,)
|
|
||||||
|
|
||||||
def PlayGame():
|
|
||||||
|
|
||||||
menu_def = [['&File', ['&Open PGN File', 'E&xit' ]],
|
|
||||||
['&Help', '&About...'],]
|
|
||||||
|
|
||||||
# sg.SetOptions(margins=(0,0))
|
|
||||||
sg.ChangeLookAndFeel('GreenTan')
|
|
||||||
# create initial board setup
|
|
||||||
board = copy.deepcopy(initial_board)
|
|
||||||
# the main board display layout
|
|
||||||
board_layout = [[sg.T(' ')] + [sg.T('{}'.format(a), pad=((23,27),0), font='Any 13') for a in 'abcdefgh']]
|
|
||||||
# loop though board and create buttons with images
|
|
||||||
for i in range(8):
|
|
||||||
row = [sg.T(str(8-i)+' ', font='Any 13')]
|
|
||||||
for j in range(8):
|
|
||||||
piece_image = images[board[i][j]]
|
|
||||||
row.append(render_square(piece_image, key=(i,j), location=(i,j)))
|
|
||||||
row.append(sg.T(str(8-i)+' ', font='Any 13'))
|
|
||||||
board_layout.append(row)
|
|
||||||
# add the labels across bottom of board
|
|
||||||
board_layout.append([sg.T(' ')] + [sg.T('{}'.format(a), pad=((23,27),0), font='Any 13') for a in 'abcdefgh'])
|
|
||||||
|
|
||||||
# setup the controls on the right side of screen
|
|
||||||
openings = ('Any', 'Defense', 'Attack', 'Trap', 'Gambit','Counter', 'Sicillian', 'English','French', 'Queen\'s openings', 'King\'s Openings','Indian Openings')
|
|
||||||
|
|
||||||
board_controls = [[sg.RButton('New Game', key='Open PGN File'), sg.RButton('Draw')],
|
|
||||||
[sg.RButton('Resign Game'), sg.RButton('Set FEN')],
|
|
||||||
[sg.RButton('Player Odds'),sg.RButton('Training') ],
|
|
||||||
[sg.Drop(openings),sg.Text('Opening/Style')],
|
|
||||||
[sg.CBox('Play a White', key='_white_')],
|
|
||||||
[sg.Text('Move List')],
|
|
||||||
[sg.Multiline([], do_not_clear=True, autoscroll=True, size=(15,10),key='_movelist_')],]
|
|
||||||
|
|
||||||
# layouts for the tabs
|
|
||||||
controls_layout = [[sg.Text('Performance Parameters', font='_ 20')],
|
|
||||||
[sg.T('Put stuff like AI engine tuning parms on this tab')]]
|
|
||||||
|
|
||||||
statistics_layout = [[sg.Text('Statistics', font=('_ 20'))],
|
|
||||||
[sg.T('Game statistics go here?')]]
|
|
||||||
|
|
||||||
board_tab = [[sg.Column(board_layout)]]
|
|
||||||
|
|
||||||
# the main window layout
|
|
||||||
layout = [[sg.Menu(menu_def, tearoff=False)],
|
|
||||||
[sg.TabGroup([[sg.Tab('Board',board_tab),
|
|
||||||
sg.Tab('Controls', controls_layout),
|
|
||||||
sg.Tab('Statistics', statistics_layout)]], title_color='red'),
|
|
||||||
sg.Column(board_controls)],
|
|
||||||
[sg.Text('Click anywhere on board for next move', font='_ 14')]]
|
|
||||||
|
|
||||||
window = sg.Window('Chess', default_button_element_size=(12,1), auto_size_buttons=False, icon='kingb.ico').Layout(layout)
|
|
||||||
|
|
||||||
# ---===--- Loop taking in user input --- #
|
|
||||||
i = 0
|
|
||||||
moves = None
|
|
||||||
while True:
|
|
||||||
button, value = window.Read()
|
|
||||||
if button in (None, 'Exit'):
|
|
||||||
break
|
|
||||||
if button == 'Open PGN File':
|
|
||||||
filename = sg.PopupGetFile('', no_window=True)
|
|
||||||
if filename is not None:
|
|
||||||
moves = open_pgn_file(filename)
|
|
||||||
i = 0
|
|
||||||
board = copy.deepcopy(initial_board)
|
|
||||||
window.FindElement('_movelist_').Update(value='')
|
|
||||||
if button == 'About...':
|
|
||||||
sg.Popup('Powerd by Engine Kibitz Chess Engine')
|
|
||||||
if type(button) is tuple and moves is not None and i < len(moves):
|
|
||||||
move = moves[i] # get the current move
|
|
||||||
window.FindElement('_movelist_').Update(value='{} {}\n'.format(i+1, str(move)), append=True)
|
|
||||||
move_from = move.from_square # parse the move-from and move-to squares
|
|
||||||
move_to = move.to_square
|
|
||||||
row, col = move_from // 8, move_from % 8
|
|
||||||
piece = board[row][col] # get the move-from piece
|
|
||||||
button = window.FindElement(key=(row,col))
|
|
||||||
for x in range(3):
|
|
||||||
button.Update(button_color = ('white' , 'red' if x % 2 else 'white'))
|
|
||||||
window.Refresh()
|
|
||||||
time.sleep(.05)
|
|
||||||
board[row][col] = BLANK # place blank where piece was
|
|
||||||
row, col = move_to // 8, move_to % 8 # compute move-to square
|
|
||||||
board[row][col] = piece # place piece in the move-to square
|
|
||||||
redraw_board(window, board)
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
PlayGame()
|
|
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.9 KiB |
BIN
Chess/blank.png
Before Width: | Height: | Size: 1.2 KiB |
|
@ -1,36 +0,0 @@
|
||||||
[Event "Wch U12"]
|
|
||||||
[Site "Duisburg"]
|
|
||||||
[Date "1992.??.??"]
|
|
||||||
[Round "1"]
|
|
||||||
[White "Malakhov, Vladimir"]
|
|
||||||
[Black "Ab Rahman, M."]
|
|
||||||
[Result "1-0"]
|
|
||||||
[WhiteElo ""]
|
|
||||||
[BlackElo ""]
|
|
||||||
[ECO "A05"]
|
|
||||||
|
|
||||||
1.Nf3 Nf6 2.b3 g6 3.Bb2 Bg7 4.g3 d6 5.Bg2 O-O 6.O-O c6 7.d3 e5 8.c4 Ne8 9.Nbd2 f5
|
|
||||||
10.Qc2 Na6 11.c5 Nxc5 12.Nxe5 Qe7 13.d4 Na6 14.Qc4+ Kh8 15.Nef3 Be6 16.Qc3 f4
|
|
||||||
17.gxf4 Rxf4 18.Qe3 Rf8 19.Ng5 Nec7 20.Nc4 Rae8 21.Nxe6 Qxe6 22.Qxe6 Rxe6
|
|
||||||
23.e3 d5 24.Ne5 g5 25.Ba3 Rff6 26.Bh3 Re8 27.Bd7 Rd8 28.Be7 Rxd7 29.Bxf6 1-0
|
|
||||||
|
|
||||||
|
|
||||||
[Event "Wch U12"]
|
|
||||||
[Site "Duisburg"]
|
|
||||||
[Date "1992.??.??"]
|
|
||||||
[Round "2"]
|
|
||||||
[White "Malakhov, Vladimir"]
|
|
||||||
[Black "Berescu, Alin"]
|
|
||||||
[Result "1-0"]
|
|
||||||
[WhiteElo ""]
|
|
||||||
[BlackElo ""]
|
|
||||||
[ECO "D05"]
|
|
||||||
|
|
||||||
1.d4 Nf6 2.Nd2 d5 3.Ngf3 e6 4.e3 c5 5.c3 Nbd7 6.Bd3 Bd6 7.O-O O-O 8.Re1 b6
|
|
||||||
9.e4 dxe4 10.Nxe4 Be7 11.Ne5 Bb7 12.Ng5 g6 13.Qe2 Nxe5 14.dxe5 Nh5 15.Ne4 Qd5
|
|
||||||
16.f4 Rfd8 17.Bc2 Qc6 18.Be3 Rd7 19.Rad1 Rad8 20.Rxd7 Rxd7 21.Nd2 Ng7 22.Be4 Qc8
|
|
||||||
23.g4 Qd8 24.Bxb7 Rxb7 25.Ne4 Rd7 26.c4 h5 27.h3 h4 28.Kh2 Ne8 29.f5 Qc7
|
|
||||||
30.Bf4 Rd4 31.Qf2 Rxc4 32.f6 Qb7 33.Ng5 Bf8 34.b3 Rc3 35.Qd2 Rf3 36.Nxf3 Qxf3
|
|
||||||
37.Qe3 Qd5 38.Qe4 Qd7 39.Qf3 Nc7 40.Rd1 Nd5 41.Bg5 Qc7 42.Re1 b5 43.Qd1 c4
|
|
||||||
44.Qc1 Bb4 45.Bd2 Bxd2 46.Qxd2 Nxf6 47.bxc4 bxc4 48.Qd6 Qa5 49.Rf1 Nd5 50.Qd7 Qd2+
|
|
||||||
51.Kh1 f5 52.exf6 1-0
|
|
BIN
Chess/kingb.ico
Before Width: | Height: | Size: 15 KiB |
BIN
Chess/kingb.png
Before Width: | Height: | Size: 2.2 KiB |
BIN
Chess/kingw.png
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 2.6 KiB |
BIN
Chess/nkingb.png
Before Width: | Height: | Size: 2.6 KiB |
BIN
Chess/nkingw.png
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 2.7 KiB |
BIN
Chess/npawnb.png
Before Width: | Height: | Size: 2.6 KiB |
BIN
Chess/npawnw.png
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.9 KiB |
BIN
Chess/nrookb.png
Before Width: | Height: | Size: 2.6 KiB |
BIN
Chess/nrookw.png
Before Width: | Height: | Size: 2.7 KiB |
BIN
Chess/pawnb.png
Before Width: | Height: | Size: 797 B |
BIN
Chess/pawnw.png
Before Width: | Height: | Size: 1.3 KiB |
BIN
Chess/queenb.png
Before Width: | Height: | Size: 2.4 KiB |
BIN
Chess/queenw.png
Before Width: | Height: | Size: 2.2 KiB |
|
@ -1,16 +0,0 @@
|
||||||
# PySimpleGUI-Chess A Chess Game Playback Program
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
## Introduction
|
|
||||||
This is the start of a front-end GUI for an AI engine that plays chess. It simply reads moves the a PGN file and steps through it showing each of the moves on the board.
|
|
||||||
|
|
||||||
To play against the AI run the program
|
|
||||||
Demo_Chess_AGAINST_AI.py
|
|
||||||
|
|
||||||
Locate where the pacakge was installed and run the programs from that folder. You need to run from the installed folder so that the images of the chess pieces are located.
|
|
||||||
|
|
||||||
## Home Page (GitHub)
|
|
||||||
|
|
||||||
[www.PySimpleGUI.com](www.PySimpleGUI.com)
|
|
|
@ -1,2 +0,0 @@
|
||||||
PySimpleGUI==3.9.1
|
|
||||||
python-chess==0.23.9
|
|
BIN
Chess/rookb.png
Before Width: | Height: | Size: 725 B |
BIN
Chess/rookw.png
Before Width: | Height: | Size: 933 B |
|
@ -5,7 +5,7 @@ import warnings
|
||||||
|
|
||||||
import PySimpleGUI as sg
|
import PySimpleGUI as sg
|
||||||
|
|
||||||
__version__ = '1.7.0'
|
__version__ = '1.12.2'
|
||||||
|
|
||||||
"""
|
"""
|
||||||
PySimpleGUI Demo Program Browser
|
PySimpleGUI Demo Program Browser
|
||||||
|
@ -33,9 +33,15 @@ __version__ = '1.7.0'
|
||||||
|
|
||||||
Keeps a "history" of the previously chosen folders to easy switching between projects
|
Keeps a "history" of the previously chosen folders to easy switching between projects
|
||||||
|
|
||||||
|
Versions:
|
||||||
|
1.8.0 - Addition of option to show ALL file types, not just Python files
|
||||||
|
1.12.0 - Fix for problem with spaces in filename and using an editor specified in the demo program settings
|
||||||
|
1.12.2 - Better error handling for no editor configured
|
||||||
Copyright 2021, 2022 PySimpleGUI.org
|
Copyright 2021, 2022 PySimpleGUI.org
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
python_only = True
|
||||||
|
|
||||||
def get_file_list_dict():
|
def get_file_list_dict():
|
||||||
"""
|
"""
|
||||||
Returns dictionary of files
|
Returns dictionary of files
|
||||||
|
@ -50,7 +56,7 @@ def get_file_list_dict():
|
||||||
demo_files_dict = {}
|
demo_files_dict = {}
|
||||||
for dirname, dirnames, filenames in os.walk(demo_path):
|
for dirname, dirnames, filenames in os.walk(demo_path):
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
if filename.endswith('.py') or filename.endswith('.pyw'):
|
if python_only is not True or filename.endswith('.py') or filename.endswith('.pyw'):
|
||||||
fname_full = os.path.join(dirname, filename)
|
fname_full = os.path.join(dirname, filename)
|
||||||
if filename not in demo_files_dict.keys():
|
if filename not in demo_files_dict.keys():
|
||||||
demo_files_dict[filename] = fname_full
|
demo_files_dict[filename] = fname_full
|
||||||
|
@ -456,7 +462,7 @@ def make_window():
|
||||||
|
|
||||||
|
|
||||||
left_col = sg.Column([
|
left_col = sg.Column([
|
||||||
[sg.Listbox(values=get_file_list(), select_mode=sg.SELECT_MODE_EXTENDED, size=(50,20), bind_return_key=True, key='-DEMO LIST-')],
|
[sg.Listbox(values=get_file_list(), select_mode=sg.SELECT_MODE_EXTENDED, size=(50,20), bind_return_key=True, key='-DEMO LIST-', expand_x=True, expand_y=True)],
|
||||||
[sg.Text('Filter (F1):', tooltip=filter_tooltip), sg.Input(size=(25, 1), focus=True, enable_events=True, key='-FILTER-', tooltip=filter_tooltip),
|
[sg.Text('Filter (F1):', tooltip=filter_tooltip), sg.Input(size=(25, 1), focus=True, enable_events=True, key='-FILTER-', tooltip=filter_tooltip),
|
||||||
sg.T(size=(15,1), k='-FILTER NUMBER-')],
|
sg.T(size=(15,1), k='-FILTER NUMBER-')],
|
||||||
[sg.Button('Run'), sg.B('Edit'), sg.B('Clear'), sg.B('Open Folder'), sg.B('Copy Path')],
|
[sg.Button('Run'), sg.B('Edit'), sg.B('Clear'), sg.B('Open Folder'), sg.B('Copy Path')],
|
||||||
|
@ -468,7 +474,7 @@ def make_window():
|
||||||
[sg.Text('Find (F3):', tooltip=find_re_tooltip), sg.Input(size=(25, 1),key='-FIND RE-', tooltip=find_re_tooltip),sg.B('Find RE')]], k='-RE COL-'))
|
[sg.Text('Find (F3):', tooltip=find_re_tooltip), sg.Input(size=(25, 1),key='-FIND RE-', tooltip=find_re_tooltip),sg.B('Find RE')]], k='-RE COL-'))
|
||||||
|
|
||||||
right_col = [
|
right_col = [
|
||||||
[sg.Multiline(size=(70, 21), write_only=True, key=ML_KEY, reroute_stdout=True, echo_stdout_stderr=True, reroute_cprint=True)],
|
[sg.Multiline(size=(70, 21), write_only=True, expand_x=True, expand_y=True, key=ML_KEY, reroute_stdout=True, echo_stdout_stderr=True, reroute_cprint=True)],
|
||||||
[sg.B('Settings'), sg.Button('Exit')],
|
[sg.B('Settings'), sg.Button('Exit')],
|
||||||
[sg.T('Demo Browser Ver ' + __version__)],
|
[sg.T('Demo Browser Ver ' + __version__)],
|
||||||
[sg.T('PySimpleGUI ver ' + sg.version.split(' ')[0] + ' tkinter ver ' + sg.tclversion_detailed, font='Default 8', pad=(0,0))],
|
[sg.T('PySimpleGUI ver ' + sg.version.split(' ')[0] + ' tkinter ver ' + sg.tclversion_detailed, font='Default 8', pad=(0,0))],
|
||||||
|
@ -479,7 +485,8 @@ def make_window():
|
||||||
options_at_bottom = sg.pin(sg.Column([[sg.CB('Verbose', enable_events=True, k='-VERBOSE-', tooltip='Enable to see the matches in the right hand column'),
|
options_at_bottom = sg.pin(sg.Column([[sg.CB('Verbose', enable_events=True, k='-VERBOSE-', tooltip='Enable to see the matches in the right hand column'),
|
||||||
sg.CB('Show only first match in file', default=True, enable_events=True, k='-FIRST MATCH ONLY-', tooltip='Disable to see ALL matches found in files'),
|
sg.CB('Show only first match in file', default=True, enable_events=True, k='-FIRST MATCH ONLY-', tooltip='Disable to see ALL matches found in files'),
|
||||||
sg.CB('Find ignore case', default=True, enable_events=True, k='-IGNORE CASE-'),
|
sg.CB('Find ignore case', default=True, enable_events=True, k='-IGNORE CASE-'),
|
||||||
sg.CB('Wait for Runs to Complete', default=False, enable_events=True, k='-WAIT-')
|
sg.CB('Wait for Runs to Complete', default=False, enable_events=True, k='-WAIT-'),
|
||||||
|
sg.CB('Show ALL file types', default=not python_only, enable_events=True, k='-SHOW ALL FILES-'),
|
||||||
]],
|
]],
|
||||||
pad=(0,0), k='-OPTIONS BOTTOM-', expand_x=True, expand_y=False), expand_x=True, expand_y=False)
|
pad=(0,0), k='-OPTIONS BOTTOM-', expand_x=True, expand_y=False), expand_x=True, expand_y=False)
|
||||||
|
|
||||||
|
@ -490,16 +497,15 @@ def make_window():
|
||||||
layout = [[sg.Text('PySimpleGUI Demo Program & Project Browser', font='Any 20')],
|
layout = [[sg.Text('PySimpleGUI Demo Program & Project Browser', font='Any 20')],
|
||||||
[choose_folder_at_top],
|
[choose_folder_at_top],
|
||||||
# [sg.Column([[left_col],[ lef_col_find_re]], element_justification='l', expand_x=True, expand_y=True), sg.Column(right_col, element_justification='c', expand_x=True, expand_y=True)],
|
# [sg.Column([[left_col],[ lef_col_find_re]], element_justification='l', expand_x=True, expand_y=True), sg.Column(right_col, element_justification='c', expand_x=True, expand_y=True)],
|
||||||
[sg.Pane([sg.Column([[left_col],[ lef_col_find_re]], element_justification='l', expand_x=True, expand_y=True), sg.Column(right_col, element_justification='c', expand_x=True, expand_y=True) ], orientation='h', relief=sg.RELIEF_SUNKEN, k='-PANE-')],
|
[sg.Pane([sg.Column([[left_col],[ lef_col_find_re]], element_justification='l', expand_x=True, expand_y=True), sg.Column(right_col, element_justification='c', expand_x=True, expand_y=True) ], orientation='h', relief=sg.RELIEF_SUNKEN, expand_x=True, expand_y=True, k='-PANE-')],
|
||||||
[options_at_bottom]]
|
[options_at_bottom, sg.Sizegrip()]]
|
||||||
|
|
||||||
# --------------------------------- Create Window ---------------------------------
|
# --------------------------------- Create Window ---------------------------------
|
||||||
window = sg.Window('PSG Demo & Project Browser', layout, finalize=True, resizable=True, use_default_focus=False, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_EXIT)
|
window = sg.Window('PSG Demo & Project Browser', layout, finalize=True, resizable=True, use_default_focus=False, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_EXIT)
|
||||||
window.set_min_size(window.size)
|
window.set_min_size(window.size)
|
||||||
|
|
||||||
window['-DEMO LIST-'].expand(True, True, True)
|
|
||||||
window[ML_KEY].expand(True, True, True)
|
# window.bind("<Alt_L><x>", 'Exit') # matches the underscore shown on the Exit button (For now disabled this feature until buttons with underscore released to PyPI)
|
||||||
window['-PANE-'].expand(True, True, True)
|
|
||||||
|
|
||||||
window.bind('<F1>', '-FOCUS FILTER-')
|
window.bind('<F1>', '-FOCUS FILTER-')
|
||||||
window.bind('<F2>', '-FOCUS FIND-')
|
window.bind('<F2>', '-FOCUS FIND-')
|
||||||
|
@ -521,11 +527,12 @@ def main():
|
||||||
The main program that contains the event loop.
|
The main program that contains the event loop.
|
||||||
It will call the make_window function to create the window.
|
It will call the make_window function to create the window.
|
||||||
"""
|
"""
|
||||||
|
global python_only
|
||||||
try:
|
try:
|
||||||
version = sg.version
|
version = sg.version
|
||||||
version_parts = version.split('.')
|
version_parts = version.split('.')
|
||||||
major_version, minor_version = int(version_parts[0]), int(version_parts[1])
|
major_version, minor_version = int(version_parts[0]), int(version_parts[1])
|
||||||
if major_version < 4 or minor_version < 32:
|
if major_version < 4 or (major_version== 4 and minor_version < 32):
|
||||||
sg.popup('Warning - Your PySimpleGUI version is less then 4.35.0',
|
sg.popup('Warning - Your PySimpleGUI version is less then 4.35.0',
|
||||||
'As a result, you will not be able to use the EDIT features of this program',
|
'As a result, you will not be able to use the EDIT features of this program',
|
||||||
'Please upgrade to at least 4.35.0',
|
'Please upgrade to at least 4.35.0',
|
||||||
|
@ -570,16 +577,16 @@ def main():
|
||||||
sg.cprint(f'Editing using {editor_program}', c='white on red', end='')
|
sg.cprint(f'Editing using {editor_program}', c='white on red', end='')
|
||||||
sg.cprint('')
|
sg.cprint('')
|
||||||
sg.cprint(f'{full_filename}', c='white on purple')
|
sg.cprint(f'{full_filename}', c='white on purple')
|
||||||
# if line != 1:
|
if not get_editor():
|
||||||
if using_local_editor():
|
sg.popup_error_with_traceback('No editor has been configured', 'You need to configure an editor in order to use this feature', 'You can configure the editor in the Demo Brower Settings or the PySimpleGUI Global Settings')
|
||||||
sg.execute_command_subprocess(editor_program, full_filename)
|
|
||||||
else:
|
else:
|
||||||
try:
|
if using_local_editor():
|
||||||
sg.execute_editor(full_filename, line_number=int(line))
|
sg.execute_command_subprocess(editor_program, f'"{full_filename}"')
|
||||||
except:
|
else:
|
||||||
sg.execute_command_subprocess(editor_program, full_filename)
|
try:
|
||||||
# else:
|
sg.execute_editor(full_filename, line_number=int(line))
|
||||||
# sg.execute_editor(full_filename)
|
except:
|
||||||
|
sg.execute_command_subprocess(editor_program, f'"{full_filename}"')
|
||||||
else:
|
else:
|
||||||
sg.cprint('Editing canceled')
|
sg.cprint('Editing canceled')
|
||||||
elif event == 'Run':
|
elif event == 'Run':
|
||||||
|
@ -717,6 +724,17 @@ def main():
|
||||||
sg.clipboard_set(full_filename)
|
sg.clipboard_set(full_filename)
|
||||||
elif event == 'Version':
|
elif event == 'Version':
|
||||||
sg.popup_scrolled(sg.get_versions(), keep_on_top=True, non_blocking=True)
|
sg.popup_scrolled(sg.get_versions(), keep_on_top=True, non_blocking=True)
|
||||||
|
elif event == '-SHOW ALL FILES-':
|
||||||
|
python_only = not values[event]
|
||||||
|
file_list_dict = get_file_list_dict()
|
||||||
|
file_list = get_file_list()
|
||||||
|
window['-DEMO LIST-'].update(values=file_list)
|
||||||
|
window['-FILTER NUMBER-'].update(f'{len(file_list)} files')
|
||||||
|
window['-ML-'].update('')
|
||||||
|
window['-FIND NUMBER-'].update('')
|
||||||
|
window['-FIND-'].update('')
|
||||||
|
window['-FIND RE-'].update('')
|
||||||
|
window['-FILTER-'].update('')
|
||||||
window.close()
|
window.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
Displays the values dictionary entry for each element
|
Displays the values dictionary entry for each element
|
||||||
And more!
|
And more!
|
||||||
|
|
||||||
Copyright 2021 PySimpleGUI
|
Copyright 2021, 2022, 2023 PySimpleGUI
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import PySimpleGUI as sg
|
import PySimpleGUI as sg
|
||||||
|
@ -36,7 +36,7 @@ def make_window(theme):
|
||||||
sg.Image(data=sg.DEFAULT_BASE64_LOADING_GIF, enable_events=True, key='-GIF-IMAGE-'),],
|
sg.Image(data=sg.DEFAULT_BASE64_LOADING_GIF, enable_events=True, key='-GIF-IMAGE-'),],
|
||||||
[sg.Checkbox('Checkbox', default=True, k='-CB-')],
|
[sg.Checkbox('Checkbox', default=True, k='-CB-')],
|
||||||
[sg.Radio('Radio1', "RadioDemo", default=True, size=(10,1), k='-R1-'), sg.Radio('Radio2', "RadioDemo", default=True, size=(10,1), k='-R2-')],
|
[sg.Radio('Radio1', "RadioDemo", default=True, size=(10,1), k='-R1-'), sg.Radio('Radio2', "RadioDemo", default=True, size=(10,1), k='-R2-')],
|
||||||
[sg.Combo(values=('Combo 1', 'Combo 2', 'Combo 3'), default_value='Combo 1', readonly=True, k='-COMBO-'),
|
[sg.Combo(values=('Combo 1', 'Combo 2', 'Combo 3'), default_value='Combo 1', readonly=False, k='-COMBO-'),
|
||||||
sg.OptionMenu(values=('Option 1', 'Option 2', 'Option 3'), k='-OPTION MENU-'),],
|
sg.OptionMenu(values=('Option 1', 'Option 2', 'Option 3'), k='-OPTION MENU-'),],
|
||||||
[sg.Spin([i for i in range(1,11)], initial_value=10, k='-SPIN-'), sg.Text('Spin')],
|
[sg.Spin([i for i in range(1,11)], initial_value=10, k='-SPIN-'), sg.Text('Spin')],
|
||||||
[sg.Multiline('Demo of a Multi-Line Text Element!\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6\nLine 7\nYou get the point.', size=(45,5), expand_x=True, expand_y=True, k='-MLINE-')],
|
[sg.Multiline('Demo of a Multi-Line Text Element!\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6\nLine 7\nYou get the point.', size=(45,5), expand_x=True, expand_y=True, k='-MLINE-')],
|
||||||
|
@ -88,20 +88,17 @@ def make_window(theme):
|
||||||
|
|
||||||
]]
|
]]
|
||||||
layout[-1].append(sg.Sizegrip())
|
layout[-1].append(sg.Sizegrip())
|
||||||
window = sg.Window('All Elements Demo', layout, right_click_menu=right_click_menu_def, right_click_menu_tearoff=True, grab_anywhere=True, resizable=True, margins=(0,0), use_custom_titlebar=True, finalize=True, keep_on_top=True,
|
window = sg.Window('All Elements Demo', layout, right_click_menu=right_click_menu_def, right_click_menu_tearoff=True, grab_anywhere=True, resizable=True, margins=(0,0), use_custom_titlebar=True, finalize=True, keep_on_top=True)
|
||||||
# scaling=2.0,
|
|
||||||
)
|
|
||||||
window.set_min_size(window.size)
|
window.set_min_size(window.size)
|
||||||
return window
|
return window
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
window = make_window(sg.theme())
|
window = make_window(sg.theme())
|
||||||
|
|
||||||
# This is an Event Loop
|
# This is an Event Loop
|
||||||
while True:
|
while True:
|
||||||
event, values = window.read(timeout=100)
|
event, values = window.read(timeout=100)
|
||||||
# keep an animation running so show things are happening
|
# keep an animation running so show things are happening
|
||||||
window['-GIF-IMAGE-'].update_animation(sg.DEFAULT_BASE64_LOADING_GIF, time_between_frames=100)
|
|
||||||
if event not in (sg.TIMEOUT_EVENT, sg.WIN_CLOSED):
|
if event not in (sg.TIMEOUT_EVENT, sg.WIN_CLOSED):
|
||||||
print('============ Event = ', event, ' ==============')
|
print('============ Event = ', event, ' ==============')
|
||||||
print('-------- Values Dictionary (key=value) --------')
|
print('-------- Values Dictionary (key=value) --------')
|
||||||
|
@ -110,7 +107,9 @@ def main():
|
||||||
if event in (None, 'Exit'):
|
if event in (None, 'Exit'):
|
||||||
print("[LOG] Clicked Exit!")
|
print("[LOG] Clicked Exit!")
|
||||||
break
|
break
|
||||||
elif event == 'About':
|
|
||||||
|
window['-GIF-IMAGE-'].update_animation(sg.DEFAULT_BASE64_LOADING_GIF, time_between_frames=100)
|
||||||
|
if event == 'About':
|
||||||
print("[LOG] Clicked About!")
|
print("[LOG] Clicked About!")
|
||||||
sg.popup('PySimpleGUI Demo All Elements',
|
sg.popup('PySimpleGUI Demo All Elements',
|
||||||
'Right click anywhere to see right click menu',
|
'Right click anywhere to see right click menu',
|
||||||
|
@ -151,7 +150,7 @@ def main():
|
||||||
elif event == 'Edit Me':
|
elif event == 'Edit Me':
|
||||||
sg.execute_editor(__file__)
|
sg.execute_editor(__file__)
|
||||||
elif event == 'Versions':
|
elif event == 'Versions':
|
||||||
sg.popup(sg.get_versions(), keep_on_top=True)
|
sg.popup_scrolled(__file__, sg.get_versions(), keep_on_top=True, non_blocking=True)
|
||||||
|
|
||||||
window.close()
|
window.close()
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|
|
@ -8,7 +8,8 @@ import PySimpleGUI as sg
|
||||||
Copyright 2022 PySimpleGUI
|
Copyright 2022 PySimpleGUI
|
||||||
"""
|
"""
|
||||||
|
|
||||||
use_custom_titlebar = False
|
|
||||||
|
use_custom_titlebar = True if sg.running_trinket() else False
|
||||||
|
|
||||||
def make_window(theme=None):
|
def make_window(theme=None):
|
||||||
|
|
||||||
|
@ -50,7 +51,7 @@ def make_window(theme=None):
|
||||||
[name('Image'), sg.Image(sg.EMOJI_BASE64_HAPPY_THUMBS_UP)],
|
[name('Image'), sg.Image(sg.EMOJI_BASE64_HAPPY_THUMBS_UP)],
|
||||||
[name('Graph'), sg.Graph((125, 50), (0,0), (125,50), k='-GRAPH-')] ]
|
[name('Graph'), sg.Graph((125, 50), (0,0), (125,50), k='-GRAPH-')] ]
|
||||||
|
|
||||||
layout_r = [[name('Canvas'), sg.Canvas(background_color=sg.theme_button_color()[1], size=(125,50))],
|
layout_r = [[name('Canvas'), sg.Canvas(background_color=sg.theme_button_color()[1], size=(125,40))],
|
||||||
[name('ProgressBar'), sg.ProgressBar(100, orientation='h', s=(10,20), k='-PBAR-')],
|
[name('ProgressBar'), sg.ProgressBar(100, orientation='h', s=(10,20), k='-PBAR-')],
|
||||||
[name('Table'), sg.Table([[1,2,3], [4,5,6]], ['Col 1','Col 2','Col 3'], num_rows=2)],
|
[name('Table'), sg.Table([[1,2,3], [4,5,6]], ['Col 1','Col 2','Col 3'], num_rows=2)],
|
||||||
[name('Tree'), sg.Tree(treedata, ['Heading',], num_rows=3)],
|
[name('Tree'), sg.Tree(treedata, ['Heading',], num_rows=3)],
|
||||||
|
@ -68,9 +69,9 @@ def make_window(theme=None):
|
||||||
|
|
||||||
# Note - LOCAL Menu element is used (see about for how that's defined)
|
# Note - LOCAL Menu element is used (see about for how that's defined)
|
||||||
layout = [[Menu([['File', ['Exit']], ['Edit', ['Edit Me', ]]], k='-CUST MENUBAR-',p=0)],
|
layout = [[Menu([['File', ['Exit']], ['Edit', ['Edit Me', ]]], k='-CUST MENUBAR-',p=0)],
|
||||||
[sg.Checkbox('Use Custom Titlebar & Menubar', sg.theme_use_custom_titlebar(), enable_events=True, k='-USE CUSTOM TITLEBAR-')],
|
[sg.T('PySimpleGUI Elements - Use Combo to Change Themes', font='_ 14', justification='c', expand_x=True)],
|
||||||
[sg.T('PySimpleGUI Elements - Use Combo to Change Themes', font='_ 18', justification='c', expand_x=True)],
|
[sg.Checkbox('Use Custom Titlebar & Menubar', use_custom_titlebar, enable_events=True, k='-USE CUSTOM TITLEBAR-', p=0)],
|
||||||
[sg.Col(layout_l), sg.Col(layout_r)]]
|
[sg.Col(layout_l, p=0), sg.Col(layout_r, p=0)]]
|
||||||
|
|
||||||
window = sg.Window('The PySimpleGUI Element List', layout, finalize=True, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_EXIT, keep_on_top=True, use_custom_titlebar=use_custom_titlebar)
|
window = sg.Window('The PySimpleGUI Element List', layout, finalize=True, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_EXIT, keep_on_top=True, use_custom_titlebar=use_custom_titlebar)
|
||||||
|
|
||||||
|
@ -87,8 +88,7 @@ while True:
|
||||||
# sg.Print(event, values)
|
# sg.Print(event, values)
|
||||||
if event == sg.WIN_CLOSED or event == 'Exit':
|
if event == sg.WIN_CLOSED or event == 'Exit':
|
||||||
break
|
break
|
||||||
if event == 'Edit Me':
|
|
||||||
sg.execute_editor(__file__)
|
|
||||||
if values['-COMBO-'] != sg.theme():
|
if values['-COMBO-'] != sg.theme():
|
||||||
sg.theme(values['-COMBO-'])
|
sg.theme(values['-COMBO-'])
|
||||||
window.close()
|
window.close()
|
||||||
|
@ -98,8 +98,10 @@ while True:
|
||||||
sg.set_options(use_custom_titlebar=use_custom_titlebar)
|
sg.set_options(use_custom_titlebar=use_custom_titlebar)
|
||||||
window.close()
|
window.close()
|
||||||
window = make_window()
|
window = make_window()
|
||||||
|
if event == 'Edit Me':
|
||||||
|
sg.execute_editor(__file__)
|
||||||
elif event == 'Version':
|
elif event == 'Version':
|
||||||
sg.popup_scrolled(sg.get_versions(), __file__, keep_on_top=True, non_blocking=True)
|
sg.popup_scrolled(__file__, sg.get_versions(), keep_on_top=True, non_blocking=True)
|
||||||
window.close()
|
window.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,21 @@
|
||||||
import PySimpleGUI as sg
|
import PySimpleGUI as sg
|
||||||
import random
|
import random
|
||||||
|
|
||||||
# Bars drawing in PySimpleGUI
|
"""
|
||||||
#
|
Demo - Using a Graph Element to make Bar Charts
|
||||||
# .--.
|
|
||||||
# | |
|
The Graph Element is very versatile. Because you can define your own
|
||||||
# .--.| |.--.
|
coordinate system, it makes producing graphs of many lines (bar, line, etc) very
|
||||||
# | || || |
|
straightforward.
|
||||||
# | || || |
|
|
||||||
# | || || |
|
In this Demo a "bar" is nothing more than a rectangle drawn in a Graph Element (draw_rectangle).
|
||||||
# .--.| || || |
|
|
||||||
# .--.| || || || |.--.
|
To make things a little more interesting, this is a barchart with that data values
|
||||||
# | || || || || || |
|
placed as labels atop each bar, another Graph element method (draw_text)
|
||||||
# | || || || || || |
|
|
||||||
# .--.| || || || || || |.--.
|
Copyright 2022 PySimpleGUI
|
||||||
# | || || || || || || || |.--.
|
"""
|
||||||
# | || || || || || || || || |
|
|
||||||
# '--''--''--''--''--''--''--''--''--'
|
|
||||||
|
|
||||||
|
|
||||||
BAR_WIDTH = 50 # width of each bar
|
BAR_WIDTH = 50 # width of each bar
|
||||||
|
@ -38,12 +37,13 @@ while True:
|
||||||
|
|
||||||
graph.erase()
|
graph.erase()
|
||||||
for i in range(7):
|
for i in range(7):
|
||||||
graph_value = random.randint(0, GRAPH_SIZE[1])
|
graph_value = random.randint(0, GRAPH_SIZE[1]-25) # choose an int just short of the max value to give room for the label
|
||||||
graph.draw_rectangle(top_left=(i * BAR_SPACING + EDGE_OFFSET, graph_value),
|
graph.draw_rectangle(top_left=(i * BAR_SPACING + EDGE_OFFSET, graph_value),
|
||||||
bottom_right=(i * BAR_SPACING + EDGE_OFFSET + BAR_WIDTH, 0),
|
bottom_right=(i * BAR_SPACING + EDGE_OFFSET + BAR_WIDTH, 0),
|
||||||
fill_color=sg.theme_button_color()[1])
|
fill_color='green')
|
||||||
|
# fill_color=sg.theme_button_color()[1])
|
||||||
|
|
||||||
graph.draw_text(text=graph_value, location=(i*BAR_SPACING+EDGE_OFFSET+25, graph_value+10))
|
graph.draw_text(text=graph_value, location=(i*BAR_SPACING+EDGE_OFFSET+25, graph_value+10), font='_ 14')
|
||||||
|
|
||||||
# Normally at the top of the loop, but because we're drawing the graph first, making it at the bottom
|
# Normally at the top of the loop, but because we're drawing the graph first, making it at the bottom
|
||||||
event, values = window.read()
|
event, values = window.read()
|
||||||
|
|
49
DemoPrograms/Demo_Button_Can_Button_Images.py
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import PySimpleGUI as sg
|
||||||
|
|
||||||
|
"""
|
||||||
|
Demo Program - Simulated Buttons with Mouseover Highlights
|
||||||
|
|
||||||
|
The purpose of this demo is to teach you 5 unique PySimpleGUI constructs that when combined
|
||||||
|
create a "Button" that highlights on mouseover regarless of the Operating System.
|
||||||
|
Because of how tktiner works, mouseover highlighting is inconsistent across operating systems for Buttons.
|
||||||
|
This is one (dare I say "clever") way to get this effect in your program
|
||||||
|
|
||||||
|
1. Binding the Enter and Leave tkinter events
|
||||||
|
2. Using Tuples as keys
|
||||||
|
3. Using List Comprehensions to build a layout
|
||||||
|
4. Using Text Elements to Simulate Buttons
|
||||||
|
5. Using a "User Defined Element" to make what appears to be a new type of Button in the layout
|
||||||
|
|
||||||
|
The KEY to making this work simply is these "Buttons" have a tuple as a key.
|
||||||
|
The format of the key is ('-B-', button_text)
|
||||||
|
|
||||||
|
An element's bind method will make a tuple if the original key is a tuple.
|
||||||
|
(('-B-', button_text), 'ENTER') will be the event when the mouse is moved over the "Button"
|
||||||
|
|
||||||
|
Copyright 2022 PySimpleGUI.org
|
||||||
|
"""
|
||||||
|
|
||||||
|
# sg.theme('dark red')
|
||||||
|
|
||||||
|
def TextButton(text):
|
||||||
|
"""
|
||||||
|
A User Defined Element. It looks like a Button, but is a Text element
|
||||||
|
:param text: The text that will be put on the "Button"
|
||||||
|
:return: A Text element with a tuple as the key
|
||||||
|
"""
|
||||||
|
return sg.Text(text, key=('-B-', text), relief='raised', enable_events=True, font='_ 15',text_color=sg.theme_button_color_text(), background_color=sg.theme_button_color_background())
|
||||||
|
|
||||||
|
def do_binds(window, button_text):
|
||||||
|
"""
|
||||||
|
This is magic code that enables the mouseover highlighting to work.
|
||||||
|
"""
|
||||||
|
for btext in button_text:
|
||||||
|
window[('-B-', btext)].bind('<Enter>', 'ENTER')
|
||||||
|
window[('-B-', btext)].bind('<Leave>', 'EXIT')
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Defines the text on the 3 buttons we're making
|
||||||
|
button_text = ('Button 1', 'Button 2', 'Button 3')
|
||||||
|
|
||||||
|
# The window's layout
|
||||||
|
layout = [[TextButton(text) for text in button_text],
|
||||||
|
[sg.Text(font='_ 14', k='-STATUS-')],
|
||||||
|
[sg.Ok(), sg.Exit()]]
|
||||||
|
|
||||||
|
window = sg.Window('Custom Mouseover Highlighting Buttons', layout, finalize=True)
|
||||||
|
|
||||||
|
# After the window is finalized, then can perform the bindings
|
||||||
|
do_binds(window, button_text)
|
||||||
|
|
||||||
|
# The Event Looop
|
||||||
|
while True:
|
||||||
|
event, values = window.read()
|
||||||
|
print(event, values)
|
||||||
|
if event == sg.WIN_CLOSED or event == 'Exit':
|
||||||
|
break
|
||||||
|
# if the event is a tuple, it's one of our TextButtons
|
||||||
|
if isinstance(event, tuple):
|
||||||
|
# if second item is one of the bound strings, then do the mouseeover code
|
||||||
|
if event[1] in ('ENTER', 'EXIT'):
|
||||||
|
button_key = event[0]
|
||||||
|
if event[1] == 'ENTER':
|
||||||
|
window[button_key].update(text_color=sg.theme_button_color_background(), background_color=sg.theme_button_color_text())
|
||||||
|
if event[1] == 'EXIT':
|
||||||
|
window[button_key].update(text_color=sg.theme_button_color_text(), background_color=sg.theme_button_color_background())
|
||||||
|
else: # a "normal" button click (Text clicked) so print the text which we put into the tuple
|
||||||
|
window['-STATUS-'].update(f'Button pressed = {event[1]}')
|
||||||
|
window.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -1,5 +1,12 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
import PySimpleGUI as sg
|
import PySimpleGUI as sg
|
||||||
|
"""
|
||||||
|
Demo - Base64 Buttons with Images
|
||||||
|
|
||||||
|
This is perhaps the easiest, quickest, and safest way to use buttons with images in PySimpleGUI.
|
||||||
|
By putting the button into your code, then you only have to distribute a single file.
|
||||||
|
|
||||||
|
Copyright 2022 PySimpleGUI
|
||||||
|
"""
|
||||||
|
|
||||||
# First the button images
|
# First the button images
|
||||||
|
|
||||||
|
@ -7,24 +14,19 @@ play = b'iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABmJLR0QA/wD/AP+gvaeTAAA
|
||||||
stop = b'iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABmJLR0QA/wD/AP+gvaeTAAAAaklEQVRoge3ZQQqAMAxFwSre/8p6AZFUiXzKzLqLPNJVOwYAvLcVzpztU9Q8zrr/NUW3Y+JsZXsdSjdimY0ISSMkjZA0QtIISSMkjZA0QtIISSMkjZA0QtIISSMkzcxrfMo/ya1lNgIAX1zq+ANHUjXZuAAAAABJRU5ErkJggg=='
|
stop = b'iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABmJLR0QA/wD/AP+gvaeTAAAAaklEQVRoge3ZQQqAMAxFwSre/8p6AZFUiXzKzLqLPNJVOwYAvLcVzpztU9Q8zrr/NUW3Y+JsZXsdSjdimY0ISSMkjZA0QtIISSMkjZA0QtIISSMkjZA0QtIISSMkzcxrfMo/ya1lNgIAX1zq+ANHUjXZuAAAAABJRU5ErkJggg=='
|
||||||
eject = b'iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABmJLR0QA/wD/AP+gvaeTAAAByklEQVRoge3YO2gUURSA4S+JRnyACIGADyxERAsb0UKrWIidWIidlSA2YpFWSauNVtrYiIU2YpFCLGwEEWwsBAsLEbFQFARFfKBZizkyK5pkZvZmZ7PeH05z595z/sPszpxdMplMJpMZbDZFLGsm8CxiomWXxqzBQ3QiHmNdq0YNGMc9RQOvIjqxNt6iVy1GcF0h/h47sR1vY+0mRluzq8ElhfBn7O9a34tPce1KC161OK8Q/Y7D/7h+EF9jz7k+etXilELwJ44vsO8ofsTeM33wqsURpdzZCvtPK5s+toRetZjCF4XYTI1zM3HmGw4lt6rJbnxQCF1tcP5ynP2IPQm9arENb0LkDsYa5BjFrcjxDjuS2VVkI16EwH2s6iHXStxVvjy39GxXkfV4Iu3Y0T3OPMWGBDkXZDUeRMHnmEyY+/eA2cEjrE2Y+w/GcDsKvcbWJaixGS+jxixWpC4wgmvK+WlX6gJddM9lN6J2Mi4q56cDKRPPwz7lXHYhVdJp5W+KtmK61yZOYG4AGpnDyV6byWT+ZxZ7Rnf6YlGdeX2XxZ8AVag6AiR9uzZg0U/G0NyR3MigUfU7MmhPr78YmjuSyWQymUxmmPgFokSdfYSQKDwAAAAASUVORK5CYII='
|
eject = b'iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABmJLR0QA/wD/AP+gvaeTAAAByklEQVRoge3YO2gUURSA4S+JRnyACIGADyxERAsb0UKrWIidWIidlSA2YpFWSauNVtrYiIU2YpFCLGwEEWwsBAsLEbFQFARFfKBZizkyK5pkZvZmZ7PeH05z595z/sPszpxdMplMJpMZbDZFLGsm8CxiomWXxqzBQ3QiHmNdq0YNGMc9RQOvIjqxNt6iVy1GcF0h/h47sR1vY+0mRluzq8ElhfBn7O9a34tPce1KC161OK8Q/Y7D/7h+EF9jz7k+etXilELwJ44vsO8ofsTeM33wqsURpdzZCvtPK5s+toRetZjCF4XYTI1zM3HmGw4lt6rJbnxQCF1tcP5ynP2IPQm9arENb0LkDsYa5BjFrcjxDjuS2VVkI16EwH2s6iHXStxVvjy39GxXkfV4Iu3Y0T3OPMWGBDkXZDUeRMHnmEyY+/eA2cEjrE2Y+w/GcDsKvcbWJaixGS+jxixWpC4wgmvK+WlX6gJddM9lN6J2Mi4q56cDKRPPwz7lXHYhVdJp5W+KtmK61yZOYG4AGpnDyV6byWT+ZxZ7Rnf6YlGdeX2XxZ8AVag6AiR9uzZg0U/G0NyR3MigUfU7MmhPr78YmjuSyWQymUxmmPgFokSdfYSQKDwAAAAASUVORK5CYII='
|
||||||
|
|
||||||
sg.theme('Light Green 3') # Set a color theme
|
sg.theme('Light Green 3')
|
||||||
|
|
||||||
bg = sg.LOOK_AND_FEEL_TABLE[sg.CURRENT_LOOK_AND_FEEL]['BACKGROUND'] # Get the background for the current theme
|
|
||||||
|
|
||||||
# Define the window's layout
|
# Define the window's layout
|
||||||
layout = [ [sg.Text('Your Application', font='Any 15')],
|
layout = [[sg.Button(image_data=play, key='-PLAY-', button_color=sg.theme_background_color(), border_width=0),
|
||||||
[sg.Text('Event = '), sg.Text(size=(12,1), key='-OUT-')],
|
sg.Button(image_data=stop, key='-STOP-', button_color=sg.theme_background_color(), border_width=0),
|
||||||
[sg.Button(image_data=play, key='Play', border_width=0, button_color=(bg, bg)),
|
sg.Button(image_data=eject, key='-EXIT-', button_color=sg.theme_background_color(), border_width=0)] ]
|
||||||
sg.Button(image_data=stop, key='Stop', button_color=(bg, bg), border_width=0),
|
|
||||||
sg.Button(image_data=eject, key='Exit', button_color=(bg, bg), border_width=0)] ]
|
|
||||||
|
|
||||||
# Create the window
|
# Create the window
|
||||||
window = sg.Window('Window Title', layout)
|
window = sg.Window('Simple Base64 Buttons', layout)
|
||||||
|
|
||||||
while True: # Event Loop
|
while True: # Event Loop
|
||||||
event, values = window.read() # type: str, dict
|
event, values = window.read() # type: str, dict
|
||||||
print(event, values)
|
print(event, values)
|
||||||
if event in (sg.WIN_CLOSED, 'Exit'): # If the user exits
|
if event in (sg.WIN_CLOSED, '-EXIT-'): # If the user exits
|
||||||
break
|
break
|
||||||
window['-OUT-'].Update(event) # Output the event to the window
|
window.close() # Exiting so clean up
|
||||||
window.close(); del window # Exiting so clean up
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ def resize_base64_image(image64, size):
|
||||||
'''
|
'''
|
||||||
image_file = io.BytesIO(base64.b64decode(image64))
|
image_file = io.BytesIO(base64.b64decode(image64))
|
||||||
img = Image.open(image_file)
|
img = Image.open(image_file)
|
||||||
img.thumbnail(size, Image.ANTIALIAS)
|
img.thumbnail(size, Image.LANCZOS)
|
||||||
bio = io.BytesIO()
|
bio = io.BytesIO()
|
||||||
img.save(bio, format='PNG')
|
img.save(bio, format='PNG')
|
||||||
imgbytes = bio.getvalue()
|
imgbytes = bio.getvalue()
|
||||||
|
|
26
DemoPrograms/Demo_CLI_or_GUI.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
"""
|
||||||
|
Demo Command Line Application or GUI Application
|
||||||
|
|
||||||
|
If your program is run with arguments, then a command line version is used.
|
||||||
|
If no arguments are given, then a GUI is shown that asks for a filename.
|
||||||
|
|
||||||
|
http://www.PySimpleGUI.org
|
||||||
|
Copyright 2022 PySimpleGUI
|
||||||
|
"""
|
||||||
|
|
||||||
|
import PySimpleGUI as sg
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def main_cli(filename):
|
||||||
|
print(f'Your filename = {filename}')
|
||||||
|
|
||||||
|
|
||||||
|
def main_gui():
|
||||||
|
filename = sg.popup_get_file('Please enter a filename:')
|
||||||
|
main_cli(filename)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
main_gui()
|
||||||
|
else:
|
||||||
|
main_cli(sys.argv[1])
|
|
@ -4,25 +4,28 @@ import PySimpleGUI as sg
|
||||||
'''
|
'''
|
||||||
A simple send/response chat window. Add call to your send-routine and print the response
|
A simple send/response chat window. Add call to your send-routine and print the response
|
||||||
If async responses can come in, then will need to use a different design that uses PySimpleGUI async design pattern
|
If async responses can come in, then will need to use a different design that uses PySimpleGUI async design pattern
|
||||||
|
|
||||||
|
Copyright 2023 PySimpleGUI
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
sg.theme('GreenTan') # give our window a spiffy set of colors
|
sg.theme('GreenTan') # give our window a spiffy set of colors
|
||||||
|
|
||||||
layout = [[sg.Text('Your output will go here', size=(40, 1))],
|
layout = [[sg.Text('Your output will go here', size=(40, 1))],
|
||||||
[sg.Output(size=(110, 20), font=('Helvetica 10'))],
|
[sg.Output(size=(110, 20), font=('Helvetica 10'))],
|
||||||
[sg.Multiline(size=(70, 5), enter_submits=False, key='-QUERY-', do_not_clear=False),
|
[sg.Multiline(size=(70, 5), enter_submits=True, key='-QUERY-', do_not_clear=False),
|
||||||
sg.Button('SEND', button_color=(sg.YELLOWS[0], sg.BLUES[0]), bind_return_key=True),
|
sg.Button('SEND', button_color=(sg.YELLOWS[0], sg.BLUES[0]), bind_return_key=True),
|
||||||
sg.Button('EXIT', button_color=(sg.YELLOWS[0], sg.GREENS[0]))]]
|
sg.Button('EXIT', button_color=(sg.YELLOWS[0], sg.GREENS[0]))]]
|
||||||
|
|
||||||
window = sg.Window('Chat window', layout, font=('Helvetica', ' 13'), default_button_element_size=(8,2), use_default_focus=False)
|
window = sg.Window('Chat window', layout, font=('Helvetica', ' 13'), default_button_element_size=(8,2), use_default_focus=False)
|
||||||
|
|
||||||
while True: # The Event Loop
|
while True: # The Event Loop
|
||||||
event, value = window.read()
|
event, values = window.read()
|
||||||
if event in (sg.WIN_CLOSED, 'EXIT'): # quit if exit button or X
|
if event in (sg.WIN_CLOSED, 'EXIT'): # quit if exit button or X
|
||||||
break
|
break
|
||||||
if event == 'SEND':
|
if event == 'SEND':
|
||||||
query = value['-QUERY-'].rstrip()
|
query = values['-QUERY-'].rstrip()
|
||||||
# EXECUTE YOUR COMMAND HERE
|
# EXECUTE YOUR COMMAND HERE
|
||||||
print('The command you entered was {}'.format(query), flush=True)
|
print('The command you entered was {}'.format(query), flush=True)
|
||||||
|
|
||||||
window.close()
|
window.close()
|
||||||
|
|
45
DemoPrograms/Demo_Checkboxes_Custom.py
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import PySimpleGUI as sg
|
||||||
|
|
||||||
|
"""
|
||||||
|
Demo - Custom Checkboxes done simply
|
||||||
|
|
||||||
|
The Base64 Image encoding feature of PySimpleGUI makes it possible to create beautiful GUIs very simply
|
||||||
|
|
||||||
|
These 2 checkboxes required 3 extra lines of code than a normal checkbox.
|
||||||
|
1. Keep track of the current value using the Image Element's Metadata
|
||||||
|
2. Changle / Update the image when clicked
|
||||||
|
3. The Base64 image definition
|
||||||
|
|
||||||
|
Enable the event on the Image with the checkbox so that you can take action (flip the value)
|
||||||
|
|
||||||
|
Copyright 2022 PySimpleGUI
|
||||||
|
"""
|
||||||
|
|
||||||
|
def main():
|
||||||
|
layout = [[sg.Text('Fancy Checkboxes... Simply')],
|
||||||
|
[sg.Image(checked, key=('-IMAGE-', 1), metadata=True, enable_events=True), sg.Text(True, enable_events=True, k=('-TEXT-', 1))],
|
||||||
|
[sg.Image(unchecked, key=('-IMAGE-', 2), metadata=False, enable_events=True), sg.Text(False, enable_events=True, k=('-TEXT-', 2))],
|
||||||
|
[sg.Button('Go'), sg.Button('Exit')]]
|
||||||
|
|
||||||
|
window = sg.Window('Custom Checkboxes', layout, font="_ 14")
|
||||||
|
while True:
|
||||||
|
event, values = window.read()
|
||||||
|
print(event, values)
|
||||||
|
if event == sg.WIN_CLOSED or event == 'Exit':
|
||||||
|
break
|
||||||
|
# if a checkbox is clicked, flip the vale and the image
|
||||||
|
if event[0] in ('-IMAGE-', '-TEXT-'):
|
||||||
|
cbox_key = ('-IMAGE-', event[1])
|
||||||
|
text_key = ('-TEXT-', event[1])
|
||||||
|
window[cbox_key].metadata = not window[cbox_key].metadata
|
||||||
|
window[cbox_key].update(checked if window[cbox_key].metadata else unchecked)
|
||||||
|
# Update the string next to the checkbox
|
||||||
|
window[text_key].update(window[cbox_key].metadata)
|
||||||
|
|
||||||
|
window.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
checked = b'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'
|
||||||
|
unchecked = b'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'
|
||||||
|
main()
|
|
@ -4,10 +4,38 @@ import PySimpleGUI as sg
|
||||||
Demo - Class wrapper
|
Demo - Class wrapper
|
||||||
|
|
||||||
Using a class to encapsulate PySimpleGUI Window creation & event loop
|
Using a class to encapsulate PySimpleGUI Window creation & event loop
|
||||||
|
|
||||||
|
This is NOT a recommended design pattern. It mimics the object oriented design that many OO-based
|
||||||
|
GUI frameworks use, but there is no advantage to structuring you code in his manner. It adds
|
||||||
|
confusion, not clarity.
|
||||||
|
|
||||||
|
The class version is 18 lines of code. The plain version is 13 lines of code.
|
||||||
|
|
||||||
|
Two things about the class wrapper jump out as adding confusion:
|
||||||
|
1. Unneccessary fragmentation of the event loop - the button click code is pulled out of the loop entirely
|
||||||
|
2. "self" clutters the code without adding value
|
||||||
|
|
||||||
|
|
||||||
Copyright 2022 PySimpleGUI
|
Copyright 2022, 2023 PySimpleGUI
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
'''
|
||||||
|
MM'""""'YMM dP
|
||||||
|
M' .mmm. `M 88
|
||||||
|
M MMMMMooM 88 .d8888b. .d8888b. .d8888b.
|
||||||
|
M MMMMMMMM 88 88' `88 Y8ooooo. Y8ooooo.
|
||||||
|
M. `MMM' .M 88 88. .88 88 88
|
||||||
|
MM. .dM dP `88888P8 `88888P' `88888P'
|
||||||
|
MMMMMMMMMMM
|
||||||
|
|
||||||
|
M""MMMMM""M oo
|
||||||
|
M MMMMM M
|
||||||
|
M MMMMP M .d8888b. 88d888b. .d8888b. dP .d8888b. 88d888b.
|
||||||
|
M MMMM' .M 88ooood8 88' `88 Y8ooooo. 88 88' `88 88' `88
|
||||||
|
M MMP' .MM 88. ... 88 88 88 88. .88 88 88
|
||||||
|
M .dMMM `88888P' dP `88888P' dP `88888P' dP dP
|
||||||
|
MMMMMMMMMMM
|
||||||
|
'''
|
||||||
class SampleGUI():
|
class SampleGUI():
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -35,3 +63,41 @@ class SampleGUI():
|
||||||
my_gui = SampleGUI()
|
my_gui = SampleGUI()
|
||||||
# run the event loop
|
# run the event loop
|
||||||
my_gui.run()
|
my_gui.run()
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
M"""""""`YM dP
|
||||||
|
M mmmm. M 88
|
||||||
|
M MMMMM M .d8888b. 88d888b. 88d8b.d8b. .d8888b. 88
|
||||||
|
M MMMMM M 88' `88 88' `88 88'`88'`88 88' `88 88
|
||||||
|
M MMMMM M 88. .88 88 88 88 88 88. .88 88
|
||||||
|
M MMMMM M `88888P' dP dP dP dP `88888P8 dP
|
||||||
|
MMMMMMMMMMM
|
||||||
|
|
||||||
|
M""MMMMM""M oo
|
||||||
|
M MMMMM M
|
||||||
|
M MMMMP M .d8888b. 88d888b. .d8888b. dP .d8888b. 88d888b.
|
||||||
|
M MMMM' .M 88ooood8 88' `88 Y8ooooo. 88 88' `88 88' `88
|
||||||
|
M MMP' .MM 88. ... 88 88 88 88. .88 88 88
|
||||||
|
M .dMMM `88888P' dP `88888P' dP `88888P' dP dP
|
||||||
|
MMMMMMMMMMM
|
||||||
|
'''
|
||||||
|
|
||||||
|
def gui_function():
|
||||||
|
layout = [ [sg.Text('My layout')],
|
||||||
|
[sg.Input(key='-IN-')],
|
||||||
|
[sg.Button('Go'), sg.Button('Exit')] ]
|
||||||
|
|
||||||
|
window = sg.Window('My new window', layout)
|
||||||
|
|
||||||
|
while True: # Event Loop
|
||||||
|
event, values = window.read()
|
||||||
|
if event in (sg.WIN_CLOSED, 'Exit'):
|
||||||
|
break
|
||||||
|
|
||||||
|
if event == 'Go':
|
||||||
|
sg.popup('Go button clicked', 'Input value:', values['-IN-'])
|
||||||
|
|
||||||
|
window.close()
|
||||||
|
|
||||||
|
gui_function()
|
||||||
|
|
48
DemoPrograms/Demo_Cursor_Previewer.py
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import PySimpleGUI as sg
|
||||||
|
|
||||||
|
"""
|
||||||
|
Demo - Preview tkinter cursors
|
||||||
|
|
||||||
|
Shows the standard tkinter cursors using Buttons
|
||||||
|
|
||||||
|
The name of the cursor is on the Button. Mouse over the Button and you'll see
|
||||||
|
what that cursor looks like.
|
||||||
|
This list of cursors is a constant defined in PySimpleGUI. The constant name is:
|
||||||
|
sg.TKINTER_CURSORS
|
||||||
|
|
||||||
|
Copyright 2022 PySimpleGUI
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
cursors = sg.TKINTER_CURSORS
|
||||||
|
# Make a layout that's 10 buttons across
|
||||||
|
NUM_BUTTONS_PER_ROW = 10
|
||||||
|
layout = [[]]
|
||||||
|
row = []
|
||||||
|
for i, c in enumerate(cursors):
|
||||||
|
# print(i, c)
|
||||||
|
row.append(sg.Button(c, size=(14,3), k=c))
|
||||||
|
if ((i+1) % NUM_BUTTONS_PER_ROW) == 0:
|
||||||
|
layout.append(row)
|
||||||
|
row = []
|
||||||
|
# print(row)
|
||||||
|
# Add on the last, partial row
|
||||||
|
start = len(cursors)//NUM_BUTTONS_PER_ROW * NUM_BUTTONS_PER_ROW
|
||||||
|
row = []
|
||||||
|
for i in range(start, len(cursors)):
|
||||||
|
row.append(sg.Button(cursors[i], size=(14,3), k=cursors[i]))
|
||||||
|
layout.append(row)
|
||||||
|
|
||||||
|
window = sg.Window('Cursor Previewer',layout, finalize=True)
|
||||||
|
|
||||||
|
# set the cursor on each of the buttons that has the name of the cursor as the text
|
||||||
|
for c in cursors:
|
||||||
|
window[c].set_cursor(c)
|
||||||
|
|
||||||
|
# The ubiquitous event loop...
|
||||||
|
while True:
|
||||||
|
event, values = window.read()
|
||||||
|
if event == sg.WIN_CLOSED:
|
||||||
|
break
|
||||||
|
|
||||||
|
window.close()
|
|
@ -9,11 +9,11 @@ import webbrowser
|
||||||
|
|
||||||
If you want no cursor, set the cursor to the string 'none'.
|
If you want no cursor, set the cursor to the string 'none'.
|
||||||
|
|
||||||
Copyright 2021 PySimpleGUI
|
Copyright 2021, 2022 PySimpleGUI
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Here is a more complete list of cursors you can choose from
|
# Here is a more complete list of cursors you can choose from
|
||||||
cursors = ('X_cursor', 'no', 'arrow','based_arrow_down','based_arrow_up','boat','bogosity','bottom_left_corner','bottom_right_corner','bottom_side','bottom_tee','box_spiral','center_ptr','circle','clock','coffee_mug','cross','cross_reverse','crosshair','diamond_cross','dot','dotbox','double_arrow','draft_large','draft_small','draped_box','exchange','fleur','gobbler','gumby','hand1','hand2','heart','icon','iron_cross','left_ptr','left_side','left_tee','leftbutton','ll_angle','lr_angle','man','middlebutton','mouse','no','pencil','pirate','plus','question_arrow','right_ptr','right_side','right_tee','rightbutton','rtl_logo','sailboat','sb_down_arrow','sb_h_double_arrow','sb_left_arrow','sb_right_arrow','sb_up_arrow','sb_v_double_arrow','shuttle','sizing','spider','spraycan','star','target','tcross','top_left_arrow','top_left_corner','top_right_corner','top_side','top_tee','trek','ul_angle','umbrella','ur_angle','watch','xterm','arrow','center_ptr','crosshair','fleur','ibeam','icon','sb_h_double_arrow','sb_v_double_arrow','watch','xterm','no','starting','size','size_ne_sw','size_ns','size_nw_se','size_we','uparrow','wait','arrow','cross','crosshair','ibeam','plus','watch','xterm')
|
cursors = sg.TKINTER_CURSORS
|
||||||
|
|
||||||
sg.theme('Light Blue 2')
|
sg.theme('Light Blue 2')
|
||||||
|
|
||||||
|
|
|
@ -274,7 +274,7 @@ def main(location):
|
||||||
[sg.T(size=(5, 1), font='Any 20', justification='c', background_color='black', k='-gauge VALUE-')]]
|
[sg.T(size=(5, 1), font='Any 20', justification='c', background_color='black', k='-gauge VALUE-')]]
|
||||||
|
|
||||||
|
|
||||||
window = sg.Window('CPU Usage Widget Square', layout, location=location, no_titlebar=True, grab_anywhere=True, margins=(0, 0), element_padding=(0, 0), alpha_channel=ALPHA, background_color='black', element_justification='c', finalize=True, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_EXIT, enable_close_attempted_event=True)
|
window = sg.Window('CPU Usage Widget Square', layout, location=location, no_titlebar=True, grab_anywhere=True, margins=(0, 0), element_padding=(0, 0), alpha_channel=ALPHA, background_color='black', element_justification='c', finalize=True, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_EXIT, enable_close_attempted_event=True)
|
||||||
|
|
||||||
gauge = Gauge(pointer_color=sg.theme_text_color(), clock_color=sg.theme_text_color(), major_tick_color=sg.theme_text_color(),
|
gauge = Gauge(pointer_color=sg.theme_text_color(), clock_color=sg.theme_text_color(), major_tick_color=sg.theme_text_color(),
|
||||||
minor_tick_color=sg.theme_input_background_color(), pointer_outer_color=sg.theme_text_color(), major_tick_start_radius=45,
|
minor_tick_color=sg.theme_input_background_color(), pointer_outer_color=sg.theme_text_color(), major_tick_start_radius=45,
|
||||||
|
@ -298,6 +298,8 @@ def main(location):
|
||||||
break
|
break
|
||||||
if event == 'Edit Me':
|
if event == 'Edit Me':
|
||||||
sg.execute_editor(__file__)
|
sg.execute_editor(__file__)
|
||||||
|
elif event == 'Version':
|
||||||
|
sg.popup_scrolled(__file__, sg.get_versions(), location=window.current_location(), keep_on_top=True, non_blocking=True)
|
||||||
window.close()
|
window.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ def main(location):
|
||||||
|
|
||||||
layout = [[graph]]
|
layout = [[graph]]
|
||||||
|
|
||||||
window = sg.Window('CPU Usage Widget Square', layout, location=location, no_titlebar=True, grab_anywhere=True, margins=(0, 0), element_padding=(0, 0), alpha_channel=ALPHA, finalize=True, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_EXIT, enable_close_attempted_event=True)
|
window = sg.Window('CPU Usage Widget Square', layout, location=location, no_titlebar=True, grab_anywhere=True, margins=(0, 0), element_padding=(0, 0), alpha_channel=ALPHA, finalize=True, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_EXIT, enable_close_attempted_event=True)
|
||||||
|
|
||||||
text_id2 = graph.draw_text(f'CPU', (GSIZE[0] // 2, GSIZE[1] // 4), font='Any 20', text_location=sg.TEXT_LOCATION_CENTER, color=sg.theme_button_color()[0])
|
text_id2 = graph.draw_text(f'CPU', (GSIZE[0] // 2, GSIZE[1] // 4), font='Any 20', text_location=sg.TEXT_LOCATION_CENTER, color=sg.theme_button_color()[0])
|
||||||
|
|
||||||
|
@ -46,6 +46,8 @@ def main(location):
|
||||||
break
|
break
|
||||||
if event == 'Edit Me':
|
if event == 'Edit Me':
|
||||||
sg.execute_editor(__file__)
|
sg.execute_editor(__file__)
|
||||||
|
elif event == 'Version':
|
||||||
|
sg.popup_scrolled(__file__, sg.get_versions(), location=window.current_location(), keep_on_top=True, non_blocking=True)
|
||||||
# erase figures so they can be redrawn
|
# erase figures so they can be redrawn
|
||||||
graph.delete_figure(rect_id)
|
graph.delete_figure(rect_id)
|
||||||
graph.delete_figure(text_id1)
|
graph.delete_figure(text_id1)
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
import PySimpleGUI as sg
|
import PySimpleGUI as sg
|
||||||
import datetime
|
import datetime
|
||||||
import PIL.Image, PIL.ImageTk
|
import PIL
|
||||||
|
from PIL import Image
|
||||||
import random
|
import random
|
||||||
import os
|
import os
|
||||||
|
import io
|
||||||
|
import base64
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Another simple Desktop Widget using PySimpleGUI
|
Another simple Desktop Widget using PySimpleGUI
|
||||||
|
@ -16,17 +20,76 @@ import os
|
||||||
* How long to show the image and if you wnt this time to vary semi-randomly
|
* How long to show the image and if you wnt this time to vary semi-randomly
|
||||||
* Folder containing your images
|
* Folder containing your images
|
||||||
|
|
||||||
Copyright 2021 PySimpleGUI
|
Copyright 2021, 2023 PySimpleGUI
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ALPHA = 0.9 # Initial alpha until user changes
|
ALPHA = 0.9 # Initial alpha until user changes
|
||||||
refresh_font = sg.user_settings_get_entry('-refresh font-', 'Courier 8')
|
refresh_font = sg.user_settings_get_entry('-refresh font-', 'Courier 8')
|
||||||
|
|
||||||
def convert_to_bytes(file_or_bytes, resize=None):
|
|
||||||
image = PIL.Image.open(file_or_bytes)
|
def make_square(im, fill_color=(0, 0, 0, 0)):
|
||||||
image.thumbnail(resize)
|
x, y = im.size
|
||||||
photo_img = PIL.ImageTk.PhotoImage(image)
|
size = max(x, y)
|
||||||
return photo_img
|
new_im = Image.new('RGBA', (size, size), fill_color)
|
||||||
|
new_im.paste(im, (int((size - x) / 2), int((size - y) / 2)))
|
||||||
|
return new_im
|
||||||
|
|
||||||
|
def get_image_size(source):
|
||||||
|
if isinstance(source, str):
|
||||||
|
image = PIL.Image.open(source)
|
||||||
|
elif isinstance(source, bytes):
|
||||||
|
image = PIL.Image.open(io.BytesIO(base64.b64decode(source)))
|
||||||
|
else:
|
||||||
|
image = PIL.Image.open(io.BytesIO(source))
|
||||||
|
|
||||||
|
width, height = image.size
|
||||||
|
return (width, height)
|
||||||
|
|
||||||
|
def convert_to_bytes(source, size=(None, None), subsample=None, zoom=None, fill=False):
|
||||||
|
"""
|
||||||
|
Will convert into bytes and optionally resize an image that is a file or a base64 bytes object.
|
||||||
|
Turns into PNG format in the process so that can be displayed by tkinter
|
||||||
|
:param source: either a string filename or a bytes base64 image object
|
||||||
|
:type source: (Union[str, bytes])
|
||||||
|
:param size: optional new size (width, height)
|
||||||
|
:type size: (Tuple[int, int] or None)
|
||||||
|
:param subsample: change the size by multiplying width and height by 1/subsample
|
||||||
|
:type subsample: (int)
|
||||||
|
:param zoom: change the size by multiplying width and height by zoom
|
||||||
|
:type zoom: (int)
|
||||||
|
:param fill: If True then the image is filled/padded so that the image is square
|
||||||
|
:type fill: (bool)
|
||||||
|
:return: (bytes) a byte-string object
|
||||||
|
:rtype: (bytes)
|
||||||
|
"""
|
||||||
|
# print(f'converting {source} {size}')
|
||||||
|
if isinstance(source, str):
|
||||||
|
image = PIL.Image.open(source)
|
||||||
|
elif isinstance(source, bytes):
|
||||||
|
image = PIL.Image.open(io.BytesIO(base64.b64decode(source)))
|
||||||
|
else:
|
||||||
|
image = PIL.Image.open(io.BytesIO(source))
|
||||||
|
|
||||||
|
width, height = image.size
|
||||||
|
|
||||||
|
scale = None
|
||||||
|
if size != (None, None):
|
||||||
|
new_width, new_height = size
|
||||||
|
scale = min(new_height/height, new_width/width)
|
||||||
|
elif subsample is not None:
|
||||||
|
scale = 1/subsample
|
||||||
|
elif zoom is not None:
|
||||||
|
scale = zoom
|
||||||
|
|
||||||
|
resized_image = image.resize((int(width * scale), int(height * scale)), Image.LANCZOS) if scale is not None else image
|
||||||
|
if fill and scale is not None:
|
||||||
|
resized_image = make_square(resized_image)
|
||||||
|
# encode a PNG formatted version of image into BASE64
|
||||||
|
with io.BytesIO() as bio:
|
||||||
|
resized_image.save(bio, format="PNG")
|
||||||
|
contents = bio.getvalue()
|
||||||
|
encoded = base64.b64encode(contents)
|
||||||
|
return encoded
|
||||||
|
|
||||||
|
|
||||||
def choose_theme(location):
|
def choose_theme(location):
|
||||||
|
@ -43,6 +106,15 @@ def choose_theme(location):
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def reset_settings():
|
||||||
|
sg.user_settings_set_entry('-time per image-', 60)
|
||||||
|
sg.user_settings_set_entry('-random time-', False)
|
||||||
|
sg.user_settings_set_entry('-image size-', (None, None))
|
||||||
|
sg.user_settings_set_entry('-image_folder-', None)
|
||||||
|
sg.user_settings_set_entry('-location-', (None, None))
|
||||||
|
sg.user_settings_set_entry('-single image-', None)
|
||||||
|
sg.user_settings_set_entry('-alpha-', ALPHA)
|
||||||
|
|
||||||
|
|
||||||
def make_window(location):
|
def make_window(location):
|
||||||
alpha = sg.user_settings_get_entry('-alpha-', ALPHA)
|
alpha = sg.user_settings_get_entry('-alpha-', ALPHA)
|
||||||
|
@ -61,7 +133,7 @@ def make_window(location):
|
||||||
layout = [[sg.Image(k='-IMAGE-', enable_events=True)],
|
layout = [[sg.Image(k='-IMAGE-', enable_events=True)],
|
||||||
[sg.pin(sg.Column(refresh_info, key='-REFRESH INFO-', element_justification='c', visible=sg.user_settings_get_entry('-show refresh-', True)))]]
|
[sg.pin(sg.Column(refresh_info, key='-REFRESH INFO-', element_justification='c', visible=sg.user_settings_get_entry('-show refresh-', True)))]]
|
||||||
|
|
||||||
window = sg.Window('Photo Frame', layout, location=location, no_titlebar=True, grab_anywhere=True, margins=(0, 0), element_justification='c', element_padding=(0, 0), alpha_channel=alpha, finalize=True, right_click_menu=right_click_menu, keep_on_top=True, enable_close_attempted_event=True)
|
window = sg.Window('Photo Frame', layout, location=location, no_titlebar=True, grab_anywhere=True, margins=(0, 0), element_justification='c', element_padding=(0, 0), alpha_channel=alpha, finalize=True, right_click_menu=right_click_menu, keep_on_top=True, enable_close_attempted_event=True, enable_window_config_events=True)
|
||||||
|
|
||||||
return window
|
return window
|
||||||
|
|
||||||
|
@ -69,11 +141,10 @@ def make_window(location):
|
||||||
def main():
|
def main():
|
||||||
loc = sg.user_settings_get_entry('-location-', (None, None))
|
loc = sg.user_settings_get_entry('-location-', (None, None))
|
||||||
sg.theme(sg.user_settings_get_entry('-theme-', None))
|
sg.theme(sg.user_settings_get_entry('-theme-', None))
|
||||||
window = make_window(loc)
|
|
||||||
|
|
||||||
time_per_image = sg.user_settings_get_entry('-time per image-', 60)
|
time_per_image = sg.user_settings_get_entry('-time per image-', 60)
|
||||||
vary_randomly = sg.user_settings_get_entry('-random time-', False)
|
vary_randomly = sg.user_settings_get_entry('-random time-', False)
|
||||||
width, height = sg.user_settings_get_entry('-image size-', (400,300))
|
width, height = sg.user_settings_get_entry('-image size-', (None, None))
|
||||||
image_folder = sg.user_settings_get_entry('-image_folder-', None)
|
image_folder = sg.user_settings_get_entry('-image_folder-', None)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -82,36 +153,26 @@ def main():
|
||||||
image_folder = None
|
image_folder = None
|
||||||
sg.user_settings_set_entry('-image_folder-', None)
|
sg.user_settings_set_entry('-image_folder-', None)
|
||||||
|
|
||||||
single_image = sg.user_settings_get_entry('-single image-', None)
|
image_name = single_image = sg.user_settings_get_entry('-single image-', None)
|
||||||
|
|
||||||
if image_folder is None and single_image is None:
|
if image_folder is None and single_image is None:
|
||||||
while True:
|
image_name = single_image = sg.popup_get_file('Choose a starting image', keep_on_top=True)
|
||||||
images = None
|
if not single_image:
|
||||||
image_folder = sg.popup_get_folder('Choose location of your images', location=window.current_location(), keep_on_top=True)
|
if sg.popup_yes_no('No folder entered','Go you want to exit the program entirely?', keep_on_top=True) == 'Yes':
|
||||||
if image_folder is not None:
|
exit()
|
||||||
sg.user_settings_set_entry('-image_folder-', image_folder)
|
if image_folder is not None and single_image is None:
|
||||||
break
|
|
||||||
else:
|
|
||||||
if sg.popup_yes_no('No folder entered','Go you want to exit the program entirely?', keep_on_top=True) == 'Yes':
|
|
||||||
exit()
|
|
||||||
elif single_image is None:
|
|
||||||
images = os.listdir(image_folder)
|
images = os.listdir(image_folder)
|
||||||
images = [i for i in images if i.lower().endswith(('.png', '.jpg', '.gif'))]
|
images = [i for i in images if i.lower().endswith(('.png', '.jpg', '.gif'))]
|
||||||
|
image_name = os.path.join(image_folder, random.choice(images))
|
||||||
else: # means single image is not none
|
else: # means single image is not none
|
||||||
images = None
|
images = None
|
||||||
|
image_name = single_image
|
||||||
|
window = make_window(loc)
|
||||||
|
|
||||||
|
window_size = window.size
|
||||||
|
image_data = convert_to_bytes(image_name, (width, height))
|
||||||
|
|
||||||
while True: # Event Loop
|
while True: # Event Loop
|
||||||
# First update the status information
|
|
||||||
# for debugging show the last update date time
|
|
||||||
if single_image is None:
|
|
||||||
image_name =random.choice(images)
|
|
||||||
image_data = convert_to_bytes(os.path.join(image_folder, image_name), (width, height))
|
|
||||||
window['-FOLDER-'].update(image_folder)
|
|
||||||
else:
|
|
||||||
image_name = single_image
|
|
||||||
image_data = convert_to_bytes(single_image, (width, height))
|
|
||||||
window['-FILENAME-'].update(image_name)
|
|
||||||
window['-IMAGE-'].update(data=image_data)
|
|
||||||
window['-REFRESHED-'].update(datetime.datetime.now().strftime("%m/%d/%Y %I:%M:%S %p"))
|
|
||||||
# -------------- Start of normal event loop --------------
|
# -------------- Start of normal event loop --------------
|
||||||
timeout = time_per_image * 1000 + (random.randint(int(-time_per_image * 500), int(time_per_image * 500)) if vary_randomly else 0) if single_image is None else None
|
timeout = time_per_image * 1000 + (random.randint(int(-time_per_image * 500), int(time_per_image * 500)) if vary_randomly else 0) if single_image is None else None
|
||||||
event, values = window.read(timeout=timeout)
|
event, values = window.read(timeout=timeout)
|
||||||
|
@ -120,6 +181,28 @@ def main():
|
||||||
elif event in (sg.WIN_CLOSE_ATTEMPTED_EVENT, 'Exit'):
|
elif event in (sg.WIN_CLOSE_ATTEMPTED_EVENT, 'Exit'):
|
||||||
sg.user_settings_set_entry('-location-', window.current_location()) # The line of code to save the position before exiting
|
sg.user_settings_set_entry('-location-', window.current_location()) # The line of code to save the position before exiting
|
||||||
break
|
break
|
||||||
|
# First update the status information
|
||||||
|
# for debugging show the last update date time
|
||||||
|
if event == sg.TIMEOUT_EVENT:
|
||||||
|
if single_image is None:
|
||||||
|
image_name =random.choice(images)
|
||||||
|
image_data = convert_to_bytes(os.path.join(image_folder, image_name))
|
||||||
|
window['-FOLDER-'].update(image_folder)
|
||||||
|
else:
|
||||||
|
image_name = single_image
|
||||||
|
image_data = convert_to_bytes(single_image, (width, height))
|
||||||
|
window['-FILENAME-'].update(image_name)
|
||||||
|
window['-IMAGE-'].update(data=image_data)
|
||||||
|
window['-REFRESHED-'].update(datetime.datetime.now().strftime("%m/%d/%Y %I:%M:%S %p"))
|
||||||
|
if event == sg.WINDOW_CONFIG_EVENT:
|
||||||
|
new_size = window.size
|
||||||
|
if new_size != window_size:
|
||||||
|
print(f'resizing {new_size}')
|
||||||
|
(width, height) = new_size
|
||||||
|
image_data = convert_to_bytes(image_data, (width, height))
|
||||||
|
window['-IMAGE-'].update(data=image_data)
|
||||||
|
window.size = get_image_size(image_data)
|
||||||
|
window_size = window.size
|
||||||
if event == 'Edit Me':
|
if event == 'Edit Me':
|
||||||
sg.execute_editor(__file__)
|
sg.execute_editor(__file__)
|
||||||
elif event == 'Choose Image Folder':
|
elif event == 'Choose Image Folder':
|
||||||
|
@ -175,12 +258,15 @@ def main():
|
||||||
window.close()
|
window.close()
|
||||||
window = make_window(loc)
|
window = make_window(loc)
|
||||||
elif event == 'Choose Single Image':
|
elif event == 'Choose Single Image':
|
||||||
single_image = sg.popup_get_file('Choose single image to show', history=True)
|
image_name = single_image = sg.popup_get_file('Choose single image to show', history=True)
|
||||||
sg.user_settings_set_entry('-single image-', single_image)
|
sg.user_settings_set_entry('-single image-', single_image)
|
||||||
|
(width, height) = get_image_size(single_image)
|
||||||
|
sg.user_settings_set_entry('-image size-', (width, height))
|
||||||
|
image_data = convert_to_bytes(image_name, (width, height))
|
||||||
|
window['-IMAGE-'].update(data=image_data)
|
||||||
|
window.size = window_size = (width, height)
|
||||||
window.close()
|
window.close()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
# reset_settings() # if get corrupted problems, uncomment this
|
||||||
main()
|
main()
|
|
@ -275,7 +275,7 @@ def main(location):
|
||||||
[sg.T(size=(8, 1), font='Any 14', justification='c', background_color='black', k='-RAM USED-')],
|
[sg.T(size=(8, 1), font='Any 14', justification='c', background_color='black', k='-RAM USED-')],
|
||||||
]
|
]
|
||||||
|
|
||||||
window = sg.Window('CPU Usage Widget Square', layout, location=location, no_titlebar=True, grab_anywhere=True, margins=(0, 0), element_padding=(0, 0), alpha_channel=ALPHA, background_color='black', element_justification='c', finalize=True, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_EXIT)
|
window = sg.Window('CPU Usage Widget Square', layout, location=location, no_titlebar=True, grab_anywhere=True, margins=(0, 0), element_padding=(0, 0), alpha_channel=ALPHA, background_color='black', element_justification='c', finalize=True, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_EXIT, enable_close_attempted_event=True)
|
||||||
|
|
||||||
gauge = Gauge(pointer_color=sg.theme_text_color(), clock_color=sg.theme_text_color(), major_tick_color=sg.theme_text_color(),
|
gauge = Gauge(pointer_color=sg.theme_text_color(), clock_color=sg.theme_text_color(), major_tick_color=sg.theme_text_color(),
|
||||||
minor_tick_color=sg.theme_input_background_color(), pointer_outer_color=sg.theme_text_color(), major_tick_start_radius=45,
|
minor_tick_color=sg.theme_input_background_color(), pointer_outer_color=sg.theme_text_color(), major_tick_start_radius=45,
|
||||||
|
@ -290,7 +290,7 @@ def main(location):
|
||||||
|
|
||||||
if gauge.change():
|
if gauge.change():
|
||||||
new_angle = ram_percent*180/100
|
new_angle = ram_percent*180/100
|
||||||
window['-gauge VALUE-'].update(f'{ram_percent}')
|
window['-gauge VALUE-'].update(f'{ram_percent}%')
|
||||||
window['-RAM USED-'].update(f'{human_size(ram.used)}')
|
window['-RAM USED-'].update(f'{human_size(ram.used)}')
|
||||||
gauge.change(degree=new_angle, step=180)
|
gauge.change(degree=new_angle, step=180)
|
||||||
gauge.change()
|
gauge.change()
|
||||||
|
@ -298,10 +298,13 @@ def main(location):
|
||||||
|
|
||||||
# update the window, wait for a while, then check for exit
|
# update the window, wait for a while, then check for exit
|
||||||
event, values = window.read(timeout=UPDATE_FREQUENCY_MILLISECONDS)
|
event, values = window.read(timeout=UPDATE_FREQUENCY_MILLISECONDS)
|
||||||
if event == sg.WIN_CLOSED or event == 'Exit':
|
if event in (sg.WIN_CLOSE_ATTEMPTED_EVENT, 'Exit'):
|
||||||
|
sg.user_settings_set_entry('-location-', window.current_location()) # The line of code to save the position before exiting
|
||||||
break
|
break
|
||||||
if event == 'Edit Me':
|
if event == 'Edit Me':
|
||||||
sg.execute_editor(__file__)
|
sg.execute_editor(__file__)
|
||||||
|
elif event == 'Version':
|
||||||
|
sg.popup_scrolled(__file__, sg.get_versions(), location=window.current_location(), keep_on_top=True, non_blocking=True)
|
||||||
window.close()
|
window.close()
|
||||||
|
|
||||||
|
|
||||||
|
@ -311,5 +314,5 @@ if __name__ == '__main__':
|
||||||
location = sys.argv[1].split(',')
|
location = sys.argv[1].split(',')
|
||||||
location = (int(location[0]), int(location[1]))
|
location = (int(location[0]), int(location[1]))
|
||||||
else:
|
else:
|
||||||
location = (None, None)
|
location = sg.user_settings_get_entry('-location-', (None, None))
|
||||||
main(location)
|
main(location)
|
||||||
|
|
|
@ -30,7 +30,7 @@ def main(location):
|
||||||
graph = sg.Graph(GSIZE, (0, 0), GSIZE, key='-GRAPH-', enable_events=True)
|
graph = sg.Graph(GSIZE, (0, 0), GSIZE, key='-GRAPH-', enable_events=True)
|
||||||
layout = [[graph]]
|
layout = [[graph]]
|
||||||
|
|
||||||
window = sg.Window('RAM Usage Widget Square', layout, location=location, no_titlebar=True, grab_anywhere=True, margins=(0, 0), element_padding=(0, 0), alpha_channel=ALPHA, finalize=True, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_EXIT, enable_close_attempted_event=True)
|
window = sg.Window('RAM Usage Widget Square', layout, location=location, no_titlebar=True, grab_anywhere=True, margins=(0, 0), element_padding=(0, 0), alpha_channel=ALPHA, finalize=True, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_EXIT, enable_close_attempted_event=True, keep_on_top=True)
|
||||||
|
|
||||||
|
|
||||||
while True: # Event Loop
|
while True: # Event Loop
|
||||||
|
@ -47,8 +47,10 @@ def main(location):
|
||||||
if event != sg.WIN_CLOSED:
|
if event != sg.WIN_CLOSED:
|
||||||
sg.user_settings_set_entry('-location-', window.current_location()) # The line of code to save the position before exiting
|
sg.user_settings_set_entry('-location-', window.current_location()) # The line of code to save the position before exiting
|
||||||
break
|
break
|
||||||
elif event == 'Edit Me':
|
if event == 'Edit Me':
|
||||||
sg.execute_editor(__file__)
|
sg.execute_editor(__file__)
|
||||||
|
elif event == 'Version':
|
||||||
|
sg.popup_scrolled(__file__, sg.get_versions(), location=window.current_location(), keep_on_top=True, non_blocking=True)
|
||||||
|
|
||||||
graph.delete_figure(rect_id)
|
graph.delete_figure(rect_id)
|
||||||
graph.delete_figure(text_id1)
|
graph.delete_figure(text_id1)
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
import PySimpleGUI as sg
|
||||||
|
import time
|
||||||
|
|
||||||
|
"""
|
||||||
|
Demo Program - Timer Desktop Widget using Window.timer_start and Window.timer_stop
|
||||||
|
|
||||||
|
This is a re-implementation of the original timer desktop widget that used window.read timeouts as
|
||||||
|
the means of getting timer events.
|
||||||
|
|
||||||
|
This program uses the new Window.timer_start to get timer events. It is simpler because:
|
||||||
|
There is only 1 call to window.read and it's in the standard location in the event loop
|
||||||
|
The timer pause/run button uses the timer_start and timer_stop calls - perhaps more intuitive
|
||||||
|
|
||||||
|
Note that this Demo Program requires PySimpleGUI 4.60.4.132 and greater
|
||||||
|
|
||||||
|
Copyright 2022 PySimpleGUI
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def time_as_int():
|
||||||
|
return int(round(time.time() * 100))
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------- Create Form ----------------
|
||||||
|
sg.theme('Black')
|
||||||
|
|
||||||
|
layout = [[sg.Text('')],
|
||||||
|
[sg.Text('', size=(8, 2), font=('Helvetica', 20),
|
||||||
|
justification='center', key='text')],
|
||||||
|
[sg.Button('Pause', key='-RUN-PAUSE-', button_color=('white', '#001480')),
|
||||||
|
sg.Button('Reset', button_color=('white', '#007339'), key='-RESET-'),
|
||||||
|
sg.Exit(button_color=('white', 'firebrick4'), key='Exit')]]
|
||||||
|
|
||||||
|
window = sg.Window('Running Timer', layout,
|
||||||
|
no_titlebar=True,
|
||||||
|
auto_size_buttons=False,
|
||||||
|
keep_on_top=True,
|
||||||
|
grab_anywhere=True,
|
||||||
|
element_padding=(0, 0),
|
||||||
|
finalize=True,
|
||||||
|
element_justification='c',
|
||||||
|
right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_EXIT)
|
||||||
|
|
||||||
|
current_time, paused_time, paused = 0, 0, False
|
||||||
|
start_time = time_as_int()
|
||||||
|
timer_id = window.timer_start(10)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
event, values = window.read()
|
||||||
|
if event in (sg.WIN_CLOSED, 'Exit'): # ALWAYS give a way out of program
|
||||||
|
break
|
||||||
|
# --------- Handle events --------
|
||||||
|
if event == '-RUN-PAUSE-':
|
||||||
|
paused = not paused
|
||||||
|
if paused:
|
||||||
|
window.timer_stop(timer_id)
|
||||||
|
paused_time = time_as_int()
|
||||||
|
else:
|
||||||
|
timer_id = window.timer_start(10)
|
||||||
|
start_time = start_time + time_as_int() - paused_time
|
||||||
|
window['-RUN-PAUSE-'].update('Run' if paused else 'Pause')
|
||||||
|
elif event == sg.EVENT_TIMER:
|
||||||
|
current_time = time_as_int() - start_time
|
||||||
|
if event == '-RESET-':
|
||||||
|
current_time = 0
|
||||||
|
start_time = paused_time = time_as_int()
|
||||||
|
elif event == 'Edit Me':
|
||||||
|
sg.execute_editor(__file__)
|
||||||
|
# --------- Display timer_id in window --------
|
||||||
|
window['text'].update('{:02d}:{:02d}.{:02d}'.format((current_time // 100) // 60,
|
||||||
|
(current_time // 100) % 60,
|
||||||
|
current_time % 100))
|
||||||
|
window.close()
|
|
@ -75,8 +75,9 @@ def change_settings(settings, window_location=(None, None)):
|
||||||
nearest_postal = ''
|
nearest_postal = ''
|
||||||
|
|
||||||
layout = [[sg.T('Enter Zipcode or City for your location')],
|
layout = [[sg.T('Enter Zipcode or City for your location')],
|
||||||
[sg.I(settings.get('-location-', nearest_postal), size=(15, 1), key='-LOCATION-')],
|
[sg.I(settings.get('-location-', nearest_postal), size=(15, 1), key='-LOCATION-'), sg.T('City')],
|
||||||
[sg.I(settings.get('-country-', 'US'), size=(15, 1), key='-COUNTRY-')],
|
[sg.I(settings.get('-country-', 'US'), size=(15, 1), key='-COUNTRY-'), sg.T('Country')],
|
||||||
|
[sg.I(settings.get('-friends name-', ''), size=(15, 1), key='-FRIENDS NAME-'), sg.T('Who')],
|
||||||
[sg.I(settings.get('-api key-', ''), size=(32, 1), key='-API KEY-')],
|
[sg.I(settings.get('-api key-', ''), size=(32, 1), key='-API KEY-')],
|
||||||
[sg.CBox('Use Metric For Temperatures', default=settings.get('-celsius-', False),key='-CELSIUS-')],
|
[sg.CBox('Use Metric For Temperatures', default=settings.get('-celsius-', False),key='-CELSIUS-')],
|
||||||
[sg.B('Ok', border_width=0, bind_return_key=True), sg.B('Register For a Key', border_width=0, k='-REGISTER-'), sg.B('Cancel', border_width=0)], ]
|
[sg.B('Ok', border_width=0, bind_return_key=True), sg.B('Register For a Key', border_width=0, k='-REGISTER-'), sg.B('Cancel', border_width=0)], ]
|
||||||
|
@ -96,6 +97,7 @@ def change_settings(settings, window_location=(None, None)):
|
||||||
settings['-country-'] = values['-COUNTRY-']
|
settings['-country-'] = values['-COUNTRY-']
|
||||||
API_KEY = settings['-api key-'] = values['-API KEY-']
|
API_KEY = settings['-api key-'] = values['-API KEY-']
|
||||||
settings['-celsius-'] = values['-CELSIUS-']
|
settings['-celsius-'] = values['-CELSIUS-']
|
||||||
|
settings['-friends name-'] = values['-FRIENDS NAME-']
|
||||||
else:
|
else:
|
||||||
API_KEY = settings['-api key-']
|
API_KEY = settings['-api key-']
|
||||||
user_location = settings['-location-']
|
user_location = settings['-location-']
|
||||||
|
@ -186,43 +188,42 @@ def metric_row(metric):
|
||||||
sg.Text(APP_DATA[metric], font=('Arial', 10, 'bold'), pad=(0, 0), size=(9, 1), key=metric)]
|
sg.Text(APP_DATA[metric], font=('Arial', 10, 'bold'), pad=(0, 0), size=(9, 1), key=metric)]
|
||||||
|
|
||||||
|
|
||||||
def create_window(win_location):
|
def create_window(win_location, settings):
|
||||||
""" Create the application window """
|
""" Create the application window """
|
||||||
|
friends_name = settings.get('-friends name-', '')
|
||||||
col1 = sg.Column(
|
col1 = sg.Column(
|
||||||
[[sg.Text(APP_DATA['City'], font=('Arial Rounded MT Bold', 18), pad=((10, 0), (50, 0)), size=(18, 1), background_color=BG_COLOR, text_color=TXT_COLOR, key='City')],
|
[[sg.Text(APP_DATA['City'], font=('Arial Rounded MT Bold', 18), background_color=BG_COLOR, text_color=TXT_COLOR, key='City'),
|
||||||
|
sg.Text(f' - {friends_name}' if friends_name else '', background_color=BG_COLOR, text_color=TXT_COLOR, font=('Arial Rounded MT Bold', 18),)],
|
||||||
[sg.Text(APP_DATA['Description'], font=('Arial', 12), pad=(10, 0), background_color=BG_COLOR, text_color=TXT_COLOR, key='Description')]],
|
[sg.Text(APP_DATA['Description'], font=('Arial', 12), pad=(10, 0), background_color=BG_COLOR, text_color=TXT_COLOR, key='Description')]],
|
||||||
background_color=BG_COLOR, key='COL1')
|
background_color=BG_COLOR, key='COL1')
|
||||||
|
|
||||||
col2 = sg.Column(
|
col2 = sg.Column([[sg.Image(data=APP_DATA['Icon'], size=(100, 100), background_color=BG_COLOR, key='Icon')]],
|
||||||
[[sg.Text('×', font=('Arial Black', 16), pad=(0, 0), justification='right', background_color=BG_COLOR, text_color=TXT_COLOR, enable_events=True, key='-QUIT-')],
|
element_justification='center', background_color=BG_COLOR, key='COL2')
|
||||||
[sg.Image(data=APP_DATA['Icon'], pad=((5, 10), (0, 0)), size=(100, 100), background_color=BG_COLOR, key='Icon')]],
|
|
||||||
element_justification='center', background_color=BG_COLOR, key='COL2')
|
|
||||||
|
|
||||||
col3 = sg.Column(
|
col3 = sg.Column([[sg.Text(APP_DATA['Updated'], font=('Arial', 8), background_color=BG_COLOR, text_color=TXT_COLOR, key='Updated')]],
|
||||||
[[sg.Text(APP_DATA['Updated'], font=('Arial', 8), background_color=BG_COLOR, text_color=TXT_COLOR, key='Updated')]],
|
pad=(10, 5), element_justification='left', background_color=BG_COLOR, key='COL3')
|
||||||
pad=(10, 5), element_justification='left', background_color=BG_COLOR, key='COL3')
|
|
||||||
|
|
||||||
col4 = sg.Column(
|
col4 = sg.Column(
|
||||||
[[sg.Text('Settings', font=('Arial', 8, 'italic'), background_color=BG_COLOR, text_color=TXT_COLOR, enable_events=True, key='-CHANGE-'),
|
[[sg.Text('Settings', font=('Arial', 8, 'italic'), background_color=BG_COLOR, text_color=TXT_COLOR, enable_events=True, key='-CHANGE-'),
|
||||||
sg.Text('Refresh', font=('Arial', 8, 'italic'), background_color=BG_COLOR, text_color=TXT_COLOR, enable_events=True, key='-REFRESH-')]],
|
sg.Text('Refresh', font=('Arial', 8, 'italic'), background_color=BG_COLOR, text_color=TXT_COLOR, enable_events=True, key='-REFRESH-')]],
|
||||||
pad=(10, 5), element_justification='right', background_color=BG_COLOR, key='COL4')
|
pad=(10, 5), element_justification='right', background_color=BG_COLOR, key='COL4')
|
||||||
|
|
||||||
top_col = sg.Column([[col1, col2]], pad=(0, 0), background_color=BG_COLOR, key='TopCOL')
|
top_col = sg.Column([[col1, sg.Push(background_color=BG_COLOR), col2, sg.Text('×', font=('Arial Black', 16), pad=(0, 0), justification='right', background_color=BG_COLOR, text_color=TXT_COLOR, enable_events=True, key='-QUIT-')]], pad=(0, 0), background_color=BG_COLOR, key='TopCOL')
|
||||||
|
|
||||||
bot_col = sg.Column([[col3, col4]], pad=(0, 0), background_color=BG_COLOR, key='BotCOL')
|
bot_col = sg.Column([[col3, col4]],
|
||||||
|
pad=(0, 0), background_color=BG_COLOR, key='BotCOL')
|
||||||
|
|
||||||
lf_col = sg.Column(
|
lf_col = sg.Column(
|
||||||
[[sg.Text(APP_DATA['Temp'], font=('Haettenschweiler', 90), pad=((10, 0), (0, 0)), justification='center', key='Temp')]],
|
[[sg.Text(APP_DATA['Temp'], font=('Haettenschweiler', 90), pad=((10, 0), (0, 0)), justification='center', key='Temp')]],
|
||||||
pad=(10, 0), element_justification='center', key='LfCOL')
|
pad=(10, 0), element_justification='center', key='LfCOL')
|
||||||
|
|
||||||
rt_col = sg.Column(
|
rt_col = sg.Column([metric_row('Feels Like'), metric_row('Wind'), metric_row('Humidity'), metric_row('Precip 1hr'), metric_row('Pressure')],
|
||||||
[metric_row('Feels Like'), metric_row('Wind'), metric_row('Humidity'), metric_row('Precip 1hr'), metric_row('Pressure')],
|
pad=((15, 0), (25, 5)), key='RtCOL')
|
||||||
pad=((15, 0), (25, 5)), key='RtCOL')
|
|
||||||
|
|
||||||
layout = [[top_col],
|
layout = [[top_col],
|
||||||
[lf_col, rt_col],
|
[lf_col, rt_col],
|
||||||
[bot_col],
|
[bot_col],
|
||||||
[sg.Text(f'{sg.ver} {sg.framework_version} {sys.version}', font=('Arial', 8), background_color=BG_COLOR, text_color=TXT_COLOR, pad=(0,0))]]
|
[sg.Text(f'PSG: {sg.ver} Tk:{sg.framework_version} Py:{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}', font=('Arial', 8), justification='c', background_color=BG_COLOR, text_color=TXT_COLOR, pad=(0,0), expand_x=True)]]
|
||||||
|
|
||||||
window = sg.Window(layout=layout, title='Weather Widget', margins=(0, 0), finalize=True, location=win_location,
|
window = sg.Window(layout=layout, title='Weather Widget', margins=(0, 0), finalize=True, location=win_location,
|
||||||
element_justification='center', keep_on_top=True, no_titlebar=True, grab_anywhere=True, alpha_channel=ALPHA,
|
element_justification='center', keep_on_top=True, no_titlebar=True, grab_anywhere=True, alpha_channel=ALPHA,
|
||||||
|
@ -277,28 +278,36 @@ def main(refresh_rate, win_location):
|
||||||
sg.popup_error('Having trouble with location. Your location: ', location)
|
sg.popup_error('Having trouble with location. Your location: ', location)
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
window = create_window(win_location)
|
window = create_window(win_location, settings)
|
||||||
|
|
||||||
|
|
||||||
while True: # Event Loop
|
while True: # Event Loop
|
||||||
event, values = window.read(timeout=refresh_in_milliseconds)
|
event, values = window.read(timeout=refresh_in_milliseconds)
|
||||||
if event in (None, '-QUIT-', 'Exit', sg.WIN_CLOSE_ATTEMPTED_EVENT):
|
if event in (None, '-QUIT-', 'Exit', sg.WIN_CLOSE_ATTEMPTED_EVENT):
|
||||||
sg.user_settings_set_entry('-win location-', window.current_location()) # The line of code to save the position before exiting
|
sg.user_settings_set_entry('-win location-', window.current_location()) # The line of code to save the position before exiting
|
||||||
break
|
break
|
||||||
if event == '-CHANGE-':
|
try:
|
||||||
x, y = window.current_location()
|
if event == '-CHANGE-':
|
||||||
settings = change_settings(settings, (x + 200, y+50))
|
x, y = window.current_location()
|
||||||
elif event == '-REFRESH-':
|
settings = change_settings(settings, (x + 200, y+50))
|
||||||
sg.popup_quick_message('Refreshing...', keep_on_top=True, background_color='red', text_color='white',
|
window.close()
|
||||||
auto_close_duration=3, non_blocking=False, location=(window.current_location()[0]+window.size[0]//2-30, window.current_location()[1]+window.size[1]//2-10))
|
window = create_window(win_location, settings)
|
||||||
elif event == 'Edit Me':
|
elif event == '-REFRESH-':
|
||||||
sg.execute_editor(__file__)
|
sg.popup_quick_message('Refreshing...', keep_on_top=True, background_color='red', text_color='white',
|
||||||
elif event == 'Versions':
|
auto_close_duration=3, non_blocking=False, location=(window.current_location()[0]+window.size[0]//2-30, window.current_location()[1]+window.size[1]//2-10))
|
||||||
sg.main_get_debug_data()
|
elif event == 'Edit Me':
|
||||||
elif event != sg.TIMEOUT_KEY:
|
sg.execute_editor(__file__)
|
||||||
sg.Print('Unknown event received\nEvent & values:\n', event, values, location=win_location)
|
elif event == 'Versions':
|
||||||
|
sg.main_get_debug_data()
|
||||||
|
elif event != sg.TIMEOUT_KEY:
|
||||||
|
sg.Print('Unknown event received\nEvent & values:\n', event, values, location=win_location)
|
||||||
|
|
||||||
update_weather()
|
update_weather()
|
||||||
update_metrics(window)
|
update_metrics(window)
|
||||||
|
except Exception as e:
|
||||||
|
sg.Print('*** GOT Exception in event loop ***', c='white on red', location=window.current_location(), keep_on_top=True)
|
||||||
|
sg.Print('File = ', __file__, f'Window title: {window.Title}')
|
||||||
|
sg.Print('Exception = ', e, wait=True) # IMPORTANT to add a wait/blocking so that the print pauses execution. Otherwise program continue and exits
|
||||||
window.close()
|
window.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
* If-Else
|
* If-Else
|
||||||
* Dictionaries
|
* Dictionaries
|
||||||
* Functions as keys
|
* Functions as keys
|
||||||
|
* Lambda as key (callable like functions are)
|
||||||
|
|
||||||
The handlers in this demo are all functions that are called once the event is detected
|
The handlers in this demo are all functions that are called once the event is detected
|
||||||
|
|
||||||
|
@ -22,7 +23,7 @@
|
||||||
event loop rather than functions, then do it in the event loop.
|
event loop rather than functions, then do it in the event loop.
|
||||||
|
|
||||||
http://www.PySimpleGUI.org
|
http://www.PySimpleGUI.org
|
||||||
Copyright 2021 PySimpleGUI
|
Copyright 2021, 2022, 2023 PySimpleGUI
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import PySimpleGUI as sg
|
import PySimpleGUI as sg
|
||||||
|
@ -76,7 +77,7 @@ def main():
|
||||||
[sg.Text('Status:'), sg.Text(size=(3, 1), key='-STATUS-')],
|
[sg.Text('Status:'), sg.Text(size=(3, 1), key='-STATUS-')],
|
||||||
[sg.Text(size=(50, 1), key='-OUT-')],
|
[sg.Text(size=(50, 1), key='-OUT-')],
|
||||||
[sg.Button('Simple'), sg.Button('Go'), sg.Button('Stop'), sg.Button('Other', key=do_other),
|
[sg.Button('Simple'), sg.Button('Go'), sg.Button('Stop'), sg.Button('Other', key=do_other),
|
||||||
sg.Button('Tuple', key=(1,2)), sg.Button('Bad')]]
|
sg.Button('Tuple', key=(1,2)), sg.Button('Lambda', key= lambda window: do_other(window)), sg.Button('Bad')]]
|
||||||
|
|
||||||
window = sg.Window('Dispatchers', layout, font='Default 16', keep_on_top=True)
|
window = sg.Window('Dispatchers', layout, font='Default 16', keep_on_top=True)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import PySimpleGUI as sg
|
import PySimpleGUI as sg
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Demo "Edit Me"
|
Demo "Edit Me" (and Version)
|
||||||
|
|
||||||
More and more of these Demos are getting an "Edit me" option added.
|
More and more of these Demos are getting an "Edit me" option added.
|
||||||
|
|
||||||
|
@ -12,26 +12,26 @@ import PySimpleGUI as sg
|
||||||
You can add this capability to your program by adding a right click menu to your window and calling the
|
You can add this capability to your program by adding a right click menu to your window and calling the
|
||||||
editor that you set up in the global PySimpleGUI options.
|
editor that you set up in the global PySimpleGUI options.
|
||||||
|
|
||||||
You need to do 2 things to make this work:
|
A constant MENU_RIGHT_CLICK_EDITME_VER_EXIT, when set at the right click menu shows a "Version" and "Edit Me" meny item.
|
||||||
1. Add a right click menu - requires you to add 1 parameter to your Window creation
|
|
||||||
2. Add 1 if statement to your event loop.
|
|
||||||
|
|
||||||
You will need to have first set up your editor by using the menu in sg.main()
|
You will need to have first set up your editor by using the menu in sg.main()
|
||||||
|
|
||||||
Copyright 2021 PySimpleGUI.org
|
Copyright 2021, 2022, 2023 PySimpleGUI.org
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
layout = [[sg.Text('Edit this program by right clicking and choosing "Edit me"')],
|
layout = [[sg.Text('Edit this program by right clicking and choosing "Edit me"')],
|
||||||
[sg.Button('Exit')]]
|
[sg.Button('Exit')]]
|
||||||
|
|
||||||
window = sg.Window('Edit Me Right Click Menu Demo', layout, right_click_menu=[[''], ['Edit Me', 'Exit',]])
|
window = sg.Window('Edit Me Right Click Menu Demo', layout, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_EXIT)
|
||||||
|
|
||||||
while True: # Event Loop
|
while True: # Event Loop
|
||||||
event, values = window.read()
|
event, values = window.read()
|
||||||
if event == sg.WIN_CLOSED or event == 'Exit':
|
if event == sg.WIN_CLOSED or event == 'Exit':
|
||||||
break
|
break
|
||||||
if event == 'Edit Me':
|
if event == 'Edit Me':
|
||||||
sg.execute_editor(__file__)
|
sg.execute_editor(__file__)
|
||||||
|
elif event == 'Version':
|
||||||
|
sg.popup_scrolled(__file__, sg.get_versions(), location=window.current_location(), keep_on_top=True, non_blocking=True)
|
||||||
|
|
||||||
window.close()
|
window.close()
|
||||||
|
|
|
@ -1,38 +1,36 @@
|
||||||
import PySimpleGUI as sg
|
import PySimpleGUI as sg
|
||||||
# import PySimpleGUIWeb as sg
|
|
||||||
# import PySimpleGUIWx as sg
|
|
||||||
# import PySimpleGUIQt as sg
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Copyright 2019 PySimpleGUI.org
|
Learn how to send emails from PySimpleGUI using the smtplib and email modules
|
||||||
|
|
||||||
|
The GUI portion is simple
|
||||||
|
|
||||||
Based on a send-email script originally written by by Israel Dryer
|
Based on a send-email script originally written by by Israel Dryer
|
||||||
|
(Thank you Israel for figuring out the hard part of the stmp and email module calls!)
|
||||||
|
|
||||||
|
Copyright 2019, 2022 PySimpleGUI
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
# If you are using a mail service that's not gmail, hotmail, live or yahoo:
|
||||||
|
# then you can enter the smtp server address here so you don't have to keep typing it into the GUI
|
||||||
|
smtp_host_default = ''
|
||||||
|
|
||||||
# used for sending the email
|
# used for sending the email
|
||||||
import smtplib as smtp
|
import smtplib as smtp
|
||||||
# used to build the email
|
# used to build the email
|
||||||
from email.message import EmailMessage
|
from email.message import EmailMessage
|
||||||
|
|
||||||
# create and send email
|
# create and send email
|
||||||
def send_an_email(from_address, to_address, subject, message_text, user, password):
|
def send_an_email(from_address, to_address, subject, message_text, user, password, smtp_host, smtp_port):
|
||||||
# SMTP Servers for popular free services... add your own if needed. Format is: address, port
|
|
||||||
google_smtp_server = 'smtp.gmail.com', 587
|
|
||||||
microsoft_smtp_server = 'smtp.office365.com', 587
|
|
||||||
yahoo_smtp_server = 'smtp.mail.yahoo.com', 587 # or port 465
|
|
||||||
|
|
||||||
# open the email server connection
|
|
||||||
if 'gmail' in user:
|
|
||||||
smtp_host, smtp_port = google_smtp_server
|
|
||||||
elif 'hotmail' in user or 'live' in user:
|
|
||||||
smtp_host, smtp_port = microsoft_smtp_server
|
|
||||||
elif 'yahoo' in user:
|
|
||||||
smtp_host, smtp_port = yahoo_smtp_server
|
|
||||||
else:
|
|
||||||
sg.popup('Username does not contain a supported email provider')
|
|
||||||
return
|
|
||||||
server = smtp.SMTP(host=smtp_host, port=smtp_port)
|
server = smtp.SMTP(host=smtp_host, port=smtp_port)
|
||||||
server.starttls()
|
server.starttls()
|
||||||
server.login(user=user, password=password)
|
try:
|
||||||
|
server.login(user=user, password=password)
|
||||||
|
except Exception as e:
|
||||||
|
sg.popup_error('Error authenticaing your email credentials', e, image=sg.EMOJI_BASE64_WEARY)
|
||||||
|
server.close()
|
||||||
|
return
|
||||||
|
|
||||||
# create the email message headers and set the payload
|
# create the email message headers and set the payload
|
||||||
msg = EmailMessage()
|
msg = EmailMessage()
|
||||||
|
@ -42,9 +40,15 @@ def send_an_email(from_address, to_address, subject, message_text, user, passwor
|
||||||
msg.set_payload(message_text)
|
msg.set_payload(message_text)
|
||||||
|
|
||||||
# open the email server and send the message
|
# open the email server and send the message
|
||||||
server.send_message(msg)
|
try:
|
||||||
|
server.send_message(msg)
|
||||||
|
except Exception as e:
|
||||||
|
sg.popup_error('Error sending your email', e, image=sg.EMOJI_BASE64_WEARY)
|
||||||
|
server.close()
|
||||||
|
return
|
||||||
|
|
||||||
server.close()
|
server.close()
|
||||||
|
sg.popup('Email sent successfully!', image=sg.EMOJI_BASE64_HAPPY_JOY)
|
||||||
|
|
||||||
'''
|
'''
|
||||||
important notes about using gmail
|
important notes about using gmail
|
||||||
|
@ -61,33 +65,47 @@ def send_an_email(from_address, to_address, subject, message_text, user, passwor
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
smtp_server_dict = {'gmail.com':'smtp.gmail.com','hotmail.com':'smtp.office365.com', 'live.com': 'smtp.office365.com', 'yahoo.com':'smtp.mail.yahoo.com'}
|
||||||
|
|
||||||
sg.theme('Dark Blue 3')
|
sg.theme('Dark Blue 3')
|
||||||
layout = [[sg.Text('Send an Email', font='Default 18')],
|
layout = [[sg.Text('Send an Email', font='Default 18')],
|
||||||
[sg.T('From:', size=(8,1)), sg.Input(key='-EMAIL FROM-', size=(35,1))],
|
[sg.T('From:', size=(8,1)), sg.Input(key='-EMAIL FROM-', size=(35,1))],
|
||||||
[sg.T('To:', size=(8,1)), sg.Input(key='-EMAIL TO-', size=(35,1))],
|
[sg.T('To:', size=(8,1)), sg.Input(key='-EMAIL TO-', size=(35,1))],
|
||||||
[sg.T('Subject:', size=(8,1)), sg.Input(key='-EMAIL SUBJECT-', size=(35,1))],
|
[sg.T('Subject:', size=(8,1)), sg.Input(key='-EMAIL SUBJECT-', size=(35,1))],
|
||||||
[sg.T('Mail login information', font='Default 18')],
|
[sg.T('Mail login information', font='Default 18')],
|
||||||
[sg.T('User:', size=(8,1)), sg.Input(key='-USER-', size=(35,1))],
|
[sg.T('User:', size=(8,1)), sg.Input(key='-USER-', size=(35,1), enable_events=True)],
|
||||||
[sg.T('Password:', size=(8,1)), sg.Input(password_char='*', key='-PASSWORD-', size=(35,1))],
|
[sg.T('Password:', size=(8,1)), sg.Input(password_char='*', key='-PASSWORD-', size=(35,1))],
|
||||||
|
[sg.T('SMTP Server Info', font='_ 14')],
|
||||||
|
[sg.T('SMTP Hostname'), sg.Input(smtp_host_default, s=20, key='-SMTP HOST-'), sg.T('SMTP Port'), sg.In(587, s=4, key='-SMTP PORT-') ],
|
||||||
[sg.Multiline('Type your message here', size=(60,10), key='-EMAIL TEXT-')],
|
[sg.Multiline('Type your message here', size=(60,10), key='-EMAIL TEXT-')],
|
||||||
[sg.Button('Send'), sg.Button('Exit')]]
|
[sg.Button('Send'), sg.Button('Exit')]]
|
||||||
|
|
||||||
window = sg.Window('Send An Email', layout)
|
window = sg.Window('Send An Email', layout)
|
||||||
|
|
||||||
while True: # Event Loop
|
while True:
|
||||||
event, values = window.read()
|
event, values = window.read()
|
||||||
if event in (sg.WIN_CLOSED, 'Exit'):
|
if event in (sg.WIN_CLOSED, 'Exit'):
|
||||||
break
|
break
|
||||||
if event == 'Send':
|
if event == 'Send':
|
||||||
if sg.__name__ != 'PySimpleGUIWeb': # auto close popups not yet supported in PySimpleGUIWeb
|
if values['-SMTP HOST-']:
|
||||||
sg.popup_quick_message('Sending your message... this will take a moment...', background_color='red')
|
sg.popup_quick_message('Sending your message... this will take a moment...', background_color='red')
|
||||||
send_an_email(from_address=values['-EMAIL FROM-'],
|
send_an_email(from_address=values['-EMAIL FROM-'],
|
||||||
to_address=values['-EMAIL TO-'],
|
to_address=values['-EMAIL TO-'],
|
||||||
subject=values['-EMAIL SUBJECT-'],
|
subject=values['-EMAIL SUBJECT-'],
|
||||||
message_text=values['-EMAIL TEXT-'],
|
message_text=values['-EMAIL TEXT-'],
|
||||||
user=values['-USER-'],
|
user=values['-USER-'],
|
||||||
password=values['-PASSWORD-'])
|
password=values['-PASSWORD-'],
|
||||||
|
smtp_host=values['-SMTP HOST-'],
|
||||||
|
smtp_port = values['-SMTP PORT-'])
|
||||||
|
else:
|
||||||
|
sg.popup_error('Missing SMTP Hostname... you have to supply a hostname (gmail, hotmail, live, yahoo are autofilled)')
|
||||||
|
elif event == '-USER-': # as the email sender is typed in, try to fill in the smtp hostname automatically
|
||||||
|
for service in smtp_server_dict.keys():
|
||||||
|
if service in values[event].lower():
|
||||||
|
window['-SMTP HOST-'].update(smtp_server_dict[service])
|
||||||
|
break
|
||||||
|
|
||||||
window.close()
|
window.close()
|
||||||
|
|
||||||
main()
|
if __name__ == '__main__':
|
||||||
|
main()
|
60
DemoPrograms/Demo_Focus_Navigation_Using_Arrow_Keys.py
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import PySimpleGUI as sg
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Demo - Navigating a window's focus using arrow keys
|
||||||
|
|
||||||
|
This Demo Program has 2 features of PySimpleGUI in use:
|
||||||
|
1. Binding the arrow keys
|
||||||
|
2. Navigating a window's elements using focus
|
||||||
|
|
||||||
|
The first step is to bind the left, right and down arrows to an event.
|
||||||
|
The call to window.bind will cause events to be generated when these keys are pressed
|
||||||
|
|
||||||
|
The next step is to add the focus navigation to your event loop.
|
||||||
|
When the right key is pressed, the focus moves to the element that should get focus next
|
||||||
|
When the left arrow key is pressed, the focus moves to the previous element
|
||||||
|
And when the down arrow is pressed the program exits
|
||||||
|
|
||||||
|
|
||||||
|
Copyright 2022 PySimpleGUI
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
layout = [ [sg.Text('My Window')],
|
||||||
|
[sg.Input(key='-IN-')],
|
||||||
|
[sg.Input(key='-IN2-')],
|
||||||
|
[sg.Input(key='-IN3-')],
|
||||||
|
[sg.Input(key='-IN4-')],
|
||||||
|
[sg.Input(key='-IN5-')],
|
||||||
|
[sg.Input(key='-IN6-')],
|
||||||
|
[sg.Input(key='-IN7-')],
|
||||||
|
[sg.Button('Go'), sg.Button('Exit')]]
|
||||||
|
|
||||||
|
window = sg.Window('Window Title', layout, finalize=True)
|
||||||
|
|
||||||
|
# Bind the Left, Right and Down arrow keys to events
|
||||||
|
window.bind('<Right>', '-NEXT-')
|
||||||
|
window.bind('<Left>', '-PREV-')
|
||||||
|
window.bind('<Down>', 'Exit')
|
||||||
|
|
||||||
|
while True: # Event Loop
|
||||||
|
event, values = window.read()
|
||||||
|
print(event, values)
|
||||||
|
if event == sg.WIN_CLOSED or event == 'Exit':
|
||||||
|
break
|
||||||
|
# Right arrow pressed, so move to the next element that should get focus
|
||||||
|
if event == '-NEXT-':
|
||||||
|
next_element = window.find_element_with_focus().get_next_focus()
|
||||||
|
next_element.set_focus()
|
||||||
|
|
||||||
|
# Left arrow pressed, so move to the previous element that should get focus
|
||||||
|
if event == '-PREV-':
|
||||||
|
prev_element = window.find_element_with_focus().get_previous_focus()
|
||||||
|
prev_element.set_focus()
|
||||||
|
window.close()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -1,5 +1,6 @@
|
||||||
import pyglet
|
import pyglet
|
||||||
import PySimpleGUI as sg
|
import PySimpleGUI as sg
|
||||||
|
import os
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Demo - Using pyglet to get custom fonts into PySimpleGUI
|
Demo - Using pyglet to get custom fonts into PySimpleGUI
|
||||||
|
@ -17,8 +18,11 @@ import PySimpleGUI as sg
|
||||||
http://scripts.sil.org/OFL
|
http://scripts.sil.org/OFL
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pyglet.font.add_file(r".\OpenFlame.ttf")
|
font_file = os.path.join(os.path.dirname(__file__), "OpenFlame.ttf")
|
||||||
|
|
||||||
|
pyglet.font.add_file(font_file)
|
||||||
|
|
||||||
|
# sg.execute_command_subprocess(font_file)
|
||||||
font1 = ("Open Flame", 40) # Note - use the font "face name" not the filename when specifying the font
|
font1 = ("Open Flame", 40) # Note - use the font "face name" not the filename when specifying the font
|
||||||
font2 = ("Courier New", 40)
|
font2 = ("Courier New", 40)
|
||||||
font3 = ("Helvetica", 40)
|
font3 = ("Helvetica", 40)
|
||||||
|
|
47
DemoPrograms/Demo_Frame_For_Screen_Captures.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import PySimpleGUI as sg
|
||||||
|
|
||||||
|
"""
|
||||||
|
Demo Frame For Screen Captures
|
||||||
|
|
||||||
|
This program can be used to help you record videos.
|
||||||
|
|
||||||
|
Because it relies on the "transparent color" feature that's only available on Windows, this Demo is only going
|
||||||
|
to work the indended way on Windows.
|
||||||
|
|
||||||
|
Some video recorders that record a portion of the screen do not show you, at all times, what portion of the screen
|
||||||
|
is being recorded. This can make it difficult for you to stay within the bounds being recorded.
|
||||||
|
This demo program is meant to help the situation by showing a thin line that is 20 pixels larger than the area
|
||||||
|
being recorded.
|
||||||
|
|
||||||
|
The top edge of the window has the controls. There's an exit button, a solid "bar" for you to grab with your mouse to move
|
||||||
|
the frame around your window, and 2 inputs with a "resize" button that enables you to set the frame to the size you want to stay
|
||||||
|
within.
|
||||||
|
|
||||||
|
|
||||||
|
Copyright 2022 PySimpleGUI.org
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
offset = (20, 20) # Number of extra pixels to add to the recording area
|
||||||
|
default_size = (1920, 1080) # The default size of the recording
|
||||||
|
location = (None, None) # A specific location to place the window if you want a specific spot
|
||||||
|
|
||||||
|
window = sg.Window('Window Title',
|
||||||
|
[[sg.Button('Exit'), sg.T(sg.SYMBOL_SQUARE * 10, grab=True), sg.I(default_size[0], s=4, k='-W-'), sg.I(default_size[1], s=4, k='-H-'), sg.B('Resize')],
|
||||||
|
[sg.Frame('', [[]], s=(default_size[0] + offset[0], default_size[1] + offset[1]), k='-FRAME-')]], transparent_color=sg.theme_background_color(),
|
||||||
|
right_click_menu=['', ['Edit Me', 'Exit']], location=location, no_titlebar=True, keep_on_top=True)
|
||||||
|
|
||||||
|
while True: # Event Loop
|
||||||
|
event, values = window.read()
|
||||||
|
if event == sg.WIN_CLOSED or event == 'Exit':
|
||||||
|
break
|
||||||
|
if event == 'Edit Me':
|
||||||
|
sg.execute_editor(__file__)
|
||||||
|
elif event == 'Resize':
|
||||||
|
window['-FRAME-'].set_size((int(values['-W-']) + offset[0], int(values['-H-']) + offset[1]))
|
||||||
|
window.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
44
DemoPrograms/Demo_Graph_Drag_Rectangle_Super_Simple.py
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import PySimpleGUI as sg
|
||||||
|
|
||||||
|
"""
|
||||||
|
Demo - Drag a rectangle and move
|
||||||
|
|
||||||
|
This demo shows how to use a Graph Element to draw a square and move it with the mouse.
|
||||||
|
It's a very simple, single element program. Like many Demo Programs, it started as
|
||||||
|
a "Test Harness" that demonstrated a bug that happened with a timeout of 0
|
||||||
|
was added to the window.read()
|
||||||
|
|
||||||
|
Original code comes courtesy of user @davesmivers .... Thanks Dave!!
|
||||||
|
|
||||||
|
Copyright 2022 PySimpleGUI
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
GRAPH_SIZE = (400, 400)
|
||||||
|
START = (200, 200) # We'll assume X and Y are both this value
|
||||||
|
SQ_SIZE = 40 # Both width and height will be this value
|
||||||
|
|
||||||
|
layout = [[sg.Graph(
|
||||||
|
canvas_size=GRAPH_SIZE, graph_bottom_left=(0, 0), graph_top_right=GRAPH_SIZE, # Define the graph area
|
||||||
|
change_submits=True, # mouse click events
|
||||||
|
drag_submits=True, # mouse move events
|
||||||
|
background_color='lightblue',
|
||||||
|
key="-GRAPH-",
|
||||||
|
pad=0)]]
|
||||||
|
|
||||||
|
window = sg.Window("Simple Square Movement", layout, finalize=True, margins=(0,0))
|
||||||
|
|
||||||
|
# draw the square we'll move around
|
||||||
|
square = window["-GRAPH-"].draw_rectangle(START, (START[0]+SQ_SIZE, START[1]+SQ_SIZE), fill_color='black')
|
||||||
|
|
||||||
|
while True:
|
||||||
|
event, values = window.read()
|
||||||
|
if event == sg.WIN_CLOSED:
|
||||||
|
break
|
||||||
|
print(event, values) if event != sg.TIMEOUT_EVENT else None # our normal debug print, but for this demo, don't spam output with timeouts
|
||||||
|
|
||||||
|
if event == "-GRAPH-": # if there's a "Graph" event, then it's a mouse movement. Move the square
|
||||||
|
x, y = values["-GRAPH-"] # get mouse position
|
||||||
|
window["-GRAPH-"].relocate_figure(square, x - SQ_SIZE // 2, y + SQ_SIZE // 2) # Move using center of square to mouse pos
|
||||||
|
|
||||||
|
window.close()
|
|
@ -1,5 +1,4 @@
|
||||||
import PySimpleGUI as sg
|
import PySimpleGUI as sg
|
||||||
from PIL import ImageGrab
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Demo - Drawing and moving demo
|
Demo - Drawing and moving demo
|
||||||
|
@ -7,22 +6,9 @@ from PIL import ImageGrab
|
||||||
This demo shows how to use a Graph Element to (optionally) display an image and then use the
|
This demo shows how to use a Graph Element to (optionally) display an image and then use the
|
||||||
mouse to "drag" and draw rectangles and circles.
|
mouse to "drag" and draw rectangles and circles.
|
||||||
|
|
||||||
Copyright 2020 PySimpleGUI.org
|
Copyright 2020, 2021, 2022, 2023 PySimpleGUI.org
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def save_element_as_file(element, filename):
|
|
||||||
"""
|
|
||||||
Saves any element as an image file. Element needs to have an underlyiong Widget available (almost if not all of them do)
|
|
||||||
:param element: The element to save
|
|
||||||
:param filename: The filename to save to. The extension of the filename determines the format (jpg, png, gif, ?)
|
|
||||||
"""
|
|
||||||
widget = element.Widget
|
|
||||||
box = (widget.winfo_rootx(), widget.winfo_rooty(), widget.winfo_rootx() + widget.winfo_width(), widget.winfo_rooty() + widget.winfo_height())
|
|
||||||
grab = ImageGrab.grab(bbox=box)
|
|
||||||
grab.save(filename)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
||||||
sg.theme('Dark Blue 3')
|
sg.theme('Dark Blue 3')
|
||||||
|
@ -38,7 +24,6 @@ def main():
|
||||||
[sg.R('Bring to front', 1, key='-FRONT-', enable_events=True)],
|
[sg.R('Bring to front', 1, key='-FRONT-', enable_events=True)],
|
||||||
[sg.R('Move Everything', 1, key='-MOVEALL-', enable_events=True)],
|
[sg.R('Move Everything', 1, key='-MOVEALL-', enable_events=True)],
|
||||||
[sg.R('Move Stuff', 1, key='-MOVE-', enable_events=True)],
|
[sg.R('Move Stuff', 1, key='-MOVE-', enable_events=True)],
|
||||||
[sg.B('Save Image', key='-SAVE-')],
|
|
||||||
]
|
]
|
||||||
|
|
||||||
layout = [[sg.Graph(
|
layout = [[sg.Graph(
|
||||||
|
@ -49,20 +34,19 @@ def main():
|
||||||
enable_events=True,
|
enable_events=True,
|
||||||
background_color='lightblue',
|
background_color='lightblue',
|
||||||
drag_submits=True,
|
drag_submits=True,
|
||||||
right_click_menu=[[],['Erase item',]]
|
motion_events=True,
|
||||||
|
right_click_menu=[[''],['Erase item','Send to back']]
|
||||||
), sg.Col(col, key='-COL-') ],
|
), sg.Col(col, key='-COL-') ],
|
||||||
[sg.Text(key='-INFO-', size=(60, 1))]]
|
[sg.Text(key='-INFO-', size=(60, 1))]]
|
||||||
|
|
||||||
window = sg.Window("Drawing and Moving Stuff Around", layout, finalize=True)
|
window = sg.Window("Drawing and Moving Stuff Around", layout, finalize=True)
|
||||||
|
|
||||||
# get the graph element for ease of use later
|
# get the graph element for ease of use later
|
||||||
graph = window["-GRAPH-"] # type: sg.Graph
|
graph = window["-GRAPH-"] # type: sg.Graph
|
||||||
graph.draw_image(data=logo200, location=(0,400))
|
graph.draw_image(data=logo200, location=(0,400))
|
||||||
|
|
||||||
dragging = False
|
dragging = False
|
||||||
start_point = end_point = prior_rect = None
|
start_point = end_point = prior_rect = None
|
||||||
# graph.bind('<Button-3>', '+RIGHT+')
|
crosshair_lines = []
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
event, values = window.read()
|
event, values = window.read()
|
||||||
print(event, values)
|
print(event, values)
|
||||||
|
@ -73,7 +57,14 @@ def main():
|
||||||
graph.set_cursor(cursor='fleur') # not yet released method... coming soon!
|
graph.set_cursor(cursor='fleur') # not yet released method... coming soon!
|
||||||
elif not event.startswith('-GRAPH-'):
|
elif not event.startswith('-GRAPH-'):
|
||||||
graph.set_cursor(cursor='left_ptr') # not yet released method... coming soon!
|
graph.set_cursor(cursor='left_ptr') # not yet released method... coming soon!
|
||||||
|
if event.endswith('+MOVE'):
|
||||||
|
window["-INFO-"].update(value=f"mouse {values['-GRAPH-']}")
|
||||||
|
# Delete crosshairs if any exists
|
||||||
|
if len(crosshair_lines):
|
||||||
|
for fig in crosshair_lines:
|
||||||
|
graph.delete_figure(fig)
|
||||||
|
crosshair_lines = []
|
||||||
|
window.refresh()
|
||||||
if event == "-GRAPH-": # if there's a "Graph" event, then it's a mouse
|
if event == "-GRAPH-": # if there's a "Graph" event, then it's a mouse
|
||||||
x, y = values["-GRAPH-"]
|
x, y = values["-GRAPH-"]
|
||||||
if not dragging:
|
if not dragging:
|
||||||
|
@ -114,25 +105,28 @@ def main():
|
||||||
for fig in drag_figures:
|
for fig in drag_figures:
|
||||||
graph.send_figure_to_back(fig)
|
graph.send_figure_to_back(fig)
|
||||||
window["-INFO-"].update(value=f"mouse {values['-GRAPH-']}")
|
window["-INFO-"].update(value=f"mouse {values['-GRAPH-']}")
|
||||||
elif event.endswith('+UP'): # The drawing has ended because mouse up
|
elif event.endswith('+UP'): # The drawing has ended because mouse up
|
||||||
window["-INFO-"].update(value=f"grabbed rectangle from {start_point} to {end_point}")
|
window["-INFO-"].update(value=f"grabbed rectangle from {start_point} to {end_point}")
|
||||||
start_point, end_point = None, None # enable grabbing a new rect
|
start_point, end_point = None, None # enable grabbing a new rect
|
||||||
dragging = False
|
dragging = False
|
||||||
prior_rect = None
|
prior_rect = None
|
||||||
elif event.endswith('+RIGHT+'): # Righ click
|
# elif event.endswith('+RIGHT+'): # Right click
|
||||||
window["-INFO-"].update(value=f"Right clicked location {values['-GRAPH-']}")
|
# window["-INFO-"].update(value=f"Right clicked location {values['-GRAPH-']}")
|
||||||
elif event.endswith('+MOTION+'): # Righ click
|
# elif event.endswith('+MOTION+'): # Right click
|
||||||
window["-INFO-"].update(value=f"mouse freely moving {values['-GRAPH-']}")
|
# window["-INFO-"].update(value=f"mouse freely moving {values['-GRAPH-']}")
|
||||||
elif event == '-SAVE-':
|
elif event == 'Send to back': # Right clicked menu item
|
||||||
# filename = sg.popup_get_file('Choose file (PNG, JPG, GIF) to save to', save_as=True)
|
figures = graph.get_figures_at_location(values["-GRAPH-"]) # get items in front-to-back order
|
||||||
filename=r'test.jpg'
|
if figures: # make sure at least 1 item found
|
||||||
save_element_as_file(window['-GRAPH-'], filename)
|
graph.send_figure_to_back(figures[-1]) # get the last item which will be the top-most
|
||||||
elif event == 'Erase item':
|
elif event == 'Erase item':
|
||||||
window["-INFO-"].update(value=f"Right click erase at {values['-GRAPH-']}")
|
window["-INFO-"].update(value=f"Right click erase at {values['-GRAPH-']}")
|
||||||
if values['-GRAPH-'] != (None, None):
|
if values['-GRAPH-'] != (None, None):
|
||||||
drag_figures = graph.get_figures_at_location(values['-GRAPH-'])
|
figures = graph.get_figures_at_location(values['-GRAPH-'])
|
||||||
for figure in drag_figures:
|
if figures:
|
||||||
graph.delete_figure(figure)
|
graph.delete_figure(figures[-1]) # delete the one on top
|
||||||
|
location = values['-GRAPH-']
|
||||||
|
crosshair_lines = [graph.draw_line((location[0], 0), (location[0], 800), color='red'),
|
||||||
|
graph.draw_line((0, location[1]), (800, location[1]), color='red')]
|
||||||
|
|
||||||
window.close()
|
window.close()
|
||||||
|
|
||||||
|
|
156
DemoPrograms/Demo_Graph_Elem_CPU_Meter.py
Normal file
|
@ -44,7 +44,7 @@ def convert_to_bytes(file_or_bytes, resize=None):
|
||||||
if resize:
|
if resize:
|
||||||
new_width, new_height = resize
|
new_width, new_height = resize
|
||||||
scale = min(new_height/cur_height, new_width/cur_width)
|
scale = min(new_height/cur_height, new_width/cur_width)
|
||||||
img = img.resize((int(cur_width*scale), int(cur_height*scale)), PIL.Image.ANTIALIAS)
|
img = img.resize((int(cur_width*scale), int(cur_height*scale)), PIL.Image.LANCZOS)
|
||||||
bio = io.BytesIO()
|
bio = io.BytesIO()
|
||||||
img.save(bio, format="PNG")
|
img.save(bio, format="PNG")
|
||||||
del img
|
del img
|
||||||
|
|
|
@ -1,11 +1,30 @@
|
||||||
import PySimpleGUI as sg
|
import PySimpleGUI as sg
|
||||||
import math
|
import math
|
||||||
|
|
||||||
# Yet another usage of Graph element.
|
"""
|
||||||
|
Demo - Graph Element used to plot a mathematical formula
|
||||||
|
|
||||||
|
The Graph element has a flexible coordinate system that you define.
|
||||||
|
Thie makes is possible for you to work in your coordinates instead of an
|
||||||
|
arbitrary system.
|
||||||
|
|
||||||
|
For example, in a typical mathematics graph, (0,0) is located at the center
|
||||||
|
of the graph / page / diagram.
|
||||||
|
This Demo Program shows a graph with (0,0) being at the center of the Graph
|
||||||
|
area rather than at one of the corners.
|
||||||
|
|
||||||
|
It graphs the formula:
|
||||||
|
y = sine(x/x2) * x1
|
||||||
|
|
||||||
|
The values of x1 and x2 can be changed using 2 sliders
|
||||||
|
|
||||||
|
Copyright 2018, 2019, 2020, 2021, 2022 PySimpleGUI
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
SIZE_X = 200
|
SIZE_X = 200
|
||||||
SIZE_Y = 100
|
SIZE_Y = 200
|
||||||
NUMBER_MARKER_FREQUENCY = 25
|
NUMBER_MARKER_FREQUENCY = SIZE_X//8 # How often to put tick marks on the axis
|
||||||
|
|
||||||
|
|
||||||
def draw_axis():
|
def draw_axis():
|
||||||
|
@ -13,44 +32,45 @@ def draw_axis():
|
||||||
graph.draw_line((0, -SIZE_Y), (0, SIZE_Y))
|
graph.draw_line((0, -SIZE_Y), (0, SIZE_Y))
|
||||||
|
|
||||||
for x in range(-SIZE_X, SIZE_X+1, NUMBER_MARKER_FREQUENCY):
|
for x in range(-SIZE_X, SIZE_X+1, NUMBER_MARKER_FREQUENCY):
|
||||||
graph.draw_line((x, -3), (x, 3)) # tick marks
|
graph.draw_line((x, -SIZE_Y/66), (x, SIZE_Y/66)) # tick marks
|
||||||
if x != 0:
|
if x != 0:
|
||||||
# numeric labels
|
# numeric labels
|
||||||
graph.draw_text(str(x), (x, -10), color='green')
|
graph.draw_text(str(x), (x, -SIZE_Y/15), color='green', font='courier 10')
|
||||||
|
|
||||||
for y in range(-SIZE_Y, SIZE_Y+1, NUMBER_MARKER_FREQUENCY):
|
for y in range(-SIZE_Y, SIZE_Y+1, NUMBER_MARKER_FREQUENCY):
|
||||||
graph.draw_line((-3, y), (3, y))
|
graph.draw_line((-SIZE_X/66, y), (SIZE_X/66, y))
|
||||||
if y != 0:
|
if y != 0:
|
||||||
graph.draw_text(str(y), (-10, y), color='blue')
|
graph.draw_text(str(y), (-SIZE_X/11, y), color='blue', font='courier 10')
|
||||||
|
|
||||||
sg.theme('DarkAmber')
|
# Create the graph that will be put into the window. Making outside of layout so have element in a variable
|
||||||
|
graph = sg.Graph(canvas_size=(500, 500),
|
||||||
# Create the graph that will be put into the window
|
|
||||||
graph = sg.Graph(canvas_size=(400, 400),
|
|
||||||
graph_bottom_left=(-(SIZE_X+5), -(SIZE_Y+5)),
|
graph_bottom_left=(-(SIZE_X+5), -(SIZE_Y+5)),
|
||||||
graph_top_right=(SIZE_X+5, SIZE_Y+5),
|
graph_top_right=(SIZE_X+5, SIZE_Y+5),
|
||||||
background_color='white',
|
background_color='white', expand_x=True, expand_y=True,
|
||||||
key='graph')
|
key='-GRAPH-')
|
||||||
# Window layout
|
# Window layout
|
||||||
layout = [[sg.Text('Example of Using Math with a Graph', justification='center', size=(50, 1), relief=sg.RELIEF_SUNKEN)],
|
layout = [[sg.Text('Graph Element Combined with Math!', justification='center', relief=sg.RELIEF_SUNKEN, expand_x=True, font='Courier 18')],
|
||||||
[graph],
|
[graph],
|
||||||
[sg.Text('y = sin(x / x2 * x1)', font='COURIER 18')],
|
[sg.Text('y = sin(x / x2) * x1', font='COURIER 18')],
|
||||||
[sg.Text('x1'), sg.Slider((0, 200), orientation='h',
|
[sg.Text('x1', font='Courier 14'), sg.Slider((0, SIZE_Y), orientation='h', enable_events=True, key='-SLIDER-', expand_x=True)],
|
||||||
enable_events=True, key='-SLIDER-')],
|
[sg.Text('x2', font='Courier 14'), sg.Slider((1, SIZE_Y), orientation='h', enable_events=True, key='-SLIDER2-', expand_x=True)]]
|
||||||
[sg.Text('x2'), sg.Slider((1, 200), orientation='h', enable_events=True, key='-SLIDER2-')]]
|
|
||||||
|
|
||||||
window = sg.Window('Graph of Sine Function', layout)
|
window = sg.Window('Graph of Sine Function', layout, finalize=True)
|
||||||
|
|
||||||
|
draw_axis() # draw the axis (an empty graph)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
event, values = window.read()
|
event, values = window.read()
|
||||||
if event == sg.WIN_CLOSED:
|
if event == sg.WIN_CLOSED:
|
||||||
break
|
break
|
||||||
graph.erase()
|
|
||||||
draw_axis()
|
graph.erase() # erase entire graph every time there's a change to a slider
|
||||||
prev_x = prev_y = None
|
draw_axis() # redraw the axis
|
||||||
|
|
||||||
|
# plot the function by drawing short line segments
|
||||||
|
prev_x = prev_y = None
|
||||||
for x in range(-SIZE_X, SIZE_X):
|
for x in range(-SIZE_X, SIZE_X):
|
||||||
y = math.sin(x/int(values['-SLIDER2-']))*int(values['-SLIDER-'])
|
y = math.sin(x/int(values['-SLIDER2-'])) * int(values['-SLIDER-'])
|
||||||
if prev_x is not None:
|
if prev_x is not None:
|
||||||
graph.draw_line((prev_x, prev_y), (x, y), color='red')
|
graph.draw_line((prev_x, prev_y), (x, y), color='red')
|
||||||
prev_x, prev_y = x, y
|
prev_x, prev_y = x, y
|
||||||
|
|
45
DemoPrograms/Demo_Graph_Window_Resize.py
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import PySimpleGUI as sg
|
||||||
|
|
||||||
|
"""
|
||||||
|
Demo - Graph Element Rescale Figures When Window Resizes
|
||||||
|
|
||||||
|
This demo shows how you can redraw your Graph element's figures so that when
|
||||||
|
you resize the window, all of the figures on the graph resize.
|
||||||
|
|
||||||
|
There may be a tkinter method to help do this?
|
||||||
|
|
||||||
|
Copyright 2022 PySimpleGUI
|
||||||
|
"""
|
||||||
|
|
||||||
|
gsize = (400,400)
|
||||||
|
|
||||||
|
layout = [ [sg.Text('Rescaling a Graph Element When Window is Resized')],
|
||||||
|
[sg.Graph(gsize, (0,0),gsize, expand_x=True, expand_y=True, k='-G-', background_color='green')],
|
||||||
|
[sg.Button('Exit'), sg.Sizegrip()] ]
|
||||||
|
|
||||||
|
window = sg.Window('Graph Element Scale With Window', layout, finalize=True, resizable=True, enable_window_config_events=True)
|
||||||
|
|
||||||
|
graph = window['-G-'] #type: sg.Graph
|
||||||
|
|
||||||
|
orig_win_size = window.current_size_accurate()
|
||||||
|
# Draw the figure desired (will repeat this code later)
|
||||||
|
fig = window['-G-'].draw_circle((200, 200), 50, fill_color='blue')
|
||||||
|
|
||||||
|
while True:
|
||||||
|
event, values = window.read()
|
||||||
|
if event == sg.WIN_CLOSED or event == 'Exit':
|
||||||
|
break
|
||||||
|
if event == sg.WINDOW_CONFIG_EVENT: # if get a window resized event
|
||||||
|
# Determine how much the window was resized by and tell the Graph element the new size for the Canvas
|
||||||
|
new_size = window.current_size_accurate()
|
||||||
|
dx = orig_win_size[0]-new_size[0]
|
||||||
|
dy = orig_win_size[1]-new_size[1]
|
||||||
|
gsize = (gsize[0] - dx, gsize[1] - dy)
|
||||||
|
orig_win_size = new_size
|
||||||
|
graph.CanvasSize = gsize
|
||||||
|
# Erase entire Graph and redraw all figures0
|
||||||
|
graph.erase()
|
||||||
|
# Redraw your figures here
|
||||||
|
fig = window['-G-'].draw_circle((200, 200), 50, fill_color='blue')
|
||||||
|
|
||||||
|
window.close()
|
|
@ -45,7 +45,7 @@ def convert_to_bytes(file_or_bytes, resize=None):
|
||||||
if resize:
|
if resize:
|
||||||
new_width, new_height = resize
|
new_width, new_height = resize
|
||||||
scale = min(new_height/cur_height, new_width/cur_width)
|
scale = min(new_height/cur_height, new_width/cur_width)
|
||||||
img = img.resize((int(cur_width*scale), int(cur_height*scale)), PIL.Image.ANTIALIAS)
|
img = img.resize((int(cur_width*scale), int(cur_height*scale)), PIL.Image.LANCZOS)
|
||||||
with io.BytesIO() as bio:
|
with io.BytesIO() as bio:
|
||||||
img.save(bio, format="PNG")
|
img.save(bio, format="PNG")
|
||||||
del img
|
del img
|
||||||
|
|
32
DemoPrograms/Demo_Image_From_URL.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import PySimpleGUI as sg
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
"""
|
||||||
|
Display an Image Located at a URL
|
||||||
|
|
||||||
|
Downloads and displays a PNG (or GIF) image given a URL
|
||||||
|
|
||||||
|
NOTE:
|
||||||
|
Early versions of tkinter (for example 8.6.6 found in Python 3.6) have trouble with some PNG formats.
|
||||||
|
Moving to Python 3.7 fixes this or you can use a tool to re-encode the image (e.g. psgresizer) save it and
|
||||||
|
it will then work OK in Python 3.6.
|
||||||
|
Example of one of these images - https://www.python.org/static/community_logos/python-logo-master-v3-TM.png
|
||||||
|
|
||||||
|
Copyright 2022 PySimpleGUI.org
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
image_URL = r'https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png'
|
||||||
|
|
||||||
|
layout = [[sg.Image(urllib.request.urlopen(image_URL).read())]]
|
||||||
|
|
||||||
|
window = sg.Window('Image From URL', layout)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
event, values = window.read()
|
||||||
|
if event == sg.WIN_CLOSED or event == 'Exit':
|
||||||
|
break
|
||||||
|
|
||||||
|
window.close()
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import PySimpleGUI as sg
|
import PySimpleGUI as sg
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import PIL
|
|
||||||
import os
|
import os
|
||||||
import base64
|
import base64
|
||||||
import io
|
import io
|
||||||
|
@ -25,12 +24,19 @@ import webbrowser
|
||||||
Copyright 2021 PySimpleGUI
|
Copyright 2021 PySimpleGUI
|
||||||
"""
|
"""
|
||||||
|
|
||||||
version = '1.3.1'
|
version = '1.6.0'
|
||||||
__version__ = version.split()[0]
|
__version__ = version.split()[0]
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Change log
|
Change log
|
||||||
|
1.6.0 12-July-2022
|
||||||
|
Fixed output filename to match the size indicated under the filename.
|
||||||
|
1.5.4 10-May-2022
|
||||||
|
Had to mess around with the entry point due to setuptools
|
||||||
|
1.5.0 10-May-2022
|
||||||
|
Moved icon to bottom of file and called set_global_icon so all windows in this application (including popups) will use this icon
|
||||||
|
1.4.0 16-Nov-2021
|
||||||
|
Explicitly set the settings filename. I'm still learning about these PyPI .EXE releases. Need to be explicit rather than default
|
||||||
1.3.1 16-Nov-2021
|
1.3.1 16-Nov-2021
|
||||||
Added correct readme to PyPI
|
Added correct readme to PyPI
|
||||||
1.3.0 16-Nov-2021
|
1.3.0 16-Nov-2021
|
||||||
|
@ -49,7 +55,7 @@ def resize(input_file, size, output_file=None, encode_format='PNG'):
|
||||||
new_width, new_height = size
|
new_width, new_height = size
|
||||||
if new_width != width or new_height != height: # if the requested size is different than original size
|
if new_width != width or new_height != height: # if the requested size is different than original size
|
||||||
scale = min(new_height / height, new_width / width)
|
scale = min(new_height / height, new_width / width)
|
||||||
resized_image = image.resize((int(width * scale), int(height * scale)), Image.ANTIALIAS)
|
resized_image = image.resize((int(width * scale), int(height * scale)), Image.LANCZOS)
|
||||||
else:
|
else:
|
||||||
resized_image = image
|
resized_image = image
|
||||||
|
|
||||||
|
@ -65,7 +71,6 @@ def resize(input_file, size, output_file=None, encode_format='PNG'):
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
image_resize_icon = b'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'
|
|
||||||
|
|
||||||
def update_outfilename():
|
def update_outfilename():
|
||||||
infile = values['-IN-']
|
infile = values['-IN-']
|
||||||
|
@ -76,8 +81,12 @@ def main():
|
||||||
window['-ORIG WIDTH-'].update(image.size[0])
|
window['-ORIG WIDTH-'].update(image.size[0])
|
||||||
if not values['-WIDTH-']:
|
if not values['-WIDTH-']:
|
||||||
window['-WIDTH-'].update(image.size[0])
|
window['-WIDTH-'].update(image.size[0])
|
||||||
|
else:
|
||||||
|
width = values['-WIDTH-']
|
||||||
if not values['-HEIGHT-']:
|
if not values['-HEIGHT-']:
|
||||||
window['-HEIGHT-'].update(image.size[1])
|
window['-HEIGHT-'].update(image.size[1])
|
||||||
|
else:
|
||||||
|
height = values['-HEIGHT-']
|
||||||
window['-ORIG HEIGHT-'].update(image.size[1])
|
window['-ORIG HEIGHT-'].update(image.size[1])
|
||||||
|
|
||||||
infilename = os.path.basename(infile)
|
infilename = os.path.basename(infile)
|
||||||
|
@ -88,7 +97,7 @@ def main():
|
||||||
outfileext = 'jpg'
|
outfileext = 'jpg'
|
||||||
else:
|
else:
|
||||||
outfileext = infileext[1:] # strip off the .
|
outfileext = infileext[1:] # strip off the .
|
||||||
outfile = f'{infilenameonly}{width}x{height}.{outfileext}'
|
outfile = f'{infilenameonly}_{width}x{height}.{outfileext}'
|
||||||
outfullfilename = os.path.join(os.path.dirname(infile), outfile)
|
outfullfilename = os.path.join(os.path.dirname(infile), outfile)
|
||||||
|
|
||||||
if values['-DO NOT SAVE-']:
|
if values['-DO NOT SAVE-']:
|
||||||
|
@ -104,6 +113,9 @@ def main():
|
||||||
# window['-HEIGHT-'].update('')
|
# window['-HEIGHT-'].update('')
|
||||||
window['-NEW FILENAME-'].update()
|
window['-NEW FILENAME-'].update()
|
||||||
|
|
||||||
|
sg.user_settings_filename(filename='psgresizer.json')
|
||||||
|
|
||||||
|
|
||||||
format_list = ('', 'PNG', 'JPEG', 'BMP', 'ICO', 'GIF', 'TIFF')
|
format_list = ('', 'PNG', 'JPEG', 'BMP', 'ICO', 'GIF', 'TIFF')
|
||||||
new_format_layout = [
|
new_format_layout = [
|
||||||
[sg.Combo(format_list, default_value=sg.user_settings_get_entry('-new format-', ''), readonly=True, enable_events=True, key='-NEW FORMAT-')]]
|
[sg.Combo(format_list, default_value=sg.user_settings_get_entry('-new format-', ''), readonly=True, enable_events=True, key='-NEW FORMAT-')]]
|
||||||
|
@ -112,8 +124,8 @@ def main():
|
||||||
[sg.Frame('Input Filename', [[sg.Input(key='-IN-', enable_events=True, s=80), sg.FileBrowse(), ],
|
[sg.Frame('Input Filename', [[sg.Input(key='-IN-', enable_events=True, s=80), sg.FileBrowse(), ],
|
||||||
[sg.T('Original size'), sg.T(k='-ORIG WIDTH-'), sg.T('X'), sg.T(k='-ORIG HEIGHT-')]])],
|
[sg.T('Original size'), sg.T(k='-ORIG WIDTH-'), sg.T('X'), sg.T(k='-ORIG HEIGHT-')]])],
|
||||||
[sg.Frame('Output Filename', [[sg.In(k='-NEW FILENAME-', s=80), sg.FileBrowse(), ],
|
[sg.Frame('Output Filename', [[sg.In(k='-NEW FILENAME-', s=80), sg.FileBrowse(), ],
|
||||||
[sg.In(default_text=sg.user_settings_get_entry('-width-', ''), s=4, k='-WIDTH-'), sg.T('X'),
|
[sg.In(default_text=sg.user_settings_get_entry('-width-', ''), s=4, k='-WIDTH-', enable_events=True), sg.T('X'),
|
||||||
sg.In(default_text=sg.user_settings_get_entry('-height-', ''), s=4, k='-HEIGHT-')]])],
|
sg.In(default_text=sg.user_settings_get_entry('-height-', ''), s=4, k='-HEIGHT-', enable_events=True)]])],
|
||||||
[sg.Frame('Convert To New Format', new_format_layout)],
|
[sg.Frame('Convert To New Format', new_format_layout)],
|
||||||
[sg.CBox('Encode to Base64 and leave on Clipboard', k='-BASE64-', default=sg.user_settings_get_entry('-base64-', True))],
|
[sg.CBox('Encode to Base64 and leave on Clipboard', k='-BASE64-', default=sg.user_settings_get_entry('-base64-', True))],
|
||||||
# [sg.CBox('Use PNG for all Base64 Encoding', default=True, k='-PNG CONVERT-')],
|
# [sg.CBox('Use PNG for all Base64 Encoding', default=True, k='-PNG CONVERT-')],
|
||||||
|
@ -128,7 +140,7 @@ def main():
|
||||||
sg.T('A PySimpleGUI Application - Go to PySimpleGUI home', font='_ 8', enable_events=True, k='-PYSIMPLEGUI-')],
|
sg.T('A PySimpleGUI Application - Go to PySimpleGUI home', font='_ 8', enable_events=True, k='-PYSIMPLEGUI-')],
|
||||||
]
|
]
|
||||||
|
|
||||||
window = sg.Window('Resize Image', layout, icon=image_resize_icon, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_LOC_EXIT,
|
window = sg.Window('Resize Image', layout, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_LOC_EXIT,
|
||||||
enable_close_attempted_event=True, finalize=True)
|
enable_close_attempted_event=True, finalize=True)
|
||||||
window['-PSGRESIZER-'].set_cursor('hand1')
|
window['-PSGRESIZER-'].set_cursor('hand1')
|
||||||
window['-PYSIMPLEGUI-'].set_cursor('hand1')
|
window['-PYSIMPLEGUI-'].set_cursor('hand1')
|
||||||
|
@ -182,6 +194,8 @@ def main():
|
||||||
webbrowser.open_new_tab(r'http://www.PySimpleGUI.com')
|
webbrowser.open_new_tab(r'http://www.PySimpleGUI.com')
|
||||||
elif event == '-PSGRESIZER-':
|
elif event == '-PSGRESIZER-':
|
||||||
webbrowser.open_new_tab(r'https://github.com/PySimpleGUI/psgresizer')
|
webbrowser.open_new_tab(r'https://github.com/PySimpleGUI/psgresizer')
|
||||||
|
elif event in ('-WIDTH-', '-HEIGHT-'):
|
||||||
|
update_outfilename()
|
||||||
|
|
||||||
if event != sg.WIN_CLOSED:
|
if event != sg.WIN_CLOSED:
|
||||||
sg.user_settings_set_entry('-autoclose-', values['-AUTOCLOSE-'])
|
sg.user_settings_set_entry('-autoclose-', values['-AUTOCLOSE-'])
|
||||||
|
@ -193,5 +207,11 @@ def main():
|
||||||
window.close()
|
window.close()
|
||||||
|
|
||||||
|
|
||||||
|
def main_entry_point():
|
||||||
|
image_resize_icon = b'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'
|
||||||
|
|
||||||
|
sg.set_global_icon(image_resize_icon)
|
||||||
|
main()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main_entry_point()
|
||||||
|
|
|
@ -56,7 +56,7 @@ def convert_to_bytes(file_or_bytes, resize=None, fill=False):
|
||||||
if resize:
|
if resize:
|
||||||
new_width, new_height = resize
|
new_width, new_height = resize
|
||||||
scale = min(new_height / cur_height, new_width / cur_width)
|
scale = min(new_height / cur_height, new_width / cur_width)
|
||||||
img = img.resize((int(cur_width * scale), int(cur_height * scale)), PIL.Image.ANTIALIAS)
|
img = img.resize((int(cur_width * scale), int(cur_height * scale)), PIL.Image.LANCZOS)
|
||||||
if fill:
|
if fill:
|
||||||
img = make_square(img, THUMBNAIL_SIZE[0])
|
img = make_square(img, THUMBNAIL_SIZE[0])
|
||||||
with io.BytesIO() as bio:
|
with io.BytesIO() as bio:
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import PySimpleGUI as sg
|
||||||
|
|
||||||
|
"""
|
||||||
|
Demo - Save previously entered value in Input element by using user_settings calls
|
||||||
|
|
||||||
|
Tired of typing in the same value or entering the same filename into an Input element?
|
||||||
|
If so, this may be exactly what you need.
|
||||||
|
|
||||||
|
It simply saves the last value you entered so that the next time you start your program, that will be the default
|
||||||
|
|
||||||
|
Copyright 2022 PySimpleGUI.org
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
sg.user_settings_filename(path='.') # The settings file will be in the same folder as this program
|
||||||
|
|
||||||
|
layout = [[sg.T('This is your layout')],
|
||||||
|
[sg.T('Remembers last value for this:'), sg.In(sg.user_settings_get_entry('-input-', ''), k='-INPUT-')],
|
||||||
|
[sg.OK(), sg.Button('Exit')]]
|
||||||
|
|
||||||
|
# make a window, read it, and automatically close after 1 event happens (button or X to close window)
|
||||||
|
event, values = sg.Window('Save Input Element Last Value', layout).read(close=True)
|
||||||
|
|
||||||
|
# only save the value if OK was clicked
|
||||||
|
if event == 'OK':
|
||||||
|
sg.user_settings_set_entry('-input-', values['-INPUT-'])
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
|
@ -19,13 +19,12 @@ import PySimpleGUI as sg
|
||||||
For other ports of PySimpleGUI such as the Qt port, the position is remembered by Qt and as a
|
For other ports of PySimpleGUI such as the Qt port, the position is remembered by Qt and as a
|
||||||
result this technique using "pin" is not needed.
|
result this technique using "pin" is not needed.
|
||||||
|
|
||||||
Copyright 2020 PySimpleGUI.org
|
Copyright 2020, 2022 PySimpleGUI.org
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
layout = [ [sg.Text('Hide Button or Multiline. Buttons 1 & 2 hide Button 2')],
|
||||||
layout = [ [sg.Text('Hide Button or Input. Button3 hides Input. Buttons 1 & 2 hide Button 2')],
|
[sg.pin(sg.Multiline(size=(60, 10), key='-MLINE-'))],
|
||||||
[sg.pin(sg.Input(key='-IN-'))],
|
[sg.pin(sg.Button('Button1')), sg.pin(sg.Button('Button2'), shrink=False), sg.B('Toggle Multiline')],
|
||||||
[sg.pin(sg.Button('Button1')), sg.pin(sg.Button('Button2')), sg.B('Button3')],
|
|
||||||
]
|
]
|
||||||
|
|
||||||
window = sg.Window('Visible / Invisible Element Demo', layout)
|
window = sg.Window('Visible / Invisible Element Demo', layout)
|
||||||
|
@ -40,7 +39,6 @@ while True: # Event Loop
|
||||||
if event in ('Button1', 'Button2'):
|
if event in ('Button1', 'Button2'):
|
||||||
window['Button2'].update(visible=toggle)
|
window['Button2'].update(visible=toggle)
|
||||||
toggle = not toggle
|
toggle = not toggle
|
||||||
if event == 'Button3':
|
elif event == 'Toggle Multiline':
|
||||||
window['-IN-'].update(visible=toggle_in)
|
window['-MLINE-'].update(visible=not window['-MLINE-'].visible)
|
||||||
toggle_in = not toggle_in
|
|
||||||
window.close()
|
window.close()
|
||||||
|
|
65
DemoPrograms/Demo_Layout_Add_and_Delete_Rows.py
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import PySimpleGUI as sg
|
||||||
|
|
||||||
|
"""
|
||||||
|
Demo - Add and "Delete" Rows from a window
|
||||||
|
|
||||||
|
This is cut-down version of the Fed-Ex package tracking demo
|
||||||
|
|
||||||
|
The purpose is to show a technique for making windows that grow by clicking an "Add Row" button
|
||||||
|
Each row can be individually "deleted".
|
||||||
|
|
||||||
|
The reason for using the quotes are "deleted" is that the elements are simply hidden. The effect is the same as deleting them.
|
||||||
|
|
||||||
|
Copyright 2022 PySimpleGUI
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def item_row(item_num):
|
||||||
|
"""
|
||||||
|
A "Row" in this case is a Button with an "X", an Input element and a Text element showing the current counter
|
||||||
|
:param item_num: The number to use in the tuple for each element
|
||||||
|
:type: int
|
||||||
|
:return: List
|
||||||
|
"""
|
||||||
|
row = [sg.pin(sg.Col([[sg.B(sg.SYMBOL_X, border_width=0, button_color=(sg.theme_text_color(), sg.theme_background_color()), k=('-DEL-', item_num), tooltip='Delete this item'),
|
||||||
|
sg.In(size=(20,1), k=('-DESC-', item_num)),
|
||||||
|
sg.T(f'Key number {item_num}', k=('-STATUS-', item_num))]], k=('-ROW-', item_num)))]
|
||||||
|
return row
|
||||||
|
|
||||||
|
|
||||||
|
def make_window():
|
||||||
|
|
||||||
|
layout = [ [sg.Text('Add and "Delete" Rows From a Window', font='_ 15')],
|
||||||
|
[sg.Col([item_row(0)], k='-TRACKING SECTION-')],
|
||||||
|
[sg.pin(sg.Text(size=(35,1), font='_ 8', k='-REFRESHED-',))],
|
||||||
|
[sg.T(sg.SYMBOL_X, enable_events=True, k='Exit', tooltip='Exit Application'), sg.T('↻', enable_events=True, k='Refresh', tooltip='Save Changes & Refresh'), sg.T('+', enable_events=True, k='Add Item', tooltip='Add Another Item')]]
|
||||||
|
|
||||||
|
right_click_menu = [[''], ['Add Item', 'Edit Me', 'Version']]
|
||||||
|
|
||||||
|
window = sg.Window('Window Title', layout, right_click_menu=right_click_menu, use_default_focus=False, font='_ 15', metadata=0)
|
||||||
|
|
||||||
|
return window
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
window = make_window()
|
||||||
|
while True:
|
||||||
|
event, values = window.read() # wake every hour
|
||||||
|
print(event, values)
|
||||||
|
if event == sg.WIN_CLOSED or event == 'Exit':
|
||||||
|
break
|
||||||
|
if event == 'Add Item':
|
||||||
|
window.metadata += 1
|
||||||
|
window.extend_layout(window['-TRACKING SECTION-'], [item_row(window.metadata)])
|
||||||
|
elif event == 'Edit Me':
|
||||||
|
sg.execute_editor(__file__)
|
||||||
|
elif event == 'Version':
|
||||||
|
sg.popup_scrolled(__file__, sg.get_versions(), location=window.current_location(), keep_on_top=True, non_blocking=True)
|
||||||
|
elif event[0] == '-DEL-':
|
||||||
|
window[('-ROW-', event[1])].update(visible=False)
|
||||||
|
window.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
43
DemoPrograms/Demo_Listbox_Using_Objects.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import PySimpleGUI as sg
|
||||||
|
|
||||||
|
"""
|
||||||
|
Demo - Listbox Using Objects
|
||||||
|
|
||||||
|
Several elements can take not just strings, but objects. The Listsbox is one of them.
|
||||||
|
This demo show how you can use objects directly in a Listbox in a way that you can access
|
||||||
|
information about each object that is different than what is shown in the Window.
|
||||||
|
|
||||||
|
The important part of this design pattern is the use of the __str__ method in your item objects.
|
||||||
|
This method is what determines what is shown in the window.
|
||||||
|
|
||||||
|
Copyright 2022 PySimpleGUI
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Item():
|
||||||
|
def __init__(self, internal, shown):
|
||||||
|
self.internal = internal
|
||||||
|
self.shown = shown
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.shown
|
||||||
|
|
||||||
|
# make list of some objects
|
||||||
|
my_item_list = [Item(f'Internal {i}', f'shown {i}') for i in range(100)]
|
||||||
|
|
||||||
|
layout = [ [sg.Text('Select 1 or more items and click "Go"')],
|
||||||
|
[sg.Listbox(my_item_list, key='-LB-', s=(20,20), select_mode=sg.LISTBOX_SELECT_MODE_EXTENDED)],
|
||||||
|
[sg.Output(s=(40,10))],
|
||||||
|
[sg.Button('Go'), sg.Button('Exit')] ]
|
||||||
|
|
||||||
|
window = sg.Window('Listbox Using Objects', layout)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
event, values = window.read()
|
||||||
|
# print(event, values)
|
||||||
|
if event == sg.WIN_CLOSED or event == 'Exit':
|
||||||
|
break
|
||||||
|
elif event == 'Go':
|
||||||
|
print('You selected:')
|
||||||
|
for item in values['-LB-']:
|
||||||
|
print(item.internal)
|
||||||
|
window.close()
|
|
@ -824,15 +824,26 @@ def AxesGrid():
|
||||||
|
|
||||||
# The magic function that makes it possible.... glues together tkinter and pyplot using Canvas Widget
|
# The magic function that makes it possible.... glues together tkinter and pyplot using Canvas Widget
|
||||||
def draw_figure(canvas, figure):
|
def draw_figure(canvas, figure):
|
||||||
|
if not hasattr(draw_figure, 'canvas_packed'):
|
||||||
|
draw_figure.canvas_packed = {}
|
||||||
figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
|
figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
|
||||||
figure_canvas_agg.draw()
|
figure_canvas_agg.draw()
|
||||||
figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
|
widget = figure_canvas_agg.get_tk_widget()
|
||||||
|
if widget not in draw_figure.canvas_packed:
|
||||||
|
draw_figure.canvas_packed[widget] = figure
|
||||||
|
widget.pack(side='top', fill='both', expand=1)
|
||||||
return figure_canvas_agg
|
return figure_canvas_agg
|
||||||
|
|
||||||
|
|
||||||
def delete_figure_agg(figure_agg):
|
def delete_figure_agg(figure_agg):
|
||||||
figure_agg.get_tk_widget().forget()
|
figure_agg.get_tk_widget().forget()
|
||||||
|
try:
|
||||||
|
draw_figure.canvas_packed.pop(figure_agg.get_tk_widget())
|
||||||
|
except Exception as e:
|
||||||
|
print(f'Error removing {figure_agg} from list', e)
|
||||||
plt.close('all')
|
plt.close('all')
|
||||||
|
|
||||||
|
|
||||||
# -------------------------------- GUI Starts Here -------------------------------#
|
# -------------------------------- GUI Starts Here -------------------------------#
|
||||||
# fig = your figure you want to display. Assumption is that 'fig' holds the #
|
# fig = your figure you want to display. Assumption is that 'fig' holds the #
|
||||||
# information to display. #
|
# information to display. #
|
||||||
|
@ -873,6 +884,9 @@ while True:
|
||||||
choice = values['-LISTBOX-'][0] # get first listbox item chosen (returned as a list)
|
choice = values['-LISTBOX-'][0] # get first listbox item chosen (returned as a list)
|
||||||
func = fig_dict[choice] # get function to call from the dictionary
|
func = fig_dict[choice] # get function to call from the dictionary
|
||||||
window['-MULTILINE-'].update(inspect.getsource(func)) # show source code to function in multiline
|
window['-MULTILINE-'].update(inspect.getsource(func)) # show source code to function in multiline
|
||||||
fig = func() # call function to get the figure
|
try:
|
||||||
figure_agg = draw_figure(window['-CANVAS-'].TKCanvas, fig) # draw the figure
|
fig = func() # call function to get the figure
|
||||||
|
figure_agg = draw_figure(window['-CANVAS-'].TKCanvas, fig) # draw the figure
|
||||||
|
except Exception as e:
|
||||||
|
print('Exception in fucntion', e)
|
||||||
window.close()
|
window.close()
|
|
@ -837,14 +837,23 @@ def AxesGrid():
|
||||||
|
|
||||||
|
|
||||||
def draw_figure(canvas, figure):
|
def draw_figure(canvas, figure):
|
||||||
|
if not hasattr(draw_figure, 'canvas_packed'):
|
||||||
|
draw_figure.canvas_packed = {}
|
||||||
figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
|
figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
|
||||||
figure_canvas_agg.draw()
|
figure_canvas_agg.draw()
|
||||||
figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
|
widget = figure_canvas_agg.get_tk_widget()
|
||||||
|
if widget not in draw_figure.canvas_packed:
|
||||||
|
draw_figure.canvas_packed[widget] = figure
|
||||||
|
widget.pack(side='top', fill='both', expand=1)
|
||||||
return figure_canvas_agg
|
return figure_canvas_agg
|
||||||
|
|
||||||
|
|
||||||
def delete_figure_agg(figure_agg):
|
def delete_figure_agg(figure_agg):
|
||||||
figure_agg.get_tk_widget().forget()
|
figure_agg.get_tk_widget().forget()
|
||||||
|
try:
|
||||||
|
draw_figure.canvas_packed.pop(figure_agg.get_tk_widget())
|
||||||
|
except Exception as e:
|
||||||
|
print(f'Error removing {figure_agg} from list', e)
|
||||||
plt.close('all')
|
plt.close('all')
|
||||||
|
|
||||||
|
|
||||||
|
@ -881,13 +890,11 @@ layout = [[sg.Text('Matplotlib Plot Test', font=('ANY 18'))],
|
||||||
[sg.Col(col_listbox), col_instructions], ]
|
[sg.Col(col_listbox), col_instructions], ]
|
||||||
|
|
||||||
# create the form and show it without the plot
|
# create the form and show it without the plot
|
||||||
window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI',
|
window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI', layout, resizable=True, finalize=True)
|
||||||
layout, resizable=True, finalize=True)
|
|
||||||
|
|
||||||
canvas_elem = window['-CANVAS-']
|
canvas_elem = window['-CANVAS-']
|
||||||
multiline_elem = window['-MULTILINE-']
|
multiline_elem = window['-MULTILINE-']
|
||||||
figure_agg = None
|
figure_agg = None
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
event, values = window.read()
|
event, values = window.read()
|
||||||
if event in (sg.WIN_CLOSED, 'Exit'):
|
if event in (sg.WIN_CLOSED, 'Exit'):
|
||||||
|
@ -902,6 +909,8 @@ while True:
|
||||||
func = fig_dict[choice]
|
func = fig_dict[choice]
|
||||||
# show source code to function in multiline
|
# show source code to function in multiline
|
||||||
window['-MULTILINE-'].update(inspect.getsource(func))
|
window['-MULTILINE-'].update(inspect.getsource(func))
|
||||||
fig = func() # call function to get the figure
|
try:
|
||||||
figure_agg = draw_figure(
|
fig = func() # call function to get the figure
|
||||||
window['-CANVAS-'].TKCanvas, fig) # draw the figure
|
figure_agg = draw_figure(window['-CANVAS-'].TKCanvas, fig) # draw the figure
|
||||||
|
except Exception as e:
|
||||||
|
print('Error in plotting', e)
|
||||||
|
|
|
@ -890,7 +890,7 @@ def convert_to_bytes(file_or_bytes, resize=None):
|
||||||
if resize:
|
if resize:
|
||||||
new_width, new_height = resize
|
new_width, new_height = resize
|
||||||
scale = min(new_height/cur_height, new_width/cur_width)
|
scale = min(new_height/cur_height, new_width/cur_width)
|
||||||
img = img.resize((int(cur_width*scale), int(cur_height*scale)), PIL.Image.ANTIALIAS)
|
img = img.resize((int(cur_width*scale), int(cur_height*scale)), PIL.Image.LANCZOS)
|
||||||
with io.BytesIO() as bio:
|
with io.BytesIO() as bio:
|
||||||
img.save(bio, format="PNG")
|
img.save(bio, format="PNG")
|
||||||
del img
|
del img
|
||||||
|
|
|
@ -39,7 +39,7 @@ def main():
|
||||||
# sg.theme('black')
|
# sg.theme('black')
|
||||||
|
|
||||||
menu_def = [['&File', ['&Open Ctrl-O', '&Save Ctrl-S', '&Properties', 'E&xit']],
|
menu_def = [['&File', ['&Open Ctrl-O', '&Save Ctrl-S', '&Properties', 'E&xit']],
|
||||||
['&Edit', ['Me', 'Special', 'Normal',['Normal1', 'Normal2'] , 'Undo']],
|
['&Edit', ['Edit Me', 'Special', 'Normal',['Normal1', 'Normal2'] , 'Undo']],
|
||||||
['!Disabled', ['Special', 'Normal',['Normal1', 'Normal2'], 'Undo']],
|
['!Disabled', ['Special', 'Normal',['Normal1', 'Normal2'], 'Undo']],
|
||||||
['&Toolbar', ['---', 'Command &1::Command_Key', 'Command &2', '---', 'Command &3', 'Command &4']],
|
['&Toolbar', ['---', 'Command &1::Command_Key', 'Command &2', '---', 'Command &3', 'Command &4']],
|
||||||
['&Help', ['&About...']], ]
|
['&Help', ['&About...']], ]
|
||||||
|
@ -47,7 +47,7 @@ def main():
|
||||||
layout = [[sg.MenubarCustom(menu_def, pad=(0,0), k='-CUST MENUBAR-')],
|
layout = [[sg.MenubarCustom(menu_def, pad=(0,0), k='-CUST MENUBAR-')],
|
||||||
[sg.Multiline(size=(70, 20), reroute_cprint=True, write_only=True, no_scrollbar=True, k='-MLINE-')]]
|
[sg.Multiline(size=(70, 20), reroute_cprint=True, write_only=True, no_scrollbar=True, k='-MLINE-')]]
|
||||||
|
|
||||||
window = sg.Window("Custom Titlebar with Custom (Simulated) Menubar", layout, use_custom_titlebar=True, keep_on_top=True)
|
window = sg.Window("Custom Titlebar with Custom (Simulated) Menubar", layout, use_custom_titlebar=True, keep_on_top=True, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_EXIT)
|
||||||
|
|
||||||
# ------ Event Loop ------ #
|
# ------ Event Loop ------ #
|
||||||
while True:
|
while True:
|
||||||
|
@ -66,8 +66,10 @@ def main():
|
||||||
sg.popup('About this program', 'Simulated Menubar to accompany a simulated Titlebar',
|
sg.popup('About this program', 'Simulated Menubar to accompany a simulated Titlebar',
|
||||||
'PySimpleGUI Version', sg.get_versions(), grab_anywhere=True, keep_on_top=True)
|
'PySimpleGUI Version', sg.get_versions(), grab_anywhere=True, keep_on_top=True)
|
||||||
window.reappear()
|
window.reappear()
|
||||||
elif event == 'Me':
|
elif event == 'Edit Me':
|
||||||
sg.execute_editor(__file__)
|
sg.execute_editor(__file__)
|
||||||
|
elif event == 'Version':
|
||||||
|
sg.popup_scrolled(__file__, sg.get_versions(), keep_on_top=True, non_blocking=True)
|
||||||
elif event.startswith('Open'):
|
elif event.startswith('Open'):
|
||||||
filename = sg.popup_get_file('file to open', no_window=True)
|
filename = sg.popup_get_file('file to open', no_window=True)
|
||||||
print(filename)
|
print(filename)
|
||||||
|
|
|
@ -51,10 +51,8 @@ def Menubar(menu_def, text_color, background_color, pad=(0, 0)):
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
sg.theme('dark green 7')
|
sg.theme('dark green 7')
|
||||||
sg.theme('dark amber')
|
|
||||||
# sg.theme('dark purple 3')
|
|
||||||
|
|
||||||
menu_def = [['&File', ['&Open Ctrl-O', '&Save Ctrl-S', '&Properties', 'E&xit']],
|
menu_def = [['&File', ['&Open & Ctrl-O', '&Save & Ctrl-S', '&Properties', 'E&xit']],
|
||||||
['&Edit', [['Special', 'Normal',['Normal1', 'Normal2'] ], 'Undo'], ],
|
['&Edit', [['Special', 'Normal',['Normal1', 'Normal2'] ], 'Undo'], ],
|
||||||
['!Disabled', [['Special', 'Normal',['Normal1', 'Normal2'] ], 'Undo'], ],
|
['!Disabled', [['Special', 'Normal',['Normal1', 'Normal2'] ], 'Undo'], ],
|
||||||
['&Toolbar', ['---', 'Command &1::Command_Key', 'Command &2', '---', 'Command &3', 'Command &4']],
|
['&Toolbar', ['---', 'Command &1::Command_Key', 'Command &2', '---', 'Command &3', 'Command &4']],
|
||||||
|
@ -78,13 +76,13 @@ def main():
|
||||||
|
|
||||||
layout3 = [[sg.Multiline(size=(70, 20), reroute_stdout=True, reroute_cprint=True, write_only=True)],]
|
layout3 = [[sg.Multiline(size=(70, 20), reroute_stdout=True, reroute_cprint=True, write_only=True)],]
|
||||||
|
|
||||||
window = sg.Window("Custom Titlebar and Menu", layout, use_custom_titlebar=True, finalize=True)
|
window = sg.Window("Custom Titlebar and Menu", layout, use_custom_titlebar=True, finalize=True, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_EXIT)
|
||||||
|
|
||||||
win_loc = window.current_location()
|
win_loc = window.current_location()
|
||||||
|
|
||||||
window2 = sg.Window("Traditional Titlebar and Menu", layout2, finalize=True, location=(win_loc[0]-window.size[0]-40, win_loc[1]))
|
window2 = sg.Window("Traditional Titlebar and Menu", layout2, finalize=True, location=(win_loc[0]-window.size[0]-40, win_loc[1]), right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_EXIT)
|
||||||
|
|
||||||
window3 = sg.Window("Output Window", layout3, finalize=True, location=(win_loc[0]-window.size[0]//1.5, win_loc[1]+window.size[1]+30), use_custom_titlebar=True)
|
window3 = sg.Window("Output Window", layout3, finalize=True, location=(int(win_loc[0]-window.size[0]//1.5), int(win_loc[1]+window.size[1]+30)), use_custom_titlebar=True, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_EXIT)
|
||||||
|
|
||||||
|
|
||||||
# ------ Event Loop ------ #
|
# ------ Event Loop ------ #
|
||||||
|
@ -97,7 +95,11 @@ def main():
|
||||||
|
|
||||||
if event in (sg.WIN_CLOSED, 'Exit'):
|
if event in (sg.WIN_CLOSED, 'Exit'):
|
||||||
break
|
break
|
||||||
|
elif event == 'Edit Me':
|
||||||
|
sg.execute_editor(__file__)
|
||||||
|
elif event == 'Version':
|
||||||
|
sg.popup_scrolled(__file__, sg.get_versions(), keep_on_top=True, non_blocking=True)
|
||||||
|
|
||||||
sg.cprint(f'event = {event}', c='white on red')
|
sg.cprint(f'event = {event}', c='white on red')
|
||||||
sg.cprint(f'values = {values}', c='white on green')
|
sg.cprint(f'values = {values}', c='white on green')
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
import PySimpleGUI as sg
|
import PySimpleGUI as sg
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Demonstration of MENUS!
|
Demo of Menu element, ButtonMenu element and right-click menus
|
||||||
How do menus work? Like buttons is how.
|
|
||||||
Check out the variable menu_def for a hint on how to
|
The same basic structure is used for all menus in PySimpleGUI.
|
||||||
define menus
|
Each entry is a list of items to display. If any of those items is a list, then a cancade menu is added.
|
||||||
|
|
||||||
|
Copyright 2018, 2019, 2020, 2021, 2022 PySimpleGUI
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,20 +27,22 @@ def test_menus():
|
||||||
sg.set_options(element_padding=(0, 0))
|
sg.set_options(element_padding=(0, 0))
|
||||||
|
|
||||||
# ------ Menu Definition ------ #
|
# ------ Menu Definition ------ #
|
||||||
menu_def = [['&File', ['&Open Ctrl-O', '&Save Ctrl-S', '&Properties', 'E&xit']],
|
menu_def = [
|
||||||
['&Edit', ['&Paste', ['Special', 'Normal', ], 'Undo'], ],
|
['&File', ['&Open Ctrl-O', '&Save Ctrl-S', '&Properties', 'E&xit']],
|
||||||
|
['&Edit', ['&Paste', ['Special', 'Normal', ], 'Undo', 'Options::this_is_a_menu_key'], ],
|
||||||
['&Toolbar', ['---', 'Command &1', 'Command &2',
|
['&Toolbar', ['---', 'Command &1', 'Command &2',
|
||||||
'---', 'Command &3', 'Command &4']],
|
'---', 'Command &3', 'Command &4']],
|
||||||
['&Help', '&About...'], ]
|
['&Help', ['&About...']]
|
||||||
|
]
|
||||||
|
|
||||||
right_click_menu = ['Unused', ['Right', '!&Click', '&Menu', 'E&xit', 'Properties']]
|
right_click_menu = ['Unused', ['Right', '!&Click', '&Menu', 'E&xit', 'Properties']]
|
||||||
|
|
||||||
# ------ GUI Defintion ------ #
|
# ------ GUI Defintion ------ #
|
||||||
layout = [
|
layout = [
|
||||||
[sg.Menu(menu_def, tearoff=False, pad=(200, 1))],
|
[sg.Menu(menu_def, tearoff=True, font='_ 12', key='-MENUBAR-')],
|
||||||
[sg.Text('Right click me for a right click menu example')],
|
[sg.Text('Right click me for a right click menu example')],
|
||||||
[sg.Output(size=(60, 20))],
|
[sg.Output(size=(60, 20))],
|
||||||
[sg.ButtonMenu('ButtonMenu', right_click_menu, key='-BMENU-'), sg.Button('Plain Button')],
|
[sg.ButtonMenu('ButtonMenu', right_click_menu, key='-BMENU-', text_color='red', disabled_text_color='green'), sg.Button('Plain Button')],
|
||||||
]
|
]
|
||||||
|
|
||||||
window = sg.Window("Windows-like program",
|
window = sg.Window("Windows-like program",
|
||||||
|
@ -55,8 +60,7 @@ def test_menus():
|
||||||
# ------ Process menu choices ------ #
|
# ------ Process menu choices ------ #
|
||||||
if event == 'About...':
|
if event == 'About...':
|
||||||
window.disappear()
|
window.disappear()
|
||||||
sg.popup('About this program', 'Version 1.0',
|
sg.popup('About this program', 'Version 1.0', 'PySimpleGUI Version', sg.get_versions())
|
||||||
'PySimpleGUI Version', sg.version, grab_anywhere=True)
|
|
||||||
window.reappear()
|
window.reappear()
|
||||||
elif event == 'Open':
|
elif event == 'Open':
|
||||||
filename = sg.popup_get_file('file to open', no_window=True)
|
filename = sg.popup_get_file('file to open', no_window=True)
|
||||||
|
|
46
DemoPrograms/Demo_Multi_Window_read_all_windows.py
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import PySimpleGUI as sg
|
||||||
|
|
||||||
|
"""
|
||||||
|
Read all windows example
|
||||||
|
The input elements are shown as output on the other window when "Go" is pressed
|
||||||
|
The checkboxes on window 1 are mirrored on window 2 if "mirror" checkbox is set
|
||||||
|
|
||||||
|
Copyright 2022 PySimpleGUI
|
||||||
|
"""
|
||||||
|
layout1 = [ [sg.Text('My Window')],
|
||||||
|
[sg.Input(k='-IN-'), sg.Text(k='-OUT-')],
|
||||||
|
[sg.CB('Check 1', k='-CB1-', enable_events=True), sg.CB('Check 2', k='-CB2-', enable_events=True), sg.CB('Mirror on Window 2', enable_events=True, k='-CB3-')],
|
||||||
|
[sg.Button('Go'), sg.Button('Exit')] ]
|
||||||
|
|
||||||
|
window1 = sg.Window('Window 1 Title', layout1, finalize=True, grab_anywhere=True, relative_location=(-600, 0))
|
||||||
|
|
||||||
|
layout2 = [ [sg.Text('My Window')],
|
||||||
|
[sg.Input(k='-IN-'), sg.Text(k='-OUT-')],
|
||||||
|
[sg.CB('Check 1', k='-CB1-'), sg.CB('Check 2', k='-CB2-')],
|
||||||
|
[sg.Button('Go'), sg.Button('Exit')] ]
|
||||||
|
|
||||||
|
window2 = sg.Window('Window 2 Title', layout2, finalize=True, grab_anywhere=True)
|
||||||
|
|
||||||
|
while True: # Event Loop
|
||||||
|
window, event, values = sg.read_all_windows()
|
||||||
|
if window is None:
|
||||||
|
print('exiting because no windows are left')
|
||||||
|
break
|
||||||
|
print(window.Title, event, values) if window is not None else None
|
||||||
|
if event == sg.WIN_CLOSED or event == 'Exit':
|
||||||
|
window.close()
|
||||||
|
if event == 'Go':
|
||||||
|
# Output the input element to the other windwow
|
||||||
|
try: # try to update the other window
|
||||||
|
if window == window1:
|
||||||
|
window2['-OUT-'].update(values['-IN-'])
|
||||||
|
else:
|
||||||
|
window1['-OUT-'].update(values['-IN-'])
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
if window == window1 and values['-CB3-']:
|
||||||
|
window2['-CB1-'].update(values['-CB1-'])
|
||||||
|
window2['-CB2-'].update(values['-CB2-'])
|
||||||
|
except:
|
||||||
|
pass
|
142
DemoPrograms/Demo_Multithreaded_DataPump.py
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
import PySimpleGUI as sg
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
|
|
||||||
|
"""
|
||||||
|
Demo - Multi-threaded "Data Pump" Design Pattern
|
||||||
|
|
||||||
|
Send data to your PySimpleGUI program through a Python Queue, enabling integration with many
|
||||||
|
different types of data sources.
|
||||||
|
|
||||||
|
A thread gets data from a queue object and passes it over to the main event loop.
|
||||||
|
The external_thread is only used here to generaate random data. It's not part of the
|
||||||
|
overall "Design Pattern".
|
||||||
|
|
||||||
|
The thread the_thread IS part of the design pattern. It reads data from the thread_queue and sends that
|
||||||
|
data over to the PySimpleGUI event loop.
|
||||||
|
|
||||||
|
Copyright 2022 PySimpleGUI
|
||||||
|
"""
|
||||||
|
|
||||||
|
gsize = (400, 400) # size of the graph
|
||||||
|
|
||||||
|
THREAD_KEY = '-THREAD-'
|
||||||
|
THREAD_INCOMING_DATA = '-INCOMING DATA-'
|
||||||
|
THREAD_EXITNG = '-THREAD EXITING-'
|
||||||
|
THREAD_EXTERNAL_EXITNG = '-EXTERNAL THREAD EXITING-'
|
||||||
|
|
||||||
|
# This queue is where you will send your data that you want to eventually arrive as an event
|
||||||
|
thread_queue = queue.Queue()
|
||||||
|
|
||||||
|
# M""""""""M dP dP
|
||||||
|
# Mmmm mmmM 88 88
|
||||||
|
# MMMM MMMM 88d888b. 88d888b. .d8888b. .d8888b. .d888b88
|
||||||
|
# MMMM MMMM 88' `88 88' `88 88ooood8 88' `88 88' `88
|
||||||
|
# MMMM MMMM 88 88 88 88. ... 88. .88 88. .88
|
||||||
|
# MMMM MMMM dP dP dP `88888P' `88888P8 `88888P8
|
||||||
|
# MMMMMMMMMM
|
||||||
|
#
|
||||||
|
# MP""""""`MM oo dP dP oo
|
||||||
|
# M mmmmm..M 88 88
|
||||||
|
# M. `YM dP 88d8b.d8b. dP dP 88 .d8888b. d8888P dP 88d888b. .d8888b.
|
||||||
|
# MMMMMMM. M 88 88'`88'`88 88 88 88 88' `88 88 88 88' `88 88' `88
|
||||||
|
# M. .MMM' M 88 88 88 88 88. .88 88 88. .88 88 88 88 88 88. .88
|
||||||
|
# Mb. .dM dP dP dP dP `88888P' dP `88888P8 dP dP dP dP `8888P88
|
||||||
|
# MMMMMMMMMMM .88
|
||||||
|
# d8888P
|
||||||
|
# M""""""'YMM dP MP""""""`MM
|
||||||
|
# M mmmm. `M 88 M mmmmm..M
|
||||||
|
# M MMMMM M .d8888b. d8888P .d8888b. M. `YM .d8888b. dP dP 88d888b. .d8888b. .d8888b.
|
||||||
|
# M MMMMM M 88' `88 88 88' `88 MMMMMMM. M 88' `88 88 88 88' `88 88' `"" 88ooood8
|
||||||
|
# M MMMM' .M 88. .88 88 88. .88 M. .MMM' M 88. .88 88. .88 88 88. ... 88. ...
|
||||||
|
# M .MM `88888P8 dP `88888P8 Mb. .dM `88888P' `88888P' dP `88888P' `88888P'
|
||||||
|
# MMMMMMMMMMM MMMMMMMMMMM
|
||||||
|
#
|
||||||
|
|
||||||
|
def external_thread(thread_queue:queue.Queue):
|
||||||
|
"""
|
||||||
|
Represents some external source of data.
|
||||||
|
You would not include this code as a starting point with this Demo Program. Your data is assumed to
|
||||||
|
come from somewhere else. The important part is that you add data to the thread_queue
|
||||||
|
:param thread_queue:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
i = 0
|
||||||
|
while True:
|
||||||
|
time.sleep(.01)
|
||||||
|
point = (random.randint(0,gsize[0]), random.randint(0,gsize[1]))
|
||||||
|
radius = random.randint(10, 40)
|
||||||
|
thread_queue.put((point, radius))
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
|
||||||
|
# M""""""""M dP dP MM""""""""`M
|
||||||
|
# Mmmm mmmM 88 88 MM mmmmmmmM
|
||||||
|
# MMMM MMMM 88d888b. 88d888b. .d8888b. .d8888b. .d888b88 M' MMMM .d8888b. 88d888b.
|
||||||
|
# MMMM MMMM 88' `88 88' `88 88ooood8 88' `88 88' `88 MM MMMMMMMM 88' `88 88' `88
|
||||||
|
# MMMM MMMM 88 88 88 88. ... 88. .88 88. .88 MM MMMMMMMM 88. .88 88
|
||||||
|
# MMMM MMMM dP dP dP `88888P' `88888P8 `88888P8 MM MMMMMMMM `88888P' dP
|
||||||
|
# MMMMMMMMMM MMMMMMMMMMMM
|
||||||
|
#
|
||||||
|
# MM"""""""`YM MP""""""`MM MM'"""""`MM MM""""""""`M dP
|
||||||
|
# MM mmmmm M M mmmmm..M M' .mmm. `M MM mmmmmmmM 88
|
||||||
|
# M' .M M. `YM M MMMMMMMM M` MMMM dP .dP .d8888b. 88d888b. d8888P .d8888b.
|
||||||
|
# MM MMMMMMMM MMMMMMM. M M MMM `M MM MMMMMMMM 88 d8' 88ooood8 88' `88 88 Y8ooooo.
|
||||||
|
# MM MMMMMMMM M. .MMM' M M. `MMM' .M MM MMMMMMMM 88 .88' 88. ... 88 88 88 88
|
||||||
|
# MM MMMMMMMM Mb. .dM MM. .MM MM .M 8888P' `88888P' dP dP dP `88888P'
|
||||||
|
# MMMMMMMMMMMM MMMMMMMMMMM MMMMMMMMMMM MMMMMMMMMMMM
|
||||||
|
|
||||||
|
|
||||||
|
def the_thread(window:sg.Window, thread_queue:queue.Queue):
|
||||||
|
"""
|
||||||
|
The thread that communicates with the application through the window's events.
|
||||||
|
Waits for data from a queue and sends that data on to the event loop
|
||||||
|
:param window:
|
||||||
|
:param thread_queue:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
while True:
|
||||||
|
data = thread_queue.get()
|
||||||
|
window.write_event_value((THREAD_KEY, THREAD_INCOMING_DATA), data) # Data sent is a tuple of thread name and counter
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
layout = [ [sg.Text('My Simulated Data Pump')],
|
||||||
|
[sg.Multiline(size=(60, 20), k='-MLINE-')],
|
||||||
|
[sg.Graph(gsize, (0, 0), gsize, k='-G-', background_color='gray')],
|
||||||
|
[sg.Button('Go'), sg.Button('Exit')] ]
|
||||||
|
|
||||||
|
window = sg.Window('Simulated Data Pump', layout, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_EXIT)
|
||||||
|
|
||||||
|
graph = window['-G-'] # type: sg.Graph
|
||||||
|
|
||||||
|
while True: # Event Loop
|
||||||
|
event, values = window.read()
|
||||||
|
# print(event, values)
|
||||||
|
if event == sg.WIN_CLOSED or event == 'Exit':
|
||||||
|
break
|
||||||
|
if event == 'Go':
|
||||||
|
window.start_thread(lambda: the_thread(window, thread_queue), (THREAD_KEY, THREAD_EXITNG))
|
||||||
|
window.start_thread(lambda: external_thread(thread_queue), (THREAD_KEY, THREAD_EXTERNAL_EXITNG))
|
||||||
|
# Events coming from the Thread
|
||||||
|
elif event[0] == THREAD_KEY:
|
||||||
|
if event[1] == THREAD_INCOMING_DATA:
|
||||||
|
point, radius = values[event]
|
||||||
|
graph.draw_circle(point, radius=radius, fill_color='green')
|
||||||
|
window['-MLINE-'].print(f'Drawing at {point} radius {radius}', c='white on red')
|
||||||
|
elif event[1] == THREAD_EXITNG:
|
||||||
|
window['-MLINE-'].print('Thread has exited')
|
||||||
|
elif event[1] == THREAD_EXTERNAL_EXITNG:
|
||||||
|
window['-MLINE-'].print('Data Pump thread has exited')
|
||||||
|
if event == 'Edit Me':
|
||||||
|
sg.execute_editor(__file__)
|
||||||
|
elif event == 'Version':
|
||||||
|
sg.popup_scrolled(__file__, sg.get_versions(), location=window.current_location(), keep_on_top=True, non_blocking=True)
|
||||||
|
|
||||||
|
window.close()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -0,0 +1,151 @@
|
||||||
|
import PySimpleGUI as sg
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
|
||||||
|
"""
|
||||||
|
Demo - Multi-threaded - Show Windows and perform other PySimpleGUI calls in what appread to be from a thread
|
||||||
|
|
||||||
|
Just so that it's clear, you CANNOT make PySimpleGUI calls directly from a thread. There is ONE exception to this
|
||||||
|
rule. A thread may call window.write_event_values which enables it to communicate to a window through the window.read calls.
|
||||||
|
|
||||||
|
The main GUI will not be visible on your screen nor on your taskbar despite running in the background. The calls you
|
||||||
|
make, such as popup, or even Window.read will create windows that your user will see.
|
||||||
|
|
||||||
|
The basic function that you'll use in your thread has this format:
|
||||||
|
make_delegate_call(lambda: sg.popup('This is a popup', i, auto_close=True, auto_close_duration=2, keep_on_top=True, non_blocking=True))
|
||||||
|
|
||||||
|
Everything after the "lambda" looks exactly like a PySimpleGUI call.
|
||||||
|
If you want to display an entire window, then the suggestion is to put it into a function and pass the function to make_delegate_call
|
||||||
|
|
||||||
|
Note - the behavior of variables may be a bit of a surprise as they are not evaluated until the mainthread processes the event. This means
|
||||||
|
in the example below that the counter variable being passed to the popup will not appear to be counting correctly. This is because the
|
||||||
|
value shown will be the value at the time the popup is DISPLAYED, not the value when the make_delegate_call was made.
|
||||||
|
|
||||||
|
Copyright 2022 PySimpleGUI
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# Design decision was to make the window a global. You can just as easily pass it to your function after initizing your window
|
||||||
|
# But there becomes a problem then of wheere do you place the thread startup code. Using this global decouples them so that
|
||||||
|
# the thread is not started in the function that makes and executes the GUI
|
||||||
|
|
||||||
|
window:sg.Window = None
|
||||||
|
|
||||||
|
# M""MMMM""M
|
||||||
|
# M. `MM' .M
|
||||||
|
# MM. .MM .d8888b. dP dP 88d888b.
|
||||||
|
# MMMb dMMM 88' `88 88 88 88' `88
|
||||||
|
# MMMM MMMM 88. .88 88. .88 88
|
||||||
|
# MMMM MMMM `88888P' `88888P' dP
|
||||||
|
# MMMMMMMMMM
|
||||||
|
#
|
||||||
|
# M""""""""M dP dP
|
||||||
|
# Mmmm mmmM 88 88
|
||||||
|
# MMMM MMMM 88d888b. 88d888b. .d8888b. .d8888b. .d888b88
|
||||||
|
# MMMM MMMM 88' `88 88' `88 88ooood8 88' `88 88' `88
|
||||||
|
# MMMM MMMM 88 88 88 88. ... 88. .88 88. .88
|
||||||
|
# MMMM MMMM dP dP dP `88888P' `88888P8 `88888P8
|
||||||
|
# MMMMMMMMMM
|
||||||
|
|
||||||
|
def the_thread():
|
||||||
|
"""
|
||||||
|
This is code that is unique to your application. It wants to "make calls to PySimpleGUI", but it cannot directly do so.
|
||||||
|
Instead it will send the request to make the call to the mainthread that is running the GUI.
|
||||||
|
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Wait for the GUI to start running
|
||||||
|
while window is None:
|
||||||
|
time.sleep(.2)
|
||||||
|
|
||||||
|
for i in range(5):
|
||||||
|
time.sleep(.2)
|
||||||
|
make_delegate_call(lambda: sg.popup('This is a popup', i, relative_location=(0, -300), auto_close=True, auto_close_duration=2, keep_on_top=True, non_blocking=True))
|
||||||
|
make_delegate_call(lambda: sg.popup_scrolled(__file__, sg.get_versions(), auto_close=True, auto_close_duration=1.5, non_blocking=True))
|
||||||
|
|
||||||
|
make_delegate_call(lambda: sg.popup('One last popup before exiting...', relative_location=(-200, -200)))
|
||||||
|
|
||||||
|
# when finished and ready to stop, tell the main GUI to exit
|
||||||
|
window.write_event_value('-THREAD EXIT-', None)
|
||||||
|
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
# The remainder of the code is part of the overall design pattern. You should copy this code
|
||||||
|
# and use it as the basis for creating this time of delegated PySimpleGUI calls
|
||||||
|
|
||||||
|
|
||||||
|
# M""""""'YMM oo
|
||||||
|
# M mmmm. `M
|
||||||
|
# M MMMMM M .d8888b. .d8888b. dP .d8888b. 88d888b.
|
||||||
|
# M MMMMM M 88ooood8 Y8ooooo. 88 88' `88 88' `88
|
||||||
|
# M MMMM' .M 88. ... 88 88 88. .88 88 88
|
||||||
|
# M .MM `88888P' `88888P' dP `8888P88 dP dP
|
||||||
|
# MMMMMMMMMMM .88
|
||||||
|
# d8888P
|
||||||
|
# MM"""""""`YM dP dP
|
||||||
|
# MM mmmmm M 88 88
|
||||||
|
# M' .M .d8888b. d8888P d8888P .d8888b. 88d888b. 88d888b.
|
||||||
|
# MM MMMMMMMM 88' `88 88 88 88ooood8 88' `88 88' `88
|
||||||
|
# MM MMMMMMMM 88. .88 88 88 88. ... 88 88 88
|
||||||
|
# MM MMMMMMMM `88888P8 dP dP `88888P' dP dP dP
|
||||||
|
# MMMMMMMMMMMM
|
||||||
|
|
||||||
|
def make_delegate_call(func):
|
||||||
|
"""
|
||||||
|
Make a delegate call to PySimpleGUI.
|
||||||
|
|
||||||
|
:param func: A lambda expression most likely. It's a function that will be called by the mainthread that's executing the GUI
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if window is not None:
|
||||||
|
window.write_event_value('-THREAD DELEGATE-', func)
|
||||||
|
|
||||||
|
|
||||||
|
# oo
|
||||||
|
#
|
||||||
|
# 88d8b.d8b. .d8888b. dP 88d888b.
|
||||||
|
# 88'`88'`88 88' `88 88 88' `88
|
||||||
|
# 88 88 88 88. .88 88 88 88
|
||||||
|
# dP dP dP `88888P8 dP dP dP
|
||||||
|
|
||||||
|
def main():
|
||||||
|
global window
|
||||||
|
|
||||||
|
# create a window. A key is needed so that the values dictionary will return the thread's value as a key
|
||||||
|
layout = [[sg.Text('', k='-T-')]]
|
||||||
|
|
||||||
|
# set the window to be both invisible and have no taskbar icon
|
||||||
|
window = sg.Window('Invisible window', layout, no_titlebar=True, alpha_channel=0, finalize=True, font='_ 1', margins=(0,0), element_padding=(0,0))
|
||||||
|
window.hide()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
event, values = window.read()
|
||||||
|
if event in ('Exit', sg.WIN_CLOSED):
|
||||||
|
break
|
||||||
|
# if the event is from the thread, then the value is the function that should be called
|
||||||
|
if event == '-THREAD DELEGATE-':
|
||||||
|
try:
|
||||||
|
values[event]()
|
||||||
|
except Exception as e:
|
||||||
|
sg.popup_error_with_traceback('Error calling your function passed to GUI', event, values, e)
|
||||||
|
elif event == '-THREAD EXIT-':
|
||||||
|
break
|
||||||
|
window.close()
|
||||||
|
|
||||||
|
|
||||||
|
# MP""""""`MM dP dP
|
||||||
|
# M mmmmm..M 88 88
|
||||||
|
# M. `YM d8888P .d8888b. 88d888b. d8888P dP dP 88d888b.
|
||||||
|
# MMMMMMM. M 88 88' `88 88' `88 88 88 88 88' `88
|
||||||
|
# M. .MMM' M 88 88. .88 88 88 88. .88 88. .88
|
||||||
|
# Mb. .dM dP `88888P8 dP dP `88888P' 88Y888P'
|
||||||
|
# MMMMMMMMMMM 88
|
||||||
|
# dP
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# first your thread will be started
|
||||||
|
threading.Thread(target=the_thread, daemon=True).start()
|
||||||
|
# then startup the main GUI
|
||||||
|
main()
|
|
@ -1,25 +1,23 @@
|
||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
import threading
|
|
||||||
import time
|
import time
|
||||||
import PySimpleGUI as sg
|
import PySimpleGUI as sg
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
DESIGN PATTERN - Multithreaded Long Tasks GUI
|
Demo Program - Multithreaded Long Tasks GUI
|
||||||
|
|
||||||
Presents one method for running long-running operations in a PySimpleGUI environment.
|
Presents one method for running long-running operations in a PySimpleGUI environment.
|
||||||
|
|
||||||
The PySimpleGUI code, and thus the underlying GUI framework, runs as the primary, main thread
|
The PySimpleGUI code, and thus the underlying GUI framework, runs as the primary, main thread
|
||||||
The "long work" is contained in the thread that is being started.
|
The "long work" is contained in the thread that is being started.
|
||||||
|
|
||||||
July 2020 - Note that this program has been updated to use the new Window.write_event_value method.
|
So that you don't have to import and understand the threading module, this program uses window.start_thread to run a thread.
|
||||||
This method has not yet been ported to the other PySimpleGUI ports and is thus limited to the tkinter ports for now.
|
|
||||||
|
|
||||||
Internally to PySimpleGUI, a queue.Queue is used by the threads to communicate with main GUI code
|
The thread is using TUPLES for its keys. This enables you to easily find the thread events by looking at event[0].
|
||||||
The PySimpleGUI code is structured just like a typical PySimpleGUI program. A layout defined,
|
The Thread Keys look something like this: ('-THREAD-', message)
|
||||||
a Window is created, and an event loop is executed.
|
If event [0] == '-THREAD-' then you know it's one of these tuple keys.
|
||||||
|
|
||||||
|
Copyright 2022 PySimpleGUI
|
||||||
This design pattern works for all of the flavors of PySimpleGUI including the Web and also repl.it
|
|
||||||
You'll find a repl.it version here: https://repl.it/@PySimpleGUI/Async-With-Queue-Communicationspy
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,12 +26,12 @@ def long_operation_thread(seconds, window):
|
||||||
A worker thread that communicates with the GUI through a queue
|
A worker thread that communicates with the GUI through a queue
|
||||||
This thread can block for as long as it wants and the GUI will not be affected
|
This thread can block for as long as it wants and the GUI will not be affected
|
||||||
:param seconds: (int) How long to sleep, the ultimate blocking call
|
:param seconds: (int) How long to sleep, the ultimate blocking call
|
||||||
:param gui_queue: (queue.Queue) Queue to communicate back to GUI that task is completed
|
:param window: (sg.Window) the window to communicate with
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
print('Starting thread - will sleep for {} seconds'.format(seconds))
|
window.write_event_value(('-THREAD-', 'Starting thread - will sleep for {} seconds'.format(seconds)), None)
|
||||||
time.sleep(seconds) # sleep for a while
|
time.sleep(seconds) # sleep for a while
|
||||||
window.write_event_value('-THREAD-', '** DONE **') # put a message into queue for GUI
|
window.write_event_value(('-THREAD-', '** DONE **'), 'Done!') # put a message into queue for GUI
|
||||||
|
|
||||||
|
|
||||||
def the_gui():
|
def the_gui():
|
||||||
|
@ -47,7 +45,7 @@ def the_gui():
|
||||||
layout = [[sg.Text('Long task to perform example')],
|
layout = [[sg.Text('Long task to perform example')],
|
||||||
[sg.Output(size=(70, 12))],
|
[sg.Output(size=(70, 12))],
|
||||||
[sg.Text('Number of seconds your task will take'),
|
[sg.Text('Number of seconds your task will take'),
|
||||||
sg.Input(key='-SECONDS-', size=(5, 1)),
|
sg.Input(default_text=5, key='-SECONDS-', size=(5, 1)),
|
||||||
sg.Button('Do Long Task', bind_return_key=True)],
|
sg.Button('Do Long Task', bind_return_key=True)],
|
||||||
[sg.Button('Click Me'), sg.Button('Exit')], ]
|
[sg.Button('Click Me'), sg.Button('Exit')], ]
|
||||||
|
|
||||||
|
@ -58,14 +56,14 @@ def the_gui():
|
||||||
event, values = window.read()
|
event, values = window.read()
|
||||||
if event in (sg.WIN_CLOSED, 'Exit'):
|
if event in (sg.WIN_CLOSED, 'Exit'):
|
||||||
break
|
break
|
||||||
elif event.startswith('Do'):
|
elif event == 'Do Long Task':
|
||||||
seconds = int(values['-SECONDS-'])
|
seconds = int(values['-SECONDS-'])
|
||||||
print('Thread ALIVE! Long work....sending value of {} seconds'.format(seconds))
|
print('Thread ALIVE! Long work....sending value of {} seconds'.format(seconds))
|
||||||
threading.Thread(target=long_operation_thread, args=(seconds, window,), daemon=True).start()
|
window.start_thread(lambda: long_operation_thread(seconds, window), ('-THREAD-', '-THEAD ENDED-'))
|
||||||
elif event == 'Click Me':
|
elif event == 'Click Me':
|
||||||
print('Your GUI is alive and well')
|
print('Your GUI is alive and well')
|
||||||
elif event == '-THREAD-':
|
elif event[0] == '-THREAD-':
|
||||||
print('Got a message back from the thread: ', values[event])
|
print('Got a message back from the thread: ', event[1])
|
||||||
|
|
||||||
# if user exits the window, then close the window and exit the GUI func
|
# if user exits the window, then close the window and exit the GUI func
|
||||||
window.close()
|
window.close()
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import PySimpleGUI as sg
|
import PySimpleGUI as sg
|
||||||
import threading
|
|
||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
@ -14,37 +13,46 @@ import time
|
||||||
have implemented your own progress meter in your window.
|
have implemented your own progress meter in your window.
|
||||||
|
|
||||||
Using the write_event_value method enables you to easily do either of these.
|
Using the write_event_value method enables you to easily do either of these.
|
||||||
|
|
||||||
|
In this demo, all thread events are a TUPLE with the first item in tuple being THREAD_KEY ---> '-THEAD-'
|
||||||
|
This allows easy separation of all of the thread-based keys into 1 if statment:
|
||||||
|
elif event[0] == THREAD_KEY:
|
||||||
|
Example
|
||||||
|
(THREAD_KEY, DL_START_KEY) indicates the download is starting and provices the Max value
|
||||||
|
(THREAD_KEY, DL_END_KEY) indicates the downloading has completed
|
||||||
|
|
||||||
|
The main window uses a relative location when making the window so that the one-line-progress-meter has room
|
||||||
|
|
||||||
Copyright 2021 PySimpleGUI
|
Copyright 2021, 2022 PySimpleGUI
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
THREAD_KEY = '-THREAD-'
|
||||||
DL_START_KEY = '-START DOWNLOAD-'
|
DL_START_KEY = '-START DOWNLOAD-'
|
||||||
DL_COUNT_KEY = '-COUNT-'
|
DL_COUNT_KEY = '-COUNT-'
|
||||||
DL_END_KEY = '-END DOWNLOAD-'
|
DL_END_KEY = '-END DOWNLOAD-'
|
||||||
|
DL_THREAD_EXITNG = '-THREAD EXITING-'
|
||||||
|
|
||||||
def the_thread(window:sg.Window):
|
def the_thread(window:sg.Window):
|
||||||
"""
|
"""
|
||||||
The thread that communicates with the application through the window's events.
|
The thread that communicates with the application through the window's events.
|
||||||
|
|
||||||
Once a second wakes and sends a new event and associated value to the window
|
Simulates downloading a random number of chinks from 50 to 100-
|
||||||
"""
|
"""
|
||||||
max_value = random.randint(50, 100)
|
max_value = random.randint(50, 100)
|
||||||
window.write_event_value(DL_START_KEY, max_value) # Data sent is a tuple of thread name and counter
|
window.write_event_value((THREAD_KEY, DL_START_KEY), max_value) # Data sent is a tuple of thread name and counter
|
||||||
for i in range(max_value):
|
for i in range(max_value):
|
||||||
time.sleep(.1)
|
time.sleep(.1)
|
||||||
window.write_event_value(DL_COUNT_KEY, i) # Data sent is a tuple of thread name and counter
|
window.write_event_value((THREAD_KEY, DL_COUNT_KEY), i) # Data sent is a tuple of thread name and counter
|
||||||
window.write_event_value(DL_END_KEY, max_value) # Data sent is a tuple of thread name and counter
|
window.write_event_value((THREAD_KEY, DL_END_KEY), max_value) # Data sent is a tuple of thread name and counter
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
layout = [ [sg.Text('My Window')],
|
layout = [ [sg.Text('My Multi-threaded PySimpleGUI Program')],
|
||||||
[sg.ProgressBar(100, 'h', size=(30,20), k='-PROGRESS-')],
|
[sg.ProgressBar(100, 'h', size=(30,20), k='-PROGRESS-', expand_x=True)],
|
||||||
|
[sg.Text(key='-STATUS-')],
|
||||||
[sg.Button('Go'), sg.Button('Exit')] ]
|
[sg.Button('Go'), sg.Button('Exit')] ]
|
||||||
|
|
||||||
window = sg.Window('Window Title', layout, finalize=True)
|
window = sg.Window('Window Title', layout, finalize=True, relative_location=(0, -300))
|
||||||
window.read(timeout=0)
|
|
||||||
window.move(window.current_location()[0], window.current_location()[1]-300)
|
|
||||||
downloading, max_value = False, 0
|
downloading, max_value = False, 0
|
||||||
|
|
||||||
while True: # Event Loop
|
while True: # Event Loop
|
||||||
|
@ -53,17 +61,24 @@ def main():
|
||||||
if event == sg.WIN_CLOSED or event == 'Exit':
|
if event == sg.WIN_CLOSED or event == 'Exit':
|
||||||
break
|
break
|
||||||
if event == 'Go' and not downloading:
|
if event == 'Go' and not downloading:
|
||||||
threading.Thread(target=the_thread, args=(window,), daemon=True).start()
|
window.start_thread(lambda: the_thread(window), (THREAD_KEY, DL_THREAD_EXITNG))
|
||||||
elif event == DL_START_KEY:
|
# Events coming from the Thread
|
||||||
max_value = values[event]
|
elif event[0] == THREAD_KEY:
|
||||||
downloading = True
|
if event[1] == DL_START_KEY:
|
||||||
sg.one_line_progress_meter(f'Downloading {max_value} segments', 0, max_value, 1, f'Downloading {max_value} segments', )
|
max_value = values[event]
|
||||||
window['-PROGRESS-'].update(0, max_value)
|
downloading = True
|
||||||
elif event == DL_COUNT_KEY:
|
window['-STATUS-'].update('Starting download')
|
||||||
sg.one_line_progress_meter(f'Downloading {max_value} segments', values[event]+1, max_value, 1, f'Downloading {max_value} segments')
|
sg.one_line_progress_meter(f'Downloading {max_value} segments', 0, max_value, 1, f'Downloading {max_value} segments', )
|
||||||
window['-PROGRESS-'].update(values[event]+1, max_value)
|
window['-PROGRESS-'].update(0, max_value)
|
||||||
elif event == DL_END_KEY:
|
elif event[1] == DL_COUNT_KEY:
|
||||||
downloading = False
|
sg.one_line_progress_meter(f'Downloading {max_value} segments', values[event]+1, max_value, 1, f'Downloading {max_value} segments')
|
||||||
|
window['-STATUS-'].update(f'Got a new current count update {values[event]}')
|
||||||
|
window['-PROGRESS-'].update(values[event]+1, max_value)
|
||||||
|
elif event[1] == DL_END_KEY:
|
||||||
|
downloading = False
|
||||||
|
window['-STATUS-'].update('Download finished')
|
||||||
|
elif event[1] == DL_THREAD_EXITNG:
|
||||||
|
window['-STATUS-'].update('Last step - Thread has exited')
|
||||||
|
|
||||||
window.close()
|
window.close()
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ button64 = 'iVBORw0KGgoAAAANSUhEUgAAAoAAAAFACAMAAAAbEz04AAAABGdBTUEAALGPC/xhBQAA
|
||||||
def image_file_to_bytes(image64, size):
|
def image_file_to_bytes(image64, size):
|
||||||
image_file = io.BytesIO(base64.b64decode(image64))
|
image_file = io.BytesIO(base64.b64decode(image64))
|
||||||
img = Image.open(image_file)
|
img = Image.open(image_file)
|
||||||
img.thumbnail(size, Image.ANTIALIAS)
|
img.thumbnail(size, Image.LANCZOS)
|
||||||
bio = io.BytesIO()
|
bio = io.BytesIO()
|
||||||
img.save(bio, format='PNG')
|
img.save(bio, format='PNG')
|
||||||
imgbytes = bio.getvalue()
|
imgbytes = bio.getvalue()
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
import PySimpleGUI as sg
|
import PySimpleGUI as sg
|
||||||
from PIL import Image
|
|
||||||
import cv2 as cv
|
import cv2 as cv
|
||||||
import io
|
# from PIL import Image
|
||||||
|
# import io
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Demo program to open and play a file using OpenCV
|
Demo program to open and play a file using OpenCV
|
||||||
It's main purpose is to show you:
|
It's main purpose is to show you:
|
||||||
1. How to get a frame at a time from a video file using OpenCV
|
1. How to get a frame at a time from a video file using OpenCV
|
||||||
2. How to display an image in a PySimpleGUI Window
|
2. How to display an image in a PySimpleGUI Window
|
||||||
|
|
||||||
For added fun, you can reposition the video using the slider.
|
For added fun, you can reposition the video using the slider.
|
||||||
|
|
||||||
|
Copyright 2022 PySimpleGUI
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,37 +30,39 @@ def main():
|
||||||
|
|
||||||
# ---===--- define the window layout --- #
|
# ---===--- define the window layout --- #
|
||||||
layout = [[sg.Text('OpenCV Demo', size=(15, 1), font='Helvetica 20')],
|
layout = [[sg.Text('OpenCV Demo', size=(15, 1), font='Helvetica 20')],
|
||||||
[sg.Image(filename='', key='-image-')],
|
[sg.Image(key='-IMAGE-')],
|
||||||
[sg.Slider(range=(0, num_frames),
|
[sg.Slider(range=(0, num_frames), size=(60, 10), orientation='h', key='-SLIDER-')],
|
||||||
size=(60, 10), orientation='h', key='-slider-')],
|
[sg.Push(), sg.Button('Exit', font='Helvetica 14')]]
|
||||||
[sg.Button('Exit', size=(7, 1), pad=((600, 0), 3), font='Helvetica 14')]]
|
|
||||||
|
|
||||||
# create the window and show it without the plot
|
# create the window and show it without the plot
|
||||||
window = sg.Window('Demo Application - OpenCV Integration', layout, no_titlebar=False, location=(0, 0))
|
window = sg.Window('Demo Application - OpenCV Integration', layout, no_titlebar=False, location=(0, 0))
|
||||||
|
|
||||||
# locate the elements we'll be updating. Does the search only 1 time
|
# locate the elements we'll be updating. Does the search only 1 time
|
||||||
image_elem = window['-image-']
|
image_elem = window['-IMAGE-']
|
||||||
slider_elem = window['-slider-']
|
slider_elem = window['-SLIDER-']
|
||||||
|
timeout = 1000//fps # time in ms to use for window reads
|
||||||
|
|
||||||
# ---===--- LOOP through video file by frame --- #
|
# ---===--- LOOP through video file by frame --- #
|
||||||
cur_frame = 0
|
cur_frame = 0
|
||||||
while vidFile.isOpened():
|
while vidFile.isOpened():
|
||||||
event, values = window.read(timeout=0)
|
event, values = window.read(timeout=timeout)
|
||||||
if event in ('Exit', None):
|
if event in ('Exit', None):
|
||||||
break
|
break
|
||||||
ret, frame = vidFile.read()
|
ret, frame = vidFile.read()
|
||||||
if not ret: # if out of data stop looping
|
if not ret: # if out of data stop looping
|
||||||
break
|
break
|
||||||
# if someone moved the slider manually, the jump to that frame
|
# if someone moved the slider manually, the jump to that frame
|
||||||
if int(values['-slider-']) != cur_frame-1:
|
if int(values['-SLIDER-']) != cur_frame-1:
|
||||||
cur_frame = int(values['-slider-'])
|
cur_frame = int(values['-SLIDER-'])
|
||||||
vidFile.set(cv.CAP_PROP_POS_FRAMES, cur_frame)
|
vidFile.set(cv.CAP_PROP_POS_FRAMES, cur_frame)
|
||||||
slider_elem.update(cur_frame)
|
slider_elem.update(cur_frame)
|
||||||
cur_frame += 1
|
cur_frame += 1
|
||||||
|
|
||||||
imgbytes = cv.imencode('.png', frame)[1].tobytes() # ditto
|
imgbytes = cv.imencode('.ppm', frame)[1].tobytes() # can also use png. ppm found to be more efficient
|
||||||
image_elem.update(data=imgbytes)
|
image_elem.update(data=imgbytes)
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# | | #
|
# | | #
|
||||||
# | | #
|
# | | #
|
||||||
|
@ -74,6 +78,4 @@ def main():
|
||||||
img.save(bio, format= 'PNG') # save image as png to it
|
img.save(bio, format= 'PNG') # save image as png to it
|
||||||
imgbytes = bio.getvalue() # this can be used by OpenCV hopefully
|
imgbytes = bio.getvalue() # this can be used by OpenCV hopefully
|
||||||
image_elem.update(data=imgbytes)
|
image_elem.update(data=imgbytes)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
main()
|
|
|
@ -2,8 +2,6 @@ import cv2
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import PySimpleGUI as sg
|
import PySimpleGUI as sg
|
||||||
font_size = 6
|
|
||||||
USING_QT = False
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Interesting program that shows your webcam's image as ASCII text. Runs in realtime, producing a stream of
|
Interesting program that shows your webcam's image as ASCII text. Runs in realtime, producing a stream of
|
||||||
|
@ -22,40 +20,34 @@ USING_QT = False
|
||||||
pip install opencv-python
|
pip install opencv-python
|
||||||
|
|
||||||
On Linux / Mac use pip3 instead of pip
|
On Linux / Mac use pip3 instead of pip
|
||||||
|
|
||||||
|
Copyright 2022, PySimpleGUI
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# The magic bits that make the ASCII stuff work shamelessly taken from https://gist.github.com/cdiener/10491632
|
# The magic bits that make the ASCII stuff work shamelessly taken from https://gist.github.com/cdiener/10491632
|
||||||
chars = np.asarray(list(' .,:;irsXA253hMHGS#9B&@'))
|
chars = np.asarray(list(' .,:;irsXA253hMHGS#9B&@'))
|
||||||
SC, GCF, WCF = .1, 1, 7/4
|
SC, GCF, WCF = .1, 1, 7/4
|
||||||
|
|
||||||
sg.theme('Black') # make it look cool
|
sg.theme('Black') # make it look cool with white chars on black background
|
||||||
|
font_size = 6
|
||||||
|
|
||||||
# define the window layout
|
# define the window layout
|
||||||
# number of lines of text elements. Depends on cameras image size and the variable SC (scaller)
|
# number of lines of text elements. Depends on cameras image size and the variable SC (scaller)
|
||||||
NUM_LINES = 48
|
NUM_LINES = 48
|
||||||
if USING_QT:
|
|
||||||
layout = [[sg.Text(i, size_px=(800, 12),
|
|
||||||
font=('Courier', font_size),
|
|
||||||
key='-OUT-' + str(i))] for i in range(NUM_LINES)]
|
|
||||||
else:
|
|
||||||
layout = [[sg.Text(i, size=(120, 1), font=('Courier', font_size),
|
|
||||||
pad=(0, 0), key='-OUT-'+str(i))] for i in range(NUM_LINES)]
|
|
||||||
|
|
||||||
layout += [[sg.Button('Exit', size=(5, 1)),
|
layout = [[[sg.Text(i, font=('Courier', font_size), pad=(0, 0), key=('-OUT-', i))] for i in range(NUM_LINES)],
|
||||||
sg.Text('GCF', size=(4, 1)),
|
[sg.Text('GCF', s=9, justification='r'), sg.Slider((0.1, 20), resolution=.05, default_value=1, orientation='h', key='-SPIN-GCF-', size=(15, 15))],
|
||||||
sg.Spin([round(i, 2) for i in np.arange(0.1, 20.0, 0.1)],
|
[sg.Text('Font Size', s=9, justification='r'), sg.Slider((4, 20), resolution=1, default_value=font_size, orientation='h', key='-FONT SIZE-', size=(15, 15)),
|
||||||
initial_value=1, key='-SPIN-GCF-', size=(5, 1)),
|
sg.Push(), sg.Button('Exit')]]
|
||||||
sg.Text('WCF', size=(4, 1)),
|
|
||||||
sg.Slider((1, 4), resolution=.05, default_value=1.75,
|
|
||||||
orientation='h', key='-SLIDER-WCF-', size=(15, 15))]]
|
|
||||||
|
|
||||||
# create the window and show it without the plot
|
# create the window and show it without the plot
|
||||||
window = sg.Window('Demo Application - OpenCV Integration', layout,
|
window = sg.Window('Demo Application - OpenCV - ASCII Chars Output', layout, font='Any 18', resizable=True)
|
||||||
location=(800, 400), font='Any 18')
|
|
||||||
|
|
||||||
# ---===--- Event LOOP Read and display frames, operate the GUI --- #
|
# ---===--- Event LOOP Read and display frames, operate the GUI --- #
|
||||||
# Setup the OpenCV capture device (webcam)
|
# Setup the OpenCV capture device (webcam)
|
||||||
|
|
||||||
cap = cv2.VideoCapture(0)
|
cap = cv2.VideoCapture(0)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
|
||||||
event, values = window.read(timeout=0)
|
event, values = window.read(timeout=0)
|
||||||
|
@ -66,7 +58,7 @@ while True:
|
||||||
|
|
||||||
img = Image.fromarray(frame) # create PIL image from frame
|
img = Image.fromarray(frame) # create PIL image from frame
|
||||||
GCF = float(values['-SPIN-GCF-'])
|
GCF = float(values['-SPIN-GCF-'])
|
||||||
WCF = values['-SLIDER-WCF-']
|
WCF = 1.75
|
||||||
# More magic that coverts the image to ascii
|
# More magic that coverts the image to ascii
|
||||||
S = (round(img.size[0] * SC * WCF), round(img.size[1] * SC))
|
S = (round(img.size[0] * SC * WCF), round(img.size[1] * SC))
|
||||||
img = np.sum(np.asarray(img.resize(S)), axis=2)
|
img = np.sum(np.asarray(img.resize(S)), axis=2)
|
||||||
|
@ -74,7 +66,8 @@ while True:
|
||||||
img = (1.0 - img / img.max()) ** GCF * (chars.size - 1)
|
img = (1.0 - img / img.max()) ** GCF * (chars.size - 1)
|
||||||
|
|
||||||
# "Draw" the image in the window, one line of text at a time!
|
# "Draw" the image in the window, one line of text at a time!
|
||||||
|
font_size = int(values['-FONT SIZE-'])
|
||||||
for i, r in enumerate(chars[img.astype(int)]):
|
for i, r in enumerate(chars[img.astype(int)]):
|
||||||
window['-OUT-'+str(i)].update("".join(r))
|
window[('-OUT-', i)].update("".join(r), font=('Courier', font_size))
|
||||||
|
|
||||||
window.close()
|
window.close()
|
||||||
|
|
|
@ -29,7 +29,7 @@ layout = [ [sg.Graph((100,100), (0,100), (100,0), key='-GRAPH-')],
|
||||||
[sg.T(k='-OUT LOC-')],
|
[sg.T(k='-OUT LOC-')],
|
||||||
[sg.T('F1 copy F2 Exit')]]
|
[sg.T('F1 copy F2 Exit')]]
|
||||||
|
|
||||||
window = sg.Window('Color Picker', layout, no_titlebar=True, keep_on_top=True, grab_anywhere=True, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_EXIT, finalize=True)
|
window = sg.Window('Color Picker', layout, no_titlebar=False, keep_on_top=True, grab_anywhere=True, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_EXIT, finalize=True)
|
||||||
|
|
||||||
window.bind('<F1>', '-COPY-')
|
window.bind('<F1>', '-COPY-')
|
||||||
window.bind('<F2>', 'Exit')
|
window.bind('<F2>', 'Exit')
|
||||||
|
@ -45,7 +45,7 @@ while True:
|
||||||
if event == 'Edit Me':
|
if event == 'Edit Me':
|
||||||
sp = sg.execute_editor(__file__)
|
sp = sg.execute_editor(__file__)
|
||||||
elif event == 'Version':
|
elif event == 'Version':
|
||||||
sg.popup_scrolled(__file__, sg.get_versions(), keep_on_top=True, location=window.current_location())
|
sg.popup_scrolled(__file__, sg.get_versions(), keep_on_top=True, location=window.current_location(), non_blocking=True)
|
||||||
|
|
||||||
window['-GRAPH-'].erase()
|
window['-GRAPH-'].erase()
|
||||||
x, y = window.mouse_location()
|
x, y = window.mouse_location()
|
||||||
|
|
|
@ -13,51 +13,65 @@ import random
|
||||||
This function is your gateway to using any format of image (not just PNG & GIF) and to
|
This function is your gateway to using any format of image (not just PNG & GIF) and to
|
||||||
resize / convert it so that it can be used with the Button and Image elements.
|
resize / convert it so that it can be used with the Button and Image elements.
|
||||||
|
|
||||||
Copyright 2020 PySimpleGUI.org
|
Copyright 2020, 2022 PySimpleGUI.org
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def make_square(im, min_size=256, fill_color=(0, 0, 0, 0)):
|
def make_square(im, fill_color=(0, 0, 0, 0)):
|
||||||
x, y = im.size
|
x, y = im.size
|
||||||
size = max(min_size, x, y)
|
size = max(x, y)
|
||||||
new_im = Image.new('RGBA', (size, size), fill_color)
|
new_im = Image.new('RGBA', (size, size), fill_color)
|
||||||
new_im.paste(im, (int((size - x) / 2), int((size - y) / 2)))
|
new_im.paste(im, (int((size - x) / 2), int((size - y) / 2)))
|
||||||
return new_im
|
return new_im
|
||||||
|
|
||||||
|
|
||||||
def convert_to_bytes(file_or_bytes, resize=None, fill=False):
|
|
||||||
|
def convert_to_bytes(source, size=(None, None), subsample=None, zoom=None, fill=False):
|
||||||
"""
|
"""
|
||||||
Will convert into bytes and optionally resize an image that is a file or a base64 bytes object.
|
Will convert into bytes and optionally resize an image that is a file or a base64 bytes object.
|
||||||
Turns into PNG format in the process so that can be displayed by tkinter
|
Turns into PNG format in the process so that can be displayed by tkinter
|
||||||
:param file_or_bytes: either a string filename or a bytes base64 image object
|
:param source: either a string filename or a bytes base64 image object
|
||||||
:type file_or_bytes: (Union[str, bytes])
|
:type source: (Union[str, bytes])
|
||||||
:param resize: optional new size
|
:param size: optional new size (width, height)
|
||||||
:type resize: (Tuple[int, int] or None)
|
:type size: (Tuple[int, int] or None)
|
||||||
:param fill: If True then the image is filled/padded so that the image is not distorted
|
:param subsample: change the size by multiplying width and height by 1/subsample
|
||||||
|
:type subsample: (int)
|
||||||
|
:param zoom: change the size by multiplying width and height by zoom
|
||||||
|
:type zoom: (int)
|
||||||
|
:param fill: If True then the image is filled/padded so that the image is square
|
||||||
:type fill: (bool)
|
:type fill: (bool)
|
||||||
:return: (bytes) a byte-string object
|
:return: (bytes) a byte-string object
|
||||||
:rtype: (bytes)
|
:rtype: (bytes)
|
||||||
"""
|
"""
|
||||||
if isinstance(file_or_bytes, str):
|
if isinstance(source, str):
|
||||||
img = PIL.Image.open(file_or_bytes)
|
image = Image.open(source)
|
||||||
|
elif isinstance(source, bytes):
|
||||||
|
image = Image.open(io.BytesIO(base64.b64decode(source)))
|
||||||
else:
|
else:
|
||||||
try:
|
image = PIL.Image.open(io.BytesIO(source))
|
||||||
img = PIL.Image.open(io.BytesIO(base64.b64decode(file_or_bytes)))
|
|
||||||
except Exception as e:
|
|
||||||
dataBytesIO = io.BytesIO(file_or_bytes)
|
|
||||||
img = PIL.Image.open(dataBytesIO)
|
|
||||||
|
|
||||||
cur_width, cur_height = img.size
|
width, height = image.size
|
||||||
if resize:
|
|
||||||
new_width, new_height = resize
|
scale = None
|
||||||
scale = min(new_height / cur_height, new_width / cur_width)
|
if size != (None, None):
|
||||||
img = img.resize((int(cur_width * scale), int(cur_height * scale)), PIL.Image.ANTIALIAS)
|
new_width, new_height = size
|
||||||
if fill:
|
scale = min(new_height/height, new_width/width)
|
||||||
if resize is not None:
|
elif subsample is not None:
|
||||||
img = make_square(img, resize[0])
|
scale = 1/subsample
|
||||||
|
elif zoom is not None:
|
||||||
|
scale = zoom
|
||||||
|
|
||||||
|
resized_image = image.resize((int(width * scale), int(height * scale)), Image.LANCZOS) if scale is not None else image
|
||||||
|
if fill and scale is not None:
|
||||||
|
resized_image = make_square(resized_image)
|
||||||
|
# encode a PNG formatted version of image into BASE64
|
||||||
with io.BytesIO() as bio:
|
with io.BytesIO() as bio:
|
||||||
img.save(bio, format="PNG")
|
resized_image.save(bio, format="PNG")
|
||||||
del img
|
contents = bio.getvalue()
|
||||||
return bio.getvalue()
|
encoded = base64.b64encode(contents)
|
||||||
|
return encoded
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def random_image():
|
def random_image():
|
||||||
return random.choice(sg.EMOJI_BASE64_LIST)
|
return random.choice(sg.EMOJI_BASE64_LIST)
|
||||||
|
|
|
@ -59,7 +59,7 @@ def convert_to_bytes(file_or_bytes, resize=None):
|
||||||
if resize:
|
if resize:
|
||||||
new_width, new_height = resize
|
new_width, new_height = resize
|
||||||
scale = min(new_height / cur_height, new_width / cur_width)
|
scale = min(new_height / cur_height, new_width / cur_width)
|
||||||
img = img.resize((int(cur_width * scale), int(cur_height * scale)), PIL.Image.ANTIALIAS)
|
img = img.resize((int(cur_width * scale), int(cur_height * scale)), PIL.Image.LANCZOS)
|
||||||
with io.BytesIO() as bio:
|
with io.BytesIO() as bio:
|
||||||
img.save(bio, format="PNG")
|
img.save(bio, format="PNG")
|
||||||
del img
|
del img
|
||||||
|
@ -69,7 +69,7 @@ def convert_to_bytes(file_or_bytes, resize=None):
|
||||||
# def image_file_to_bytes(filename, size):
|
# def image_file_to_bytes(filename, size):
|
||||||
# try:
|
# try:
|
||||||
# image = Image.open(filename)
|
# image = Image.open(filename)
|
||||||
# image.thumbnail(size, Image.ANTIALIAS)
|
# image.thumbnail(size, Image.LANCZOS)
|
||||||
# bio = io.BytesIO() # a binary memory resident stream
|
# bio = io.BytesIO() # a binary memory resident stream
|
||||||
# image.save(bio, format='PNG') # save image as png to it
|
# image.save(bio, format='PNG') # save image as png to it
|
||||||
# imgbytes = bio.getvalue()
|
# imgbytes = bio.getvalue()
|
||||||
|
@ -80,7 +80,7 @@ def convert_to_bytes(file_or_bytes, resize=None):
|
||||||
|
|
||||||
def set_image_to_blank(key):
|
def set_image_to_blank(key):
|
||||||
img = PIL.Image.new('RGB', (100, 100), (255, 255, 255))
|
img = PIL.Image.new('RGB', (100, 100), (255, 255, 255))
|
||||||
img.thumbnail((1, 1), PIL.Image.ANTIALIAS)
|
img.thumbnail((1, 1), PIL.Image.LANCZOS)
|
||||||
bio = io.BytesIO()
|
bio = io.BytesIO()
|
||||||
img.save(bio, format='PNG')
|
img.save(bio, format='PNG')
|
||||||
imgbytes = bio.getvalue()
|
imgbytes = bio.getvalue()
|
||||||
|
|
|
@ -32,14 +32,14 @@ def main():
|
||||||
|
|
||||||
# define layout, show and read the window
|
# define layout, show and read the window
|
||||||
col = [[sg.Text(png_files[0], size=(80, 3), key='-FILENAME-')],
|
col = [[sg.Text(png_files[0], size=(80, 3), key='-FILENAME-')],
|
||||||
[sg.Image(filename=png_files[0], key='-IMAGE-')],
|
[sg.Image(filename=png_files[0], key='-IMAGE-', expand_x=True, expand_y=True)],
|
||||||
[sg.Button('Next', size=(8, 2)), sg.Button('Prev', size=(8, 2)),
|
[sg.Button('Next', size=(8, 2)), sg.Button('Prev', size=(8, 2)),
|
||||||
sg.Text('File 1 of {}'.format(len(png_files)), size=(15, 1), key='-FILENUM-')]]
|
sg.Text('File 1 of {}'.format(len(png_files)), size=(15, 1), key='-FILENUM-')]]
|
||||||
|
|
||||||
col_files = [[sg.Listbox(values=filenames_only, size=(60, 30), key='-LISTBOX-', enable_events=True)],
|
col_files = [[sg.Listbox(values=filenames_only, size=(60, 30), key='-LISTBOX-', enable_events=True)],
|
||||||
[sg.Text('Select a file. Use scrollwheel or arrow keys on keyboard to scroll through files one by one.')]]
|
[sg.Text('Select a file. Use scrollwheel or arrow keys on keyboard to scroll through files one by one.')]]
|
||||||
|
|
||||||
layout = [[sg.Menu(menu)], [sg.Col(col_files), sg.Col(col)]]
|
layout = [[sg.Menu(menu)], [sg.Col(col_files), sg.Col(col, expand_x=True, expand_y=True)]]
|
||||||
|
|
||||||
window = sg.Window('Image Browser', layout, return_keyboard_events=True, use_default_focus=False)
|
window = sg.Window('Image Browser', layout, return_keyboard_events=True, use_default_focus=False)
|
||||||
|
|
||||||
|
|
|
@ -13,16 +13,16 @@ def main():
|
||||||
layout = [[sg.T('Choose 2 files to compare using PyCharm\'s compare utility', font='_ 18')],
|
layout = [[sg.T('Choose 2 files to compare using PyCharm\'s compare utility', font='_ 18')],
|
||||||
[sg.Text('Filename:'), sg.Combo(values=sorted(sg.user_settings_get_entry('-filenames1-', [])),
|
[sg.Text('Filename:'), sg.Combo(values=sorted(sg.user_settings_get_entry('-filenames1-', [])),
|
||||||
default_value=sg.user_settings_get_entry('-last filename chosen1-', None),
|
default_value=sg.user_settings_get_entry('-last filename chosen1-', None),
|
||||||
size=(90,1), auto_size_text=False, k='-COMBO1-'), sg.FileBrowse(), sg.B('Clear History', k='-CLEAR1-')],
|
size=(90,30), auto_size_text=False, k='-COMBO1-'), sg.FileBrowse(), sg.B('Clear History', k='-CLEAR1-')],
|
||||||
[sg.Text('Filename:'),sg.Combo(values=sorted(sg.user_settings_get_entry('-filenames2-', [])),
|
[sg.Text('Filename:'),sg.Combo(values=sorted(sg.user_settings_get_entry('-filenames2-', [])),
|
||||||
default_value=sg.user_settings_get_entry('-last filename chosen2-', None),
|
default_value=sg.user_settings_get_entry('-last filename chosen2-', None),
|
||||||
size=(90,1), auto_size_text=False, k='-COMBO2-'), sg.FileBrowse(), sg.B('Clear History', k='-CLEAR2-')],
|
size=(90,30), auto_size_text=False, k='-COMBO2-'), sg.FileBrowse(), sg.B('Clear History', k='-CLEAR2-')],
|
||||||
[sg.Button('Compare'), sg.Button('Exit'), sg.T('PySimpleGUI ver ' + sg.version.split(' ')[0] + ' tkinter ver ' + sg.tclversion_detailed + 'Python ver ' + sys.version, font='Default 8', pad=(0,0))],
|
[sg.Button('Compare'), sg.Button('Exit'), sg.T('PySimpleGUI ver ' + sg.version.split(' ')[0] + ' tkinter ver ' + sg.tclversion_detailed + 'Python ver ' + sys.version, font='Default 8', pad=(0,0))],
|
||||||
[sg.Text('Note - You must setup the PyCharm information using PySimpleGUI global settings')],
|
[sg.Text('Note - You must setup the PyCharm information using PySimpleGUI global settings')],
|
||||||
[sg.Button('Global Settings')]
|
[sg.Button('Global Settings')]
|
||||||
]
|
]
|
||||||
|
|
||||||
window = sg.Window('Compare 2 files using PyCharm', layout)
|
window = sg.Window('Compare 2 files using PyCharm', layout, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_EXIT)
|
||||||
while True:
|
while True:
|
||||||
event, values = window.read()
|
event, values = window.read()
|
||||||
print(event, values)
|
print(event, values)
|
||||||
|
@ -45,6 +45,10 @@ def main():
|
||||||
window['-COMBO2-'].update(values=[], value='')
|
window['-COMBO2-'].update(values=[], value='')
|
||||||
elif event == 'Global Settings':
|
elif event == 'Global Settings':
|
||||||
sg.main_global_pysimplegui_settings()
|
sg.main_global_pysimplegui_settings()
|
||||||
|
if event == 'Edit Me':
|
||||||
|
sg.execute_editor(__file__)
|
||||||
|
elif event == 'Version':
|
||||||
|
sg.popup_scrolled(__file__, sg.get_versions(), non_blocking=True, keep_on_top=True)
|
||||||
window.close()
|
window.close()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import PySimpleGUI as sg
|
import PySimpleGUI as sg
|
||||||
import re
|
import re
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Demo Program - Realtime output of a shell command in the window using ANSI color codes
|
Demo Program - Realtime output of a shell command in the window using ANSI color codes
|
||||||
Shows how you can run a long-running subprocess and have the output
|
Shows how you can run a long-running subprocess and have the output
|
||||||
be displayed in realtime in the window. The output is assumed to have color codes embedded in it
|
be displayed in realtime in the window. The output is assumed to have color codes embedded in it.
|
||||||
|
|
||||||
|
The commands you enter will be run as shell commands. The output is then shown with the ANSI strings parsed.
|
||||||
|
|
||||||
|
Copyright 2022 PySimpleGUI
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@ -78,50 +80,38 @@ def cut_ansi_string_into_parts(string_with_ansi_codes):
|
||||||
|
|
||||||
for x in range(0, len(tuple_list)):
|
for x in range(0, len(tuple_list)):
|
||||||
if tuple_list[x][0]:
|
if tuple_list[x][0]:
|
||||||
new_tuple_list += [(tuple_list[x][0], tuple_list[x][1], tuple_list[x][2], tuple_list[x][3])]
|
new_tuple_list += [[tuple_list[x][0], tuple_list[x][1], tuple_list[x][2], tuple_list[x][3]]]
|
||||||
|
|
||||||
return new_tuple_list
|
return new_tuple_list
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
layout = [
|
layout = [
|
||||||
[sg.Multiline(size=(110, 30), font='courier 10', background_color='black', text_color='white', key='-MLINE-')],
|
[sg.Multiline(size=(110, 30), font='courier 10', background_color='black', text_color='white', key='-MLINE-', expand_x=True, expand_y=True)],
|
||||||
[sg.T('Promt> '), sg.Input(key='-IN-', focus=True, do_not_clear=False)],
|
[sg.T('Promt> '), sg.Input(key='-IN-', focus=True, do_not_clear=False)],
|
||||||
[sg.Button('Run', bind_return_key=True), sg.Button('Exit')]]
|
[sg.Button('Run', bind_return_key=True), sg.Button('Exit'), sg.Sizegrip()]]
|
||||||
|
|
||||||
window = sg.Window('Realtime Shell Command Output', layout)
|
window = sg.Window('Realtime Shell Command Output', layout, resizable=True)
|
||||||
|
|
||||||
while True: # Event Loop
|
while True: # Event Loop
|
||||||
event, values = window.read()
|
event, values = window.read()
|
||||||
# print(event, values)
|
|
||||||
if event in (sg.WIN_CLOSED, 'Exit'):
|
if event in (sg.WIN_CLOSED, 'Exit'):
|
||||||
break
|
break
|
||||||
elif event == 'Run':
|
elif event == 'Run':
|
||||||
runCommand(cmd=values['-IN-'], window=window)
|
args = values['-IN-'].split(' ')
|
||||||
|
p = sg.execute_command_subprocess(args[0], *args[1:], wait=False, pipe_output=True, merge_stderr_with_stdout=True )
|
||||||
|
lines = sg.execute_get_results(p)
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
if line is None:
|
||||||
|
continue
|
||||||
|
ansi_list = cut_ansi_string_into_parts(line)
|
||||||
|
for ansi_item in ansi_list:
|
||||||
|
if ansi_item[1] == 'Reset':
|
||||||
|
ansi_item[1] = None
|
||||||
|
window['-MLINE-'].update(ansi_item[0] , text_color_for_value=ansi_item[1], background_color_for_value=ansi_item[2], append=True, autoscroll=True)
|
||||||
|
window.refresh()
|
||||||
|
|
||||||
window.close()
|
window.close()
|
||||||
|
|
||||||
|
|
||||||
def runCommand(cmd, timeout=None, window=None):
|
|
||||||
""" run shell command
|
|
||||||
@param cmd: command to execute
|
|
||||||
@param timeout: timeout for command execution
|
|
||||||
@param window: the PySimpleGUI window that the output is going to (needed to do refresh on)
|
|
||||||
@return: (return code from command, command output)
|
|
||||||
"""
|
|
||||||
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
||||||
for line in p.stdout:
|
|
||||||
line = line.decode(errors='replace' if (sys.version_info) < (3, 5) else 'backslashreplace').rstrip()
|
|
||||||
ansi_list = cut_ansi_string_into_parts(line)
|
|
||||||
for ansi_item in ansi_list:
|
|
||||||
if ansi_item[1] == 'Reset':
|
|
||||||
ansi_item[1] = None
|
|
||||||
window['-MLINE-'].update(ansi_item[0] + '\n', text_color_for_value=ansi_item[1], background_color_for_value=ansi_item[2], append=True,
|
|
||||||
autoscroll=True)
|
|
||||||
window.refresh()
|
|
||||||
|
|
||||||
retval = p.wait(timeout)
|
|
||||||
return retval
|
|
||||||
|
|
||||||
|
|
||||||
sg.theme('Dark Blue 3')
|
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -21,7 +21,8 @@ def main():
|
||||||
if event in (sg.WIN_CLOSED, 'Exit'):
|
if event in (sg.WIN_CLOSED, 'Exit'):
|
||||||
break
|
break
|
||||||
elif event == 'Run':
|
elif event == 'Run':
|
||||||
sp = sg.execute_command_subprocess(values['-IN-'], pipe_output=True, wait=False)
|
cmd_list = values['-IN-'].split(' ')
|
||||||
|
sp = sg.execute_command_subprocess(cmd_list[0], *cmd_list[1:], pipe_output=True, wait=False)
|
||||||
results = sg.execute_get_results(sp, timeout=1)
|
results = sg.execute_get_results(sp, timeout=1)
|
||||||
print(results[0])
|
print(results[0])
|
||||||
|
|
||||||
|
|
|
@ -2,18 +2,17 @@ import PySimpleGUI as sg, random
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from typing import List, Any, Union, Tuple, Dict
|
from typing import List, Any, Union, Tuple, Dict
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Sudoku Puzzle Demo
|
Sudoku Puzzle Demo
|
||||||
|
|
||||||
How to easily generate a GUI for a Sudoku puzzle.
|
How to easily generate a GUI for a Sudoku puzzle.
|
||||||
The Window definition and creation is a single line of code.
|
The Window definition and creation is a single line of code.
|
||||||
|
|
||||||
Code to generate a playable puzzle was supplied from:
|
Code to generate a playable puzzle was supplied from:
|
||||||
https://github.com/MorvanZhou/sudoku
|
https://github.com/MorvanZhou/sudoku
|
||||||
|
|
||||||
Copyright 2020 PySimpleGUI.com
|
Copyright 2020 PySimpleGUI.com
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,7 +26,7 @@ def generate_sudoku(mask_rate):
|
||||||
"""
|
"""
|
||||||
while True:
|
while True:
|
||||||
n = 9
|
n = 9
|
||||||
solution = np.zeros((n, n), np.int)
|
solution = np.zeros((n, n), np.int_)
|
||||||
rg = np.arange(1, n + 1)
|
rg = np.arange(1, n + 1)
|
||||||
solution[0, :] = np.random.choice(rg, n, replace=False)
|
solution[0, :] = np.random.choice(rg, n, replace=False)
|
||||||
try:
|
try:
|
||||||
|
@ -36,10 +35,11 @@ def generate_sudoku(mask_rate):
|
||||||
col_rest = np.setdiff1d(rg, solution[:r, c])
|
col_rest = np.setdiff1d(rg, solution[:r, c])
|
||||||
row_rest = np.setdiff1d(rg, solution[r, :c])
|
row_rest = np.setdiff1d(rg, solution[r, :c])
|
||||||
avb1 = np.intersect1d(col_rest, row_rest)
|
avb1 = np.intersect1d(col_rest, row_rest)
|
||||||
sub_r, sub_c = r//3, c//3
|
sub_r, sub_c = r // 3, c // 3
|
||||||
avb2 = np.setdiff1d(np.arange(0, n+1), solution[sub_r*3:(sub_r+1)*3, sub_c*3:(sub_c+1)*3].ravel())
|
avb2 = np.setdiff1d(np.arange(0, n + 1), solution[sub_r * 3:(sub_r + 1) * 3, sub_c * 3:(sub_c + 1) * 3].ravel())
|
||||||
avb = np.intersect1d(avb1, avb2)
|
avb = np.intersect1d(avb1, avb2)
|
||||||
solution[r, c] = np.random.choice(avb, size=1)
|
# solution[r, c] = np.random.choice(avb, size=1)
|
||||||
|
solution[r, c] = np.random.choice(avb, size=1)[0]
|
||||||
break
|
break
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
@ -48,7 +48,6 @@ def generate_sudoku(mask_rate):
|
||||||
return puzzle, solution
|
return puzzle, solution
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def check_progress(window, solution):
|
def check_progress(window, solution):
|
||||||
"""
|
"""
|
||||||
Gives you a visual hint on your progress.
|
Gives you a visual hint on your progress.
|
||||||
|
@ -65,38 +64,23 @@ def check_progress(window, solution):
|
||||||
solved = True
|
solved = True
|
||||||
for r, row in enumerate(solution):
|
for r, row in enumerate(solution):
|
||||||
for c, col in enumerate(row):
|
for c, col in enumerate(row):
|
||||||
value = window[r,c].get()
|
value = window[r, c].get()
|
||||||
if value:
|
if value:
|
||||||
try:
|
try:
|
||||||
value = int(value)
|
value = int(value)
|
||||||
except:
|
except:
|
||||||
value = 0
|
value = 0
|
||||||
if value != solution[r][c]:
|
if value != solution[r][c]:
|
||||||
window[r,c].update(background_color='red')
|
window[r, c].update(background_color='red')
|
||||||
solved = False
|
solved = False
|
||||||
else:
|
else:
|
||||||
window[r,c].update(background_color=sg.theme_input_background_color())
|
window[r, c].update(background_color=sg.theme_input_background_color())
|
||||||
else:
|
else:
|
||||||
solved = False
|
solved = False
|
||||||
window[r, c].update(background_color='yellow')
|
window[r, c].update(background_color='yellow')
|
||||||
return solved
|
return solved
|
||||||
|
|
||||||
|
|
||||||
def create_and_show_puzzle(window):
|
|
||||||
# create and display a puzzle by updating the Input elements
|
|
||||||
rate = DEFAULT_MASK_RATE
|
|
||||||
if window['-RATE-'].get():
|
|
||||||
try:
|
|
||||||
rate = float(window['-RATE-'].get())
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
puzzle, solution = generate_sudoku(mask_rate=rate)
|
|
||||||
for r, row in enumerate(puzzle):
|
|
||||||
for c, col in enumerate(row):
|
|
||||||
window[r, c].update(puzzle[r][c] if puzzle[r][c] else '', background_color=sg.theme_input_background_color())
|
|
||||||
return puzzle, solution
|
|
||||||
|
|
||||||
|
|
||||||
def main(mask_rate=0.7):
|
def main(mask_rate=0.7):
|
||||||
""""
|
""""
|
||||||
The Main GUI - It does it all.
|
The Main GUI - It does it all.
|
||||||
|
@ -105,8 +89,19 @@ def main(mask_rate=0.7):
|
||||||
addressing of the individual squares is via a key that's a tuple (0,0) to (8,8)
|
addressing of the individual squares is via a key that's a tuple (0,0) to (8,8)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def create_and_show_puzzle():
|
||||||
|
# create and display a puzzle by updating the Input elements
|
||||||
|
rate = mask_rate
|
||||||
|
if window['-RATE-'].get():
|
||||||
|
try:
|
||||||
|
rate = float(window['-RATE-'].get())
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
puzzle, solution = generate_sudoku(mask_rate=rate)
|
||||||
|
for r, row in enumerate(puzzle):
|
||||||
|
for c, col in enumerate(row):
|
||||||
|
window[r, c].update(puzzle[r][c] if puzzle[r][c] else '', background_color=sg.theme_input_background_color())
|
||||||
|
return puzzle, solution
|
||||||
|
|
||||||
# It's 1 line of code to make a Sudoku board. If you don't like it, then replace it.
|
# It's 1 line of code to make a Sudoku board. If you don't like it, then replace it.
|
||||||
# Dude (Dudette), it's 1-line of code. If you don't like the board, write a line of code.
|
# Dude (Dudette), it's 1-line of code. If you don't like the board, write a line of code.
|
||||||
|
@ -114,16 +109,18 @@ def main(mask_rate=0.7):
|
||||||
# Get an input element for a position using: window[row, col]
|
# Get an input element for a position using: window[row, col]
|
||||||
# To get a better understanding, take it apart. Spread it out. You'll learn in the process.
|
# To get a better understanding, take it apart. Spread it out. You'll learn in the process.
|
||||||
window = sg.Window('Sudoku',
|
window = sg.Window('Sudoku',
|
||||||
[[sg.Frame('', [[sg.I(random.randint(1,9), justification='r', size=(3,1),enable_events=True, key=(fr*3+r,fc*3+c)) for c in range(3)] for r in range(3)]) for fc in range(3)] for fr in range(3)] +
|
[[sg.Frame('', [[sg.I(random.randint(1, 9), justification='r', size=(3, 1), key=(fr * 3 + r, fc * 3 + c)) for c in range(3)] for r in range(3)]) for fc in
|
||||||
[[sg.B('Solve'), sg.B('Check'), sg.B('Hint'), sg.B('New Game'), sg.T('Mask rate (0-1)'), sg.In(str(mask_rate), size=(3,1),key='-RATE-')],], finalize=True)
|
range(3)] for fr in range(3)] +
|
||||||
|
[[sg.B('Solve'), sg.B('Check'), sg.B('Hint'), sg.B('New Game')], [sg.T('Mask rate (0-1)'), sg.In(str(mask_rate), size=(3, 1), key='-RATE-')], ],
|
||||||
|
finalize=True)
|
||||||
|
|
||||||
# create and display a puzzle by updating the Input elements
|
# create and display a puzzle by updating the Input elements
|
||||||
|
|
||||||
puzzle, solution = create_and_show_puzzle(window)
|
puzzle, solution = create_and_show_puzzle()
|
||||||
check_showing = False
|
|
||||||
while True: # The Event Loop
|
while True: # The Event Loop
|
||||||
event, values = window.read()
|
event, values = window.read()
|
||||||
if event == sg.WIN_CLOSED:
|
if event is None:
|
||||||
break
|
break
|
||||||
|
|
||||||
if event == 'Solve':
|
if event == 'Solve':
|
||||||
|
@ -131,7 +128,6 @@ def main(mask_rate=0.7):
|
||||||
for c, col in enumerate(row):
|
for c, col in enumerate(row):
|
||||||
window[r, c].update(solution[r][c], background_color=sg.theme_input_background_color())
|
window[r, c].update(solution[r][c], background_color=sg.theme_input_background_color())
|
||||||
elif event == 'Check':
|
elif event == 'Check':
|
||||||
check_showing = True
|
|
||||||
solved = check_progress(window, solution)
|
solved = check_progress(window, solution)
|
||||||
if solved:
|
if solved:
|
||||||
sg.popup('Solved! You have solved the puzzle correctly.')
|
sg.popup('Solved! You have solved the puzzle correctly.')
|
||||||
|
@ -140,17 +136,14 @@ def main(mask_rate=0.7):
|
||||||
try:
|
try:
|
||||||
elem.update(solution[elem.Key[0]][elem.Key[1]], background_color=sg.theme_input_background_color())
|
elem.update(solution[elem.Key[0]][elem.Key[1]], background_color=sg.theme_input_background_color())
|
||||||
except:
|
except:
|
||||||
pass # Likely because an input element didn't have focus
|
pass # Likely because an input element didn't have focus
|
||||||
elif event == 'New Game':
|
elif event == 'New Game':
|
||||||
puzzle, solution = create_and_show_puzzle(window)
|
puzzle, solution = create_and_show_puzzle()
|
||||||
elif check_showing: # an input was changed, so clear any background colors from prior hints
|
|
||||||
check_showing = False
|
|
||||||
for r, row in enumerate(solution):
|
|
||||||
for c, col in enumerate(row):
|
|
||||||
window[r, c].update(background_color=sg.theme_input_background_color())
|
|
||||||
window.close()
|
window.close()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
DEFAULT_MASK_RATE = 0.7 # % Of cells to hide
|
if __name__ == "__main__":
|
||||||
main(DEFAULT_MASK_RATE)
|
mask_rate = 0.7 # % Of cells to hide
|
||||||
|
main(mask_rate)
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ def resize_base64_image(image64, size):
|
||||||
"""
|
"""
|
||||||
image_file = io.BytesIO(base64.b64decode(image64))
|
image_file = io.BytesIO(base64.b64decode(image64))
|
||||||
img = Image.open(image_file)
|
img = Image.open(image_file)
|
||||||
img.thumbnail(size, Image.ANTIALIAS)
|
img.thumbnail(size, Image.LANCZOS)
|
||||||
bio = io.BytesIO()
|
bio = io.BytesIO()
|
||||||
img.save(bio, format='PNG')
|
img.save(bio, format='PNG')
|
||||||
imgbytes = bio.getvalue()
|
imgbytes = bio.getvalue()
|
||||||
|
|
161
DemoPrograms/Demo_Table_CSV_Display.pyw
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
import PySimpleGUI as sg
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
import operator
|
||||||
|
|
||||||
|
"""
|
||||||
|
Demo - Simple CSV Table Display
|
||||||
|
|
||||||
|
Enables you to easily filter and sort tables that are in a CSV file format
|
||||||
|
|
||||||
|
Choose your CSV file and then a table will be displayed.
|
||||||
|
Clicking on a heading will sort on that column if no value is entered for the filter.
|
||||||
|
If a filter value is entered and then a heading is clicked, then only rows matchines the filter in that column as are displayed
|
||||||
|
The filtering is not case sensative so no need to worry about exact matches
|
||||||
|
Use the checkbox to specify ascending or descending sorting
|
||||||
|
|
||||||
|
The first row in your table needs to be the Column Names
|
||||||
|
|
||||||
|
Copyright 2022 PySimpleGUI
|
||||||
|
"""
|
||||||
|
|
||||||
|
sg.theme('Dark gray 13')
|
||||||
|
|
||||||
|
CSV_FILE = sg.popup_get_file('CSV File to Display', file_types=(("CSV Files", "*.csv"),), initial_folder=os.path.dirname(__file__), history=True)
|
||||||
|
if CSV_FILE is None:
|
||||||
|
sg.popup_error('Canceling')
|
||||||
|
exit()
|
||||||
|
|
||||||
|
csv.field_size_limit(2147483647) # enables huge tables
|
||||||
|
|
||||||
|
def sort_table(table, cols, descending=False):
|
||||||
|
""" sort a table by multiple columns
|
||||||
|
table: a list of lists (or tuple of tuples) where each inner list
|
||||||
|
represents a row
|
||||||
|
cols: a list (or tuple) specifying the column numbers to sort by
|
||||||
|
e.g. (1,0) would sort by column 1, then by column 0
|
||||||
|
"""
|
||||||
|
|
||||||
|
for col in reversed(cols):
|
||||||
|
try:
|
||||||
|
table = sorted(table, key=operator.itemgetter(col), reverse=descending)
|
||||||
|
except Exception as e:
|
||||||
|
sg.popup_error('Error in sort_table', 'Exception in sort_table', e)
|
||||||
|
return table
|
||||||
|
|
||||||
|
|
||||||
|
def read_csv_file(filename):
|
||||||
|
data = []
|
||||||
|
header_list = []
|
||||||
|
if filename is not None:
|
||||||
|
try:
|
||||||
|
with open(filename, encoding='UTF-16') as infile:
|
||||||
|
reader = csv.reader(infile,delimiter='\t')
|
||||||
|
# reader = fix_nulls(filename)
|
||||||
|
header_list = next(reader)
|
||||||
|
try:
|
||||||
|
data = list(reader) # read everything else into a list of rows
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
sg.popup_error('Error reading file', e)
|
||||||
|
return None, None
|
||||||
|
except:
|
||||||
|
with open(filename, encoding='utf-8') as infile:
|
||||||
|
reader = csv.reader(infile, delimiter=',')
|
||||||
|
# reader = fix_nulls(filename)
|
||||||
|
header_list = next(reader)
|
||||||
|
try:
|
||||||
|
data = list(reader) # read everything else into a list of rows
|
||||||
|
except Exception as e:
|
||||||
|
with open(filename) as infile:
|
||||||
|
reader = csv.reader(infile, delimiter=',')
|
||||||
|
# reader = fix_nulls(filename)
|
||||||
|
header_list = next(reader)
|
||||||
|
try:
|
||||||
|
data = list(reader) # read everything else into a list of rows
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
sg.popup_error('Error reading file', e)
|
||||||
|
return None, None
|
||||||
|
return data, header_list
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
data, header_list = read_csv_file(CSV_FILE)
|
||||||
|
|
||||||
|
sg.popup_quick_message('Building your main window.... one moment....', background_color='#1c1e23', text_color='white', keep_on_top=True, font='_ 30')
|
||||||
|
|
||||||
|
# ------ Window Layout ------
|
||||||
|
layout = [ [sg.Text('Click a heading to sort on that column or enter a filter and click a heading to search for matches in that column')],
|
||||||
|
[sg.Text(f'{len(data)} Records in table', font='_ 18')],
|
||||||
|
[sg.Text(k='-RECORDS SHOWN-', font='_ 18')],
|
||||||
|
[sg.Text(k='-SELECTED-')],
|
||||||
|
[sg.T('Filter:'), sg.Input(k='-FILTER-', focus=True, tooltip='Not case sensative\nEnter value and click on a col header'),
|
||||||
|
sg.B('Reset Table', tooltip='Resets entire table to your original data'),
|
||||||
|
sg.Checkbox('Sort Descending', k='-DESCENDING-'), sg.Checkbox('Filter Out (exclude)', k='-FILTER OUT-', tooltip='Check to remove matching entries when filtering a column'), sg.Push()],
|
||||||
|
[sg.Table(values=data, headings=header_list, max_col_width=25,
|
||||||
|
auto_size_columns=True, display_row_numbers=True, vertical_scroll_only=True,
|
||||||
|
justification='right', num_rows=50,
|
||||||
|
key='-TABLE-', selected_row_colors='red on yellow', enable_events=True,
|
||||||
|
expand_x=True, expand_y=True,
|
||||||
|
enable_click_events=True)],
|
||||||
|
[sg.Sizegrip()]]
|
||||||
|
|
||||||
|
# ------ Create Window ------
|
||||||
|
window = sg.Window('CSV Table Display', layout, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_EXIT, resizable=True, finalize=True)
|
||||||
|
window.bind("<Control_L><End>", '-CONTROL END-')
|
||||||
|
window.bind("<End>", '-CONTROL END-')
|
||||||
|
window.bind("<Control_L><Home>", '-CONTROL HOME-')
|
||||||
|
window.bind("<Home>", '-CONTROL HOME-')
|
||||||
|
original_data = data # save a copy of the data
|
||||||
|
# ------ Event Loop ------
|
||||||
|
while True:
|
||||||
|
event, values = window.read()
|
||||||
|
# print(event, values)
|
||||||
|
if event in (sg.WIN_CLOSED, 'Exit'):
|
||||||
|
break
|
||||||
|
if values['-TABLE-']: # Show how many rows are slected
|
||||||
|
window['-SELECTED-'].update(f'{len(values["-TABLE-"])} rows selected')
|
||||||
|
else:
|
||||||
|
window['-SELECTED-'].update('')
|
||||||
|
if event[0] == '-TABLE-':
|
||||||
|
# if isinstance(event, tuple):
|
||||||
|
filter_value = values['-FILTER-']
|
||||||
|
# TABLE CLICKED Event has value in format ('-TABLE=', '+CLICKED+', (row,col))
|
||||||
|
if event[0] == '-TABLE-':
|
||||||
|
if event[2][0] == -1 and event[2][1] != -1: # Header was clicked and wasn't the "row" column
|
||||||
|
col_num_clicked = event[2][1]
|
||||||
|
# if there's a filter, first filter based on the column clicked
|
||||||
|
if filter_value not in (None, ''):
|
||||||
|
filter_out = values['-FILTER OUT-'] # get bool filter out setting
|
||||||
|
new_data = []
|
||||||
|
for line in data:
|
||||||
|
if not filter_out and (filter_value.lower() in line[col_num_clicked].lower()):
|
||||||
|
new_data.append(line)
|
||||||
|
elif filter_out and (filter_value.lower() not in line[col_num_clicked].lower()):
|
||||||
|
new_data.append(line)
|
||||||
|
data = new_data
|
||||||
|
new_table = sort_table(data, (col_num_clicked, 0), values['-DESCENDING-'])
|
||||||
|
window['-TABLE-'].update(new_table)
|
||||||
|
data = new_table
|
||||||
|
window['-RECORDS SHOWN-'].update(f'{len(new_table)} Records shown')
|
||||||
|
window['-FILTER-'].update('') # once used, clear the filter
|
||||||
|
window['-FILTER OUT-'].update(False) # Also clear the filter out flag
|
||||||
|
elif event == 'Reset Table':
|
||||||
|
data = original_data
|
||||||
|
window['-TABLE-'].update(data)
|
||||||
|
window['-RECORDS SHOWN-'].update(f'{len(data)} Records shown')
|
||||||
|
elif event == '-CONTROL END-':
|
||||||
|
window['-TABLE-'].set_vscroll_position(100)
|
||||||
|
elif event == '-CONTROL HOME-':
|
||||||
|
window['-TABLE-'].set_vscroll_position(0)
|
||||||
|
elif event == 'Edit Me':
|
||||||
|
sg.execute_editor(__file__)
|
||||||
|
elif event == 'Version':
|
||||||
|
sg.popup_scrolled(__file__, sg.get_versions(), location=window.current_location(), keep_on_top=True, non_blocking=True)
|
||||||
|
window.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
85
DemoPrograms/Demo_Table_Checkmark.py
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
import PySimpleGUI as sg
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
|
"""
|
||||||
|
Demo Program - Table with checkboxes
|
||||||
|
|
||||||
|
This clever solution was sugged by GitHub user robochopbg.
|
||||||
|
The beauty of the simplicity is that the checkbox is simply another column in the table. When the checkbox changes
|
||||||
|
state, then the data in the table is changed and the table is updated in the Table element.
|
||||||
|
A big thank you again to user robochopbg!
|
||||||
|
|
||||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡀⠀⠀
|
||||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣿⣿⠆⠀
|
||||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⡿⠁⠀⠀
|
||||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⠟⠀⠀⠀⠀
|
||||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿⡿⠃⠀⠀⠀⠀⠀
|
||||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣾⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀
|
||||||
|
⠀⢀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀
|
||||||
|
⠀⠺⣿⣷⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||||
|
⠀⠀⠈⠻⣿⣿⣦⣄⠀⠀⠀⠀⠀⠀⣠⣿⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||||
|
⠀⠀⠀⠀⠈⠻⣿⣿⣷⣤⡀⠀⠀⣰⣿⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||||
|
⠀⠀⠀⠀⠀⠀⠙⢿⣿⣿⣿⣦⣼⣿⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣿⣿⣿⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠻⣿⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢿⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||||
|
|
||||||
|
Copyright 2023 PySimpleGUI
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Characters used for the checked and unchecked checkboxes. Feel free to change
|
||||||
|
BLANK_BOX = '☐'
|
||||||
|
CHECKED_BOX = '☑'
|
||||||
|
|
||||||
|
# ------ Some functions to help generate data for the table ------
|
||||||
|
def word():
|
||||||
|
return ''.join(random.choice(string.ascii_lowercase) for i in range(10))
|
||||||
|
def number(max_val=1000):
|
||||||
|
return random.randint(0, max_val)
|
||||||
|
|
||||||
|
def make_table(num_rows, num_cols):
|
||||||
|
data = [[j for j in range(num_cols)] for i in range(num_rows)]
|
||||||
|
data[0] = [word() for __ in range(num_cols)]
|
||||||
|
for i in range(1, num_rows):
|
||||||
|
data[i] = [BLANK_BOX if random.randint(0,2) % 2 else CHECKED_BOX] + [word(), *[number() for i in range(num_cols - 1)]]
|
||||||
|
return data
|
||||||
|
|
||||||
|
# ------ Make the Table Data ------
|
||||||
|
data = make_table(num_rows=15, num_cols=6)
|
||||||
|
headings = [str(data[0][x])+' ..' for x in range(len(data[0]))]
|
||||||
|
headings[0] = 'Checkbox'
|
||||||
|
# The selected rows is stored in a set
|
||||||
|
selected = {i for i, row in enumerate(data[1:][:]) if row[0] == CHECKED_BOX}
|
||||||
|
|
||||||
|
# ------ Window Layout ------
|
||||||
|
layout = [[sg.Table(values=data[1:][:], headings=headings, max_col_width=25, auto_size_columns=False, col_widths=[10, 10, 20, 20 ,30, 5],
|
||||||
|
display_row_numbers=True, justification='center', num_rows=20, key='-TABLE-', selected_row_colors='red on yellow',
|
||||||
|
expand_x=False, expand_y=True, vertical_scroll_only=False, enable_click_events=True, font='_ 14'),
|
||||||
|
sg.Sizegrip()]]
|
||||||
|
|
||||||
|
# ------ Create Window ------
|
||||||
|
window = sg.Window('Table with Checkbox', layout, resizable=True, finalize=True)
|
||||||
|
|
||||||
|
# Highlight the rows (select) that have checkboxes checked
|
||||||
|
window['-TABLE-'].update(values=data[1:][:], select_rows=list(selected))
|
||||||
|
|
||||||
|
# ------ Event Loop ------
|
||||||
|
while True:
|
||||||
|
event, values = window.read()
|
||||||
|
if event == sg.WIN_CLOSED:
|
||||||
|
break
|
||||||
|
elif event[0] == '-TABLE-' and event[2][0] not in (None, -1): # if clicked a data row rather than header or outside table
|
||||||
|
row = event[2][0]+1
|
||||||
|
if data[row][0] == CHECKED_BOX: # Going from Checked to Unchecked
|
||||||
|
selected.remove(row-1)
|
||||||
|
data[row][0] = BLANK_BOX
|
||||||
|
else: # Going from Unchecked to Checked
|
||||||
|
selected.add(row-1)
|
||||||
|
data[row ][0] = CHECKED_BOX
|
||||||
|
window['-TABLE-'].update(values=data[1:][:], select_rows=list(selected)) # Update the table and the selected rows
|
||||||
|
|
||||||
|
window.close()
|
||||||
|
|