mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-09-14 10:32:10 +02:00
Compare commits
1180 Commits
Author | SHA1 | Date | |
---|---|---|---|
6040337bb4
|
|||
1ced72388b
|
|||
4751a948e7
|
|||
3939c2263a
|
|||
b0ba4595ae
|
|||
38b1057a4c
|
|||
af5d785ff2
|
|||
655d334714
|
|||
eee72959e6
|
|||
89b8977f7d
|
|||
9ede2e1ccc
|
|||
03d1bc0f4c
|
|||
cde9f66111
|
|||
8149ed50e1
|
|||
24f5075116
|
|||
2a636245b4
|
|||
3f4844f6f6
|
|||
29308de3ee
|
|||
0d3d744d94
|
|||
d38e525fed
|
|||
e2ac38ed0b
|
|||
fa534f9eb3
|
|||
ec7827df24
|
|||
b915488651
|
|||
bf9a0226be
|
|||
68582f6973
|
|||
03f3d4d450
|
|||
115428ec50
|
|||
5f60852fbb
|
|||
a7a5723c4b
|
|||
17e42df42d
|
|||
7e692460d8
|
|||
f41a5946e4
|
|||
29fee155d7
|
|||
32728fc20a
|
|||
394cb80022
|
|||
21e49505d1
|
|||
e0025e02d1
|
|||
4a9590c1d9
|
|||
7de0c50387
|
|||
ccb87351c1
|
|||
273e7266eb
|
|||
427975e5ce
|
|||
adb304b6a2
|
|||
3a89a28f8b
|
|||
cff93dcc97
|
|||
dfde38ea3b
|
|||
a8e7f065cf
|
|||
5ebfc67e48
|
|||
57b03baad9
|
|||
c91f1d0e5e
|
|||
6a421292b3
|
|||
ceae748503
|
|||
008de87e55
|
|||
e7479ef9e3
|
|||
13f8f12ac8
|
|||
fd634379d7
|
|||
e854315a81
|
|||
86136d7692
|
|||
db251bfdfd
|
|||
9d39f26d54
|
|||
9cd813c02c
|
|||
8e1f87e062
|
|||
dda954285c
|
|||
ed4f7b6b72
|
|||
7239dcf4d2
|
|||
901cca268e
|
|||
b909341988
|
|||
3e607ae0fe
|
|||
587060f73c
|
|||
3ea6f6ac18
|
|||
c162761464
|
|||
994f9635ef
|
|||
ab8752845d
|
|||
a94ee2fe4b
|
|||
763c999b09
|
|||
b18cd2658c
|
|||
d9782f554f
|
|||
f1d9663709
|
|||
bcf77052a5
|
|||
85d15b32e9 | |||
3d3b695c85 | |||
88b7cac298 | |||
c7efa631e1 | |||
aeb90cbb1a | |||
ae1c59847f | |||
651d9be57c | |||
eeb32db6fb | |||
daa0780644 | |||
8502f9105f | |||
16ced3d827 | |||
1c16187346 | |||
2fe058d9cb | |||
cefdadd53a | |||
c21c10df63 | |||
b4d359d30c | |||
1e8c62ac25 | |||
c578f36644 | |||
c973d0cff4 | |||
0c3d9ae46a | |||
a834e8b6a2 | |||
9f5580d983 | |||
e94e3cecf8 | |||
1991f7f50f | |||
9eb4e623e7 | |||
ad28d2279f | |||
1e3de31fc3 | |||
f85bd41b96 | |||
563124b68c | |||
63de08c635 | |||
8a0a215443 | |||
f1b7cd633e | |||
458eeeccda | |||
464e758b94 | |||
4c61047e9b | |||
1bf9e7fb56 | |||
ca69554f37 | |||
cf7029037e | |||
418388b0ab | |||
a0f8689d4f | |||
95007bdd26 | |||
ab91540deb | |||
b2ebb984f8 | |||
f7e9ad74d1 | |||
d48da3d51c | |||
76d22554c5 | |||
6eaafd883b | |||
5961a80b23 | |||
f41c6fe533 | |||
65b8efe13c | |||
89529f9c96 | |||
e90f6ebc63 | |||
5888d540a6 | |||
ae8b740600 | |||
ab4e2f5bda | |||
1091b6d232 | |||
fc89744238 | |||
34e049a002 | |||
980bf2c307 | |||
762aea1e20 | |||
c1aefc7163 | |||
9480ba26e0 | |||
c2c9160ed9 | |||
175b067a17 | |||
9d8656ca20 | |||
0863001c80 | |||
0ee22a30ad | |||
447697ba45 | |||
aea77ff909 | |||
af5da76f72 | |||
a369c65451 | |||
318f65f187 | |||
1cd60e831c | |||
b988959eaa | |||
1eb1f9848a | |||
7f6cc0da01 | |||
19fcb69525 | |||
22cef0a44c | |||
2459d31bff | |||
19f104239a | |||
bd0be65038 | |||
bbb7907e54 | |||
a6963a18d4 | |||
92716ea3e0 | |||
aec4c1feea | |||
d505b3305b | |||
a34a02e14d | |||
26d2d7a51e | |||
c2f7e52d13 | |||
de68d8934d | |||
4fdf7fc958 | |||
42a5e72f19 | |||
f7359ebc8a | |||
f395ac53dc | |||
1113e0b559 | |||
5e3bd31862 | |||
11d978dad1 | |||
f7961024d7 | |||
72973a8707 | |||
68254f48d5 | |||
eac4f30c50 | |||
25680fa980 | |||
ff5e1da14d | |||
95afff7879 | |||
50bd526025 | |||
108a0fefc3 | |||
dd8c5d27be | |||
b2937bc776 | |||
4d8e764211 | |||
544b8664fd | |||
d0610865bd | |||
ebc0b51590 | |||
4487f1169e | |||
85559b6083 | |||
1056273c57 | |||
61af2ebc8b | |||
9121c86656 | |||
1ccefe853a | |||
aca438b837 | |||
7210c29cd8 | |||
26d90c0c9b | |||
a03b222a95 | |||
7944a24d3c | |||
cc8459c759 | |||
10074ff92c | |||
173f25bebc | |||
31680fc4ae | |||
e937d43614 | |||
20e29a7975 | |||
ef815dabce | |||
1fb133e6b8 | |||
50b58cd6a6 | |||
01485d7ef9 | |||
b17c6a5ac7 | |||
d2ed2b4a00 | |||
710a7524a1 | |||
2be46464d6 | |||
8d536a6734 | |||
250d502238 | |||
e8de7266d0 | |||
9414f372d7 | |||
b0f9de67cf | |||
9b082e114e | |||
816a5334ac | |||
15a4e10da9 | |||
01b9302b0c | |||
442126a11a | |||
a9c140c0fc | |||
97ad7a3e68 | |||
7d737eefb6 | |||
4ac05b38d3 | |||
651bbbb672 | |||
52da4d8687 | |||
36063ae76a | |||
2fcec2d2cd | |||
762a7fdfb7 | |||
cd7aeaeed2 | |||
6f414d312c | |||
1b5304efb7 | |||
d59375308f | |||
8c9509a906 | |||
fb86d8f3a8 | |||
50e909cb3d | |||
2f54edf7e7 | |||
c251603e1e | |||
4476edb6c3 | |||
28fc67660f | |||
6e8b5a5ce5 | |||
e53681416f | |||
acb5e184e8 | |||
bdbafb3e5c | |||
ac70cf87c6 | |||
93de835ab4 | |||
2ea38b88ce | |||
2c4f2be57d | |||
fa4beea425 | |||
7a976edc82 | |||
bb22c35221 | |||
ff3dc59016 | |||
2e4dd3df3e | |||
b82e5d33f9 | |||
65d56b336b | |||
7836d61173 | |||
898437720b | |||
d9a80d1085 | |||
ab3d8b0ae2 | |||
b865074c32 | |||
2b8ca77c0d | |||
fa8b4e1e7f | |||
a310c5bcc1 | |||
b5dccd6b91 | |||
c2bcb39b38 | |||
4515add0a0 | |||
5b6aaec48c | |||
810e56ca31 | |||
5bcc8ac2e0 | |||
f5bfb35867 | |||
9088b8cd07 | |||
a7d90dc708 | |||
5968b57a01 | |||
94946a9ed6 | |||
561aec5ef0 | |||
e31696d843 | |||
8b33fd2002 | |||
064e43750f | |||
29d2f4f681 | |||
c11b40b94a | |||
e874e1d798 | |||
01244ec632 | |||
671657e2b0 | |||
dff7278e2e | |||
0881328636 | |||
a951c3a7c9 | |||
b25fae54fe | |||
a87bc4609e | |||
3e68026949 | |||
78d6d285cd | |||
72e3596a3c | |||
643ebcaab4 | |||
52ef6cd95a | |||
4aec2f3260 | |||
73549515eb | |||
d83d2660cf | |||
8de913172c | |||
4acfd64cff | |||
b56f90ed52 | |||
1375630727 | |||
668825bc01 | |||
90414ae579 | |||
41c8caa2a4 | |||
15305ecabe | |||
50090effd4 | |||
6dffdcd1ed | |||
db9daf2714 | |||
502ac4ebc1 | |||
3f44f3bab4 | |||
746eba185b | |||
82dec92510 | |||
2edc93df47 | |||
c5a6738199 | |||
dfd578165b | |||
285d400f69 | |||
47a3444ace | |||
0ec227da52 | |||
f9cf582306 | |||
b0883229bf | |||
31b5d9a4c0 | |||
6841a78556 | |||
9d2089a8ce | |||
6d6bb79199 | |||
937c8e22c4 | |||
34c8d44dfe | |||
2dbf778e56 | |||
873fe2b393 | |||
0c53bc6f32 | |||
fa407e150b | |||
4e52102c5c | |||
0355a5c646 | |||
377d9c3554 | |||
20b1b3c895 | |||
41bbe7c51b | |||
27fa6aefd3 | |||
a63c3232da | |||
146908a115 | |||
48b0f35fee | |||
6c435ebe26 | |||
b8d0b721a2 | |||
a6d5957f46 | |||
9e5f676e23 | |||
414f3a1f9d | |||
fedf9c60ee | |||
b6385d9622 | |||
76d25a712d | |||
2c6d935273 | |||
830d98a101 | |||
d7378bd75a | |||
0f41cb9dbc | |||
77bc922d93 | |||
c91b635132 | |||
e5521de34a | |||
7c0f8d0f24 | |||
f60d5f650f | |||
ca67f2fe0a | |||
16cce8be1d | |||
770619d948 | |||
cfedb7d6b1 | |||
738557b3a2 | |||
38b01deec1 | |||
1a31e69ec9 | |||
e065983c95 | |||
30a169171a | |||
8d1900362e | |||
e154189de1 | |||
b0f147de24 | |||
979b3548db | |||
05d6c578b3 | |||
a117559063 | |||
f87c649b09 | |||
6504dc9184 | |||
25a8ddffd4 | |||
fa0f9b89cf | |||
4d00a67891 | |||
bd2c43e1f4 | |||
c7279eaa34 | |||
fd523e552c | |||
cb877b8b23 | |||
ed1bee8b89 | |||
a8e1492056 | |||
5587216c01 | |||
86569261ad | |||
4a9049c7aa | |||
75d60a8182 | |||
14d4dc2ed9 | |||
fd0e1740a5 | |||
70ca890bef | |||
b9318dfd8e | |||
995642a719 | |||
d14de4ac9e | |||
b7f325a241 | |||
27c55718c2 | |||
421ff0654b | |||
ed947458f9 | |||
9cdb20ba84 | |||
d8774b735f | |||
adcb42695f | |||
dd77b5bcbb | |||
d2445be155 | |||
10254c8af7 | |||
d7e830badf | |||
b445a3a9b8 | |||
97f42ead66 | |||
03730fafb9 | |||
0be9465dca | |||
d7f1df4995 | |||
3cb0f90706 | |||
a3e9b15a8a | |||
00bfa68a57 | |||
c311e24f08 | |||
1cdd4e4455 | |||
8078c0081a | |||
a867e1fc40 | |||
61da36ac1c | |||
720ca2a018 | |||
b39c593552 | |||
c808952a45 | |||
b468d7a766 | |||
28578f60be | |||
92a39e2527 | |||
1bce5e4342 | |||
68f586e104 | |||
d27a66202e | |||
07de2f450c | |||
3c03726634 | |||
6fb2643063 | |||
5eef6c8196 | |||
829c332e13 | |||
47eec14bca | |||
e7ee1d6be7 | |||
e41b5e5ff7 | |||
ba1bacd08c | |||
1029ea5840 | |||
339eaf0195 | |||
aa1e1549d8 | |||
1f8ae9ef80 | |||
65165de060 | |||
485836d2ce | |||
64c07c14d9 | |||
b6313c2b72 | |||
58124b5821 | |||
b6a599f8a6 | |||
19a6bc0dbd | |||
8cb81d44ee | |||
22d0a372d8 | |||
988fae75c3 | |||
a82b0e3622 | |||
bc6cacacf9 | |||
03ad1b3cbc | |||
eac300627f | |||
12525ac386 | |||
7558551859 | |||
a9cce13eef | |||
5bb2c43dd0 | |||
5b1dcc88cc | |||
5c8fc1d136 | |||
82c2ab3448 | |||
b05c8d180f | |||
87109e5d01 | |||
be1a809098 | |||
ba0e3f1bd4 | |||
27d41e6164 | |||
1ce5ddfd98 | |||
0096a1a4ef | |||
d2a6560a90 | |||
4d7c048139 | |||
1d78bd2655 | |||
9250f1907c | |||
a63e210b88 | |||
06bd65b7f8 | |||
b6c17eb05e | |||
a3d40fdc2b | |||
c064ef7a30 | |||
762717da1e | |||
b7d3758bea | |||
d20541fd24 | |||
2c2f860f26 | |||
d1db3aa673 | |||
cedc52cdf5 | |||
33f8eafbcf | |||
ad45cf8c72 | |||
f99d035621 | |||
f3072caea8 | |||
1410974292 | |||
44413fa96c | |||
342a4b4067 | |||
4356dde92d | |||
21e64a18d8 | |||
5a305a6740 | |||
44595bad40 | |||
7fc9edc9cb | |||
93e191f522 | |||
8d8355e792 | |||
a5379d290c | |||
caea8d4315 | |||
24224ab4c6 | |||
4dbc02360c | |||
aa7a29af0c | |||
296d0c6199 | |||
812a034e8d | |||
e9de789b79 | |||
cfbc1b9575 | |||
e39e85e4dd | |||
3f0b161cd0 | |||
ebe3868720 | |||
ffd0f5e986 | |||
217535a3ba | |||
7abfbea2da | |||
86ffeaac9a | |||
ab915b7115 | |||
705b5d38cf | |||
fc2acb00b3 | |||
5add8a1d0e | |||
063d3a2637 | |||
f1f90a2ee3 | |||
ed317a4e46 | |||
cca16f3bb1 | |||
aba156cb3b | |||
cd4e4d7095 | |||
8fbb639430 | |||
d5bf8ec558 | |||
b6cff40f1e | |||
833e42f455 | |||
8134843dad | |||
1f92d5e633 | |||
dc51c0ae85 | |||
45c79643d6 | |||
9041bfc627 | |||
0b3b3dd0be | |||
89e92dab59 | |||
8c168c9ad7 | |||
9f63357a92 | |||
d91b4bd1f3 | |||
c0c64f6d62 | |||
1a5d2af779 | |||
f40a33192b | |||
ca4900aff0 | |||
56fc9e2d40 | |||
d2174c0b69 | |||
9f76754ad3 | |||
118ceaec35 | |||
5a57d28a7d | |||
07af99f862 | |||
59fba7fba0 | |||
dd4edc4249 | |||
856226473a | |||
8d1c07d6b2 | |||
c32462cc9e | |||
ec94ea3273 | |||
41acd8c15b | |||
155a79f2ec | |||
9197cb9be6 | |||
03d50c847b | |||
bf45c40365 | |||
679e126194 | |||
50e39164bd | |||
cb9f75e968 | |||
aa7f6cc3b1 | |||
fe601aed41 | |||
2282a9df28 | |||
2b54627750 | |||
16051a0d25 | |||
66d5f0d790 | |||
07d29207f0 | |||
a60be2afcc | |||
027f3ee253 | |||
04774815e4 | |||
61a73c055b | |||
7731534ffc | |||
ed7bf99610 | |||
cbe4272556 | |||
8f5e3dfdcc | |||
35500c51f1 | |||
629f873bb2 | |||
a44cb884c4 | |||
d5ad1d0daa | |||
61ae7e3b6a | |||
01583e424f | |||
5c0aa1b3da | |||
07391efa70 | |||
b80f1bfc7c | |||
ad310db86c | |||
4ce0122a29 | |||
a8894f7054 | |||
1d1515351b | |||
2a9ddd4468 | |||
0f9a944775 | |||
34ee9ebd66 | |||
43f632b555 | |||
7cf3f1d32c | |||
e51e87647e | |||
b8aae88b11 | |||
d06e29db15 | |||
62449450f3 | |||
b290c94635 | |||
f909b887d9 | |||
5cf4843212 | |||
b3d1e1bfac | |||
df47499a28 | |||
421475ec87 | |||
29d999b8eb | |||
acacd9a5e5 | |||
b81c26f93f | |||
00b212944c | |||
70ba006e4d | |||
118e0cae62 | |||
c003bb4e71 | |||
e9b2fa7603 | |||
35afaa105d | |||
2e300a7b8f | |||
f3f5b88550 | |||
22f491d98a | |||
7908c8ebd9 | |||
e114a93714 | |||
931761600f | |||
e5b4b03e1a | |||
f1e8b3fbf0 | |||
4d64243a07 | |||
3422b4d4d6 | |||
b170d529fd | |||
83741db5aa | |||
c4b2b3ab25 | |||
676df44985 | |||
037adc6b5c | |||
186d17dd98 | |||
ab9ff980ef | |||
f297cb2623 | |||
b53c672768 | |||
1a2b967749 | |||
6ba30c48cf | |||
1af9ee9ced | |||
e50480aa35 | |||
6943c7813f | |||
7c9b4382ca | |||
3187f97592 | |||
b71a367052 | |||
2d4bbf2a6f | |||
6e59dfddcc | |||
bd92fc6ee0 | |||
2f61de7025 | |||
8fcec7ec7c | |||
33d9ba3871 | |||
4f8c778ba0 | |||
804c739038 | |||
a0445fbb12 | |||
7ab5d7b796 | |||
7f83a7773b | |||
fc9e8a808f | |||
5ab8976bc7 | |||
e2a28f2811 | |||
137a20ed0e | |||
f956f696f4 | |||
bb7cbde38f | |||
8c452d3fa2 | |||
f65c33c432 | |||
da2758ccb1 | |||
de10112b7f | |||
301d4fb171 | |||
f0a79add14 | |||
d33bc9fe25 | |||
a2a5dfd435 | |||
f3d7c8d4c3 | |||
67f60dd787 | |||
62310ce4a4 | |||
3a27089364 | |||
a05460f562 | |||
390872c305 | |||
594d12df79 | |||
c42c12c72b | |||
c37f4fe365 | |||
8d37d68770 | |||
1b3d1fb36a | |||
2f352ef9bb | |||
527f3cab4c | |||
f67bf27db2 | |||
a8bb3ba349 | |||
321ab12213 | |||
4cab18e557 | |||
c15ea97a36 | |||
a0cc4109df | |||
f66ff1000a | |||
51a9bb6d3c | |||
07017bd29b | |||
45b6f49a08 | |||
103ad72788 | |||
543259f29f | |||
98799734c5 | |||
96f491a666 | |||
29e541dbef | |||
1343b9c113 | |||
94920fd459 | |||
b2f3b245b7 | |||
15bc6c1d73 | |||
2c175b8d3a | |||
a48c17a769 | |||
03465c4ab0 | |||
b4e936c530 | |||
fb1482370a | |||
e831bc2bea | |||
c74c168c96 | |||
40b53fa40c | |||
3481cc0349 | |||
09abd889e9 | |||
330bbfbb31 | |||
6b7b690476 | |||
cea72801a7 | |||
04369e22a7 | |||
f1b16eab9a | |||
13646d9c90 | |||
17d762ce91 | |||
edb40adaa1 | |||
bc0809994a | |||
a3e3d517b0 | |||
d8b63a54ca | |||
b81e7583eb | |||
51f9ba3642 | |||
296626f7c7 | |||
5b2daf9746 | |||
9a6b615174 | |||
18f8d5b269 | |||
2867a875c9 | |||
ee2f5ae8cb | |||
bd5c301fb9 | |||
6df68629f7 | |||
be08fd4445 | |||
6f1afb94fb | |||
7401b8a52d | |||
c83b62ebaa | |||
108cf8923e | |||
4e26fd9d56 | |||
8c9168a4bf | |||
97da0b79e4 | |||
d7e5f6876b | |||
1b92b112e2 | |||
ca55119531 | |||
d9da14b5dc | |||
512b5666ac | |||
64977964e8 | |||
2bc13e0de6 | |||
b90c5f17cf | |||
7d8d0bd43b | |||
![]() |
54c1137927 | ||
e6655219ee | |||
5896f8e35a | |||
934cba7251 | |||
9cc1a11bef | |||
c1bc956d6d | |||
351b174b86 | |||
0b4aaf80dc | |||
c10c185817 | |||
327ef1cbee | |||
15eb823c7f | |||
54613e5242 | |||
df1352cbe3 | |||
0559afd972 | |||
afffca020e | |||
d663cc3f64 | |||
110d41e393 | |||
1a8823f592 | |||
6374a852b0 | |||
a10c7dd7c3 | |||
547c7ea417 | |||
760607995a | |||
4704197c09 | |||
093ac1ac40 | |||
9ed8b0d904 | |||
7346ce370d | |||
adefdadc19 | |||
703bce2d00 | |||
97928ecd84 | |||
be9ea7f64a | |||
ec2aaa8789 | |||
ab14b72526 | |||
d8e304f3c1 | |||
ea53ce361f | |||
2fce80b347 | |||
373c0b1cc3 | |||
e5e1b7e608 | |||
7e9221c9e0 | |||
6b849f854e | |||
831f6bc744 | |||
d282a7a537 | |||
fb2f1e3031 | |||
00a0da3df3 | |||
8c447b1ffb | |||
a4841175e8 | |||
9b139132a1 | |||
4a404ecabc | |||
aee758b559 | |||
be060d0386 | |||
0195378c10 | |||
bc804c6a53 | |||
76b15f1971 | |||
c4d43c9d5b | |||
e8d3e530de | |||
e145adec58 | |||
e2dad3e477 | |||
27bdbde171 | |||
e9ec27169c | |||
2e24cb634c | |||
beb9046055 | |||
e57301952c | |||
7411279e48 | |||
16acfa85b5 | |||
41ef37f3f0 | |||
00d8538726 | |||
6eeb3f9895 | |||
d19dca6ea5 | |||
2008ccdaa4 | |||
ba2e62de3a | |||
2b62eb254d | |||
31f72b7957 | |||
fdc4616875 | |||
b7de261d25 | |||
ae78a5a026 | |||
fd2cf5d4d7 | |||
9f0997be1a | |||
dbade7f854 | |||
3cdc1e190a | |||
36bede7211 | |||
46689bb700 | |||
13e1a6543c | |||
820ce9e845 | |||
f17806f4e8 | |||
3f5ffc9e10 | |||
aeb0842ab4 | |||
38837ae84c | |||
a4eb6935af | |||
52f1f4c4eb | |||
6c1782a038 | |||
8b8f5f5473 | |||
61d3ed891a | |||
b1abf87320 | |||
9aedfc2799 | |||
ad6240a067 | |||
9539eb076a | |||
c808e7bd83 | |||
13ea388f5e | |||
c46dc0f1a3 | |||
2ae311007d | |||
9344e02bff | |||
40ad836fc3 | |||
e8604a261d | |||
2a41d21a29 | |||
4c62aa067b | |||
49db3074c6 | |||
f5e3b34f30 | |||
f0affa4aec | |||
4f5075ac54 | |||
20f0445b10 | |||
c77c974455 | |||
44397b2d45 | |||
943d4d4d72 | |||
6468c03465 | |||
8141a5a5c5 | |||
26a1779310 | |||
45d18ffafe | |||
5f1c30609c | |||
7266d705d3 | |||
ee6bb782d6 | |||
8ae6e2c886 | |||
dd3a0d3890 | |||
8d8e2da57e | |||
e60d204302 | |||
3d642d8ad2 | |||
8db6e8a090 | |||
8153fcde85 | |||
96469cfca5 | |||
7601645c12 | |||
c28615d548 | |||
b515add94e | |||
9fd5e9443d | |||
b2ddb1fab2 | |||
fdac42947c | |||
eeaf6949c5 | |||
d7ad62d476 | |||
cd87a329fc | |||
8c0d306823 | |||
d5c3ea0862 | |||
83c962a7a4 | |||
40ef9a42dd | |||
868af5ac6a | |||
625227d0ce | |||
064627961e | |||
de0321cb2d | |||
0d71a33b28 | |||
6d779f17b3 | |||
05510d7bc1 | |||
8e162fe031 | |||
7ea7366a43 | |||
445e6fcec0 | |||
42f4d97d5d | |||
6357708533 | |||
59c9801437 | |||
d691bef1fb | |||
442d74d0cb | |||
588bb9a093 | |||
380e580d65 | |||
4e306661f8 | |||
9f3f33da93 | |||
69cd96a37c | |||
1293a2a533 | |||
d24b7bbcb9 | |||
b55b47b689 | |||
c4c032b4d5 | |||
970cd21964 | |||
8ca9d242b2 | |||
6f0518edcc | |||
e2d15dd7e3 | |||
5c310e8647 | |||
01dca0bc66 | |||
8b54fbdb2f | |||
663d0a633e | |||
ccd5edb0e4 | |||
c6190db918 | |||
3d4cec3b22 | |||
5ed970b5a0 | |||
c22934336b | |||
a3a52e0a1c | |||
68dca6e3d9 | |||
017f883e0b | |||
77b5c95f75 | |||
9d052c8339 | |||
d67623a657 | |||
c740b3dd46 | |||
2ef5f7f96f | |||
404568d795 | |||
b5a6337a0c | |||
82170c3fbd | |||
e6d6275fcc | |||
97c865a127 | |||
1ff21f0ee0 | |||
2a3dca4467 | |||
d4ecfcceec | |||
ec5d503e4d | |||
346391ca2d | |||
9074cdf340 | |||
2fcf3604a8 | |||
34e5185fa1 | |||
e09e0e69ca | |||
963c98e588 | |||
92acb823a4 | |||
b967b1288f | |||
1db271ce90 | |||
58c64025e3 | |||
643a7a87aa | |||
5e9ed5d713 | |||
78e492c764 | |||
59c2a3642b | |||
40ca923745 | |||
03af6cecaa | |||
3992e447f4 | |||
14a9edeb73 | |||
92f1e9f7ec | |||
19c294c53e | |||
fe88ea5c05 | |||
c9d551213a | |||
1e86a33ceb | |||
551dd229f5 | |||
5ecf3c4147 | |||
91bb2f4df0 | |||
ae3a0ae83d | |||
63ce7523de | |||
9e3b92bfc1 | |||
bc1767fb84 | |||
f917096cc7 | |||
308926a2ae | |||
76f2b1a454 | |||
d899e4b38b | |||
e1422e35cc | |||
2c00c6bb81 | |||
7e56ba6408 | |||
8ceb70e67d | |||
37d5efef1d | |||
924065c26e | |||
58cc7ea10d | |||
f93e275ddf | |||
06d2a5f715 | |||
3a7455eafe | |||
8b676fe6ce | |||
54d12686af | |||
f231256402 | |||
410ead66f8 | |||
c833a810af | |||
50f1336b1d | |||
60ed0b8cde | |||
cc55a81c1b | |||
f832e04e9e | |||
fc760b9a0c | |||
9addff0521 | |||
dcaa3aab19 | |||
628785c68c | |||
a5aa396fda | |||
f53a9f05e3 | |||
7749b14156 | |||
c15f339718 | |||
775f590bfa | |||
76408ea56f | |||
a391d8ee83 | |||
48c38f6e1d | |||
37c5fba162 | |||
23e99b1d44 | |||
8432240a47 | |||
a4bab743d6 | |||
60766789ab | |||
ca014f881c | |||
886eabe26c | |||
65b7167b5f | |||
abbdde851e | |||
54ac54aba6 | |||
184340f400 | |||
93dd6813e8 | |||
b689b08711 | |||
1479a097d6 | |||
b2be530f6b | |||
e4967ea46d | |||
3f28f18fb4 | |||
1b90e0f65e | |||
756ed649e6 | |||
fbc423e2a7 | |||
f04cdb6a13 | |||
63b58b1cfe | |||
77e656d8e4 | |||
a673957bd0 | |||
c99a0c9974 | |||
0fb06d0ff2 | |||
c51eebfe22 | |||
a51b34b48f | |||
1b239bada1 | |||
50ab1a6ac3 | |||
f181f1fadc | |||
c686349922 | |||
5f44a1f4ad | |||
a968938832 | |||
8d67f3dfdc | |||
973ae8cb5d | |||
a4747b0d7b | |||
f07640cc84 | |||
c235c55b19 | |||
485ef684be | |||
7caca22e57 | |||
f1d9e32bf5 | |||
23d5fa3107 | |||
4e7d8aba1c | |||
98ba871a71 | |||
3ff23c0264 | |||
e21f89477b | |||
f177f514f5 | |||
af30f3b348 | |||
82df618429 | |||
bb3538e270 | |||
71925e1126 | |||
93c1cbd231 | |||
894b890fe5 | |||
8e9e8f7fad | |||
2a0461a76f | |||
85f923a6fc | |||
b35e4d4d01 | |||
cb24a859f4 | |||
b1ef00746f | |||
aebe82e3a7 | |||
7c87856b4d | |||
d1b1dd539f | |||
55eea88ace | |||
a70f64e1f6 | |||
fa0cb120a7 | |||
e3080d07dc | |||
34726c533e | |||
4a0d72d2cc | |||
fe3fc5c9f7 | |||
441228e2b0 | |||
7538aee4f2 | |||
acf809268e | |||
4ebc0c10b6 | |||
a453888ca2 | |||
530b44762b | |||
f85587fb0b | |||
edb8799b1a | |||
e47aeb37f0 | |||
776e9968dc | |||
1898bf4731 | |||
78df020737 | |||
b93f9a4b9a | |||
748b230ef5 | |||
deb8dde9e1 | |||
dbb2f10754 | |||
0ded03ab92 | |||
2198e84f3b | |||
14d44528b0 | |||
eb8159ca0f | |||
9811f40a53 | |||
8de7e13aa3 | |||
c63e6a1e49 | |||
5a21d2cb10 | |||
424c0e596c | |||
d431b63c27 | |||
38c2781cd3 | |||
796fb348a3 | |||
71b306d5fd | |||
4c610ea32d | |||
4bff006743 | |||
1645079bc0 | |||
9afb58e4a7 | |||
2820fc8acf | |||
4d77a498f6 | |||
d77de3bb12 | |||
29e7ad6ce6 | |||
1712b5120e | |||
06c0153cf5 | |||
44f7ecda6d | |||
fb94bf1b80 | |||
4818652582 | |||
c69b9784fc | |||
0ac244a3ea | |||
19a445fdab | |||
c90a18a2c0 | |||
502310c413 | |||
6f9424d4ec | |||
bb379fe667 | |||
0fd86bf214 | |||
29b75d4391 | |||
a7124e5449 | |||
a714f3480a | |||
c10e0df898 | |||
fba734fd5a | |||
27e2372097 | |||
7f5b99495c | |||
1efe2a56af | |||
850873aec8 | |||
d9e6afbf36 | |||
7f3bd2715c | |||
c81cb393e9 | |||
4800faa783 | |||
1087b5e1d1 | |||
5dc2e71976 | |||
28eb7d0810 | |||
c641a92d89 | |||
3e57cc045f | |||
c60a8ddf66 | |||
8becef3e45 | |||
3237634e3b | |||
2a4a659e39 | |||
fcaa47c0a8 | |||
68ea17ccbd | |||
80308c7102 | |||
6f4a99a7cb | |||
ac245f5128 | |||
7f9e9e27a0 | |||
abf58a4aec | |||
5d9a700a6e | |||
3662b1eb1d | |||
34449da2b8 | |||
42f367f822 | |||
781ca0bb77 | |||
ebe5d50dae | |||
00d6dc5626 | |||
a1648c307f | |||
b8f170ae39 | |||
073f1da5b4 | |||
6310711136 | |||
fcac7a4ce1 | |||
c2b1aef810 | |||
c8ab26275c | |||
a1fd6a2b6b | |||
561c08e0cc | |||
4658e30e89 | |||
225e6b369a | |||
0e9094a19f | |||
2da0e03c6c | |||
9a6fac5fc8 | |||
b541f0a896 | |||
cec7cce077 |
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto eof=lf
|
||||
*.cs diff=csharp
|
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
github: chylex
|
||||
patreon: chylex
|
||||
ko_fi: chylex
|
7
.gitignore
vendored
7
.gitignore
vendored
@@ -5,8 +5,14 @@
|
||||
bld/*
|
||||
!bld/*.iss
|
||||
!bld/*.bat
|
||||
!bld/*.ps1
|
||||
!bld/Redist
|
||||
!bld/Resources
|
||||
|
||||
# Rider
|
||||
.idea/.idea.TweetDuck/.idea/dictionaries
|
||||
.idea/.idea.TweetDuck/.idea/misc.xml
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
@@ -140,4 +146,3 @@ _UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
|
8
.idea/.idea.TweetDuck/.idea/.gitignore
generated
vendored
Normal file
8
.idea/.idea.TweetDuck/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Rider ignored files
|
||||
/projectSettingsUpdater.xml
|
||||
/modules.xml
|
||||
/contentModel.xml
|
||||
/.idea.TweetDuck.iml
|
450
.idea/.idea.TweetDuck/.idea/codeStyles/Project.xml
generated
Normal file
450
.idea/.idea.TweetDuck/.idea/codeStyles/Project.xml
generated
Normal file
@@ -0,0 +1,450 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<option name="AUTODETECT_INDENTS" value="false" />
|
||||
<option name="OTHER_INDENT_OPTIONS">
|
||||
<value>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="LINE_SEPARATOR" value=" " />
|
||||
<option name="RIGHT_MARGIN" value="999" />
|
||||
<option name="FORMATTER_TAGS_ENABLED" value="true" />
|
||||
<CssCodeStyleSettings>
|
||||
<option name="HEX_COLOR_LOWER_CASE" value="true" />
|
||||
</CssCodeStyleSettings>
|
||||
<DB2CodeStyleSettings version="6">
|
||||
<option name="USE_GENERIC_STYLE" value="true" />
|
||||
</DB2CodeStyleSettings>
|
||||
<DerbyCodeStyleSettings version="6">
|
||||
<option name="USE_GENERIC_STYLE" value="true" />
|
||||
</DerbyCodeStyleSettings>
|
||||
<GoCodeStyleSettings>
|
||||
<option name="MOVE_ALL_STDLIB_IMPORTS_IN_ONE_GROUP" value="true" />
|
||||
<option name="GROUP_STDLIB_IMPORTS" value="true" />
|
||||
<option name="WRAP_COMP_LIT" value="5" />
|
||||
<option name="WRAP_FUNC_PARAMS" value="5" />
|
||||
<option name="WRAP_FUNC_RESULT" value="5" />
|
||||
</GoCodeStyleSettings>
|
||||
<H2CodeStyleSettings version="6">
|
||||
<option name="USE_GENERIC_STYLE" value="true" />
|
||||
</H2CodeStyleSettings>
|
||||
<H2CodeStyleSettings version="6">
|
||||
<option name="USE_GENERIC_STYLE" value="true" />
|
||||
</H2CodeStyleSettings>
|
||||
<HSQLCodeStyleSettings version="6">
|
||||
<option name="USE_GENERIC_STYLE" value="true" />
|
||||
</HSQLCodeStyleSettings>
|
||||
<HTMLCodeStyleSettings>
|
||||
<option name="HTML_ALIGN_TEXT" value="true" />
|
||||
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
||||
<option name="HTML_ELEMENTS_TO_REMOVE_NEW_LINE_BEFORE" value="" />
|
||||
<option name="HTML_DO_NOT_INDENT_CHILDREN_OF" value="" />
|
||||
</HTMLCodeStyleSettings>
|
||||
<JSCodeStyleSettings version="0">
|
||||
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACKETS" value="true" />
|
||||
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||
<option name="USE_EXPLICIT_JS_EXTENSION" value="TRUE" />
|
||||
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||
<option name="USE_CHAINED_CALLS_GROUP_INDENTS" value="true" />
|
||||
<option name="SPACE_BEFORE_ASYNC_ARROW_LPAREN" value="false" />
|
||||
<option name="IMPORT_SORT_MODULE_NAME" value="true" />
|
||||
</JSCodeStyleSettings>
|
||||
<JSON>
|
||||
<option name="OBJECT_WRAPPING" value="5" />
|
||||
<option name="ARRAY_WRAPPING" value="5" />
|
||||
</JSON>
|
||||
<JavaCodeStyleSettings>
|
||||
<option name="INSERT_INNER_CLASS_IMPORTS" value="true" />
|
||||
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
|
||||
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
|
||||
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
|
||||
<value />
|
||||
</option>
|
||||
<option name="IMPORT_LAYOUT_TABLE">
|
||||
<value>
|
||||
<package name="" withSubpackages="true" static="false" />
|
||||
<package name="javax" withSubpackages="true" static="false" />
|
||||
<package name="java" withSubpackages="true" static="false" />
|
||||
<package name="" withSubpackages="true" static="true" />
|
||||
</value>
|
||||
</option>
|
||||
</JavaCodeStyleSettings>
|
||||
<JetCodeStyleSettings>
|
||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||
<value>
|
||||
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="ALIGN_IN_COLUMNS_CASE_BRANCH" value="true" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
|
||||
<option name="IMPORT_NESTED_CLASSES" value="true" />
|
||||
<option name="WRAP_ELVIS_EXPRESSIONS" value="0" />
|
||||
<option name="ALLOW_TRAILING_COMMA" value="true" />
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<LessCodeStyleSettings>
|
||||
<option name="HEX_COLOR_LOWER_CASE" value="true" />
|
||||
</LessCodeStyleSettings>
|
||||
<MSSQLCodeStyleSettings version="6">
|
||||
<option name="USE_GENERIC_STYLE" value="true" />
|
||||
</MSSQLCodeStyleSettings>
|
||||
<MySQLCodeStyleSettings version="6">
|
||||
<option name="USE_GENERIC_STYLE" value="true" />
|
||||
</MySQLCodeStyleSettings>
|
||||
<Objective-C>
|
||||
<option name="INDENT_DIRECTIVE_AS_CODE" value="true" />
|
||||
<option name="KEEP_STRUCTURES_IN_ONE_LINE" value="true" />
|
||||
<option name="KEEP_CASE_EXPRESSIONS_IN_ONE_LINE" value="true" />
|
||||
<option name="SPACE_BEFORE_INIT_LIST" value="true" />
|
||||
<option name="SPACE_AFTER_DICTIONARY_LITERAL_COLON" value="false" />
|
||||
</Objective-C>
|
||||
<OracleCodeStyleSettings version="6">
|
||||
<option name="USE_GENERIC_STYLE" value="true" />
|
||||
</OracleCodeStyleSettings>
|
||||
<PHPCodeStyleSettings>
|
||||
<option name="ALIGN_KEY_VALUE_PAIRS" value="true" />
|
||||
<option name="CONCAT_SPACES" value="false" />
|
||||
<option name="COMMA_AFTER_LAST_ARRAY_ELEMENT" value="true" />
|
||||
<option name="PHPDOC_BLANK_LINE_BEFORE_TAGS" value="true" />
|
||||
<option name="LOWER_CASE_BOOLEAN_CONST" value="true" />
|
||||
<option name="LOWER_CASE_NULL_CONST" value="true" />
|
||||
<option name="ELSE_IF_STYLE" value="COMBINE" />
|
||||
<option name="VARIABLE_NAMING_STYLE" value="SNAKE_CASE" />
|
||||
<option name="KEEP_BLANK_LINES_AFTER_LBRACE" value="0" />
|
||||
<option name="SPACE_BEFORE_CLOSURE_LEFT_PARENTHESIS" value="false" />
|
||||
<option name="FORCE_SHORT_DECLARATION_ARRAY_STYLE" value="true" />
|
||||
<option name="NEW_LINE_AFTER_PHP_OPENING_TAG" value="true" />
|
||||
<option name="SPACE_AROUND_ASSIGNMENT_IN_DECLARE" value="true" />
|
||||
</PHPCodeStyleSettings>
|
||||
<PostgresCodeStyleSettings version="6">
|
||||
<option name="USE_GENERIC_STYLE" value="true" />
|
||||
</PostgresCodeStyleSettings>
|
||||
<Properties>
|
||||
<option name="KEEP_BLANK_LINES" value="true" />
|
||||
</Properties>
|
||||
<Python>
|
||||
<option name="SPACE_AROUND_EQ_IN_NAMED_PARAMETER" value="true" />
|
||||
<option name="SPACE_AROUND_EQ_IN_KEYWORD_ARGUMENT" value="true" />
|
||||
<option name="NEW_LINE_AFTER_COLON" value="true" />
|
||||
<option name="DICT_WRAPPING" value="5" />
|
||||
<option name="DICT_NEW_LINE_AFTER_LEFT_BRACE" value="true" />
|
||||
<option name="DICT_NEW_LINE_BEFORE_RIGHT_BRACE" value="true" />
|
||||
</Python>
|
||||
<RsCodeStyleSettings>
|
||||
<option name="ALIGN_RET_TYPE" value="false" />
|
||||
<option name="ALIGN_TYPE_PARAMS" value="true" />
|
||||
<option name="ALLOW_ONE_LINE_MATCH" value="true" />
|
||||
<option name="SPACE_AROUND_ASSOC_TYPE_BINDING" value="true" />
|
||||
</RsCodeStyleSettings>
|
||||
<Ruby>
|
||||
<option name="INDENT_PRIVATE_METHODS" value="true" />
|
||||
<option name="INDENT_PROTECTED_METHODS" value="true" />
|
||||
<option name="INDENT_PUBLIC_METHODS" value="true" />
|
||||
<option name="INDENT_WHEN_CASES" value="true" />
|
||||
<option name="CHAIN_CALLS_ALIGNMENT" value="2" />
|
||||
</Ruby>
|
||||
<SQLiteCodeStyleSettings version="6">
|
||||
<option name="USE_GENERIC_STYLE" value="true" />
|
||||
</SQLiteCodeStyleSettings>
|
||||
<ScssCodeStyleSettings>
|
||||
<option name="HEX_COLOR_LOWER_CASE" value="true" />
|
||||
</ScssCodeStyleSettings>
|
||||
<SqlCodeStyleSettings version="6">
|
||||
<option name="KEYWORD_CASE" value="2" />
|
||||
<option name="TYPE_CASE" value="2" />
|
||||
<option name="CUSTOM_TYPE_CASE" value="2" />
|
||||
<option name="SUBQUERY_CONTENT" value="1" />
|
||||
<option name="SUBQUERY_CLOSING" value="1" />
|
||||
<option name="INSERT_TABLE_EL_LINE" value="0" />
|
||||
<option name="INSERT_EL_WRAP" value="2" />
|
||||
<option name="SET_EL_WRAP" value="2" />
|
||||
<option name="SET_ALIGN_EQUAL_SIGN" value="false" />
|
||||
<option name="FROM_EL_WRAP" value="2" />
|
||||
<option name="FROM_ALIGN_JOIN_TABLES" value="true" />
|
||||
<option name="FROM_INDENT_JOIN" value="false" />
|
||||
<option name="FROM_ONLY_JOIN_INDENT" value="2" />
|
||||
<option name="WHERE_EL_WRAP" value="2" />
|
||||
<option name="TABLE_OPENING" value="1" />
|
||||
<option name="TABLE_CONTENT" value="2" />
|
||||
<option name="TABLE_CLOSING" value="3" />
|
||||
<option name="TABLE_DEFAULTS_ALIGN" value="false" />
|
||||
<option name="TABLE_NULLABILITIES_ALIGN" value="false" />
|
||||
<option name="CONSTRAINT_WRAP_1" value="false" />
|
||||
<option name="CONSTRAINT_WRAP_3" value="true" />
|
||||
<option name="CONSTRAINT_WRAP_4" value="true" />
|
||||
<option name="VIEW_INDENT_QUERY" value="true" />
|
||||
<option name="EXPR_CASE_WHEN_WRAP" value="false" />
|
||||
</SqlCodeStyleSettings>
|
||||
<SybaseCodeStyleSettings version="6">
|
||||
<option name="USE_GENERIC_STYLE" value="true" />
|
||||
</SybaseCodeStyleSettings>
|
||||
<TypeScriptCodeStyleSettings version="0">
|
||||
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||
<option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
|
||||
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||
<option name="USE_CHAINED_CALLS_GROUP_INDENTS" value="true" />
|
||||
<option name="SPACE_BEFORE_ASYNC_ARROW_LPAREN" value="false" />
|
||||
</TypeScriptCodeStyleSettings>
|
||||
<XML>
|
||||
<option name="XML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
||||
</XML>
|
||||
<codeStyleSettings language="CMake">
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
<option name="SMART_TABS" value="true" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="CSS">
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="Groovy">
|
||||
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
|
||||
<option name="IF_BRACE_FORCE" value="3" />
|
||||
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||
<option name="FOR_BRACE_FORCE" value="3" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
<option name="SMART_TABS" value="true" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="HTML">
|
||||
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||
<option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="JAVA">
|
||||
<option name="RIGHT_MARGIN" value="999" />
|
||||
<option name="BLANK_LINES_AFTER_PACKAGE" value="0" />
|
||||
<option name="BLANK_LINES_BEFORE_IMPORTS" value="0" />
|
||||
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
|
||||
<option name="SPACE_AFTER_TYPE_CAST" value="false" />
|
||||
<option name="SPACE_BEFORE_SYNCHRONIZED_PARENTHESES" value="false" />
|
||||
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
|
||||
<option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
|
||||
<option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
|
||||
<option name="IF_BRACE_FORCE" value="3" />
|
||||
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||
<option name="FOR_BRACE_FORCE" value="3" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
<option name="SMART_TABS" value="true" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="JSON">
|
||||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||
<option name="SPACE_WITHIN_BRACKETS" value="true" />
|
||||
<option name="SPACE_WITHIN_BRACES" value="true" />
|
||||
<indentOptions>
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="JavaScript">
|
||||
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||
<option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" />
|
||||
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
|
||||
<option name="IF_BRACE_FORCE" value="3" />
|
||||
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||
<option name="FOR_BRACE_FORCE" value="3" />
|
||||
<indentOptions>
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
<option name="SMART_TABS" value="true" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="LESS">
|
||||
<indentOptions>
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="Lua">
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="ObjectiveC">
|
||||
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||
<option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||
<option name="LINE_COMMENT_ADD_SPACE" value="true" />
|
||||
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
|
||||
<option name="IF_BRACE_FORCE" value="3" />
|
||||
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||
<option name="FOR_BRACE_FORCE" value="3" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
<option name="SMART_TABS" value="true" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="PHP">
|
||||
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
|
||||
<option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
|
||||
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
|
||||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
|
||||
<option name="CLASS_BRACE_STYLE" value="1" />
|
||||
<option name="METHOD_BRACE_STYLE" value="1" />
|
||||
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||
<option name="SPECIAL_ELSE_IF_TREATMENT" value="true" />
|
||||
<option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
|
||||
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
|
||||
<option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" />
|
||||
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
|
||||
<option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
|
||||
<option name="IF_BRACE_FORCE" value="3" />
|
||||
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||
<option name="FOR_BRACE_FORCE" value="3" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="Puppet">
|
||||
<indentOptions>
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="Python">
|
||||
<indentOptions>
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="RHTML">
|
||||
<indentOptions>
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="Rust">
|
||||
<option name="RIGHT_MARGIN" value="140" />
|
||||
<option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
|
||||
<indentOptions>
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
<option name="SMART_TABS" value="true" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="SASS">
|
||||
<indentOptions>
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="SCSS">
|
||||
<indentOptions>
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="SQL">
|
||||
<indentOptions>
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
<option name="SMART_TABS" value="true" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="Shell Script">
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="TOML">
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="TypeScript">
|
||||
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||
<option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" />
|
||||
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
|
||||
<option name="IF_BRACE_FORCE" value="3" />
|
||||
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||
<option name="FOR_BRACE_FORCE" value="3" />
|
||||
<indentOptions>
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
<option name="SMART_TABS" value="true" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
<option name="SMART_TABS" value="true" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="go">
|
||||
<option name="CALL_PARAMETERS_WRAP" value="5" />
|
||||
<indentOptions>
|
||||
<option name="SMART_TABS" value="true" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||
<option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||
<option name="LINE_COMMENT_ADD_SPACE" value="true" />
|
||||
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||
<option name="METHOD_ANNOTATION_WRAP" value="0" />
|
||||
<option name="CLASS_ANNOTATION_WRAP" value="0" />
|
||||
<option name="FIELD_ANNOTATION_WRAP" value="0" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
<option name="SMART_TABS" value="true" />
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="liquid">
|
||||
<indentOptions>
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="ruby">
|
||||
<option name="SPACE_WITHIN_BRACES" value="true" />
|
||||
<indentOptions>
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="yaml">
|
||||
<indentOptions>
|
||||
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
5
.idea/.idea.TweetDuck/.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/.idea.TweetDuck/.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
6
.idea/.idea.TweetDuck/.idea/encodings.xml
generated
Normal file
6
.idea/.idea.TweetDuck/.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
|
||||
<file url="PROJECT" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
8
.idea/.idea.TweetDuck/.idea/indexLayout.xml
generated
Normal file
8
.idea/.idea.TweetDuck/.idea/indexLayout.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
770
.idea/.idea.TweetDuck/.idea/inspectionProfiles/Project.xml
generated
Normal file
770
.idea/.idea.TweetDuck/.idea/inspectionProfiles/Project.xml
generated
Normal file
@@ -0,0 +1,770 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project" />
|
||||
<inspection_tool class="AccessToNonThreadSafeStaticFieldFromInstance" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="nonThreadSafeClasses">
|
||||
<value />
|
||||
</option>
|
||||
<option name="nonThreadSafeTypes" value="" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="AccessToStaticFieldLockedOnInstance" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AddOperatorModifier" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AmbiguousFieldAccess" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AmbiguousMethodCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AnonymousInnerClassMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ArrayEquality" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssertEqualsCalledOnArray" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssertsWithoutMessagesTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssignmentOrReturnOfFieldWithMutableType" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssignmentToCatchBlockParameter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssignmentToLambdaParameter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssignmentToMethodParameter" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreTransformationOfOriginalParameter" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="AssignmentToStaticFieldFromInstanceMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AssignmentUsedAsCondition" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AutoBoxing" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreAddedToCollection" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoredTypes" value="java.util.stream.Stream,java.util.stream.IntStream,java.util.stream.LongStream,java.util.stream.DoubleStream,net.minecraft.client.Minecraft,net.minecraft.client.MainWindow" />
|
||||
<option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,net.minecraft.client.MinecraftClient,getInstance|getWindow" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="AutoUnboxing" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AwaitNotInLoop" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AwaitWithoutCorrespondingSignal" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="BadOddness" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="BigDecimalEquals" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="BigDecimalLegacyMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="BooleanExpressionMayBeConditional" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CallToNativeMethodWhileLocked" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CallableParameterUseCaseInTypeContextInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="CascadeStringReplacementInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="USE_SHORT_ARRAYS_SYNTAX" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="CastConflictsWithInstanceof" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CastToIncompatibleInterface" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ChainedEquality" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ChannelResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="insideTryAllowed" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ClassIndependentOfModule" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassLoaderInstantiation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassMayBeInterface" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="reportClassesWithNonAbstractMethods" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ClassMethodNameMatchesFieldNameInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="ClassNameDiffersFromFileName" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassNewInstance" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassOnlyUsedInOneModule" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassOnlyUsedInOnePackage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassOverridesFieldOfSuperClassInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="REPORT_PRIVATE_REDEFINITION" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ClassUnconnectedToPackage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ClassWithOnlyPrivateConstructors" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CloneableClassInSecureContext" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CollectionContainsUrl" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CollectionsFieldAccessReplaceableByMethodCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ComparableImplementedButEqualsNotOverridden" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ComparatorNotSerializable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CompareToUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ComposeMissingKeys" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="ConditionSignal" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConditionalExpression" enabled="true" level="INFORMATION" enabled_by_default="true">
|
||||
<option name="ignoreSimpleAssignmentsAndReturns" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ConditionalExpressionWithIdenticalBranchesJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConfusingElse" enabled="false" level="WEAK WARNING" enabled_by_default="false">
|
||||
<option name="reportWhenNoStatementFollow" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ConfusingMainMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConfusingOctalEscape" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConfusingPlusesOrMinusesJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConstantJUnitAssertArgument" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConstantMathCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConstantTestNGAssertArgument" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ControlFlowStatementWithoutBraces" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConvertJavadoc" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConvertLambdaToReference" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ConvertOldAnnotations" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CssConvertColorToHexInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CssConvertColorToRgbInspection" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
||||
<inspection_tool class="CssMissingSemicolon" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CssReplaceWithShorthandUnsafely" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CustomClassloader" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CustomSecurityManager" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CyclicClassDependency" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CyclicPackageDependency" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DateToString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DefaultNotLastCaseInSwitch" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DisallowWritingIntoStaticPropertiesInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DisconnectedForeachInstructionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="DisjointPackage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DivideByZeroJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DocumentWriteJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DoubleBraceInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DoubleCheckedLocking" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreOnVolatileVariables" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="DriverManagerGetConnection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DuplicateBooleanBranch" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DuplicateConditionJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="DynamicallyGeneratedCodeJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ES6ConvertIndexedForToForOf" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ES6ConvertLetToConst" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ES6ConvertToForOf" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ES6ShorthandObjectProperty" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ES6TopLevelAwaitExpression" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="EmptyDirectory" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EmptyStatementBody" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_reportEmptyBlocks" value="true" />
|
||||
<option name="commentsAreContent" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="EmptySynchronizedStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EnumSwitchStatementWhichMissesCases" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="ignoreSwitchStatementsWithDefault" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="EqualsCalledOnEnumConstant" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EqualsHashCodeCalledOnUrl" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="EqualsUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ErrorRethrown" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ExceptionNameDoesntEndWithException" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ExceptionPackage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ExpectedExceptionNeverThrownTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ExplicitArgumentCanBeLambda" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ExtendsThread" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ExtendsThrowable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ExternalizableWithSerializationMethods" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FallthruInSwitchStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FieldAccessedSynchronizedAndUnsynchronized" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="countGettersAndSetters" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="FieldDeclarationSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="FieldHidesSuperclassField" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreInvisibleFields" value="false" />
|
||||
<option name="ignoreStaticFields" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="FieldMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="Finalize" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreTrivialFinalizers" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="FinalizeNotProtected" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FixedTimeStartWithInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FloatingPointEquality" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="FoldInitializerAndIfToElvis" enabled="false" level="INFO" enabled_by_default="false" />
|
||||
<inspection_tool class="ForLoopThatDoesntUseLoopVariableJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ForgottenDebugOutputInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="configuration">
|
||||
<list>
|
||||
<option value="\Codeception\Util\Debug::debug" />
|
||||
<option value="\Codeception\Util\Debug::pause" />
|
||||
<option value="\Doctrine\Common\Util\Debug::dump" />
|
||||
<option value="\Doctrine\Common\Util\Debug::export" />
|
||||
<option value="\Illuminate\Support\Debug\Dumper::dump" />
|
||||
<option value="\Symfony\Component\Debug\Debug::enable" />
|
||||
<option value="\Symfony\Component\Debug\DebugClassLoader::enable" />
|
||||
<option value="\Symfony\Component\Debug\ErrorHandler::register" />
|
||||
<option value="\Symfony\Component\Debug\ExceptionHandler::register" />
|
||||
<option value="\TYPO3\CMS\Core\Utility\DebugUtility::debug" />
|
||||
<option value="\Zend\Debug\Debug::dump" />
|
||||
<option value="\Zend\Di\Display\Console::export" />
|
||||
<option value="dd" />
|
||||
<option value="debug_print_backtrace" />
|
||||
<option value="debug_zval_dump" />
|
||||
<option value="dpm" />
|
||||
<option value="dpq" />
|
||||
<option value="dsm" />
|
||||
<option value="dump" />
|
||||
<option value="dvm" />
|
||||
<option value="error_log" />
|
||||
<option value="kpr" />
|
||||
<option value="phpinfo" />
|
||||
<option value="print_r" />
|
||||
<option value="var_dump" />
|
||||
<option value="var_export" />
|
||||
<option value="wp_die" />
|
||||
<option value="xdebug_break" />
|
||||
<option value="xdebug_call_class" />
|
||||
<option value="xdebug_call_file" />
|
||||
<option value="xdebug_call_function" />
|
||||
<option value="xdebug_call_line" />
|
||||
<option value="xdebug_code_coverage_started" />
|
||||
<option value="xdebug_debug_zval" />
|
||||
<option value="xdebug_debug_zval_stdout" />
|
||||
<option value="xdebug_dump_superglobals" />
|
||||
<option value="xdebug_enable" />
|
||||
<option value="xdebug_get_code_coverage" />
|
||||
<option value="xdebug_get_collected_errors" />
|
||||
<option value="xdebug_get_declared_vars" />
|
||||
<option value="xdebug_get_function_stack" />
|
||||
<option value="xdebug_get_headers" />
|
||||
<option value="xdebug_get_monitored_functions" />
|
||||
<option value="xdebug_get_profiler_filename" />
|
||||
<option value="xdebug_get_stack_depth" />
|
||||
<option value="xdebug_get_tracefile_name" />
|
||||
<option value="xdebug_is_enabled" />
|
||||
<option value="xdebug_memory_usage" />
|
||||
<option value="xdebug_peak_memory_usage" />
|
||||
<option value="xdebug_print_function_stack" />
|
||||
<option value="xdebug_start_code_coverage" />
|
||||
<option value="xdebug_start_error_collection" />
|
||||
<option value="xdebug_start_function_monitor" />
|
||||
<option value="xdebug_start_trace" />
|
||||
<option value="xdebug_stop_code_coverage" />
|
||||
<option value="xdebug_stop_error_collection" />
|
||||
<option value="xdebug_stop_function_monitor" />
|
||||
<option value="xdebug_stop_trace" />
|
||||
<option value="xdebug_time_index" />
|
||||
<option value="xdebug_var_dump" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="migratedIntoUserSpace" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="FunctionNamingConventionJS" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_regex" value="[a-z][A-Za-z]*" />
|
||||
<option name="m_minLength" value="0" />
|
||||
<option name="m_maxLength" value="99" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="FunctionWithInconsistentReturnsJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="HashCodeUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="HibernateResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="insideTryAllowed" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="HtmlMissingClosingTag" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="HtmlPresentationalElement" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="HtmlRequiredTitleAttribute" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
||||
<inspection_tool class="IOResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoredTypesString" value="java.io.ByteArrayOutputStream,java.io.ByteArrayInputStream,java.io.StringBufferInputStream,java.io.CharArrayWriter,java.io.CharArrayReader,java.io.StringWriter,java.io.StringReader" />
|
||||
<option name="insideTryAllowed" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="IfStatementWithIdenticalBranchesJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ImplicitDefaultCharsetUsage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InconsistentLanguageLevel" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InconsistentLineSeparators" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="IncrementDecrementOperationEquivalentInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="InnerClassOnInterface" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreInnerInterfaces" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="InnerClassReferencedViaSubclass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InnerClassVariableHidesOuterClassVariable" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreInvisibleFields" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="InsertLiteralUnderscores" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InstanceofCatchParameter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InstanceofIncompatibleInterface" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InstanceofThis" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InterfaceMayBeAnnotatedFunctional" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="IsEmptyFunctionUsageInspection" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="SUGGEST_TO_USE_COUNT_CHECK" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="IsNullFunctionUsageInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="IteratorNextDoesNotThrowNoSuchElementException" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JDBCExecuteWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JDBCPrepareStatementWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JDBCResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="insideTryAllowed" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="JNDIResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="insideTryAllowed" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="JSArrowFunctionBracesCanBeRemoved" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JSClassNamingConvention" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_minLength" value="0" />
|
||||
<option name="m_maxLength" value="99" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="JSConstructorReturnsPrimitive" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JSEqualityComparisonWithCoercion.TS" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="mySeverity" value="Always" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="JSJoinVariableDeclarationAndAssignment" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JSMissingSwitchBranches" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JSNonASCIINames" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="myAllowOnlyAscii" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="JSNonStrictModeUsed" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JSOctalInteger" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="myReportNonStrictEs5" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="JSUndeclaredVariable" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="myCheckGlobalDefinitions" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="JSUnusedGlobalSymbols" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="myReportUnusedDefinitions" value="true" />
|
||||
<option name="myReportUnusedProperties" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="JUnitDatapoint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JUnitRule" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JUnitTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JavadocHtmlLint" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="JoinDeclarationAndAssignmentJava" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LambdaCanBeMethodCall" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LengthOneStringInIndexOf" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LengthOneStringsInConcatenation" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LoadLibraryWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="LocalCanBeFinal" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="REPORT_VARIABLES" value="true" />
|
||||
<option name="REPORT_PARAMETERS" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="LocalVariableDeclarationSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="LocalVariableNamingConventionJS" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_regex" value="[a-z][A-Za-z]*" />
|
||||
<option name="m_minLength" value="0" />
|
||||
<option name="m_maxLength" value="99" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MalformedSetUpTearDown" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MapReplaceableByEnumMap" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodCallSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_onlyPrivateOrFinal" value="false" />
|
||||
<option name="m_ignoreEmptyMethods" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MethodMayBeSynchronized" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodOverloadsParentMethod" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="reportIncompatibleParameters" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MethodOverridesInaccessibleMethodOfSuper" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodOverridesStaticMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MethodSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="MisorderedAssertEqualsArgumentsTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MisorderedAssertEqualsParameters" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MisorderedModifiersInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="MissingOverrideAnnotation" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreObjectMethods" value="false" />
|
||||
<option name="ignoreAnonymousClassMethods" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MissortedModifiers" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_requireAnnotationsFirst" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MultipleTopLevelClassesInFile" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NakedNotify" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NegatedConditional" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreNegatedNullComparison" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="NestedAssignment" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NestedClassSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="NestedConditionalExpressionJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NestedSwitchStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NestedSynchronizedStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NestedTernaryOperatorInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="NewExpressionSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="NonBlockStatementBodyJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonExceptionNameEndsWithException" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonFinalClone" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonFinalFieldInEnum" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonFinalFieldOfException" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonFinalUtilityClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonReproducibleMathCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonSerializableFieldInSerializableClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignorableAnnotations">
|
||||
<value />
|
||||
</option>
|
||||
<option name="ignoreAnonymousInnerClasses" value="false" />
|
||||
<option name="superClassString" value="java.awt.Component" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="NonSerializableObjectBoundToHttpSession" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonSerializableObjectPassedToObjectStream" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonSerializableWithSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonSerializableWithSerializationMethods" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonShortCircuitBoolean" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonSynchronizedMethodOverridesSynchronizedMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NonThreadSafeLazyInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NotOptimalIfConditionsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="REPORT_DUPLICATE_CONDITIONS" value="false" />
|
||||
<option name="SUGGEST_OPTIMIZING_CONDITIONS" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="NotifyCalledOnCondition" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NotifyWithoutCorrespondingWait" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NullThrown" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="NumericToString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="OCInconsistentNaming" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ObjectAllocationIgnoredJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ObjectInstantiationInEqualsHashCode" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ObjectNotify" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ObjectToString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ObsoleteCollection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreRequiredObsoleteCollectionTypes" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="OctalAndDecimalIntegersMixed" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="OffsetOperationsInspection" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="OneTimeUseVariablesInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ALLOW_LONG_STATEMENTS" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="OverloadedVarargsMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PackageInMultipleModules" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ParameterHidingMemberVariable" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreInvisibleFields" value="true" />
|
||||
<option name="m_ignoreStaticMethodParametersHidingInstanceFields" value="true" />
|
||||
<option name="m_ignoreForConstructors" value="true" />
|
||||
<option name="m_ignoreForPropertySetters" value="true" />
|
||||
<option name="m_ignoreForAbstractMethods" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ParameterNamingConventionJS" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_regex" value="[a-z][A-Za-z]*" />
|
||||
<option name="m_minLength" value="0" />
|
||||
<option name="m_maxLength" value="99" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ParameterizedParametersStaticCollection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpAssignmentInConditionInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpAssignmentReplaceableWithOperatorAssignmentInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpAssignmentReplaceableWithPrefixExpressionInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpCSValidationInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false">
|
||||
<option name="EXTENSIONS" value="php,js,css,inc" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpClassNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_minLength" value="0" />
|
||||
<option name="m_maxLength" value="0" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpClosureCanBeConvertedToShortArrowFunctionInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpCompoundNamespaceDepthInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpConstantNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_minLength" value="0" />
|
||||
<option name="m_maxLength" value="0" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpConstantReassignmentInspection" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpDivisionByZeroInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpDocMissingThrowsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="SKIP_ON_EMPTY_PHPDOC" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpFunctionNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_minLength" value="0" />
|
||||
<option name="m_maxLength" value="0" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpInconsistentReturnPointsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ALLOW_RETURN_NULL_IN_VOID" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpLongTypeFormInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpLoopCanBeConvertedToArrayFillInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpLoopCanBeConvertedToArrayFilterInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpLoopCanBeConvertedToArrayMapInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpMethodNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_minLength" value="0" />
|
||||
<option name="m_maxLength" value="0" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpMethodOrClassCallIsNotCaseSensitiveInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpMissingParentCallMagicInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpMissingReturnTypeInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpMissingStrictTypesDeclarationInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpMissingVisibilityInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpNewClassMissingParameterListInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpNonCanonicalElementsOrderInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpOverridingMethodVisibilityInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpPropertyNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_regex" value="[a-z][_a-z\d]*" />
|
||||
<option name="m_minLength" value="0" />
|
||||
<option name="m_maxLength" value="0" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpRedundantClosingTagInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="PhpSeparateElseIfInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpShortOpenTagInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpSingleStatementWithBracesInspection" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
||||
<inspection_tool class="PhpStatementHasEmptyBodyInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="myCommentsCountAsContent" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpStatementWithoutBracesInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpStaticAsDynamicMethodCallInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="SHOW_FOR_MAGIC" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpTraditionalSyntaxArrayLiteralInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpTraitsUseListInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpUndefinedCallbackInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpUndefinedClassConstantInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="DOWNGRADE_SEVERITY" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpUndefinedMethodInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="DOWNGRADE_SEVERITY" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpUnused" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="SHOW_UNUSED_BY_ENTRIES" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpUnusedParameterInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="DONT_REPORT_ANONYMOUS" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpVarUsageInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpVariableNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_minLength" value="0" />
|
||||
<option name="m_maxLength" value="0" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpVariableVariableInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PointlessBitwiseExpressionJS" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreExpressionsContainingConstants" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ProblematicVarargsMethodOverride" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ProblematicWhitespace" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PropertyCanBeStaticInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ProtectedMemberInFinalClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PublicFieldAccessedInSynchronizedContext" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PublicStaticArrayField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PublicStaticCollectionField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PyAugmentAssignmentInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PyClassicStyleClassInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PyMissingTypeHintsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="m_onlyWhenTypesAreKnown" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="RawTypeCanBeGeneric" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReadObjectAndWriteObjectPrivate" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReadObjectInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReadResolveAndWriteReplaceProtected" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RedundantElseClauseInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="RedundantImplements" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreSerializable" value="true" />
|
||||
<option name="ignoreCloneable" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="RedundantObjectTypeCheck" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RedundantSuppression" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="IGNORE_ALL" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ReferencingObjectsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="RegExpOctalEscape" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReplaceAssignmentWithOperatorAssignment" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreLazyOperators" value="true" />
|
||||
<option name="ignoreObscureOperators" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ReplaceAssignmentWithOperatorAssignmentJS" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReplaceCollectionCountWithSize" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReplaceGuardClauseWithFunctionCall" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReplaceStringFormatWithLiteral" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReplaceSubstringWithDropLast" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReplaceSubstringWithIndexingOperation" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReplaceSubstringWithSubstringAfter" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReplaceSubstringWithSubstringBefore" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReplaceSubstringWithTake" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ResultOfObjectAllocationIgnored" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ResultSetIndexZero" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ReturnOfInnerClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RsSimplifyBooleanExpression" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RuntimeExec" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="RuntimeExecWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SafeLock" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SecurityAdvisoriesInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="optionConfiguration">
|
||||
<list>
|
||||
<option value="barryvdh/laravel-debugbar" />
|
||||
<option value="behat/behat" />
|
||||
<option value="brianium/paratest" />
|
||||
<option value="codeception/codeception" />
|
||||
<option value="codedungeon/phpunit-result-printer" />
|
||||
<option value="composer/composer" />
|
||||
<option value="doctrine/coding-standard" />
|
||||
<option value="filp/whoops" />
|
||||
<option value="friendsofphp/php-cs-fixer" />
|
||||
<option value="humbug/humbug" />
|
||||
<option value="infection/infection" />
|
||||
<option value="jakub-onderka/php-parallel-lint" />
|
||||
<option value="johnkary/phpunit-speedtrap" />
|
||||
<option value="kalessil/production-dependencies-guard" />
|
||||
<option value="mikey179/vfsStream" />
|
||||
<option value="mockery/mockery" />
|
||||
<option value="mybuilder/phpunit-accelerator" />
|
||||
<option value="orchestra/testbench" />
|
||||
<option value="pdepend/pdepend" />
|
||||
<option value="phan/phan" />
|
||||
<option value="phing/phing" />
|
||||
<option value="phpcompatibility/php-compatibility" />
|
||||
<option value="phpmd/phpmd" />
|
||||
<option value="phpro/grumphp" />
|
||||
<option value="phpspec/phpspec" />
|
||||
<option value="phpspec/prophecy" />
|
||||
<option value="phpstan/phpstan" />
|
||||
<option value="phpunit/phpunit" />
|
||||
<option value="povils/phpmnd" />
|
||||
<option value="roave/security-advisories" />
|
||||
<option value="satooshi/php-coveralls" />
|
||||
<option value="sebastian/phpcpd" />
|
||||
<option value="slevomat/coding-standard" />
|
||||
<option value="spatie/phpunit-watcher" />
|
||||
<option value="squizlabs/php_codesniffer" />
|
||||
<option value="sstalle/php7cc" />
|
||||
<option value="symfony/debug" />
|
||||
<option value="symfony/maker-bundle" />
|
||||
<option value="symfony/phpunit-bridge" />
|
||||
<option value="symfony/var-dumper" />
|
||||
<option value="vimeo/psalm" />
|
||||
<option value="wimg/php-compatibility" />
|
||||
<option value="wp-coding-standards/wpcs" />
|
||||
<option value="yiisoft/yii2-coding-standards" />
|
||||
<option value="yiisoft/yii2-debug" />
|
||||
<option value="yiisoft/yii2-gii" />
|
||||
<option value="zendframework/zend-coding-standard" />
|
||||
<option value="zendframework/zend-debug" />
|
||||
<option value="zendframework/zend-test" />
|
||||
</list>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SerialPersistentFieldsWithWrongSignature" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SerialVersionUIDNotStaticFinal" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SerializableHasSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreAnonymousInnerClasses" value="false" />
|
||||
<option name="superClassString" value="java.awt.Component" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SerializableHasSerializationMethods" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreAnonymousInnerClasses" value="false" />
|
||||
<option name="superClassString" value="java.awt.Component" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SerializableInnerClassHasSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreAnonymousInnerClasses" value="false" />
|
||||
<option name="superClassString" value="java.awt.Component" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SerializableInnerClassWithNonSerializableOuterClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreAnonymousInnerClasses" value="false" />
|
||||
<option name="superClassString" value="java.awt.Component" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SerializableStoresNonSerializable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SerializableWithUnconstructableAncestor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SetReplaceableByEnumSet" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SeveralTargetsMessage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SharedThreadLocalRandom" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ShortEchoTagCanBeUsedInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SignalWithoutCorrespondingAwait" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SimpleDateFormatWithoutLocale" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SimplifiableAnnotation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SimplifiableIfStatement" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SingleStatementInBlock" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
||||
<inspection_tool class="SizeReplaceableByIsEmpty" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SleepWhileHoldingLock" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SocketResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="insideTryAllowed" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||
<option name="processCode" value="true" />
|
||||
<option name="processLiterals" value="true" />
|
||||
<option name="processComments" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SqlGotoInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SqlRedundantOrderingDirectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlWithoutWhereInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="myDontWarnForLimit" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="StrTrUsageAsStrReplaceInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="StringBufferToStringInConcatenation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StringConcatenationInFormatCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StringConcatenationInMessageFormatCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StringConcatenationMissingWhitespace" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="StringEqualsEmptyString" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="SUPPRESS_FOR_VALUES_WHICH_COULD_BE_NULL" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="StringReplaceableByStringBuffer" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="onlyWarnOnLoop" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="StringToUpperWithoutLocale" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SubStrUsedAsStrPosInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SubtractionInCompareTo" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SuspiciousArrayCast" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SuspiciousIndentAfterControlStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SuspiciousLiteralUnderscore" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SwitchStatementWithConfusingDeclaration" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SynchronizationOnStaticField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SynchronizeOnLock" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SynchronizeOnThis" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SynchronizedMethod" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_includeNativeMethods" value="true" />
|
||||
<option name="ignoreSynchronizedSuperMethods" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SynchronizedOnLiteralObject" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SystemGC" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SystemGetenv" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SystemProperties" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SystemRunFinalizersOnExit" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SystemSetSecurityManager" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TestCaseInProductCode" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TestCaseWithConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TestCaseWithNoTestMethods" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreSupers" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="TestMethodInProductCode" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TestMethodWithoutAssertion" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TextLabelInSwitchStatementJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThreadLocalNotStaticFinal" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThreadPriority" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThreadStartInConstruction" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThreadStopSuspendResume" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThreadYield" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="ThrowCaughtLocally" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreRethrownExceptions" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ThrowRawExceptionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="TimeToString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TooBroadScope" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="m_allowConstructorAsInitializer" value="false" />
|
||||
<option name="m_onlyLookAtBlocks" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="TransientFieldInNonSerializableClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TransientFieldNotInitialized" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="TrivialStringConcatenation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnNecessaryDoubleQuotesInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnaryPlus" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnclearBinaryExpression" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="UnconditionalWait" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnconstrainedVariableType" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UndeclaredTests" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnknownInspectionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="UnnecessarilyQualifiedStaticUsage" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreStaticFieldAccesses" value="false" />
|
||||
<option name="m_ignoreStaticMethodCalls" value="false" />
|
||||
<option name="m_ignoreStaticAccessFromStaticContext" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnnecessarilyQualifiedStaticallyImportedElement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryBoxing" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="onlyReportSuperfluouslyBoxed" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnnecessaryConstantArrayCreationExpression" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryConstructor" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreAnnotations" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnnecessaryLocalVariable" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreImmediatelyReturnedVariables" value="true" />
|
||||
<option name="m_ignoreAnnotatedVariables" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnnecessaryLocalVariableJS" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreImmediatelyReturnedVariables" value="true" />
|
||||
<option name="m_ignoreAnnotatedVariables" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnnecessaryParentheses" enabled="true" level="INFORMATION" enabled_by_default="true">
|
||||
<option name="ignoreClarifyingParentheses" value="true" />
|
||||
<option name="ignoreParenthesesOnConditionals" value="false" />
|
||||
<option name="ignoreParenthesesOnLambdaParameter" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnnecessaryToStringCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryUnaryMinus" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UnnecessaryUnboxing" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="onlyReportSuperfluouslyUnboxed" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnsetConstructsCanBeMergedInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="UnterminatedStatementJS" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreSemicolonAtEndOfBlock" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnusedCatchParameterJS" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_ignoreCatchBlocksWithComments" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnusedProperty" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="UseOfAWTPeerClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UseOfJDBCDriverClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UseOfObsoleteAssert" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UseOfObsoleteDateTimeApi" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UseOfProcessBuilder" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UseOfPropertiesAsHashtable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UseOfSunClasses" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UsingInclusionReturnValueInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UtilityClassWithPublicConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UtilityClassWithoutPrivateConstructor" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignorableAnnotations">
|
||||
<value />
|
||||
</option>
|
||||
<option name="ignoreClassesWithOnlyMain" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="VariableNotUsedInsideIf" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="VariableUseSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="VoidExpressionJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="VolatileArrayField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="WaitCalledOnCondition" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="WaitNotInLoop" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="WaitNotifyNotInSynchronizedContext" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="WaitOrAwaitWithoutTimeout" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="WaitWhileHoldingTwoLocks" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="WaitWithoutCorrespondingNotify" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="WhileLoopSpinsOnField" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreNonEmtpyLoops" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ZeroLengthArrayInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
6
.idea/.idea.TweetDuck/.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/.idea.TweetDuck/.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="PROJECT_PROFILE" value="Project" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
6
.idea/.idea.TweetDuck/.idea/jsLibraryMappings.xml
generated
Normal file
6
.idea/.idea.TweetDuck/.idea/jsLibraryMappings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptLibraryMappings">
|
||||
<file url="PROJECT" libraries="{@types/jquery}" />
|
||||
</component>
|
||||
</project>
|
20
.idea/.idea.TweetDuck/.idea/runConfigurations/TweetDuck.xml
generated
Normal file
20
.idea/.idea.TweetDuck/.idea/runConfigurations/TweetDuck.xml
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="TweetDuck" type="DotNetProject" factoryName=".NET Project">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/bin/x86/Debug/TweetDuck.exe" />
|
||||
<option name="PROGRAM_PARAMETERS" value="-datafolder TweetDuckDebug -nogdpr" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/bin/x86/Debug" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||
<option name="PROJECT_PATH" value="$PROJECT_DIR$/TweetDuck.csproj" />
|
||||
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="Console" />
|
||||
<option name="PROJECT_TFM" value=".NETFramework,Version=v4.7.2" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
11
.idea/.idea.TweetDuck/.idea/vcs.xml
generated
Normal file
11
.idea/.idea.TweetDuck/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GitSharedSettings">
|
||||
<option name="FORCE_PUSH_PROHIBITED_PATTERNS">
|
||||
<list />
|
||||
</option>
|
||||
</component>
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
43
Application/FileDialogs.cs
Normal file
43
Application/FileDialogs.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Management;
|
||||
using TweetLib.Core.Application;
|
||||
using TweetLib.Core.Systems.Dialogs;
|
||||
|
||||
namespace TweetDuck.Application {
|
||||
sealed class FileDialogs : IAppFileDialogs {
|
||||
public void SaveFile(SaveFileDialogSettings settings, Action<string> onAccepted) {
|
||||
static string FormatFilter(FileDialogFilter filter) {
|
||||
var builder = new StringBuilder();
|
||||
builder.Append(filter.Name);
|
||||
|
||||
var extensions = string.Join(";", filter.Extensions.Select(ext => "*" + ext));
|
||||
if (extensions.Length > 0) {
|
||||
builder.Append(" (");
|
||||
builder.Append(extensions);
|
||||
builder.Append(")");
|
||||
}
|
||||
|
||||
builder.Append('|');
|
||||
builder.Append(extensions.Length == 0 ? "*.*" : extensions);
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
FormManager.RunOnUIThreadAsync(() => {
|
||||
using SaveFileDialog dialog = new SaveFileDialog {
|
||||
AutoUpgradeEnabled = true,
|
||||
OverwritePrompt = settings.OverwritePrompt,
|
||||
Title = settings.DialogTitle,
|
||||
FileName = settings.FileName,
|
||||
Filter = settings.Filters == null ? null : string.Join("|", settings.Filters.Select(FormatFilter))
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() == DialogResult.OK) {
|
||||
onAccepted(dialog.FileName);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
15
Application/MessageDialogs.cs
Normal file
15
Application/MessageDialogs.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using TweetDuck.Dialogs;
|
||||
using TweetDuck.Management;
|
||||
using TweetLib.Core.Application;
|
||||
|
||||
namespace TweetDuck.Application {
|
||||
sealed class MessageDialogs : IAppMessageDialogs {
|
||||
public void Information(string caption, string text, string buttonAccept) {
|
||||
FormManager.RunOnUIThreadAsync(() => FormMessage.Information(caption, text, buttonAccept));
|
||||
}
|
||||
|
||||
public void Error(string caption, string text, string buttonAccept) {
|
||||
FormManager.RunOnUIThreadAsync(() => FormMessage.Error(caption, text, buttonAccept));
|
||||
}
|
||||
}
|
||||
}
|
155
Application/SystemHandler.cs
Normal file
155
Application/SystemHandler.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Browser;
|
||||
using TweetDuck.Dialogs;
|
||||
using TweetDuck.Management;
|
||||
using TweetLib.Core;
|
||||
using TweetLib.Core.Application;
|
||||
using TweetLib.Core.Features.Twitter;
|
||||
using TweetLib.Core.Systems.Configuration;
|
||||
|
||||
namespace TweetDuck.Application {
|
||||
sealed class SystemHandler : IAppSystemHandler {
|
||||
public void OpenBrowser(string url) {
|
||||
if (string.IsNullOrWhiteSpace(url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
FormManager.RunOnUIThreadAsync(() => {
|
||||
var config = Program.Config.User;
|
||||
|
||||
switch (TwitterUrls.Check(url)) {
|
||||
case TwitterUrls.UrlType.Fine:
|
||||
string browserPath = config.BrowserPath;
|
||||
|
||||
if (browserPath == null || !File.Exists(browserPath)) {
|
||||
OpenAssociatedProgram(url);
|
||||
}
|
||||
else {
|
||||
string quotedUrl = '"' + url + '"';
|
||||
string browserArgs = config.BrowserPathArgs == null ? quotedUrl : config.BrowserPathArgs + ' ' + quotedUrl;
|
||||
|
||||
try {
|
||||
using (Process.Start(browserPath, browserArgs)) {}
|
||||
} catch (Exception e) {
|
||||
App.ErrorHandler.HandleException("Error Opening Browser", "Could not open the browser.", true, e);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case TwitterUrls.UrlType.Tracking:
|
||||
if (config.IgnoreTrackingUrlWarning) {
|
||||
goto case TwitterUrls.UrlType.Fine;
|
||||
}
|
||||
|
||||
using (FormMessage form = new FormMessage("Blocked URL", "TweetDuck has blocked a tracking url due to privacy concerns. Do you want to visit it anyway?\n" + url, MessageBoxIcon.Warning)) {
|
||||
form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel | ControlType.Focused);
|
||||
form.AddButton(FormMessage.Yes, DialogResult.Yes, ControlType.Accept);
|
||||
form.AddButton("Always Visit", DialogResult.Ignore);
|
||||
|
||||
DialogResult result = form.ShowDialog();
|
||||
|
||||
if (result == DialogResult.Ignore) {
|
||||
config.IgnoreTrackingUrlWarning = true;
|
||||
config.Save();
|
||||
}
|
||||
|
||||
if (result == DialogResult.Ignore || result == DialogResult.Yes) {
|
||||
goto case TwitterUrls.UrlType.Fine;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case TwitterUrls.UrlType.Invalid:
|
||||
FormMessage.Warning("Blocked URL", "A potentially malicious or invalid URL was blocked from opening:\n" + url, FormMessage.OK);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void OpenFileExplorer(string path) {
|
||||
if (File.Exists(path)) {
|
||||
using (Process.Start("explorer.exe", "/select,\"" + path.Replace('/', '\\') + "\"")) {}
|
||||
}
|
||||
else if (Directory.Exists(path)) {
|
||||
using (Process.Start("explorer.exe", '"' + path.Replace('/', '\\') + '"')) {}
|
||||
}
|
||||
}
|
||||
|
||||
public IAppSystemHandler.OpenAssociatedProgramFunc OpenAssociatedProgram { get; } = path => {
|
||||
try {
|
||||
using (Process.Start(new ProcessStartInfo {
|
||||
FileName = path,
|
||||
ErrorDialog = true
|
||||
})) {}
|
||||
} catch (Exception e) {
|
||||
App.ErrorHandler.HandleException("Error Opening Program", "Could not open the associated program for " + path, true, e);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public IAppSystemHandler.CopyImageFromFileFunc CopyImageFromFile { get; } = path => {
|
||||
FormManager.RunOnUIThreadAsync(() => {
|
||||
Image image;
|
||||
|
||||
try {
|
||||
image = Image.FromFile(path);
|
||||
} catch (Exception ex) {
|
||||
FormMessage.Error("Copy Image", "An error occurred while copying the image: " + ex.Message, FormMessage.OK);
|
||||
return;
|
||||
}
|
||||
|
||||
ClipboardManager.SetImage(image);
|
||||
});
|
||||
};
|
||||
|
||||
public IAppSystemHandler.CopyTextFunc CopyText { get; } = text => {
|
||||
FormManager.RunOnUIThreadAsync(() => ClipboardManager.SetText(text, TextDataFormat.UnicodeText));
|
||||
};
|
||||
|
||||
public IAppSystemHandler.SearchTextFunc SearchText { get; } = text => {
|
||||
if (string.IsNullOrWhiteSpace(text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
void PerformSearch() {
|
||||
var config = Program.Config.User;
|
||||
string searchUrl = config.SearchEngineUrl;
|
||||
|
||||
if (string.IsNullOrEmpty(searchUrl)) {
|
||||
if (FormMessage.Question("Search Options", "You have not configured a default search engine yet, would you like to do it now?", FormMessage.Yes, FormMessage.No)) {
|
||||
bool wereSettingsOpen = FormManager.TryFind<FormSettings>() != null;
|
||||
|
||||
FormManager.TryFind<FormBrowser>()?.OpenSettings();
|
||||
|
||||
if (wereSettingsOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
FormSettings settings = FormManager.TryFind<FormSettings>();
|
||||
|
||||
if (settings == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
settings.FormClosed += (sender, args) => {
|
||||
if (args.CloseReason == CloseReason.UserClosing && config.SearchEngineUrl != searchUrl) {
|
||||
PerformSearch();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
else {
|
||||
App.SystemHandler.OpenBrowser(searchUrl + Uri.EscapeUriString(text));
|
||||
}
|
||||
}
|
||||
|
||||
FormManager.RunOnUIThreadAsync(PerformSearch);
|
||||
};
|
||||
}
|
||||
}
|
125
Browser/Adapters/CefBrowserComponent.cs
Normal file
125
Browser/Adapters/CefBrowserComponent.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using TweetDuck.Browser.Handling;
|
||||
using TweetDuck.Management;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Browser.Base;
|
||||
using TweetLib.Browser.Events;
|
||||
using TweetLib.Browser.Interfaces;
|
||||
using TweetLib.Utils.Static;
|
||||
using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
|
||||
using IResourceRequestHandler = TweetLib.Browser.Interfaces.IResourceRequestHandler;
|
||||
|
||||
namespace TweetDuck.Browser.Adapters {
|
||||
abstract class CefBrowserComponent : IBrowserComponent {
|
||||
public bool Ready { get; private set; }
|
||||
|
||||
public string Url => browser.Address;
|
||||
public string CacheFolder => BrowserCache.CacheFolder;
|
||||
|
||||
public event EventHandler<BrowserLoadedEventArgs> BrowserLoaded;
|
||||
public event EventHandler<PageLoadEventArgs> PageLoadStart;
|
||||
public event EventHandler<PageLoadEventArgs> PageLoadEnd;
|
||||
|
||||
private readonly ChromiumWebBrowser browser;
|
||||
|
||||
protected CefBrowserComponent(ChromiumWebBrowser browser) {
|
||||
this.browser = browser;
|
||||
this.browser.JsDialogHandler = new JavaScriptDialogHandler();
|
||||
this.browser.LifeSpanHandler = new CustomLifeSpanHandler();
|
||||
this.browser.LoadingStateChanged += OnLoadingStateChanged;
|
||||
this.browser.LoadError += OnLoadError;
|
||||
this.browser.FrameLoadStart += OnFrameLoadStart;
|
||||
this.browser.FrameLoadEnd += OnFrameLoadEnd;
|
||||
this.browser.SetupZoomEvents();
|
||||
}
|
||||
|
||||
void IBrowserComponent.Setup(BrowserSetup setup) {
|
||||
browser.MenuHandler = SetupContextMenu(setup.ContextMenuHandler);
|
||||
browser.ResourceRequestHandlerFactory = SetupResourceHandlerFactory(setup.ResourceRequestHandler);
|
||||
}
|
||||
|
||||
protected abstract ContextMenuBase SetupContextMenu(IContextMenuHandler handler);
|
||||
|
||||
protected abstract CefResourceHandlerFactory SetupResourceHandlerFactory(IResourceRequestHandler handler);
|
||||
|
||||
private void OnLoadingStateChanged(object sender, LoadingStateChangedEventArgs e) {
|
||||
if (!e.IsLoading) {
|
||||
Ready = true;
|
||||
browser.LoadingStateChanged -= OnLoadingStateChanged;
|
||||
BrowserLoaded?.Invoke(this, new BrowserLoadedEventArgsImpl(browser));
|
||||
BrowserLoaded = null;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class BrowserLoadedEventArgsImpl : BrowserLoadedEventArgs {
|
||||
private readonly IWebBrowser browser;
|
||||
|
||||
public BrowserLoadedEventArgsImpl(IWebBrowser browser) {
|
||||
this.browser = browser;
|
||||
}
|
||||
|
||||
public override void AddDictionaryWords(params string[] words) {
|
||||
foreach (string word in words) {
|
||||
browser.AddWordToDictionary(word);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLoadError(object sender, LoadErrorEventArgs e) {
|
||||
if (e.ErrorCode == CefErrorCode.Aborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!e.FailedUrl.StartsWithOrdinal("td://resources/error/")) {
|
||||
string errorName = Enum.GetName(typeof(CefErrorCode), e.ErrorCode);
|
||||
string errorTitle = StringUtils.ConvertPascalCaseToScreamingSnakeCase(errorName ?? string.Empty);
|
||||
browser.Load("td://resources/error/error.html#" + Uri.EscapeDataString(errorTitle));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFrameLoadStart(object sender, FrameLoadStartEventArgs e) {
|
||||
if (e.Frame.IsMain) {
|
||||
PageLoadStart?.Invoke(this, new PageLoadEventArgs(e.Url));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFrameLoadEnd(object sender, FrameLoadEndEventArgs e) {
|
||||
if (e.Frame.IsMain) {
|
||||
PageLoadEnd?.Invoke(this, new PageLoadEventArgs(e.Url));
|
||||
}
|
||||
}
|
||||
|
||||
public void AttachBridgeObject(string name, object bridge) {
|
||||
browser.JavascriptObjectRepository.Settings.LegacyBindingEnabled = true;
|
||||
browser.JavascriptObjectRepository.Register(name, bridge, isAsync: true, BindingOptions.DefaultBinder);
|
||||
}
|
||||
|
||||
public void RunScript(string identifier, string script) {
|
||||
using IFrame frame = browser.GetMainFrame();
|
||||
frame.ExecuteJavaScriptAsync(script, identifier, 1);
|
||||
}
|
||||
|
||||
public void DownloadFile(string url, string path, Action onSuccess, Action<Exception> onError) {
|
||||
Cef.UIThreadTaskFactory.StartNew(() => {
|
||||
try {
|
||||
using IFrame frame = browser.GetMainFrame();
|
||||
var request = frame.CreateRequest(false);
|
||||
|
||||
request.Method = "GET";
|
||||
request.Url = url;
|
||||
request.Flags = UrlRequestFlags.AllowStoredCredentials;
|
||||
request.SetReferrer(Url, ReferrerPolicy.Default);
|
||||
|
||||
var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
var client = new DownloadRequestClient(fileStream, onSuccess, onError);
|
||||
frame.CreateUrlRequest(request, client);
|
||||
} catch (Exception e) {
|
||||
onError?.Invoke(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
28
Browser/Adapters/CefContextMenuActionRegistry.cs
Normal file
28
Browser/Adapters/CefContextMenuActionRegistry.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CefSharp;
|
||||
|
||||
namespace TweetDuck.Browser.Adapters {
|
||||
sealed class CefContextMenuActionRegistry {
|
||||
private readonly Dictionary<CefMenuCommand, Action> actions = new Dictionary<CefMenuCommand, Action>();
|
||||
|
||||
public CefMenuCommand AddAction(Action action) {
|
||||
CefMenuCommand id = CefMenuCommand.UserFirst + 500 + actions.Count;
|
||||
actions[id] = action;
|
||||
return id;
|
||||
}
|
||||
|
||||
public bool Execute(CefMenuCommand id) {
|
||||
if (actions.TryGetValue(id, out var action)) {
|
||||
action();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Clear() {
|
||||
actions.Clear();
|
||||
}
|
||||
}
|
||||
}
|
80
Browser/Adapters/CefContextMenuModel.cs
Normal file
80
Browser/Adapters/CefContextMenuModel.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using CefSharp;
|
||||
using TweetLib.Browser.Contexts;
|
||||
using TweetLib.Browser.Interfaces;
|
||||
using TweetLib.Core.Features.TweetDeck;
|
||||
using TweetLib.Core.Features.Twitter;
|
||||
|
||||
namespace TweetDuck.Browser.Adapters {
|
||||
sealed class CefContextMenuModel : IContextMenuBuilder {
|
||||
private readonly IMenuModel model;
|
||||
private readonly CefContextMenuActionRegistry actionRegistry;
|
||||
|
||||
public CefContextMenuModel(IMenuModel model, CefContextMenuActionRegistry actionRegistry) {
|
||||
this.model = model;
|
||||
this.actionRegistry = actionRegistry;
|
||||
}
|
||||
|
||||
public void AddAction(string name, Action action) {
|
||||
var id = actionRegistry.AddAction(action);
|
||||
model.AddItem(id, name);
|
||||
}
|
||||
|
||||
public void AddActionWithCheck(string name, bool isChecked, Action action) {
|
||||
var id = actionRegistry.AddAction(action);
|
||||
model.AddCheckItem(id, name);
|
||||
model.SetChecked(id, isChecked);
|
||||
}
|
||||
|
||||
public void AddSeparator() {
|
||||
if (model.Count > 0 && model.GetTypeAt(model.Count - 1) != MenuItemType.Separator) { // do not add separators if there is nothing to separate
|
||||
model.AddSeparator();
|
||||
}
|
||||
}
|
||||
|
||||
public static Context CreateContext(IContextMenuParams parameters, TweetDeckExtraContext extraContext, ImageQuality imageQuality) {
|
||||
var context = new Context();
|
||||
var flags = parameters.TypeFlags;
|
||||
|
||||
var tweet = extraContext?.Tweet;
|
||||
if (tweet != null && !flags.HasFlag(ContextMenuType.Editable)) {
|
||||
context.Tweet = tweet;
|
||||
}
|
||||
|
||||
context.Link = GetLink(parameters, extraContext);
|
||||
context.Media = GetMedia(parameters, extraContext, imageQuality);
|
||||
|
||||
if (flags.HasFlag(ContextMenuType.Selection)) {
|
||||
context.Selection = new Selection(parameters.SelectionText, flags.HasFlag(ContextMenuType.Editable));
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
private static Link? GetLink(IContextMenuParams parameters, TweetDeckExtraContext extraContext) {
|
||||
var link = extraContext?.Link;
|
||||
if (link != null) {
|
||||
return link;
|
||||
}
|
||||
|
||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && extraContext?.Media == null) {
|
||||
return new Link(parameters.LinkUrl, parameters.UnfilteredLinkUrl);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Media? GetMedia(IContextMenuParams parameters, TweetDeckExtraContext extraContext, ImageQuality imageQuality) {
|
||||
var media = extraContext?.Media;
|
||||
if (media != null) {
|
||||
return media;
|
||||
}
|
||||
|
||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) {
|
||||
return new Media(Media.Type.Image, TwitterUrls.GetMediaLink(parameters.SourceUrl, imageQuality));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
23
Browser/Adapters/CefResourceHandlerFactory.cs
Normal file
23
Browser/Adapters/CefResourceHandlerFactory.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using CefSharp;
|
||||
using IResourceRequestHandler = TweetLib.Browser.Interfaces.IResourceRequestHandler;
|
||||
|
||||
namespace TweetDuck.Browser.Adapters {
|
||||
sealed class CefResourceHandlerFactory : IResourceRequestHandlerFactory {
|
||||
bool IResourceRequestHandlerFactory.HasHandlers => registry != null;
|
||||
|
||||
private readonly CefResourceRequestHandler resourceRequestHandler;
|
||||
private readonly CefResourceHandlerRegistry registry;
|
||||
|
||||
public CefResourceHandlerFactory(IResourceRequestHandler resourceRequestHandler, CefResourceHandlerRegistry registry) {
|
||||
this.resourceRequestHandler = new CefResourceRequestHandler(registry, resourceRequestHandler);
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "RedundantAssignment")]
|
||||
CefSharp.IResourceRequestHandler IResourceRequestHandlerFactory.GetResourceRequestHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling) {
|
||||
disableDefaultHandling = registry != null && registry.HasHandler(request.Url);
|
||||
return resourceRequestHandler;
|
||||
}
|
||||
}
|
||||
}
|
42
Browser/Adapters/CefResourceHandlerRegistry.cs
Normal file
42
Browser/Adapters/CefResourceHandlerRegistry.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text;
|
||||
using CefSharp;
|
||||
|
||||
namespace TweetDuck.Browser.Adapters {
|
||||
sealed class CefResourceHandlerRegistry {
|
||||
private readonly ConcurrentDictionary<string, Func<IResourceHandler>> resourceHandlers = new ConcurrentDictionary<string, Func<IResourceHandler>>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public bool HasHandler(string url) {
|
||||
return resourceHandlers.ContainsKey(url);
|
||||
}
|
||||
|
||||
public IResourceHandler GetHandler(string url) {
|
||||
return resourceHandlers.TryGetValue(url, out var handler) ? handler() : null;
|
||||
}
|
||||
|
||||
private void Register(string url, Func<IResourceHandler> factory) {
|
||||
if (!Uri.TryCreate(url, UriKind.Absolute, out Uri uri)) {
|
||||
throw new ArgumentException("Resource handler URL must be absolute!");
|
||||
}
|
||||
|
||||
resourceHandlers.AddOrUpdate(uri.AbsoluteUri, factory, (key, prev) => factory);
|
||||
}
|
||||
|
||||
public void RegisterStatic(string url, byte[] staticData, string mimeType = ResourceHandler.DefaultMimeType) {
|
||||
Register(url, () => ResourceHandler.FromByteArray(staticData, mimeType));
|
||||
}
|
||||
|
||||
public void RegisterStatic(string url, string staticData, string mimeType = ResourceHandler.DefaultMimeType) {
|
||||
Register(url, () => ResourceHandler.FromString(staticData, Encoding.UTF8, mimeType: mimeType));
|
||||
}
|
||||
|
||||
public void RegisterDynamic(string url, IResourceHandler handler) {
|
||||
Register(url, () => handler);
|
||||
}
|
||||
|
||||
public void Unregister(string url) {
|
||||
resourceHandlers.TryRemove(url, out _);
|
||||
}
|
||||
}
|
||||
}
|
77
Browser/Adapters/CefResourceRequestHandler.cs
Normal file
77
Browser/Adapters/CefResourceRequestHandler.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System.Collections.Generic;
|
||||
using CefSharp;
|
||||
using CefSharp.Handler;
|
||||
using TweetDuck.Browser.Handling;
|
||||
using TweetLib.Browser.Interfaces;
|
||||
using TweetLib.Browser.Request;
|
||||
using IResourceRequestHandler = TweetLib.Browser.Interfaces.IResourceRequestHandler;
|
||||
using ResourceType = TweetLib.Browser.Request.ResourceType;
|
||||
|
||||
namespace TweetDuck.Browser.Adapters {
|
||||
sealed class CefResourceRequestHandler : ResourceRequestHandler {
|
||||
private readonly CefResourceHandlerRegistry resourceHandlerRegistry;
|
||||
private readonly IResourceRequestHandler resourceRequestHandler;
|
||||
private readonly Dictionary<ulong, IResponseProcessor> responseProcessors = new Dictionary<ulong, IResponseProcessor>();
|
||||
|
||||
public CefResourceRequestHandler(CefResourceHandlerRegistry resourceHandlerRegistry, IResourceRequestHandler resourceRequestHandler) {
|
||||
this.resourceHandlerRegistry = resourceHandlerRegistry;
|
||||
this.resourceRequestHandler = resourceRequestHandler;
|
||||
}
|
||||
|
||||
protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback) {
|
||||
if (request.ResourceType == CefSharp.ResourceType.CspReport) {
|
||||
callback.Dispose();
|
||||
return CefReturnValue.Cancel;
|
||||
}
|
||||
|
||||
if (resourceRequestHandler != null) {
|
||||
var result = resourceRequestHandler.Handle(request.Url, TranslateResourceType(request.ResourceType));
|
||||
|
||||
switch (result) {
|
||||
case RequestHandleResult.Redirect redirect:
|
||||
request.Url = redirect.Url;
|
||||
break;
|
||||
|
||||
case RequestHandleResult.Process process:
|
||||
request.SetHeaderByName("Accept-Encoding", "identity", overwrite: true);
|
||||
responseProcessors[request.Identifier] = process.Processor;
|
||||
break;
|
||||
|
||||
case RequestHandleResult.Cancel _:
|
||||
callback.Dispose();
|
||||
return CefReturnValue.Cancel;
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnBeforeResourceLoad(chromiumWebBrowser, browser, frame, request, callback);
|
||||
}
|
||||
|
||||
protected override IResourceHandler GetResourceHandler(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request) {
|
||||
return resourceHandlerRegistry?.GetHandler(request.Url);
|
||||
}
|
||||
|
||||
protected override IResponseFilter GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response) {
|
||||
if (responseProcessors.TryGetValue(request.Identifier, out var processor) && int.TryParse(response.Headers["Content-Length"], out int totalBytes)) {
|
||||
return new ResponseFilter(processor, totalBytes);
|
||||
}
|
||||
|
||||
return base.GetResourceResponseFilter(browserControl, browser, frame, request, response);
|
||||
}
|
||||
|
||||
protected override void OnResourceLoadComplete(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response, UrlRequestStatus status, long receivedContentLength) {
|
||||
responseProcessors.Remove(request.Identifier);
|
||||
base.OnResourceLoadComplete(chromiumWebBrowser, browser, frame, request, response, status, receivedContentLength);
|
||||
}
|
||||
|
||||
private static ResourceType TranslateResourceType(CefSharp.ResourceType resourceType) {
|
||||
return resourceType switch {
|
||||
CefSharp.ResourceType.MainFrame => ResourceType.MainFrame,
|
||||
CefSharp.ResourceType.Script => ResourceType.Script,
|
||||
CefSharp.ResourceType.Stylesheet => ResourceType.Stylesheet,
|
||||
CefSharp.ResourceType.Xhr => ResourceType.Xhr,
|
||||
CefSharp.ResourceType.Image => ResourceType.Image,
|
||||
_ => ResourceType.Unknown
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
29
Browser/Adapters/CefSchemeHandlerFactory.cs
Normal file
29
Browser/Adapters/CefSchemeHandlerFactory.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using TweetLib.Browser.Interfaces;
|
||||
|
||||
namespace TweetDuck.Browser.Adapters {
|
||||
sealed class CefSchemeHandlerFactory : ISchemeHandlerFactory {
|
||||
public static void Register(CefSettings settings, ICustomSchemeHandler handler) {
|
||||
settings.RegisterScheme(new CefCustomScheme {
|
||||
SchemeName = handler.Protocol,
|
||||
IsStandard = false,
|
||||
IsSecure = true,
|
||||
IsCorsEnabled = true,
|
||||
IsCSPBypassing = true,
|
||||
SchemeHandlerFactory = new CefSchemeHandlerFactory(handler)
|
||||
});
|
||||
}
|
||||
|
||||
private readonly ICustomSchemeHandler handler;
|
||||
|
||||
private CefSchemeHandlerFactory(ICustomSchemeHandler handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName, IRequest request) {
|
||||
return Uri.TryCreate(request.Url, UriKind.Absolute, out var uri) ? handler.Resolve(uri)?.Visit(CefSchemeResourceVisitor.Instance) : null;
|
||||
}
|
||||
}
|
||||
}
|
38
Browser/Adapters/CefSchemeResourceVisitor.cs
Normal file
38
Browser/Adapters/CefSchemeResourceVisitor.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using CefSharp;
|
||||
using TweetLib.Browser.Interfaces;
|
||||
using TweetLib.Browser.Request;
|
||||
|
||||
namespace TweetDuck.Browser.Adapters {
|
||||
sealed class CefSchemeResourceVisitor : ISchemeResourceVisitor<IResourceHandler> {
|
||||
public static CefSchemeResourceVisitor Instance { get; } = new CefSchemeResourceVisitor();
|
||||
|
||||
private static readonly SchemeResource.Status FileIsEmpty = new SchemeResource.Status(HttpStatusCode.NoContent, "File is empty.");
|
||||
|
||||
private CefSchemeResourceVisitor() {}
|
||||
|
||||
public IResourceHandler Status(SchemeResource.Status status) {
|
||||
var handler = CreateHandler(Array.Empty<byte>());
|
||||
handler.StatusCode = (int) status.Code;
|
||||
handler.StatusText = status.Message;
|
||||
return handler;
|
||||
}
|
||||
|
||||
public IResourceHandler File(SchemeResource.File file) {
|
||||
byte[] contents = file.Contents;
|
||||
if (contents.Length == 0) {
|
||||
return Status(FileIsEmpty); // FromByteArray crashes CEF internals with no contents
|
||||
}
|
||||
|
||||
var handler = CreateHandler(contents);
|
||||
handler.MimeType = Cef.GetMimeType(file.Extension);
|
||||
return handler;
|
||||
}
|
||||
|
||||
private static ResourceHandler CreateHandler(byte[] bytes) {
|
||||
return ResourceHandler.FromStream(new MemoryStream(bytes), autoDisposeStream: true);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,21 +1,10 @@
|
||||
namespace TweetDuck.Core {
|
||||
namespace TweetDuck.Browser {
|
||||
sealed partial class FormBrowser {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing && (components != null)) {
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
@@ -24,25 +13,32 @@
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.trayIcon = new TweetDuck.Core.TrayIcon(this.components);
|
||||
this.trayIcon = new TrayIcon(this.components);
|
||||
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
||||
this.timerResize = new System.Windows.Forms.Timer(this.components);
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// timerResize
|
||||
//
|
||||
this.timerResize.Interval = 500;
|
||||
this.timerResize.Tick += new System.EventHandler(this.timerResize_Tick);
|
||||
//
|
||||
// FormBrowser
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = TweetDuck.Core.Utils.BrowserUtils.BackgroundColor;
|
||||
this.ClientSize = new System.Drawing.Size(324, 386);
|
||||
this.BackColor = TweetDuck.Browser.TweetDeckBrowser.BackgroundColor;
|
||||
this.ClientSize = new System.Drawing.Size(1008, 730);
|
||||
this.Icon = Properties.Resources.icon;
|
||||
this.Location = TweetDuck.Core.Controls.ControlExtensions.InvisibleLocation;
|
||||
this.MinimumSize = new System.Drawing.Size(340, 424);
|
||||
this.Location = TweetDuck.Controls.ControlExtensions.InvisibleLocation;
|
||||
this.MinimumSize = new System.Drawing.Size(348, 424);
|
||||
this.Name = "FormBrowser";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
|
||||
this.Activated += new System.EventHandler(this.FormBrowser_Activated);
|
||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormBrowser_FormClosing);
|
||||
this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.FormBrowser_FormClosed);
|
||||
this.ResizeEnd += new System.EventHandler(this.FormBrowser_ResizeEnd);
|
||||
this.LocationChanged += new System.EventHandler(this.FormBrowser_LocationChanged);
|
||||
this.Resize += new System.EventHandler(this.FormBrowser_Resize);
|
||||
this.ResumeLayout(false);
|
||||
|
||||
@@ -52,6 +48,7 @@
|
||||
|
||||
private TrayIcon trayIcon;
|
||||
private System.Windows.Forms.ToolTip toolTip;
|
||||
private System.Windows.Forms.Timer timerResize;
|
||||
}
|
||||
}
|
||||
|
608
Browser/FormBrowser.cs
Normal file
608
Browser/FormBrowser.cs
Normal file
@@ -0,0 +1,608 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using TweetDuck.Browser.Handling;
|
||||
using TweetDuck.Browser.Notification;
|
||||
using TweetDuck.Browser.Notification.Screenshot;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Controls;
|
||||
using TweetDuck.Dialogs;
|
||||
using TweetDuck.Dialogs.Settings;
|
||||
using TweetDuck.Management;
|
||||
using TweetDuck.Updates;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Core;
|
||||
using TweetLib.Core.Features.Notifications;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
using TweetLib.Core.Features.TweetDeck;
|
||||
using TweetLib.Core.Resources;
|
||||
using TweetLib.Core.Systems.Configuration;
|
||||
using TweetLib.Core.Systems.Updates;
|
||||
|
||||
namespace TweetDuck.Browser {
|
||||
sealed partial class FormBrowser : Form, CustomKeyboardHandler.IBrowserKeyHandler {
|
||||
private static UserConfig Config => Program.Config.User;
|
||||
|
||||
public bool IsWaiting {
|
||||
set {
|
||||
if (value) {
|
||||
browser.Enabled = false;
|
||||
Cursor = Cursors.WaitCursor;
|
||||
}
|
||||
else {
|
||||
browser.Enabled = true;
|
||||
Cursor = Cursors.Default;
|
||||
|
||||
if (Focused) { // re-focus browser only if the window or a child is activated
|
||||
browser.Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public UpdateInstaller UpdateInstaller { get; private set; }
|
||||
|
||||
#pragma warning disable IDE0069 // Disposable fields should be disposed
|
||||
private readonly TweetDeckBrowser browser;
|
||||
private readonly FormNotificationTweet notification;
|
||||
#pragma warning restore IDE0069 // Disposable fields should be disposed
|
||||
|
||||
private readonly ResourceCache resourceCache;
|
||||
private readonly ITweetDeckInterface tweetDeckInterface;
|
||||
private readonly PluginManager plugins;
|
||||
private readonly UpdateChecker updates;
|
||||
private readonly ContextMenu contextMenu;
|
||||
private readonly uint windowRestoreMessage;
|
||||
|
||||
private bool isLoaded;
|
||||
private FormWindowState prevState;
|
||||
|
||||
private TweetScreenshotManager notificationScreenshotManager;
|
||||
private VideoPlayer videoPlayer;
|
||||
|
||||
public FormBrowser(ResourceCache resourceCache, PluginManager pluginManager, IUpdateCheckClient updateCheckClient, uint windowRestoreMessage) {
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName;
|
||||
|
||||
this.resourceCache = resourceCache;
|
||||
|
||||
this.plugins = pluginManager;
|
||||
|
||||
this.tweetDeckInterface = new TweetDeckInterfaceImpl(this);
|
||||
|
||||
this.notification = new FormNotificationTweet(this, tweetDeckInterface, plugins);
|
||||
this.notification.Show();
|
||||
|
||||
this.updates = new UpdateChecker(updateCheckClient, TaskScheduler.FromCurrentSynchronizationContext());
|
||||
this.updates.InteractionManager.UpdateAccepted += updateInteractionManager_UpdateAccepted;
|
||||
this.updates.InteractionManager.UpdateDismissed += updateInteractionManager_UpdateDismissed;
|
||||
|
||||
this.browser = new TweetDeckBrowser(this, plugins, tweetDeckInterface, updates);
|
||||
this.contextMenu = ContextMenuBrowser.CreateMenu(this);
|
||||
|
||||
this.windowRestoreMessage = windowRestoreMessage;
|
||||
|
||||
Controls.Add(new MenuStrip { Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update
|
||||
|
||||
Config.MuteToggled += Config_MuteToggled;
|
||||
Config.TrayBehaviorChanged += Config_TrayBehaviorChanged;
|
||||
|
||||
Disposed += (sender, args) => {
|
||||
Config.MuteToggled -= Config_MuteToggled;
|
||||
Config.TrayBehaviorChanged -= Config_TrayBehaviorChanged;
|
||||
browser.Dispose();
|
||||
};
|
||||
|
||||
this.trayIcon.ClickRestore += trayIcon_ClickRestore;
|
||||
this.trayIcon.ClickClose += trayIcon_ClickClose;
|
||||
|
||||
UpdateTray();
|
||||
|
||||
if (Config.MuteNotifications) {
|
||||
UpdateFormIcon();
|
||||
}
|
||||
|
||||
RestoreWindow();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing) {
|
||||
components?.Dispose();
|
||||
|
||||
updates.Dispose();
|
||||
contextMenu.Dispose();
|
||||
|
||||
notificationScreenshotManager?.Dispose();
|
||||
videoPlayer?.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private void ShowChildForm(Form form) {
|
||||
form.VisibleChanged += (sender, args) => form.MoveToCenter(this);
|
||||
form.Show(this);
|
||||
}
|
||||
|
||||
public void ForceClose() {
|
||||
trayIcon.Visible = false; // checked in FormClosing event
|
||||
Close();
|
||||
}
|
||||
|
||||
// window setup
|
||||
|
||||
private void RestoreWindow() {
|
||||
Config.BrowserWindow.Restore(this, true);
|
||||
browser.PrepareSize(ClientSize);
|
||||
|
||||
prevState = WindowState;
|
||||
isLoaded = true;
|
||||
}
|
||||
|
||||
private void UpdateFormIcon() { // TODO fix to show icon in taskbar too
|
||||
Icon = Config.MuteNotifications ? Properties.Resources.icon_muted : Properties.Resources.icon;
|
||||
}
|
||||
|
||||
private void UpdateTray() {
|
||||
trayIcon.Visible = Config.TrayBehavior.ShouldDisplayIcon();
|
||||
}
|
||||
|
||||
// event handlers
|
||||
|
||||
private void timerResize_Tick(object sender, EventArgs e) {
|
||||
FormBrowser_ResizeEnd(this, e); // also stops timer
|
||||
}
|
||||
|
||||
private void FormBrowser_Activated(object sender, EventArgs e) {
|
||||
if (!isLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
trayIcon.HasNotifications = false;
|
||||
|
||||
if (!browser.Enabled) { // when taking a screenshot, the window is unfocused and
|
||||
browser.Enabled = true; // the browser is disabled; if the user clicks back into
|
||||
} // the window, enable the browser again
|
||||
}
|
||||
|
||||
private void FormBrowser_LocationChanged(object sender, EventArgs e) {
|
||||
if (!isLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
timerResize.Stop();
|
||||
timerResize.Start();
|
||||
}
|
||||
|
||||
private void FormBrowser_Resize(object sender, EventArgs e) {
|
||||
if (!isLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (WindowState != prevState) {
|
||||
prevState = WindowState;
|
||||
|
||||
if (WindowState == FormWindowState.Minimized) {
|
||||
if (Config.TrayBehavior.ShouldHideOnMinimize()) {
|
||||
Hide(); // hides taskbar too?! welp that works I guess
|
||||
}
|
||||
}
|
||||
else {
|
||||
FormBrowser_ResizeEnd(sender, e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
timerResize.Stop();
|
||||
timerResize.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private void FormBrowser_ResizeEnd(object sender, EventArgs e) { // also triggers when the window moves
|
||||
if (!isLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
timerResize.Stop();
|
||||
browser.PrepareSize(ClientSize); // needed to pre-size browser control when launched in maximized state
|
||||
|
||||
if (Location != ControlExtensions.InvisibleLocation) {
|
||||
Config.BrowserWindow.Save(this);
|
||||
Config.Save();
|
||||
}
|
||||
}
|
||||
|
||||
private void FormBrowser_FormClosing(object sender, FormClosingEventArgs e) {
|
||||
if (!isLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Config.TrayBehavior.ShouldHideOnClose() && trayIcon.Visible && e.CloseReason == CloseReason.UserClosing) {
|
||||
Hide(); // hides taskbar too?! welp that works I guess
|
||||
e.Cancel = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void FormBrowser_FormClosed(object sender, FormClosedEventArgs e) {
|
||||
if (isLoaded && UpdateInstaller == null) {
|
||||
updates.InteractionManager.ClearUpdate();
|
||||
updates.InteractionManager.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void Config_MuteToggled(object sender, EventArgs e) {
|
||||
UpdateFormIcon();
|
||||
}
|
||||
|
||||
private void Config_TrayBehaviorChanged(object sender, EventArgs e) {
|
||||
UpdateTray();
|
||||
}
|
||||
|
||||
private void trayIcon_ClickRestore(object sender, EventArgs e) {
|
||||
Show();
|
||||
RestoreWindow();
|
||||
Activate();
|
||||
UpdateTray();
|
||||
}
|
||||
|
||||
private void trayIcon_ClickClose(object sender, EventArgs e) {
|
||||
ForceClose();
|
||||
}
|
||||
|
||||
private void updateInteractionManager_UpdateAccepted(object sender, UpdateInfo update) {
|
||||
this.InvokeAsyncSafe(() => {
|
||||
FormManager.CloseAllDialogs();
|
||||
|
||||
if (!string.IsNullOrEmpty(Config.DismissedUpdate)) {
|
||||
Config.DismissedUpdate = null;
|
||||
Config.Save();
|
||||
}
|
||||
|
||||
void OnFinished() {
|
||||
UpdateDownloadStatus status = update.DownloadStatus;
|
||||
|
||||
if (status == UpdateDownloadStatus.Done) {
|
||||
UpdateInstaller = new UpdateInstaller(update.InstallerPath);
|
||||
ForceClose();
|
||||
}
|
||||
else if (status != UpdateDownloadStatus.Canceled && FormMessage.Error("Update Has Failed", "Could not automatically download the update: " + (update.DownloadError?.Message ?? "unknown error") + "\n\nWould you like to open the website and try downloading the update manually?", FormMessage.Yes, FormMessage.No)) {
|
||||
App.SystemHandler.OpenBrowser(Program.Website);
|
||||
ForceClose();
|
||||
}
|
||||
else {
|
||||
Show();
|
||||
}
|
||||
}
|
||||
|
||||
if (update.DownloadStatus.IsFinished(true)) {
|
||||
OnFinished();
|
||||
}
|
||||
else {
|
||||
FormUpdateDownload downloadForm = new FormUpdateDownload(update);
|
||||
|
||||
downloadForm.VisibleChanged += (sender2, args2) => {
|
||||
downloadForm.MoveToCenter(this);
|
||||
Hide();
|
||||
};
|
||||
|
||||
downloadForm.FormClosed += (sender2, args2) => {
|
||||
if (downloadForm.DialogResult != DialogResult.OK) {
|
||||
update.CancelDownload();
|
||||
}
|
||||
|
||||
downloadForm.Dispose();
|
||||
OnFinished();
|
||||
};
|
||||
|
||||
downloadForm.Show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateInteractionManager_UpdateDismissed(object sender, UpdateInfo update) {
|
||||
this.InvokeAsyncSafe(() => {
|
||||
Config.DismissedUpdate = update.VersionTag;
|
||||
Config.Save();
|
||||
});
|
||||
}
|
||||
|
||||
protected override void WndProc(ref Message m) {
|
||||
if (isLoaded && m.Msg == windowRestoreMessage) {
|
||||
using Process me = Process.GetCurrentProcess();
|
||||
|
||||
if (me.Id == m.WParam.ToInt32()) {
|
||||
trayIcon_ClickRestore(trayIcon, EventArgs.Empty);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (browser.Ready && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN) {
|
||||
if (videoPlayer is { Running: true }) {
|
||||
videoPlayer.Close();
|
||||
}
|
||||
else {
|
||||
browser.Functions.OnMouseClickExtra((m.WParam.ToInt32() >> 16) & 0xFFFF);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
base.WndProc(ref m);
|
||||
}
|
||||
|
||||
// bridge methods
|
||||
|
||||
public void PauseNotification() {
|
||||
notification.PauseNotification();
|
||||
}
|
||||
|
||||
public void ResumeNotification() {
|
||||
notification.ResumeNotification();
|
||||
}
|
||||
|
||||
public void ReloadToTweetDeck() {
|
||||
#if DEBUG
|
||||
Resources.ResourceHotSwap.Run();
|
||||
resourceCache.ClearCache();
|
||||
#else
|
||||
if (ModifierKeys.HasFlag(Keys.Shift)) {
|
||||
resourceCache.ClearCache();
|
||||
}
|
||||
#endif
|
||||
|
||||
browser.ReloadToTweetDeck();
|
||||
}
|
||||
|
||||
public void OpenDevTools() {
|
||||
browser.OpenDevTools();
|
||||
}
|
||||
|
||||
// callback handlers
|
||||
|
||||
private void OnIntroductionClosed(bool showGuide) {
|
||||
if (Config.FirstRun) {
|
||||
Config.FirstRun = false;
|
||||
Config.Save();
|
||||
}
|
||||
|
||||
if (showGuide) {
|
||||
FormGuide.Show();
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenContextMenu() {
|
||||
contextMenu.Show(this, PointToClient(Cursor.Position));
|
||||
}
|
||||
|
||||
public void OpenSettings() {
|
||||
OpenSettings(null);
|
||||
}
|
||||
|
||||
public void OpenSettings(Type startTab) {
|
||||
if (!FormManager.TryBringToFront<FormSettings>()) {
|
||||
bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
|
||||
|
||||
FormSettings form = new FormSettings(this, plugins, updates, browser.Functions, startTab);
|
||||
|
||||
form.FormClosed += (sender, args) => {
|
||||
if (!prevEnableUpdateCheck && Config.EnableUpdateCheck) {
|
||||
Config.DismissedUpdate = null;
|
||||
Config.Save();
|
||||
|
||||
updates.Check(true);
|
||||
}
|
||||
|
||||
if (!Config.EnableTrayHighlight) {
|
||||
trayIcon.HasNotifications = false;
|
||||
}
|
||||
|
||||
BrowserCache.RefreshTimer();
|
||||
|
||||
if (form.ShouldReloadBrowser) {
|
||||
FormManager.TryFind<FormPlugins>()?.Close();
|
||||
plugins.Reload(); // also reloads the browser
|
||||
}
|
||||
else {
|
||||
Program.Config.User.TriggerOptionsDialogClosed();
|
||||
}
|
||||
|
||||
notification.RequiresResize = true;
|
||||
form.Dispose();
|
||||
};
|
||||
|
||||
ShowChildForm(form);
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenAbout() {
|
||||
if (!FormManager.TryBringToFront<FormAbout>()) {
|
||||
ShowChildForm(new FormAbout());
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenPlugins() {
|
||||
if (!FormManager.TryBringToFront<FormPlugins>()) {
|
||||
ShowChildForm(new FormPlugins(plugins));
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenProfileImport() {
|
||||
FormManager.TryFind<FormSettings>()?.Close();
|
||||
|
||||
using DialogSettingsManage dialog = new DialogSettingsManage(plugins, true);
|
||||
|
||||
if (!dialog.IsDisposed && dialog.ShowDialog() == DialogResult.OK && !dialog.IsRestarting) { // needs disposal check because the dialog may be closed in constructor
|
||||
BrowserProcessHandler.UpdatePrefs();
|
||||
FormManager.TryFind<FormPlugins>()?.Close();
|
||||
plugins.Reload(); // also reloads the browser
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowDesktopNotification(DesktopNotification notification) {
|
||||
this.notification.ShowNotification(notification);
|
||||
}
|
||||
|
||||
private void OnTweetNotification() { // may be called multiple times, once for each type of notification
|
||||
if (Config.EnableTrayHighlight && !ContainsFocus) {
|
||||
trayIcon.HasNotifications = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveVideo(string url, string username) {
|
||||
browser.SaveVideo(url, username);
|
||||
}
|
||||
|
||||
private void PlayVideo(string videoUrl, string tweetUrl, string username, IJavascriptCallback callShowOverlay) {
|
||||
if (Arguments.HasFlag(Arguments.ArgHttpVideo)) {
|
||||
videoUrl = Regex.Replace(videoUrl, "^https://", "http://");
|
||||
}
|
||||
|
||||
string playerPath = Config.VideoPlayerPath;
|
||||
|
||||
if (playerPath == null || !File.Exists(playerPath)) {
|
||||
if (videoPlayer == null) {
|
||||
videoPlayer = new VideoPlayer(this);
|
||||
videoPlayer.ProcessExited += (sender, args) => browser.HideVideoOverlay(true);
|
||||
}
|
||||
|
||||
callShowOverlay.ExecuteAsync();
|
||||
callShowOverlay.Dispose();
|
||||
|
||||
videoPlayer.Launch(videoUrl, tweetUrl, username);
|
||||
}
|
||||
else {
|
||||
callShowOverlay.Dispose();
|
||||
|
||||
string quotedUrl = '"' + videoUrl + '"';
|
||||
string playerArgs = Config.VideoPlayerPathArgs == null ? quotedUrl : Config.VideoPlayerPathArgs + ' ' + quotedUrl;
|
||||
|
||||
try {
|
||||
using (Process.Start(playerPath, playerArgs)) {}
|
||||
} catch (Exception e) {
|
||||
App.ErrorHandler.HandleException("Error Opening Video Player", "Could not open the video player.", true, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void StopVideo() {
|
||||
videoPlayer?.Close();
|
||||
}
|
||||
|
||||
public bool ShowTweetDetail(string columnId, string chirpId, string fallbackUrl) {
|
||||
Activate();
|
||||
|
||||
if (!browser.IsTweetDeckWebsite) {
|
||||
FormMessage.Error("View Tweet Detail", "TweetDeck is not currently loaded.", FormMessage.OK);
|
||||
return false;
|
||||
}
|
||||
|
||||
browser.Functions.ShowTweetDetail(columnId, chirpId, fallbackUrl);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnTweetScreenshotReady(string html, int width) {
|
||||
notificationScreenshotManager ??= new TweetScreenshotManager(this, plugins);
|
||||
notificationScreenshotManager.Trigger(html, width);
|
||||
}
|
||||
|
||||
private void DisplayTooltip(string text) {
|
||||
if (string.IsNullOrEmpty(text)) {
|
||||
toolTip.Hide(this);
|
||||
}
|
||||
else {
|
||||
Point position = PointToClient(Cursor.Position);
|
||||
position.Offset(20, 10);
|
||||
toolTip.Show(text, this, position);
|
||||
}
|
||||
}
|
||||
|
||||
public FormNotificationExample CreateExampleNotification() {
|
||||
return new FormNotificationExample(this, tweetDeckInterface, plugins);
|
||||
}
|
||||
|
||||
bool CustomKeyboardHandler.IBrowserKeyHandler.HandleBrowserKey(Keys key) {
|
||||
if (videoPlayer is { Running: true }) {
|
||||
videoPlayer.SendKeyEvent(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private sealed class TweetDeckInterfaceImpl : ITweetDeckInterface {
|
||||
private readonly FormBrowser form;
|
||||
|
||||
public TweetDeckInterfaceImpl(FormBrowser form) {
|
||||
this.form = form;
|
||||
}
|
||||
|
||||
public void Alert(string type, string contents) {
|
||||
MessageBoxIcon icon = type switch {
|
||||
"error" => MessageBoxIcon.Error,
|
||||
"warning" => MessageBoxIcon.Warning,
|
||||
"info" => MessageBoxIcon.Information,
|
||||
_ => MessageBoxIcon.None
|
||||
};
|
||||
|
||||
FormMessage.Show("TweetDuck Browser Message", contents, icon, FormMessage.OK);
|
||||
}
|
||||
|
||||
public void DisplayTooltip(string text) {
|
||||
form.InvokeAsyncSafe(() => form.DisplayTooltip(text));
|
||||
}
|
||||
|
||||
public void FixClipboard() {
|
||||
form.InvokeAsyncSafe(ClipboardManager.StripHtmlStyles);
|
||||
}
|
||||
|
||||
public int GetIdleSeconds() {
|
||||
return NativeMethods.GetIdleSeconds();
|
||||
}
|
||||
|
||||
public void OnIntroductionClosed(bool showGuide) {
|
||||
form.InvokeAsyncSafe(() => form.OnIntroductionClosed(showGuide));
|
||||
}
|
||||
|
||||
public void OnSoundNotification() {
|
||||
form.InvokeAsyncSafe(form.OnTweetNotification);
|
||||
}
|
||||
|
||||
public void OpenContextMenu() {
|
||||
form.InvokeAsyncSafe(form.OpenContextMenu);
|
||||
}
|
||||
|
||||
public void OpenProfileImport() {
|
||||
form.InvokeAsyncSafe(form.OpenProfileImport);
|
||||
}
|
||||
|
||||
public void PlayVideo(string videoUrl, string tweetUrl, string username, object callShowOverlay) {
|
||||
form.InvokeAsyncSafe(() => form.PlayVideo(videoUrl, tweetUrl, username, (IJavascriptCallback) callShowOverlay));
|
||||
}
|
||||
|
||||
public void ScreenshotTweet(string html, int width) {
|
||||
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width));
|
||||
}
|
||||
|
||||
public void ShowDesktopNotification(DesktopNotification notification) {
|
||||
form.InvokeAsyncSafe(() => {
|
||||
form.OnTweetNotification();
|
||||
form.ShowDesktopNotification(notification);
|
||||
});
|
||||
}
|
||||
|
||||
public void StopVideo() {
|
||||
form.InvokeAsyncSafe(form.StopVideo);
|
||||
}
|
||||
|
||||
public Task ExecuteCallback(object callback, params object[] parameters) {
|
||||
return ((IJavascriptCallback) callback).ExecuteAsync(parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -117,11 +117,13 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
||||
<data name="trayIcon.TrayLocation" type="System.Drawing.Point, System.Drawing">
|
||||
<metadata name="trayIcon.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</data>
|
||||
<data name="toolTip.TrayLocation" type="System.Drawing.Point, System.Drawing">
|
||||
</metadata>
|
||||
<metadata name="toolTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>112, 17</value>
|
||||
</data>
|
||||
</metadata>
|
||||
<metadata name="timerResize.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>202, 17</value>
|
||||
</metadata>
|
||||
</root>
|
28
Browser/Handling/BrowserProcessHandler.cs
Normal file
28
Browser/Handling/BrowserProcessHandler.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using CefSharp;
|
||||
using TweetDuck.Configuration;
|
||||
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
sealed class BrowserProcessHandler : IBrowserProcessHandler {
|
||||
public static Task UpdatePrefs() {
|
||||
return Cef.UIThreadTaskFactory.StartNew(UpdatePrefsInternal);
|
||||
}
|
||||
|
||||
private static void UpdatePrefsInternal() {
|
||||
UserConfig config = Program.Config.User;
|
||||
using IRequestContext ctx = Cef.GetGlobalRequestContext();
|
||||
|
||||
ctx.SetPreference("browser.enable_spellchecking", config.EnableSpellCheck, out string _);
|
||||
ctx.SetPreference("spellcheck.dictionary", config.SpellCheckLanguage, out string _);
|
||||
ctx.SetPreference("settings.a11y.animation_policy", config.EnableAnimatedImages ? "allowed" : "none", out string _);
|
||||
}
|
||||
|
||||
void IBrowserProcessHandler.OnContextInitialized() {
|
||||
UpdatePrefsInternal();
|
||||
}
|
||||
|
||||
void IBrowserProcessHandler.OnScheduleMessagePumpWork(long delay) {}
|
||||
void IDisposable.Dispose() {}
|
||||
}
|
||||
}
|
109
Browser/Handling/ContextMenuBase.cs
Normal file
109
Browser/Handling/ContextMenuBase.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using CefSharp;
|
||||
using TweetDuck.Browser.Adapters;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Browser.Contexts;
|
||||
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
abstract class ContextMenuBase : IContextMenuHandler {
|
||||
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand) 26500;
|
||||
|
||||
private static readonly HashSet<CefMenuCommand> AllowedCefCommands = new HashSet<CefMenuCommand> {
|
||||
CefMenuCommand.NotFound,
|
||||
CefMenuCommand.Undo,
|
||||
CefMenuCommand.Redo,
|
||||
CefMenuCommand.Cut,
|
||||
CefMenuCommand.Copy,
|
||||
CefMenuCommand.Paste,
|
||||
CefMenuCommand.Delete,
|
||||
CefMenuCommand.SelectAll,
|
||||
CefMenuCommand.SpellCheckSuggestion0,
|
||||
CefMenuCommand.SpellCheckSuggestion1,
|
||||
CefMenuCommand.SpellCheckSuggestion2,
|
||||
CefMenuCommand.SpellCheckSuggestion3,
|
||||
CefMenuCommand.SpellCheckSuggestion4,
|
||||
CefMenuCommand.SpellCheckNoSuggestions,
|
||||
CefMenuCommand.AddToDictionary
|
||||
};
|
||||
|
||||
protected static UserConfig Config => Program.Config.User;
|
||||
|
||||
private readonly TweetLib.Browser.Interfaces.IContextMenuHandler handler;
|
||||
private readonly CefContextMenuActionRegistry actionRegistry;
|
||||
|
||||
protected ContextMenuBase(TweetLib.Browser.Interfaces.IContextMenuHandler handler) {
|
||||
this.handler = handler;
|
||||
this.actionRegistry = new CefContextMenuActionRegistry();
|
||||
}
|
||||
|
||||
protected virtual Context CreateContext(IContextMenuParams parameters) {
|
||||
return CefContextMenuModel.CreateContext(parameters, null, Config.TwitterImageQuality);
|
||||
}
|
||||
|
||||
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
|
||||
for (int i = model.Count - 1; i >= 0; i--) {
|
||||
CefMenuCommand command = model.GetCommandIdAt(i);
|
||||
|
||||
if (!AllowedCefCommands.Contains(command) && !(command >= CefMenuCommand.CustomFirst && command <= CefMenuCommand.CustomLast)) {
|
||||
model.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = model.Count - 2; i >= 0; i--) {
|
||||
if (model.GetTypeAt(i) == MenuItemType.Separator && model.GetTypeAt(i + 1) == MenuItemType.Separator) {
|
||||
model.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (model.Count > 0 && model.GetTypeAt(0) == MenuItemType.Separator) {
|
||||
model.RemoveAt(0);
|
||||
}
|
||||
|
||||
AddSeparator(model);
|
||||
handler.Show(new CefContextMenuModel(model, actionRegistry), CreateContext(parameters));
|
||||
RemoveSeparatorIfLast(model);
|
||||
}
|
||||
|
||||
public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags) {
|
||||
if (actionRegistry.Execute(commandId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (commandId == MenuOpenDevTools) {
|
||||
browserControl.OpenDevToolsCustom(new Point(parameters.XCoord, parameters.YCoord));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame) {
|
||||
actionRegistry.Clear();
|
||||
}
|
||||
|
||||
public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback) {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected static void AddDebugMenuItems(IMenuModel model) {
|
||||
if (Config.DevToolsInContextMenu) {
|
||||
AddSeparator(model);
|
||||
model.AddItem(MenuOpenDevTools, "Open dev tools");
|
||||
}
|
||||
}
|
||||
|
||||
protected static void RemoveSeparatorIfLast(IMenuModel model) {
|
||||
if (model.Count > 0 && model.GetTypeAt(model.Count - 1) == MenuItemType.Separator) {
|
||||
model.RemoveAt(model.Count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void AddSeparator(IMenuModel model) {
|
||||
if (model.Count > 0 && model.GetTypeAt(model.Count - 1) != MenuItemType.Separator) { // do not add separators if there is nothing to separate
|
||||
model.AddSeparator();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
123
Browser/Handling/ContextMenuBrowser.cs
Normal file
123
Browser/Handling/ContextMenuBrowser.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using TweetDuck.Browser.Adapters;
|
||||
using TweetDuck.Controls;
|
||||
using TweetLib.Browser.Contexts;
|
||||
using TweetLib.Core.Features.TweetDeck;
|
||||
using TweetLib.Core.Features.Twitter;
|
||||
using TweetLib.Core.Systems.Configuration;
|
||||
using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
|
||||
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
sealed class ContextMenuBrowser : ContextMenuBase {
|
||||
private const CefMenuCommand MenuGlobal = (CefMenuCommand) 26600;
|
||||
private const CefMenuCommand MenuMute = (CefMenuCommand) 26601;
|
||||
private const CefMenuCommand MenuSettings = (CefMenuCommand) 26602;
|
||||
private const CefMenuCommand MenuPlugins = (CefMenuCommand) 26003;
|
||||
private const CefMenuCommand MenuAbout = (CefMenuCommand) 26604;
|
||||
|
||||
private const string TitleReloadBrowser = "Reload browser";
|
||||
private const string TitleMuteNotifications = "Mute notifications";
|
||||
private const string TitleSettings = "Options";
|
||||
private const string TitlePlugins = "Plugins";
|
||||
private const string TitleAboutProgram = "About " + Program.BrandName;
|
||||
|
||||
private readonly FormBrowser form;
|
||||
private readonly TweetDeckExtraContext extraContext;
|
||||
|
||||
public ContextMenuBrowser(FormBrowser form, IContextMenuHandler handler, TweetDeckExtraContext extraContext) : base(handler) {
|
||||
this.form = form;
|
||||
this.extraContext = extraContext;
|
||||
}
|
||||
|
||||
protected override Context CreateContext(IContextMenuParams parameters) {
|
||||
return CefContextMenuModel.CreateContext(parameters, extraContext, Config.TwitterImageQuality);
|
||||
}
|
||||
|
||||
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
|
||||
if (!TwitterUrls.IsTweetDeck(frame.Url) || browser.IsLoading) {
|
||||
extraContext.Reset();
|
||||
}
|
||||
|
||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||
|
||||
bool isSelecting = parameters.TypeFlags.HasFlag(ContextMenuType.Selection);
|
||||
bool isEditing = parameters.TypeFlags.HasFlag(ContextMenuType.Editable);
|
||||
|
||||
if (!isSelecting && !isEditing) {
|
||||
AddSeparator(model);
|
||||
|
||||
IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu(MenuGlobal, Program.BrandName);
|
||||
|
||||
globalMenu.AddItem(CefMenuCommand.Reload, TitleReloadBrowser);
|
||||
globalMenu.AddCheckItem(MenuMute, TitleMuteNotifications);
|
||||
globalMenu.SetChecked(MenuMute, Config.MuteNotifications);
|
||||
globalMenu.AddSeparator();
|
||||
|
||||
globalMenu.AddItem(MenuSettings, TitleSettings);
|
||||
globalMenu.AddItem(MenuPlugins, TitlePlugins);
|
||||
globalMenu.AddItem(MenuAbout, TitleAboutProgram);
|
||||
|
||||
AddDebugMenuItems(globalMenu);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags) {
|
||||
if (base.OnContextMenuCommand(browserControl, browser, frame, parameters, commandId, eventFlags)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (commandId) {
|
||||
case CefMenuCommand.Reload:
|
||||
form.InvokeAsyncSafe(form.ReloadToTweetDeck);
|
||||
return true;
|
||||
|
||||
case MenuSettings:
|
||||
form.InvokeAsyncSafe(form.OpenSettings);
|
||||
return true;
|
||||
|
||||
case MenuAbout:
|
||||
form.InvokeAsyncSafe(form.OpenAbout);
|
||||
return true;
|
||||
|
||||
case MenuPlugins:
|
||||
form.InvokeAsyncSafe(form.OpenPlugins);
|
||||
return true;
|
||||
|
||||
case MenuMute:
|
||||
form.InvokeAsyncSafe(ToggleMuteNotifications);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame) {
|
||||
base.OnContextMenuDismissed(browserControl, browser, frame);
|
||||
extraContext.Reset();
|
||||
}
|
||||
|
||||
public static ContextMenu CreateMenu(FormBrowser form) {
|
||||
ContextMenu menu = new ContextMenu();
|
||||
|
||||
menu.MenuItems.Add(TitleReloadBrowser, (sender, args) => form.ReloadToTweetDeck());
|
||||
menu.MenuItems.Add(TitleMuteNotifications, (sender, args) => ToggleMuteNotifications());
|
||||
menu.MenuItems.Add("-");
|
||||
menu.MenuItems.Add(TitleSettings, (sender, args) => form.OpenSettings());
|
||||
menu.MenuItems.Add(TitlePlugins, (sender, args) => form.OpenPlugins());
|
||||
menu.MenuItems.Add(TitleAboutProgram, (sender, args) => form.OpenAbout());
|
||||
|
||||
menu.Popup += (sender, args) => {
|
||||
menu.MenuItems[1].Checked = Config.MuteNotifications;
|
||||
};
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
private static void ToggleMuteNotifications() {
|
||||
Config.MuteNotifications = !Config.MuteNotifications;
|
||||
Config.Save();
|
||||
}
|
||||
}
|
||||
}
|
13
Browser/Handling/ContextMenuGuide.cs
Normal file
13
Browser/Handling/ContextMenuGuide.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using CefSharp;
|
||||
using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
|
||||
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
sealed class ContextMenuGuide : ContextMenuBase {
|
||||
public ContextMenuGuide(IContextMenuHandler handler) : base(handler) {}
|
||||
|
||||
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
|
||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||
AddDebugMenuItems(model);
|
||||
}
|
||||
}
|
||||
}
|
32
Browser/Handling/ContextMenuNotification.cs
Normal file
32
Browser/Handling/ContextMenuNotification.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using CefSharp;
|
||||
using TweetDuck.Browser.Notification;
|
||||
using TweetDuck.Controls;
|
||||
using TweetLib.Browser.Contexts;
|
||||
using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
|
||||
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
sealed class ContextMenuNotification : ContextMenuBase {
|
||||
private readonly FormNotificationBase form;
|
||||
|
||||
public ContextMenuNotification(FormNotificationBase form, IContextMenuHandler handler) : base(handler) {
|
||||
this.form = form;
|
||||
}
|
||||
|
||||
protected override Context CreateContext(IContextMenuParams parameters) {
|
||||
Context context = base.CreateContext(parameters);
|
||||
context.Notification = new TweetLib.Browser.Contexts.Notification(form.CurrentTweetUrl, form.CurrentQuoteUrl);
|
||||
return context;
|
||||
}
|
||||
|
||||
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
|
||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||
AddDebugMenuItems(model);
|
||||
form.InvokeAsyncSafe(() => form.ContextMenuOpen = true);
|
||||
}
|
||||
|
||||
public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame) {
|
||||
base.OnContextMenuDismissed(browserControl, browser, frame);
|
||||
form.InvokeAsyncSafe(() => form.ContextMenuOpen = false);
|
||||
}
|
||||
}
|
||||
}
|
43
Browser/Handling/CustomKeyboardHandler.cs
Normal file
43
Browser/Handling/CustomKeyboardHandler.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Utils.Static;
|
||||
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
sealed class CustomKeyboardHandler : IKeyboardHandler {
|
||||
private readonly IBrowserKeyHandler handler;
|
||||
|
||||
public CustomKeyboardHandler(IBrowserKeyHandler handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
bool IKeyboardHandler.OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut) {
|
||||
if (type != KeyType.RawKeyDown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
using (var frame = browser.FocusedFrame) {
|
||||
if (frame.Url.StartsWithOrdinal("devtools://")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Keys key = (Keys) windowsKeyCode;
|
||||
|
||||
if (modifiers == (CefEventFlags.ControlDown | CefEventFlags.ShiftDown) && key == Keys.I) {
|
||||
browserControl.OpenDevToolsCustom();
|
||||
return true;
|
||||
}
|
||||
|
||||
return handler != null && handler.HandleBrowserKey(key);
|
||||
}
|
||||
|
||||
bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public interface IBrowserKeyHandler {
|
||||
bool HandleBrowserKey(Keys key);
|
||||
}
|
||||
}
|
||||
}
|
37
Browser/Handling/CustomLifeSpanHandler.cs
Normal file
37
Browser/Handling/CustomLifeSpanHandler.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using CefSharp;
|
||||
using CefSharp.Handler;
|
||||
using TweetLib.Core;
|
||||
using TweetLib.Utils.Static;
|
||||
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
sealed class CustomLifeSpanHandler : LifeSpanHandler {
|
||||
private static bool IsPopupAllowed(string url) {
|
||||
return url.StartsWithOrdinal("https://twitter.com/teams/authorize?") ||
|
||||
url.StartsWithOrdinal("https://accounts.google.com/") ||
|
||||
url.StartsWithOrdinal("https://appleid.apple.com/");
|
||||
}
|
||||
|
||||
public static bool HandleLinkClick(WindowOpenDisposition targetDisposition, string targetUrl) {
|
||||
switch (targetDisposition) {
|
||||
case WindowOpenDisposition.NewBackgroundTab:
|
||||
case WindowOpenDisposition.NewForegroundTab:
|
||||
case WindowOpenDisposition.NewPopup when !IsPopupAllowed(targetUrl):
|
||||
case WindowOpenDisposition.NewWindow:
|
||||
App.SystemHandler.OpenBrowser(targetUrl);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnBeforePopup(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser) {
|
||||
newBrowser = null;
|
||||
return HandleLinkClick(targetDisposition, targetUrl);
|
||||
}
|
||||
|
||||
protected override bool DoClose(IWebBrowser browserControl, IBrowser browser) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
62
Browser/Handling/DownloadRequestClient.cs
Normal file
62
Browser/Handling/DownloadRequestClient.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using CefSharp;
|
||||
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
sealed class DownloadRequestClient : UrlRequestClient {
|
||||
private readonly FileStream fileStream;
|
||||
private readonly Action onSuccess;
|
||||
private readonly Action<Exception> onError;
|
||||
|
||||
private bool hasFailed;
|
||||
|
||||
public DownloadRequestClient(FileStream fileStream, Action onSuccess, Action<Exception> onError) {
|
||||
this.fileStream = fileStream;
|
||||
this.onSuccess = onSuccess;
|
||||
this.onError = onError;
|
||||
}
|
||||
|
||||
protected override bool GetAuthCredentials(bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback) {
|
||||
onError?.Invoke(new Exception("This URL requires authentication."));
|
||||
fileStream.Dispose();
|
||||
hasFailed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnDownloadData(IUrlRequest request, Stream data) {
|
||||
if (hasFailed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
data.CopyTo(fileStream);
|
||||
} catch (Exception e) {
|
||||
fileStream.Dispose();
|
||||
onError?.Invoke(e);
|
||||
hasFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnRequestComplete(IUrlRequest request) {
|
||||
if (hasFailed) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool isEmpty = fileStream.Position == 0;
|
||||
fileStream.Dispose();
|
||||
|
||||
var status = request.RequestStatus;
|
||||
if (status == UrlRequestStatus.Failed) {
|
||||
onError?.Invoke(new Exception("Unknown error."));
|
||||
}
|
||||
else if (status == UrlRequestStatus.Success) {
|
||||
if (isEmpty) {
|
||||
onError?.Invoke(new Exception("File is empty."));
|
||||
return;
|
||||
}
|
||||
|
||||
onSuccess?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
35
Browser/Handling/DragHandlerBrowser.cs
Normal file
35
Browser/Handling/DragHandlerBrowser.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Collections.Generic;
|
||||
using CefSharp;
|
||||
using CefSharp.Enums;
|
||||
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
sealed class DragHandlerBrowser : IDragHandler {
|
||||
private readonly RequestHandlerBrowser requestHandler;
|
||||
|
||||
public DragHandlerBrowser(RequestHandlerBrowser requestHandler) {
|
||||
this.requestHandler = requestHandler;
|
||||
}
|
||||
|
||||
public bool OnDragEnter(IWebBrowser browserControl, IBrowser browser, IDragData dragData, DragOperationsMask mask) {
|
||||
void TriggerDragStart(string type, string data = null) {
|
||||
browserControl.BrowserCore.ExecuteScriptAsync("window.TDGF_onGlobalDragStart", type, data);
|
||||
}
|
||||
|
||||
requestHandler.BlockNextUserNavUrl = dragData.LinkUrl; // empty if not a link
|
||||
|
||||
if (dragData.IsLink) {
|
||||
TriggerDragStart("link", dragData.LinkUrl);
|
||||
}
|
||||
else if (dragData.IsFragment) {
|
||||
TriggerDragStart("text", dragData.FragmentText.Trim());
|
||||
}
|
||||
else {
|
||||
TriggerDragStart("unknown");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnDraggableRegionsChanged(IWebBrowser browserControl, IBrowser browser, IFrame frame, IList<DraggableRegion> regions) {}
|
||||
}
|
||||
}
|
68
Browser/Handling/FileDialogHandler.cs
Normal file
68
Browser/Handling/FileDialogHandler.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using TweetLib.Core;
|
||||
using TweetLib.Utils.Static;
|
||||
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
sealed class FileDialogHandler : IDialogHandler {
|
||||
public bool OnFileDialog(IWebBrowser browserControl, IBrowser browser, CefFileDialogMode mode, CefFileDialogFlags flags, string title, string defaultFilePath, List<string> acceptFilters, int selectedAcceptFilter, IFileDialogCallback callback) {
|
||||
if (mode == CefFileDialogMode.Open || mode == CefFileDialogMode.OpenMultiple) {
|
||||
string allFilters = string.Join(";", acceptFilters.SelectMany(ParseFileType).Where(filter => !string.IsNullOrEmpty(filter)).Select(filter => "*" + filter));
|
||||
|
||||
using OpenFileDialog dialog = new OpenFileDialog {
|
||||
AutoUpgradeEnabled = true,
|
||||
DereferenceLinks = true,
|
||||
Multiselect = mode == CefFileDialogMode.OpenMultiple,
|
||||
Title = "Open Files",
|
||||
Filter = $"All Supported Formats ({allFilters})|{allFilters}|All Files (*.*)|*.*"
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() == DialogResult.OK) {
|
||||
string ext = Path.GetExtension(dialog.FileName)?.ToLower();
|
||||
callback.Continue(acceptFilters.FindIndex(filter => ParseFileType(filter).Contains(ext)), dialog.FileNames.ToList());
|
||||
}
|
||||
else {
|
||||
callback.Cancel();
|
||||
}
|
||||
|
||||
callback.Dispose();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
callback.Dispose();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<string> ParseFileType(string type) {
|
||||
if (string.IsNullOrEmpty(type)) {
|
||||
return StringUtils.EmptyArray;
|
||||
}
|
||||
|
||||
if (type[0] == '.') {
|
||||
return new string[] { type };
|
||||
}
|
||||
|
||||
string[] extensions = type switch {
|
||||
"image/jpeg" => new string[] { ".jpg", ".jpeg" },
|
||||
"image/png" => new string[] { ".png" },
|
||||
"image/gif" => new string[] { ".gif" },
|
||||
"image/webp" => new string[] { ".webp" },
|
||||
"video/mp4" => new string[] { ".mp4" },
|
||||
"video/quicktime" => new string[] { ".mov", ".qt" },
|
||||
_ => StringUtils.EmptyArray
|
||||
};
|
||||
|
||||
if (extensions.Length == 0) {
|
||||
App.Logger.Warn("Unknown file type: " + type);
|
||||
Debugger.Break();
|
||||
}
|
||||
|
||||
return extensions;
|
||||
}
|
||||
}
|
||||
}
|
96
Browser/Handling/JavaScriptDialogHandler.cs
Normal file
96
Browser/Handling/JavaScriptDialogHandler.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using TweetDuck.Controls;
|
||||
using TweetDuck.Dialogs;
|
||||
using TweetDuck.Utils;
|
||||
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
sealed class JavaScriptDialogHandler : IJsDialogHandler {
|
||||
private static FormMessage CreateMessageForm(string caption, string text) {
|
||||
MessageBoxIcon icon = MessageBoxIcon.None;
|
||||
int pipe = text.IndexOf('|');
|
||||
|
||||
if (pipe != -1) {
|
||||
icon = text.Substring(0, pipe) switch {
|
||||
"error" => MessageBoxIcon.Error,
|
||||
"warning" => MessageBoxIcon.Warning,
|
||||
"info" => MessageBoxIcon.Information,
|
||||
"question" => MessageBoxIcon.Question,
|
||||
_ => MessageBoxIcon.None
|
||||
};
|
||||
|
||||
if (icon != MessageBoxIcon.None) {
|
||||
text = text.Substring(pipe + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return new FormMessage(caption, text, icon);
|
||||
}
|
||||
|
||||
bool IJsDialogHandler.OnJSDialog(IWebBrowser browserControl, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage) {
|
||||
var control = (ChromiumWebBrowser) browserControl;
|
||||
|
||||
control.InvokeSafe(() => {
|
||||
FormMessage form;
|
||||
TextBox input = null;
|
||||
|
||||
if (dialogType == CefJsDialogType.Alert) {
|
||||
form = CreateMessageForm("Browser Message", messageText);
|
||||
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
|
||||
}
|
||||
else if (dialogType == CefJsDialogType.Confirm) {
|
||||
form = CreateMessageForm("Browser Confirmation", messageText);
|
||||
form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel);
|
||||
form.AddButton(FormMessage.Yes, ControlType.Focused);
|
||||
}
|
||||
else if (dialogType == CefJsDialogType.Prompt) {
|
||||
form = CreateMessageForm("Browser Prompt", messageText);
|
||||
form.AddButton(FormMessage.Cancel, DialogResult.Cancel, ControlType.Cancel);
|
||||
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
|
||||
|
||||
float dpiScale = form.GetDPIScale();
|
||||
int inputPad = form.HasIcon ? 43 : 0;
|
||||
|
||||
input = new TextBox {
|
||||
Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
|
||||
Font = SystemFonts.MessageBoxFont,
|
||||
Location = new Point(BrowserUtils.Scale(22 + inputPad, dpiScale), form.ActionPanelY - BrowserUtils.Scale(46, dpiScale)),
|
||||
Size = new Size(form.ClientSize.Width - BrowserUtils.Scale(44 + inputPad, dpiScale), BrowserUtils.Scale(23, dpiScale))
|
||||
};
|
||||
|
||||
form.Controls.Add(input);
|
||||
form.ActiveControl = input;
|
||||
form.Height += input.Size.Height + input.Margin.Vertical;
|
||||
}
|
||||
else {
|
||||
callback.Continue(false);
|
||||
return;
|
||||
}
|
||||
|
||||
bool success = form.ShowDialog() == DialogResult.OK;
|
||||
|
||||
if (input == null) {
|
||||
callback.Continue(success);
|
||||
}
|
||||
else {
|
||||
callback.Continue(success, input.Text);
|
||||
input.Dispose();
|
||||
}
|
||||
|
||||
form.Dispose();
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IJsDialogHandler.OnBeforeUnloadDialog(IWebBrowser browserControl, IBrowser browser, string messageText, bool isReload, IJsDialogCallback callback) {
|
||||
callback.Dispose();
|
||||
return false;
|
||||
}
|
||||
|
||||
void IJsDialogHandler.OnResetDialogState(IWebBrowser browserControl, IBrowser browser) {}
|
||||
void IJsDialogHandler.OnDialogClosed(IWebBrowser browserControl, IBrowser browser) {}
|
||||
}
|
||||
}
|
22
Browser/Handling/RequestHandlerBase.cs
Normal file
22
Browser/Handling/RequestHandlerBase.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using CefSharp;
|
||||
using CefSharp.Handler;
|
||||
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
class RequestHandlerBase : RequestHandler {
|
||||
private readonly bool autoReload;
|
||||
|
||||
public RequestHandlerBase(bool autoReload) {
|
||||
this.autoReload = autoReload;
|
||||
}
|
||||
|
||||
protected override bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture) {
|
||||
return CustomLifeSpanHandler.HandleLinkClick(targetDisposition, targetUrl);
|
||||
}
|
||||
|
||||
protected override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status) {
|
||||
if (autoReload) {
|
||||
browser.Reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
23
Browser/Handling/RequestHandlerBrowser.cs
Normal file
23
Browser/Handling/RequestHandlerBrowser.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using CefSharp;
|
||||
using TweetLib.Core.Features.Twitter;
|
||||
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
sealed class RequestHandlerBrowser : RequestHandlerBase {
|
||||
public string BlockNextUserNavUrl { get; set; }
|
||||
|
||||
public RequestHandlerBrowser() : base(true) {}
|
||||
|
||||
protected override bool OnBeforeBrowse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect) {
|
||||
if (userGesture && request.TransitionType == TransitionType.LinkClicked) {
|
||||
bool block = request.Url == BlockNextUserNavUrl;
|
||||
BlockNextUserNavUrl = string.Empty;
|
||||
return block;
|
||||
}
|
||||
else if (request.TransitionType.HasFlag(TransitionType.ForwardBack) && TwitterUrls.IsTweetDeck(frame.Url)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnBeforeBrowse(browserControl, browser, frame, request, userGesture, isRedirect);
|
||||
}
|
||||
}
|
||||
}
|
79
Browser/Handling/ResourceHandlerNotification.cs
Normal file
79
Browser/Handling/ResourceHandlerNotification.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using CefSharp;
|
||||
using CefSharp.Callback;
|
||||
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
sealed class ResourceHandlerNotification : IResourceHandler {
|
||||
private readonly NameValueCollection headers = new NameValueCollection(0);
|
||||
private MemoryStream dataIn;
|
||||
|
||||
public void SetHTML(string html) {
|
||||
dataIn?.Dispose();
|
||||
dataIn = ResourceHandler.GetMemoryStream(html, Encoding.UTF8);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
if (dataIn != null) {
|
||||
dataIn.Dispose();
|
||||
dataIn = null;
|
||||
}
|
||||
}
|
||||
|
||||
bool IResourceHandler.Open(IRequest request, out bool handleRequest, ICallback callback) {
|
||||
callback.Dispose();
|
||||
handleRequest = true;
|
||||
|
||||
if (dataIn != null) {
|
||||
dataIn.Position = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void IResourceHandler.GetResponseHeaders(IResponse response, out long responseLength, out string redirectUrl) {
|
||||
redirectUrl = null;
|
||||
|
||||
response.MimeType = "text/html";
|
||||
response.StatusCode = 200;
|
||||
response.StatusText = "OK";
|
||||
response.Headers = headers;
|
||||
responseLength = dataIn?.Length ?? 0;
|
||||
}
|
||||
|
||||
bool IResourceHandler.Read(Stream dataOut, out int bytesRead, IResourceReadCallback callback) {
|
||||
callback?.Dispose(); // TODO unnecessary null check once ReadResponse is removed
|
||||
|
||||
try {
|
||||
byte[] buffer = new byte[Math.Min(dataIn.Length - dataIn.Position, dataOut.Length)];
|
||||
int length = buffer.Length;
|
||||
|
||||
dataIn.Read(buffer, 0, length);
|
||||
dataOut.Write(buffer, 0, length);
|
||||
bytesRead = length;
|
||||
} catch { // catch IOException, possibly NullReferenceException if dataIn is null
|
||||
bytesRead = 0;
|
||||
}
|
||||
|
||||
return bytesRead > 0;
|
||||
}
|
||||
|
||||
bool IResourceHandler.Skip(long bytesToSkip, out long bytesSkipped, IResourceSkipCallback callback) {
|
||||
bytesSkipped = -2; // ERR_FAILED
|
||||
callback.Dispose();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IResourceHandler.ProcessRequest(IRequest request, ICallback callback) {
|
||||
return ((IResourceHandler) this).Open(request, out bool _, callback);
|
||||
}
|
||||
|
||||
bool IResourceHandler.ReadResponse(Stream dataOut, out int bytesRead, ICallback callback) {
|
||||
return ((IResourceHandler) this).Read(dataOut, out bytesRead, null);
|
||||
}
|
||||
|
||||
void IResourceHandler.Cancel() {}
|
||||
}
|
||||
}
|
76
Browser/Handling/ResponseFilter.cs
Normal file
76
Browser/Handling/ResponseFilter.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using CefSharp;
|
||||
using TweetLib.Browser.Interfaces;
|
||||
|
||||
namespace TweetDuck.Browser.Handling {
|
||||
sealed class ResponseFilter : IResponseFilter {
|
||||
private enum State {
|
||||
Reading,
|
||||
Writing,
|
||||
Done
|
||||
}
|
||||
|
||||
private readonly IResponseProcessor processor;
|
||||
private byte[] responseData;
|
||||
|
||||
private State state;
|
||||
private int offset;
|
||||
|
||||
public ResponseFilter(IResponseProcessor processor, int totalBytes) {
|
||||
this.processor = processor;
|
||||
this.responseData = new byte[totalBytes];
|
||||
this.state = State.Reading;
|
||||
}
|
||||
|
||||
public bool InitFilter() {
|
||||
return true;
|
||||
}
|
||||
|
||||
FilterStatus IResponseFilter.Filter(Stream dataIn, out long dataInRead, Stream dataOut, out long dataOutWritten) {
|
||||
int responseLength = responseData.Length;
|
||||
|
||||
if (state == State.Reading) {
|
||||
int bytesToRead = Math.Min(responseLength - offset, (int) Math.Min(dataIn?.Length ?? 0, int.MaxValue));
|
||||
|
||||
dataIn?.Read(responseData, offset, bytesToRead);
|
||||
offset += bytesToRead;
|
||||
|
||||
dataInRead = bytesToRead;
|
||||
dataOutWritten = 0;
|
||||
|
||||
if (offset >= responseLength) {
|
||||
responseData = processor.Process(responseData);
|
||||
state = State.Writing;
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
return FilterStatus.NeedMoreData;
|
||||
}
|
||||
else if (state == State.Writing) {
|
||||
int bytesToWrite = Math.Min(responseLength - offset, (int) Math.Min(dataOut.Length, int.MaxValue));
|
||||
|
||||
if (bytesToWrite > 0) {
|
||||
dataOut.Write(responseData, offset, bytesToWrite);
|
||||
offset += bytesToWrite;
|
||||
}
|
||||
|
||||
dataOutWritten = bytesToWrite;
|
||||
dataInRead = 0;
|
||||
|
||||
if (offset < responseLength) {
|
||||
return FilterStatus.NeedMoreData;
|
||||
}
|
||||
else {
|
||||
state = State.Done;
|
||||
return FilterStatus.Done;
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new InvalidOperationException("This resource filter cannot be reused.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {}
|
||||
}
|
||||
}
|
@@ -1,21 +1,10 @@
|
||||
namespace TweetDuck.Core.Notification {
|
||||
namespace TweetDuck.Browser.Notification {
|
||||
partial class FormNotificationBase {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing && (components != null)) {
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
@@ -34,7 +23,7 @@
|
||||
this.BackColor = System.Drawing.SystemColors.Control;
|
||||
this.ClientSize = new System.Drawing.Size(284, 122);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
|
||||
this.Location = TweetDuck.Core.Controls.ControlExtensions.InvisibleLocation;
|
||||
this.Location = TweetDuck.Controls.ControlExtensions.InvisibleLocation;
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "FormNotification";
|
251
Browser/Notification/FormNotificationBase.cs
Normal file
251
Browser/Notification/FormNotificationBase.cs
Normal file
@@ -0,0 +1,251 @@
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp.WinForms;
|
||||
using TweetDuck.Browser.Adapters;
|
||||
using TweetDuck.Browser.Handling;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Controls;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Browser.Interfaces;
|
||||
using TweetLib.Core.Features.Notifications;
|
||||
using TweetLib.Core.Features.Twitter;
|
||||
using TweetLib.Core.Systems.Configuration;
|
||||
|
||||
namespace TweetDuck.Browser.Notification {
|
||||
abstract partial class FormNotificationBase : Form {
|
||||
protected static UserConfig Config => Program.Config.User;
|
||||
|
||||
protected delegate NotificationBrowser CreateBrowserImplFunc(FormNotificationBase form, IBrowserComponent browserComponent);
|
||||
|
||||
protected virtual Point PrimaryLocation {
|
||||
get {
|
||||
Screen screen;
|
||||
|
||||
if (Config.NotificationDisplay > 0 && Config.NotificationDisplay <= Screen.AllScreens.Length) {
|
||||
screen = Screen.AllScreens[Config.NotificationDisplay - 1];
|
||||
}
|
||||
else {
|
||||
screen = Screen.FromControl(owner);
|
||||
}
|
||||
|
||||
int edgeDist = Config.NotificationEdgeDistance;
|
||||
|
||||
switch (Config.NotificationPosition) {
|
||||
case DesktopNotification.Position.TopLeft:
|
||||
return new Point(screen.WorkingArea.X + edgeDist, screen.WorkingArea.Y + edgeDist);
|
||||
|
||||
case DesktopNotification.Position.TopRight:
|
||||
return new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + edgeDist);
|
||||
|
||||
case DesktopNotification.Position.BottomLeft:
|
||||
return new Point(screen.WorkingArea.X + edgeDist, screen.WorkingArea.Y + screen.WorkingArea.Height - edgeDist - Height);
|
||||
|
||||
case DesktopNotification.Position.BottomRight:
|
||||
return new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + screen.WorkingArea.Height - edgeDist - Height);
|
||||
|
||||
case DesktopNotification.Position.Custom:
|
||||
if (!Config.IsCustomNotificationPositionSet) {
|
||||
Config.CustomNotificationPosition = new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + edgeDist);
|
||||
Config.Save();
|
||||
}
|
||||
|
||||
return Config.CustomNotificationPosition;
|
||||
}
|
||||
|
||||
return Location;
|
||||
}
|
||||
}
|
||||
|
||||
protected bool IsNotificationVisible => Location != ControlExtensions.InvisibleLocation;
|
||||
protected virtual bool CanDragWindow => true;
|
||||
|
||||
public new Point Location {
|
||||
get {
|
||||
return base.Location;
|
||||
}
|
||||
|
||||
set {
|
||||
Visible = (base.Location = value) != ControlExtensions.InvisibleLocation;
|
||||
FormBorderStyle = NotificationBorderStyle;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual FormBorderStyle NotificationBorderStyle {
|
||||
get {
|
||||
if (WindowsUtils.ShouldAvoidToolWindow && Visible) { // Visible = workaround for alt+tab
|
||||
return FormBorderStyle.FixedSingle;
|
||||
}
|
||||
else {
|
||||
return FormBorderStyle.FixedToolWindow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool ShowWithoutActivation => true;
|
||||
|
||||
protected float DpiScale { get; }
|
||||
protected double SizeScale => DpiScale * Config.ZoomLevel / 100.0;
|
||||
|
||||
private readonly FormBrowser owner;
|
||||
|
||||
protected readonly IBrowserComponent browserComponent;
|
||||
private readonly NotificationBrowser browserImpl;
|
||||
|
||||
#pragma warning disable IDE0069 // Disposable fields should be disposed
|
||||
protected readonly ChromiumWebBrowser browser;
|
||||
#pragma warning restore IDE0069 // Disposable fields should be disposed
|
||||
|
||||
private readonly ResourceHandlerNotification resourceHandler = new ResourceHandlerNotification();
|
||||
|
||||
private DesktopNotification currentNotification;
|
||||
private int pauseCounter;
|
||||
|
||||
public string CurrentTweetUrl => currentNotification?.TweetUrl;
|
||||
public string CurrentQuoteUrl => currentNotification?.QuoteUrl;
|
||||
|
||||
protected bool IsPaused => pauseCounter > 0;
|
||||
protected internal bool IsCursorOverBrowser => browser.Bounds.Contains(PointToClient(Cursor.Position));
|
||||
|
||||
public bool FreezeTimer { get; set; }
|
||||
public bool ContextMenuOpen { get; set; }
|
||||
|
||||
protected FormNotificationBase(FormBrowser owner, CreateBrowserImplFunc createBrowserImpl) {
|
||||
InitializeComponent();
|
||||
|
||||
this.owner = owner;
|
||||
this.owner.FormClosed += owner_FormClosed;
|
||||
|
||||
this.browser = new ChromiumWebBrowser(NotificationBrowser.BlankURL) {
|
||||
RequestHandler = new RequestHandlerBase(false)
|
||||
};
|
||||
|
||||
this.browserComponent = new ComponentImpl(browser, this);
|
||||
this.browserImpl = createBrowserImpl(this, browserComponent);
|
||||
|
||||
this.browser.Dock = DockStyle.None;
|
||||
this.browser.ClientSize = ClientSize;
|
||||
|
||||
Controls.Add(browser);
|
||||
|
||||
Disposed += (sender, args) => {
|
||||
this.owner.FormClosed -= owner_FormClosed;
|
||||
this.browserImpl.Dispose();
|
||||
this.browser.Dispose();
|
||||
};
|
||||
|
||||
DpiScale = this.GetDPIScale();
|
||||
|
||||
// ReSharper disable once VirtualMemberCallInContructor
|
||||
UpdateTitle();
|
||||
}
|
||||
|
||||
protected sealed class ComponentImpl : CefBrowserComponent {
|
||||
private readonly FormNotificationBase owner;
|
||||
|
||||
public ComponentImpl(ChromiumWebBrowser browser, FormNotificationBase owner) : base(browser) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
protected override ContextMenuBase SetupContextMenu(IContextMenuHandler handler) {
|
||||
return new ContextMenuNotification(owner, handler);
|
||||
}
|
||||
|
||||
protected override CefResourceHandlerFactory SetupResourceHandlerFactory(IResourceRequestHandler handler) {
|
||||
var registry = new CefResourceHandlerRegistry();
|
||||
registry.RegisterStatic(NotificationBrowser.BlankURL, string.Empty);
|
||||
registry.RegisterDynamic(TwitterUrls.TweetDeck, owner.resourceHandler);
|
||||
return new CefResourceHandlerFactory(handler, registry);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing) {
|
||||
components?.Dispose();
|
||||
resourceHandler.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
protected override void WndProc(ref Message m) {
|
||||
if (m.Msg == 0x0112 && (m.WParam.ToInt32() & 0xFFF0) == 0xF010 && !CanDragWindow) { // WM_SYSCOMMAND, SC_MOVE
|
||||
return;
|
||||
}
|
||||
|
||||
base.WndProc(ref m);
|
||||
}
|
||||
|
||||
// event handlers
|
||||
|
||||
private void owner_FormClosed(object sender, FormClosedEventArgs e) {
|
||||
Close();
|
||||
}
|
||||
|
||||
// notification methods
|
||||
|
||||
public virtual void HideNotification() {
|
||||
browser.Load(NotificationBrowser.BlankURL);
|
||||
DisplayTooltip(null);
|
||||
|
||||
Location = ControlExtensions.InvisibleLocation;
|
||||
currentNotification = null;
|
||||
}
|
||||
|
||||
public virtual void FinishCurrentNotification() {}
|
||||
|
||||
public virtual void PauseNotification() {
|
||||
if (pauseCounter++ == 0 && IsNotificationVisible) {
|
||||
Location = ControlExtensions.InvisibleLocation;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ResumeNotification() {
|
||||
if (pauseCounter > 0) {
|
||||
--pauseCounter;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void LoadTweet(DesktopNotification tweet) {
|
||||
currentNotification = tweet;
|
||||
resourceHandler.SetHTML(browserImpl.GetTweetHTML(tweet));
|
||||
|
||||
browser.Load(TwitterUrls.TweetDeck);
|
||||
DisplayTooltip(null);
|
||||
}
|
||||
|
||||
protected virtual void SetNotificationSize(int width, int height) {
|
||||
browser.ClientSize = ClientSize = new Size(BrowserUtils.Scale(width, SizeScale), BrowserUtils.Scale(height, SizeScale));
|
||||
}
|
||||
|
||||
protected virtual void UpdateTitle() {
|
||||
string title = currentNotification?.ColumnTitle;
|
||||
Text = string.IsNullOrEmpty(title) || !Config.DisplayNotificationColumn ? Program.BrandName : $"{Program.BrandName} - {title}";
|
||||
}
|
||||
|
||||
public void ShowTweetDetail() {
|
||||
if (currentNotification != null && owner.ShowTweetDetail(currentNotification.ColumnId, currentNotification.ChirpId, currentNotification.TweetUrl)) {
|
||||
FinishCurrentNotification();
|
||||
}
|
||||
}
|
||||
|
||||
public void MoveToVisibleLocation() {
|
||||
bool needsReactivating = Location == ControlExtensions.InvisibleLocation;
|
||||
Location = PrimaryLocation;
|
||||
|
||||
if (needsReactivating) {
|
||||
NativeMethods.SetFormPos(this, NativeMethods.HWND_TOPMOST, NativeMethods.SWP_NOACTIVATE);
|
||||
}
|
||||
}
|
||||
|
||||
public void DisplayTooltip(string text) {
|
||||
if (string.IsNullOrEmpty(text)) {
|
||||
toolTip.Hide(this);
|
||||
}
|
||||
else {
|
||||
Point position = PointToClient(Cursor.Position);
|
||||
position.Offset(20, 5);
|
||||
toolTip.Show(text, this, position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
62
Browser/Notification/FormNotificationExample.cs
Normal file
62
Browser/Notification/FormNotificationExample.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Controls;
|
||||
using TweetLib.Browser.Interfaces;
|
||||
using TweetLib.Core.Features.Notifications;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
using TweetLib.Core.Features.TweetDeck;
|
||||
using TweetLib.Core.Resources;
|
||||
|
||||
namespace TweetDuck.Browser.Notification {
|
||||
sealed class FormNotificationExample : FormNotificationMain {
|
||||
private static NotificationBrowser CreateBrowserImpl(IBrowserComponent browserComponent, INotificationInterface notificationInterface, ITweetDeckInterface tweetDeckInterface, PluginManager pluginManager) {
|
||||
return new NotificationBrowser.Example(browserComponent, notificationInterface, tweetDeckInterface, pluginManager);
|
||||
}
|
||||
|
||||
public override bool RequiresResize => true;
|
||||
protected override bool CanDragWindow => Config.NotificationPosition == DesktopNotification.Position.Custom;
|
||||
|
||||
protected override FormBorderStyle NotificationBorderStyle {
|
||||
get {
|
||||
if (Config.NotificationSize == DesktopNotification.Size.Custom) {
|
||||
switch (base.NotificationBorderStyle) {
|
||||
case FormBorderStyle.FixedSingle: return FormBorderStyle.Sizable;
|
||||
case FormBorderStyle.FixedToolWindow: return FormBorderStyle.SizableToolWindow;
|
||||
}
|
||||
}
|
||||
|
||||
return base.NotificationBorderStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler Ready;
|
||||
|
||||
private readonly DesktopNotification exampleNotification;
|
||||
|
||||
public FormNotificationExample(FormBrowser owner, ITweetDeckInterface tweetDeckInterface, PluginManager pluginManager) : base(owner, (form, browserComponent) => CreateBrowserImpl(browserComponent, new NotificationInterfaceImpl(form), tweetDeckInterface, pluginManager)) {
|
||||
browserComponent.BrowserLoaded += (sender, args) => {
|
||||
Ready?.Invoke(this, EventArgs.Empty);
|
||||
};
|
||||
|
||||
string exampleTweetHTML = ResourceUtils.ReadFileOrNull("notification/example/example.html") ?? string.Empty;
|
||||
exampleNotification = new DesktopNotification(string.Empty, string.Empty, "Home", exampleTweetHTML, 176, string.Empty, string.Empty);
|
||||
}
|
||||
|
||||
public override void HideNotification() {
|
||||
Location = ControlExtensions.InvisibleLocation;
|
||||
}
|
||||
|
||||
public override void FinishCurrentNotification() {}
|
||||
|
||||
public void ShowExampleNotification(bool reset) {
|
||||
if (reset) {
|
||||
LoadTweet(exampleNotification);
|
||||
}
|
||||
else {
|
||||
PrepareAndDisplayWindow();
|
||||
}
|
||||
|
||||
UpdateTitle();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
namespace TweetDuck.Core.Notification {
|
||||
namespace TweetDuck.Browser.Notification {
|
||||
partial class FormNotificationMain {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
@@ -26,7 +26,7 @@
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.timerDisplayDelay = new System.Windows.Forms.Timer(this.components);
|
||||
this.timerProgress = new System.Windows.Forms.Timer(this.components);
|
||||
this.progressBarTimer = new TweetDuck.Core.Controls.FlatProgressBar();
|
||||
this.progressBarTimer = new TweetDuck.Controls.FlatProgressBar();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// timerDisplayDelay
|
318
Browser/Notification/FormNotificationMain.cs
Normal file
318
Browser/Notification/FormNotificationMain.cs
Normal file
@@ -0,0 +1,318 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using TweetDuck.Browser.Handling;
|
||||
using TweetDuck.Controls;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Core.Features.Notifications;
|
||||
|
||||
namespace TweetDuck.Browser.Notification {
|
||||
abstract partial class FormNotificationMain : FormNotificationBase, CustomKeyboardHandler.IBrowserKeyHandler {
|
||||
protected sealed class NotificationInterfaceImpl : INotificationInterface {
|
||||
public bool FreezeTimer {
|
||||
get => notification.FreezeTimer;
|
||||
set => notification.FreezeTimer = value;
|
||||
}
|
||||
|
||||
public bool IsHovered => notification.IsCursorOverBrowser;
|
||||
|
||||
private readonly FormNotificationBase notification;
|
||||
|
||||
public NotificationInterfaceImpl(FormNotificationBase notification) {
|
||||
this.notification = notification;
|
||||
}
|
||||
|
||||
public void DisplayTooltip(string text) {
|
||||
notification.InvokeAsyncSafe(() => notification.DisplayTooltip(text));
|
||||
}
|
||||
|
||||
public void FinishCurrentNotification() {
|
||||
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
|
||||
}
|
||||
|
||||
public void ShowTweetDetail() {
|
||||
notification.InvokeAsyncSafe(notification.ShowTweetDetail);
|
||||
}
|
||||
}
|
||||
|
||||
private static int FontSizeLevel {
|
||||
get => NotificationBrowser.FontSize switch {
|
||||
"largest" => 4,
|
||||
"large" => 3,
|
||||
"small" => 1,
|
||||
"smallest" => 0,
|
||||
_ => 2
|
||||
};
|
||||
}
|
||||
|
||||
private readonly int timerBarHeight;
|
||||
|
||||
protected int timeLeft, totalTime;
|
||||
protected bool pausedDuringNotification;
|
||||
|
||||
private readonly NativeMethods.HookProc mouseHookDelegate;
|
||||
private IntPtr mouseHook;
|
||||
private bool blockXButtonUp;
|
||||
|
||||
private int currentOpacity;
|
||||
|
||||
private bool? prevDisplayTimer;
|
||||
private int? prevFontSize;
|
||||
|
||||
public virtual bool RequiresResize {
|
||||
get {
|
||||
return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Config.DisplayNotificationTimer || prevFontSize != FontSizeLevel;
|
||||
}
|
||||
|
||||
set {
|
||||
if (value) {
|
||||
prevDisplayTimer = null;
|
||||
prevFontSize = null;
|
||||
}
|
||||
else {
|
||||
prevDisplayTimer = Config.DisplayNotificationTimer;
|
||||
prevFontSize = FontSizeLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int BaseClientWidth {
|
||||
get => Config.NotificationSize switch {
|
||||
DesktopNotification.Size.Custom => Config.CustomNotificationSize.Width,
|
||||
_ => BrowserUtils.Scale(284, SizeScale * (1.0 + 0.05 * FontSizeLevel))
|
||||
};
|
||||
}
|
||||
|
||||
private int BaseClientHeight {
|
||||
get => Config.NotificationSize switch {
|
||||
DesktopNotification.Size.Custom => Config.CustomNotificationSize.Height,
|
||||
_ => BrowserUtils.Scale(122, SizeScale * (1.0 + 0.08 * FontSizeLevel))
|
||||
};
|
||||
}
|
||||
|
||||
public Size BrowserSize => Config.DisplayNotificationTimer ? new Size(ClientSize.Width, ClientSize.Height - timerBarHeight) : ClientSize;
|
||||
|
||||
protected FormNotificationMain(FormBrowser owner, CreateBrowserImplFunc createBrowserImpl) : base(owner, createBrowserImpl) {
|
||||
InitializeComponent();
|
||||
|
||||
this.timerBarHeight = BrowserUtils.Scale(4, DpiScale);
|
||||
|
||||
browser.KeyboardHandler = new CustomKeyboardHandler(this);
|
||||
|
||||
browser.LoadingStateChanged += Browser_LoadingStateChanged;
|
||||
|
||||
mouseHookDelegate = MouseHookProc;
|
||||
Disposed += (sender, args) => StopMouseHook(true);
|
||||
}
|
||||
|
||||
private void SetOpacity(int opacity) {
|
||||
if (currentOpacity != opacity) {
|
||||
currentOpacity = opacity;
|
||||
Opacity = opacity / 100.0;
|
||||
}
|
||||
}
|
||||
|
||||
// mouse wheel hook
|
||||
|
||||
private void StartMouseHook() {
|
||||
if (mouseHook == IntPtr.Zero) {
|
||||
mouseHook = NativeMethods.SetWindowsHookEx(NativeMethods.WM_MOUSE_LL, mouseHookDelegate, IntPtr.Zero, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void StopMouseHook(bool force) {
|
||||
if (mouseHook != IntPtr.Zero && (force || !blockXButtonUp)) {
|
||||
NativeMethods.UnhookWindowsHookEx(mouseHook);
|
||||
mouseHook = IntPtr.Zero;
|
||||
blockXButtonUp = false;
|
||||
}
|
||||
}
|
||||
|
||||
private IntPtr MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam) {
|
||||
if (nCode == 0) {
|
||||
int eventType = wParam.ToInt32();
|
||||
|
||||
if (eventType == NativeMethods.WM_MOUSEWHEEL && IsCursorOverBrowser) {
|
||||
int delta = BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Config.NotificationScrollSpeed * 0.01);
|
||||
|
||||
if (Config.EnableSmoothScrolling) {
|
||||
browser.BrowserCore.ExecuteScriptAsync("window.TDGF_scrollSmoothly", (int) Math.Round(-delta / 0.6));
|
||||
}
|
||||
else {
|
||||
browser.SendMouseWheelEvent(0, 0, 0, delta, CefEventFlags.None);
|
||||
}
|
||||
|
||||
return NativeMethods.HOOK_HANDLED;
|
||||
}
|
||||
else if (eventType == NativeMethods.WM_XBUTTONDOWN && DesktopBounds.Contains(Cursor.Position)) {
|
||||
int extraButton = NativeMethods.GetMouseHookData(lParam);
|
||||
|
||||
if (extraButton == 2) { // forward button
|
||||
this.InvokeAsyncSafe(FinishCurrentNotification);
|
||||
}
|
||||
else if (extraButton == 1) { // back button
|
||||
this.InvokeAsyncSafe(Close);
|
||||
}
|
||||
|
||||
blockXButtonUp = true;
|
||||
return NativeMethods.HOOK_HANDLED;
|
||||
}
|
||||
else if (eventType == NativeMethods.WM_XBUTTONUP && blockXButtonUp) {
|
||||
blockXButtonUp = false;
|
||||
|
||||
if (!Visible) {
|
||||
StopMouseHook(false);
|
||||
}
|
||||
|
||||
return NativeMethods.HOOK_HANDLED;
|
||||
}
|
||||
}
|
||||
|
||||
return NativeMethods.CallNextHookEx(mouseHook, nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
// event handlers
|
||||
|
||||
private void FormNotification_FormClosing(object sender, FormClosingEventArgs e) {
|
||||
if (e.CloseReason == CloseReason.UserClosing) {
|
||||
HideNotification();
|
||||
e.Cancel = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e) {
|
||||
if (!e.IsLoading && browser.Address != NotificationBrowser.BlankURL) {
|
||||
this.InvokeSafe(() => {
|
||||
Visible = true; // ensures repaint before moving the window to a visible location
|
||||
timerDisplayDelay.Start();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void timerDisplayDelay_Tick(object sender, EventArgs e) {
|
||||
OnNotificationReady();
|
||||
timerDisplayDelay.Stop();
|
||||
}
|
||||
|
||||
private void timerHideProgress_Tick(object sender, EventArgs e) {
|
||||
bool isCursorInside = Bounds.Contains(Cursor.Position);
|
||||
|
||||
if (isCursorInside) {
|
||||
StartMouseHook();
|
||||
SetOpacity(100);
|
||||
}
|
||||
else {
|
||||
StopMouseHook(false);
|
||||
SetOpacity(Config.NotificationWindowOpacity);
|
||||
}
|
||||
|
||||
if (isCursorInside || FreezeTimer || ContextMenuOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
timeLeft -= timerProgress.Interval;
|
||||
|
||||
int value = BrowserUtils.Scale(progressBarTimer.Maximum + 25, (totalTime - timeLeft) / (double) totalTime);
|
||||
progressBarTimer.SetValueInstant(Config.NotificationTimerCountDown ? progressBarTimer.Maximum - value : value);
|
||||
|
||||
if (timeLeft <= 0) {
|
||||
FinishCurrentNotification();
|
||||
}
|
||||
}
|
||||
|
||||
// notification methods
|
||||
|
||||
public virtual void ShowNotification(DesktopNotification notification) {
|
||||
LoadTweet(notification);
|
||||
}
|
||||
|
||||
public override void HideNotification() {
|
||||
base.HideNotification();
|
||||
|
||||
progressBarTimer.Value = Config.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
|
||||
timerProgress.Stop();
|
||||
totalTime = 0;
|
||||
|
||||
StopMouseHook(false);
|
||||
}
|
||||
|
||||
public override void FinishCurrentNotification() {
|
||||
timerProgress.Stop();
|
||||
}
|
||||
|
||||
public override void PauseNotification() {
|
||||
if (!IsPaused) {
|
||||
pausedDuringNotification = IsNotificationVisible;
|
||||
timerProgress.Stop();
|
||||
StopMouseHook(true);
|
||||
}
|
||||
|
||||
base.PauseNotification();
|
||||
}
|
||||
|
||||
public override void ResumeNotification() {
|
||||
bool wasPaused = IsPaused;
|
||||
base.ResumeNotification();
|
||||
|
||||
if (wasPaused && !IsPaused && pausedDuringNotification) {
|
||||
OnNotificationReady();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void LoadTweet(DesktopNotification tweet) {
|
||||
timerProgress.Stop();
|
||||
totalTime = timeLeft = tweet.GetDisplayDuration(Config.NotificationDurationValue);
|
||||
progressBarTimer.Value = Config.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
|
||||
|
||||
base.LoadTweet(tweet);
|
||||
}
|
||||
|
||||
protected override void SetNotificationSize(int width, int height) {
|
||||
if (Config.DisplayNotificationTimer) {
|
||||
ClientSize = new Size(width, height + timerBarHeight);
|
||||
progressBarTimer.Visible = true;
|
||||
}
|
||||
else {
|
||||
ClientSize = new Size(width, height);
|
||||
progressBarTimer.Visible = false;
|
||||
}
|
||||
|
||||
browser.ClientSize = new Size(width, height);
|
||||
}
|
||||
|
||||
protected void PrepareAndDisplayWindow() {
|
||||
if (RequiresResize) {
|
||||
RequiresResize = false;
|
||||
SetNotificationSize(BaseClientWidth, BaseClientHeight);
|
||||
}
|
||||
|
||||
SetOpacity(IsCursorOverBrowser ? 100 : Config.NotificationWindowOpacity);
|
||||
MoveToVisibleLocation();
|
||||
}
|
||||
|
||||
protected virtual void OnNotificationReady() {
|
||||
PrepareAndDisplayWindow();
|
||||
timerProgress.Start();
|
||||
}
|
||||
|
||||
bool CustomKeyboardHandler.IBrowserKeyHandler.HandleBrowserKey(Keys key) {
|
||||
switch (key) {
|
||||
case Keys.Enter:
|
||||
this.InvokeAsyncSafe(FinishCurrentNotification);
|
||||
return true;
|
||||
|
||||
case Keys.Escape:
|
||||
this.InvokeAsyncSafe(HideNotification);
|
||||
return true;
|
||||
|
||||
case Keys.Space:
|
||||
this.InvokeAsyncSafe(() => FreezeTimer = !FreezeTimer);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
namespace TweetDuck.Core.Notification {
|
||||
namespace TweetDuck.Browser.Notification {
|
||||
partial class FormNotificationTweet {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
@@ -40,7 +40,6 @@ namespace TweetDuck.Core.Notification {
|
||||
//
|
||||
// FormNotificationTweet
|
||||
//
|
||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormNotificationTweet_FormClosing);
|
||||
this.ResumeLayout(true);
|
||||
|
||||
}
|
171
Browser/Notification/FormNotificationTweet.cs
Normal file
171
Browser/Notification/FormNotificationTweet.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Browser.Interfaces;
|
||||
using TweetLib.Core.Features.Notifications;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
using TweetLib.Core.Features.TweetDeck;
|
||||
|
||||
namespace TweetDuck.Browser.Notification {
|
||||
sealed partial class FormNotificationTweet : FormNotificationMain {
|
||||
private static NotificationBrowser CreateBrowserImpl(IBrowserComponent browserComponent, INotificationInterface notificationInterface, ITweetDeckInterface tweetDeckInterface, PluginManager pluginManager) {
|
||||
return new NotificationBrowser.Tweet(browserComponent, notificationInterface, tweetDeckInterface, pluginManager);
|
||||
}
|
||||
|
||||
private const int NonIntrusiveIdleLimit = 30;
|
||||
private const int TrimMinimum = 32;
|
||||
|
||||
protected override Point PrimaryLocation => hasTemporarilyMoved && IsNotificationVisible ? Location : base.PrimaryLocation;
|
||||
private bool IsCursorOverNotificationArea => new Rectangle(PrimaryLocation, Size).Contains(Cursor.Position);
|
||||
|
||||
protected override bool CanDragWindow {
|
||||
get {
|
||||
if (ModifierKeys.HasFlag(Keys.Alt)) {
|
||||
hasTemporarilyMoved = true;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Queue<DesktopNotification> tweetQueue = new Queue<DesktopNotification>(4);
|
||||
private bool needsTrim;
|
||||
private bool hasTemporarilyMoved;
|
||||
|
||||
public FormNotificationTweet(FormBrowser owner, ITweetDeckInterface tweetDeckInterface, PluginManager pluginManager) : base(owner, (form, browserComponent) => CreateBrowserImpl(browserComponent, new NotificationInterfaceImpl(form), tweetDeckInterface, pluginManager)) {
|
||||
InitializeComponent();
|
||||
|
||||
Config.MuteToggled += Config_MuteToggled;
|
||||
Disposed += (sender, args) => Config.MuteToggled -= Config_MuteToggled;
|
||||
|
||||
if (Config.MuteNotifications) {
|
||||
PauseNotification();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void WndProc(ref Message m) {
|
||||
if (m.Msg == 0x00A7) { // WM_NCMBUTTONDOWN
|
||||
int hitTest = m.WParam.ToInt32();
|
||||
|
||||
if (hitTest == 2 || hitTest == 20) { // HTCAPTION, HTCLOSE
|
||||
hasTemporarilyMoved = false;
|
||||
MoveToVisibleLocation();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
base.WndProc(ref m);
|
||||
}
|
||||
|
||||
// event handlers
|
||||
|
||||
private void Config_MuteToggled(object sender, EventArgs e) {
|
||||
if (Config.MuteNotifications) {
|
||||
PauseNotification();
|
||||
}
|
||||
else {
|
||||
ResumeNotification();
|
||||
}
|
||||
}
|
||||
|
||||
private void timerCursorCheck_Tick(object sender, EventArgs e) {
|
||||
if (!IsCursorOverNotificationArea) {
|
||||
ResumeNotification();
|
||||
timerCursorCheck.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
private void timerIdlePauseCheck_Tick(object sender, EventArgs e) {
|
||||
if (NativeMethods.GetIdleSeconds() < Config.NotificationIdlePauseSeconds) {
|
||||
ResumeNotification();
|
||||
timerIdlePauseCheck.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
// notification methods
|
||||
|
||||
public override void ShowNotification(DesktopNotification notification) {
|
||||
tweetQueue.Enqueue(notification);
|
||||
|
||||
if (!IsPaused) {
|
||||
UpdateTitle();
|
||||
|
||||
if (totalTime == 0) {
|
||||
LoadNextNotification();
|
||||
}
|
||||
}
|
||||
|
||||
needsTrim |= tweetQueue.Count >= TrimMinimum;
|
||||
}
|
||||
|
||||
public override void HideNotification() {
|
||||
base.HideNotification();
|
||||
tweetQueue.Clear();
|
||||
|
||||
if (needsTrim) {
|
||||
tweetQueue.TrimExcess();
|
||||
needsTrim = false;
|
||||
}
|
||||
|
||||
hasTemporarilyMoved = false;
|
||||
}
|
||||
|
||||
public override void FinishCurrentNotification() {
|
||||
if (tweetQueue.Count > 0) {
|
||||
LoadNextNotification();
|
||||
}
|
||||
else {
|
||||
HideNotification();
|
||||
}
|
||||
}
|
||||
|
||||
public override void ResumeNotification() {
|
||||
bool wasPaused = IsPaused;
|
||||
base.ResumeNotification();
|
||||
|
||||
if (wasPaused && !IsPaused && !pausedDuringNotification && tweetQueue.Count > 0) {
|
||||
LoadNextNotification();
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadNextNotification() {
|
||||
if (!IsNotificationVisible) {
|
||||
if (Config.NotificationNonIntrusiveMode && IsCursorOverNotificationArea && NativeMethods.GetIdleSeconds() < NonIntrusiveIdleLimit) {
|
||||
if (!timerCursorCheck.Enabled) {
|
||||
PauseNotification();
|
||||
timerCursorCheck.Start();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else if (Config.NotificationIdlePauseSeconds > 0 && NativeMethods.GetIdleSeconds() >= Config.NotificationIdlePauseSeconds) {
|
||||
if (!timerIdlePauseCheck.Enabled) {
|
||||
PauseNotification();
|
||||
timerIdlePauseCheck.Start();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LoadTweet(tweetQueue.Dequeue());
|
||||
}
|
||||
|
||||
protected override void UpdateTitle() {
|
||||
base.UpdateTitle();
|
||||
|
||||
if (tweetQueue.Count > 0) {
|
||||
Text = Text + " (" + tweetQueue.Count + " more left)";
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnNotificationReady() {
|
||||
UpdateTitle();
|
||||
base.OnNotificationReady();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using CefSharp;
|
||||
using CefSharp.DevTools.Page;
|
||||
using TweetDuck.Controls;
|
||||
using TweetDuck.Dialogs;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Browser.Interfaces;
|
||||
using TweetLib.Core.Features.Notifications;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
using TweetLib.Core.Resources;
|
||||
|
||||
namespace TweetDuck.Browser.Notification.Screenshot {
|
||||
sealed class FormNotificationScreenshotable : FormNotificationBase {
|
||||
private static NotificationBrowser CreateBrowserImpl( IBrowserComponent browserComponent, PluginManager pluginManager) {
|
||||
return new NotificationBrowser.Screenshot(browserComponent, pluginManager.NotificationInjections);
|
||||
}
|
||||
|
||||
protected override bool CanDragWindow => false;
|
||||
|
||||
private int height;
|
||||
|
||||
public FormNotificationScreenshotable(Action callback, FormBrowser owner, PluginManager pluginManager, string html, int width) : base(owner, (_, browserComponent) => CreateBrowserImpl(browserComponent, pluginManager)) {
|
||||
int realWidth = BrowserUtils.Scale(width, DpiScale);
|
||||
|
||||
browserComponent.AttachBridgeObject("$TD_NotificationScreenshot", new ScreenshotBridge(this, SetScreenshotHeight, callback));
|
||||
|
||||
browserComponent.BrowserLoaded += (sender, args) => {
|
||||
string script = ResourceUtils.ReadFileOrNull("notification/screenshot/screenshot.js");
|
||||
|
||||
if (script == null) {
|
||||
this.InvokeAsyncSafe(callback);
|
||||
return;
|
||||
}
|
||||
|
||||
string substituted = script.Replace("{width}", realWidth.ToString()).Replace("1/*FRAMES*/", TweetScreenshotManager.WaitFrames.ToString());
|
||||
browserComponent.RunScript("gen:screenshot", substituted);
|
||||
};
|
||||
|
||||
SetNotificationSize(realWidth, 1024);
|
||||
LoadTweet(new DesktopNotification(string.Empty, string.Empty, string.Empty, html, 0, string.Empty, string.Empty));
|
||||
}
|
||||
|
||||
private void SetScreenshotHeight(int browserHeight) {
|
||||
this.height = BrowserUtils.Scale(browserHeight, SizeScale);
|
||||
}
|
||||
|
||||
public Task<Image> TakeScreenshot(bool ignoreHeightError = false) {
|
||||
if (!ignoreHeightError) {
|
||||
if (height == 0) {
|
||||
FormMessage.Error("Screenshot Failed", "Could not detect screenshot size.", FormMessage.OK);
|
||||
return null;
|
||||
}
|
||||
else if (height > ClientSize.Height) {
|
||||
FormMessage.Error("Screenshot Failed", $"Screenshot is too large: {height}px > {ClientSize.Height}px", FormMessage.OK);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return Task.Run(TakeScreenshotImpl);
|
||||
}
|
||||
|
||||
private async Task<Image> TakeScreenshotImpl() {
|
||||
if (this.height == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Viewport viewport = new Viewport {
|
||||
Width = this.ClientSize.Width,
|
||||
Height = this.height,
|
||||
Scale = 1
|
||||
};
|
||||
|
||||
byte[] data;
|
||||
using (var devToolsClient = browser.GetDevToolsClient()) {
|
||||
data = (await devToolsClient.Page.CaptureScreenshotAsync(CaptureScreenshotFormat.Png, clip: viewport)).Data;
|
||||
}
|
||||
|
||||
return Image.FromStream(new MemoryStream(data));
|
||||
}
|
||||
}
|
||||
}
|
28
Browser/Notification/Screenshot/ScreenshotBridge.cs
Normal file
28
Browser/Notification/Screenshot/ScreenshotBridge.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Controls;
|
||||
|
||||
namespace TweetDuck.Browser.Notification.Screenshot {
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
sealed class ScreenshotBridge {
|
||||
private readonly Control owner;
|
||||
|
||||
private readonly Action<int> safeSetHeight;
|
||||
private readonly Action safeTriggerScreenshot;
|
||||
|
||||
public ScreenshotBridge(Control owner, Action<int> safeSetHeight, Action safeTriggerScreenshot) {
|
||||
this.owner = owner;
|
||||
this.safeSetHeight = safeSetHeight;
|
||||
this.safeTriggerScreenshot = safeTriggerScreenshot;
|
||||
}
|
||||
|
||||
public void SetHeight(int tweetHeight) {
|
||||
owner.InvokeSafe(() => safeSetHeight(tweetHeight));
|
||||
}
|
||||
|
||||
public void TriggerScreenshot() {
|
||||
owner.InvokeSafe(safeTriggerScreenshot);
|
||||
}
|
||||
}
|
||||
}
|
164
Browser/Notification/Screenshot/TweetScreenshotManager.cs
Normal file
164
Browser/Notification/Screenshot/TweetScreenshotManager.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
#if DEBUG
|
||||
// Uncomment to keep screenshot windows visible for debugging
|
||||
// #define NO_HIDE_SCREENSHOTS
|
||||
|
||||
// Uncomment to generate screenshots of individual frames for at most 1 second
|
||||
// #define GEN_SCREENSHOT_FRAMES
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Controls;
|
||||
using TweetLib.Core;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
#if GEN_SCREENSHOT_FRAMES
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using TweetDuck.Utils;
|
||||
#endif
|
||||
|
||||
namespace TweetDuck.Browser.Notification.Screenshot {
|
||||
sealed class TweetScreenshotManager : IDisposable {
|
||||
private readonly FormBrowser owner;
|
||||
private readonly PluginManager plugins;
|
||||
private readonly Timer timeout;
|
||||
private readonly Timer disposer;
|
||||
|
||||
#if GEN_SCREENSHOT_FRAMES
|
||||
private readonly Timer debugger;
|
||||
private int frameCounter;
|
||||
|
||||
public const int WaitFrames = 60;
|
||||
#else
|
||||
public const int WaitFrames = 5;
|
||||
#endif
|
||||
|
||||
private FormNotificationScreenshotable screenshot;
|
||||
|
||||
public TweetScreenshotManager(FormBrowser owner, PluginManager pluginManager) {
|
||||
this.owner = owner;
|
||||
this.plugins = pluginManager;
|
||||
|
||||
this.timeout = new Timer { Interval = 8000 };
|
||||
this.timeout.Tick += timeout_Tick;
|
||||
|
||||
this.disposer = new Timer { Interval = 1 };
|
||||
this.disposer.Tick += disposer_Tick;
|
||||
|
||||
#if GEN_SCREENSHOT_FRAMES
|
||||
this.debugger = new Timer { Interval = 16 };
|
||||
this.debugger.Tick += debugger_Tick;
|
||||
#endif
|
||||
}
|
||||
|
||||
private void timeout_Tick(object sender, EventArgs e) {
|
||||
timeout.Stop();
|
||||
OnFinished();
|
||||
}
|
||||
|
||||
private void disposer_Tick(object sender, EventArgs e) {
|
||||
disposer.Stop();
|
||||
screenshot.Dispose();
|
||||
screenshot = null;
|
||||
}
|
||||
|
||||
public void Trigger(string html, int width) {
|
||||
if (screenshot != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
screenshot = new FormNotificationScreenshotable(Callback, owner, plugins, html, width);
|
||||
screenshot.Show();
|
||||
timeout.Start();
|
||||
|
||||
#if GEN_SCREENSHOT_FRAMES
|
||||
StartDebugger();
|
||||
#endif
|
||||
|
||||
#if !NO_HIDE_SCREENSHOTS
|
||||
owner.IsWaiting = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
private void Callback() {
|
||||
if (!timeout.Enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
timeout.Stop();
|
||||
screenshot.TakeScreenshot().ContinueWith(HandleResult, TaskScheduler.FromCurrentSynchronizationContext());
|
||||
}
|
||||
|
||||
private void HandleResult(Task<Image> task) {
|
||||
if (task.IsFaulted) {
|
||||
App.ErrorHandler.HandleException("Screenshot Failed", "An error occurred while taking a screenshot.", true, task.Exception!.InnerException);
|
||||
}
|
||||
else if (task.IsCompleted) {
|
||||
Clipboard.SetImage(task.Result);
|
||||
#if !NO_HIDE_SCREENSHOTS
|
||||
OnFinished();
|
||||
#else
|
||||
screenshot.MoveToVisibleLocation();
|
||||
screenshot.FormClosed += (sender, args) => disposer.Start();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFinished() {
|
||||
#if GEN_SCREENSHOT_FRAMES
|
||||
debugger.Stop();
|
||||
#endif
|
||||
|
||||
screenshot.Location = ControlExtensions.InvisibleLocation;
|
||||
owner.IsWaiting = false;
|
||||
disposer.Start();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
#if GEN_SCREENSHOT_FRAMES
|
||||
debugger.Dispose();
|
||||
#endif
|
||||
|
||||
timeout.Dispose();
|
||||
disposer.Dispose();
|
||||
screenshot?.Dispose();
|
||||
}
|
||||
|
||||
#if GEN_SCREENSHOT_FRAMES
|
||||
private static readonly string DebugScreenshotPath = Path.Combine(Program.StoragePath, "TD_Screenshots");
|
||||
|
||||
private void StartDebugger() {
|
||||
frameCounter = 0;
|
||||
|
||||
try {
|
||||
Directory.Delete(DebugScreenshotPath, true);
|
||||
WindowsUtils.TrySleepUntil(() => !Directory.Exists(DebugScreenshotPath), 1000, 10);
|
||||
} catch (DirectoryNotFoundException) {}
|
||||
|
||||
Directory.CreateDirectory(DebugScreenshotPath);
|
||||
debugger.Start();
|
||||
}
|
||||
|
||||
private void debugger_Tick(object sender, EventArgs e) {
|
||||
if (frameCounter < 63) {
|
||||
int frame = ++frameCounter;
|
||||
screenshot.TakeScreenshot(true).ContinueWith(task => SaveDebugFrame(task, frame), TaskScheduler.FromCurrentSynchronizationContext());
|
||||
}
|
||||
else {
|
||||
debugger.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
private static void SaveDebugFrame(Task<Image> task, int frame) {
|
||||
if (task.IsFaulted) {
|
||||
System.Diagnostics.Debug.WriteLine("Failed generating frame " + frame + ": " + task.Exception!.InnerException);
|
||||
}
|
||||
else if (task.IsCompleted) {
|
||||
task.Result?.Save(Path.Combine(DebugScreenshotPath, "frame_" + (++frame) + ".png"), ImageFormat.Png);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
67
Browser/Notification/SoundNotification.cs
Normal file
67
Browser/Notification/SoundNotification.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Browser.Adapters;
|
||||
using TweetDuck.Controls;
|
||||
using TweetDuck.Dialogs;
|
||||
using TweetDuck.Dialogs.Settings;
|
||||
using TweetDuck.Management;
|
||||
using TweetLib.Core.Features.TweetDeck;
|
||||
|
||||
namespace TweetDuck.Browser.Notification {
|
||||
sealed class SoundNotification : ISoundNotificationHandler {
|
||||
public const string SupportedFormats = "*.wav;*.ogg;*.mp3;*.flac;*.opus;*.weba;*.webm";
|
||||
|
||||
private readonly CefResourceHandlerRegistry registry;
|
||||
|
||||
public SoundNotification(CefResourceHandlerRegistry registry) {
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
public void Unregister(string url) {
|
||||
registry.Unregister(url);
|
||||
}
|
||||
|
||||
public void Register(string url, string path) {
|
||||
var fileHandler = CreateFileHandler(path);
|
||||
if (fileHandler.HasValue) {
|
||||
var (data, mimeType) = fileHandler.Value;
|
||||
registry.RegisterStatic(url, data, mimeType);
|
||||
}
|
||||
}
|
||||
|
||||
private static (byte[] data, string mimeType)? CreateFileHandler(string path) {
|
||||
string mimeType = Path.GetExtension(path) switch {
|
||||
".weba" => "audio/webm",
|
||||
".webm" => "audio/webm",
|
||||
".wav" => "audio/wav",
|
||||
".ogg" => "audio/ogg",
|
||||
".mp3" => "audio/mp3",
|
||||
".flac" => "audio/flac",
|
||||
".opus" => "audio/ogg; codecs=opus",
|
||||
_ => null
|
||||
};
|
||||
|
||||
try {
|
||||
return (File.ReadAllBytes(path), mimeType);
|
||||
} catch {
|
||||
FormBrowser browser = FormManager.TryFind<FormBrowser>();
|
||||
|
||||
browser?.InvokeAsyncSafe(() => {
|
||||
using FormMessage form = new FormMessage("Sound Notification Error", "Could not find custom notification sound file:\n" + path, MessageBoxIcon.Error);
|
||||
form.AddButton(FormMessage.Ignore, ControlType.Cancel | ControlType.Focused);
|
||||
|
||||
Button btnViewOptions = form.AddButton("View Options");
|
||||
btnViewOptions.Width += 16;
|
||||
btnViewOptions.Location = new Point(btnViewOptions.Location.X - 16, btnViewOptions.Location.Y);
|
||||
|
||||
if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnViewOptions) {
|
||||
browser.OpenSettings(typeof(TabSettingsSounds));
|
||||
}
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,21 +1,10 @@
|
||||
namespace TweetDuck.Core {
|
||||
namespace TweetDuck.Browser {
|
||||
partial class TrayIcon {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing && (components != null)) {
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Component Designer generated code
|
||||
|
||||
/// <summary>
|
129
Browser/TrayIcon.cs
Normal file
129
Browser/TrayIcon.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetLib.Core.Systems.Configuration;
|
||||
using Res = TweetDuck.Properties.Resources;
|
||||
|
||||
namespace TweetDuck.Browser {
|
||||
sealed partial class TrayIcon : Component {
|
||||
public enum Behavior { // keep order
|
||||
Disabled,
|
||||
DisplayOnly,
|
||||
MinimizeToTray,
|
||||
CloseToTray,
|
||||
Combined
|
||||
}
|
||||
|
||||
private static UserConfig Config => Program.Config.User;
|
||||
|
||||
public event EventHandler ClickRestore;
|
||||
public event EventHandler ClickClose;
|
||||
|
||||
public bool Visible {
|
||||
get {
|
||||
return notifyIcon.Visible;
|
||||
}
|
||||
|
||||
set {
|
||||
notifyIcon.Visible = value;
|
||||
hasNotifications = false;
|
||||
UpdateIcon();
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasNotifications {
|
||||
get {
|
||||
return hasNotifications;
|
||||
}
|
||||
|
||||
set {
|
||||
if (hasNotifications != value) {
|
||||
hasNotifications = value;
|
||||
UpdateIcon();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ContextMenu contextMenu;
|
||||
private bool hasNotifications;
|
||||
|
||||
public TrayIcon() {
|
||||
InitializeComponent();
|
||||
|
||||
this.contextMenu = new ContextMenu();
|
||||
this.contextMenu.MenuItems.Add("Restore", menuItemRestore_Click);
|
||||
this.contextMenu.MenuItems.Add("Mute notifications", menuItemMuteNotifications_Click);
|
||||
this.contextMenu.MenuItems.Add("Close", menuItemClose_Click);
|
||||
this.contextMenu.Popup += contextMenu_Popup;
|
||||
|
||||
this.notifyIcon.ContextMenu = contextMenu;
|
||||
this.notifyIcon.Text = Program.BrandName;
|
||||
|
||||
Config.MuteToggled += Config_MuteToggled;
|
||||
Disposed += (sender, args) => Config.MuteToggled -= Config_MuteToggled;
|
||||
}
|
||||
|
||||
public TrayIcon(IContainer container) : this() {
|
||||
container.Add(this);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing) {
|
||||
components?.Dispose();
|
||||
contextMenu.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private void UpdateIcon() {
|
||||
if (Visible) {
|
||||
notifyIcon.Icon = HasNotifications ? Res.icon_tray_new : Config.MuteNotifications ? Res.icon_tray_muted : Res.icon_tray;
|
||||
}
|
||||
}
|
||||
|
||||
// event handlers
|
||||
|
||||
private void Config_MuteToggled(object sender, EventArgs e) {
|
||||
UpdateIcon();
|
||||
}
|
||||
|
||||
private void trayIcon_MouseClick(object sender, MouseEventArgs e) {
|
||||
if (e.Button == MouseButtons.Left) {
|
||||
menuItemRestore_Click(sender, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void contextMenu_Popup(object sender, EventArgs e) {
|
||||
contextMenu.MenuItems[1].Checked = Config.MuteNotifications;
|
||||
}
|
||||
|
||||
private void menuItemRestore_Click(object sender, EventArgs e) {
|
||||
ClickRestore?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void menuItemMuteNotifications_Click(object sender, EventArgs e) {
|
||||
Config.MuteNotifications = !contextMenu.MenuItems[1].Checked;
|
||||
Config.Save();
|
||||
}
|
||||
|
||||
private void menuItemClose_Click(object sender, EventArgs e) {
|
||||
ClickClose?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
static class BehaviorExtensions {
|
||||
public static bool ShouldDisplayIcon(this TrayIcon.Behavior behavior) {
|
||||
return behavior != TrayIcon.Behavior.Disabled;
|
||||
}
|
||||
|
||||
public static bool ShouldHideOnMinimize(this TrayIcon.Behavior behavior) {
|
||||
return behavior == TrayIcon.Behavior.MinimizeToTray || behavior == TrayIcon.Behavior.Combined;
|
||||
}
|
||||
|
||||
public static bool ShouldHideOnClose(this TrayIcon.Behavior behavior) {
|
||||
return behavior == TrayIcon.Behavior.CloseToTray || behavior == TrayIcon.Behavior.Combined;
|
||||
}
|
||||
}
|
||||
}
|
132
Browser/TweetDeckBrowser.cs
Normal file
132
Browser/TweetDeckBrowser.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using TweetDuck.Browser.Adapters;
|
||||
using TweetDuck.Browser.Handling;
|
||||
using TweetDuck.Browser.Notification;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Utils;
|
||||
using TweetLib.Core.Features.Plugins;
|
||||
using TweetLib.Core.Features.TweetDeck;
|
||||
using TweetLib.Core.Features.Twitter;
|
||||
using TweetLib.Core.Systems.Updates;
|
||||
using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
|
||||
using IResourceRequestHandler = TweetLib.Browser.Interfaces.IResourceRequestHandler;
|
||||
using TweetDeckBrowserImpl = TweetLib.Core.Features.TweetDeck.TweetDeckBrowser;
|
||||
|
||||
namespace TweetDuck.Browser {
|
||||
sealed class TweetDeckBrowser : IDisposable {
|
||||
public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153);
|
||||
|
||||
public bool Ready => browserComponent.Ready;
|
||||
|
||||
public bool Enabled {
|
||||
get => browser.Enabled;
|
||||
set => browser.Enabled = value;
|
||||
}
|
||||
|
||||
public bool IsTweetDeckWebsite {
|
||||
get {
|
||||
if (!Ready) {
|
||||
return false;
|
||||
}
|
||||
|
||||
using IFrame frame = browser.GetBrowser().MainFrame;
|
||||
return TwitterUrls.IsTweetDeck(frame.Url);
|
||||
}
|
||||
}
|
||||
|
||||
public TweetDeckFunctions Functions => browserImpl.Functions;
|
||||
|
||||
private readonly CefBrowserComponent browserComponent;
|
||||
private readonly TweetDeckBrowserImpl browserImpl;
|
||||
private readonly ChromiumWebBrowser browser;
|
||||
|
||||
public TweetDeckBrowser(FormBrowser owner, PluginManager pluginManager, ITweetDeckInterface tweetDeckInterface, UpdateChecker updateChecker) {
|
||||
RequestHandlerBrowser requestHandler = new RequestHandlerBrowser();
|
||||
|
||||
this.browser = new ChromiumWebBrowser(TwitterUrls.TweetDeck) {
|
||||
DialogHandler = new FileDialogHandler(),
|
||||
DragHandler = new DragHandlerBrowser(requestHandler),
|
||||
KeyboardHandler = new CustomKeyboardHandler(owner),
|
||||
RequestHandler = requestHandler
|
||||
};
|
||||
|
||||
// ReSharper disable once PossiblyImpureMethodCallOnReadonlyVariable
|
||||
this.browser.BrowserSettings.BackgroundColor = (uint) BackgroundColor.ToArgb();
|
||||
|
||||
var extraContext = new TweetDeckExtraContext();
|
||||
var resourceHandlerRegistry = new CefResourceHandlerRegistry();
|
||||
var soundNotificationHandler = new SoundNotification(resourceHandlerRegistry);
|
||||
|
||||
this.browserComponent = new ComponentImpl(browser, owner, extraContext, resourceHandlerRegistry);
|
||||
this.browserImpl = new TweetDeckBrowserImpl(browserComponent, tweetDeckInterface, extraContext, soundNotificationHandler, pluginManager, updateChecker);
|
||||
|
||||
if (Arguments.HasFlag(Arguments.ArgIgnoreGDPR)) {
|
||||
browserComponent.PageLoadEnd += (sender, args) => {
|
||||
if (TwitterUrls.IsTweetDeck(args.Url)) {
|
||||
browserComponent.RunScript("gen:gdpr", "TD.storage.Account.prototype.requiresConsent = function() { return false; }");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
owner.Controls.Add(browser);
|
||||
}
|
||||
|
||||
private sealed class ComponentImpl : CefBrowserComponent {
|
||||
private readonly FormBrowser owner;
|
||||
private readonly TweetDeckExtraContext extraContext;
|
||||
private readonly CefResourceHandlerRegistry registry;
|
||||
|
||||
public ComponentImpl(ChromiumWebBrowser browser, FormBrowser owner, TweetDeckExtraContext extraContext, CefResourceHandlerRegistry registry) : base(browser) {
|
||||
this.owner = owner;
|
||||
this.extraContext = extraContext;
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
protected override ContextMenuBase SetupContextMenu(IContextMenuHandler handler) {
|
||||
return new ContextMenuBrowser(owner, handler, extraContext);
|
||||
}
|
||||
|
||||
protected override CefResourceHandlerFactory SetupResourceHandlerFactory(IResourceRequestHandler handler) {
|
||||
return new CefResourceHandlerFactory(handler, registry);
|
||||
}
|
||||
}
|
||||
|
||||
public void PrepareSize(Size size) {
|
||||
if (!Ready) {
|
||||
browser.Size = size;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
browserImpl.Dispose();
|
||||
browser.Dispose();
|
||||
}
|
||||
|
||||
public void Focus() {
|
||||
browser.Focus();
|
||||
}
|
||||
|
||||
public void OpenDevTools() {
|
||||
browser.OpenDevToolsCustom();
|
||||
}
|
||||
|
||||
public void ReloadToTweetDeck() {
|
||||
browserImpl.ReloadToTweetDeck();
|
||||
}
|
||||
|
||||
public void SaveVideo(string url, string username) {
|
||||
browserImpl.FileDownloadManager.SaveVideo(url, username);
|
||||
}
|
||||
|
||||
public void HideVideoOverlay(bool focus) {
|
||||
if (focus) {
|
||||
browser.GetBrowser().GetHost().SendFocusEvent(true);
|
||||
}
|
||||
|
||||
browserComponent.RunScript("gen:hidevideo", "$('#td-video-player-overlay').remove()");
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,17 +1,17 @@
|
||||
using System;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetLib.Utils.Collections;
|
||||
|
||||
namespace TweetDuck.Configuration {
|
||||
static class Arguments {
|
||||
// public args
|
||||
public const string ArgDataFolder = "-datafolder";
|
||||
public const string ArgLocale = "-locale";
|
||||
public const string ArgLogging = "-log";
|
||||
public const string ArgDebugUpdates = "-debugupdates";
|
||||
public const string ArgIgnoreGDPR = "-nogdpr";
|
||||
public const string ArgHttpVideo = "-httpvideo";
|
||||
public const string ArgFreeze = "-freeze";
|
||||
|
||||
// internal args
|
||||
public const string ArgRestart = "-restart";
|
||||
public const string ArgImportCookies = "-importcookies";
|
||||
public const string ArgUpdated = "-updated";
|
||||
|
||||
// class data and methods
|
||||
@@ -21,14 +21,13 @@ namespace TweetDuck.Configuration{
|
||||
return Current.HasFlag(flag);
|
||||
}
|
||||
|
||||
public static string GetValue(string key, string defaultValue){
|
||||
return Current.GetValue(key, defaultValue);
|
||||
public static string GetValue(string key) {
|
||||
return Current.GetValue(key);
|
||||
}
|
||||
|
||||
public static CommandLineArgs GetCurrentClean() {
|
||||
CommandLineArgs args = Current.Clone();
|
||||
args.RemoveFlag(ArgRestart);
|
||||
args.RemoveFlag(ArgImportCookies);
|
||||
args.RemoveFlag(ArgUpdated);
|
||||
return args;
|
||||
}
|
||||
|
@@ -1,176 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Configuration{
|
||||
sealed class LockManager{
|
||||
public enum Result{
|
||||
Success, HasProcess, Fail
|
||||
}
|
||||
|
||||
public Process LockingProcess { get; private set; }
|
||||
|
||||
private readonly string file;
|
||||
private FileStream lockStream;
|
||||
|
||||
public LockManager(string file){
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
private void CreateLockFileStream(){
|
||||
lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
WriteIntToStream(lockStream, GetCurrentProcessId());
|
||||
lockStream.Flush(true);
|
||||
}
|
||||
|
||||
private bool ReleaseLockFileStream(){
|
||||
if (lockStream != null){
|
||||
lockStream.Dispose();
|
||||
lockStream = null;
|
||||
return true;
|
||||
}
|
||||
else{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Result TryCreateLockFile(){
|
||||
if (lockStream != null){
|
||||
throw new InvalidOperationException("Lock file already exists.");
|
||||
}
|
||||
|
||||
try{
|
||||
CreateLockFileStream();
|
||||
return Result.Success;
|
||||
}catch(DirectoryNotFoundException){
|
||||
try{
|
||||
CreateLockFileStream();
|
||||
return Result.Success;
|
||||
}catch{
|
||||
ReleaseLockFileStream();
|
||||
return Result.Fail;
|
||||
}
|
||||
}catch(IOException){
|
||||
return Result.HasProcess;
|
||||
}catch{
|
||||
ReleaseLockFileStream();
|
||||
return Result.Fail;
|
||||
}
|
||||
}
|
||||
|
||||
public Result Lock(){
|
||||
if (lockStream != null){
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
Result initialResult = TryCreateLockFile();
|
||||
|
||||
if (initialResult == Result.HasProcess){
|
||||
try{
|
||||
int pid;
|
||||
|
||||
using(FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){
|
||||
pid = ReadIntFromStream(fileStream);
|
||||
}
|
||||
|
||||
try{
|
||||
Process foundProcess = Process.GetProcessById(pid);
|
||||
|
||||
using(Process currentProcess = Process.GetCurrentProcess()){
|
||||
if (foundProcess.MainModule.FileVersionInfo.InternalName == currentProcess.MainModule.FileVersionInfo.InternalName){
|
||||
LockingProcess = foundProcess;
|
||||
}
|
||||
else{
|
||||
foundProcess.Close();
|
||||
}
|
||||
}
|
||||
}catch{
|
||||
// GetProcessById throws ArgumentException if the process is missing
|
||||
// Process.MainModule can throw exceptions in some cases
|
||||
}
|
||||
|
||||
return LockingProcess == null ? Result.Fail : Result.HasProcess;
|
||||
}catch{
|
||||
return Result.Fail;
|
||||
}
|
||||
}
|
||||
|
||||
return initialResult;
|
||||
}
|
||||
|
||||
public bool Unlock(){
|
||||
bool result = true;
|
||||
|
||||
if (ReleaseLockFileStream()){
|
||||
try{
|
||||
File.Delete(file);
|
||||
}catch(Exception e){
|
||||
Program.Reporter.Log(e.ToString());
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool CloseLockingProcess(int closeTimeout, int killTimeout){
|
||||
if (LockingProcess != null){
|
||||
try{
|
||||
if (LockingProcess.CloseMainWindow()){
|
||||
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, closeTimeout, 250);
|
||||
}
|
||||
|
||||
if (!LockingProcess.HasExited){
|
||||
LockingProcess.Kill();
|
||||
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, killTimeout, 250);
|
||||
}
|
||||
|
||||
if (LockingProcess.HasExited){
|
||||
LockingProcess.Dispose();
|
||||
LockingProcess = null;
|
||||
return true;
|
||||
}
|
||||
}catch(Exception ex){
|
||||
if (ex is InvalidOperationException || ex is Win32Exception){
|
||||
if (LockingProcess != null){
|
||||
LockingProcess.Refresh();
|
||||
|
||||
bool hasExited = LockingProcess.HasExited;
|
||||
LockingProcess.Dispose();
|
||||
return hasExited;
|
||||
}
|
||||
}
|
||||
else throw;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckLockingProcessExited(){
|
||||
LockingProcess.Refresh();
|
||||
return LockingProcess.HasExited;
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
|
||||
private static void WriteIntToStream(Stream stream, int value){
|
||||
byte[] id = BitConverter.GetBytes(value);
|
||||
stream.Write(id, 0, id.Length);
|
||||
}
|
||||
|
||||
private static int ReadIntFromStream(Stream stream){
|
||||
byte[] bytes = new byte[4];
|
||||
stream.Read(bytes, 0, 4);
|
||||
return BitConverter.ToInt32(bytes, 0);
|
||||
}
|
||||
|
||||
private static int GetCurrentProcessId(){
|
||||
using(Process process = Process.GetCurrentProcess()){
|
||||
return process.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,66 +1,69 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using TweetLib.Core;
|
||||
using TweetLib.Core.Application;
|
||||
using TweetLib.Core.Systems.Configuration;
|
||||
|
||||
namespace TweetDuck.Configuration {
|
||||
sealed class SystemConfig{
|
||||
public static readonly bool IsHardwareAccelerationSupported = File.Exists(Path.Combine(Program.ProgramPath, "libEGL.dll")) &&
|
||||
File.Exists(Path.Combine(Program.ProgramPath, "libGLESv2.dll"));
|
||||
sealed class SystemConfig : BaseConfig<SystemConfig>, IAppSystemConfiguration {
|
||||
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
|
||||
public int MigrationVersion { get; set; } = 0;
|
||||
|
||||
private bool _hardwareAcceleration = true;
|
||||
private bool _enableTouchAdjustment = false;
|
||||
private bool _enableColorProfileDetection = false;
|
||||
private bool _useSystemProxyForAllConnections = false;
|
||||
|
||||
public bool ClearCacheAutomatically { get; set; } = true;
|
||||
public int ClearCacheThreshold { get; set; } = 250;
|
||||
|
||||
// SPECIAL PROPERTIES
|
||||
|
||||
public bool HardwareAcceleration {
|
||||
get => hardwareAcceleration && IsHardwareAccelerationSupported;
|
||||
set => hardwareAcceleration = value;
|
||||
get => _hardwareAcceleration;
|
||||
set => UpdatePropertyWithCallback(ref _hardwareAcceleration, value, App.ConfigManager.TriggerProgramRestartRequested);
|
||||
}
|
||||
|
||||
private readonly string file;
|
||||
|
||||
private bool hardwareAcceleration;
|
||||
|
||||
private SystemConfig(string file){
|
||||
this.file = file;
|
||||
|
||||
HardwareAcceleration = true;
|
||||
public bool EnableTouchAdjustment {
|
||||
get => _enableTouchAdjustment;
|
||||
set => UpdatePropertyWithCallback(ref _enableTouchAdjustment, value, App.ConfigManager.TriggerProgramRestartRequested);
|
||||
}
|
||||
|
||||
private void WriteToStream(Stream stream){
|
||||
stream.WriteByte((byte)(HardwareAcceleration ? 1 : 0));
|
||||
public bool EnableColorProfileDetection {
|
||||
get => _enableColorProfileDetection;
|
||||
set => UpdatePropertyWithCallback(ref _enableColorProfileDetection, value, App.ConfigManager.TriggerProgramRestartRequested);
|
||||
}
|
||||
|
||||
private void ReadFromStream(Stream stream){
|
||||
HardwareAcceleration = stream.ReadByte() > 0;
|
||||
public bool UseSystemProxyForAllConnections {
|
||||
get => _useSystemProxyForAllConnections;
|
||||
set => UpdatePropertyWithCallback(ref _useSystemProxyForAllConnections, value, App.ConfigManager.TriggerProgramRestartRequested);
|
||||
}
|
||||
|
||||
public bool Save(){
|
||||
try{
|
||||
string directory = Path.GetDirectoryName(file);
|
||||
if (directory == null)return false;
|
||||
// END OF CONFIG
|
||||
|
||||
Directory.CreateDirectory(directory);
|
||||
#pragma warning disable CS0618
|
||||
|
||||
using(Stream stream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None)){
|
||||
WriteToStream(stream);
|
||||
public bool Migrate() {
|
||||
bool hasChanged = false;
|
||||
|
||||
if (MigrationVersion < 1) {
|
||||
MigrationVersion = 1;
|
||||
hasChanged = true;
|
||||
|
||||
var userConfig = Program.Config.User;
|
||||
_enableTouchAdjustment = userConfig.EnableTouchAdjustment;
|
||||
_enableColorProfileDetection = userConfig.EnableColorProfileDetection;
|
||||
_useSystemProxyForAllConnections = userConfig.UseSystemProxyForAllConnections;
|
||||
}
|
||||
|
||||
return true;
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Configuration Error", "Could not save the system configuration file.", true, e);
|
||||
return false;
|
||||
}
|
||||
return hasChanged;
|
||||
}
|
||||
|
||||
public static SystemConfig Load(string file){
|
||||
SystemConfig config = new SystemConfig(file);
|
||||
#pragma warning restore CS0618
|
||||
|
||||
try{
|
||||
using(Stream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)){
|
||||
config.ReadFromStream(stream);
|
||||
}
|
||||
}catch(FileNotFoundException){
|
||||
}catch(DirectoryNotFoundException){
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Configuration Error", "Could not open the system configuration file. If you continue, you will lose system specific configuration such as Hardware Acceleration.", true, e);
|
||||
}
|
||||
|
||||
return config;
|
||||
public override SystemConfig ConstructWithDefaults() {
|
||||
return new SystemConfig {
|
||||
MigrationVersion = 1
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,255 +1,167 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using TweetDuck.Core;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Notification;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Browser;
|
||||
using TweetDuck.Controls;
|
||||
using TweetLib.Core;
|
||||
using TweetLib.Core.Application;
|
||||
using TweetLib.Core.Features.Notifications;
|
||||
using TweetLib.Core.Features.Twitter;
|
||||
using TweetLib.Core.Systems.Configuration;
|
||||
using TweetLib.Utils.Data;
|
||||
|
||||
namespace TweetDuck.Configuration {
|
||||
[Serializable]
|
||||
sealed class UserConfig{
|
||||
private static readonly IFormatter Formatter = new BinaryFormatter{ Binder = new LegacyBinder() };
|
||||
sealed class UserConfig : BaseConfig<UserConfig>, IAppUserConfiguration {
|
||||
public bool FirstRun { get; set; } = true;
|
||||
|
||||
private class LegacyBinder : SerializationBinder{
|
||||
public override Type BindToType(string assemblyName, string typeName){
|
||||
return Type.GetType(string.Format("{0}, {1}", typeName.Replace("TweetDck", "TweetDuck"), assemblyName.Replace("TweetDck", "TweetDuck")));
|
||||
}
|
||||
}
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
public bool AllowDataCollection { get; set; } = false;
|
||||
|
||||
private const int CurrentFileVersion = 9;
|
||||
public WindowState BrowserWindow { get; set; } = new WindowState();
|
||||
public Size PluginsWindowSize { get; set; } = Size.Empty;
|
||||
|
||||
// START OF CONFIGURATION
|
||||
public bool ExpandLinksOnHover { get; set; } = true;
|
||||
public bool FocusDmInput { get; set; } = true;
|
||||
public bool OpenSearchInFirstColumn { get; set; } = true;
|
||||
public bool KeepLikeFollowDialogsOpen { get; set; } = true;
|
||||
public bool BestImageQuality { get; set; } = true;
|
||||
public bool EnableAnimatedImages { get; set; } = true;
|
||||
public bool HideTweetsByNftUsers { get; set; } = false;
|
||||
|
||||
public WindowState BrowserWindow { get; set; }
|
||||
public bool DisplayNotificationColumn { get; set; }
|
||||
public bool DisplayNotificationTimer { get; set; }
|
||||
public bool NotificationTimerCountDown { get; set; }
|
||||
public bool NotificationSkipOnLinkClick { get; set; }
|
||||
public bool NotificationNonIntrusiveMode { get; set; }
|
||||
private bool _enableSmoothScrolling = true;
|
||||
private string _customCefArgs = null;
|
||||
|
||||
public TweetNotification.Position NotificationPosition { get; set; }
|
||||
public Point CustomNotificationPosition { get; set; }
|
||||
public int NotificationEdgeDistance { get; set; }
|
||||
public int NotificationDisplay { get; set; }
|
||||
public int NotificationIdlePauseSeconds { get; set; }
|
||||
public int NotificationDurationValue { get; set; }
|
||||
public string BrowserPath { get; set; } = null;
|
||||
public string BrowserPathArgs { get; set; } = null;
|
||||
public bool IgnoreTrackingUrlWarning { get; set; } = false;
|
||||
public string SearchEngineUrl { get; set; } = null;
|
||||
private int _zoomLevel = 100;
|
||||
|
||||
public bool EnableSpellCheck { get; set; }
|
||||
public bool ExpandLinksOnHover { get; set; }
|
||||
public bool SwitchAccountSelectors { get; set; }
|
||||
public bool EnableTrayHighlight { get; set; }
|
||||
public string VideoPlayerPath { get; set; } = null;
|
||||
public string VideoPlayerPathArgs { get; set; } = null;
|
||||
public int VideoPlayerVolume { get; set; } = 50;
|
||||
|
||||
public bool EnableUpdateCheck { get; set; }
|
||||
public string DismissedUpdate { get; set; }
|
||||
public bool EnableSpellCheck { get; set; } = false;
|
||||
private string _spellCheckLanguage = "en-US";
|
||||
|
||||
public WindowState PluginsWindow { get; set; }
|
||||
public string TranslationTarget { get; set; } = "en";
|
||||
public int CalendarFirstDay { get; set; } = -1;
|
||||
|
||||
public string CustomCefArgs { get; set; }
|
||||
public string CustomBrowserCSS { get; set; }
|
||||
public string CustomNotificationCSS { get; set; }
|
||||
private TrayIcon.Behavior _trayBehavior = TrayIcon.Behavior.Disabled;
|
||||
public bool EnableTrayHighlight { get; set; } = true;
|
||||
|
||||
public bool EnableUpdateCheck { get; set; } = true;
|
||||
public string DismissedUpdate { get; set; } = null;
|
||||
|
||||
public bool DisplayNotificationColumn { get; set; } = false;
|
||||
public bool NotificationMediaPreviews { get; set; } = true;
|
||||
public bool NotificationSkipOnLinkClick { get; set; } = false;
|
||||
public bool NotificationNonIntrusiveMode { get; set; } = true;
|
||||
public int NotificationIdlePauseSeconds { get; set; } = 0;
|
||||
|
||||
public bool DisplayNotificationTimer { get; set; } = true;
|
||||
public bool NotificationTimerCountDown { get; set; } = false;
|
||||
public int NotificationDurationValue { get; set; } = 25;
|
||||
|
||||
public DesktopNotification.Position NotificationPosition { get; set; } = DesktopNotification.Position.TopRight;
|
||||
public Point CustomNotificationPosition { get; set; } = ControlExtensions.InvisibleLocation;
|
||||
public int NotificationDisplay { get; set; } = 0;
|
||||
public int NotificationEdgeDistance { get; set; } = 8;
|
||||
public int NotificationWindowOpacity { get; set; } = 100;
|
||||
|
||||
public DesktopNotification.Size NotificationSize { get; set; } = DesktopNotification.Size.Auto;
|
||||
public Size CustomNotificationSize { get; set; } = Size.Empty;
|
||||
public int NotificationScrollSpeed { get; set; } = 100;
|
||||
|
||||
private string _notificationSoundPath;
|
||||
private int _notificationSoundVolume = 100;
|
||||
|
||||
private bool _muteNotifications;
|
||||
|
||||
public string CustomBrowserCSS { get; set; } = null;
|
||||
public string CustomNotificationCSS { get; set; } = null;
|
||||
|
||||
public bool DevToolsInContextMenu { get; set; } = false;
|
||||
public bool DevToolsWindowOnTop { get; set; } = true;
|
||||
|
||||
// SPECIAL PROPERTIES
|
||||
|
||||
public bool IsCustomNotificationPositionSet => CustomNotificationPosition != ControlExtensions.InvisibleLocation;
|
||||
public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty;
|
||||
public bool IsCustomSoundNotificationSet => NotificationSoundPath != string.Empty;
|
||||
|
||||
public ImageQuality TwitterImageQuality => BestImageQuality ? ImageQuality.Best : ImageQuality.Default;
|
||||
|
||||
public string NotificationSoundPath {
|
||||
get => string.IsNullOrEmpty(notificationSoundPath) ? string.Empty : notificationSoundPath;
|
||||
set => notificationSoundPath = value;
|
||||
get => _notificationSoundPath ?? string.Empty;
|
||||
set => UpdatePropertyWithEvent(ref _notificationSoundPath, value, SoundNotificationChanged);
|
||||
}
|
||||
|
||||
public int NotificationSoundVolume {
|
||||
get => _notificationSoundVolume;
|
||||
set => UpdatePropertyWithEvent(ref _notificationSoundVolume, value, SoundNotificationChanged);
|
||||
}
|
||||
|
||||
public bool MuteNotifications {
|
||||
get => muteNotifications;
|
||||
|
||||
set{
|
||||
if (muteNotifications != value){
|
||||
muteNotifications = value;
|
||||
MuteToggled?.Invoke(this, new EventArgs());
|
||||
}
|
||||
}
|
||||
get => _muteNotifications;
|
||||
set => UpdatePropertyWithEvent(ref _muteNotifications, value, MuteToggled);
|
||||
}
|
||||
|
||||
public int ZoomLevel {
|
||||
get => zoomLevel;
|
||||
|
||||
set{
|
||||
if (zoomLevel != value){
|
||||
zoomLevel = value;
|
||||
ZoomLevelChanged?.Invoke(this, new EventArgs());
|
||||
get => _zoomLevel;
|
||||
set => UpdatePropertyWithEvent(ref _zoomLevel, value, ZoomLevelChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double ZoomMultiplier => zoomLevel/100.0;
|
||||
|
||||
public TrayIcon.Behavior TrayBehavior {
|
||||
get => trayBehavior;
|
||||
|
||||
set{
|
||||
if (trayBehavior != value){
|
||||
trayBehavior = value;
|
||||
TrayBehaviorChanged?.Invoke(this, new EventArgs());
|
||||
}
|
||||
}
|
||||
get => _trayBehavior;
|
||||
set => UpdatePropertyWithEvent(ref _trayBehavior, value, TrayBehaviorChanged);
|
||||
}
|
||||
|
||||
// END OF CONFIGURATION
|
||||
public bool EnableSmoothScrolling {
|
||||
get => _enableSmoothScrolling;
|
||||
set => UpdatePropertyWithCallback(ref _enableSmoothScrolling, value, App.ConfigManager.TriggerProgramRestartRequested);
|
||||
}
|
||||
|
||||
public string CustomCefArgs {
|
||||
get => _customCefArgs;
|
||||
set => UpdatePropertyWithCallback(ref _customCefArgs, value, App.ConfigManager.TriggerProgramRestartRequested);
|
||||
}
|
||||
|
||||
public string SpellCheckLanguage {
|
||||
get => _spellCheckLanguage;
|
||||
set => UpdatePropertyWithCallback(ref _spellCheckLanguage, value, App.ConfigManager.TriggerProgramRestartRequested);
|
||||
}
|
||||
|
||||
// DEPRECATED
|
||||
|
||||
[Obsolete("Moved to SystemConfig")]
|
||||
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
|
||||
public bool EnableTouchAdjustment { get; set; }
|
||||
|
||||
[Obsolete("Moved to SystemConfig")]
|
||||
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
|
||||
public bool EnableColorProfileDetection { get; set; }
|
||||
|
||||
[Obsolete("Moved to SystemConfig")]
|
||||
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
|
||||
public bool UseSystemProxyForAllConnections { get; set; }
|
||||
|
||||
// EVENTS
|
||||
|
||||
[field:NonSerialized]
|
||||
public event EventHandler MuteToggled;
|
||||
|
||||
[field:NonSerialized]
|
||||
public event EventHandler ZoomLevelChanged;
|
||||
|
||||
[field:NonSerialized]
|
||||
public event EventHandler TrayBehaviorChanged;
|
||||
public event EventHandler SoundNotificationChanged;
|
||||
public event EventHandler OptionsDialogClosed;
|
||||
|
||||
[NonSerialized]
|
||||
private string file;
|
||||
|
||||
private int fileVersion;
|
||||
private bool muteNotifications;
|
||||
private int zoomLevel;
|
||||
private string notificationSoundPath;
|
||||
private TrayIcon.Behavior trayBehavior;
|
||||
|
||||
private UserConfig(string file){
|
||||
this.file = file;
|
||||
|
||||
BrowserWindow = new WindowState();
|
||||
ZoomLevel = 100;
|
||||
DisplayNotificationTimer = true;
|
||||
NotificationNonIntrusiveMode = true;
|
||||
NotificationPosition = TweetNotification.Position.TopRight;
|
||||
CustomNotificationPosition = ControlExtensions.InvisibleLocation;
|
||||
NotificationEdgeDistance = 8;
|
||||
NotificationDurationValue = 25;
|
||||
EnableUpdateCheck = true;
|
||||
ExpandLinksOnHover = true;
|
||||
SwitchAccountSelectors = true;
|
||||
EnableTrayHighlight = true;
|
||||
PluginsWindow = new WindowState();
|
||||
public void TriggerOptionsDialogClosed() {
|
||||
OptionsDialogClosed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void UpgradeFile(){
|
||||
if (fileVersion == CurrentFileVersion){
|
||||
return;
|
||||
}
|
||||
// END OF CONFIG
|
||||
|
||||
// if outdated, cycle through all versions
|
||||
if (fileVersion == 0){
|
||||
DisplayNotificationTimer = true;
|
||||
EnableUpdateCheck = true;
|
||||
++fileVersion;
|
||||
}
|
||||
|
||||
if (fileVersion == 1){
|
||||
ExpandLinksOnHover = true;
|
||||
++fileVersion;
|
||||
}
|
||||
|
||||
if (fileVersion == 2){
|
||||
BrowserWindow = new WindowState();
|
||||
PluginsWindow = new WindowState();
|
||||
++fileVersion;
|
||||
}
|
||||
|
||||
if (fileVersion == 3){
|
||||
EnableTrayHighlight = true;
|
||||
NotificationDurationValue = 25;
|
||||
++fileVersion;
|
||||
}
|
||||
|
||||
if (fileVersion == 4){
|
||||
++fileVersion;
|
||||
}
|
||||
|
||||
if (fileVersion == 5){
|
||||
++fileVersion;
|
||||
}
|
||||
|
||||
if (fileVersion == 6){
|
||||
NotificationNonIntrusiveMode = true;
|
||||
++fileVersion;
|
||||
}
|
||||
|
||||
if (fileVersion == 7){
|
||||
ZoomLevel = 100;
|
||||
++fileVersion;
|
||||
}
|
||||
|
||||
if (fileVersion == 8){
|
||||
SwitchAccountSelectors = true;
|
||||
++fileVersion;
|
||||
}
|
||||
|
||||
// update the version
|
||||
fileVersion = CurrentFileVersion;
|
||||
Save();
|
||||
}
|
||||
|
||||
public bool Save(){
|
||||
try{
|
||||
string directory = Path.GetDirectoryName(file);
|
||||
if (directory == null)return false;
|
||||
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
if (File.Exists(file)){
|
||||
string backupFile = GetBackupFile(file);
|
||||
File.Delete(backupFile);
|
||||
File.Move(file, backupFile);
|
||||
}
|
||||
|
||||
using(Stream stream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None)){
|
||||
Formatter.Serialize(stream, this);
|
||||
}
|
||||
|
||||
return true;
|
||||
}catch(Exception e){
|
||||
Program.Reporter.HandleException("Configuration Error", "Could not save the configuration file.", true, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static UserConfig Load(string file){
|
||||
UserConfig config = null;
|
||||
Exception firstException = null;
|
||||
|
||||
for(int attempt = 0; attempt < 2; attempt++){
|
||||
try{
|
||||
using(Stream stream = new FileStream(attempt == 0 ? file : GetBackupFile(file), FileMode.Open, FileAccess.Read, FileShare.Read)){
|
||||
if ((config = Formatter.Deserialize(stream) as UserConfig) != null){
|
||||
config.file = file;
|
||||
}
|
||||
}
|
||||
|
||||
config?.UpgradeFile();
|
||||
break;
|
||||
}catch(FileNotFoundException){
|
||||
}catch(DirectoryNotFoundException){
|
||||
break;
|
||||
}catch(Exception e){
|
||||
if (attempt == 0){
|
||||
firstException = e;
|
||||
Program.Reporter.Log(e.ToString());
|
||||
}
|
||||
else if (firstException != null){
|
||||
Program.Reporter.HandleException("Configuration Error", "Could not open the backup configuration file. If you continue, your program options will be reset.", true, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (firstException != null && config == null){
|
||||
Program.Reporter.HandleException("Configuration Error", "Could not open the configuration file.", true, firstException);
|
||||
}
|
||||
|
||||
return config ?? new UserConfig(file);
|
||||
}
|
||||
|
||||
public static string GetBackupFile(string file){
|
||||
return file+".bak";
|
||||
public override UserConfig ConstructWithDefaults() {
|
||||
return new UserConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
103
Controls/ControlExtensions.cs
Normal file
103
Controls/ControlExtensions.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using TweetLib.Utils.Data;
|
||||
|
||||
namespace TweetDuck.Controls {
|
||||
static class ControlExtensions {
|
||||
public static readonly Point InvisibleLocation = new Point(-32000, -32000);
|
||||
|
||||
public static void InvokeSafe(this Control control, Action func) {
|
||||
if (control.InvokeRequired) {
|
||||
control.Invoke(func);
|
||||
}
|
||||
else {
|
||||
func();
|
||||
}
|
||||
}
|
||||
|
||||
public static void InvokeAsyncSafe(this Control control, Action func) {
|
||||
if (control.InvokeRequired) {
|
||||
control.BeginInvoke(func);
|
||||
}
|
||||
else {
|
||||
func();
|
||||
}
|
||||
}
|
||||
|
||||
public static float GetDPIScale(this Control control) {
|
||||
using Graphics graphics = control.CreateGraphics();
|
||||
return graphics.DpiY / 96F;
|
||||
}
|
||||
|
||||
public static bool IsFullyOutsideView(this Form form) {
|
||||
return !Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(form.Bounds));
|
||||
}
|
||||
|
||||
public static void MoveToCenter(this Form targetForm, Form parentForm) {
|
||||
targetForm.Location = new Point(parentForm.Location.X + (parentForm.Width / 2) - (targetForm.Width / 2), parentForm.Location.Y + (parentForm.Height / 2) - (targetForm.Height / 2));
|
||||
}
|
||||
|
||||
public static void SetValueInstant(this ProgressBar bar, int value) {
|
||||
if (value == bar.Maximum) {
|
||||
bar.Value = value;
|
||||
bar.Value = value - 1;
|
||||
bar.Value = value;
|
||||
}
|
||||
else {
|
||||
bar.Value = value + 1;
|
||||
bar.Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetValueSafe(this NumericUpDown numUpDown, int value) {
|
||||
if (value >= numUpDown.Minimum && value <= numUpDown.Maximum) {
|
||||
numUpDown.Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetValueSafe(this TrackBar trackBar, int value) {
|
||||
if (value >= trackBar.Minimum && value <= trackBar.Maximum) {
|
||||
trackBar.Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool AlignValueToTick(this TrackBar trackBar) {
|
||||
if (trackBar.Value % trackBar.SmallChange != 0) {
|
||||
trackBar.Value = trackBar.SmallChange * (int) Math.Floor(((double) trackBar.Value / trackBar.SmallChange) + 0.5);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void EnableMultilineShortcuts(this TextBox textBox) {
|
||||
textBox.KeyDown += (sender, args) => {
|
||||
if (args.Control && args.KeyCode == Keys.A) {
|
||||
((TextBox) sender).SelectAll();
|
||||
args.SuppressKeyPress = true;
|
||||
args.Handled = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static void Save(this WindowState state, Form form) {
|
||||
state.Bounds = form.WindowState == FormWindowState.Normal ? form.DesktopBounds : form.RestoreBounds;
|
||||
state.IsMaximized = form.WindowState == FormWindowState.Maximized;
|
||||
}
|
||||
|
||||
public static void Restore(this WindowState state, Form form, bool firstTimeFullscreen) {
|
||||
if (state.Bounds != Rectangle.Empty) {
|
||||
form.DesktopBounds = state.Bounds;
|
||||
form.WindowState = state.IsMaximized ? FormWindowState.Maximized : FormWindowState.Normal;
|
||||
}
|
||||
|
||||
if ((state.Bounds == Rectangle.Empty && firstTimeFullscreen) || form.IsFullyOutsideView()) {
|
||||
form.DesktopBounds = Screen.PrimaryScreen.WorkingArea;
|
||||
form.WindowState = FormWindowState.Maximized;
|
||||
state.Save(form);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
16
Controls/FlatButton.cs
Normal file
16
Controls/FlatButton.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck.Controls {
|
||||
sealed class FlatButton : Button {
|
||||
protected override bool ShowFocusCues => false;
|
||||
|
||||
public FlatButton() {
|
||||
GotFocus += FlatButton_GotFocus;
|
||||
}
|
||||
|
||||
private void FlatButton_GotFocus(object sender, EventArgs e) { // removes extra border when focused
|
||||
NotifyDefault(false);
|
||||
}
|
||||
}
|
||||
}
|
38
Controls/FlatProgressBar.cs
Normal file
38
Controls/FlatProgressBar.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck.Controls {
|
||||
sealed class FlatProgressBar : ProgressBar {
|
||||
private readonly SolidBrush brush;
|
||||
|
||||
public FlatProgressBar() {
|
||||
brush = new SolidBrush(Color.White);
|
||||
|
||||
SetStyle(ControlStyles.UserPaint, true);
|
||||
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
|
||||
}
|
||||
|
||||
public void SetValueInstant(int value) {
|
||||
ControlExtensions.SetValueInstant(this, Math.Max(Minimum, Math.Min(Maximum, value)));
|
||||
}
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e) {
|
||||
if (brush.Color != ForeColor) {
|
||||
brush.Color = ForeColor;
|
||||
}
|
||||
|
||||
Rectangle rect = e.ClipRectangle;
|
||||
rect.Width = (int) (rect.Width * ((double) Value / Maximum));
|
||||
e.Graphics.FillRectangle(brush, rect);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing) {
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing) {
|
||||
brush.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
14
Controls/FlowLayoutPanelNoHScroll.cs
Normal file
14
Controls/FlowLayoutPanelNoHScroll.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Utils;
|
||||
|
||||
namespace TweetDuck.Controls {
|
||||
sealed class FlowLayoutPanelNoHScroll : FlowLayoutPanel {
|
||||
protected override void WndProc(ref Message m) {
|
||||
if (m.Msg == 0x85) { // WM_NCPAINT
|
||||
NativeMethods.ShowScrollBar(Handle, NativeMethods.SB_HORZ, false); // basically fuck the horizontal scrollbar very much
|
||||
}
|
||||
|
||||
base.WndProc(ref m);
|
||||
}
|
||||
}
|
||||
}
|
22
Controls/LabelVertical.cs
Normal file
22
Controls/LabelVertical.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck.Controls {
|
||||
sealed class LabelVertical : Label {
|
||||
public int LineHeight { get; set; }
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e) {
|
||||
int y = (int) Math.Floor((ClientRectangle.Height - Text.Length * LineHeight) / 2F) - 1;
|
||||
using Brush brush = new SolidBrush(ForeColor);
|
||||
|
||||
foreach (char chr in Text) {
|
||||
string str = chr.ToString();
|
||||
float x = (ClientRectangle.Width - e.Graphics.MeasureString(str, Font).Width) / 2F;
|
||||
|
||||
e.Graphics.DrawString(str, Font, brush, x, y);
|
||||
y += LineHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
Controls/NumericUpDownEx.cs
Normal file
18
Controls/NumericUpDownEx.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck.Controls {
|
||||
sealed class NumericUpDownEx : NumericUpDown {
|
||||
public string TextSuffix { get; set ; }
|
||||
|
||||
protected override void UpdateEditText() {
|
||||
base.UpdateEditText();
|
||||
|
||||
if (LicenseManager.UsageMode != LicenseUsageMode.Designtime) {
|
||||
ChangingText = true;
|
||||
Text += TextSuffix;
|
||||
ChangingText = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,19 +0,0 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
|
||||
namespace TweetDuck.Core.Bridge{
|
||||
sealed class CallbackBridge{
|
||||
private readonly Control owner;
|
||||
private readonly Action safeCallback;
|
||||
|
||||
public CallbackBridge(Control owner, Action safeCallback){
|
||||
this.owner = owner;
|
||||
this.safeCallback = safeCallback;
|
||||
}
|
||||
|
||||
public void Trigger(){
|
||||
owner.InvokeSafe(safeCallback);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,45 +0,0 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace TweetDuck.Core.Bridge{
|
||||
static class PropertyBridge{
|
||||
[Flags]
|
||||
public enum Properties{
|
||||
ExpandLinksOnHover = 1,
|
||||
MuteNotifications = 2,
|
||||
HasCustomNotificationSound = 4,
|
||||
SkipOnLinkClick = 8,
|
||||
SwitchAccountSelectors = 16,
|
||||
AllBrowser = ExpandLinksOnHover | SwitchAccountSelectors | MuteNotifications | HasCustomNotificationSound,
|
||||
AllNotification = ExpandLinksOnHover | SkipOnLinkClick
|
||||
}
|
||||
|
||||
public static string GenerateScript(Properties properties){
|
||||
StringBuilder build = new StringBuilder();
|
||||
build.Append("(function(c){");
|
||||
|
||||
if (properties.HasFlag(Properties.ExpandLinksOnHover)){
|
||||
build.Append("c.expandLinksOnHover=").Append(Program.UserConfig.ExpandLinksOnHover ? "true;" : "false;");
|
||||
}
|
||||
|
||||
if (properties.HasFlag(Properties.SwitchAccountSelectors)){
|
||||
build.Append("c.switchAccountSelectors=").Append(Program.UserConfig.SwitchAccountSelectors ? "true;" : "false;");
|
||||
}
|
||||
|
||||
if (properties.HasFlag(Properties.MuteNotifications)){
|
||||
build.Append("c.muteNotifications=").Append(Program.UserConfig.MuteNotifications ? "true;" : "false;");
|
||||
}
|
||||
|
||||
if (properties.HasFlag(Properties.HasCustomNotificationSound)){
|
||||
build.Append("c.hasCustomNotificationSound=").Append(Program.UserConfig.NotificationSoundPath.Length > 0 ? "true;" : "false;");
|
||||
}
|
||||
|
||||
if (properties.HasFlag(Properties.SkipOnLinkClick)){
|
||||
build.Append("c.skipOnLinkClick=").Append(Program.UserConfig.NotificationSkipOnLinkClick ? "true;" : "false;");
|
||||
}
|
||||
|
||||
build.Append("})(window.$TDX=window.$TDX||{})");
|
||||
return build.ToString();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,118 +0,0 @@
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Notification;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Bridge{
|
||||
sealed class TweetDeckBridge{
|
||||
public static string LastRightClickedLink = string.Empty;
|
||||
public static string LastHighlightedTweet = string.Empty;
|
||||
public static string LastHighlightedQuotedTweet = string.Empty;
|
||||
|
||||
public static void ResetStaticProperties(){
|
||||
LastRightClickedLink = LastHighlightedTweet = LastHighlightedQuotedTweet = string.Empty;
|
||||
}
|
||||
|
||||
private readonly FormBrowser form;
|
||||
private readonly FormNotificationMain notification;
|
||||
|
||||
public TweetDeckBridge(FormBrowser form, FormNotificationMain notification){
|
||||
this.form = form;
|
||||
this.notification = notification;
|
||||
}
|
||||
|
||||
public void LoadFontSizeClass(string fsClass){
|
||||
form.InvokeAsyncSafe(() => {
|
||||
TweetNotification.SetFontSizeClass(fsClass);
|
||||
});
|
||||
}
|
||||
|
||||
public void LoadNotificationHeadContents(string headContents){
|
||||
form.InvokeAsyncSafe(() => {
|
||||
TweetNotification.SetHeadTag(headContents);
|
||||
});
|
||||
}
|
||||
|
||||
public void SetLastRightClickedLink(string link){
|
||||
form.InvokeAsyncSafe(() => LastRightClickedLink = link);
|
||||
}
|
||||
|
||||
public void SetLastHighlightedTweet(string link, string quotedLink){
|
||||
form.InvokeAsyncSafe(() => {
|
||||
LastHighlightedTweet = link;
|
||||
LastHighlightedQuotedTweet = quotedLink;
|
||||
});
|
||||
}
|
||||
|
||||
public void OpenContextMenu(){
|
||||
form.InvokeAsyncSafe(form.OpenContextMenu);
|
||||
}
|
||||
|
||||
public void OnTweetPopup(string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl){
|
||||
notification.InvokeAsyncSafe(() => {
|
||||
form.OnTweetNotification();
|
||||
notification.ShowNotification(new TweetNotification(columnName, tweetHtml, tweetCharacters, tweetUrl, quoteUrl));
|
||||
});
|
||||
}
|
||||
|
||||
public void OnTweetSound(){
|
||||
form.InvokeAsyncSafe(() => {
|
||||
form.OnTweetNotification();
|
||||
form.PlayNotificationSound();
|
||||
});
|
||||
}
|
||||
|
||||
public void DisplayTooltip(string text, bool showInNotification){
|
||||
if (showInNotification){
|
||||
notification.InvokeAsyncSafe(() => notification.DisplayTooltip(text));
|
||||
}
|
||||
else{
|
||||
form.InvokeAsyncSafe(() => form.DisplayTooltip(text));
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadNextNotification(){
|
||||
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
|
||||
}
|
||||
|
||||
public void ScreenshotTweet(string html, int width, int height){
|
||||
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width, height));
|
||||
}
|
||||
|
||||
public void FixClipboard(){
|
||||
form.InvokeAsyncSafe(WindowsUtils.ClipboardStripHtmlStyles);
|
||||
}
|
||||
|
||||
public int GetIdleSeconds(){
|
||||
return NativeMethods.GetIdleSeconds();
|
||||
}
|
||||
|
||||
public void OpenBrowser(string url){
|
||||
BrowserUtils.OpenExternalBrowser(url);
|
||||
}
|
||||
|
||||
public void Alert(string type, string contents){
|
||||
MessageBoxIcon icon;
|
||||
|
||||
switch(type){
|
||||
case "error": icon = MessageBoxIcon.Error; break;
|
||||
case "warning": icon = MessageBoxIcon.Warning; break;
|
||||
case "info": icon = MessageBoxIcon.Information; break;
|
||||
default: icon = MessageBoxIcon.None; break;
|
||||
}
|
||||
|
||||
MessageBox.Show(contents, Program.BrandName+" Browser Message", MessageBoxButtons.OK, icon);
|
||||
}
|
||||
|
||||
public void CrashDebug(string message){
|
||||
#if DEBUG
|
||||
Log(message);
|
||||
System.Diagnostics.Debugger.Break();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Log(string data){
|
||||
System.Diagnostics.Debug.WriteLine(data);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,66 +0,0 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Controls{
|
||||
static class ControlExtensions{
|
||||
public static readonly Point InvisibleLocation = new Point(-32000, -32000);
|
||||
|
||||
public static void InvokeSafe(this Control control, Action func){
|
||||
if (control.InvokeRequired){
|
||||
control.Invoke(func);
|
||||
}
|
||||
else{
|
||||
func();
|
||||
}
|
||||
}
|
||||
|
||||
public static void InvokeAsyncSafe(this Control control, Action func){
|
||||
control.BeginInvoke(func);
|
||||
}
|
||||
|
||||
public static bool IsFullyOutsideView(this Form form){
|
||||
return !Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(form.Bounds));
|
||||
}
|
||||
|
||||
public static void MoveToCenter(this Form targetForm, Form parentForm){
|
||||
targetForm.Location = new Point(parentForm.Location.X+parentForm.Width/2-targetForm.Width/2, parentForm.Location.Y+parentForm.Height/2-targetForm.Height/2);
|
||||
}
|
||||
|
||||
public static void SetValueInstant(this ProgressBar bar, int value){
|
||||
if (value == bar.Maximum){
|
||||
bar.Value = value;
|
||||
bar.Value = value-1;
|
||||
bar.Value = value;
|
||||
}
|
||||
else{
|
||||
bar.Value = value+1;
|
||||
bar.Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetValueSafe(this TrackBar trackBar, int value){
|
||||
if (value >= trackBar.Minimum && value <= trackBar.Maximum){
|
||||
trackBar.Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetElevated(this Button button){
|
||||
button.Text = " "+button.Text;
|
||||
button.FlatStyle = FlatStyle.System;
|
||||
NativeMethods.SendMessage(button.Handle, NativeMethods.BCM_SETSHIELD, 0, new IntPtr(1));
|
||||
}
|
||||
|
||||
public static void EnableMultilineShortcuts(this TextBox textBox){
|
||||
textBox.KeyDown += (sender, args) => {
|
||||
if (args.Control && args.KeyCode == Keys.A){
|
||||
((TextBox)sender).SelectAll();
|
||||
args.SuppressKeyPress = true;
|
||||
args.Handled = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck.Core.Controls{
|
||||
class FlatButton : Button{
|
||||
protected override bool ShowFocusCues => false;
|
||||
|
||||
public FlatButton(){
|
||||
GotFocus += FlatButton_GotFocus;
|
||||
}
|
||||
|
||||
private void FlatButton_GotFocus(object sender, EventArgs e){ // removes extra border when focused
|
||||
NotifyDefault(false);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,37 +0,0 @@
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck.Core.Controls{
|
||||
sealed class FlatProgressBar : ProgressBar{
|
||||
private readonly SolidBrush brush;
|
||||
|
||||
public FlatProgressBar(){
|
||||
brush = new SolidBrush(Color.White);
|
||||
|
||||
SetStyle(ControlStyles.UserPaint, true);
|
||||
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
|
||||
}
|
||||
|
||||
public void SetValueInstant(int value){
|
||||
ControlExtensions.SetValueInstant(this, value);
|
||||
}
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e){
|
||||
if (brush.Color != ForeColor){
|
||||
brush.Color = ForeColor;
|
||||
}
|
||||
|
||||
Rectangle rect = e.ClipRectangle;
|
||||
rect.Width = (int)(rect.Width*((double)Value/Maximum));
|
||||
e.Graphics.FillRectangle(brush,rect);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing){
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing){
|
||||
brush.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,25 +0,0 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck.Core.Controls{
|
||||
sealed class TabButton : FlatButton{
|
||||
public Action Callback { get; private set; }
|
||||
|
||||
public void SetupButton(int locationX, int sizeWidth, string title, Action callback){
|
||||
Callback = callback;
|
||||
|
||||
SuspendLayout();
|
||||
FlatAppearance.BorderColor = Color.DimGray;
|
||||
FlatAppearance.MouseDownBackColor = Color.White;
|
||||
FlatAppearance.MouseOverBackColor = Color.White;
|
||||
FlatStyle = FlatStyle.Flat;
|
||||
Location = new Point(locationX, 0);
|
||||
Margin = new Padding(0);
|
||||
Size = new Size(sizeWidth, 30);
|
||||
Text = title;
|
||||
UseVisualStyleBackColor = true;
|
||||
ResumeLayout(true);
|
||||
}
|
||||
}
|
||||
}
|
68
Core/Controls/TabPanel.Designer.cs
generated
68
Core/Controls/TabPanel.Designer.cs
generated
@@ -1,68 +0,0 @@
|
||||
namespace TweetDuck.Core.Controls {
|
||||
partial class TabPanel {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing && (components != null)) {
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Component Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
this.panelButtons = new System.Windows.Forms.Panel();
|
||||
this.panelContent = new System.Windows.Forms.Panel();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// panelButtons
|
||||
//
|
||||
this.panelButtons.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
this.panelButtons.Location = new System.Drawing.Point(0, 0);
|
||||
this.panelButtons.Margin = new System.Windows.Forms.Padding(0);
|
||||
this.panelButtons.Name = "panelButtons";
|
||||
this.panelButtons.Size = new System.Drawing.Size(640, 30);
|
||||
this.panelButtons.TabIndex = 0;
|
||||
//
|
||||
// panelContent
|
||||
//
|
||||
this.panelContent.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.panelContent.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
||||
this.panelContent.Location = new System.Drawing.Point(0, 29);
|
||||
this.panelContent.Margin = new System.Windows.Forms.Padding(0);
|
||||
this.panelContent.Name = "panelContent";
|
||||
this.panelContent.Size = new System.Drawing.Size(640, 451);
|
||||
this.panelContent.TabIndex = 1;
|
||||
//
|
||||
// TabPanel
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.Controls.Add(this.panelContent);
|
||||
this.Controls.Add(this.panelButtons);
|
||||
this.Name = "TabPanel";
|
||||
this.Size = new System.Drawing.Size(640, 480);
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Panel panelButtons;
|
||||
private System.Windows.Forms.Panel panelContent;
|
||||
}
|
||||
}
|
@@ -1,52 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck.Core.Controls{
|
||||
sealed partial class TabPanel : UserControl{
|
||||
public Panel Content => panelContent;
|
||||
public IEnumerable<TabButton> Buttons => panelButtons.Controls.Cast<TabButton>();
|
||||
|
||||
public TabButton ActiveButton { get; private set; }
|
||||
|
||||
private int btnWidth;
|
||||
|
||||
public TabPanel(){
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void SetupTabPanel(int buttonWidth){
|
||||
this.btnWidth = buttonWidth;
|
||||
}
|
||||
|
||||
public TabButton AddButton(string title, Action callback){
|
||||
TabButton button = new TabButton();
|
||||
button.SetupButton((btnWidth-1)*panelButtons.Controls.Count, btnWidth, title, callback);
|
||||
button.Click += (sender, args) => SelectTab((TabButton)sender);
|
||||
|
||||
panelButtons.Controls.Add(button);
|
||||
return button;
|
||||
}
|
||||
|
||||
public void SelectTab(TabButton button){
|
||||
if (ActiveButton != null){
|
||||
ActiveButton.BackColor = SystemColors.Control;
|
||||
}
|
||||
|
||||
button.BackColor = Color.White;
|
||||
button.Callback();
|
||||
|
||||
ActiveButton = button;
|
||||
}
|
||||
|
||||
public void ReplaceContent(Control newControl){
|
||||
newControl.Dock = DockStyle.Fill;
|
||||
Content.SuspendLayout();
|
||||
Content.Controls.Clear();
|
||||
Content.Controls.Add(newControl);
|
||||
Content.ResumeLayout(true);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,460 +0,0 @@
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Handling;
|
||||
using TweetDuck.Core.Notification;
|
||||
using TweetDuck.Core.Notification.Screenshot;
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
using TweetDuck.Plugins.Events;
|
||||
using TweetDuck.Resources;
|
||||
using TweetDuck.Updates;
|
||||
using TweetDuck.Updates.Events;
|
||||
using TweetLib.Audio.Utils;
|
||||
|
||||
namespace TweetDuck.Core{
|
||||
sealed partial class FormBrowser : Form{
|
||||
private static UserConfig Config => Program.UserConfig;
|
||||
|
||||
public string UpdateInstallerPath { get; private set; }
|
||||
|
||||
private readonly ChromiumWebBrowser browser;
|
||||
private readonly PluginManager plugins;
|
||||
private readonly UpdateHandler updates;
|
||||
private readonly FormNotificationTweet notification;
|
||||
private readonly ContextMenu contextMenu;
|
||||
|
||||
private bool isLoaded;
|
||||
private bool isBrowserReady;
|
||||
private FormWindowState prevState;
|
||||
|
||||
private TweetScreenshotManager notificationScreenshotManager;
|
||||
private SoundNotification soundNotification;
|
||||
|
||||
public FormBrowser(PluginManager pluginManager, UpdaterSettings updaterSettings){
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName;
|
||||
|
||||
this.plugins = pluginManager;
|
||||
this.plugins.Reloaded += plugins_Reloaded;
|
||||
this.plugins.PluginChangedState += plugins_PluginChangedState;
|
||||
|
||||
this.contextMenu = ContextMenuBrowser.CreateMenu(this);
|
||||
|
||||
this.notification = new FormNotificationTweet(this, plugins){
|
||||
#if DEBUG
|
||||
CanMoveWindow = () => (ModifierKeys & Keys.Alt) == Keys.Alt
|
||||
#else
|
||||
CanMoveWindow = () => false
|
||||
#endif
|
||||
};
|
||||
|
||||
this.notification.Show();
|
||||
|
||||
this.browser = new ChromiumWebBrowser("https://tweetdeck.twitter.com/"){
|
||||
MenuHandler = new ContextMenuBrowser(this),
|
||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||
LifeSpanHandler = new LifeSpanHandler(),
|
||||
RequestHandler = new RequestHandlerBrowser()
|
||||
};
|
||||
|
||||
#if DEBUG
|
||||
this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
|
||||
#endif
|
||||
|
||||
this.browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||
this.browser.FrameLoadStart += browser_FrameLoadStart;
|
||||
this.browser.FrameLoadEnd += browser_FrameLoadEnd;
|
||||
this.browser.LoadError += browser_LoadError;
|
||||
this.browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(this, notification));
|
||||
this.browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
|
||||
|
||||
browser.BrowserSettings.BackgroundColor = (uint)BrowserUtils.BackgroundColor.ToArgb();
|
||||
browser.Dock = DockStyle.None;
|
||||
browser.Location = ControlExtensions.InvisibleLocation;
|
||||
Controls.Add(browser);
|
||||
|
||||
Controls.Add(new MenuStrip{ Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update
|
||||
|
||||
Disposed += (sender, args) => {
|
||||
browser.Dispose();
|
||||
contextMenu.Dispose();
|
||||
|
||||
notificationScreenshotManager?.Dispose();
|
||||
soundNotification?.Dispose();
|
||||
};
|
||||
|
||||
this.trayIcon.ClickRestore += trayIcon_ClickRestore;
|
||||
this.trayIcon.ClickClose += trayIcon_ClickClose;
|
||||
Config.TrayBehaviorChanged += Config_TrayBehaviorChanged;
|
||||
|
||||
UpdateTrayIcon();
|
||||
|
||||
Config.MuteToggled += Config_MuteToggled;
|
||||
Config.ZoomLevelChanged += Config_ZoomLevelChanged;
|
||||
|
||||
this.updates = new UpdateHandler(browser, this, updaterSettings);
|
||||
this.updates.UpdateAccepted += updates_UpdateAccepted;
|
||||
this.updates.UpdateDismissed += updates_UpdateDismissed;
|
||||
|
||||
RestoreWindow();
|
||||
}
|
||||
|
||||
private bool TryBringToFront<T>() where T : Form{
|
||||
T form = Application.OpenForms.OfType<T>().FirstOrDefault();
|
||||
|
||||
if (form != null){
|
||||
form.BringToFront();
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
|
||||
private void ShowChildForm(Form form){
|
||||
form.VisibleChanged += (sender, args) => form.MoveToCenter(this);
|
||||
form.Show(this);
|
||||
}
|
||||
|
||||
public void ForceClose(){
|
||||
trayIcon.Visible = false; // checked in FormClosing event
|
||||
Close();
|
||||
}
|
||||
|
||||
// window setup
|
||||
|
||||
private void RestoreWindow(){
|
||||
Config.BrowserWindow.Restore(this, true);
|
||||
prevState = WindowState;
|
||||
isLoaded = true;
|
||||
}
|
||||
|
||||
private void OnBrowserReady(){
|
||||
if (!isBrowserReady){
|
||||
browser.Location = Point.Empty;
|
||||
browser.Dock = DockStyle.Fill;
|
||||
isBrowserReady = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTrayIcon(){
|
||||
trayIcon.Visible = Config.TrayBehavior.ShouldDisplayIcon();
|
||||
}
|
||||
|
||||
// active event handlers
|
||||
|
||||
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
|
||||
if (!e.IsLoading){
|
||||
foreach(string word in BrowserUtils.DictionaryWords){
|
||||
browser.AddWordToDictionary(word);
|
||||
}
|
||||
|
||||
BeginInvoke(new Action(OnBrowserReady));
|
||||
browser.LoadingStateChanged -= browser_LoadingStateChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){
|
||||
if (e.Frame.IsMain){
|
||||
if (Config.ZoomLevel != 100){
|
||||
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Config.ZoomLevel);
|
||||
}
|
||||
|
||||
if (BrowserUtils.IsTwitterWebsite(e.Frame)){
|
||||
ScriptLoader.ExecuteFile(e.Frame, "twitter.js");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||
if (e.Frame.IsMain && BrowserUtils.IsTweetDeckWebsite(e.Frame)){
|
||||
e.Frame.ExecuteJavaScriptAsync(BrowserUtils.BackgroundColorFix);
|
||||
|
||||
UpdateProperties(PropertyBridge.Properties.AllBrowser);
|
||||
ScriptLoader.ExecuteFile(e.Frame, "code.js");
|
||||
ReinjectCustomCSS(Config.CustomBrowserCSS);
|
||||
|
||||
if (plugins.HasAnyPlugin(PluginEnvironment.Browser)){
|
||||
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginBrowserScriptFile);
|
||||
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginGlobalScriptFile);
|
||||
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Browser, true);
|
||||
}
|
||||
|
||||
TweetDeckBridge.ResetStaticProperties();
|
||||
}
|
||||
}
|
||||
|
||||
private void browser_LoadError(object sender, LoadErrorEventArgs e){
|
||||
if (e.ErrorCode == CefErrorCode.Aborted){
|
||||
return;
|
||||
}
|
||||
|
||||
if (!e.FailedUrl.StartsWith("http://td/")){
|
||||
string errorPage = ScriptLoader.LoadResource("pages/error.html", true);
|
||||
|
||||
if (errorPage != null){
|
||||
browser.LoadHtml(errorPage.Replace("{err}", BrowserUtils.GetErrorName(e.ErrorCode)), "http://td/error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FormBrowser_Activated(object sender, EventArgs e){
|
||||
if (!isLoaded)return;
|
||||
|
||||
trayIcon.HasNotifications = false;
|
||||
}
|
||||
|
||||
private void FormBrowser_Resize(object sender, EventArgs e){
|
||||
if (!isLoaded)return;
|
||||
|
||||
if (WindowState != prevState){
|
||||
prevState = WindowState;
|
||||
|
||||
if (WindowState == FormWindowState.Minimized){
|
||||
if (Config.TrayBehavior.ShouldHideOnMinimize()){
|
||||
Hide(); // hides taskbar too?! welp that works I guess
|
||||
}
|
||||
}
|
||||
else{
|
||||
FormBrowser_ResizeEnd(sender, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FormBrowser_ResizeEnd(object sender, EventArgs e){ // also triggers when the window moves
|
||||
if (!isLoaded)return;
|
||||
|
||||
if (Location != ControlExtensions.InvisibleLocation){
|
||||
Config.BrowserWindow.Save(this);
|
||||
Config.Save();
|
||||
}
|
||||
}
|
||||
|
||||
private void FormBrowser_FormClosing(object sender, FormClosingEventArgs e){
|
||||
if (!isLoaded)return;
|
||||
|
||||
if (Config.TrayBehavior.ShouldHideOnClose() && trayIcon.Visible && e.CloseReason == CloseReason.UserClosing){
|
||||
Hide(); // hides taskbar too?! welp that works I guess
|
||||
e.Cancel = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void FormBrowser_FormClosed(object sender, FormClosedEventArgs e){
|
||||
if (isLoaded && UpdateInstallerPath == null){
|
||||
updates.CleanupDownload();
|
||||
}
|
||||
}
|
||||
|
||||
private void Config_MuteToggled(object sender, EventArgs e){
|
||||
UpdateProperties(PropertyBridge.Properties.MuteNotifications);
|
||||
}
|
||||
|
||||
private void Config_ZoomLevelChanged(object sender, EventArgs e){
|
||||
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Config.ZoomLevel);
|
||||
}
|
||||
|
||||
private void Config_TrayBehaviorChanged(object sender, EventArgs e){
|
||||
UpdateTrayIcon();
|
||||
}
|
||||
|
||||
private void trayIcon_ClickRestore(object sender, EventArgs e){
|
||||
Show();
|
||||
RestoreWindow();
|
||||
Activate();
|
||||
UpdateTrayIcon();
|
||||
}
|
||||
|
||||
private void trayIcon_ClickClose(object sender, EventArgs e){
|
||||
ForceClose();
|
||||
}
|
||||
|
||||
private void plugins_Reloaded(object sender, PluginErrorEventArgs e){
|
||||
browser.GetBrowser().Reload();
|
||||
}
|
||||
|
||||
private void plugins_PluginChangedState(object sender, PluginChangedStateEventArgs e){
|
||||
browser.ExecuteScriptAsync("window.TDPF_setPluginState", e.Plugin, e.IsEnabled);
|
||||
}
|
||||
|
||||
private void updates_UpdateAccepted(object sender, UpdateAcceptedEventArgs e){
|
||||
foreach(Form form in Application.OpenForms.Cast<Form>().Reverse()){
|
||||
if (form is FormSettings || form is FormPlugins || form is FormAbout){
|
||||
form.Close();
|
||||
}
|
||||
}
|
||||
|
||||
updates.BeginUpdateDownload(this, e.UpdateInfo, update => {
|
||||
if (update.DownloadStatus == UpdateDownloadStatus.Done){
|
||||
UpdateInstallerPath = update.InstallerPath;
|
||||
}
|
||||
|
||||
ForceClose();
|
||||
});
|
||||
}
|
||||
|
||||
private void updates_UpdateDismissed(object sender, UpdateDismissedEventArgs e){
|
||||
Config.DismissedUpdate = e.VersionTag;
|
||||
Config.Save();
|
||||
}
|
||||
|
||||
private void soundNotification_PlaybackError(object sender, PlaybackErrorEventArgs e){
|
||||
e.Ignore = true;
|
||||
|
||||
using(FormMessage form = new FormMessage("Notification Sound Error", "Could not play custom notification sound."+Environment.NewLine+e.Message, MessageBoxIcon.Error)){
|
||||
form.CancelButton = form.AddButton("Ignore");
|
||||
|
||||
Button btnOpenSettings = form.AddButton("View Options");
|
||||
btnOpenSettings.Width += 16;
|
||||
btnOpenSettings.Location = new Point(btnOpenSettings.Location.X-16, btnOpenSettings.Location.Y);
|
||||
|
||||
if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnOpenSettings){
|
||||
OpenSettings(FormSettings.TabIndexSounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void WndProc(ref Message m){
|
||||
if (isLoaded && m.Msg == Program.WindowRestoreMessage){
|
||||
using(Process process = Process.GetCurrentProcess()){
|
||||
if (process.Id == m.WParam.ToInt32()){
|
||||
trayIcon_ClickRestore(trayIcon, new EventArgs());
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBrowserReady && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN){
|
||||
browser.ExecuteScriptAsync("TDGF_onMouseClickExtra", (m.WParam.ToInt32() >> 16) & 0xFFFF);
|
||||
return;
|
||||
}
|
||||
|
||||
base.WndProc(ref m);
|
||||
}
|
||||
|
||||
// notification helpers
|
||||
|
||||
public FormNotificationMain CreateNotificationForm(bool enableContextMenu){
|
||||
return new FormNotificationMain(this, plugins, enableContextMenu);
|
||||
}
|
||||
|
||||
public void PauseNotification(){
|
||||
notification.PauseNotification();
|
||||
}
|
||||
|
||||
public void ResumeNotification(){
|
||||
notification.ResumeNotification();
|
||||
}
|
||||
|
||||
// javascript calls
|
||||
|
||||
public void ReinjectCustomCSS(string css){
|
||||
browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty);
|
||||
}
|
||||
|
||||
public void UpdateProperties(PropertyBridge.Properties properties){
|
||||
browser.ExecuteScriptAsync(PropertyBridge.GenerateScript(properties));
|
||||
}
|
||||
|
||||
public void ReloadToTweetDeck(){
|
||||
browser.ExecuteScriptAsync("window.location.href = 'https://tweetdeck.twitter.com'");
|
||||
}
|
||||
|
||||
// callback handlers
|
||||
|
||||
public void OpenContextMenu(){
|
||||
contextMenu.Show(this, PointToClient(Cursor.Position));
|
||||
}
|
||||
|
||||
public void OpenSettings(){
|
||||
OpenSettings(0);
|
||||
}
|
||||
|
||||
public void OpenSettings(int tabIndex){
|
||||
if (!TryBringToFront<FormSettings>()){
|
||||
bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
|
||||
|
||||
FormSettings form = new FormSettings(this, plugins, updates, tabIndex);
|
||||
|
||||
form.FormClosed += (sender, args) => {
|
||||
if (!prevEnableUpdateCheck && Config.EnableUpdateCheck){
|
||||
updates.DismissUpdate(string.Empty);
|
||||
updates.Check(false);
|
||||
}
|
||||
|
||||
if (!Config.EnableTrayHighlight){
|
||||
trayIcon.HasNotifications = false;
|
||||
}
|
||||
|
||||
UpdateProperties(PropertyBridge.Properties.ExpandLinksOnHover | PropertyBridge.Properties.SwitchAccountSelectors | PropertyBridge.Properties.HasCustomNotificationSound);
|
||||
form.Dispose();
|
||||
};
|
||||
|
||||
ShowChildForm(form);
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenAbout(){
|
||||
if (!TryBringToFront<FormAbout>()){
|
||||
ShowChildForm(new FormAbout());
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenPlugins(){
|
||||
if (!TryBringToFront<FormPlugins>()){
|
||||
ShowChildForm(new FormPlugins(plugins));
|
||||
}
|
||||
}
|
||||
|
||||
public void OnTweetNotification(){ // may be called multiple times, once for each type of notification
|
||||
if (Config.EnableTrayHighlight && !ContainsFocus){
|
||||
trayIcon.HasNotifications = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void PlayNotificationSound(){
|
||||
if (Config.NotificationSoundPath.Length == 0){
|
||||
return;
|
||||
}
|
||||
|
||||
if (soundNotification == null){
|
||||
soundNotification = new SoundNotification();
|
||||
soundNotification.PlaybackError += soundNotification_PlaybackError;
|
||||
}
|
||||
|
||||
soundNotification.Play(Config.NotificationSoundPath);
|
||||
}
|
||||
|
||||
public void OnTweetScreenshotReady(string html, int width, int height){
|
||||
if (notificationScreenshotManager == null){
|
||||
notificationScreenshotManager = new TweetScreenshotManager(this);
|
||||
}
|
||||
|
||||
notificationScreenshotManager.Trigger(html, width, height);
|
||||
}
|
||||
|
||||
public void DisplayTooltip(string text){
|
||||
if (string.IsNullOrEmpty(text)){
|
||||
toolTip.Hide(this);
|
||||
}
|
||||
else{
|
||||
Point position = PointToClient(Cursor.Position);
|
||||
position.Offset(20, 10);
|
||||
toolTip.Show(text, this, position);
|
||||
}
|
||||
}
|
||||
|
||||
public void TriggerTweetScreenshot(){
|
||||
browser.ExecuteScriptAsync("TDGF_triggerScreenshot()");
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,15 +0,0 @@
|
||||
using CefSharp;
|
||||
using System;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
class BrowserProcessHandler : IBrowserProcessHandler{
|
||||
void IBrowserProcessHandler.OnContextInitialized(){
|
||||
using(IRequestContext ctx = Cef.GetGlobalRequestContext()){
|
||||
ctx.SetPreference("browser.enable_spellchecking", Program.UserConfig.EnableSpellCheck, out string _);
|
||||
}
|
||||
}
|
||||
|
||||
void IBrowserProcessHandler.OnScheduleMessagePumpWork(long delay){}
|
||||
void IDisposable.Dispose(){}
|
||||
}
|
||||
}
|
@@ -1,148 +0,0 @@
|
||||
using CefSharp;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
abstract class ContextMenuBase : IContextMenuHandler{
|
||||
private static readonly Lazy<Regex> RegexTwitterAccount = new Lazy<Regex>(() => new Regex(@"^https?://twitter\.com/([^/]+)/?$", RegexOptions.Compiled), false);
|
||||
protected static readonly bool HasDevTools = File.Exists(Path.Combine(Program.ProgramPath, "devtools_resources.pak"));
|
||||
|
||||
private const int MenuOpenLinkUrl = 26500;
|
||||
private const int MenuCopyLinkUrl = 26501;
|
||||
private const int MenuCopyUsername = 26502;
|
||||
private const int MenuOpenImage = 26503;
|
||||
private const int MenuSaveImage = 26504;
|
||||
private const int MenuCopyImageUrl = 26505;
|
||||
private const int MenuOpenDevTools = 26599;
|
||||
|
||||
private readonly Form form;
|
||||
|
||||
protected ContextMenuBase(Form form){
|
||||
this.form = form;
|
||||
}
|
||||
|
||||
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && !parameters.UnfilteredLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal)){
|
||||
if (RegexTwitterAccount.Value.IsMatch(parameters.UnfilteredLinkUrl)){
|
||||
model.AddItem((CefMenuCommand)MenuOpenLinkUrl, "Open account in browser");
|
||||
model.AddItem((CefMenuCommand)MenuCopyLinkUrl, "Copy account address");
|
||||
model.AddItem((CefMenuCommand)MenuCopyUsername, "Copy account username");
|
||||
}
|
||||
else{
|
||||
model.AddItem((CefMenuCommand)MenuOpenLinkUrl, "Open link in browser");
|
||||
model.AddItem((CefMenuCommand)MenuCopyLinkUrl, "Copy link address");
|
||||
}
|
||||
|
||||
model.AddSeparator();
|
||||
}
|
||||
|
||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents){
|
||||
model.AddItem((CefMenuCommand)MenuOpenImage, "Open image in browser");
|
||||
model.AddItem((CefMenuCommand)MenuSaveImage, "Save image as...");
|
||||
model.AddItem((CefMenuCommand)MenuCopyImageUrl, "Copy image address");
|
||||
model.AddSeparator();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
|
||||
switch((int)commandId){
|
||||
case MenuOpenLinkUrl:
|
||||
BrowserUtils.OpenExternalBrowser(parameters.LinkUrl);
|
||||
break;
|
||||
|
||||
case MenuCopyLinkUrl:
|
||||
SetClipboardText(string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedLink) ? parameters.UnfilteredLinkUrl : TweetDeckBridge.LastRightClickedLink);
|
||||
break;
|
||||
|
||||
case MenuOpenImage:
|
||||
BrowserUtils.OpenExternalBrowser(parameters.SourceUrl);
|
||||
break;
|
||||
|
||||
case MenuSaveImage:
|
||||
string fileName = GetImageFileName(parameters.SourceUrl);
|
||||
string extension = Path.GetExtension(fileName);
|
||||
string saveTarget;
|
||||
|
||||
using(SaveFileDialog dialog = new SaveFileDialog{
|
||||
AutoUpgradeEnabled = true,
|
||||
OverwritePrompt = true,
|
||||
Title = "Save image",
|
||||
FileName = fileName,
|
||||
Filter = "Image ("+(string.IsNullOrEmpty(extension) ? "unknown" : extension)+")|*.*"
|
||||
}){
|
||||
saveTarget = dialog.ShowDialog() == DialogResult.OK ? dialog.FileName : null;
|
||||
}
|
||||
|
||||
if (saveTarget != null){
|
||||
BrowserUtils.DownloadFileAsync(parameters.SourceUrl, saveTarget, null, ex => {
|
||||
MessageBox.Show("An error occurred while downloading the image: "+ex.Message, Program.BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case MenuCopyImageUrl:
|
||||
SetClipboardText(parameters.SourceUrl);
|
||||
break;
|
||||
|
||||
case MenuCopyUsername:
|
||||
Match match = RegexTwitterAccount.Value.Match(parameters.UnfilteredLinkUrl);
|
||||
SetClipboardText(match.Success ? match.Groups[1].Value : parameters.UnfilteredLinkUrl);
|
||||
break;
|
||||
|
||||
case MenuOpenDevTools:
|
||||
browserControl.ShowDevTools();
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){}
|
||||
|
||||
public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void SetClipboardText(string text){
|
||||
form.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText));
|
||||
}
|
||||
|
||||
protected void AddDebugMenuItems(IMenuModel model){
|
||||
model.AddItem((CefMenuCommand)MenuOpenDevTools, "Open dev tools");
|
||||
}
|
||||
|
||||
protected static void RemoveSeparatorIfLast(IMenuModel model){
|
||||
if (model.Count > 0 && model.GetTypeAt(model.Count-1) == MenuItemType.Separator){
|
||||
model.RemoveAt(model.Count-1);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void AddSeparator(IMenuModel model){
|
||||
if (model.Count > 0 && model.GetTypeAt(model.Count-1) != MenuItemType.Separator){ // do not add separators if there is nothing to separate
|
||||
model.AddSeparator();
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetImageFileName(string url){
|
||||
// twimg adds a colon after file extension
|
||||
int dot = url.LastIndexOf('.');
|
||||
|
||||
if (dot != -1){
|
||||
int colon = url.IndexOf(':', dot);
|
||||
|
||||
if (colon != -1){
|
||||
url = url.Substring(0, colon);
|
||||
}
|
||||
}
|
||||
|
||||
// return file name
|
||||
return BrowserUtils.GetFileNameFromUrl(url) ?? "unknown";
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,166 +0,0 @@
|
||||
using CefSharp;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
class ContextMenuBrowser : ContextMenuBase{
|
||||
private const int MenuGlobal = 26600;
|
||||
private const int MenuMute = 26601;
|
||||
private const int MenuSettings = 26602;
|
||||
private const int MenuPlugins = 26003;
|
||||
private const int MenuAbout = 26604;
|
||||
|
||||
private const int MenuOpenTweetUrl = 26610;
|
||||
private const int MenuCopyTweetUrl = 26611;
|
||||
private const int MenuOpenQuotedTweetUrl = 26612;
|
||||
private const int MenuCopyQuotedTweetUrl = 26613;
|
||||
private const int MenuScreenshotTweet = 26614;
|
||||
|
||||
private const string TitleReloadBrowser = "Reload browser";
|
||||
private const string TitleMuteNotifications = "Mute notifications";
|
||||
private const string TitleSettings = "Options";
|
||||
private const string TitlePlugins = "Plugins";
|
||||
private const string TitleAboutProgram = "About "+Program.BrandName;
|
||||
|
||||
private readonly FormBrowser form;
|
||||
|
||||
private string lastHighlightedTweet;
|
||||
private string lastHighlightedQuotedTweet;
|
||||
|
||||
public ContextMenuBrowser(FormBrowser form) : base(form){
|
||||
this.form = form;
|
||||
}
|
||||
|
||||
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
||||
model.Remove(CefMenuCommand.Back);
|
||||
model.Remove(CefMenuCommand.Forward);
|
||||
model.Remove(CefMenuCommand.Print);
|
||||
model.Remove(CefMenuCommand.ViewSource);
|
||||
RemoveSeparatorIfLast(model);
|
||||
|
||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){
|
||||
model.AddSeparator();
|
||||
}
|
||||
|
||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||
|
||||
lastHighlightedTweet = TweetDeckBridge.LastHighlightedTweet;
|
||||
lastHighlightedQuotedTweet = TweetDeckBridge.LastHighlightedQuotedTweet;
|
||||
|
||||
if (!BrowserUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
|
||||
lastHighlightedTweet = string.Empty;
|
||||
lastHighlightedQuotedTweet = string.Empty;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(lastHighlightedTweet) && (parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
|
||||
model.AddItem((CefMenuCommand)MenuOpenTweetUrl, "Open tweet in browser");
|
||||
model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address");
|
||||
model.AddItem((CefMenuCommand)MenuScreenshotTweet, "Screenshot tweet to clipboard");
|
||||
|
||||
if (!string.IsNullOrEmpty(lastHighlightedQuotedTweet)){
|
||||
model.AddSeparator();
|
||||
model.AddItem((CefMenuCommand)MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
|
||||
model.AddItem((CefMenuCommand)MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
|
||||
}
|
||||
|
||||
model.AddSeparator();
|
||||
}
|
||||
|
||||
if ((parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
|
||||
AddSeparator(model);
|
||||
|
||||
IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu((CefMenuCommand)MenuGlobal, Program.BrandName);
|
||||
|
||||
globalMenu.AddItem(CefMenuCommand.Reload, TitleReloadBrowser);
|
||||
globalMenu.AddCheckItem((CefMenuCommand)MenuMute, TitleMuteNotifications);
|
||||
globalMenu.SetChecked((CefMenuCommand)MenuMute, Program.UserConfig.MuteNotifications);
|
||||
globalMenu.AddSeparator();
|
||||
|
||||
globalMenu.AddItem((CefMenuCommand)MenuSettings, TitleSettings);
|
||||
globalMenu.AddItem((CefMenuCommand)MenuPlugins, TitlePlugins);
|
||||
globalMenu.AddItem((CefMenuCommand)MenuAbout, TitleAboutProgram);
|
||||
|
||||
if (HasDevTools){
|
||||
globalMenu.AddSeparator();
|
||||
AddDebugMenuItems(globalMenu);
|
||||
}
|
||||
}
|
||||
|
||||
RemoveSeparatorIfLast(model);
|
||||
}
|
||||
|
||||
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
|
||||
if (base.OnContextMenuCommand(browserControl, browser, frame, parameters, commandId, eventFlags)){
|
||||
return true;
|
||||
}
|
||||
|
||||
switch((int)commandId){
|
||||
case (int)CefMenuCommand.Reload:
|
||||
form.InvokeAsyncSafe(form.ReloadToTweetDeck);
|
||||
return true;
|
||||
|
||||
case MenuSettings:
|
||||
form.InvokeAsyncSafe(form.OpenSettings);
|
||||
return true;
|
||||
|
||||
case MenuAbout:
|
||||
form.InvokeAsyncSafe(form.OpenAbout);
|
||||
return true;
|
||||
|
||||
case MenuPlugins:
|
||||
form.InvokeAsyncSafe(form.OpenPlugins);
|
||||
return true;
|
||||
|
||||
case MenuMute:
|
||||
form.InvokeAsyncSafe(ToggleMuteNotifications);
|
||||
return true;
|
||||
|
||||
case MenuOpenTweetUrl:
|
||||
BrowserUtils.OpenExternalBrowser(lastHighlightedTweet);
|
||||
return true;
|
||||
|
||||
case MenuCopyTweetUrl:
|
||||
SetClipboardText(lastHighlightedTweet);
|
||||
return true;
|
||||
|
||||
case MenuScreenshotTweet:
|
||||
form.InvokeAsyncSafe(form.TriggerTweetScreenshot);
|
||||
return true;
|
||||
|
||||
case MenuOpenQuotedTweetUrl:
|
||||
BrowserUtils.OpenExternalBrowser(lastHighlightedQuotedTweet);
|
||||
return true;
|
||||
|
||||
case MenuCopyQuotedTweetUrl:
|
||||
SetClipboardText(lastHighlightedQuotedTweet);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static ContextMenu CreateMenu(FormBrowser form){
|
||||
ContextMenu menu = new ContextMenu();
|
||||
|
||||
menu.MenuItems.Add(TitleReloadBrowser, (sender, args) => form.ReloadToTweetDeck());
|
||||
menu.MenuItems.Add(TitleMuteNotifications, (sender, args) => ToggleMuteNotifications());
|
||||
menu.MenuItems.Add("-");
|
||||
menu.MenuItems.Add(TitleSettings, (sender, args) => form.OpenSettings());
|
||||
menu.MenuItems.Add(TitlePlugins, (sender, args) => form.OpenPlugins());
|
||||
menu.MenuItems.Add(TitleAboutProgram, (sender, args) => form.OpenAbout());
|
||||
|
||||
menu.Popup += (sender, args) => {
|
||||
menu.MenuItems[1].Checked = Program.UserConfig.MuteNotifications;
|
||||
};
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
private static void ToggleMuteNotifications(){
|
||||
Program.UserConfig.MuteNotifications = !Program.UserConfig.MuteNotifications;
|
||||
Program.UserConfig.Save();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,87 +0,0 @@
|
||||
using CefSharp;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Notification;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
class ContextMenuNotification : ContextMenuBase{
|
||||
private const int MenuSkipTweet = 26600;
|
||||
private const int MenuFreeze = 26601;
|
||||
private const int MenuCopyTweetUrl = 26602;
|
||||
private const int MenuCopyQuotedTweetUrl = 26603;
|
||||
|
||||
private readonly FormNotificationBase form;
|
||||
private readonly bool enableCustomMenu;
|
||||
|
||||
public ContextMenuNotification(FormNotificationBase form, bool enableCustomMenu) : base(form){
|
||||
this.form = form;
|
||||
this.enableCustomMenu = enableCustomMenu;
|
||||
}
|
||||
|
||||
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
||||
model.Clear();
|
||||
|
||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){
|
||||
model.AddItem(CefMenuCommand.Copy, "Copy");
|
||||
model.AddSeparator();
|
||||
}
|
||||
|
||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||
|
||||
if (enableCustomMenu){
|
||||
model.AddItem((CefMenuCommand)MenuSkipTweet, "Skip tweet");
|
||||
model.AddCheckItem((CefMenuCommand)MenuFreeze, "Freeze");
|
||||
model.SetChecked((CefMenuCommand)MenuFreeze, form.FreezeTimer);
|
||||
model.AddSeparator();
|
||||
|
||||
if (!string.IsNullOrEmpty(form.CurrentTweetUrl)){
|
||||
model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address");
|
||||
|
||||
if (!string.IsNullOrEmpty(form.CurrentQuoteUrl)){
|
||||
model.AddItem((CefMenuCommand)MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
|
||||
}
|
||||
|
||||
model.AddSeparator();
|
||||
}
|
||||
}
|
||||
|
||||
if (HasDevTools){
|
||||
AddDebugMenuItems(model);
|
||||
}
|
||||
|
||||
RemoveSeparatorIfLast(model);
|
||||
|
||||
form.InvokeAsyncSafe(() => form.ContextMenuOpen = true);
|
||||
}
|
||||
|
||||
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
|
||||
if (base.OnContextMenuCommand(browserControl, browser, frame, parameters, commandId, eventFlags)){
|
||||
return true;
|
||||
}
|
||||
|
||||
switch((int)commandId){
|
||||
case MenuSkipTweet:
|
||||
form.InvokeAsyncSafe(form.FinishCurrentNotification);
|
||||
return true;
|
||||
|
||||
case MenuFreeze:
|
||||
form.InvokeAsyncSafe(() => form.FreezeTimer = !form.FreezeTimer);
|
||||
return true;
|
||||
|
||||
case MenuCopyTweetUrl:
|
||||
SetClipboardText(form.CurrentTweetUrl);
|
||||
return true;
|
||||
|
||||
case MenuCopyQuotedTweetUrl:
|
||||
SetClipboardText(form.CurrentQuoteUrl);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
|
||||
base.OnContextMenuDismissed(browserControl, browser, frame);
|
||||
form.InvokeAsyncSafe(() => form.ContextMenuOpen = false);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,60 +0,0 @@
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Other;
|
||||
|
||||
namespace TweetDuck.Core.Handling {
|
||||
class JavaScriptDialogHandler : IJsDialogHandler{
|
||||
bool IJsDialogHandler.OnJSDialog(IWebBrowser browserControl, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage){
|
||||
((ChromiumWebBrowser)browserControl).InvokeSafe(() => {
|
||||
FormMessage form = new FormMessage(Program.BrandName, messageText, MessageBoxIcon.None);
|
||||
TextBox input = null;
|
||||
|
||||
if (dialogType == CefJsDialogType.Alert){
|
||||
form.AcceptButton = form.AddButton("OK");
|
||||
}
|
||||
else if (dialogType == CefJsDialogType.Confirm){
|
||||
form.CancelButton = form.AddButton("No", DialogResult.No);
|
||||
form.AcceptButton = form.AddButton("Yes");
|
||||
}
|
||||
else if (dialogType == CefJsDialogType.Prompt){
|
||||
form.CancelButton = form.AddButton("Cancel", DialogResult.Cancel);
|
||||
form.AcceptButton = form.AddButton("OK");
|
||||
|
||||
input = new TextBox{
|
||||
Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
|
||||
Location = new Point(27, form.ActionPanelY-46),
|
||||
Size = new Size(form.ClientSize.Width-54, 20)
|
||||
};
|
||||
|
||||
form.Controls.Add(input);
|
||||
form.ActiveControl = input;
|
||||
form.Height += input.Size.Height+input.Margin.Vertical;
|
||||
}
|
||||
|
||||
bool success = form.ShowDialog() == DialogResult.OK;
|
||||
|
||||
if (input == null){
|
||||
callback.Continue(success);
|
||||
}
|
||||
else{
|
||||
callback.Continue(success, input.Text);
|
||||
input.Dispose();
|
||||
}
|
||||
|
||||
form.Dispose();
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IJsDialogHandler.OnJSBeforeUnload(IWebBrowser browserControl, IBrowser browser, string message, bool isReload, IJsDialogCallback callback){
|
||||
return false;
|
||||
}
|
||||
|
||||
void IJsDialogHandler.OnResetDialogState(IWebBrowser browserControl, IBrowser browser){}
|
||||
void IJsDialogHandler.OnDialogClosed(IWebBrowser browserControl, IBrowser browser){}
|
||||
}
|
||||
}
|
@@ -1,30 +0,0 @@
|
||||
using CefSharp;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
class LifeSpanHandler : ILifeSpanHandler{
|
||||
public bool OnBeforePopup(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser){
|
||||
newBrowser = null;
|
||||
|
||||
switch(targetDisposition){
|
||||
case WindowOpenDisposition.NewBackgroundTab:
|
||||
case WindowOpenDisposition.NewForegroundTab:
|
||||
case WindowOpenDisposition.NewPopup:
|
||||
case WindowOpenDisposition.NewWindow:
|
||||
BrowserUtils.OpenExternalBrowser(targetUrl);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnAfterCreated(IWebBrowser browserControl, IBrowser browser){}
|
||||
|
||||
public bool DoClose(IWebBrowser browserControl, IBrowser browser){
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnBeforeClose(IWebBrowser browserControl, IBrowser browser){}
|
||||
}
|
||||
}
|
@@ -1,68 +0,0 @@
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using CefSharp;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
abstract class RequestHandler : IRequestHandler{
|
||||
// Browser
|
||||
|
||||
public virtual void OnRenderViewReady(IWebBrowser browserControl, IBrowser browser){}
|
||||
|
||||
public virtual void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){}
|
||||
|
||||
public virtual bool OnBeforeBrowse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, bool isRedirect){
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture){
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual bool OnProtocolExecution(IWebBrowser browserControl, IBrowser browser, string url){
|
||||
return false;
|
||||
}
|
||||
|
||||
// Resources
|
||||
|
||||
public virtual CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){
|
||||
return CefReturnValue.Continue;
|
||||
}
|
||||
|
||||
public virtual void OnResourceRedirect(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response, ref string newUrl){}
|
||||
|
||||
public virtual bool OnResourceResponse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual IResponseFilter GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual void OnResourceLoadComplete(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response, UrlRequestStatus status, long receivedContentLength){}
|
||||
|
||||
// JavaScript & Plugins
|
||||
|
||||
public virtual bool OnQuotaRequest(IWebBrowser browserControl, IBrowser browser, string originUrl, long newSize, IRequestCallback callback){
|
||||
callback.Dispose();
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual void OnPluginCrashed(IWebBrowser browserControl, IBrowser browser, string pluginPath){}
|
||||
|
||||
// Auth
|
||||
|
||||
public virtual bool GetAuthCredentials(IWebBrowser browserControl, IBrowser browser, IFrame frame, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback){
|
||||
callback.Dispose();
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual bool OnSelectClientCertificate(IWebBrowser browserControl, IBrowser browser, bool isProxy, string host, int port, X509Certificate2Collection certificates, ISelectClientCertificateCallback callback){
|
||||
callback.Dispose();
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual bool OnCertificateError(IWebBrowser browserControl, IBrowser browser, CefErrorCode errorCode, string requestUrl, ISslInfo sslInfo, IRequestCallback callback){
|
||||
callback.Dispose();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
using CefSharp;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
class RequestHandlerBrowser : RequestHandler{
|
||||
public override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){
|
||||
browser.Reload();
|
||||
}
|
||||
|
||||
public override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){
|
||||
if (request.ResourceType == ResourceType.Script && request.Url.Contains("analytics.")){
|
||||
return CefReturnValue.Cancel;
|
||||
}
|
||||
|
||||
return CefReturnValue.Continue;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,63 +0,0 @@
|
||||
using CefSharp;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
class ResourceHandlerNotification : IResourceHandler{
|
||||
private readonly NameValueCollection headers = new NameValueCollection(0);
|
||||
private MemoryStream dataIn;
|
||||
|
||||
public void SetHTML(string html){
|
||||
dataIn?.Dispose();
|
||||
dataIn = ResourceHandler.GetMemoryStream(html, Encoding.UTF8);
|
||||
}
|
||||
|
||||
public void Dispose(){
|
||||
if (dataIn != null){
|
||||
dataIn.Dispose();
|
||||
dataIn = null;
|
||||
}
|
||||
}
|
||||
|
||||
bool IResourceHandler.ProcessRequest(IRequest request, ICallback callback){
|
||||
callback.Continue();
|
||||
return true;
|
||||
}
|
||||
|
||||
void IResourceHandler.GetResponseHeaders(IResponse response, out long responseLength, out string redirectUrl){
|
||||
redirectUrl = null;
|
||||
|
||||
response.MimeType = "text/html";
|
||||
response.StatusCode = 200;
|
||||
response.StatusText = "OK";
|
||||
response.ResponseHeaders = headers;
|
||||
responseLength = dataIn?.Length ?? -1;
|
||||
}
|
||||
|
||||
bool IResourceHandler.ReadResponse(Stream dataOut, out int bytesRead, ICallback callback){
|
||||
callback.Dispose();
|
||||
|
||||
try{
|
||||
int length = (int)dataIn.Length;
|
||||
|
||||
dataIn.CopyTo(dataOut, length);
|
||||
bytesRead = length;
|
||||
return true;
|
||||
}catch{ // catch IOException, possibly NullReferenceException if dataIn is null
|
||||
bytesRead = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IResourceHandler.CanGetCookie(Cookie cookie){
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IResourceHandler.CanSetCookie(Cookie cookie){
|
||||
return true;
|
||||
}
|
||||
|
||||
void IResourceHandler.Cancel(){}
|
||||
}
|
||||
}
|
@@ -1,211 +0,0 @@
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Handling;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Notification{
|
||||
partial class FormNotificationBase : Form{
|
||||
protected Point PrimaryLocation{
|
||||
get{
|
||||
UserConfig config = Program.UserConfig;
|
||||
Screen screen;
|
||||
|
||||
if (config.NotificationDisplay > 0 && config.NotificationDisplay <= Screen.AllScreens.Length){
|
||||
screen = Screen.AllScreens[config.NotificationDisplay-1];
|
||||
}
|
||||
else{
|
||||
screen = Screen.FromControl(owner);
|
||||
}
|
||||
|
||||
int edgeDist = config.NotificationEdgeDistance;
|
||||
|
||||
switch(config.NotificationPosition){
|
||||
case TweetNotification.Position.TopLeft:
|
||||
return new Point(screen.WorkingArea.X+edgeDist, screen.WorkingArea.Y+edgeDist);
|
||||
|
||||
case TweetNotification.Position.TopRight:
|
||||
return new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+edgeDist);
|
||||
|
||||
case TweetNotification.Position.BottomLeft:
|
||||
return new Point(screen.WorkingArea.X+edgeDist, screen.WorkingArea.Y+screen.WorkingArea.Height-edgeDist-Height);
|
||||
|
||||
case TweetNotification.Position.BottomRight:
|
||||
return new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+screen.WorkingArea.Height-edgeDist-Height);
|
||||
|
||||
case TweetNotification.Position.Custom:
|
||||
if (!config.IsCustomNotificationPositionSet){
|
||||
config.CustomNotificationPosition = new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+edgeDist);
|
||||
config.Save();
|
||||
}
|
||||
|
||||
return config.CustomNotificationPosition;
|
||||
}
|
||||
|
||||
return Location;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsNotificationVisible => Location != ControlExtensions.InvisibleLocation;
|
||||
|
||||
public new Point Location{
|
||||
get => base.Location;
|
||||
|
||||
set{
|
||||
Visible = (base.Location = value) != ControlExtensions.InvisibleLocation;
|
||||
|
||||
if (WindowsUtils.ShouldAvoidToolWindow){
|
||||
FormBorderStyle = Visible ? FormBorderStyle.FixedSingle : FormBorderStyle.FixedToolWindow; // workaround for alt+tab
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Func<bool> CanMoveWindow = () => true;
|
||||
protected override bool ShowWithoutActivation => true;
|
||||
|
||||
protected readonly Form owner;
|
||||
protected readonly ChromiumWebBrowser browser;
|
||||
|
||||
private readonly ResourceHandlerNotification resourceHandler = new ResourceHandlerNotification();
|
||||
|
||||
private string currentColumn;
|
||||
private int pauseCounter;
|
||||
|
||||
public bool IsPaused => pauseCounter > 0;
|
||||
|
||||
public bool FreezeTimer { get; set; }
|
||||
public bool ContextMenuOpen { get; set; }
|
||||
public string CurrentTweetUrl { get; private set; }
|
||||
public string CurrentQuoteUrl { get; private set; }
|
||||
|
||||
public event EventHandler Initialized;
|
||||
|
||||
public FormNotificationBase(Form owner, bool enableContextMenu){
|
||||
InitializeComponent();
|
||||
|
||||
this.owner = owner;
|
||||
this.owner.FormClosed += owner_FormClosed;
|
||||
|
||||
this.browser = new ChromiumWebBrowser("about:blank"){
|
||||
MenuHandler = new ContextMenuNotification(this, enableContextMenu),
|
||||
LifeSpanHandler = new LifeSpanHandler()
|
||||
};
|
||||
|
||||
this.browser.Dock = DockStyle.None;
|
||||
this.browser.ClientSize = ClientSize;
|
||||
this.browser.IsBrowserInitializedChanged += Browser_IsBrowserInitializedChanged;
|
||||
|
||||
#if DEBUG
|
||||
this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
|
||||
#endif
|
||||
|
||||
DefaultResourceHandlerFactory handlerFactory = (DefaultResourceHandlerFactory)browser.ResourceHandlerFactory;
|
||||
handlerFactory.RegisterHandler("https://tweetdeck.twitter.com", this.resourceHandler);
|
||||
|
||||
Controls.Add(browser);
|
||||
|
||||
Disposed += (sender, args) => {
|
||||
this.browser.Dispose();
|
||||
this.owner.FormClosed -= owner_FormClosed;
|
||||
};
|
||||
|
||||
// ReSharper disable once VirtualMemberCallInContructor
|
||||
UpdateTitle();
|
||||
}
|
||||
|
||||
protected override void WndProc(ref Message m){
|
||||
if (m.Msg == 0x0112 && (m.WParam.ToInt32() & 0xFFF0) == 0xF010 && !CanMoveWindow()){ // WM_SYSCOMMAND, SC_MOVE
|
||||
return;
|
||||
}
|
||||
|
||||
base.WndProc(ref m);
|
||||
}
|
||||
|
||||
// event handlers
|
||||
|
||||
private void owner_FormClosed(object sender, FormClosedEventArgs e){
|
||||
Close();
|
||||
}
|
||||
|
||||
private void Browser_IsBrowserInitializedChanged(object sender, IsBrowserInitializedChangedEventArgs e){
|
||||
if (e.IsBrowserInitialized){
|
||||
Initialized?.Invoke(this, new EventArgs());
|
||||
}
|
||||
}
|
||||
|
||||
// notification methods
|
||||
|
||||
public virtual void HideNotification(bool loadBlank){
|
||||
if (loadBlank){
|
||||
browser.Load("about:blank");
|
||||
}
|
||||
|
||||
Location = ControlExtensions.InvisibleLocation;
|
||||
currentColumn = null;
|
||||
}
|
||||
|
||||
public virtual void FinishCurrentNotification(){}
|
||||
|
||||
public virtual void PauseNotification(){
|
||||
if (pauseCounter++ == 0 && IsNotificationVisible){
|
||||
Location = ControlExtensions.InvisibleLocation;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ResumeNotification(){
|
||||
if (pauseCounter > 0){
|
||||
--pauseCounter;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual string GetTweetHTML(TweetNotification tweet){
|
||||
string bodyClasses = browser.Bounds.Contains(PointToClient(Cursor.Position)) ? "td-hover" : string.Empty;
|
||||
return tweet.GenerateHtml(bodyClasses);
|
||||
}
|
||||
|
||||
protected virtual void LoadTweet(TweetNotification tweet){
|
||||
CurrentTweetUrl = tweet.TweetUrl;
|
||||
CurrentQuoteUrl = tweet.QuoteUrl;
|
||||
currentColumn = tweet.Column;
|
||||
|
||||
resourceHandler.SetHTML(GetTweetHTML(tweet));
|
||||
browser.Load("https://tweetdeck.twitter.com");
|
||||
}
|
||||
|
||||
protected virtual void SetNotificationSize(int width, int height){
|
||||
browser.ClientSize = ClientSize = new Size((int)Math.Round(width*Program.UserConfig.ZoomMultiplier), (int)Math.Round(height*Program.UserConfig.ZoomMultiplier));
|
||||
}
|
||||
|
||||
protected virtual void OnNotificationReady(){
|
||||
MoveToVisibleLocation();
|
||||
}
|
||||
|
||||
protected virtual void UpdateTitle(){
|
||||
Text = string.IsNullOrEmpty(currentColumn) || !Program.UserConfig.DisplayNotificationColumn ? Program.BrandName : Program.BrandName+" - "+currentColumn;
|
||||
}
|
||||
|
||||
public void MoveToVisibleLocation(){
|
||||
bool needsReactivating = Location == ControlExtensions.InvisibleLocation;
|
||||
Location = PrimaryLocation;
|
||||
|
||||
if (needsReactivating){
|
||||
NativeMethods.SetFormPos(this, NativeMethods.HWND_TOPMOST, NativeMethods.SWP_NOACTIVATE);
|
||||
}
|
||||
}
|
||||
|
||||
public void DisplayTooltip(string text){
|
||||
if (string.IsNullOrEmpty(text)){
|
||||
toolTip.Hide(this);
|
||||
}
|
||||
else{
|
||||
Point position = PointToClient(Cursor.Position);
|
||||
position.Offset(20, 5);
|
||||
toolTip.Show(text, this, position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,282 +0,0 @@
|
||||
using CefSharp;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
using TweetDuck.Resources;
|
||||
|
||||
namespace TweetDuck.Core.Notification{
|
||||
partial class FormNotificationMain : FormNotificationBase{
|
||||
private const string NotificationScriptFile = "notification.js";
|
||||
|
||||
private static readonly string NotificationScriptIdentifier = ScriptLoader.GetRootIdentifier(NotificationScriptFile);
|
||||
private static readonly string PluginScriptIdentifier = ScriptLoader.GetRootIdentifier(PluginManager.PluginNotificationScriptFile);
|
||||
|
||||
private static readonly string NotificationJS, PluginJS;
|
||||
|
||||
static FormNotificationMain(){
|
||||
NotificationJS = ScriptLoader.LoadResource(NotificationScriptFile);
|
||||
PluginJS = ScriptLoader.LoadResource(PluginManager.PluginNotificationScriptFile);
|
||||
}
|
||||
|
||||
private static int BaseClientWidth{
|
||||
get{
|
||||
int level = TweetNotification.FontSizeLevel;
|
||||
int width = level == 0 ? 284 : (int)Math.Round(284.0*(1.0+0.05*level));
|
||||
return (int)Math.Round(width*Program.UserConfig.ZoomMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
private static int BaseClientHeight{
|
||||
get{
|
||||
int level = TweetNotification.FontSizeLevel;
|
||||
int height = level == 0 ? 118 : (int)Math.Round(118.0*(1.0+0.075*level));
|
||||
return (int)Math.Round(height*Program.UserConfig.ZoomMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly PluginManager plugins;
|
||||
|
||||
protected int timeLeft, totalTime;
|
||||
protected bool pausedDuringNotification;
|
||||
|
||||
private readonly NativeMethods.HookProc mouseHookDelegate;
|
||||
private IntPtr mouseHook;
|
||||
private bool blockXButtonUp;
|
||||
|
||||
private bool? prevDisplayTimer;
|
||||
private int? prevFontSize;
|
||||
|
||||
private bool RequiresResize{
|
||||
get{
|
||||
return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Program.UserConfig.DisplayNotificationTimer || prevFontSize != TweetNotification.FontSizeLevel;
|
||||
}
|
||||
|
||||
set{
|
||||
if (value){
|
||||
prevDisplayTimer = null;
|
||||
prevFontSize = null;
|
||||
}
|
||||
else{
|
||||
prevDisplayTimer = Program.UserConfig.DisplayNotificationTimer;
|
||||
prevFontSize = TweetNotification.FontSizeLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FormNotificationMain(FormBrowser owner, PluginManager pluginManager, bool enableContextMenu) : base(owner, enableContextMenu){
|
||||
InitializeComponent();
|
||||
|
||||
this.plugins = pluginManager;
|
||||
|
||||
browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(owner, this));
|
||||
browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
|
||||
|
||||
browser.LoadingStateChanged += Browser_LoadingStateChanged;
|
||||
browser.FrameLoadEnd += Browser_FrameLoadEnd;
|
||||
|
||||
mouseHookDelegate = MouseHookProc;
|
||||
Disposed += (sender, args) => StopMouseHook(true);
|
||||
}
|
||||
|
||||
// mouse wheel hook
|
||||
|
||||
private void StartMouseHook(){
|
||||
if (mouseHook == IntPtr.Zero){
|
||||
mouseHook = NativeMethods.SetWindowsHookEx(NativeMethods.WM_MOUSE_LL, mouseHookDelegate, IntPtr.Zero, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void StopMouseHook(bool force){
|
||||
if (mouseHook != IntPtr.Zero && (force || !blockXButtonUp)){
|
||||
NativeMethods.UnhookWindowsHookEx(mouseHook);
|
||||
mouseHook = IntPtr.Zero;
|
||||
blockXButtonUp = false;
|
||||
}
|
||||
}
|
||||
|
||||
private IntPtr MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam){
|
||||
if (nCode == 0){
|
||||
int eventType = wParam.ToInt32();
|
||||
|
||||
if (eventType == NativeMethods.WM_MOUSEWHEEL && browser.Bounds.Contains(PointToClient(Cursor.Position)) && !ContainsFocus && !owner.ContainsFocus){
|
||||
browser.SendMouseWheelEvent(0, 0, 0, NativeMethods.GetMouseHookData(lParam), CefEventFlags.None);
|
||||
return NativeMethods.HOOK_HANDLED;
|
||||
}
|
||||
else if (eventType == NativeMethods.WM_XBUTTONDOWN && DesktopBounds.Contains(Cursor.Position)){
|
||||
int extraButton = NativeMethods.GetMouseHookData(lParam);
|
||||
|
||||
if (extraButton == 2){ // forward button
|
||||
this.InvokeAsyncSafe(FinishCurrentNotification);
|
||||
}
|
||||
else if (extraButton == 1){ // back button
|
||||
this.InvokeAsyncSafe(Close);
|
||||
}
|
||||
|
||||
blockXButtonUp = true;
|
||||
return NativeMethods.HOOK_HANDLED;
|
||||
}
|
||||
else if (eventType == NativeMethods.WM_XBUTTONUP && blockXButtonUp){
|
||||
blockXButtonUp = false;
|
||||
|
||||
if (!Visible){
|
||||
StopMouseHook(false);
|
||||
}
|
||||
|
||||
return NativeMethods.HOOK_HANDLED;
|
||||
}
|
||||
}
|
||||
|
||||
return NativeMethods.CallNextHookEx(mouseHook, nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
// event handlers
|
||||
|
||||
private void FormNotification_FormClosing(object sender, FormClosingEventArgs e){
|
||||
if (e.CloseReason == CloseReason.UserClosing){
|
||||
HideNotification(true);
|
||||
e.Cancel = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
|
||||
if (!e.IsLoading && browser.Address != "about:blank"){
|
||||
this.InvokeSafe(() => {
|
||||
Visible = true; // ensures repaint before moving the window to a visible location
|
||||
timerDisplayDelay.Start();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||
if (e.Frame.IsMain && NotificationJS != null && browser.Address != "about:blank"){
|
||||
e.Frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Properties.AllNotification));
|
||||
ScriptLoader.ExecuteScript(e.Frame, NotificationJS, NotificationScriptIdentifier);
|
||||
|
||||
if (plugins.HasAnyPlugin(PluginEnvironment.Notification)){
|
||||
ScriptLoader.ExecuteScript(e.Frame, PluginJS, PluginScriptIdentifier);
|
||||
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginGlobalScriptFile);
|
||||
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Notification, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void timerDisplayDelay_Tick(object sender, EventArgs e){
|
||||
OnNotificationReady();
|
||||
timerDisplayDelay.Stop();
|
||||
}
|
||||
|
||||
private void timerHideProgress_Tick(object sender, EventArgs e){
|
||||
if (Bounds.Contains(Cursor.Position) || FreezeTimer || ContextMenuOpen)return;
|
||||
|
||||
timeLeft -= timerProgress.Interval;
|
||||
|
||||
int value = (int)Math.Round(1025.0*(totalTime-timeLeft)/totalTime);
|
||||
progressBarTimer.SetValueInstant(Math.Min(1000, Math.Max(0, Program.UserConfig.NotificationTimerCountDown ? 1000-value : value)));
|
||||
|
||||
if (timeLeft <= 0){
|
||||
FinishCurrentNotification();
|
||||
}
|
||||
}
|
||||
|
||||
// notification methods
|
||||
|
||||
public virtual void ShowNotification(TweetNotification notification){
|
||||
LoadTweet(notification);
|
||||
}
|
||||
|
||||
public void ShowNotificationForSettings(bool reset){
|
||||
if (reset){
|
||||
LoadTweet(TweetNotification.ExampleTweet);
|
||||
}
|
||||
else{
|
||||
PrepareAndDisplayWindow();
|
||||
}
|
||||
|
||||
UpdateTitle();
|
||||
}
|
||||
|
||||
public override void HideNotification(bool loadBlank){
|
||||
base.HideNotification(loadBlank);
|
||||
|
||||
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0;
|
||||
timerProgress.Stop();
|
||||
totalTime = 0;
|
||||
|
||||
StopMouseHook(false);
|
||||
}
|
||||
|
||||
public override void FinishCurrentNotification(){
|
||||
timerProgress.Stop();
|
||||
}
|
||||
|
||||
public override void PauseNotification(){
|
||||
if (!IsPaused){
|
||||
pausedDuringNotification = IsNotificationVisible;
|
||||
timerProgress.Stop();
|
||||
StopMouseHook(true);
|
||||
}
|
||||
|
||||
base.PauseNotification();
|
||||
}
|
||||
|
||||
public override void ResumeNotification(){
|
||||
bool wasPaused = IsPaused;
|
||||
base.ResumeNotification();
|
||||
|
||||
if (wasPaused && !IsPaused && pausedDuringNotification){
|
||||
OnNotificationReady();
|
||||
}
|
||||
}
|
||||
|
||||
protected override string GetTweetHTML(TweetNotification tweet){
|
||||
string html = base.GetTweetHTML(tweet);
|
||||
|
||||
foreach(InjectedHTML injection in plugins.Bridge.NotificationInjections){
|
||||
html = injection.Inject(html);
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
protected override void LoadTweet(TweetNotification tweet){
|
||||
timerProgress.Stop();
|
||||
totalTime = timeLeft = tweet.GetDisplayDuration(Program.UserConfig.NotificationDurationValue);
|
||||
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0;
|
||||
|
||||
base.LoadTweet(tweet);
|
||||
}
|
||||
|
||||
protected override void SetNotificationSize(int width, int height){
|
||||
if (Program.UserConfig.DisplayNotificationTimer){
|
||||
ClientSize = new Size(width, height+4);
|
||||
progressBarTimer.Visible = true;
|
||||
}
|
||||
else{
|
||||
ClientSize = new Size(width, height);
|
||||
progressBarTimer.Visible = false;
|
||||
}
|
||||
|
||||
browser.ClientSize = new Size(width, height);
|
||||
}
|
||||
|
||||
private void PrepareAndDisplayWindow(){
|
||||
if (RequiresResize){
|
||||
RequiresResize = false;
|
||||
SetNotificationSize(BaseClientWidth, BaseClientHeight);
|
||||
}
|
||||
|
||||
MoveToVisibleLocation();
|
||||
StartMouseHook();
|
||||
}
|
||||
|
||||
protected override void OnNotificationReady(){
|
||||
PrepareAndDisplayWindow();
|
||||
timerProgress.Start();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,141 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using TweetDuck.Plugins;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Notification{
|
||||
sealed partial class FormNotificationTweet : FormNotificationMain{
|
||||
private const int NonIntrusiveIdleLimit = 30;
|
||||
private const int TrimMinimum = 32;
|
||||
|
||||
private bool IsCursorOverNotificationArea => new Rectangle(PrimaryLocation, Size).Contains(Cursor.Position);
|
||||
|
||||
private readonly Queue<TweetNotification> tweetQueue = new Queue<TweetNotification>(4);
|
||||
private bool needsTrim;
|
||||
|
||||
public FormNotificationTweet(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, true){
|
||||
InitializeComponent();
|
||||
|
||||
Program.UserConfig.MuteToggled += Config_MuteToggled;
|
||||
Disposed += (sender, args) => Program.UserConfig.MuteToggled -= Config_MuteToggled;
|
||||
|
||||
if (Program.UserConfig.MuteNotifications){
|
||||
PauseNotification();
|
||||
}
|
||||
}
|
||||
|
||||
private void FormNotificationTweet_FormClosing(object sender, FormClosingEventArgs e){
|
||||
if (e.CloseReason == CloseReason.UserClosing){
|
||||
tweetQueue.Clear(); // already canceled
|
||||
TrimQueue();
|
||||
}
|
||||
}
|
||||
|
||||
private void TrimQueue(){
|
||||
if (needsTrim){
|
||||
tweetQueue.TrimExcess();
|
||||
needsTrim = false;
|
||||
}
|
||||
}
|
||||
|
||||
// event handlers
|
||||
|
||||
private void Config_MuteToggled(object sender, EventArgs e){
|
||||
if (Program.UserConfig.MuteNotifications){
|
||||
PauseNotification();
|
||||
}
|
||||
else{
|
||||
ResumeNotification();
|
||||
}
|
||||
}
|
||||
|
||||
private void timerCursorCheck_Tick(object sender, EventArgs e){
|
||||
if (!IsCursorOverNotificationArea){
|
||||
ResumeNotification();
|
||||
timerCursorCheck.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
private void timerIdlePauseCheck_Tick(object sender, EventArgs e){
|
||||
if (NativeMethods.GetIdleSeconds() < Program.UserConfig.NotificationIdlePauseSeconds){
|
||||
ResumeNotification();
|
||||
timerIdlePauseCheck.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
// notification methods
|
||||
|
||||
public override void ShowNotification(TweetNotification notification){
|
||||
if (IsPaused){
|
||||
tweetQueue.Enqueue(notification);
|
||||
}
|
||||
else{
|
||||
tweetQueue.Enqueue(notification);
|
||||
UpdateTitle();
|
||||
|
||||
if (totalTime == 0){
|
||||
LoadNextNotification();
|
||||
}
|
||||
}
|
||||
|
||||
needsTrim |= tweetQueue.Count >= TrimMinimum;
|
||||
}
|
||||
|
||||
public override void FinishCurrentNotification(){
|
||||
if (tweetQueue.Count > 0){
|
||||
LoadNextNotification();
|
||||
}
|
||||
else{
|
||||
HideNotification(true);
|
||||
TrimQueue();
|
||||
}
|
||||
}
|
||||
|
||||
public override void ResumeNotification(){
|
||||
bool wasPaused = IsPaused;
|
||||
base.ResumeNotification();
|
||||
|
||||
if (wasPaused && !IsPaused && !pausedDuringNotification && tweetQueue.Count > 0){
|
||||
LoadNextNotification();
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadNextNotification(){
|
||||
if (!IsNotificationVisible){
|
||||
if (Program.UserConfig.NotificationNonIntrusiveMode && IsCursorOverNotificationArea && NativeMethods.GetIdleSeconds() < NonIntrusiveIdleLimit){
|
||||
if (!timerCursorCheck.Enabled){
|
||||
PauseNotification();
|
||||
timerCursorCheck.Start();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else if (Program.UserConfig.NotificationIdlePauseSeconds > 0 && NativeMethods.GetIdleSeconds() >= Program.UserConfig.NotificationIdlePauseSeconds){
|
||||
if (!timerIdlePauseCheck.Enabled){
|
||||
PauseNotification();
|
||||
timerIdlePauseCheck.Start();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LoadTweet(tweetQueue.Dequeue());
|
||||
}
|
||||
|
||||
protected override void UpdateTitle(){
|
||||
base.UpdateTitle();
|
||||
|
||||
if (tweetQueue.Count > 0){
|
||||
Text = Text+" ("+tweetQueue.Count+" more left)";
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnNotificationReady(){
|
||||
UpdateTitle();
|
||||
base.OnNotificationReady();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,49 +0,0 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Resources;
|
||||
|
||||
namespace TweetDuck.Core.Notification.Screenshot{
|
||||
sealed class FormNotificationScreenshotable : FormNotificationBase{
|
||||
public FormNotificationScreenshotable(Action callback, Form owner) : base(owner, false){
|
||||
browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new CallbackBridge(this, callback));
|
||||
|
||||
browser.FrameLoadEnd += (sender, args) => {
|
||||
if (args.Frame.IsMain && browser.Address != "about:blank"){
|
||||
ScriptLoader.ExecuteScript(args.Frame, "window.setTimeout($TD_NotificationScreenshot.trigger, 67)", "gen:screenshot");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override string GetTweetHTML(TweetNotification tweet){
|
||||
return tweet.GenerateHtml(enableCustomCSS: false);
|
||||
}
|
||||
|
||||
public void LoadNotificationForScreenshot(TweetNotification tweet, int width, int height){
|
||||
LoadTweet(tweet);
|
||||
SetNotificationSize(width, height);
|
||||
}
|
||||
|
||||
public void TakeScreenshot(){
|
||||
IntPtr context = NativeMethods.GetDC(this.Handle);
|
||||
|
||||
if (context == IntPtr.Zero){
|
||||
MessageBox.Show("Could not retrieve a graphics context handle for the notification window to take the screenshot.", "Screenshot Failed", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
else{
|
||||
using(Bitmap bmp = new Bitmap(ClientSize.Width, ClientSize.Height, PixelFormat.Format32bppRgb)){
|
||||
try{
|
||||
NativeMethods.RenderSourceIntoBitmap(context, bmp);
|
||||
}finally{
|
||||
NativeMethods.ReleaseDC(this.Handle, context);
|
||||
}
|
||||
|
||||
Clipboard.SetImage(bmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,75 +0,0 @@
|
||||
// Uncomment to keep screenshot windows visible for debugging
|
||||
// #define NO_HIDE_SCREENSHOTS
|
||||
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
|
||||
namespace TweetDuck.Core.Notification.Screenshot{
|
||||
sealed class TweetScreenshotManager : IDisposable{
|
||||
private readonly Form owner;
|
||||
private readonly Timer timeout;
|
||||
private readonly Timer disposer;
|
||||
|
||||
private FormNotificationScreenshotable screenshot;
|
||||
|
||||
public TweetScreenshotManager(Form owner){
|
||||
this.owner = owner;
|
||||
|
||||
this.timeout = new Timer{ Interval = 8000 };
|
||||
this.timeout.Tick += timeout_Tick;
|
||||
|
||||
this.disposer = new Timer{ Interval = 1 };
|
||||
this.disposer.Tick += disposer_Tick;
|
||||
}
|
||||
|
||||
private void timeout_Tick(object sender, EventArgs e){
|
||||
timeout.Stop();
|
||||
screenshot.Location = ControlExtensions.InvisibleLocation;
|
||||
disposer.Start();
|
||||
}
|
||||
|
||||
private void disposer_Tick(object sender, EventArgs e){
|
||||
disposer.Stop();
|
||||
screenshot.Dispose();
|
||||
screenshot = null;
|
||||
}
|
||||
|
||||
public void Trigger(string html, int width, int height){
|
||||
if (screenshot != null){
|
||||
return;
|
||||
}
|
||||
|
||||
screenshot = new FormNotificationScreenshotable(Callback, owner){
|
||||
CanMoveWindow = () => false
|
||||
};
|
||||
|
||||
screenshot.LoadNotificationForScreenshot(new TweetNotification(string.Empty, html, 0, string.Empty, string.Empty), width, height);
|
||||
screenshot.Show();
|
||||
timeout.Start();
|
||||
}
|
||||
|
||||
private void Callback(){
|
||||
if (!timeout.Enabled){
|
||||
return;
|
||||
}
|
||||
|
||||
timeout.Stop();
|
||||
screenshot.TakeScreenshot();
|
||||
|
||||
#if !(DEBUG && NO_HIDE_SCREENSHOTS)
|
||||
screenshot.Location = ControlExtensions.InvisibleLocation;
|
||||
disposer.Start();
|
||||
#else
|
||||
screenshot.MoveToVisibleLocation();
|
||||
screenshot.FormClosed += (sender, args) => disposer.Start();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Dispose(){
|
||||
timeout.Dispose();
|
||||
disposer.Dispose();
|
||||
screenshot?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
using System;
|
||||
using TweetLib.Audio;
|
||||
using TweetLib.Audio.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Notification{
|
||||
sealed class SoundNotification : IDisposable{
|
||||
public string SupportedFormats => player.SupportedFormats;
|
||||
public event EventHandler<PlaybackErrorEventArgs> PlaybackError;
|
||||
|
||||
private readonly AudioPlayer player;
|
||||
|
||||
public SoundNotification(){
|
||||
this.player = AudioPlayer.New();
|
||||
this.player.PlaybackError += Player_PlaybackError;
|
||||
}
|
||||
|
||||
public void Play(string file){
|
||||
player.Play(file);
|
||||
}
|
||||
|
||||
private void Player_PlaybackError(object sender, PlaybackErrorEventArgs e){
|
||||
PlaybackError?.Invoke(this, e);
|
||||
}
|
||||
|
||||
public void Dispose(){
|
||||
player.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,106 +0,0 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using TweetDuck.Resources;
|
||||
|
||||
namespace TweetDuck.Core.Notification{
|
||||
sealed class TweetNotification{
|
||||
private static string FontSizeClass { get; set; }
|
||||
private static string HeadTag { get; set; }
|
||||
|
||||
private const string DefaultFontSizeClass = "medium";
|
||||
private const string DefaultHeadTag = @"<meta charset='utf-8'><meta http-equiv='X-UA-Compatible' content='chrome=1'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/font.5ef884f9f9.css'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/app-dark.5631e0dd42.css'><style type='text/css'>body{background:#222426}</style>";
|
||||
private const string CustomCSS = @"body:before{content:none}body{overflow-y:auto}.scroll-styled-v::-webkit-scrollbar{width:7px}.scroll-styled-v::-webkit-scrollbar-thumb{border-radius:0}.scroll-styled-v::-webkit-scrollbar-track{border-left:0}#td-skip{opacity:0;cursor:pointer;transition:opacity 0.15s ease}.td-hover #td-skip{opacity:0.75}#td-skip:hover{opacity:1}";
|
||||
|
||||
public static int FontSizeLevel{
|
||||
get{
|
||||
switch(FontSizeClass){
|
||||
case "largest": return 4;
|
||||
case "large": return 3;
|
||||
case "medium": return 2;
|
||||
case "small": return 1;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string ExampleTweetHTML;
|
||||
|
||||
public static TweetNotification ExampleTweet{
|
||||
get{
|
||||
if (ExampleTweetHTML == null){
|
||||
ExampleTweetHTML = ScriptLoader.LoadResource("pages/example.html", true);
|
||||
|
||||
#if DEBUG
|
||||
ExampleTweetHTML = ExampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:64px'>Scrollbar test padding...</div>");
|
||||
#endif
|
||||
}
|
||||
|
||||
return new TweetNotification("Home", ExampleTweetHTML, 95, string.Empty, string.Empty, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetFontSizeClass(string newFSClass){
|
||||
FontSizeClass = newFSClass;
|
||||
}
|
||||
|
||||
public static void SetHeadTag(string headContents){
|
||||
HeadTag = headContents;
|
||||
}
|
||||
|
||||
public enum Position{
|
||||
TopLeft, TopRight, BottomLeft, BottomRight, Custom
|
||||
}
|
||||
|
||||
public string Column { get; }
|
||||
public string TweetUrl { get; }
|
||||
public string QuoteUrl { get; }
|
||||
|
||||
private readonly string html;
|
||||
private readonly int characters;
|
||||
private readonly bool isExample;
|
||||
|
||||
public TweetNotification(string column, string html, int characters, string tweetUrl, string quoteUrl) : this(column, html, characters, tweetUrl, quoteUrl, false){}
|
||||
|
||||
private TweetNotification(string column, string html, int characters, string tweetUrl, string quoteUrl, bool isExample){
|
||||
this.Column = column;
|
||||
this.TweetUrl = tweetUrl;
|
||||
this.QuoteUrl = quoteUrl;
|
||||
|
||||
this.html = html;
|
||||
this.characters = characters;
|
||||
this.isExample = isExample;
|
||||
}
|
||||
|
||||
public int GetDisplayDuration(int value){
|
||||
return 2000+Math.Max(1000, value*characters);
|
||||
}
|
||||
|
||||
public string GenerateHtml(string bodyClasses = null, bool enableCustomCSS = true){
|
||||
StringBuilder build = new StringBuilder();
|
||||
build.Append("<!DOCTYPE html>");
|
||||
build.Append("<html class='os-windows txt-base-").Append(FontSizeClass ?? DefaultFontSizeClass).Append("'>");
|
||||
build.Append("<head>").Append(HeadTag ?? DefaultHeadTag);
|
||||
|
||||
if (enableCustomCSS){
|
||||
build.Append("<style type='text/css'>").Append(CustomCSS).Append("</style>");
|
||||
|
||||
if (!string.IsNullOrEmpty(Program.UserConfig.CustomNotificationCSS)){
|
||||
build.Append("<style type='text/css'>").Append(Program.UserConfig.CustomNotificationCSS).Append("</style>");
|
||||
}
|
||||
}
|
||||
|
||||
build.Append("</head>");
|
||||
build.Append("<body class='scroll-styled-v");
|
||||
|
||||
if (!string.IsNullOrEmpty(bodyClasses)){
|
||||
build.Append(' ').Append(bodyClasses);
|
||||
}
|
||||
|
||||
build.Append('\'').Append(isExample ? " td-example-notification" : "").Append("><div class='column' style='width:100%;height:auto;overflow:initial;'>");
|
||||
build.Append(html);
|
||||
build.Append("</div></body>");
|
||||
build.Append("</html>");
|
||||
return build.ToString();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,25 +0,0 @@
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Other{
|
||||
sealed partial class FormAbout : Form{
|
||||
private const string TipsLink = "https://github.com/chylex/TweetDuck/wiki";
|
||||
private const string IssuesLink = "https://github.com/chylex/TweetDuck/issues";
|
||||
|
||||
public FormAbout(){
|
||||
InitializeComponent();
|
||||
|
||||
Text = "About "+Program.BrandName+" "+Program.VersionTag;
|
||||
|
||||
labelDescription.Text = Program.BrandName+" was created by chylex as a replacement to the discontinued official TweetDeck client for Windows.\n\nThe program is available for free under the open source MIT license.";
|
||||
|
||||
labelWebsite.Links.Add(new LinkLabel.Link(0, labelWebsite.Text.Length, Program.Website));
|
||||
labelTips.Links.Add(new LinkLabel.Link(0, labelTips.Text.Length, TipsLink));
|
||||
labelIssues.Links.Add(new LinkLabel.Link(0, labelIssues.Text.Length, IssuesLink));
|
||||
}
|
||||
|
||||
private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e){
|
||||
BrowserUtils.OpenExternalBrowserUnsafe(e.Link.LinkData as string);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,246 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
||||
<data name="pictureLogo.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAABGdBTUEAALGOfPtRkwAAACBjSFJNAACH
|
||||
DwAAjA8AAP1SAACBQAAAfXkAAOmLAAA85QAAGcxzPIV3AAAKOWlDQ1BQaG90b3Nob3AgSUNDIHByb2Zp
|
||||
bGUAAEjHnZZ3VFTXFofPvXd6oc0wAlKG3rvAANJ7k15FYZgZYCgDDjM0sSGiAhFFRJoiSFDEgNFQJFZE
|
||||
sRAUVLAHJAgoMRhFVCxvRtaLrqy89/Ly++Osb+2z97n77L3PWhcAkqcvl5cGSwGQyhPwgzyc6RGRUXTs
|
||||
AIABHmCAKQBMVka6X7B7CBDJy82FniFyAl8EAfB6WLwCcNPQM4BOB/+fpFnpfIHomAARm7M5GSwRF4g4
|
||||
JUuQLrbPipgalyxmGCVmvihBEcuJOWGRDT77LLKjmNmpPLaIxTmns1PZYu4V8bZMIUfEiK+ICzO5nCwR
|
||||
3xKxRoowlSviN+LYVA4zAwAUSWwXcFiJIjYRMYkfEuQi4uUA4EgJX3HcVyzgZAvEl3JJS8/hcxMSBXQd
|
||||
li7d1NqaQffkZKVwBALDACYrmcln013SUtOZvBwAFu/8WTLi2tJFRbY0tba0NDQzMv2qUP91829K3NtF
|
||||
ehn4uWcQrf+L7a/80hoAYMyJarPziy2uCoDOLQDI3fti0zgAgKSobx3Xv7oPTTwviQJBuo2xcVZWlhGX
|
||||
wzISF/QP/U+Hv6GvvmckPu6P8tBdOfFMYYqALq4bKy0lTcinZ6QzWRy64Z+H+B8H/nUeBkGceA6fwxNF
|
||||
hImmjMtLELWbx+YKuGk8Opf3n5r4D8P+pMW5FonS+BFQY4yA1HUqQH7tBygKESDR+8Vd/6NvvvgwIH55
|
||||
4SqTi3P/7zf9Z8Gl4iWDm/A5ziUohM4S8jMX98TPEqABAUgCKpAHykAd6ABDYAasgC1wBG7AG/iDEBAJ
|
||||
VgMWSASpgA+yQB7YBApBMdgJ9oBqUAcaQTNoBcdBJzgFzoNL4Bq4AW6D+2AUTIBnYBa8BgsQBGEhMkSB
|
||||
5CEVSBPSh8wgBmQPuUG+UBAUCcVCCRAPEkJ50GaoGCqDqqF6qBn6HjoJnYeuQIPQXWgMmoZ+h97BCEyC
|
||||
qbASrAUbwwzYCfaBQ+BVcAK8Bs6FC+AdcCXcAB+FO+Dz8DX4NjwKP4PnEIAQERqiihgiDMQF8UeikHiE
|
||||
j6xHipAKpAFpRbqRPuQmMorMIG9RGBQFRUcZomxRnqhQFAu1BrUeVYKqRh1GdaB6UTdRY6hZ1Ec0Ga2I
|
||||
1kfboL3QEegEdBa6EF2BbkK3oy+ib6Mn0K8xGAwNo42xwnhiIjFJmLWYEsw+TBvmHGYQM46Zw2Kx8lh9
|
||||
rB3WH8vECrCF2CrsUexZ7BB2AvsGR8Sp4Mxw7rgoHA+Xj6vAHcGdwQ3hJnELeCm8Jt4G749n43PwpfhG
|
||||
fDf+On4Cv0CQJmgT7AghhCTCJkIloZVwkfCA8JJIJKoRrYmBRC5xI7GSeIx4mThGfEuSIemRXEjRJCFp
|
||||
B+kQ6RzpLuklmUzWIjuSo8gC8g5yM/kC+RH5jQRFwkjCS4ItsUGiRqJDYkjiuSReUlPSSXK1ZK5kheQJ
|
||||
yeuSM1J4KS0pFymm1HqpGqmTUiNSc9IUaVNpf+lU6RLpI9JXpKdksDJaMm4ybJkCmYMyF2TGKQhFneJC
|
||||
YVE2UxopFykTVAxVm+pFTaIWU7+jDlBnZWVkl8mGyWbL1sielh2lITQtmhcthVZKO04bpr1borTEaQln
|
||||
yfYlrUuGlszLLZVzlOPIFcm1yd2WeydPl3eTT5bfJd8p/1ABpaCnEKiQpbBf4aLCzFLqUtulrKVFS48v
|
||||
vacIK+opBimuVTyo2K84p6Ss5KGUrlSldEFpRpmm7KicpFyufEZ5WoWiYq/CVSlXOavylC5Ld6Kn0Cvp
|
||||
vfRZVUVVT1Whar3qgOqCmrZaqFq+WpvaQ3WCOkM9Xr1cvUd9VkNFw08jT6NF454mXpOhmai5V7NPc15L
|
||||
Wytca6tWp9aUtpy2l3audov2Ax2yjoPOGp0GnVu6GF2GbrLuPt0berCehV6iXo3edX1Y31Kfq79Pf9AA
|
||||
bWBtwDNoMBgxJBk6GWYathiOGdGMfI3yjTqNnhtrGEcZ7zLuM/5oYmGSYtJoct9UxtTbNN+02/R3Mz0z
|
||||
llmN2S1zsrm7+QbzLvMXy/SXcZbtX3bHgmLhZ7HVosfig6WVJd+y1XLaSsMq1qrWaoRBZQQwShiXrdHW
|
||||
ztYbrE9Zv7WxtBHYHLf5zdbQNtn2iO3Ucu3lnOWNy8ft1OyYdvV2o/Z0+1j7A/ajDqoOTIcGh8eO6o5s
|
||||
xybHSSddpySno07PnU2c+c7tzvMuNi7rXM65Iq4erkWuA24ybqFu1W6P3NXcE9xb3Gc9LDzWepzzRHv6
|
||||
eO7yHPFS8mJ5NXvNelt5r/Pu9SH5BPtU+zz21fPl+3b7wX7efrv9HqzQXMFb0ekP/L38d/s/DNAOWBPw
|
||||
YyAmMCCwJvBJkGlQXlBfMCU4JvhI8OsQ55DSkPuhOqHC0J4wybDosOaw+XDX8LLw0QjjiHUR1yIVIrmR
|
||||
XVHYqLCopqi5lW4r96yciLaILoweXqW9KnvVldUKq1NWn46RjGHGnIhFx4bHHol9z/RnNjDn4rziauNm
|
||||
WS6svaxnbEd2OXuaY8cp40zG28WXxU8l2CXsTphOdEisSJzhunCruS+SPJPqkuaT/ZMPJX9KCU9pS8Wl
|
||||
xqae5Mnwknm9acpp2WmD6frphemja2zW7Fkzy/fhN2VAGasyugRU0c9Uv1BHuEU4lmmfWZP5Jiss60S2
|
||||
dDYvuz9HL2d7zmSue+63a1FrWWt78lTzNuWNrXNaV78eWh+3vmeD+oaCDRMbPTYe3kTYlLzpp3yT/LL8
|
||||
V5vDN3cXKBVsLBjf4rGlpVCikF84stV2a9021DbutoHt5turtn8sYhddLTYprih+X8IqufqN6TeV33za
|
||||
Eb9joNSydP9OzE7ezuFdDrsOl0mX5ZaN7/bb3VFOLy8qf7UnZs+VimUVdXsJe4V7Ryt9K7uqNKp2Vr2v
|
||||
Tqy+XeNc01arWLu9dn4fe9/Qfsf9rXVKdcV17w5wD9yp96jvaNBqqDiIOZh58EljWGPft4xvm5sUmoqb
|
||||
PhziHRo9HHS4t9mqufmI4pHSFrhF2DJ9NProje9cv+tqNWytb6O1FR8Dx4THnn4f+/3wcZ/jPScYJ1p/
|
||||
0Pyhtp3SXtQBdeR0zHYmdo52RXYNnvQ+2dNt293+o9GPh06pnqo5LXu69AzhTMGZT2dzz86dSz83cz7h
|
||||
/HhPTM/9CxEXbvUG9g5c9Ll4+ZL7pQt9Tn1nL9tdPnXF5srJq4yrndcsr3X0W/S3/2TxU/uA5UDHdavr
|
||||
XTesb3QPLh88M+QwdP6m681Lt7xuXbu94vbgcOjwnZHokdE77DtTd1PuvriXeW/h/sYH6AdFD6UeVjxS
|
||||
fNTws+7PbaOWo6fHXMf6Hwc/vj/OGn/2S8Yv7ycKnpCfVEyqTDZPmU2dmnafvvF05dOJZ+nPFmYKf5X+
|
||||
tfa5zvMffnP8rX82YnbiBf/Fp99LXsq/PPRq2aueuYC5R69TXy/MF72Rf3P4LeNt37vwd5MLWe+x7ys/
|
||||
6H7o/ujz8cGn1E+f/gUDmPP8usTo0wAAAAlwSFlzAAAuIgAALiIBquLdkgAAEVpJREFUeF7tXQl0VNd5
|
||||
Thq3Tk7tc+qmiZMm7YmdJj1OT3Gd0kTMG3mJnZQEJzaSjXETx0lqNwt2jWscQGixBAJLSMhgA8YYbIyQ
|
||||
hHYJI4lFGMwioQWxyewgMdoXtKFd4u/337lPjDRv9pk3g+A75zsMWv577/fd+9/l3Rl9johu0Y/U/OIt
|
||||
6sfrLwIYT2de+SJ4F/j34LfB+8Ap4L9J8uvvgfy9b4DfBO8EPy9DBBwC3gCI93fgDHAuuA7cDVaDzWAX
|
||||
OAAOgcNgP9gN8vc+A4vB9eBr4M/Be8GAMiPgDIBAXwVDwbXgEbAPJC+RDToGvgfOBr8mi/UbAsIACMFp
|
||||
ZRaYC7aBWuL5gh3gdvBZ8C5ZHV3hVwPQaM7hS0ETqCWQnqwDE8Apsnq6wC8GoJEPgFtAzt9aYviTPJ9k
|
||||
gkGyuj6FrgagUd8CeVIcBLUaH0gcATeD35XV9wl0MQCN4OXjn8EroFZjA5m8qooE/1o2x6vwuQGouAEs
|
||||
A7UadyPxOPgj2SyvwacGoMILwRsh3ThLTksx4BdkEz2GTwxABb8C8pJSqxGTgTvAb8jmegSvG4CK/QvI
|
||||
O1Wtik8mXgSnyma7Da8agAo9Auq5kfI3+Shkhmy+W/CaAajI42CvrNjNRJ7jZksZXIZXDEAFWPxA3FTp
|
||||
xVHQLRM8NgAFPwTejD1/InkH/biUxWl4ZAAK5LP3mynnO+JV0KWJ2W0DUNCXwVOy4Fu8zhrQ6SWqJwbk
|
||||
WxQa2Mxop6fSWyk0rYlCUhspZEs9zdxSJ8iv+WuhW5vpqYw27d93nXvB26RUduGWAQg+36KwwCULv7VF
|
||||
iDx3Wx1tPNxCBy92UU17P7VdHaLWniE619JLu8900Nv7m+jFLBgCM9gs/l3NmCqlqU+nt+D/msYtk3LZ
|
||||
hcsGIPB/gPaPF7jyKrW+rwNZnJCUBoraUU9H63po9No1NM8++odGac/ZDno5r45C2QitESGF59gv5dVT
|
||||
clUHzc5gEyb8XOaVa6DDsyMU67wBCHg7WCULsEkezi9/jEptbfKLCdzrn0s30b5znWiS6xgeuUaby1so
|
||||
NKVubDSwGZzCOG3NzTdRQXW7+NnM4+b2atUDPAPeIeXTBEK4ZMA8i+Ca5Ir+LrOOOvtHKGkfcit6kp4m
|
||||
sBh/zDGRqaMfzfEMh5CuZiXXUOgWE72Ua6L1Jc10qqmXRkbNo4nT2PMw2sHcESvl0wTCOGcAAv0DyFtv
|
||||
rULGyAKsO9SMUERD6EnRO+t1M4F76++zTNTUNSjK9wYutvVhzujTTGGrMG/wqNCqiwX5IsA/SxmtgDBO
|
||||
G7DBIqhN8oR3suEqQpnRh7watcOcU31pAvfCWUgZ1Y3Xy/YlCk+Z28rlqrTTvkwpoxUQyrEBCPCvoONz
|
||||
fVTg1xkm6hkYQajr4Mktdpd5uecrE9jg5IpWWaJvwRN1SHKtaA/PCfMLmyiq2DxPaNUN5KMKg5RzHBDO
|
||||
KQNSLYLZJFfgVSz3tMBDeM0BrMNlr9H6fXfJ8V7AErK7f1iW5hsMjYxSSnkjPbnxLP1vbq2YE04399KV
|
||||
vmGak9/kqF0FUs5xQFj7BuAXvwM6ddDGOfiNnQ0IYxvbTrYjVWDiwkrFW6OB83DaEd/3/obOfiq71Ekt
|
||||
3YOkzgg8NUTxPOdgLpiV1TE6K7vzASnrGBDCoQF8b0cz6ESyqDG77BvAOI2VxJycy7bX2q4QJoYgDVxo
|
||||
7ZPR9QVv4OzNb7OyOll8mpnWTNPXnlwnZR0DQtg2AAHuAPnCkmbwieQREFZQjzCOwfPChlJs/3mt7cFo
|
||||
YAPn5NTRsFwa6on3kII05zUIjt4uXv9icx099u5pCn7rKBkSynoMC1K/LuUVQBi7BswcF9gBWYzfZ5vE
|
||||
RsZZnG/powjsVsXc4IYR/DtLix2POm+C9wGrPjWfK1nWl3v6MxA+FB1xxgeX6OF3TpKSdASsIiMMMK48
|
||||
TkpM4RwprwDC2TUgTw3uFFEZXgo2dA4glPNgu8pruyms0HxAJnaWThrBP7v2oHnfoQd48xWBelqmHe7t
|
||||
Is2kNNJ/rj9HD648RsoKC+FVwgBj/MGDUl4BhNQ2AIH/FuTLq1aNtkfuFYWfXUEo18FGHKvrofg9DfRs
|
||||
mknEcjQq2ABejeiFyEKTWXyUbU4z7fSLj0z06NpTELnKLLyl6BOZVDVgDM+8R8ps14AQy4Y6S1fmAXvg
|
||||
npZ/oo0iiuro2VSYkdIgVhocX0zc0hQ2YDWWt3phIdrGp5+hW1vpZxsv0SOcZrR6uy1iFAQvLvqDlNmu
|
||||
Ae9aCus0IQzn8/LL3QjnHbAZpZe6RE9fsL2O/jvTJFIdlzMz2URRhZflT/oer+fX0E/ePUXBttKMI3Ia
|
||||
enNfhpRZ2wAI+XnQ4amnLXLamPdxHQ1i4+IL8AqqHvMMp6vdZ65Q8en2sXW5r/HbjZVYzVRoi+sMYZwx
|
||||
saxWWbj1Swhn04B/BPkQSVNgu+TUAHKefB/LzMkE3s3PXgfxsbLRFNd5XlMWZT6AkDYN+JmVsE5QLEPz
|
||||
WujXGY0iZ3N60GOHqheuDgzT4++U80SqJarz5DQUXfAcQlobACE5/fAb4jRFtkdOPatLWqmrf4RSKlvo
|
||||
1fzL9MSGM7TuQJ04R7nR0dDRSw8mlGqL6gpXniDjkp2xyoKULyCslQH81MutCZgNWLz7+qaI83Jz9yAm
|
||||
0A5q6/HeGb2/cPxyBxnivWEAT8T7c4Mjc+5EWCsD/gbkt4JqimyP6rGA+sRosqHgWD0Zlh/WFtUVsgHL
|
||||
S44aI3O/hrBWBvCboN273cwTMJaHvEKZjEgsOkNKogcrIJViJVTeaIzefi/CWhnA7zJvGiesC+TVT85x
|
||||
8wPryYYXNpl3upqiukQYsKKyR1my63sIa2UAv3WU3xelKbAj8k71lXz/nE76Em09A/Tjt5B+PF0BqUw6
|
||||
MogN2RSEtjKAHz+6twdg8k6Yz4NOuXceFKgorm7yTv5XmXRkVIk78H2EtjLgfpBv+moL7AR5Mv5NRp1X
|
||||
byf4GzH5n3kn/6vkkbS8dCpCWxnAnzrikQFMPiRbWFAnbkXc6OAN2BNreAPmjfwvyQbEH9I0gD/yxfM3
|
||||
W/BxRFqTuBrYOzj+lsSNht0nG72bfphJVddgwL8jvJUBHk3C4yhNeCXfRJfaPL+p5i+8ln4Cq59KbSHd
|
||||
ZVLloLJs7/0Ib2UAL0NbxgnpCWEC75Bnp5rog7IW6ujz7dURb+NsYxdvmswpQ0tItyiWod1KTJHmMpQ3
|
||||
YvxhR9qCukiekGenN4tHjU9+VENPbzpLcbtq6GS9954X+BLLtp/y7uTLNG/EmozRBd9GEVYG8Gf37LEU
|
||||
0RNy7/+o8gqdae6jQxc76eCFTqqo7aTGzsBPSRdbeuihRG/3fpANiC85qkTkaB5F8Adr8CeaaArqKsUj
|
||||
yiJ9by14C2/keXnpqXIV5pTYPblB8zZ9BcVYGcDH0f9nKaJH5I1ZSgMduNCF8DcOjtS0k+L13C/JBize
|
||||
EWeYn3wbihpvgHwgw+/71RbUDfIo+FNuvdWl3UAFn+a+KM59vLzyUWl+IPMbFHVd97EXZgPuAb3yKSfq
|
||||
ZSUeBbFFNSgi8PHRwRrzut8Xvd/Ma8FR+XYfSf4FyJ+PoymqM1TvRIakNtP098/TQ5h4psWXUnzBaRQT
|
||||
uDhZ1yknXi/uei0JHZTlpbVBr6z7IoqzNkAlRHR9Ira4E/lEcv31O5Hq9Q2Qe9bCrJPU3T+EYgILPajT
|
||||
r96v8F3qYSL/G5bszuKOjiLHeP2FBETkj5G0FlmDapp5amsrzfiwhh5ZPeFOpCXxNSWxnGauKaed2OI7
|
||||
8eZF3RCejXqjbj5MPWYDInJfZo1RpF0D+EOX7L4nbOzqdWqTSDN8J3Kst2sVbkF+sMGj4fmNlZR2uFas
|
||||
uYflg3t/mLJy11lf530Q6/+3qoaw+vkOa4xibRvAgMj8oaYTRLe+eu3UnUgt8mhgIxLKKBhLvsdXltDc
|
||||
zRV0qUXfXfKHBy7pID4ongUfKpfyOmXAf00U3ubVa0+IOCzAM+vKqOxCG6qhHzbziodvOvhq0rUk0k9Q
|
||||
WPpLUl4BVMG2AbOyu+4CW1j8Jx1dvXaHLDx6/09XHabU0loaHNb32cG7n1zQT3w+fkgs75r6Pwl3S3kF
|
||||
UA3bBjB+vvH8CnH1WqYLzeCukEcNVhnc459YXSaGf0evvk/O2OjovGqZdnQQn/l2NQVF5G2Qso4B1bFv
|
||||
gCEs/X6kmhHNoCp5NHBDBPm1ShYbTKwQPZ1723T09tfTT4hnrL2D+h9N17Zdpd9uNI88UUet9viCGAFB
|
||||
r29WpKxjQJXsG8AwLtv3sbhOpxUYjZi+5hg9s+E4zVpfRT9ZeZh+nFRKP11ZRqFry+mFD6soKreakg/V
|
||||
iPzuz/V/TqVJ1E2s83UVX9yE2294baPV3y5AtRwboIRnT0OgUavAko++fZR++cFxKj7VQgPDI9Q3OCKE
|
||||
HhgKjLOfc03d9ErqcX1TjiVhADLJdCnnOKB6jg1gGOP2pwsntQpAbxLzw/JSCsuqprNocCCgoaOPEgrP
|
||||
0MOJJeZjZT17vUo++Vz6yU4poxVQTScNWJT5XfSefvNmQqMgJhuBhirxJTQ/4wRVXGqnUT9c0Drf3E3L
|
||||
IfyPON2Ina0fer0gVoxJVcNBr31g9QZtFaiucwYwjEt2xdqcCywpRgRWOph0f7m+QuT/y+1XfbrD5Ztr
|
||||
24/V05wtR8Wmzr/CS7JWscWrpHyaQNWdN0AJS7vDmFh2zmYqmkiZmnjF8WBCibhbuWbPeTqMyZiXnp4Y
|
||||
wgdnfF2cl7Gc3x9Db+ccr/sEa4u87l9R3hAcnvllKZ8m0BTnDWAo4VmPGldyIXZSkRalGdwzeWTwaonP
|
||||
gRZlV9O6vRcoq8JEu7E0LTnXSidMHSKNHK29Qofwf76Xw99/Dz8XiRXV7z48QjPeLkOqg+gwN2BEtyRv
|
||||
WCNyQqRsNuGyAQxlya44p1KRPaqGQDxhCnrvGFlYlRZf558TcwxP+IEmuCXFI8eda6RcduGWAcaFaX9p
|
||||
jD/0qdOp6GYia7K8pCp4UfqXpFx24ZYBDGVB6jeNKypMt0ywoPm8p9UYkW3zI8omwm0DGIbXN//AuKKy
|
||||
VxSsVaGbiWYNhrBpdenPnHhkAAMTzUwUPnxTm6C2PWrbc1IWp+GxAQxDePavxlXkpiLazO2OKfqjlMMl
|
||||
eMUABruPCt1kI4HbiuVm9PY/SRlchtcMYBjCMkJQMcwJN8HEzB0tqWrIGF3wvGy+W/CqAQwlLH2aknDY
|
||||
xGthzYpPBnIHW1HRYozMeUw222143QDGtD8n3yP2CcKESZaSeAOaUFpuDM+8TzbXI/jEAIZxYepfGaIL
|
||||
48yVngQpiVMO2qHEFq81Lkr32p819JkBKgyLMmeYD/Bu4NHAdU8sr1Ui85+SzfIafG4Aw7go4y5jbPEy
|
||||
8TzhRpobxMitGjYu3fOOMWzrV2VzvApdDFDxw3mbpyhL92aL09RATkuibhitb35agIn2h7L6PoGuBqgw
|
||||
hOc8Zow7UCQayyMiIPYOnONlmow7sEeJzPPoL+Q5C78YoAI76KBpMTtXK4nlLcZV6HVqz9MUyEfkMpkr
|
||||
KnuMSz/ZpETkPCyrpwv8aoCKH8zdcPe0yG0vYum6X8wTLIivRoZczQgmVQ2izBIluvBVZeHWb8nq6IqA
|
||||
MMASWDX9k+GN7X8wxO5JU5aXnjeLxoKxIVI4YYzKCQKrX7cUWpiJf/n7CWW1yrJ9WcaYwpeU8Cynj419
|
||||
hYAzwBJB8zbdHhyVd78xpuA5ZfHOWEzgWeixFVgSNiBldGG0DICj4uE7PyEzvx4Q6YQ/ECm+5Cgm0jxl
|
||||
8Y5lyhsfPw9zpyrzU3zyp8ndRUAbwFDrZViQdlvQ/NQ7kKPvNkYX3GuIKbpPWfrJFP7IFyXu4FQYM1V5
|
||||
c//3lWV7p/A70JWobfcq4dlfN4Zn3WmYn3y7GseSgYCxulhW7Bb1p+YXb1Ev0uf+H9A3E1Z4VJUaAAAA
|
||||
AElFTkSuQmCC
|
||||
</value>
|
||||
</data>
|
||||
</root>
|
@@ -1,138 +0,0 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck.Core.Other{
|
||||
sealed partial class FormMessage : Form{
|
||||
public Button ClickedButton { get; private set; }
|
||||
|
||||
public int ActionPanelY => panelActions.Location.Y;
|
||||
|
||||
private int ClientWidth{
|
||||
get => ClientSize.Width;
|
||||
set => ClientSize = new Size(value, ClientSize.Height);
|
||||
}
|
||||
|
||||
private readonly Icon icon;
|
||||
private readonly bool isReady;
|
||||
|
||||
private int realFormWidth, minFormWidth;
|
||||
private int buttonCount;
|
||||
private int prevLabelWidth, prevLabelHeight;
|
||||
private bool wasLabelMultiline;
|
||||
|
||||
public FormMessage(string caption, string text, MessageBoxIcon messageIcon){
|
||||
InitializeComponent();
|
||||
|
||||
this.prevLabelWidth = labelMessage.Width;
|
||||
this.prevLabelHeight = labelMessage.Height;
|
||||
this.minFormWidth = 40;
|
||||
|
||||
switch(messageIcon){
|
||||
case MessageBoxIcon.Information:
|
||||
icon = SystemIcons.Information;
|
||||
break;
|
||||
|
||||
case MessageBoxIcon.Warning:
|
||||
icon = SystemIcons.Warning;
|
||||
break;
|
||||
|
||||
case MessageBoxIcon.Error:
|
||||
icon = SystemIcons.Error;
|
||||
break;
|
||||
|
||||
case MessageBoxIcon.Question:
|
||||
icon = SystemIcons.Question;
|
||||
break;
|
||||
|
||||
default:
|
||||
icon = null;
|
||||
labelMessage.Location = new Point(labelMessage.Location.X-38, labelMessage.Location.Y);
|
||||
break;
|
||||
}
|
||||
|
||||
this.isReady = true;
|
||||
|
||||
this.Text = caption;
|
||||
this.labelMessage.Text = text;
|
||||
}
|
||||
|
||||
private void FormMessage_SizeChanged(object sender, EventArgs e){
|
||||
RecalculateButtonLocation();
|
||||
}
|
||||
|
||||
public Button AddButton(string title, DialogResult result = DialogResult.OK){
|
||||
Button button = new Button{
|
||||
Anchor = AnchorStyles.Bottom,
|
||||
Font = SystemFonts.MessageBoxFont,
|
||||
Location = new Point(0, 12),
|
||||
Size = new Size(88, 26),
|
||||
TabIndex = buttonCount,
|
||||
Text = title,
|
||||
UseVisualStyleBackColor = true
|
||||
};
|
||||
|
||||
button.Click += (sender, args) => {
|
||||
ClickedButton = (Button)sender;
|
||||
DialogResult = result;
|
||||
Close();
|
||||
};
|
||||
|
||||
panelActions.Controls.Add(button);
|
||||
++buttonCount;
|
||||
|
||||
minFormWidth += 96;
|
||||
ClientWidth = Math.Max(realFormWidth, minFormWidth);
|
||||
RecalculateButtonLocation();
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
public void AddActionControl(Control control){
|
||||
panelActions.Controls.Add(control);
|
||||
|
||||
minFormWidth += control.Width+control.Margin.Horizontal;
|
||||
ClientWidth = Math.Max(realFormWidth, minFormWidth);
|
||||
}
|
||||
|
||||
private void RecalculateButtonLocation(){
|
||||
for(int index = 0; index < buttonCount; index++){
|
||||
Control control = panelActions.Controls[index];
|
||||
control.Location = new Point(ClientWidth-97-index*96, control.Location.Y);
|
||||
}
|
||||
}
|
||||
|
||||
private void labelMessage_SizeChanged(object sender, EventArgs e){
|
||||
if (!isReady){
|
||||
return;
|
||||
}
|
||||
|
||||
bool isMultiline = labelMessage.Height > labelMessage.MinimumSize.Height;
|
||||
|
||||
if (isMultiline && !wasLabelMultiline){
|
||||
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y-8);
|
||||
prevLabelHeight += 8;
|
||||
}
|
||||
else if (!isMultiline && wasLabelMultiline){
|
||||
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y+8);
|
||||
prevLabelHeight -= 8;
|
||||
}
|
||||
|
||||
realFormWidth = ClientWidth-(icon == null ? 50 : 0)+labelMessage.Width-prevLabelWidth;
|
||||
ClientWidth = Math.Max(realFormWidth, minFormWidth);
|
||||
Height += labelMessage.Height-prevLabelHeight;
|
||||
|
||||
prevLabelWidth = labelMessage.Width;
|
||||
prevLabelHeight = labelMessage.Height;
|
||||
wasLabelMultiline = isMultiline;
|
||||
}
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e){
|
||||
if (icon != null){
|
||||
e.Graphics.DrawIcon(icon, 25, 26);
|
||||
}
|
||||
|
||||
base.OnPaint(e);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,124 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Plugins.Controls;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
using TweetDuck.Plugins.Events;
|
||||
|
||||
namespace TweetDuck.Core.Other{
|
||||
sealed partial class FormPlugins : Form{
|
||||
private readonly PluginManager pluginManager;
|
||||
private readonly TabButton tabBtnOfficial, tabBtnCustom;
|
||||
private readonly PluginListFlowLayout flowLayoutPlugins;
|
||||
|
||||
private PluginGroup? selectedGroup;
|
||||
|
||||
public FormPlugins(){
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName+" Plugins";
|
||||
}
|
||||
|
||||
public FormPlugins(PluginManager pluginManager) : this(){
|
||||
this.pluginManager = pluginManager;
|
||||
this.pluginManager.Reloaded += pluginManager_Reloaded;
|
||||
|
||||
this.flowLayoutPlugins = new PluginListFlowLayout();
|
||||
this.flowLayoutPlugins.Resize += flowLayoutPlugins_Resize;
|
||||
|
||||
this.tabPanelPlugins.SetupTabPanel(90);
|
||||
this.tabPanelPlugins.ReplaceContent(flowLayoutPlugins);
|
||||
|
||||
this.tabBtnOfficial = tabPanelPlugins.AddButton("", () => SelectGroup(PluginGroup.Official));
|
||||
this.tabBtnCustom = tabPanelPlugins.AddButton("", () => SelectGroup(PluginGroup.Custom));
|
||||
|
||||
this.pluginManager_Reloaded(pluginManager, null);
|
||||
|
||||
Shown += (sender, args) => {
|
||||
Program.UserConfig.PluginsWindow.Restore(this, false);
|
||||
this.tabPanelPlugins.SelectTab(tabBtnOfficial);
|
||||
};
|
||||
|
||||
FormClosed += (sender, args) => {
|
||||
Program.UserConfig.PluginsWindow.Save(this);
|
||||
Program.UserConfig.Save();
|
||||
};
|
||||
|
||||
Disposed += (sender, args) => this.pluginManager.Reloaded -= pluginManager_Reloaded;
|
||||
}
|
||||
|
||||
private void SelectGroup(PluginGroup group){
|
||||
if (selectedGroup.HasValue && selectedGroup == group)return;
|
||||
|
||||
selectedGroup = group;
|
||||
|
||||
ReloadPluginTab();
|
||||
}
|
||||
|
||||
public void ReloadPluginTab(){
|
||||
if (!selectedGroup.HasValue)return;
|
||||
|
||||
flowLayoutPlugins.SuspendLayout();
|
||||
flowLayoutPlugins.Controls.Clear();
|
||||
|
||||
Plugin[] plugins = pluginManager.GetPluginsByGroup(selectedGroup.Value).OrderBy(plugin => !plugin.CanRun ? 0 : pluginManager.Config.IsEnabled(plugin) ? 1 : 2).ThenBy(plugin => plugin.Name).ToArray();
|
||||
|
||||
for(int index = 0; index < plugins.Length; index++){
|
||||
flowLayoutPlugins.Controls.Add(new PluginControl(pluginManager, plugins[index]));
|
||||
|
||||
if (index < plugins.Length-1){
|
||||
flowLayoutPlugins.Controls.Add(new Panel{
|
||||
BackColor = Color.DimGray,
|
||||
Size = new Size(1, 1)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
flowLayoutPlugins.ResumeLayout(true);
|
||||
flowLayoutPlugins_Resize(flowLayoutPlugins, new EventArgs());
|
||||
}
|
||||
|
||||
private void pluginManager_Reloaded(object sender, PluginErrorEventArgs e){
|
||||
tabBtnOfficial.Text = "Official: "+pluginManager.CountPluginByGroup(PluginGroup.Official);
|
||||
tabBtnCustom.Text = "Custom: "+pluginManager.CountPluginByGroup(PluginGroup.Custom);
|
||||
}
|
||||
|
||||
private void flowLayoutPlugins_Resize(object sender, EventArgs e){
|
||||
if (flowLayoutPlugins.Controls.Count == 0){
|
||||
return;
|
||||
}
|
||||
|
||||
Control lastControl = flowLayoutPlugins.Controls[flowLayoutPlugins.Controls.Count-1];
|
||||
bool showScrollBar = lastControl.Location.Y+lastControl.Height >= flowLayoutPlugins.Height;
|
||||
int horizontalOffset = showScrollBar ? SystemInformation.VerticalScrollBarWidth : 0;
|
||||
|
||||
flowLayoutPlugins.AutoScroll = showScrollBar;
|
||||
flowLayoutPlugins.VerticalScroll.Visible = showScrollBar;
|
||||
|
||||
foreach(Control control in flowLayoutPlugins.Controls){
|
||||
control.Width = flowLayoutPlugins.Width-control.Margin.Horizontal-horizontalOffset;
|
||||
}
|
||||
|
||||
flowLayoutPlugins.Focus();
|
||||
}
|
||||
|
||||
private void btnOpenFolder_Click(object sender, EventArgs e){
|
||||
using(Process.Start("explorer.exe", "\""+pluginManager.PathCustomPlugins+"\"")){}
|
||||
}
|
||||
|
||||
private void btnReload_Click(object sender, EventArgs e){
|
||||
if (MessageBox.Show("This will also reload the browser window. Do you want to proceed?", "Reloading Plugins", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
|
||||
pluginManager.Reload();
|
||||
ReloadPluginTab();
|
||||
}
|
||||
}
|
||||
|
||||
private void btnClose_Click(object sender, EventArgs e){
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,120 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
90
Core/Other/FormSettings.Designer.cs
generated
90
Core/Other/FormSettings.Designer.cs
generated
@@ -1,90 +0,0 @@
|
||||
namespace TweetDuck.Core.Other {
|
||||
sealed partial class FormSettings {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing && (components != null)) {
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
this.btnClose = new System.Windows.Forms.Button();
|
||||
this.labelTip = new System.Windows.Forms.Label();
|
||||
this.tabPanel = new TweetDuck.Core.Controls.TabPanel();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// btnClose
|
||||
//
|
||||
this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.btnClose.AutoSize = true;
|
||||
this.btnClose.Location = new System.Drawing.Point(443, 349);
|
||||
this.btnClose.Name = "btnClose";
|
||||
this.btnClose.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
||||
this.btnClose.Size = new System.Drawing.Size(49, 23);
|
||||
this.btnClose.TabIndex = 4;
|
||||
this.btnClose.Text = "Close";
|
||||
this.btnClose.UseVisualStyleBackColor = true;
|
||||
this.btnClose.Click += new System.EventHandler(this.btnClose_Click);
|
||||
//
|
||||
// labelTip
|
||||
//
|
||||
this.labelTip.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.labelTip.AutoSize = true;
|
||||
this.labelTip.Location = new System.Drawing.Point(12, 351);
|
||||
this.labelTip.Name = "labelTip";
|
||||
this.labelTip.Size = new System.Drawing.Size(310, 13);
|
||||
this.labelTip.TabIndex = 5;
|
||||
this.labelTip.Text = "Tip: Move your cursor over an option to see detailed explanation";
|
||||
//
|
||||
// tabPanel
|
||||
//
|
||||
this.tabPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.tabPanel.Location = new System.Drawing.Point(12, 12);
|
||||
this.tabPanel.Name = "tabPanel";
|
||||
this.tabPanel.Size = new System.Drawing.Size(480, 331);
|
||||
this.tabPanel.TabIndex = 3;
|
||||
//
|
||||
// FormSettings
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(504, 384);
|
||||
this.Controls.Add(this.labelTip);
|
||||
this.Controls.Add(this.btnClose);
|
||||
this.Controls.Add(this.tabPanel);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
|
||||
this.Icon = global::TweetDuck.Properties.Resources.icon;
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "FormSettings";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormSettings_FormClosing);
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private Controls.TabPanel tabPanel;
|
||||
private System.Windows.Forms.Button btnClose;
|
||||
private System.Windows.Forms.Label labelTip;
|
||||
}
|
||||
}
|
@@ -1,68 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Other.Settings;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Updates;
|
||||
|
||||
namespace TweetDuck.Core.Other{
|
||||
sealed partial class FormSettings : Form{
|
||||
public const int TabIndexSounds = 2;
|
||||
|
||||
private readonly FormBrowser browser;
|
||||
private readonly Dictionary<Type, BaseTabSettings> tabs = new Dictionary<Type, BaseTabSettings>(4);
|
||||
|
||||
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, int startTabIndex = 0){
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName+" Options";
|
||||
|
||||
this.browser = browser;
|
||||
this.browser.PauseNotification();
|
||||
|
||||
this.tabPanel.SetupTabPanel(100);
|
||||
this.tabPanel.AddButton("General", () => SelectTab(() => new TabSettingsGeneral(updates)));
|
||||
this.tabPanel.AddButton("Notifications", () => SelectTab(() => new TabSettingsNotifications(browser.CreateNotificationForm(false))));
|
||||
this.tabPanel.AddButton("Sounds", () => SelectTab(() => new TabSettingsSounds()));
|
||||
this.tabPanel.AddButton("Advanced", () => SelectTab(() => new TabSettingsAdvanced(browser.ReinjectCustomCSS, plugins)));
|
||||
|
||||
this.tabPanel.SelectTab(tabPanel.Buttons.ElementAt(startTabIndex));
|
||||
}
|
||||
|
||||
private void SelectTab<T>(Func<T> constructor) where T : BaseTabSettings{
|
||||
if (tabs.TryGetValue(typeof(T), out BaseTabSettings control)){
|
||||
tabPanel.ReplaceContent(control);
|
||||
}
|
||||
else{
|
||||
control = tabs[typeof(T)] = constructor();
|
||||
control.OnReady();
|
||||
tabPanel.ReplaceContent(control);
|
||||
}
|
||||
}
|
||||
|
||||
private void FormSettings_FormClosing(object sender, FormClosingEventArgs e){
|
||||
foreach(BaseTabSettings control in tabs.Values){
|
||||
control.OnClosing();
|
||||
}
|
||||
|
||||
Program.UserConfig.Save();
|
||||
|
||||
foreach(BaseTabSettings control in tabs.Values){
|
||||
control.Dispose();
|
||||
}
|
||||
|
||||
browser.ResumeNotification();
|
||||
}
|
||||
|
||||
private void btnClose_Click(object sender, EventArgs e){
|
||||
Close();
|
||||
}
|
||||
|
||||
public void ReloadUI(){
|
||||
tabs.Clear();
|
||||
tabPanel.Content.Controls.Clear();
|
||||
tabPanel.ActiveButton.Callback();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,21 +0,0 @@
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Configuration;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings{
|
||||
class BaseTabSettings : UserControl{
|
||||
protected static UserConfig Config => Program.UserConfig;
|
||||
|
||||
public BaseTabSettings(){
|
||||
Padding = new Padding(6);
|
||||
}
|
||||
|
||||
public virtual void OnReady(){}
|
||||
public virtual void OnClosing(){}
|
||||
|
||||
protected static void PromptRestart(){
|
||||
if (MessageBox.Show("The application must restart for the option to take place. Do you want to restart now?", Program.BrandName+" Options", MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.Yes){
|
||||
Program.Restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user