mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-09-14 19:32:10 +02:00
Compare commits
1274 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 | |||
| fb13695ca5 | |||
| 20c76d06f7 | |||
| 339a11f649 | |||
| 0989400d87 | |||
| 52aacf602d | |||
| 54d70a6a17 | |||
| d980e09e0f | |||
| 2e4cb12817 | |||
| 7b91cb2e96 | |||
| 95c04a8abc | |||
| 25822fefdb | |||
| d800ee2d28 | |||
| 2a51371aca | |||
| ee5d1a47dc | |||
| b330b74347 | |||
| 11fa13f0bb | |||
| 21400d72b3 | |||
| a710cb9d4f | |||
| 3326ad52ce | |||
| c9560df851 | |||
| 74cb45118e | |||
| c79bf19e51 | |||
| 961bec0a2f | |||
| 89e4977cd1 | |||
| bfe16475db | |||
| 915d36867c | |||
| 48435af407 | |||
| 86b6ec5212 | |||
| 775e70bc45 | |||
| 9f565447d0 | |||
| 88d27bc29d | |||
| 172ae87ac6 | |||
| 91d572235e | |||
| 64d32dcb75 | |||
| 564b4283b6 | |||
| ca4d374a81 | |||
| a753806d7b | |||
| bd1692cea3 | |||
| b7ce089f08 | |||
| 8a6b47c5db | |||
| 9f1fc4df18 | |||
| c018a2a7bc | |||
| a1aebab114 | |||
| e30702e1d8 | |||
| 008ff4b055 | |||
| d7bba22e19 | |||
| 2b9a910533 | |||
| 118ebcc627 | |||
| c741767b11 | |||
| 4a09358e14 | |||
| 3f4ea1af08 | |||
| 35bb196832 | |||
| cb5b50dd42 | |||
| 8652272526 | |||
| 0f32504fde | |||
| 4735c21fc0 | |||
| ecbcbcaed4 | |||
| 1677b73ff8 | |||
| 5929067a3d | |||
| d06834617b | |||
| 9d048efe06 | |||
| 6a379bc2cd | |||
| 9f415b11b5 | |||
| b9b9193222 | |||
| 867c2d1632 | |||
| 5447afc3f5 | |||
| b5e58db242 | |||
| 8ab99619d6 | |||
| 4c7660ee65 | |||
| c1b9bde7b0 | |||
| 0e8c6c066f | |||
| 9e44a86be0 | |||
| b61479f84f | |||
| e3c709b005 | |||
| b2b3dba504 | |||
| 4d05441aa8 | |||
| 419b3ee850 | |||
| 4c31e72d29 | |||
| e3b2ff7f0e | |||
| 4c5f5e2cce | |||
| 39ae9b8ba0 | |||
| 5c7eb0535d | |||
| 235718390b | |||
| 5d4b72f224 | |||
| dc76ae9d1f | |||
| e44f4bb003 | |||
| 1fc1370d41 | |||
| 80a669c989 | |||
| 801c9eba2d | |||
| f9704d2836 | |||
| 39687171e9 | |||
| 1d73691ef4 | |||
| f8678d2515 | |||
| fb108ea18d |
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/*
|
||||||
!bld/*.iss
|
!bld/*.iss
|
||||||
!bld/*.bat
|
!bld/*.bat
|
||||||
|
!bld/*.ps1
|
||||||
|
!bld/Redist
|
||||||
!bld/Resources
|
!bld/Resources
|
||||||
|
|
||||||
|
# Rider
|
||||||
|
.idea/.idea.TweetDuck/.idea/dictionaries
|
||||||
|
.idea/.idea.TweetDuck/.idea/misc.xml
|
||||||
|
|
||||||
# User-specific files
|
# User-specific files
|
||||||
*.suo
|
*.suo
|
||||||
*.user
|
*.user
|
||||||
@@ -140,4 +146,3 @@ _UpgradeReport_Files/
|
|||||||
Backup*/
|
Backup*/
|
||||||
UpgradeLog*.XML
|
UpgradeLog*.XML
|
||||||
UpgradeLog*.htm
|
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 TweetDck.Core {
|
namespace TweetDuck.Browser {
|
||||||
sealed partial class FormBrowser {
|
sealed partial class FormBrowser {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Required designer variable.
|
/// Required designer variable.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private System.ComponentModel.IContainer components = null;
|
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
|
#region Windows Form Designer generated code
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -24,24 +13,32 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void InitializeComponent() {
|
private void InitializeComponent() {
|
||||||
this.components = new System.ComponentModel.Container();
|
this.components = new System.ComponentModel.Container();
|
||||||
this.trayIcon = new TweetDck.Core.TrayIcon(this.components);
|
this.trayIcon = new TrayIcon(this.components);
|
||||||
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
||||||
|
this.timerResize = new System.Windows.Forms.Timer(this.components);
|
||||||
this.SuspendLayout();
|
this.SuspendLayout();
|
||||||
//
|
//
|
||||||
|
// timerResize
|
||||||
|
//
|
||||||
|
this.timerResize.Interval = 500;
|
||||||
|
this.timerResize.Tick += new System.EventHandler(this.timerResize_Tick);
|
||||||
|
//
|
||||||
// FormBrowser
|
// FormBrowser
|
||||||
//
|
//
|
||||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
this.BackColor = TweetDck.Core.Utils.BrowserUtils.BackgroundColor;
|
this.BackColor = TweetDuck.Browser.TweetDeckBrowser.BackgroundColor;
|
||||||
this.ClientSize = new System.Drawing.Size(324, 386);
|
this.ClientSize = new System.Drawing.Size(1008, 730);
|
||||||
this.Icon = Properties.Resources.icon;
|
this.Icon = Properties.Resources.icon;
|
||||||
this.Location = TweetDck.Core.Controls.ControlExtensions.InvisibleLocation;
|
this.Location = TweetDuck.Controls.ControlExtensions.InvisibleLocation;
|
||||||
this.MinimumSize = new System.Drawing.Size(340, 424);
|
this.MinimumSize = new System.Drawing.Size(348, 424);
|
||||||
this.Name = "FormBrowser";
|
this.Name = "FormBrowser";
|
||||||
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
|
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
|
||||||
this.Activated += new System.EventHandler(this.FormBrowser_Activated);
|
this.Activated += new System.EventHandler(this.FormBrowser_Activated);
|
||||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormBrowser_FormClosing);
|
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.ResizeEnd += new System.EventHandler(this.FormBrowser_ResizeEnd);
|
||||||
|
this.LocationChanged += new System.EventHandler(this.FormBrowser_LocationChanged);
|
||||||
this.Resize += new System.EventHandler(this.FormBrowser_Resize);
|
this.Resize += new System.EventHandler(this.FormBrowser_Resize);
|
||||||
this.ResumeLayout(false);
|
this.ResumeLayout(false);
|
||||||
|
|
||||||
@@ -51,6 +48,7 @@
|
|||||||
|
|
||||||
private TrayIcon trayIcon;
|
private TrayIcon trayIcon;
|
||||||
private System.Windows.Forms.ToolTip toolTip;
|
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">
|
<resheader name="writer">
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
<metadata name="trayIcon.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||||
<data name="trayIcon.TrayLocation" type="System.Drawing.Point, System.Drawing">
|
|
||||||
<value>17, 17</value>
|
<value>17, 17</value>
|
||||||
</data>
|
</metadata>
|
||||||
<data name="toolTip.TrayLocation" type="System.Drawing.Point, System.Drawing">
|
<metadata name="toolTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||||
<value>112, 17</value>
|
<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>
|
</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 TweetDck.Core.Notification {
|
namespace TweetDuck.Browser.Notification {
|
||||||
partial class FormNotificationBase {
|
partial class FormNotificationBase {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Required designer variable.
|
/// Required designer variable.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private System.ComponentModel.IContainer components = null;
|
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
|
#region Windows Form Designer generated code
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -34,7 +23,7 @@
|
|||||||
this.BackColor = System.Drawing.SystemColors.Control;
|
this.BackColor = System.Drawing.SystemColors.Control;
|
||||||
this.ClientSize = new System.Drawing.Size(284, 122);
|
this.ClientSize = new System.Drawing.Size(284, 122);
|
||||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
|
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
|
||||||
this.Location = TweetDck.Core.Controls.ControlExtensions.InvisibleLocation;
|
this.Location = TweetDuck.Controls.ControlExtensions.InvisibleLocation;
|
||||||
this.MaximizeBox = false;
|
this.MaximizeBox = false;
|
||||||
this.MinimizeBox = false;
|
this.MinimizeBox = false;
|
||||||
this.Name = "FormNotification";
|
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 TweetDck.Core.Notification {
|
namespace TweetDuck.Browser.Notification {
|
||||||
partial class FormNotificationMain {
|
partial class FormNotificationMain {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Required designer variable.
|
/// Required designer variable.
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
this.components = new System.ComponentModel.Container();
|
this.components = new System.ComponentModel.Container();
|
||||||
this.timerDisplayDelay = new System.Windows.Forms.Timer(this.components);
|
this.timerDisplayDelay = new System.Windows.Forms.Timer(this.components);
|
||||||
this.timerProgress = new System.Windows.Forms.Timer(this.components);
|
this.timerProgress = new System.Windows.Forms.Timer(this.components);
|
||||||
this.progressBarTimer = new TweetDck.Core.Controls.FlatProgressBar();
|
this.progressBarTimer = new TweetDuck.Controls.FlatProgressBar();
|
||||||
this.SuspendLayout();
|
this.SuspendLayout();
|
||||||
//
|
//
|
||||||
// timerDisplayDelay
|
// 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 TweetDck.Core.Notification {
|
namespace TweetDuck.Browser.Notification {
|
||||||
partial class FormNotificationTweet {
|
partial class FormNotificationTweet {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Required designer variable.
|
/// Required designer variable.
|
||||||
@@ -40,7 +40,6 @@ namespace TweetDck.Core.Notification {
|
|||||||
//
|
//
|
||||||
// FormNotificationTweet
|
// FormNotificationTweet
|
||||||
//
|
//
|
||||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormNotificationTweet_FormClosing);
|
|
||||||
this.ResumeLayout(true);
|
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 TweetDck.Core {
|
namespace TweetDuck.Browser {
|
||||||
partial class TrayIcon {
|
partial class TrayIcon {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Required designer variable.
|
/// Required designer variable.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private System.ComponentModel.IContainer components = null;
|
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
|
#region Component Designer generated code
|
||||||
|
|
||||||
/// <summary>
|
/// <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,34 +1,45 @@
|
|||||||
using System;
|
using System;
|
||||||
using TweetDck.Core.Utils;
|
using TweetLib.Utils.Collections;
|
||||||
|
|
||||||
namespace TweetDck.Configuration{
|
namespace TweetDuck.Configuration {
|
||||||
static class Arguments{
|
static class Arguments {
|
||||||
// public args
|
// public args
|
||||||
public const string ArgDataFolder = "-datafolder";
|
public const string ArgDataFolder = "-datafolder";
|
||||||
public const string ArgLocale = "-locale";
|
|
||||||
public const string ArgLogging = "-log";
|
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
|
// internal args
|
||||||
public const string ArgRestart = "-restart";
|
public const string ArgRestart = "-restart";
|
||||||
public const string ArgImportCookies = "-importcookies";
|
public const string ArgUpdated = "-updated";
|
||||||
|
|
||||||
// class data and methods
|
// class data and methods
|
||||||
private static readonly CommandLineArgs Current = CommandLineArgs.FromStringArray('-', Environment.GetCommandLineArgs());
|
private static readonly CommandLineArgs Current = CommandLineArgs.FromStringArray('-', Environment.GetCommandLineArgs());
|
||||||
|
|
||||||
public static bool HasFlag(string flag){
|
public static bool HasFlag(string flag) {
|
||||||
return Current.HasFlag(flag);
|
return Current.HasFlag(flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetValue(string key, string defaultValue){
|
public static string GetValue(string key) {
|
||||||
return Current.GetValue(key, defaultValue);
|
return Current.GetValue(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CommandLineArgs GetCurrentClean(){
|
public static CommandLineArgs GetCurrentClean() {
|
||||||
CommandLineArgs args = Current.Clone();
|
CommandLineArgs args = Current.Clone();
|
||||||
args.RemoveFlag(ArgRestart);
|
args.RemoveFlag(ArgRestart);
|
||||||
args.RemoveFlag(ArgImportCookies);
|
args.RemoveFlag(ArgUpdated);
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static CommandLineArgs GetCurrentForInstaller() {
|
||||||
|
CommandLineArgs args = GetCurrentClean();
|
||||||
|
args.AddFlag(ArgUpdated);
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetCurrentForInstallerCmd() {
|
||||||
|
return GetCurrentForInstaller().ToString().Replace("\"", "::");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,176 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using TweetDck.Core.Utils;
|
|
||||||
|
|
||||||
namespace TweetDck.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
69
Configuration/SystemConfig.cs
Normal file
69
Configuration/SystemConfig.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using TweetLib.Core;
|
||||||
|
using TweetLib.Core.Application;
|
||||||
|
using TweetLib.Core.Systems.Configuration;
|
||||||
|
|
||||||
|
namespace TweetDuck.Configuration {
|
||||||
|
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;
|
||||||
|
set => UpdatePropertyWithCallback(ref _hardwareAcceleration, value, App.ConfigManager.TriggerProgramRestartRequested);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnableTouchAdjustment {
|
||||||
|
get => _enableTouchAdjustment;
|
||||||
|
set => UpdatePropertyWithCallback(ref _enableTouchAdjustment, value, App.ConfigManager.TriggerProgramRestartRequested);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnableColorProfileDetection {
|
||||||
|
get => _enableColorProfileDetection;
|
||||||
|
set => UpdatePropertyWithCallback(ref _enableColorProfileDetection, value, App.ConfigManager.TriggerProgramRestartRequested);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool UseSystemProxyForAllConnections {
|
||||||
|
get => _useSystemProxyForAllConnections;
|
||||||
|
set => UpdatePropertyWithCallback(ref _useSystemProxyForAllConnections, value, App.ConfigManager.TriggerProgramRestartRequested);
|
||||||
|
}
|
||||||
|
|
||||||
|
// END OF CONFIG
|
||||||
|
|
||||||
|
#pragma warning disable CS0618
|
||||||
|
|
||||||
|
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 hasChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning restore CS0618
|
||||||
|
|
||||||
|
public override SystemConfig ConstructWithDefaults() {
|
||||||
|
return new SystemConfig {
|
||||||
|
MigrationVersion = 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,250 +1,167 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.IO;
|
using TweetDuck.Browser;
|
||||||
using System.Runtime.Serialization;
|
using TweetDuck.Controls;
|
||||||
using System.Runtime.Serialization.Formatters.Binary;
|
using TweetLib.Core;
|
||||||
using TweetDck.Core;
|
using TweetLib.Core.Application;
|
||||||
using TweetDck.Core.Controls;
|
using TweetLib.Core.Features.Notifications;
|
||||||
using TweetDck.Core.Notification;
|
using TweetLib.Core.Features.Twitter;
|
||||||
using TweetDck.Core.Utils;
|
using TweetLib.Core.Systems.Configuration;
|
||||||
using TweetDck.Plugins;
|
using TweetLib.Utils.Data;
|
||||||
|
|
||||||
namespace TweetDck.Configuration{
|
namespace TweetDuck.Configuration {
|
||||||
[Serializable]
|
sealed class UserConfig : BaseConfig<UserConfig>, IAppUserConfiguration {
|
||||||
sealed class UserConfig{
|
public bool FirstRun { get; set; } = true;
|
||||||
private static readonly IFormatter Formatter = new BinaryFormatter();
|
|
||||||
|
|
||||||
private const int CurrentFileVersion = 9;
|
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||||
|
public bool AllowDataCollection { get; set; } = false;
|
||||||
|
|
||||||
// START OF CONFIGURATION
|
public WindowState BrowserWindow { get; set; } = new WindowState();
|
||||||
|
public Size PluginsWindowSize { get; set; } = Size.Empty;
|
||||||
|
|
||||||
public WindowState BrowserWindow { get; set; }
|
public bool ExpandLinksOnHover { get; set; } = true;
|
||||||
public bool DisplayNotificationColumn { get; set; }
|
public bool FocusDmInput { get; set; } = true;
|
||||||
public bool DisplayNotificationTimer { get; set; }
|
public bool OpenSearchInFirstColumn { get; set; } = true;
|
||||||
public bool NotificationTimerCountDown { get; set; }
|
public bool KeepLikeFollowDialogsOpen { get; set; } = true;
|
||||||
public bool NotificationSkipOnLinkClick { get; set; }
|
public bool BestImageQuality { get; set; } = true;
|
||||||
public bool NotificationNonIntrusiveMode { get; set; }
|
public bool EnableAnimatedImages { get; set; } = true;
|
||||||
|
public bool HideTweetsByNftUsers { get; set; } = false;
|
||||||
|
|
||||||
public TweetNotification.Position NotificationPosition { get; set; }
|
private bool _enableSmoothScrolling = true;
|
||||||
public Point CustomNotificationPosition { get; set; }
|
private string _customCefArgs = null;
|
||||||
public int NotificationEdgeDistance { get; set; }
|
|
||||||
public int NotificationDisplay { get; set; }
|
|
||||||
public int NotificationIdlePauseSeconds { get; set; }
|
|
||||||
public int NotificationDurationValue { get; set; }
|
|
||||||
|
|
||||||
public bool EnableSpellCheck { get; set; }
|
public string BrowserPath { get; set; } = null;
|
||||||
public bool ExpandLinksOnHover { get; set; }
|
public string BrowserPathArgs { get; set; } = null;
|
||||||
public bool SwitchAccountSelectors { get; set; }
|
public bool IgnoreTrackingUrlWarning { get; set; } = false;
|
||||||
public bool EnableTrayHighlight { get; set; }
|
public string SearchEngineUrl { get; set; } = null;
|
||||||
|
private int _zoomLevel = 100;
|
||||||
|
|
||||||
public bool EnableUpdateCheck { get; set; }
|
public string VideoPlayerPath { get; set; } = null;
|
||||||
public string DismissedUpdate { get; set; }
|
public string VideoPlayerPathArgs { get; set; } = null;
|
||||||
|
public int VideoPlayerVolume { get; set; } = 50;
|
||||||
|
|
||||||
[Obsolete] public PluginConfig Plugins { get; set; } // TODO remove eventually
|
public bool EnableSpellCheck { get; set; } = false;
|
||||||
public WindowState PluginsWindow { get; set; }
|
private string _spellCheckLanguage = "en-US";
|
||||||
|
|
||||||
public string CustomCefArgs { get; set; }
|
public string TranslationTarget { get; set; } = "en";
|
||||||
public string CustomBrowserCSS { get; set; }
|
public int CalendarFirstDay { get; set; } = -1;
|
||||||
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 IsCustomNotificationPositionSet => CustomNotificationPosition != ControlExtensions.InvisibleLocation;
|
||||||
|
public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty;
|
||||||
|
public bool IsCustomSoundNotificationSet => NotificationSoundPath != string.Empty;
|
||||||
|
|
||||||
public string NotificationSoundPath{
|
public ImageQuality TwitterImageQuality => BestImageQuality ? ImageQuality.Best : ImageQuality.Default;
|
||||||
get => string.IsNullOrEmpty(notificationSoundPath) ? string.Empty : notificationSoundPath;
|
|
||||||
set => notificationSoundPath = value;
|
public string NotificationSoundPath {
|
||||||
|
get => _notificationSoundPath ?? string.Empty;
|
||||||
|
set => UpdatePropertyWithEvent(ref _notificationSoundPath, value, SoundNotificationChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool MuteNotifications{
|
public int NotificationSoundVolume {
|
||||||
get => muteNotifications;
|
get => _notificationSoundVolume;
|
||||||
|
set => UpdatePropertyWithEvent(ref _notificationSoundVolume, value, SoundNotificationChanged);
|
||||||
set{
|
|
||||||
if (muteNotifications != value){
|
|
||||||
muteNotifications = value;
|
|
||||||
MuteToggled?.Invoke(this, new EventArgs());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int ZoomLevel{
|
public bool MuteNotifications {
|
||||||
get => zoomLevel;
|
get => _muteNotifications;
|
||||||
|
set => UpdatePropertyWithEvent(ref _muteNotifications, value, MuteToggled);
|
||||||
set{
|
|
||||||
if (zoomLevel != value){
|
|
||||||
zoomLevel = value;
|
|
||||||
ZoomLevelChanged?.Invoke(this, new EventArgs());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public double ZoomMultiplier => zoomLevel/100.0;
|
public int ZoomLevel {
|
||||||
|
get => _zoomLevel;
|
||||||
public TrayIcon.Behavior TrayBehavior{
|
set => UpdatePropertyWithEvent(ref _zoomLevel, value, ZoomLevelChanged);
|
||||||
get => trayBehavior;
|
|
||||||
|
|
||||||
set{
|
|
||||||
if (trayBehavior != value){
|
|
||||||
trayBehavior = value;
|
|
||||||
TrayBehaviorChanged?.Invoke(this, new EventArgs());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// END OF CONFIGURATION
|
public TrayIcon.Behavior TrayBehavior {
|
||||||
|
get => _trayBehavior;
|
||||||
|
set => UpdatePropertyWithEvent(ref _trayBehavior, value, TrayBehaviorChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
public event EventHandler MuteToggled;
|
||||||
|
|
||||||
[field:NonSerialized]
|
|
||||||
public event EventHandler ZoomLevelChanged;
|
public event EventHandler ZoomLevelChanged;
|
||||||
|
|
||||||
[field:NonSerialized]
|
|
||||||
public event EventHandler TrayBehaviorChanged;
|
public event EventHandler TrayBehaviorChanged;
|
||||||
|
public event EventHandler SoundNotificationChanged;
|
||||||
|
public event EventHandler OptionsDialogClosed;
|
||||||
|
|
||||||
[NonSerialized]
|
public void TriggerOptionsDialogClosed() {
|
||||||
private string file;
|
OptionsDialogClosed?.Invoke(this, EventArgs.Empty);
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpgradeFile(){
|
// END OF CONFIG
|
||||||
if (fileVersion == CurrentFileVersion){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if outdated, cycle through all versions
|
public override UserConfig ConstructWithDefaults() {
|
||||||
if (fileVersion == 0){
|
return new UserConfig();
|
||||||
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, you may lose your settings and list of enabled plugins.", 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";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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 TweetDck.Core.Controls;
|
|
||||||
|
|
||||||
namespace TweetDck.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 TweetDck.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,107 +0,0 @@
|
|||||||
using System.Windows.Forms;
|
|
||||||
using TweetDck.Core.Controls;
|
|
||||||
using TweetDck.Core.Notification;
|
|
||||||
using TweetDck.Core.Utils;
|
|
||||||
|
|
||||||
namespace TweetDck.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 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 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 TweetDck.Core.Utils;
|
|
||||||
|
|
||||||
namespace TweetDck.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 TweetDck.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 TweetDck.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 TweetDck.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 TweetDck.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 TweetDck.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,457 +0,0 @@
|
|||||||
using CefSharp;
|
|
||||||
using CefSharp.WinForms;
|
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using TweetDck.Configuration;
|
|
||||||
using TweetDck.Core.Bridge;
|
|
||||||
using TweetDck.Core.Controls;
|
|
||||||
using TweetDck.Core.Handling;
|
|
||||||
using TweetDck.Core.Notification;
|
|
||||||
using TweetDck.Core.Notification.Screenshot;
|
|
||||||
using TweetDck.Core.Notification.Sound;
|
|
||||||
using TweetDck.Core.Other;
|
|
||||||
using TweetDck.Core.Utils;
|
|
||||||
using TweetDck.Plugins;
|
|
||||||
using TweetDck.Plugins.Enums;
|
|
||||||
using TweetDck.Plugins.Events;
|
|
||||||
using TweetDck.Resources;
|
|
||||||
using TweetDck.Updates;
|
|
||||||
using TweetDck.Updates.Events;
|
|
||||||
|
|
||||||
namespace TweetDck.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 ISoundNotificationPlayer 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()
|
|
||||||
};
|
|
||||||
|
|
||||||
#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 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){
|
|
||||||
Hide();
|
|
||||||
|
|
||||||
FormUpdateDownload downloadForm = new FormUpdateDownload(e.UpdateInfo);
|
|
||||||
downloadForm.MoveToCenter(this);
|
|
||||||
downloadForm.ShowDialog();
|
|
||||||
downloadForm.Dispose();
|
|
||||||
|
|
||||||
if (downloadForm.UpdateStatus == FormUpdateDownload.Status.Succeeded){
|
|
||||||
UpdateInstallerPath = downloadForm.InstallerPath;
|
|
||||||
ForceClose();
|
|
||||||
}
|
|
||||||
else if (downloadForm.UpdateStatus == FormUpdateDownload.Status.Manual){
|
|
||||||
ForceClose();
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
Show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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("Open Settings");
|
|
||||||
btnOpenSettings.Width += 16;
|
|
||||||
btnOpenSettings.Location = new Point(btnOpenSettings.Location.X-16, btnOpenSettings.Location.Y);
|
|
||||||
|
|
||||||
if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnOpenSettings){
|
|
||||||
OpenSettings(FormSettings.TabIndexNotification);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = SoundNotification.New();
|
|
||||||
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 TweetDck.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,152 +0,0 @@
|
|||||||
using CefSharp;
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using TweetDck.Core.Bridge;
|
|
||||||
using TweetDck.Core.Controls;
|
|
||||||
using TweetDck.Core.Utils;
|
|
||||||
|
|
||||||
namespace TweetDck.Core.Handling{
|
|
||||||
abstract class ContextMenuBase : IContextMenuHandler{
|
|
||||||
private static readonly Regex RegexTwitterAccount = new Regex(@"^https?://twitter\.com/([^/]+)/?$", RegexOptions.Compiled);
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
private const int MenuOpenDevTools = 26599;
|
|
||||||
|
|
||||||
protected void AddDebugMenuItems(IMenuModel model){
|
|
||||||
model.AddItem((CefMenuCommand)MenuOpenDevTools, "Open dev tools");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
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.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, 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.Match(parameters.UnfilteredLinkUrl);
|
|
||||||
SetClipboardText(match.Success ? match.Groups[1].Value : parameters.UnfilteredLinkUrl);
|
|
||||||
break;
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
case MenuOpenDevTools:
|
|
||||||
browserControl.ShowDevTools();
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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 TweetDck.Core.Bridge;
|
|
||||||
using TweetDck.Core.Controls;
|
|
||||||
using TweetDck.Core.Utils;
|
|
||||||
|
|
||||||
namespace TweetDck.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 = "Settings";
|
|
||||||
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 DEBUG
|
|
||||||
globalMenu.AddSeparator();
|
|
||||||
AddDebugMenuItems(globalMenu);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
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 TweetDck.Core.Controls;
|
|
||||||
using TweetDck.Core.Notification;
|
|
||||||
|
|
||||||
namespace TweetDck.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 DEBUG
|
|
||||||
AddDebugMenuItems(model);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
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 TweetDck.Core.Controls;
|
|
||||||
using TweetDck.Core.Other;
|
|
||||||
|
|
||||||
namespace TweetDck.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 TweetDck.Core.Utils;
|
|
||||||
|
|
||||||
namespace TweetDck.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,63 +0,0 @@
|
|||||||
using CefSharp;
|
|
||||||
using System.Collections.Specialized;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace TweetDck.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,208 +0,0 @@
|
|||||||
using CefSharp;
|
|
||||||
using CefSharp.WinForms;
|
|
||||||
using System;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using TweetDck.Configuration;
|
|
||||||
using TweetDck.Core.Controls;
|
|
||||||
using TweetDck.Core.Handling;
|
|
||||||
using TweetDck.Core.Utils;
|
|
||||||
|
|
||||||
namespace TweetDck.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (WindowsUtils.ShouldAvoidToolWindow){
|
|
||||||
FormBorderStyle = FormBorderStyle.FixedSingle;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 TweetDck.Core.Bridge;
|
|
||||||
using TweetDck.Core.Controls;
|
|
||||||
using TweetDck.Core.Utils;
|
|
||||||
using TweetDck.Plugins;
|
|
||||||
using TweetDck.Plugins.Enums;
|
|
||||||
using TweetDck.Resources;
|
|
||||||
|
|
||||||
namespace TweetDck.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 TweetDck.Plugins;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using TweetDck.Core.Utils;
|
|
||||||
|
|
||||||
namespace TweetDck.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 TweetDck.Core.Bridge;
|
|
||||||
using TweetDck.Core.Utils;
|
|
||||||
using TweetDck.Resources;
|
|
||||||
|
|
||||||
namespace TweetDck.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 TweetDck.Core.Controls;
|
|
||||||
|
|
||||||
namespace TweetDck.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,12 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace TweetDck.Core.Notification.Sound{
|
|
||||||
interface ISoundNotificationPlayer : IDisposable{
|
|
||||||
string SupportedFormats { get; }
|
|
||||||
|
|
||||||
event EventHandler<PlaybackErrorEventArgs> PlaybackError;
|
|
||||||
|
|
||||||
void Play(string file);
|
|
||||||
void Stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace TweetDck.Core.Notification.Sound{
|
|
||||||
sealed class PlaybackErrorEventArgs : EventArgs{
|
|
||||||
public string Message { get; }
|
|
||||||
public bool Ignore { get; set; }
|
|
||||||
|
|
||||||
public PlaybackErrorEventArgs(string message){
|
|
||||||
this.Message = message;
|
|
||||||
this.Ignore = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Media;
|
|
||||||
|
|
||||||
namespace TweetDck.Core.Notification.Sound{
|
|
||||||
sealed class SoundPlayerImplFallback : ISoundNotificationPlayer{
|
|
||||||
string ISoundNotificationPlayer.SupportedFormats => "*.wav";
|
|
||||||
|
|
||||||
public event EventHandler<PlaybackErrorEventArgs> PlaybackError;
|
|
||||||
|
|
||||||
private readonly SoundPlayer player;
|
|
||||||
private bool ignorePlaybackError;
|
|
||||||
|
|
||||||
public SoundPlayerImplFallback(){
|
|
||||||
player = new SoundPlayer{
|
|
||||||
LoadTimeout = 5000
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void ISoundNotificationPlayer.Play(string file){
|
|
||||||
if (player.SoundLocation != file){
|
|
||||||
player.SoundLocation = file;
|
|
||||||
ignorePlaybackError = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
|
||||||
player.Play();
|
|
||||||
}catch(FileNotFoundException e){
|
|
||||||
OnNotificationSoundError("File not found: "+e.FileName);
|
|
||||||
}catch(InvalidOperationException){
|
|
||||||
OnNotificationSoundError("File format was not recognized.");
|
|
||||||
}catch(TimeoutException){
|
|
||||||
OnNotificationSoundError("File took too long to load.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ISoundNotificationPlayer.Stop(){
|
|
||||||
player.Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void IDisposable.Dispose(){
|
|
||||||
player.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnNotificationSoundError(string message){
|
|
||||||
if (!ignorePlaybackError && PlaybackError != null){
|
|
||||||
PlaybackErrorEventArgs args = new PlaybackErrorEventArgs(message);
|
|
||||||
PlaybackError(this, args);
|
|
||||||
ignorePlaybackError = args.Ignore;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using TweetDck.Core.Utils;
|
|
||||||
using WMPLib;
|
|
||||||
|
|
||||||
namespace TweetDck.Core.Notification.Sound{
|
|
||||||
sealed class SoundPlayerImplWMP : ISoundNotificationPlayer{
|
|
||||||
string ISoundNotificationPlayer.SupportedFormats => "*.wav;*.mp3;*.mp2;*.m4a;*.mid;*.midi;*.rmi;*.wma;*.aif;*.aifc;*.aiff;*.snd;*.au";
|
|
||||||
|
|
||||||
public event EventHandler<PlaybackErrorEventArgs> PlaybackError;
|
|
||||||
|
|
||||||
private readonly WindowsMediaPlayer player;
|
|
||||||
private bool wasTryingToPlay;
|
|
||||||
private bool ignorePlaybackError;
|
|
||||||
|
|
||||||
// changing the player volume also affects the value in the Windows mixer
|
|
||||||
// however, the initial value is always 50 or some other stupid shit
|
|
||||||
// so we have to tell the player to set its volume to whatever the mixer is set to
|
|
||||||
// using the most code required for the least functionality with a sorry excuse for an API
|
|
||||||
// thanks, Microsoft
|
|
||||||
|
|
||||||
public SoundPlayerImplWMP(){
|
|
||||||
player = new WindowsMediaPlayer();
|
|
||||||
player.settings.autoStart = false;
|
|
||||||
player.settings.enableErrorDialogs = false;
|
|
||||||
player.settings.invokeURLs = false;
|
|
||||||
player.settings.volume = (int)Math.Floor(100.0*NativeCoreAudio.GetMixerVolumeForCurrentProcess());
|
|
||||||
player.MediaChange += player_MediaChange;
|
|
||||||
player.MediaError += player_MediaError;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ISoundNotificationPlayer.Play(string file){
|
|
||||||
wasTryingToPlay = true;
|
|
||||||
|
|
||||||
try{
|
|
||||||
if (player.URL != file){
|
|
||||||
player.close();
|
|
||||||
player.URL = file;
|
|
||||||
ignorePlaybackError = false;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
player.controls.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
player.controls.play();
|
|
||||||
}catch(Exception e){
|
|
||||||
OnNotificationSoundError("An error occurred in Windows Media Player: "+e.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ISoundNotificationPlayer.Stop(){
|
|
||||||
try{
|
|
||||||
player.controls.stop();
|
|
||||||
}catch{
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IDisposable.Dispose(){
|
|
||||||
player.close();
|
|
||||||
Marshal.ReleaseComObject(player);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void player_MediaChange(object item){
|
|
||||||
IWMPMedia2 media = item as IWMPMedia2;
|
|
||||||
|
|
||||||
if (media == null){
|
|
||||||
OnNotificationSoundError("Unknown error.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
|
||||||
else if (media.Error == null && media.duration == 0.0){
|
|
||||||
OnNotificationSoundError("File does not contain an audio track.");
|
|
||||||
}
|
|
||||||
else if (media.Error != null){
|
|
||||||
OnNotificationSoundError(media.Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
Marshal.ReleaseComObject(media);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void player_MediaError(object pMediaObject){
|
|
||||||
IWMPMedia2 media = pMediaObject as IWMPMedia2;
|
|
||||||
|
|
||||||
if (media == null){
|
|
||||||
OnNotificationSoundError("Unknown error.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (media.Error != null){
|
|
||||||
OnNotificationSoundError(media.Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
Marshal.ReleaseComObject(media);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnNotificationSoundError(IWMPErrorItem error){
|
|
||||||
OnNotificationSoundError(error.errorCode == -1072885353 ? "Invalid media file." : error.errorDescription);
|
|
||||||
Marshal.ReleaseComObject(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnNotificationSoundError(string message){
|
|
||||||
if (wasTryingToPlay){
|
|
||||||
wasTryingToPlay = false;
|
|
||||||
|
|
||||||
if (!ignorePlaybackError && PlaybackError != null){
|
|
||||||
PlaybackErrorEventArgs args = new PlaybackErrorEventArgs(message);
|
|
||||||
PlaybackError(this, args);
|
|
||||||
ignorePlaybackError = args.Ignore;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
using System.Runtime.InteropServices;
|
|
||||||
using TweetDck.Core.Notification.Sound;
|
|
||||||
|
|
||||||
namespace TweetDck.Core.Notification{
|
|
||||||
static class SoundNotification{
|
|
||||||
private static bool? IsWMPAvailable;
|
|
||||||
|
|
||||||
public static ISoundNotificationPlayer New(){
|
|
||||||
if (IsWMPAvailable.HasValue){
|
|
||||||
if (IsWMPAvailable.Value){
|
|
||||||
return new SoundPlayerImplWMP();
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
return new SoundPlayerImplFallback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
|
||||||
SoundPlayerImplWMP implWMP = new SoundPlayerImplWMP();
|
|
||||||
IsWMPAvailable = true;
|
|
||||||
return implWMP;
|
|
||||||
}catch(COMException){
|
|
||||||
IsWMPAvailable = false;
|
|
||||||
return new SoundPlayerImplFallback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Text;
|
|
||||||
using TweetDck.Resources;
|
|
||||||
|
|
||||||
namespace TweetDck.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 => column;
|
|
||||||
public string TweetUrl => tweetUrl;
|
|
||||||
public string QuoteUrl => quoteUrl;
|
|
||||||
|
|
||||||
private readonly string column;
|
|
||||||
private readonly string html;
|
|
||||||
private readonly int characters;
|
|
||||||
private readonly string tweetUrl;
|
|
||||||
private readonly string quoteUrl;
|
|
||||||
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.html = html;
|
|
||||||
this.tweetUrl = tweetUrl;
|
|
||||||
this.quoteUrl = quoteUrl;
|
|
||||||
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 TweetDck.Core.Utils;
|
|
||||||
|
|
||||||
namespace TweetDck.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 TweetDck.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 TweetDck.Core.Controls;
|
|
||||||
using TweetDck.Plugins;
|
|
||||||
using TweetDck.Plugins.Controls;
|
|
||||||
using TweetDck.Plugins.Enums;
|
|
||||||
using TweetDck.Plugins.Events;
|
|
||||||
|
|
||||||
namespace TweetDck.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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
90
Core/Other/FormSettings.Designer.cs
generated
90
Core/Other/FormSettings.Designer.cs
generated
@@ -1,90 +0,0 @@
|
|||||||
namespace TweetDck.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 TweetDck.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::TweetDck.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 TweetDck.Core.Other.Settings;
|
|
||||||
using TweetDck.Plugins;
|
|
||||||
using TweetDck.Updates;
|
|
||||||
|
|
||||||
namespace TweetDck.Core.Other{
|
|
||||||
sealed partial class FormSettings : Form{
|
|
||||||
public const int TabIndexNotification = 1;
|
|
||||||
|
|
||||||
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+" Settings";
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user