mirror of
https://github.com/chylex/TweetDuck.git
synced 2026-02-18 17:46:36 +01:00
Compare commits
1547 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| a7229a0677 | |||
| d0dd112b98 | |||
| 1e07120eb5 | |||
| fcd1f76cff | |||
| e400d86d75 | |||
| dca3410a5b | |||
| 9b314e2953 | |||
| 5635daf66d | |||
| 7e2e1645e9 | |||
| 4990afcdbb | |||
| c11f36dfef | |||
| abddf61c88 | |||
| 37fec7e952 | |||
| 0f6a93ae8f | |||
| 25eae334b0 | |||
| 74377d01ce | |||
| 6e78ba1e7b | |||
| 39e0dedf27 | |||
| bbe2c88802 | |||
| 586b31b63e | |||
| 041abe6d7e | |||
| a69b3cd05f | |||
| b48213e79e | |||
| 5bbc14aca5 | |||
| 8ccbf502e8 | |||
| c426ca97e2 | |||
| d9eef86a8b | |||
| 3fed921748 | |||
| 02827d53a2 | |||
| 1ad5fde9ae | |||
| 909d5ed99c | |||
| b9096df218 | |||
| 1137485c55 | |||
| 61b1155a03 | |||
| 35624bcb1c | |||
| 6e262334ed | |||
| 829d69485a | |||
| 0d32c168eb | |||
| 38d6d93f65 | |||
| 9a280492c2 | |||
| d49ee79c90 | |||
| 73b83af6b1 | |||
| 064673ef23 | |||
| 81bf93e5ab | |||
| 26d5a8ce08 | |||
| c6f5c8d91f | |||
| 63a1928468 | |||
| 27c2aee8b9 | |||
| 5219d29aca | |||
| 84955352dd | |||
| 8e05c30063 | |||
| 6f98bcafec | |||
| 4afac91bce | |||
| 6e6312f6d7 | |||
| df4c4e443d | |||
| ff40474f92 | |||
| aca06ee805 | |||
| ba8e29a9f8 | |||
| 583da2bd9f | |||
| 0ea07016b4 | |||
| 616421db9a | |||
| 293ddacd19 | |||
| 13945ec937 | |||
| 4ea6f336f8 | |||
| 58296aa266 | |||
| 742df9dff3 | |||
| 011e1f5922 | |||
| 8f67d69325 | |||
| 9ac133b605 | |||
| 538b2d26cd | |||
| cc3895c423 | |||
| 79454bfc3b | |||
| 734c7572bb | |||
| 9f93fbb161 | |||
| c78c63285e | |||
| c8cbf70a28 | |||
| 2b116d6756 | |||
| da611153cf | |||
| fd9bf4468a | |||
| fa234eb9d6 | |||
| dbfebf6a32 | |||
| 7b91e31485 | |||
| e882fc8b5e | |||
| 5a54195cac | |||
| a442adf8d5 | |||
| 38466878db | |||
| 7c86e4e743 | |||
| 41cbfb8d39 | |||
| 7c394f4b20 | |||
| c1a35e1053 | |||
| cddce8596f | |||
| c75058b1da | |||
| 1a73fcdb39 | |||
| 8e0c4f5308 | |||
| 51e2791cc7 | |||
| 130159f06c | |||
| 42d1140b55 | |||
| dfd987041a | |||
| 790d1787fd | |||
| 2a6a607c7b | |||
| b3521d2a18 | |||
| dee99caa7d | |||
| cf525a3929 | |||
| 18d658f7e1 | |||
| 1c42ab77d8 | |||
| 33d5638bb0 | |||
| 8ce92df87a | |||
| 80654449af | |||
| eee1622801 | |||
| 4c54876ecf | |||
| 1cbcd5c2da | |||
| 55253e284e | |||
| e9795cd697 | |||
| be76d9a6dc | |||
| d7cdaf2870 | |||
| 48ed0e01d1 | |||
| dca31dedde | |||
| ab7356b991 | |||
| a46a673cf8 | |||
| c8d52539ca | |||
| ed9267b6ba | |||
| b7c02d1cf8 | |||
| fb66beb29f | |||
| 8c3bf6bbc3 | |||
| b943078132 | |||
| e727617bf1 | |||
| ffaea6dcbe | |||
| bf1f72a2a4 | |||
| be0df7c5b0 | |||
| 5ab769e74d | |||
| d22ddb1731 | |||
| 9fed8c022b | |||
| a315ed90af | |||
| 1ebf3c9af2 | |||
| 0e4c923c23 | |||
| 63835b9f99 | |||
| ff17f7c132 | |||
| f4631c9b38 | |||
| 35931023ae | |||
| 6d93381760 | |||
| f1bdd5f1b2 | |||
| 1c3e2fbad7 | |||
| 29a02db07d | |||
| f1db1ba708 | |||
| c1420bac88 | |||
| 8e527fbbdf | |||
| 5ec1bcfe3f | |||
| 7226461cd0 | |||
| 6d6f383c92 | |||
| e1a6328d09 | |||
| 6e4153911a | |||
| 342f74646e | |||
| fe5191d3b5 | |||
| 504cf97c6c | |||
| f8c494c9c1 | |||
| b90d7f721a | |||
| 7936af6c9a | |||
| 52d01e3dd7 | |||
| d30d70395a | |||
| 491a3ae525 | |||
| 1eae380b08 | |||
| f091b2526e | |||
| 7548e2e202 | |||
| d9b9afbf2d | |||
| 8036659003 | |||
| f9fb4668c2 | |||
| 7047924947 | |||
| 8f6be3911a | |||
| 0d95b8eb44 | |||
| 61d2d124ff | |||
| 7b218b2544 | |||
| 87ac7daf76 | |||
| 6b4817df36 | |||
| 22d99da2e1 | |||
| 47b6cf7068 | |||
| 606c9512f8 | |||
| aef9c591e9 | |||
| 71f67e9191 | |||
| 636f2b3017 | |||
| 2de5b5c6e4 | |||
| 0cbcc8c9f3 | |||
| 96146e3dc8 | |||
| 5aaae51be1 | |||
| b98625fdbc | |||
| 09a748e9dc | |||
| 87b07c6d5b | |||
| f39e668f8d | |||
| 6ea95342a0 | |||
| c594bf5757 | |||
| cd3b198c6f | |||
| b249b5f46e | |||
| bbe3b48bcc | |||
| 3bcd056197 | |||
| 6387ab41b3 | |||
| 4df16b7f15 | |||
| ed387a2873 | |||
| 9e225530a6 | |||
| 7b23686dc6 | |||
| 4de31453fd | |||
| 4c59526e39 | |||
| 9ec1764194 | |||
| 47afa32902 | |||
| 2a09487b55 | |||
| 563c856dd3 | |||
| 69ea242408 | |||
| d6e0e0726f | |||
| 73d460d40a | |||
| 1f27d96ac9 | |||
| 93e9f28d69 | |||
| ec2e26752a | |||
| fadd95f3e6 | |||
| 00acc677e6 | |||
| 1a799881e8 | |||
| f75677593a | |||
| 19e3bd19f0 | |||
| 85701b0a3c | |||
| 014cb18dcb | |||
| e71e1c853f | |||
| ee9d9196f5 | |||
| 53c8272e01 | |||
| 7f7b6b1e2a | |||
| 405777e0f5 | |||
| df2b624cb5 | |||
| 8a48d5c2f9 | |||
| c55ee71442 | |||
| 3f82745f5b | |||
| 404187a1ae | |||
| 2b7b3f586b | |||
| 04959a3493 | |||
| 97cf4932ae | |||
| b0d88a0a37 | |||
| 67a2e40622 | |||
| 3a28556c7f | |||
| 9ecc92b9a5 | |||
| ca023be98a | |||
| 11a1423f76 | |||
| 79f6df121b | |||
| 71eade7e86 | |||
| 5f81d29036 | |||
| ec1cb5dc5f | |||
| fd969e2d55 | |||
| 37e33b77ff | |||
| f7ed7703b4 | |||
| 4bb35295ca | |||
| 1e4f673f9e | |||
| 7cadb1c403 | |||
| 37148f5093 | |||
| f6bc26789f | |||
| b3f5a88525 | |||
| 1e538d2b28 | |||
| 7d7bfb7b01 | |||
| 41d86ba440 | |||
| 3df474a8a5 | |||
| a50d6e8f47 | |||
| 6081e5b9c1 | |||
| 66ccea920c | |||
| 470d63093f | |||
| eae0507831 | |||
| 92af85d3bb | |||
| 7635af5730 | |||
| a838e89695 | |||
| b22289a8b9 | |||
| 45b3ff52c6 | |||
| 4464991f4c | |||
| b0d2f77583 | |||
| b211a4405d | |||
| 8823016d2c | |||
| 859fdc7ec1 | |||
| 028d5ed01f | |||
| 5fd5a2a436 | |||
| 79a7e7470c | |||
| 9ecef78aed | |||
| 65a837a6e1 | |||
| 6e4db4acea | |||
| 26fb977d05 | |||
| b42cd1c048 | |||
| 467f7cd12f | |||
| 66699ce9df | |||
| cf7d903932 | |||
| a7ab67925c | |||
| a474ba4260 | |||
| 09e5636e86 | |||
| 2295a875be | |||
| 82a2455afc | |||
| 268de676ee | |||
| 8fe26c07f1 | |||
| da3921b1ca | |||
| 4dd2e787d1 | |||
| ce005ae6c2 | |||
| 1513f46a11 | |||
| 7543eeb0f4 | |||
| 873242120c | |||
| 98f8095a65 | |||
| 785571a550 | |||
| 0c4bd4044e | |||
| 0319543dce | |||
| 82d70b2d7f | |||
| 62d18e010a | |||
| fc77b85083 | |||
| 50a8893f4f | |||
| 9252b3040e | |||
| d5141ed020 | |||
| 7ff9e23283 | |||
| 89854d527c | |||
| 6ff0cad2a8 | |||
| 349cfbd2d5 | |||
| 40303ef74a | |||
| 6c652122c2 | |||
| 3658e3a2aa | |||
| 2b20fcfcd1 | |||
| 554d427fef | |||
| 7cf5b23306 | |||
| b26a6098eb | |||
| 7ad927bdaf | |||
| 4ed30b3619 | |||
| edfa9264d5 | |||
| f7516b593f | |||
| 83ff998f9d | |||
| 47381e0df4 | |||
| ba62d57485 | |||
| c014c4bc24 | |||
| 5d1a3fede2 | |||
| 53b584fe45 | |||
| f53d974400 | |||
| c4b4ef19cd | |||
| 3bfc360362 | |||
| 584f16d375 | |||
| b3d84c3217 | |||
| dd14ad470e | |||
| 85b90574b8 | |||
| ee5d172468 | |||
| 7ca4b94361 | |||
| 31f1546483 | |||
| d8a88a19af | |||
| 12af79de05 | |||
| 2260dd419d | |||
| 61a940cc82 | |||
| 1bbc1e0d7e | |||
| 921294eeb3 | |||
| baaa90f49d | |||
| 4e25381770 | |||
| 272877d0ed | |||
| 555b947bf7 | |||
| da29811b16 | |||
| 241f67fd4d | |||
| eb4ce18e31 | |||
| ae99fee440 | |||
| d116ac5e56 | |||
| 28db1f4253 | |||
| 034312e676 | |||
| da83d73ba6 | |||
| 02e8dc3440 | |||
| cac6d1f889 | |||
| 68fa3294d4 | |||
| 9b983de8c9 | |||
| 3a37ee719b | |||
| 61359c2faa | |||
| 7f7c5ab35b | |||
| a1b483d20a | |||
| 04cd662d78 | |||
| da597f076f | |||
| fab3efdcf5 | |||
| a55509a34d | |||
| 84fb1c5b2b | |||
| 391a90e1df | |||
| e0fe39195d | |||
| 385fead81a | |||
| 648d1b9aa9 | |||
| 3f0028913d | |||
| 45e6ec8b0f | |||
| a3fbaa0b34 | |||
| 7102cbfb3b | |||
| c3db3ce0f2 | |||
| 7a1e7637ff | |||
| 04a78a02d3 | |||
| cb61dc742f | |||
| cd53f6e757 | |||
| c64f7daa8d | |||
| e70d792654 | |||
| 9ae533f907 | |||
| cfe92f18e3 | |||
| e2a34ea28e | |||
| ec8000360e | |||
| 57b0821e19 | |||
| 09d39df15a | |||
| 1f9db3bda6 | |||
| b7104c8828 | |||
| 5da02b4092 | |||
| 802f1e3042 | |||
| 66db0df45a | |||
| 650c2e2eb7 | |||
| 6ab3754129 | |||
| 7dca0a8cab | |||
| 7cd0b4ad54 | |||
| 97acb41eee | |||
| b916b9726e | |||
| 32d3990ace | |||
| cb1fd109cc | |||
| 0e68dd6185 | |||
| fb6502bc65 | |||
| c7e7403781 | |||
| bf224408a3 | |||
| 84b7078873 | |||
| 89e5943d8f | |||
| b78c4cb8f0 | |||
| 976ba074a8 | |||
| 5205d59b96 | |||
| e8394b9c08 | |||
| 9cd00239f9 | |||
| b6b26142f8 | |||
| 4ee99376fd | |||
| b0261342ff | |||
| 87fd2a521e | |||
| 334793c6f6 | |||
| 3e70d991bb | |||
| feec96fc5c | |||
| 765984709e | |||
| c7c9931f68 | |||
| ae64573510 | |||
| d675af5aa4 | |||
| 9480d17cfc | |||
| 5ac1df2283 | |||
| 20119db883 | |||
| a4006deb8c | |||
| 25fa3cefab | |||
| bb5161eb34 | |||
| 1bfc403a98 | |||
| 720d10e543 | |||
| 30c117672e | |||
| 6d1b5c77d1 | |||
| d1cbf608e0 | |||
| 7e3014c52d | |||
| 82beb1f5a7 | |||
| 657dc81300 | |||
| 8e22192dd3 | |||
| dc0b7d58e3 | |||
| 6919e5bdb0 | |||
| 9728a62efa | |||
| 276e070759 | |||
| fadea54f8d | |||
| 523d340ade | |||
| 96fa7efb66 | |||
| 03591f8317 |
5
.gitattributes
vendored
Normal file
5
.gitattributes
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Auto detect text files and perform LF normalization
|
||||||
|
* text=auto
|
||||||
|
|
||||||
|
*.txt text eof=lf
|
||||||
|
*.cs diff=csharp
|
||||||
4
.github/FUNDING.yml
vendored
Normal file
4
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
patreon: chylex
|
||||||
|
ko_fi: chylex
|
||||||
104
.gitignore
vendored
104
.gitignore
vendored
@@ -1,6 +1,12 @@
|
|||||||
## Ignore Visual Studio temporary files, build results, and
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
## files generated by popular Visual Studio add-ons.
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
|
||||||
|
# TweetDuck installer builds
|
||||||
|
bld/*
|
||||||
|
!bld/*.iss
|
||||||
|
!bld/*.bat
|
||||||
|
!bld/Resources
|
||||||
|
|
||||||
# User-specific files
|
# User-specific files
|
||||||
*.suo
|
*.suo
|
||||||
*.user
|
*.user
|
||||||
@@ -17,14 +23,11 @@
|
|||||||
[Rr]eleases/
|
[Rr]eleases/
|
||||||
x64/
|
x64/
|
||||||
x86/
|
x86/
|
||||||
bld/
|
|
||||||
[Bb]in/
|
[Bb]in/
|
||||||
[Oo]bj/
|
[Oo]bj/
|
||||||
|
|
||||||
# Visual Studio 2015 cache/options directory
|
# Visual Studio 2015 cache/options directory
|
||||||
.vs/
|
.vs/
|
||||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
|
||||||
#wwwroot/
|
|
||||||
|
|
||||||
# MSTest test Results
|
# MSTest test Results
|
||||||
[Tt]est[Rr]esult*/
|
[Tt]est[Rr]esult*/
|
||||||
@@ -34,11 +37,6 @@ bld/
|
|||||||
*.VisualState.xml
|
*.VisualState.xml
|
||||||
TestResult.xml
|
TestResult.xml
|
||||||
|
|
||||||
# Build Results of an ATL Project
|
|
||||||
[Dd]ebugPS/
|
|
||||||
[Rr]eleasePS/
|
|
||||||
dlldata.c
|
|
||||||
|
|
||||||
# DNX
|
# DNX
|
||||||
project.lock.json
|
project.lock.json
|
||||||
artifacts/
|
artifacts/
|
||||||
@@ -67,27 +65,12 @@ artifacts/
|
|||||||
*.svclog
|
*.svclog
|
||||||
*.scc
|
*.scc
|
||||||
|
|
||||||
# Chutzpah Test files
|
|
||||||
_Chutzpah*
|
|
||||||
|
|
||||||
# Visual C++ cache files
|
|
||||||
ipch/
|
|
||||||
*.aps
|
|
||||||
*.ncb
|
|
||||||
*.opendb
|
|
||||||
*.opensdf
|
|
||||||
*.sdf
|
|
||||||
*.cachefile
|
|
||||||
|
|
||||||
# Visual Studio profiler
|
# Visual Studio profiler
|
||||||
*.psess
|
*.psess
|
||||||
*.vsp
|
*.vsp
|
||||||
*.vspx
|
*.vspx
|
||||||
*.sap
|
*.sap
|
||||||
|
|
||||||
# TFS 2012 Local Workspace
|
|
||||||
$tf/
|
|
||||||
|
|
||||||
# Guidance Automation Toolkit
|
# Guidance Automation Toolkit
|
||||||
*.gpState
|
*.gpState
|
||||||
|
|
||||||
@@ -114,33 +97,9 @@ nCrunchTemp_*
|
|||||||
*.mm.*
|
*.mm.*
|
||||||
AutoTest.Net/
|
AutoTest.Net/
|
||||||
|
|
||||||
# Web workbench (sass)
|
|
||||||
.sass-cache/
|
|
||||||
|
|
||||||
# Installshield output folder
|
|
||||||
[Ee]xpress/
|
|
||||||
|
|
||||||
# DocProject is a documentation generator add-in
|
|
||||||
DocProject/buildhelp/
|
|
||||||
DocProject/Help/*.HxT
|
|
||||||
DocProject/Help/*.HxC
|
|
||||||
DocProject/Help/*.hhc
|
|
||||||
DocProject/Help/*.hhk
|
|
||||||
DocProject/Help/*.hhp
|
|
||||||
DocProject/Help/Html2
|
|
||||||
DocProject/Help/html
|
|
||||||
|
|
||||||
# Click-Once directory
|
# Click-Once directory
|
||||||
publish/
|
publish/
|
||||||
|
|
||||||
# Publish Web Output
|
|
||||||
*.[Pp]ublish.xml
|
|
||||||
*.azurePubxml
|
|
||||||
# TODO: Comment the next line if you want to checkin your web deploy settings
|
|
||||||
# but database connection strings (with potential passwords) will be unencrypted
|
|
||||||
*.pubxml
|
|
||||||
*.publishproj
|
|
||||||
|
|
||||||
# NuGet Packages
|
# NuGet Packages
|
||||||
*.nupkg
|
*.nupkg
|
||||||
# The packages folder can be ignored because of Package Restore
|
# The packages folder can be ignored because of Package Restore
|
||||||
@@ -153,17 +112,6 @@ publish/
|
|||||||
*.nuget.props
|
*.nuget.props
|
||||||
*.nuget.targets
|
*.nuget.targets
|
||||||
|
|
||||||
# Microsoft Azure Build Output
|
|
||||||
csx/
|
|
||||||
*.build.csdef
|
|
||||||
|
|
||||||
# Microsoft Azure Emulator
|
|
||||||
ecf/
|
|
||||||
rcf/
|
|
||||||
|
|
||||||
# Microsoft Azure ApplicationInsights config file
|
|
||||||
ApplicationInsights.config
|
|
||||||
|
|
||||||
# Windows Store app package directory
|
# Windows Store app package directory
|
||||||
AppPackages/
|
AppPackages/
|
||||||
BundleArtifacts/
|
BundleArtifacts/
|
||||||
@@ -185,9 +133,6 @@ ClientBin/
|
|||||||
node_modules/
|
node_modules/
|
||||||
orleans.codegen.cs
|
orleans.codegen.cs
|
||||||
|
|
||||||
# RIA/Silverlight projects
|
|
||||||
Generated_Code/
|
|
||||||
|
|
||||||
# Backup & report files from converting an old project file
|
# Backup & report files from converting an old project file
|
||||||
# to a newer Visual Studio version. Backup files are not needed,
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
# because we have git ;-)
|
# because we have git ;-)
|
||||||
@@ -196,40 +141,3 @@ Backup*/
|
|||||||
UpgradeLog*.XML
|
UpgradeLog*.XML
|
||||||
UpgradeLog*.htm
|
UpgradeLog*.htm
|
||||||
|
|
||||||
# SQL Server files
|
|
||||||
*.mdf
|
|
||||||
*.ldf
|
|
||||||
|
|
||||||
# Business Intelligence projects
|
|
||||||
*.rdl.data
|
|
||||||
*.bim.layout
|
|
||||||
*.bim_*.settings
|
|
||||||
|
|
||||||
# Microsoft Fakes
|
|
||||||
FakesAssemblies/
|
|
||||||
|
|
||||||
# GhostDoc plugin setting file
|
|
||||||
*.GhostDoc.xml
|
|
||||||
|
|
||||||
# Node.js Tools for Visual Studio
|
|
||||||
.ntvs_analysis.dat
|
|
||||||
|
|
||||||
# Visual Studio 6 build log
|
|
||||||
*.plg
|
|
||||||
|
|
||||||
# Visual Studio 6 workspace options file
|
|
||||||
*.opt
|
|
||||||
|
|
||||||
# Visual Studio LightSwitch build output
|
|
||||||
**/*.HTMLClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/ModelManifest.xml
|
|
||||||
**/*.Server/GeneratedArtifacts
|
|
||||||
**/*.Server/ModelManifest.xml
|
|
||||||
_Pvt_Extensions
|
|
||||||
|
|
||||||
# Paket dependency manager
|
|
||||||
.paket/paket.exe
|
|
||||||
|
|
||||||
# FAKE - F# Make
|
|
||||||
.fake/
|
|
||||||
|
|||||||
48
Configuration/Arguments.cs
Normal file
48
Configuration/Arguments.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
using TweetLib.Core.Collections;
|
||||||
|
|
||||||
|
namespace TweetDuck.Configuration{
|
||||||
|
static class Arguments{
|
||||||
|
// public args
|
||||||
|
public const string ArgDataFolder = "-datafolder";
|
||||||
|
public const string ArgLogging = "-log";
|
||||||
|
public const string ArgIgnoreGDPR = "-nogdpr";
|
||||||
|
public const string ArgFreeze = "-freeze";
|
||||||
|
|
||||||
|
// internal args
|
||||||
|
public const string ArgRestart = "-restart";
|
||||||
|
public const string ArgImportCookies = "-importcookies";
|
||||||
|
public const string ArgDeleteCookies = "-deletecookies";
|
||||||
|
public const string ArgUpdated = "-updated";
|
||||||
|
|
||||||
|
// class data and methods
|
||||||
|
private static readonly CommandLineArgs Current = CommandLineArgs.FromStringArray('-', Environment.GetCommandLineArgs());
|
||||||
|
|
||||||
|
public static bool HasFlag(string flag){
|
||||||
|
return Current.HasFlag(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetValue(string key){
|
||||||
|
return Current.GetValue(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CommandLineArgs GetCurrentClean(){
|
||||||
|
CommandLineArgs args = Current.Clone();
|
||||||
|
args.RemoveFlag(ArgRestart);
|
||||||
|
args.RemoveFlag(ArgImportCookies);
|
||||||
|
args.RemoveFlag(ArgDeleteCookies);
|
||||||
|
args.RemoveFlag(ArgUpdated);
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CommandLineArgs GetCurrentForInstaller(){
|
||||||
|
CommandLineArgs args = GetCurrentClean();
|
||||||
|
args.AddFlag(ArgUpdated);
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetCurrentForInstallerCmd(){
|
||||||
|
return GetCurrentForInstaller().ToString().Replace("\"", "::");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
82
Configuration/ConfigManager.cs
Normal file
82
Configuration/ConfigManager.cs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using TweetDuck.Data;
|
||||||
|
using TweetLib.Core.Features.Configuration;
|
||||||
|
using TweetLib.Core.Features.Plugins.Config;
|
||||||
|
using TweetLib.Core.Serialization.Converters;
|
||||||
|
using TweetLib.Core.Utils;
|
||||||
|
|
||||||
|
namespace TweetDuck.Configuration{
|
||||||
|
sealed class ConfigManager : IConfigManager{
|
||||||
|
public UserConfig User { get; }
|
||||||
|
public SystemConfig System { get; }
|
||||||
|
public PluginConfig Plugins { get; }
|
||||||
|
|
||||||
|
public event EventHandler ProgramRestartRequested;
|
||||||
|
|
||||||
|
private readonly FileConfigInstance<UserConfig> infoUser;
|
||||||
|
private readonly FileConfigInstance<SystemConfig> infoSystem;
|
||||||
|
private readonly PluginConfigInstance<PluginConfig> infoPlugins;
|
||||||
|
|
||||||
|
private readonly IConfigInstance<BaseConfig>[] infoList;
|
||||||
|
|
||||||
|
public ConfigManager(){
|
||||||
|
User = new UserConfig(this);
|
||||||
|
System = new SystemConfig(this);
|
||||||
|
Plugins = new PluginConfig(this);
|
||||||
|
|
||||||
|
infoList = new IConfigInstance<BaseConfig>[]{
|
||||||
|
infoUser = new FileConfigInstance<UserConfig>(Program.UserConfigFilePath, User, "program options"),
|
||||||
|
infoSystem = new FileConfigInstance<SystemConfig>(Program.SystemConfigFilePath, System, "system options"),
|
||||||
|
infoPlugins = new PluginConfigInstance<PluginConfig>(Program.PluginConfigFilePath, Plugins)
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO refactor further
|
||||||
|
|
||||||
|
infoUser.Serializer.RegisterTypeConverter(typeof(WindowState), WindowState.Converter);
|
||||||
|
|
||||||
|
infoUser.Serializer.RegisterTypeConverter(typeof(Point), new SingleTypeConverter<Point>{
|
||||||
|
ConvertToString = value => $"{value.X} {value.Y}",
|
||||||
|
ConvertToObject = value => {
|
||||||
|
int[] elements = StringUtils.ParseInts(value, ' ');
|
||||||
|
return new Point(elements[0], elements[1]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
infoUser.Serializer.RegisterTypeConverter(typeof(Size), new SingleTypeConverter<Size>{
|
||||||
|
ConvertToString = value => $"{value.Width} {value.Height}",
|
||||||
|
ConvertToObject = value => {
|
||||||
|
int[] elements = StringUtils.ParseInts(value, ' ');
|
||||||
|
return new Size(elements[0], elements[1]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadAll(){
|
||||||
|
infoUser.Load();
|
||||||
|
infoSystem.Load();
|
||||||
|
infoPlugins.Load();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveAll(){
|
||||||
|
infoUser.Save();
|
||||||
|
infoSystem.Save();
|
||||||
|
infoPlugins.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReloadAll(){
|
||||||
|
infoUser.Reload();
|
||||||
|
infoSystem.Reload();
|
||||||
|
infoPlugins.Reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IConfigManager.TriggerProgramRestartRequested(){
|
||||||
|
ProgramRestartRequested?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
IConfigInstance<BaseConfig> IConfigManager.GetInstanceInfo(BaseConfig instance){
|
||||||
|
Type instanceType = instance.GetType();
|
||||||
|
return Array.Find(infoList, info => info.Instance.GetType() == instanceType); // TODO handle null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,124 +1,183 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDck.Configuration{
|
namespace TweetDuck.Configuration{
|
||||||
sealed class LockManager{
|
sealed class LockManager{
|
||||||
public Process LockingProcess { get; private set; }
|
private const int RetryDelay = 250;
|
||||||
|
|
||||||
|
public enum Result{
|
||||||
|
Success, HasProcess, Fail
|
||||||
|
}
|
||||||
|
|
||||||
private readonly string file;
|
private readonly string file;
|
||||||
private FileStream lockStream;
|
private FileStream lockStream;
|
||||||
|
private Process lockingProcess;
|
||||||
|
|
||||||
public LockManager(string file){
|
public LockManager(string file){
|
||||||
this.file = file;
|
this.file = file;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CreateLockFile(){
|
// Lock file
|
||||||
|
|
||||||
|
private bool ReleaseLockFileStream(){
|
||||||
if (lockStream != null){
|
if (lockStream != null){
|
||||||
throw new InvalidOperationException("Lock file already exists.");
|
lockStream.Dispose();
|
||||||
}
|
lockStream = null;
|
||||||
|
|
||||||
try{
|
|
||||||
lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
|
|
||||||
|
|
||||||
byte[] id = BitConverter.GetBytes(Process.GetCurrentProcess().Id);
|
|
||||||
lockStream.Write(id, 0, id.Length);
|
|
||||||
lockStream.Flush();
|
|
||||||
|
|
||||||
if (LockingProcess != null){
|
|
||||||
LockingProcess.Close();
|
|
||||||
LockingProcess = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}catch(Exception){
|
}
|
||||||
if (lockStream != null){
|
else{
|
||||||
lockStream.Close();
|
|
||||||
lockStream.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Lock(){
|
private Result TryCreateLockFile(){
|
||||||
if (lockStream != null)return true;
|
void CreateLockFileStream(){
|
||||||
|
lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||||
try{
|
lockStream.Write(BitConverter.GetBytes(WindowsUtils.CurrentProcessID), 0, sizeof(int));
|
||||||
byte[] bytes = new byte[4];
|
lockStream.Flush(true);
|
||||||
|
|
||||||
using(FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){
|
|
||||||
fileStream.Read(bytes, 0, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
int pid = BitConverter.ToInt32(bytes, 0);
|
|
||||||
|
|
||||||
try{
|
|
||||||
Process foundProcess = Process.GetProcessById(pid);
|
|
||||||
|
|
||||||
using(Process currentProcess = Process.GetCurrentProcess()){
|
|
||||||
if (foundProcess.ProcessName == currentProcess.ProcessName){
|
|
||||||
LockingProcess = foundProcess;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}catch(ArgumentException){}
|
|
||||||
|
|
||||||
return LockingProcess == null && CreateLockFile();
|
|
||||||
}catch(DirectoryNotFoundException){
|
|
||||||
string dir = Path.GetDirectoryName(file);
|
|
||||||
|
|
||||||
if (dir != null){
|
|
||||||
Directory.CreateDirectory(dir);
|
|
||||||
return CreateLockFile();
|
|
||||||
}
|
|
||||||
}catch(FileNotFoundException){
|
|
||||||
return CreateLockFile();
|
|
||||||
}catch(Exception){
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock management
|
||||||
|
|
||||||
|
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)){
|
||||||
|
byte[] bytes = new byte[sizeof(int)];
|
||||||
|
fileStream.Read(bytes, 0, bytes.Length);
|
||||||
|
pid = BitConverter.ToInt32(bytes, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Result LockWait(int timeout){
|
||||||
|
for(int elapsed = 0; elapsed < timeout; elapsed += RetryDelay){
|
||||||
|
Result result = Lock();
|
||||||
|
|
||||||
|
if (result == Result.HasProcess){
|
||||||
|
Thread.Sleep(RetryDelay);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Lock();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Unlock(){
|
public bool Unlock(){
|
||||||
bool result = true;
|
if (ReleaseLockFileStream()){
|
||||||
|
|
||||||
if (lockStream != null){
|
|
||||||
lockStream.Dispose();
|
|
||||||
|
|
||||||
try{
|
try{
|
||||||
File.Delete(file);
|
File.Delete(file);
|
||||||
}catch(Exception e){
|
}catch(Exception e){
|
||||||
Program.Reporter.Log(e.ToString());
|
Program.Reporter.LogImportant(e.ToString());
|
||||||
result = false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
lockStream = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CloseLockingProcess(int timeout){
|
// Locking process
|
||||||
if (LockingProcess != null){
|
|
||||||
LockingProcess.CloseMainWindow();
|
|
||||||
|
|
||||||
for(int waited = 0; waited < timeout && !LockingProcess.HasExited;){
|
public bool RestoreLockingProcess(int failTimeout){
|
||||||
LockingProcess.Refresh();
|
if (lockingProcess != null && lockingProcess.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
|
||||||
|
NativeMethods.BroadcastMessage(Program.WindowRestoreMessage, (uint)lockingProcess.Id, 0);
|
||||||
|
|
||||||
Thread.Sleep(100);
|
if (WindowsUtils.TrySleepUntil(() => CheckLockingProcessExited() || (lockingProcess.MainWindowHandle != IntPtr.Zero && lockingProcess.Responding), failTimeout, RetryDelay)){
|
||||||
waited += 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LockingProcess.HasExited){
|
|
||||||
LockingProcess.Dispose();
|
|
||||||
LockingProcess = null;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool CloseLockingProcess(int closeTimeout, int killTimeout){
|
||||||
|
if (lockingProcess != null){
|
||||||
|
try{
|
||||||
|
if (lockingProcess.CloseMainWindow()){
|
||||||
|
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, closeTimeout, RetryDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lockingProcess.HasExited){
|
||||||
|
lockingProcess.Kill();
|
||||||
|
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, killTimeout, RetryDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lockingProcess.HasExited){
|
||||||
|
lockingProcess.Dispose();
|
||||||
|
lockingProcess = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}catch(Exception ex) when (ex is InvalidOperationException || ex is Win32Exception){
|
||||||
|
if (lockingProcess != null){
|
||||||
|
bool hasExited = CheckLockingProcessExited();
|
||||||
|
lockingProcess.Dispose();
|
||||||
|
return hasExited;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CheckLockingProcessExited(){
|
||||||
|
lockingProcess.Refresh();
|
||||||
|
return lockingProcess.HasExited;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
51
Configuration/PluginConfig.cs
Normal file
51
Configuration/PluginConfig.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using TweetLib.Core.Features.Configuration;
|
||||||
|
using TweetLib.Core.Features.Plugins;
|
||||||
|
using TweetLib.Core.Features.Plugins.Config;
|
||||||
|
using TweetLib.Core.Features.Plugins.Events;
|
||||||
|
|
||||||
|
namespace TweetDuck.Configuration{
|
||||||
|
sealed class PluginConfig : BaseConfig, IPluginConfig{
|
||||||
|
private static readonly string[] DefaultDisabled = {
|
||||||
|
"official/clear-columns",
|
||||||
|
"official/reply-account"
|
||||||
|
};
|
||||||
|
|
||||||
|
// CONFIGURATION DATA
|
||||||
|
|
||||||
|
private readonly HashSet<string> disabled = new HashSet<string>(DefaultDisabled);
|
||||||
|
|
||||||
|
// EVENTS
|
||||||
|
|
||||||
|
public event EventHandler<PluginChangedStateEventArgs> PluginChangedState;
|
||||||
|
|
||||||
|
// END OF CONFIG
|
||||||
|
|
||||||
|
public PluginConfig(IConfigManager configManager) : base(configManager){}
|
||||||
|
|
||||||
|
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager){
|
||||||
|
return new PluginConfig(configManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
// INTERFACE IMPLEMENTATION
|
||||||
|
|
||||||
|
IEnumerable<string> IPluginConfig.DisabledPlugins => disabled;
|
||||||
|
|
||||||
|
void IPluginConfig.Reset(IEnumerable<string> newDisabledPlugins){
|
||||||
|
disabled.Clear();
|
||||||
|
disabled.UnionWith(newDisabledPlugins);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetEnabled(Plugin plugin, bool enabled){
|
||||||
|
if ((enabled && disabled.Remove(plugin.Identifier)) || (!enabled && disabled.Add(plugin.Identifier))){
|
||||||
|
PluginChangedState?.Invoke(this, new PluginChangedStateEventArgs(plugin, enabled));
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsEnabled(Plugin plugin){
|
||||||
|
return !disabled.Contains(plugin.Identifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Configuration/SystemConfig.cs
Normal file
28
Configuration/SystemConfig.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using TweetLib.Core.Features.Configuration;
|
||||||
|
|
||||||
|
namespace TweetDuck.Configuration{
|
||||||
|
sealed class SystemConfig : BaseConfig{
|
||||||
|
|
||||||
|
// CONFIGURATION DATA
|
||||||
|
|
||||||
|
public bool _hardwareAcceleration = true;
|
||||||
|
|
||||||
|
public bool ClearCacheAutomatically { get; set; } = true;
|
||||||
|
public int ClearCacheThreshold { get; set; } = 250;
|
||||||
|
|
||||||
|
// SPECIAL PROPERTIES
|
||||||
|
|
||||||
|
public bool HardwareAcceleration{
|
||||||
|
get => _hardwareAcceleration;
|
||||||
|
set => UpdatePropertyWithRestartRequest(ref _hardwareAcceleration, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// END OF CONFIG
|
||||||
|
|
||||||
|
public SystemConfig(IConfigManager configManager) : base(configManager){}
|
||||||
|
|
||||||
|
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager){
|
||||||
|
return new SystemConfig(configManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,247 +1,145 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Globalization;
|
using TweetDuck.Core.Controls;
|
||||||
using System.IO;
|
using TweetDuck.Core.Notification;
|
||||||
using System.Runtime.Serialization;
|
using TweetDuck.Core.Other;
|
||||||
using System.Runtime.Serialization.Formatters.Binary;
|
using TweetDuck.Data;
|
||||||
using TweetDck.Core;
|
using TweetLib.Core.Features.Configuration;
|
||||||
using TweetDck.Core.Handling;
|
using TweetLib.Core.Features.Twitter;
|
||||||
using TweetDck.Core.Utils;
|
|
||||||
using TweetDck.Plugins;
|
|
||||||
|
|
||||||
namespace TweetDck.Configuration{
|
namespace TweetDuck.Configuration{
|
||||||
[Serializable]
|
sealed class UserConfig : BaseConfig{
|
||||||
sealed class UserConfig{
|
|
||||||
private static readonly IFormatter Formatter = new BinaryFormatter{
|
// CONFIGURATION DATA
|
||||||
Binder = new SerializationCompatibilityHandler()
|
|
||||||
};
|
|
||||||
|
|
||||||
private const int CurrentFileVersion = 5;
|
public bool FirstRun { get; set; } = true;
|
||||||
|
public bool AllowDataCollection { get; set; } = false;
|
||||||
|
|
||||||
// START OF CONFIGURATION
|
public WindowState BrowserWindow { get; set; } = new WindowState();
|
||||||
|
public Size PluginsWindowSize { get; set; } = Size.Empty;
|
||||||
|
|
||||||
public bool IgnoreMigration { get; set; }
|
public bool ExpandLinksOnHover { get; set; } = true;
|
||||||
public bool IgnoreUninstallCheck { get; set; }
|
public bool FocusDmInput { get; set; } = true;
|
||||||
|
public bool OpenSearchInFirstColumn { get; set; } = true;
|
||||||
|
public bool KeepLikeFollowDialogsOpen { get; set; } = true;
|
||||||
|
public bool BestImageQuality { get; set; } = true;
|
||||||
|
public bool EnableAnimatedImages { get; set; } = true;
|
||||||
|
|
||||||
public WindowState BrowserWindow { get; set; }
|
private bool _enableSmoothScrolling = true;
|
||||||
public bool DisplayNotificationTimer { get; set; }
|
private bool _enableTouchAdjustment = false;
|
||||||
public bool NotificationTimerCountDown { get; set; }
|
private string _customCefArgs = null;
|
||||||
|
|
||||||
public TweetNotification.Duration NotificationDuration { get; set; }
|
public string BrowserPath { get; set; } = null;
|
||||||
public TweetNotification.Position NotificationPosition { get; set; }
|
public bool IgnoreTrackingUrlWarning { get; set; } = false;
|
||||||
public Point CustomNotificationPosition { get; set; }
|
public string SearchEngineUrl { get; set; } = null;
|
||||||
public int NotificationEdgeDistance { get; set; }
|
private int _zoomLevel = 100;
|
||||||
public int NotificationDisplay { get; set; }
|
|
||||||
public int NotificationDurationValue { get; set; }
|
|
||||||
public bool NotificationLegacyLoad { get; set; }
|
|
||||||
|
|
||||||
public bool EnableSpellCheck { get; set; }
|
public int VideoPlayerVolume { get; set; } = 50;
|
||||||
public bool ExpandLinksOnHover { get; set; }
|
|
||||||
public bool EnableTrayHighlight { get; set; }
|
public bool EnableSpellCheck { get; set; } = false;
|
||||||
|
private string _spellCheckLanguage = "en-US";
|
||||||
|
|
||||||
public bool EnableUpdateCheck { get; set; }
|
public string TranslationTarget { get; set; } = "en";
|
||||||
public string DismissedUpdate { get; set; }
|
|
||||||
|
private TrayIcon.Behavior _trayBehavior = TrayIcon.Behavior.Disabled;
|
||||||
|
public bool EnableTrayHighlight { get; set; } = true;
|
||||||
|
|
||||||
public PluginConfig Plugins { get; private set; }
|
public bool EnableUpdateCheck { get; set; } = true;
|
||||||
public WindowState PluginsWindow { get; set; }
|
public string DismissedUpdate { get; set; } = null;
|
||||||
|
|
||||||
public string CustomCefArgs { get; set; }
|
public bool DisplayNotificationColumn { get; set; } = false;
|
||||||
public string CustomBrowserCSS { get; set; }
|
public bool NotificationMediaPreviews { get; set; } = true;
|
||||||
public string CustomNotificationCSS { get; set; }
|
public bool NotificationSkipOnLinkClick { get; set; } = false;
|
||||||
|
public bool NotificationNonIntrusiveMode { get; set; } = true;
|
||||||
|
public int NotificationIdlePauseSeconds { get; set; } = 0;
|
||||||
|
|
||||||
public bool IsCustomNotificationPositionSet{
|
public bool DisplayNotificationTimer { get; set; } = true;
|
||||||
get{
|
public bool NotificationTimerCountDown { get; set; } = false;
|
||||||
return CustomNotificationPosition.X != -32000 && CustomNotificationPosition.X != 32000;
|
public int NotificationDurationValue { get; set; } = 25;
|
||||||
}
|
|
||||||
|
public TweetNotification.Position NotificationPosition { get; set; } = TweetNotification.Position.TopRight;
|
||||||
|
public Point CustomNotificationPosition { get; set; } = ControlExtensions.InvisibleLocation;
|
||||||
|
public int NotificationDisplay { get; set; } = 0;
|
||||||
|
public int NotificationEdgeDistance { get; set; } = 8;
|
||||||
|
|
||||||
|
public TweetNotification.Size NotificationSize { get; set; } = TweetNotification.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;
|
||||||
|
|
||||||
|
// SPECIAL PROPERTIES
|
||||||
|
|
||||||
|
public bool IsCustomNotificationPositionSet => CustomNotificationPosition != ControlExtensions.InvisibleLocation;
|
||||||
|
public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty;
|
||||||
|
public bool IsCustomSoundNotificationSet => NotificationSoundPath != string.Empty;
|
||||||
|
|
||||||
|
public ImageQuality TwitterImageQuality => BestImageQuality ? ImageQuality.Best : ImageQuality.Default;
|
||||||
|
|
||||||
|
public string NotificationSoundPath{
|
||||||
|
get => _notificationSoundPath ?? string.Empty;
|
||||||
|
set => UpdatePropertyWithEvent(ref _notificationSoundPath, value, SoundNotificationChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int NotificationSoundVolume{
|
||||||
|
get => _notificationSoundVolume;
|
||||||
|
set => UpdatePropertyWithEvent(ref _notificationSoundVolume, value, SoundNotificationChanged);
|
||||||
|
}
|
||||||
|
|
||||||
public bool MuteNotifications{
|
public bool MuteNotifications{
|
||||||
get{
|
get => _muteNotifications;
|
||||||
return muteNotifications;
|
set => UpdatePropertyWithEvent(ref _muteNotifications, value, MuteToggled);
|
||||||
}
|
|
||||||
|
|
||||||
set{
|
|
||||||
if (muteNotifications == value)return;
|
|
||||||
|
|
||||||
muteNotifications = value;
|
|
||||||
|
|
||||||
if (MuteToggled != null){
|
|
||||||
MuteToggled(this, new EventArgs());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int ZoomLevel{
|
||||||
|
get => _zoomLevel;
|
||||||
|
set => UpdatePropertyWithEvent(ref _zoomLevel, value, ZoomLevelChanged);
|
||||||
|
}
|
||||||
|
|
||||||
public TrayIcon.Behavior TrayBehavior{
|
public TrayIcon.Behavior TrayBehavior{
|
||||||
get{
|
get => _trayBehavior;
|
||||||
return trayBehavior;
|
set => UpdatePropertyWithEvent(ref _trayBehavior, value, TrayBehaviorChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
set{
|
public bool EnableSmoothScrolling{
|
||||||
if (trayBehavior == value)return;
|
get => _enableSmoothScrolling;
|
||||||
|
set => UpdatePropertyWithRestartRequest(ref _enableSmoothScrolling, value);
|
||||||
trayBehavior = value;
|
|
||||||
|
|
||||||
if (TrayBehaviorChanged != null){
|
|
||||||
TrayBehaviorChanged(this, new EventArgs());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// END OF CONFIGURATION
|
public bool EnableTouchAdjustment{
|
||||||
|
get => _enableTouchAdjustment;
|
||||||
|
set => UpdatePropertyWithRestartRequest(ref _enableTouchAdjustment, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string CustomCefArgs{
|
||||||
|
get => _customCefArgs;
|
||||||
|
set => UpdatePropertyWithRestartRequest(ref _customCefArgs, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string SpellCheckLanguage{
|
||||||
|
get => _spellCheckLanguage;
|
||||||
|
set => UpdatePropertyWithRestartRequest(ref _spellCheckLanguage, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// EVENTS
|
||||||
|
|
||||||
[field:NonSerialized]
|
|
||||||
public event EventHandler MuteToggled;
|
public event EventHandler MuteToggled;
|
||||||
|
public event EventHandler ZoomLevelChanged;
|
||||||
[field:NonSerialized]
|
|
||||||
public event EventHandler TrayBehaviorChanged;
|
public event EventHandler TrayBehaviorChanged;
|
||||||
|
public event EventHandler SoundNotificationChanged;
|
||||||
|
|
||||||
[NonSerialized]
|
// END OF CONFIG
|
||||||
private string file;
|
|
||||||
|
|
||||||
private int fileVersion;
|
|
||||||
private bool muteNotifications;
|
|
||||||
private TrayIcon.Behavior trayBehavior;
|
|
||||||
|
|
||||||
private UserConfig(string file){
|
|
||||||
this.file = file;
|
|
||||||
|
|
||||||
BrowserWindow = new WindowState();
|
|
||||||
DisplayNotificationTimer = true;
|
|
||||||
NotificationDuration = TweetNotification.Duration.Medium;
|
|
||||||
NotificationPosition = TweetNotification.Position.TopRight;
|
|
||||||
CustomNotificationPosition = new Point(-32000, -32000);
|
|
||||||
NotificationEdgeDistance = 8;
|
|
||||||
NotificationDurationValue = 25;
|
|
||||||
EnableUpdateCheck = true;
|
|
||||||
ExpandLinksOnHover = true;
|
|
||||||
EnableTrayHighlight = true;
|
|
||||||
Plugins = new PluginConfig();
|
|
||||||
PluginsWindow = new WindowState();
|
|
||||||
|
|
||||||
Plugins.DisableOfficialFromConfig("clear-columns");
|
|
||||||
Plugins.DisableOfficialFromConfig("reply-account");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpgradeFile(){
|
|
||||||
if (fileVersion == CurrentFileVersion){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if outdated, cycle through all versions
|
|
||||||
if (fileVersion == 0){
|
|
||||||
DisplayNotificationTimer = true;
|
|
||||||
EnableUpdateCheck = true;
|
|
||||||
++fileVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileVersion == 1){
|
|
||||||
ExpandLinksOnHover = true;
|
|
||||||
++fileVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileVersion == 2){
|
|
||||||
BrowserWindow = new WindowState();
|
|
||||||
Plugins = new PluginConfig();
|
|
||||||
PluginsWindow = new WindowState();
|
|
||||||
++fileVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileVersion == 3){
|
|
||||||
EnableTrayHighlight = true;
|
|
||||||
|
|
||||||
switch(NotificationDuration){
|
|
||||||
case TweetNotification.Duration.Short: NotificationDurationValue = 15; break;
|
|
||||||
case TweetNotification.Duration.Medium: NotificationDurationValue = 25; break;
|
|
||||||
case TweetNotification.Duration.Long: NotificationDurationValue = 35; break;
|
|
||||||
case TweetNotification.Duration.VeryLong: NotificationDurationValue = 45; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
++fileVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileVersion == 4){
|
|
||||||
Plugins.DisableOfficialFromConfig("clear-columns");
|
|
||||||
Plugins.DisableOfficialFromConfig("reply-account");
|
|
||||||
++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){
|
public UserConfig(IConfigManager configManager) : base(configManager){}
|
||||||
UserConfig config = null;
|
|
||||||
Exception firstException = null;
|
|
||||||
|
|
||||||
for(int attempt = 0; attempt < 2; attempt++){
|
protected override BaseConfig ConstructWithDefaults(IConfigManager configManager){
|
||||||
try{
|
return new UserConfig(configManager);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config != null){
|
|
||||||
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";
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SerializationCompatibilityHandler : SerializationBinder{
|
|
||||||
public override Type BindToType(string assemblyName, string typeName){
|
|
||||||
assemblyName = assemblyName.Replace("TweetDick", "TweetDuck");
|
|
||||||
typeName = typeName.Replace("TweetDick", "TweetDck");
|
|
||||||
return Type.GetType(string.Format(CultureInfo.CurrentCulture, "{0}, {1}", typeName, assemblyName));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<configuration>
|
|
||||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/></startup></configuration>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<packages xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
|
||||||
<package id="cef.redist.x64" version="3.2785.1478" targetFramework="net452" />
|
|
||||||
<package id="cef.redist.x86" version="3.2785.1478" targetFramework="net452" />
|
|
||||||
<package id="CefSharp.Common" version="53.0.0-pre01" targetFramework="net452" />
|
|
||||||
<package id="CefSharp.WinForms" version="53.0.0-pre01" targetFramework="net452" />
|
|
||||||
<package id="Microsoft.VC120.CRT.JetBrains" version="12.0.21005.2" targetFramework="net452" />
|
|
||||||
</packages>
|
|
||||||
35
Core/Bridge/PropertyBridge.cs
Normal file
35
Core/Bridge/PropertyBridge.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using System.Text;
|
||||||
|
using TweetDuck.Configuration;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Bridge{
|
||||||
|
static class PropertyBridge{
|
||||||
|
public enum Environment{
|
||||||
|
Browser, Notification
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GenerateScript(Environment environment){
|
||||||
|
string Bool(bool value) => value ? "true;" : "false;";
|
||||||
|
string Str(string value) => '"'+value+"\";";
|
||||||
|
|
||||||
|
UserConfig config = Program.Config.User;
|
||||||
|
StringBuilder build = new StringBuilder(128).Append("(function(x){");
|
||||||
|
|
||||||
|
build.Append("x.expandLinksOnHover=").Append(Bool(config.ExpandLinksOnHover));
|
||||||
|
|
||||||
|
if (environment == Environment.Browser){
|
||||||
|
build.Append("x.focusDmInput=").Append(Bool(config.FocusDmInput));
|
||||||
|
build.Append("x.openSearchInFirstColumn=").Append(Bool(config.OpenSearchInFirstColumn));
|
||||||
|
build.Append("x.keepLikeFollowDialogsOpen=").Append(Bool(config.KeepLikeFollowDialogsOpen));
|
||||||
|
build.Append("x.muteNotifications=").Append(Bool(config.MuteNotifications));
|
||||||
|
build.Append("x.notificationMediaPreviews=").Append(Bool(config.NotificationMediaPreviews));
|
||||||
|
build.Append("x.translationTarget=").Append(Str(config.TranslationTarget));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (environment == Environment.Notification){
|
||||||
|
build.Append("x.skipOnLinkClick=").Append(Bool(config.NotificationSkipOnLinkClick));
|
||||||
|
}
|
||||||
|
|
||||||
|
return build.Append("})(window.$TDX=window.$TDX||{})").ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
139
Core/Bridge/TweetDeckBridge.cs
Normal file
139
Core/Bridge/TweetDeckBridge.cs
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Management;
|
||||||
|
using TweetDuck.Core.Notification;
|
||||||
|
using TweetDuck.Core.Other;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Bridge{
|
||||||
|
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||||
|
class TweetDeckBridge{
|
||||||
|
public static string FontSize { get; private set; }
|
||||||
|
public static string NotificationHeadLayout { get; private set; }
|
||||||
|
public static readonly ContextInfo ContextInfo = new ContextInfo();
|
||||||
|
|
||||||
|
public static void ResetStaticProperties(){
|
||||||
|
FontSize = NotificationHeadLayout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly FormBrowser form;
|
||||||
|
private readonly FormNotificationMain notification;
|
||||||
|
|
||||||
|
protected TweetDeckBridge(FormBrowser form, FormNotificationMain notification){
|
||||||
|
this.form = form;
|
||||||
|
this.notification = notification;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Browser only
|
||||||
|
|
||||||
|
public sealed class Browser : TweetDeckBridge{
|
||||||
|
public Browser(FormBrowser form, FormNotificationMain notification) : base(form, notification){}
|
||||||
|
|
||||||
|
public void OpenContextMenu(){
|
||||||
|
form.InvokeAsyncSafe(form.OpenContextMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenProfileImport(){
|
||||||
|
form.InvokeAsyncSafe(form.OpenProfileImport);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){
|
||||||
|
form.InvokeAsyncSafe(() => {
|
||||||
|
form.OnIntroductionClosed(showGuide, allowDataCollection);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadNotificationLayout(string fontSize, string headLayout){
|
||||||
|
form.InvokeAsyncSafe(() => {
|
||||||
|
FontSize = fontSize;
|
||||||
|
NotificationHeadLayout = headLayout;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetRightClickedLink(string type, string url){
|
||||||
|
ContextInfo.SetLink(type, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetRightClickedChirp(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
|
||||||
|
ContextInfo.SetChirp(tweetUrl, quoteUrl, chirpAuthors, chirpImages);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DisplayTooltip(string text){
|
||||||
|
form.InvokeAsyncSafe(() => form.DisplayTooltip(text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notification only
|
||||||
|
|
||||||
|
public sealed class Notification : TweetDeckBridge{
|
||||||
|
public Notification(FormBrowser form, FormNotificationMain notification) : base(form, notification){}
|
||||||
|
|
||||||
|
public void DisplayTooltip(string text){
|
||||||
|
notification.InvokeAsyncSafe(() => notification.DisplayTooltip(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadNextNotification(){
|
||||||
|
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowTweetDetail(){
|
||||||
|
notification.InvokeAsyncSafe(notification.ShowTweetDetail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global
|
||||||
|
|
||||||
|
public void OnTweetPopup(string columnId, string chirpId, string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl){
|
||||||
|
notification.InvokeAsyncSafe(() => {
|
||||||
|
form.OnTweetNotification();
|
||||||
|
notification.ShowNotification(new TweetNotification(columnId, chirpId, columnName, tweetHtml, tweetCharacters, tweetUrl, quoteUrl));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnTweetSound(){
|
||||||
|
form.InvokeAsyncSafe(() => {
|
||||||
|
form.OnTweetNotification();
|
||||||
|
form.OnTweetSound();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ScreenshotTweet(string html, int width){
|
||||||
|
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PlayVideo(string url, string username){
|
||||||
|
form.InvokeAsyncSafe(() => form.PlayVideo(url, username));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FixClipboard(){
|
||||||
|
form.InvokeAsyncSafe(WindowsUtils.ClipboardStripHtmlStyles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenBrowser(string url){
|
||||||
|
form.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetIdleSeconds(){
|
||||||
|
return NativeMethods.GetIdleSeconds();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 CrashDebug(string message){
|
||||||
|
#if DEBUG
|
||||||
|
System.Diagnostics.Debug.WriteLine(message);
|
||||||
|
System.Diagnostics.Debugger.Break();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
70
Core/Bridge/UpdateBridge.cs
Normal file
70
Core/Bridge/UpdateBridge.cs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetLib.Core.Features.Updates;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Bridge{
|
||||||
|
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||||
|
class UpdateBridge{
|
||||||
|
private readonly UpdateHandler updates;
|
||||||
|
private readonly Control sync;
|
||||||
|
|
||||||
|
private UpdateInfo nextUpdate = null;
|
||||||
|
|
||||||
|
public event EventHandler<UpdateInfo> UpdateAccepted;
|
||||||
|
public event EventHandler<UpdateInfo> UpdateDelayed;
|
||||||
|
public event EventHandler<UpdateInfo> UpdateDismissed;
|
||||||
|
|
||||||
|
public UpdateBridge(UpdateHandler updates, Control sync){
|
||||||
|
this.sync = sync;
|
||||||
|
|
||||||
|
this.updates = updates;
|
||||||
|
this.updates.CheckFinished += updates_CheckFinished;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Cleanup(){
|
||||||
|
updates.CheckFinished -= updates_CheckFinished;
|
||||||
|
nextUpdate?.DeleteInstaller();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){
|
||||||
|
UpdateInfo foundUpdate = e.Result.HasValue ? e.Result.Value : null;
|
||||||
|
|
||||||
|
if (nextUpdate != null && !nextUpdate.Equals(foundUpdate)){
|
||||||
|
nextUpdate.DeleteInstaller();
|
||||||
|
}
|
||||||
|
|
||||||
|
nextUpdate = foundUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleInteractionEvent(EventHandler<UpdateInfo> eventHandler){
|
||||||
|
UpdateInfo tmpInfo = nextUpdate;
|
||||||
|
|
||||||
|
if (tmpInfo != null){
|
||||||
|
sync.InvokeAsyncSafe(() => eventHandler?.Invoke(this, tmpInfo));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bridge methods
|
||||||
|
|
||||||
|
public void TriggerUpdateCheck(){
|
||||||
|
updates.Check(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnUpdateAccepted(){
|
||||||
|
HandleInteractionEvent(UpdateAccepted);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnUpdateDelayed(){
|
||||||
|
HandleInteractionEvent(UpdateDelayed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnUpdateDismissed(){
|
||||||
|
HandleInteractionEvent(UpdateDismissed);
|
||||||
|
|
||||||
|
nextUpdate?.DeleteInstaller();
|
||||||
|
nextUpdate = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
using System.Linq;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
namespace TweetDck.Core.Controls{
|
namespace TweetDuck.Core.Controls{
|
||||||
static class ControlExtensions{
|
static class ControlExtensions{
|
||||||
|
public static readonly Point InvisibleLocation = new Point(-32000, -32000);
|
||||||
|
|
||||||
public static void InvokeSafe(this Control control, Action func){
|
public static void InvokeSafe(this Control control, Action func){
|
||||||
if (control.InvokeRequired){
|
if (control.InvokeRequired){
|
||||||
control.Invoke(func);
|
control.Invoke(func);
|
||||||
@@ -13,6 +16,20 @@ namespace TweetDck.Core.Controls{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void InvokeAsyncSafe(this Control control, Action func){
|
||||||
|
control.BeginInvoke(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){
|
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);
|
targetForm.Location = new Point(parentForm.Location.X+parentForm.Width/2-targetForm.Width/2, parentForm.Location.Y+parentForm.Height/2-targetForm.Height/2);
|
||||||
}
|
}
|
||||||
@@ -29,12 +46,26 @@ namespace TweetDck.Core.Controls{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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){
|
public static void SetValueSafe(this TrackBar trackBar, int value){
|
||||||
if (value >= trackBar.Minimum && value <= trackBar.Maximum){
|
if (value >= trackBar.Minimum && value <= trackBar.Maximum){
|
||||||
trackBar.Value = value;
|
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;
|
||||||
|
}
|
||||||
|
else return true;
|
||||||
|
}
|
||||||
|
|
||||||
public static void EnableMultilineShortcuts(this TextBox textBox){
|
public static void EnableMultilineShortcuts(this TextBox textBox){
|
||||||
textBox.KeyDown += (sender, args) => {
|
textBox.KeyDown += (sender, args) => {
|
||||||
if (args.Control && args.KeyCode == Keys.A){
|
if (args.Control && args.KeyCode == Keys.A){
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
namespace TweetDck.Core.Controls{
|
namespace TweetDuck.Core.Controls{
|
||||||
class FlatButton : Button{
|
sealed class FlatButton : Button{
|
||||||
protected override bool ShowFocusCues{
|
protected override bool ShowFocusCues => false;
|
||||||
get{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public FlatButton(){
|
public FlatButton(){
|
||||||
GotFocus += FlatButton_GotFocus;
|
GotFocus += FlatButton_GotFocus;
|
||||||
|
|||||||
3
Core/Controls/FlatProgressBar.Designer.cs
generated
3
Core/Controls/FlatProgressBar.Designer.cs
generated
@@ -1,3 +0,0 @@
|
|||||||
namespace TweetDck.Core.Controls{
|
|
||||||
partial class FlatProgressBar{}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
using System.Drawing;
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
namespace TweetDck.Core.Controls{
|
namespace TweetDuck.Core.Controls{
|
||||||
sealed partial class FlatProgressBar : ProgressBar{
|
sealed class FlatProgressBar : ProgressBar{
|
||||||
private readonly SolidBrush brush;
|
private readonly SolidBrush brush;
|
||||||
|
|
||||||
public FlatProgressBar(){
|
public FlatProgressBar(){
|
||||||
@@ -13,7 +14,7 @@ namespace TweetDck.Core.Controls{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void SetValueInstant(int value){
|
public void SetValueInstant(int value){
|
||||||
ControlExtensions.SetValueInstant(this, value);
|
ControlExtensions.SetValueInstant(this, Math.Max(Minimum, Math.Min(Maximum, value)));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnPaint(PaintEventArgs e){
|
protected override void OnPaint(PaintEventArgs e){
|
||||||
@@ -23,7 +24,7 @@ namespace TweetDck.Core.Controls{
|
|||||||
|
|
||||||
Rectangle rect = e.ClipRectangle;
|
Rectangle rect = e.ClipRectangle;
|
||||||
rect.Width = (int)(rect.Width*((double)Value/Maximum));
|
rect.Width = (int)(rect.Width*((double)Value/Maximum));
|
||||||
e.Graphics.FillRectangle(brush,rect);
|
e.Graphics.FillRectangle(brush, rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing){
|
protected override void Dispose(bool disposing){
|
||||||
|
|||||||
23
Core/Controls/LabelVertical.cs
Normal file
23
Core/Controls/LabelVertical.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.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
Core/Controls/NumericUpDownEx.cs
Normal file
18
Core/Controls/NumericUpDownEx.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Core/Controls/TabButton.Designer.cs
generated
3
Core/Controls/TabButton.Designer.cs
generated
@@ -1,3 +0,0 @@
|
|||||||
namespace TweetDck.Core.Controls{
|
|
||||||
partial class TabButton{}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
|
|
||||||
namespace TweetDck.Core.Controls{
|
|
||||||
sealed partial 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,62 +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 IEnumerable<TabButton> Buttons{
|
|
||||||
get{
|
|
||||||
return panelButtons.Controls.Cast<TabButton>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public TabButton ActiveButton { get; private set; }
|
|
||||||
|
|
||||||
// ReSharper disable once ConvertToAutoPropertyWithPrivateSetter
|
|
||||||
public Panel Content{
|
|
||||||
get{
|
|
||||||
return panelContent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
22
Core/FormBrowser.Designer.cs
generated
22
Core/FormBrowser.Designer.cs
generated
@@ -1,4 +1,4 @@
|
|||||||
namespace TweetDck.Core {
|
namespace TweetDuck.Core {
|
||||||
sealed partial class FormBrowser {
|
sealed partial class FormBrowser {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Required designer variable.
|
/// Required designer variable.
|
||||||
@@ -24,27 +24,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.trayIcon = new TweetDuck.Core.Other.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();
|
||||||
//
|
//
|
||||||
// trayIcon
|
// timerResize
|
||||||
//
|
//
|
||||||
this.trayIcon.Visible = false;
|
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.ClientSize = new System.Drawing.Size(324, 386);
|
this.BackColor = TweetDuck.Core.Utils.TwitterUtils.BackgroundColor;
|
||||||
|
this.ClientSize = new System.Drawing.Size(1008, 730);
|
||||||
this.Icon = Properties.Resources.icon;
|
this.Icon = Properties.Resources.icon;
|
||||||
this.Location = new System.Drawing.Point(-32000, -32000);
|
this.Location = TweetDuck.Core.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);
|
||||||
|
|
||||||
@@ -52,8 +57,9 @@
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private TrayIcon trayIcon;
|
private TweetDuck.Core.Other.TrayIcon trayIcon;
|
||||||
private System.Windows.Forms.ToolTip toolTip;
|
private System.Windows.Forms.ToolTip toolTip;
|
||||||
|
private System.Windows.Forms.Timer timerResize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,133 +1,170 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Windows.Forms;
|
|
||||||
using CefSharp;
|
|
||||||
using CefSharp.WinForms;
|
|
||||||
using TweetDck.Configuration;
|
|
||||||
using TweetDck.Core.Handling;
|
|
||||||
using TweetDck.Core.Other;
|
|
||||||
using TweetDck.Resources;
|
|
||||||
using TweetDck.Core.Controls;
|
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using TweetDck.Updates;
|
using System.Threading.Tasks;
|
||||||
using TweetDck.Plugins;
|
using System.Windows.Forms;
|
||||||
using TweetDck.Plugins.Events;
|
using TweetDuck.Configuration;
|
||||||
|
using TweetDuck.Core.Bridge;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Handling;
|
||||||
|
using TweetDuck.Core.Handling.General;
|
||||||
|
using TweetDuck.Core.Management;
|
||||||
|
using TweetDuck.Core.Notification;
|
||||||
|
using TweetDuck.Core.Notification.Screenshot;
|
||||||
|
using TweetDuck.Core.Other;
|
||||||
|
using TweetDuck.Core.Other.Analytics;
|
||||||
|
using TweetDuck.Core.Other.Settings.Dialogs;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetDuck.Plugins;
|
||||||
|
using TweetDuck.Resources;
|
||||||
|
using TweetDuck.Updates;
|
||||||
|
using TweetLib.Core.Features.Plugins.Events;
|
||||||
|
using TweetLib.Core.Features.Updates;
|
||||||
|
|
||||||
namespace TweetDck.Core{
|
namespace TweetDuck.Core{
|
||||||
sealed partial class FormBrowser : Form{
|
sealed partial class FormBrowser : Form, AnalyticsFile.IProvider{
|
||||||
private static UserConfig Config{
|
private static UserConfig Config => Program.Config.User;
|
||||||
get{
|
|
||||||
return Program.UserConfig;
|
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 string UpdateInstallerPath { get; private set; }
|
public string UpdateInstallerPath { get; private set; }
|
||||||
|
private bool ignoreUpdateCheckError;
|
||||||
|
|
||||||
|
public AnalyticsFile AnalyticsFile => analytics?.File ?? AnalyticsFile.Dummy;
|
||||||
|
|
||||||
private readonly ChromiumWebBrowser browser;
|
private readonly TweetDeckBrowser browser;
|
||||||
private readonly PluginManager plugins;
|
private readonly PluginManager plugins;
|
||||||
private readonly UpdateHandler updates;
|
private readonly UpdateHandler updates;
|
||||||
|
private readonly FormNotificationTweet notification;
|
||||||
|
private readonly ContextMenu contextMenu;
|
||||||
|
private readonly UpdateBridge updateBridge;
|
||||||
|
|
||||||
private FormSettings currentFormSettings;
|
|
||||||
private FormAbout currentFormAbout;
|
|
||||||
private FormPlugins currentFormPlugins;
|
|
||||||
private bool isLoaded;
|
private bool isLoaded;
|
||||||
|
|
||||||
private FormWindowState prevState;
|
private FormWindowState prevState;
|
||||||
|
|
||||||
|
private TweetScreenshotManager notificationScreenshotManager;
|
||||||
|
private VideoPlayer videoPlayer;
|
||||||
|
private AnalyticsManager analytics;
|
||||||
|
|
||||||
public FormBrowser(PluginManager pluginManager){
|
public FormBrowser(){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName;
|
Text = Program.BrandName;
|
||||||
|
|
||||||
this.plugins = pluginManager;
|
this.plugins = new PluginManager(this, Program.Config.Plugins, Program.PluginPath, Program.PluginDataPath);
|
||||||
this.plugins.Reloaded += plugins_Reloaded;
|
this.plugins.Reloaded += plugins_Reloaded;
|
||||||
this.plugins.Config.PluginChangedState += plugins_PluginChangedState;
|
this.plugins.Executed += plugins_Executed;
|
||||||
|
this.plugins.Reload();
|
||||||
|
|
||||||
FormNotification notification = CreateNotificationForm(true);
|
this.notification = new FormNotificationTweet(this, plugins);
|
||||||
notification.CanMoveWindow = () => false;
|
this.notification.Show();
|
||||||
notification.Show();
|
|
||||||
|
this.updates = new UpdateHandler(new UpdateCheckClient(Program.InstallerPath), TaskScheduler.FromCurrentSynchronizationContext());
|
||||||
|
this.updates.CheckFinished += updates_CheckFinished;
|
||||||
|
|
||||||
this.browser = new ChromiumWebBrowser("https://tweetdeck.twitter.com/"){
|
this.updateBridge = new UpdateBridge(updates, this);
|
||||||
MenuHandler = new ContextMenuBrowser(this),
|
this.updateBridge.UpdateAccepted += updateBridge_UpdateAccepted;
|
||||||
DialogHandler = new DialogHandlerBrowser(this),
|
this.updateBridge.UpdateDelayed += updateBridge_UpdateDelayed;
|
||||||
LifeSpanHandler = new LifeSpanHandler()
|
this.updateBridge.UpdateDismissed += updateBridge_UpdateDismissed;
|
||||||
|
|
||||||
|
this.browser = new TweetDeckBrowser(this, plugins, new TweetDeckBridge.Browser(this, notification), updateBridge);
|
||||||
|
this.contextMenu = ContextMenuBrowser.CreateMenu(this);
|
||||||
|
|
||||||
|
Controls.Add(new MenuStrip{ Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update
|
||||||
|
|
||||||
|
Disposed += (sender, args) => {
|
||||||
|
Config.MuteToggled -= Config_MuteToggled;
|
||||||
|
Config.TrayBehaviorChanged -= Config_TrayBehaviorChanged;
|
||||||
|
|
||||||
|
browser.Dispose();
|
||||||
|
updates.Dispose();
|
||||||
|
contextMenu.Dispose();
|
||||||
|
|
||||||
|
notificationScreenshotManager?.Dispose();
|
||||||
|
videoPlayer?.Dispose();
|
||||||
|
analytics?.Dispose();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.browser.LoadingStateChanged += Browser_LoadingStateChanged;
|
Config.MuteToggled += Config_MuteToggled;
|
||||||
this.browser.FrameLoadEnd += Browser_FrameLoadEnd;
|
|
||||||
this.browser.RegisterJsObject("$TD", new TweetDeckBridge(this, notification));
|
|
||||||
this.browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
|
|
||||||
|
|
||||||
Controls.Add(browser);
|
|
||||||
|
|
||||||
Disposed += (sender, args) => browser.Dispose();
|
|
||||||
|
|
||||||
this.trayIcon.ClickRestore += trayIcon_ClickRestore;
|
this.trayIcon.ClickRestore += trayIcon_ClickRestore;
|
||||||
this.trayIcon.ClickClose += trayIcon_ClickClose;
|
this.trayIcon.ClickClose += trayIcon_ClickClose;
|
||||||
Config.TrayBehaviorChanged += Config_TrayBehaviorChanged;
|
Config.TrayBehaviorChanged += Config_TrayBehaviorChanged;
|
||||||
|
|
||||||
UpdateTrayIcon();
|
UpdateTray();
|
||||||
|
|
||||||
this.updates = new UpdateHandler(browser, this);
|
if (Config.MuteNotifications){
|
||||||
this.updates.UpdateAccepted += updates_UpdateAccepted;
|
UpdateFormIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Config.AllowDataCollection){
|
||||||
|
analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
RestoreWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowChildForm(Form form){
|
private void ShowChildForm(Form form){
|
||||||
|
form.VisibleChanged += (sender, args) => form.MoveToCenter(this);
|
||||||
form.Show(this);
|
form.Show(this);
|
||||||
form.Shown += (sender, args) => form.MoveToCenter(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ForceClose(){
|
public void ForceClose(){
|
||||||
trayIcon.Visible = false; // checked in FormClosing event
|
trayIcon.Visible = false; // checked in FormClosing event
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public FormNotification CreateNotificationForm(bool autoHide){
|
|
||||||
return new FormNotification(this, plugins, autoHide);
|
|
||||||
}
|
|
||||||
|
|
||||||
// window setup
|
// window setup
|
||||||
|
|
||||||
private void SetupWindow(){
|
private void RestoreWindow(){
|
||||||
Config.BrowserWindow.Restore(this, true);
|
Config.BrowserWindow.Restore(this, true);
|
||||||
prevState = WindowState;
|
prevState = WindowState;
|
||||||
isLoaded = true;
|
isLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateTrayIcon(){
|
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();
|
trayIcon.Visible = Config.TrayBehavior.ShouldDisplayIcon();
|
||||||
}
|
}
|
||||||
|
|
||||||
// active event handlers
|
// event handlers
|
||||||
|
|
||||||
private void Browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
|
private void timerResize_Tick(object sender, EventArgs e){
|
||||||
if (!e.IsLoading){
|
FormBrowser_ResizeEnd(this, e); // also stops timer
|
||||||
browser.AddWordToDictionary("tweetdeck");
|
|
||||||
browser.AddWordToDictionary("TweetDeck");
|
|
||||||
browser.AddWordToDictionary("tweetduck");
|
|
||||||
browser.AddWordToDictionary("TweetDuck");
|
|
||||||
browser.AddWordToDictionary("TD");
|
|
||||||
|
|
||||||
Invoke(new Action(SetupWindow));
|
|
||||||
browser.LoadingStateChanged -= Browser_LoadingStateChanged;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
|
||||||
if (e.Frame.IsMain){
|
|
||||||
ScriptLoader.ExecuteFile(e.Frame, "code.js");
|
|
||||||
|
|
||||||
if (plugins.HasAnyPlugin(PluginEnvironment.Browser)){
|
|
||||||
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginBrowserScriptFile);
|
|
||||||
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginGlobalScriptFile);
|
|
||||||
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Browser, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FormBrowser_Activated(object sender, EventArgs e){
|
private void FormBrowser_Activated(object sender, EventArgs e){
|
||||||
if (!isLoaded)return;
|
if (!isLoaded)return;
|
||||||
|
|
||||||
trayIcon.HasNotifications = false;
|
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){
|
private void FormBrowser_Resize(object sender, EventArgs e){
|
||||||
@@ -145,12 +182,18 @@ namespace TweetDck.Core{
|
|||||||
FormBrowser_ResizeEnd(sender, e);
|
FormBrowser_ResizeEnd(sender, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else{
|
||||||
|
timerResize.Stop();
|
||||||
|
timerResize.Start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FormBrowser_ResizeEnd(object sender, EventArgs e){ // also triggers when the window moves
|
private void FormBrowser_ResizeEnd(object sender, EventArgs e){ // also triggers when the window moves
|
||||||
if (!isLoaded)return;
|
if (!isLoaded)return;
|
||||||
|
|
||||||
if (Location.X != -32000){
|
timerResize.Stop();
|
||||||
|
|
||||||
|
if (Location != ControlExtensions.InvisibleLocation){
|
||||||
Config.BrowserWindow.Save(this);
|
Config.BrowserWindow.Save(this);
|
||||||
Config.Save();
|
Config.Save();
|
||||||
}
|
}
|
||||||
@@ -165,126 +208,361 @@ namespace TweetDck.Core{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void FormBrowser_FormClosed(object sender, FormClosedEventArgs e){
|
||||||
|
if (isLoaded && UpdateInstallerPath == null){
|
||||||
|
updateBridge.Cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Config_MuteToggled(object sender, EventArgs e){
|
||||||
|
UpdateFormIcon();
|
||||||
|
AnalyticsFile.NotificationMutes.Trigger();
|
||||||
|
}
|
||||||
|
|
||||||
private void Config_TrayBehaviorChanged(object sender, EventArgs e){
|
private void Config_TrayBehaviorChanged(object sender, EventArgs e){
|
||||||
if (!isLoaded)return;
|
UpdateTray();
|
||||||
|
|
||||||
UpdateTrayIcon();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void trayIcon_ClickRestore(object sender, EventArgs e){
|
private void trayIcon_ClickRestore(object sender, EventArgs e){
|
||||||
if (!isLoaded)return;
|
|
||||||
|
|
||||||
isLoaded = false;
|
|
||||||
Show();
|
Show();
|
||||||
SetupWindow();
|
RestoreWindow();
|
||||||
Activate();
|
Activate();
|
||||||
UpdateTrayIcon();
|
UpdateTray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void trayIcon_ClickClose(object sender, EventArgs e){
|
private void trayIcon_ClickClose(object sender, EventArgs e){
|
||||||
if (!isLoaded)return;
|
|
||||||
|
|
||||||
ForceClose();
|
ForceClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void plugins_Reloaded(object sender, PluginLoadEventArgs e){
|
private void plugins_Reloaded(object sender, PluginErrorEventArgs e){
|
||||||
ReloadBrowser();
|
if (e.HasErrors){
|
||||||
}
|
FormMessage.Error("Error Loading Plugins", "The following plugins will not be available until the issues are resolved:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK);
|
||||||
|
|
||||||
private void plugins_PluginChangedState(object sender, PluginChangedStateEventArgs e){
|
|
||||||
browser.ExecuteScriptAsync("window.TDPF_setPluginState", e.Plugin, e.IsEnabled ? 1 : 0); // ExecuteScriptAsync cannot handle boolean values as of yet
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updates_UpdateAccepted(object sender, UpdateAcceptedEventArgs e){
|
|
||||||
Hide();
|
|
||||||
|
|
||||||
FormUpdateDownload downloadForm = new FormUpdateDownload(e.UpdateInfo);
|
|
||||||
downloadForm.MoveToCenter(this);
|
|
||||||
downloadForm.ShowDialog();
|
|
||||||
|
|
||||||
if (downloadForm.UpdateStatus == FormUpdateDownload.Status.Succeeded){
|
|
||||||
UpdateInstallerPath = downloadForm.InstallerPath;
|
|
||||||
ForceClose();
|
|
||||||
}
|
}
|
||||||
else if (downloadForm.UpdateStatus == FormUpdateDownload.Status.Manual){
|
|
||||||
ForceClose();
|
if (isLoaded){
|
||||||
|
browser.ReloadToTweetDeck();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void plugins_Executed(object sender, PluginErrorEventArgs e){
|
||||||
|
if (e.HasErrors){
|
||||||
|
FormMessage.Error("Error Executing Plugins", "Failed to execute the following plugins:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){
|
||||||
|
e.Result.Handle(update => {
|
||||||
|
string tag = update.VersionTag;
|
||||||
|
|
||||||
|
if (tag != Program.VersionTag && tag != Config.DismissedUpdate){
|
||||||
|
update.BeginSilentDownload();
|
||||||
|
browser.ShowUpdateNotification(tag, update.ReleaseNotes);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
updates.StartTimer();
|
||||||
|
}
|
||||||
|
}, ex => {
|
||||||
|
if (!ignoreUpdateCheckError){
|
||||||
|
Program.Reporter.HandleException("Update Check Error", "An error occurred while checking for updates.", true, ex);
|
||||||
|
updates.StartTimer();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ignoreUpdateCheckError = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBridge_UpdateAccepted(object sender, UpdateInfo update){
|
||||||
|
FormManager.CloseAllDialogs();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(Config.DismissedUpdate)){
|
||||||
|
Config.DismissedUpdate = null;
|
||||||
|
Config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnFinished(){
|
||||||
|
UpdateDownloadStatus status = update.DownloadStatus;
|
||||||
|
|
||||||
|
if (status == UpdateDownloadStatus.Done){
|
||||||
|
UpdateInstallerPath = 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)){
|
||||||
|
BrowserUtils.OpenExternalBrowser(Program.Website);
|
||||||
|
ForceClose();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (update.DownloadStatus.IsFinished(true)){
|
||||||
|
OnFinished();
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
Show();
|
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 updateBridge_UpdateDelayed(object sender, UpdateInfo update){
|
||||||
|
// stops the timer
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBridge_UpdateDismissed(object sender, UpdateInfo update){
|
||||||
|
Config.DismissedUpdate = update.VersionTag;
|
||||||
|
Config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void WndProc(ref Message m){
|
protected override void WndProc(ref Message m){
|
||||||
if (isLoaded && m.Msg == Program.WindowRestoreMessage){
|
if (isLoaded && m.Msg == Program.WindowRestoreMessage){
|
||||||
trayIcon_ClickRestore(trayIcon, new EventArgs());
|
if (WindowsUtils.CurrentProcessID == m.WParam.ToInt32()){
|
||||||
|
trayIcon_ClickRestore(trayIcon, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (browser.Ready && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN){
|
||||||
|
if (videoPlayer != null && videoPlayer.Running){
|
||||||
|
videoPlayer.Close();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
browser.OnMouseClickExtra(m.WParam);
|
||||||
|
AnalyticsFile.BrowserExtraMouseButtons.Trigger();
|
||||||
|
}
|
||||||
|
|
||||||
if (isLoaded && m.Msg == 0x210 && (m.WParam.ToInt32() & 0xFFFF) == 0x020B){ // WM_PARENTNOTIFY, WM_XBUTTONDOWN
|
|
||||||
browser.ExecuteScriptAsync("TDGF_onMouseClickExtra", (m.WParam.ToInt32() >> 16) & 0xFFFF);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
base.WndProc(ref m);
|
base.WndProc(ref m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bridge methods
|
||||||
|
|
||||||
|
public void PauseNotification(){
|
||||||
|
notification.PauseNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResumeNotification(){
|
||||||
|
notification.ResumeNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReinjectCustomCSS(string css){
|
||||||
|
browser.ReinjectCustomCSS(css);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReloadToTweetDeck(){
|
||||||
|
#if DEBUG
|
||||||
|
ScriptLoader.HotSwap();
|
||||||
|
#else
|
||||||
|
if (ModifierKeys.HasFlag(Keys.Shift)){
|
||||||
|
ScriptLoader.ClearCache();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ignoreUpdateCheckError = false;
|
||||||
|
browser.ReloadToTweetDeck();
|
||||||
|
AnalyticsFile.BrowserReloads.Trigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddSearchColumn(string query){
|
||||||
|
browser.AddSearchColumn(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TriggerTweetScreenshot(){
|
||||||
|
browser.TriggerTweetScreenshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReloadColumns(){
|
||||||
|
browser.ReloadColumns();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PlaySoundNotification(){
|
||||||
|
browser.PlaySoundNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyROT13(){
|
||||||
|
browser.ApplyROT13();
|
||||||
|
AnalyticsFile.UsedROT13.Trigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenDevTools(){
|
||||||
|
browser.OpenDevTools();
|
||||||
|
}
|
||||||
|
|
||||||
// callback handlers
|
// callback handlers
|
||||||
|
|
||||||
|
public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){
|
||||||
|
if (Config.FirstRun){
|
||||||
|
Config.FirstRun = false;
|
||||||
|
Config.AllowDataCollection = allowDataCollection;
|
||||||
|
Config.Save();
|
||||||
|
|
||||||
|
if (allowDataCollection && analytics == null){
|
||||||
|
analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showGuide){
|
||||||
|
FormGuide.Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenContextMenu(){
|
||||||
|
contextMenu.Show(this, PointToClient(Cursor.Position));
|
||||||
|
}
|
||||||
|
|
||||||
public void OpenSettings(){
|
public void OpenSettings(){
|
||||||
if (currentFormSettings != null){
|
OpenSettings(null);
|
||||||
currentFormSettings.BringToFront();
|
}
|
||||||
}
|
|
||||||
else{
|
public void OpenSettings(Type startTab){
|
||||||
|
if (!FormManager.TryBringToFront<FormSettings>()){
|
||||||
bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
|
bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
|
||||||
|
|
||||||
currentFormSettings = new FormSettings(this, plugins, updates);
|
FormSettings form = new FormSettings(this, plugins, updates, analytics, startTab);
|
||||||
|
|
||||||
currentFormSettings.FormClosed += (sender, args) => {
|
form.FormClosed += (sender, args) => {
|
||||||
currentFormSettings = null;
|
|
||||||
|
|
||||||
if (!prevEnableUpdateCheck && Config.EnableUpdateCheck){
|
if (!prevEnableUpdateCheck && Config.EnableUpdateCheck){
|
||||||
Config.DismissedUpdate = string.Empty;
|
Config.DismissedUpdate = null;
|
||||||
Config.Save();
|
Config.Save();
|
||||||
updates.Check(false);
|
|
||||||
|
updates.Check(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Config.EnableTrayHighlight){
|
if (!Config.EnableTrayHighlight){
|
||||||
trayIcon.HasNotifications = false;
|
trayIcon.HasNotifications = false;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
ShowChildForm(currentFormSettings);
|
if (Config.AllowDataCollection){
|
||||||
|
if (analytics == null){
|
||||||
|
analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (analytics != null){
|
||||||
|
analytics.Dispose();
|
||||||
|
analytics = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
BrowserCache.RefreshTimer();
|
||||||
|
|
||||||
|
if (form.ShouldReloadBrowser){
|
||||||
|
FormManager.TryFind<FormPlugins>()?.Close();
|
||||||
|
plugins.Reload(); // also reloads the browser
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
browser.UpdateProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.RequiresResize = true;
|
||||||
|
form.Dispose();
|
||||||
|
};
|
||||||
|
|
||||||
|
AnalyticsFile.OpenOptions.Trigger();
|
||||||
|
ShowChildForm(form);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OpenAbout(){
|
public void OpenAbout(){
|
||||||
if (currentFormAbout != null){
|
if (!FormManager.TryBringToFront<FormAbout>()){
|
||||||
currentFormAbout.BringToFront();
|
AnalyticsFile.OpenAbout.Trigger();
|
||||||
}
|
ShowChildForm(new FormAbout());
|
||||||
else{
|
|
||||||
currentFormAbout = new FormAbout();
|
|
||||||
currentFormAbout.FormClosed += (sender, args) => currentFormAbout = null;
|
|
||||||
ShowChildForm(currentFormAbout);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OpenPlugins(){
|
public void OpenPlugins(){
|
||||||
if (currentFormPlugins != null){
|
if (!FormManager.TryBringToFront<FormPlugins>()){
|
||||||
currentFormPlugins.BringToFront();
|
AnalyticsFile.OpenPlugins.Trigger();
|
||||||
}
|
ShowChildForm(new FormPlugins(plugins));
|
||||||
else{
|
|
||||||
currentFormPlugins = new FormPlugins(plugins);
|
|
||||||
currentFormPlugins.FormClosed += (sender, args) => currentFormPlugins = null;
|
|
||||||
ShowChildForm(currentFormPlugins);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnTweetNotification(){
|
public 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnTweetNotification(){ // may be called multiple times, once for each type of notification
|
||||||
if (Config.EnableTrayHighlight && !ContainsFocus){
|
if (Config.EnableTrayHighlight && !ContainsFocus){
|
||||||
trayIcon.HasNotifications = true;
|
trayIcon.HasNotifications = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnTweetSound(){
|
||||||
|
AnalyticsFile.SoundNotifications.Trigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PlayVideo(string url, string username){
|
||||||
|
if (string.IsNullOrEmpty(url)){
|
||||||
|
videoPlayer?.Close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoPlayer == null){
|
||||||
|
videoPlayer = new VideoPlayer(this);
|
||||||
|
|
||||||
|
videoPlayer.ProcessExited += (sender, args) => {
|
||||||
|
browser.HideVideoOverlay(true);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
videoPlayer.Launch(url, username);
|
||||||
|
AnalyticsFile.VideoPlays.Trigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ProcessBrowserKey(Keys key){
|
||||||
|
if (videoPlayer != null && videoPlayer.Running){
|
||||||
|
videoPlayer.SendKeyEvent(key);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowTweetDetail(string columnId, string chirpId, string fallbackUrl){
|
||||||
|
Activate();
|
||||||
|
|
||||||
|
if (!browser.IsTweetDeckWebsite){
|
||||||
|
FormMessage.Error("View Tweet Detail", "TweetDeck is not currently loaded.", FormMessage.OK);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.FinishCurrentNotification();
|
||||||
|
browser.ShowTweetDetail(columnId, chirpId, fallbackUrl);
|
||||||
|
AnalyticsFile.TweetDetails.Trigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnTweetScreenshotReady(string html, int width){
|
||||||
|
if (notificationScreenshotManager == null){
|
||||||
|
notificationScreenshotManager = new TweetScreenshotManager(this, plugins);
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationScreenshotManager.Trigger(html, width);
|
||||||
|
AnalyticsFile.TweetScreenshots.Trigger();
|
||||||
|
}
|
||||||
|
|
||||||
public void DisplayTooltip(string text){
|
public void DisplayTooltip(string text){
|
||||||
if (string.IsNullOrEmpty(text)){
|
if (string.IsNullOrEmpty(text)){
|
||||||
toolTip.Hide(this);
|
toolTip.Hide(this);
|
||||||
@@ -295,17 +573,5 @@ namespace TweetDck.Core{
|
|||||||
toolTip.Show(text, this, position);
|
toolTip.Show(text, this, position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnImagePasted(){
|
|
||||||
browser.ExecuteScriptAsync("TDGF_tryPasteImage", new object[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnImagePastedFinish(){
|
|
||||||
browser.ExecuteScriptAsync("TDGF_tryPasteImageFinish", new object[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ReloadBrowser(){
|
|
||||||
browser.ExecuteScriptAsync("window.location.reload()");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
Core/FormManager.cs
Normal file
28
Core/FormManager.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core{
|
||||||
|
static class FormManager{
|
||||||
|
public static T TryFind<T>() where T : Form{
|
||||||
|
return Application.OpenForms.OfType<T>().FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryBringToFront<T>() where T : Form{
|
||||||
|
T form = TryFind<T>();
|
||||||
|
|
||||||
|
if (form != null){
|
||||||
|
form.BringToFront();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CloseAllDialogs(){
|
||||||
|
foreach(IAppDialog dialog in Application.OpenForms.OfType<IAppDialog>().Reverse()){
|
||||||
|
((Form)dialog).Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IAppDialog{}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,367 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using CefSharp;
|
|
||||||
using CefSharp.WinForms;
|
|
||||||
using TweetDck.Configuration;
|
|
||||||
using TweetDck.Core.Handling;
|
|
||||||
using TweetDck.Resources;
|
|
||||||
using TweetDck.Core.Utils;
|
|
||||||
using TweetDck.Plugins;
|
|
||||||
|
|
||||||
namespace TweetDck.Core{
|
|
||||||
sealed partial class FormNotification : Form{
|
|
||||||
private const string NotificationScriptFile = "notification.js";
|
|
||||||
|
|
||||||
private static readonly string NotificationScriptIdentifier = ScriptLoader.GetRootIdentifier(NotificationScriptFile);
|
|
||||||
private static readonly string PluginScriptIdentifier = ScriptLoader.GetRootIdentifier(PluginManager.PluginNotificationScriptFile);
|
|
||||||
|
|
||||||
public Func<bool> CanMoveWindow = () => true;
|
|
||||||
|
|
||||||
private readonly Form owner;
|
|
||||||
private readonly PluginManager plugins;
|
|
||||||
private readonly ChromiumWebBrowser browser;
|
|
||||||
|
|
||||||
private readonly Queue<TweetNotification> tweetQueue = new Queue<TweetNotification>(4);
|
|
||||||
private readonly bool autoHide;
|
|
||||||
private int timeLeft, totalTime;
|
|
||||||
|
|
||||||
private readonly NativeMethods.HookProc mouseHookDelegate;
|
|
||||||
private IntPtr mouseHook;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly string notificationJS;
|
|
||||||
private readonly string pluginJS;
|
|
||||||
|
|
||||||
protected override bool ShowWithoutActivation{
|
|
||||||
get{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool FreezeTimer { get; set; }
|
|
||||||
public bool ContextMenuOpen { get; set; }
|
|
||||||
public string CurrentUrl { get; private set; }
|
|
||||||
|
|
||||||
public EventHandler Initialized;
|
|
||||||
private bool isInitialized;
|
|
||||||
|
|
||||||
private static int BaseClientWidth{
|
|
||||||
get{
|
|
||||||
int level = TweetNotification.FontSizeLevel;
|
|
||||||
return level == 0 ? 284 : (int)Math.Round(284.0*(1.0+0.05*level));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int BaseClientHeight{
|
|
||||||
get{
|
|
||||||
int level = TweetNotification.FontSizeLevel;
|
|
||||||
return level == 0 ? 118 : (int)Math.Round(118.0*(1.0+0.075*level));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public FormNotification(FormBrowser owner, PluginManager pluginManager, bool autoHide){
|
|
||||||
InitializeComponent();
|
|
||||||
|
|
||||||
Text = Program.BrandName;
|
|
||||||
|
|
||||||
this.owner = owner;
|
|
||||||
this.plugins = pluginManager;
|
|
||||||
this.autoHide = autoHide;
|
|
||||||
|
|
||||||
owner.FormClosed += (sender, args) => Close();
|
|
||||||
|
|
||||||
notificationJS = ScriptLoader.LoadResource(NotificationScriptFile);
|
|
||||||
pluginJS = ScriptLoader.LoadResource(PluginManager.PluginNotificationScriptFile);
|
|
||||||
|
|
||||||
browser = new ChromiumWebBrowser("about:blank"){
|
|
||||||
MenuHandler = new ContextMenuNotification(this, autoHide),
|
|
||||||
LifeSpanHandler = new LifeSpanHandler()
|
|
||||||
};
|
|
||||||
|
|
||||||
browser.IsBrowserInitializedChanged += Browser_IsBrowserInitializedChanged;
|
|
||||||
browser.FrameLoadEnd += Browser_FrameLoadEnd;
|
|
||||||
browser.RegisterJsObject("$TD", new TweetDeckBridge(owner, this));
|
|
||||||
browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
|
|
||||||
|
|
||||||
panelBrowser.Controls.Add(browser);
|
|
||||||
|
|
||||||
if (autoHide){
|
|
||||||
Program.UserConfig.MuteToggled += Config_MuteToggled;
|
|
||||||
Disposed += (sender, args) => Program.UserConfig.MuteToggled -= Config_MuteToggled;
|
|
||||||
}
|
|
||||||
|
|
||||||
mouseHookDelegate = MouseHookProc;
|
|
||||||
|
|
||||||
Disposed += FormNotification_Disposed;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// mouse wheel hook
|
|
||||||
|
|
||||||
private void StartMouseHook(){
|
|
||||||
if (mouseHook == IntPtr.Zero){
|
|
||||||
mouseHook = NativeMethods.SetWindowsHookEx(NativeMethods.WH_MOUSE_LL, mouseHookDelegate, IntPtr.Zero, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StopMouseHook(){
|
|
||||||
if (mouseHook != IntPtr.Zero){
|
|
||||||
NativeMethods.UnhookWindowsHookEx(mouseHook);
|
|
||||||
mouseHook = IntPtr.Zero;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam){
|
|
||||||
if (!ContainsFocus && wParam.ToInt32() == NativeMethods.WH_MOUSEWHEEL && browser.Bounds.Contains(PointToClient(Cursor.Position))){
|
|
||||||
// fuck it, Activate() doesn't work with this
|
|
||||||
Point prevPos = Cursor.Position;
|
|
||||||
Cursor.Position = PointToScreen(new Point(0, -1));
|
|
||||||
NativeMethods.SimulateMouseClick(NativeMethods.MouseButton.Left);
|
|
||||||
Cursor.Position = prevPos;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NativeMethods.CallNextHookEx(mouseHook, nCode, wParam, lParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
// event handlers
|
|
||||||
|
|
||||||
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){
|
|
||||||
FinishCurrentTweet();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Config_MuteToggled(object sender, EventArgs e){
|
|
||||||
if (Program.UserConfig.MuteNotifications){
|
|
||||||
HideNotification(true);
|
|
||||||
}
|
|
||||||
else if (tweetQueue.Count > 0){
|
|
||||||
LoadNextNotification();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Browser_IsBrowserInitializedChanged(object sender, IsBrowserInitializedChangedEventArgs e){
|
|
||||||
if (e.IsBrowserInitialized && Initialized != null){
|
|
||||||
Initialized(this, new EventArgs());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
|
||||||
if (!e.Frame.IsMain)return;
|
|
||||||
|
|
||||||
if (!isInitialized && !Program.UserConfig.NotificationLegacyLoad){
|
|
||||||
isInitialized = true;
|
|
||||||
|
|
||||||
if (Initialized != null){
|
|
||||||
Initialized(this, new EventArgs());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (notificationJS != null && browser.Address != "about:blank"){
|
|
||||||
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 FormNotification_FormClosing(object sender, FormClosingEventArgs e){
|
|
||||||
if (e.CloseReason == CloseReason.UserClosing){
|
|
||||||
HideNotification(false);
|
|
||||||
tweetQueue.Clear();
|
|
||||||
e.Cancel = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FormNotification_Disposed(object sender, EventArgs e){
|
|
||||||
browser.Dispose();
|
|
||||||
StopMouseHook();
|
|
||||||
}
|
|
||||||
|
|
||||||
// notification methods
|
|
||||||
|
|
||||||
public void ShowNotification(TweetNotification notification){
|
|
||||||
if (Program.UserConfig.MuteNotifications){
|
|
||||||
tweetQueue.Enqueue(notification);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
tweetQueue.Enqueue(notification);
|
|
||||||
UpdateTitle();
|
|
||||||
|
|
||||||
if (!timerProgress.Enabled){
|
|
||||||
LoadNextNotification();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowNotificationForSettings(bool reset){
|
|
||||||
if (reset){
|
|
||||||
LoadTweet(TweetNotification.ExampleTweet);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
MoveToVisibleLocation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HideNotification(bool loadBlank){
|
|
||||||
if (loadBlank || Program.UserConfig.NotificationLegacyLoad){
|
|
||||||
browser.LoadHtml("", "about:blank");
|
|
||||||
}
|
|
||||||
|
|
||||||
Location = new Point(-32000, -32000);
|
|
||||||
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0;
|
|
||||||
timerProgress.Stop();
|
|
||||||
|
|
||||||
StopMouseHook();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnNotificationReady(){
|
|
||||||
UpdateTitle();
|
|
||||||
MoveToVisibleLocation();
|
|
||||||
timerProgress.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FinishCurrentTweet(){
|
|
||||||
if (tweetQueue.Count > 0){
|
|
||||||
LoadNextNotification();
|
|
||||||
}
|
|
||||||
else if (autoHide){
|
|
||||||
HideNotification(true);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
timerProgress.Stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadNextNotification(){
|
|
||||||
LoadTweet(tweetQueue.Dequeue());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadTweet(TweetNotification tweet){
|
|
||||||
CurrentUrl = tweet.Url;
|
|
||||||
|
|
||||||
timerProgress.Stop();
|
|
||||||
totalTime = timeLeft = tweet.GetDisplayDuration(Program.UserConfig.NotificationDurationValue);
|
|
||||||
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0;
|
|
||||||
|
|
||||||
browser.LoadHtml(tweet.GenerateHtml(), "http://tweetdeck.twitter.com/?"+DateTime.Now.Ticks);
|
|
||||||
|
|
||||||
if (Program.UserConfig.NotificationLegacyLoad){
|
|
||||||
OnNotificationReady();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void MoveToVisibleLocation(){
|
|
||||||
UserConfig config = Program.UserConfig;
|
|
||||||
|
|
||||||
if (RequiresResize){
|
|
||||||
RequiresResize = false;
|
|
||||||
|
|
||||||
if (config.DisplayNotificationTimer){
|
|
||||||
ClientSize = new Size(BaseClientWidth, BaseClientHeight+4);
|
|
||||||
progressBarTimer.Visible = true;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
ClientSize = new Size(BaseClientWidth, BaseClientHeight);
|
|
||||||
progressBarTimer.Visible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
panelBrowser.Height = BaseClientHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
Screen screen = Screen.FromControl(owner);
|
|
||||||
|
|
||||||
if (config.NotificationDisplay > 0 && config.NotificationDisplay <= Screen.AllScreens.Length){
|
|
||||||
screen = Screen.AllScreens[config.NotificationDisplay-1];
|
|
||||||
}
|
|
||||||
|
|
||||||
bool needsReactivating = Location.X == -32000;
|
|
||||||
int edgeDist = config.NotificationEdgeDistance;
|
|
||||||
|
|
||||||
switch(config.NotificationPosition){
|
|
||||||
case TweetNotification.Position.TopLeft:
|
|
||||||
Location = new Point(screen.WorkingArea.X+edgeDist, screen.WorkingArea.Y+edgeDist);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TweetNotification.Position.TopRight:
|
|
||||||
Location = new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+edgeDist);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TweetNotification.Position.BottomLeft:
|
|
||||||
Location = new Point(screen.WorkingArea.X+edgeDist, screen.WorkingArea.Y+screen.WorkingArea.Height-edgeDist-Height);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TweetNotification.Position.BottomRight:
|
|
||||||
Location = new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+screen.WorkingArea.Height-edgeDist-Height);
|
|
||||||
break;
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
Location = config.CustomNotificationPosition;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (needsReactivating){
|
|
||||||
NativeMethods.SetFormPos(this, NativeMethods.HWND_TOPMOST, NativeMethods.SWP_NOACTIVATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
StartMouseHook();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateTitle(){
|
|
||||||
Text = tweetQueue.Count > 0 ? Program.BrandName+" ("+tweetQueue.Count+" more left)" : Program.BrandName;
|
|
||||||
}
|
|
||||||
|
|
||||||
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,16 +0,0 @@
|
|||||||
using CefSharp;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace TweetDck.Core.Handling{
|
|
||||||
class BrowserProcessHandler : IBrowserProcessHandler{
|
|
||||||
void IBrowserProcessHandler.OnContextInitialized(){
|
|
||||||
using(IRequestContext ctx = Cef.GetGlobalRequestContext()){
|
|
||||||
string err;
|
|
||||||
ctx.SetPreference("browser.enable_spellchecking", Program.UserConfig.EnableSpellCheck, out err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IBrowserProcessHandler.OnScheduleMessagePumpWork(long delay){}
|
|
||||||
void IDisposable.Dispose(){}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +1,221 @@
|
|||||||
using CefSharp;
|
using System;
|
||||||
using System;
|
using System.Text.RegularExpressions;
|
||||||
using System.IO;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDck.Core.Utils;
|
using CefSharp;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
using System.Linq;
|
||||||
|
using TweetDuck.Configuration;
|
||||||
|
using TweetDuck.Core.Bridge;
|
||||||
|
using TweetDuck.Core.Management;
|
||||||
|
using TweetDuck.Core.Notification;
|
||||||
|
using TweetDuck.Core.Other;
|
||||||
|
using TweetDuck.Core.Other.Analytics;
|
||||||
|
using TweetDuck.Resources;
|
||||||
|
using TweetLib.Core.Features.Twitter;
|
||||||
|
using TweetLib.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDck.Core.Handling{
|
namespace TweetDuck.Core.Handling{
|
||||||
abstract class ContextMenuBase : IContextMenuHandler{
|
abstract class ContextMenuBase : IContextMenuHandler{
|
||||||
private const int MenuOpenLinkUrl = 26500;
|
protected static UserConfig Config => Program.Config.User;
|
||||||
private const int MenuCopyLinkUrl = 26501;
|
|
||||||
private const int MenuOpenImage = 26502;
|
private static ImageQuality ImageQuality => Config.TwitterImageQuality;
|
||||||
private const int MenuSaveImage = 26503;
|
|
||||||
private const int MenuCopyImageUrl = 26504;
|
private const CefMenuCommand MenuOpenLinkUrl = (CefMenuCommand)26500;
|
||||||
|
private const CefMenuCommand MenuCopyLinkUrl = (CefMenuCommand)26501;
|
||||||
|
private const CefMenuCommand MenuCopyUsername = (CefMenuCommand)26502;
|
||||||
|
private const CefMenuCommand MenuViewImage = (CefMenuCommand)26503;
|
||||||
|
private const CefMenuCommand MenuOpenMediaUrl = (CefMenuCommand)26504;
|
||||||
|
private const CefMenuCommand MenuCopyMediaUrl = (CefMenuCommand)26505;
|
||||||
|
private const CefMenuCommand MenuSaveMedia = (CefMenuCommand)26506;
|
||||||
|
private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand)26507;
|
||||||
|
private const CefMenuCommand MenuSearchInBrowser = (CefMenuCommand)26508;
|
||||||
|
private const CefMenuCommand MenuReadApplyROT13 = (CefMenuCommand)26509;
|
||||||
|
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand)26599;
|
||||||
|
|
||||||
|
protected ContextInfo.ContextData Context { get; private set; }
|
||||||
|
|
||||||
|
private readonly AnalyticsFile.IProvider analytics;
|
||||||
|
|
||||||
|
protected ContextMenuBase(AnalyticsFile.IProvider analytics){
|
||||||
|
this.analytics = analytics;
|
||||||
|
}
|
||||||
|
|
||||||
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
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 (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
|
||||||
model.AddItem((CefMenuCommand)MenuOpenLinkUrl, "Open link in browser");
|
Context = TweetDeckBridge.ContextInfo.Reset();
|
||||||
model.AddItem((CefMenuCommand)MenuCopyLinkUrl, "Copy link address");
|
}
|
||||||
|
else{
|
||||||
|
Context = TweetDeckBridge.ContextInfo.Create(parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection) && !parameters.TypeFlags.HasFlag(ContextMenuType.Editable)){
|
||||||
|
model.AddItem(MenuSearchInBrowser, "Search in browser");
|
||||||
|
model.AddSeparator();
|
||||||
|
model.AddItem(MenuReadApplyROT13, "Apply ROT13");
|
||||||
|
model.AddSeparator();
|
||||||
|
}
|
||||||
|
|
||||||
|
string TextOpen(string name) => "Open "+name+" in browser";
|
||||||
|
string TextCopy(string name) => "Copy "+name+" address";
|
||||||
|
string TextSave(string name) => "Save "+name+" as...";
|
||||||
|
|
||||||
|
if (Context.Types.HasFlag(ContextInfo.ContextType.Link) && !Context.UnsafeLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal)){
|
||||||
|
if (TwitterUtils.RegexAccount.IsMatch(Context.UnsafeLinkUrl)){
|
||||||
|
model.AddItem(MenuOpenLinkUrl, TextOpen("account"));
|
||||||
|
model.AddItem(MenuCopyLinkUrl, TextCopy("account"));
|
||||||
|
model.AddItem(MenuCopyUsername, "Copy account username");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
model.AddItem(MenuOpenLinkUrl, TextOpen("link"));
|
||||||
|
model.AddItem(MenuCopyLinkUrl, TextCopy("link"));
|
||||||
|
}
|
||||||
|
|
||||||
model.AddSeparator();
|
model.AddSeparator();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents){
|
if (Context.Types.HasFlag(ContextInfo.ContextType.Video)){
|
||||||
model.AddItem((CefMenuCommand)MenuOpenImage, "Open image in browser");
|
model.AddItem(MenuOpenMediaUrl, TextOpen("video"));
|
||||||
model.AddItem((CefMenuCommand)MenuSaveImage, "Save image as...");
|
model.AddItem(MenuCopyMediaUrl, TextCopy("video"));
|
||||||
model.AddItem((CefMenuCommand)MenuCopyImageUrl, "Copy image address");
|
model.AddItem(MenuSaveMedia, TextSave("video"));
|
||||||
|
model.AddSeparator();
|
||||||
|
}
|
||||||
|
else if (Context.Types.HasFlag(ContextInfo.ContextType.Image) && Context.MediaUrl != TweetNotification.AppLogo.Url){
|
||||||
|
model.AddItem(MenuViewImage, "View image in photo viewer");
|
||||||
|
model.AddItem(MenuOpenMediaUrl, TextOpen("image"));
|
||||||
|
model.AddItem(MenuCopyMediaUrl, TextCopy("image"));
|
||||||
|
model.AddItem(MenuSaveMedia, TextSave("image"));
|
||||||
|
|
||||||
|
if (Context.Chirp.Images.Length > 1){
|
||||||
|
model.AddItem(MenuSaveTweetImages, TextSave("all images"));
|
||||||
|
}
|
||||||
|
|
||||||
model.AddSeparator();
|
model.AddSeparator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
|
public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
|
||||||
switch((int)commandId){
|
Control control = browserControl.AsControl();
|
||||||
|
|
||||||
|
switch(commandId){
|
||||||
case MenuOpenLinkUrl:
|
case MenuOpenLinkUrl:
|
||||||
BrowserUtils.OpenExternalBrowser(parameters.LinkUrl);
|
OpenBrowser(control, Context.LinkUrl);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MenuCopyLinkUrl:
|
case MenuCopyLinkUrl:
|
||||||
Clipboard.SetText(string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedLink) ? parameters.UnfilteredLinkUrl : TweetDeckBridge.LastRightClickedLink, TextDataFormat.UnicodeText);
|
SetClipboardText(control, Context.UnsafeLinkUrl);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MenuOpenImage:
|
case MenuCopyUsername: {
|
||||||
BrowserUtils.OpenExternalBrowser(parameters.SourceUrl);
|
string url = Context.UnsafeLinkUrl;
|
||||||
|
Match match = TwitterUtils.RegexAccount.Match(url);
|
||||||
|
|
||||||
|
SetClipboardText(control, match.Success ? match.Groups[1].Value : url);
|
||||||
|
control.InvokeAsyncSafe(analytics.AnalyticsFile.CopiedUsernames.Trigger);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MenuOpenMediaUrl:
|
||||||
|
OpenBrowser(control, TwitterUtils.GetMediaLink(Context.MediaUrl, ImageQuality));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MenuSaveImage:
|
case MenuCopyMediaUrl:
|
||||||
string fileName = GetImageFileName(parameters.SourceUrl);
|
SetClipboardText(control, TwitterUtils.GetMediaLink(Context.MediaUrl, ImageQuality));
|
||||||
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;
|
break;
|
||||||
|
|
||||||
case MenuCopyImageUrl:
|
case MenuViewImage: {
|
||||||
Clipboard.SetText(parameters.SourceUrl, TextDataFormat.UnicodeText);
|
string url = Context.MediaUrl;
|
||||||
|
|
||||||
|
control.InvokeAsyncSafe(() => {
|
||||||
|
TwitterUtils.ViewImage(url, ImageQuality);
|
||||||
|
analytics.AnalyticsFile.ViewedImages.Trigger();
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MenuSaveMedia: {
|
||||||
|
bool isVideo = Context.Types.HasFlag(ContextInfo.ContextType.Video);
|
||||||
|
string url = Context.MediaUrl;
|
||||||
|
string username = Context.Chirp.Authors.LastOrDefault();
|
||||||
|
|
||||||
|
control.InvokeAsyncSafe(() => {
|
||||||
|
if (isVideo){
|
||||||
|
TwitterUtils.DownloadVideo(url, username);
|
||||||
|
analytics.AnalyticsFile.DownloadedVideos.Trigger();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
TwitterUtils.DownloadImage(url, username, ImageQuality);
|
||||||
|
analytics.AnalyticsFile.DownloadedImages.Trigger();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MenuSaveTweetImages: {
|
||||||
|
string[] urls = Context.Chirp.Images;
|
||||||
|
string username = Context.Chirp.Authors.LastOrDefault();
|
||||||
|
|
||||||
|
control.InvokeAsyncSafe(() => {
|
||||||
|
TwitterUtils.DownloadImages(urls, username, ImageQuality);
|
||||||
|
analytics.AnalyticsFile.DownloadedImages.Trigger();
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MenuReadApplyROT13:
|
||||||
|
string selection = parameters.SelectionText;
|
||||||
|
control.InvokeAsyncSafe(() => FormMessage.Information("ROT13", StringUtils.ConvertRot13(selection), FormMessage.OK));
|
||||||
|
control.InvokeAsyncSafe(analytics.AnalyticsFile.UsedROT13.Trigger);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MenuSearchInBrowser:
|
||||||
|
string query = parameters.SelectionText;
|
||||||
|
control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalSearch(query));
|
||||||
|
DeselectAll(frame);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MenuOpenDevTools:
|
||||||
|
browserControl.ShowDevTools();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){}
|
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
|
||||||
|
Context = TweetDeckBridge.ContextInfo.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){
|
public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static void DeselectAll(IFrame frame){
|
||||||
|
ScriptLoader.ExecuteScript(frame, "window.getSelection().removeAllRanges()", "gen:deselect");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void OpenBrowser(Control control, string url){
|
||||||
|
control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void SetClipboardText(Control control, string text){
|
||||||
|
control.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void InsertSelectionSearchItem(IMenuModel model, CefMenuCommand insertCommand, string insertLabel){
|
||||||
|
model.InsertItemAt(model.GetIndexOf(MenuSearchInBrowser)+1, insertCommand, insertLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void AddDebugMenuItems(IMenuModel model){
|
||||||
|
if (BrowserUtils.HasDevTools){
|
||||||
|
AddSeparator(model);
|
||||||
|
model.AddItem(MenuOpenDevTools, "Open dev tools");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected static void RemoveSeparatorIfLast(IMenuModel model){
|
protected static void RemoveSeparatorIfLast(IMenuModel model){
|
||||||
if (model.Count > 0 && model.GetTypeAt(model.Count-1) == MenuItemType.Separator){
|
if (model.Count > 0 && model.GetTypeAt(model.Count-1) == MenuItemType.Separator){
|
||||||
model.RemoveAt(model.Count-1);
|
model.RemoveAt(model.Count-1);
|
||||||
@@ -89,21 +227,5 @@ namespace TweetDck.Core.Handling{
|
|||||||
model.AddSeparator();
|
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,63 +1,96 @@
|
|||||||
using CefSharp;
|
using CefSharp;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDck.Core.Utils;
|
using TweetDuck.Core.Management;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDck.Core.Handling{
|
namespace TweetDuck.Core.Handling{
|
||||||
class ContextMenuBrowser : ContextMenuBase{
|
sealed class ContextMenuBrowser : ContextMenuBase{
|
||||||
private const int MenuGlobal = 26600;
|
private const CefMenuCommand MenuGlobal = (CefMenuCommand)26600;
|
||||||
private const int MenuMute = 26601;
|
private const CefMenuCommand MenuMute = (CefMenuCommand)26601;
|
||||||
private const int MenuSettings = 26602;
|
private const CefMenuCommand MenuSettings = (CefMenuCommand)26602;
|
||||||
private const int MenuPlugins = 26003;
|
private const CefMenuCommand MenuPlugins = (CefMenuCommand)26003;
|
||||||
private const int MenuAbout = 26604;
|
private const CefMenuCommand MenuAbout = (CefMenuCommand)26604;
|
||||||
|
|
||||||
private const int MenuOpenTweetUrl = 26610;
|
private const CefMenuCommand MenuOpenTweetUrl = (CefMenuCommand)26610;
|
||||||
private const int MenuCopyTweetUrl = 26611;
|
private const CefMenuCommand MenuCopyTweetUrl = (CefMenuCommand)26611;
|
||||||
private const int MenuOpenQuotedTweetUrl = 26612;
|
private const CefMenuCommand MenuOpenQuotedTweetUrl = (CefMenuCommand)26612;
|
||||||
private const int MenuCopyQuotedTweetUrl = 26613;
|
private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand)26613;
|
||||||
|
private const CefMenuCommand MenuScreenshotTweet = (CefMenuCommand)26614;
|
||||||
|
private const CefMenuCommand MenuWriteApplyROT13 = (CefMenuCommand)26615;
|
||||||
|
private const CefMenuCommand MenuSearchInColumn = (CefMenuCommand)26616;
|
||||||
|
|
||||||
|
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 FormBrowser form;
|
||||||
|
|
||||||
public ContextMenuBrowser(FormBrowser form){
|
public ContextMenuBrowser(FormBrowser form) : base(form){
|
||||||
this.form = form;
|
this.form = form;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
||||||
|
bool isSelecting = parameters.TypeFlags.HasFlag(ContextMenuType.Selection);
|
||||||
|
bool isEditing = parameters.TypeFlags.HasFlag(ContextMenuType.Editable);
|
||||||
|
|
||||||
model.Remove(CefMenuCommand.Back);
|
model.Remove(CefMenuCommand.Back);
|
||||||
model.Remove(CefMenuCommand.Forward);
|
model.Remove(CefMenuCommand.Forward);
|
||||||
model.Remove(CefMenuCommand.Print);
|
model.Remove(CefMenuCommand.Print);
|
||||||
model.Remove(CefMenuCommand.ViewSource);
|
model.Remove(CefMenuCommand.ViewSource);
|
||||||
RemoveSeparatorIfLast(model);
|
RemoveSeparatorIfLast(model);
|
||||||
|
|
||||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
if (isSelecting){
|
||||||
|
if (isEditing){
|
||||||
if (!string.IsNullOrEmpty(TweetDeckBridge.LastHighlightedTweet) && (parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
|
|
||||||
model.AddItem((CefMenuCommand)MenuOpenTweetUrl, "Open tweet in browser");
|
|
||||||
model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address");
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(TweetDeckBridge.LastHighlightedQuotedTweet)){
|
|
||||||
model.AddSeparator();
|
model.AddSeparator();
|
||||||
model.AddItem((CefMenuCommand)MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
|
model.AddItem(MenuWriteApplyROT13, "Apply ROT13");
|
||||||
model.AddItem((CefMenuCommand)MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model.AddSeparator();
|
model.AddSeparator();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
|
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||||
|
|
||||||
|
if (isSelecting && !isEditing && TwitterUtils.IsTweetDeckWebsite(frame)){
|
||||||
|
InsertSelectionSearchItem(model, MenuSearchInColumn, "Search in a column");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Context.Types.HasFlag(ContextInfo.ContextType.Chirp) && !isSelecting && !isEditing){
|
||||||
|
model.AddItem(MenuOpenTweetUrl, "Open tweet in browser");
|
||||||
|
model.AddItem(MenuCopyTweetUrl, "Copy tweet address");
|
||||||
|
model.AddItem(MenuScreenshotTweet, "Screenshot tweet to clipboard");
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(Context.Chirp.QuoteUrl)){
|
||||||
|
model.AddSeparator();
|
||||||
|
model.AddItem(MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
|
||||||
|
model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
|
||||||
|
}
|
||||||
|
|
||||||
|
model.AddSeparator();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSelecting && !isEditing){
|
||||||
AddSeparator(model);
|
AddSeparator(model);
|
||||||
|
|
||||||
IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu((CefMenuCommand)MenuGlobal, Program.BrandName);
|
IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu(MenuGlobal, Program.BrandName);
|
||||||
|
|
||||||
globalMenu.AddItem(CefMenuCommand.Reload, "Reload browser");
|
globalMenu.AddItem(CefMenuCommand.Reload, TitleReloadBrowser);
|
||||||
globalMenu.AddCheckItem((CefMenuCommand)MenuMute, "Mute notifications");
|
globalMenu.AddCheckItem(MenuMute, TitleMuteNotifications);
|
||||||
globalMenu.SetChecked((CefMenuCommand)MenuMute, Program.UserConfig.MuteNotifications);
|
globalMenu.SetChecked(MenuMute, Config.MuteNotifications);
|
||||||
globalMenu.AddSeparator();
|
globalMenu.AddSeparator();
|
||||||
|
|
||||||
globalMenu.AddItem((CefMenuCommand)MenuSettings, "Settings");
|
globalMenu.AddItem(MenuSettings, TitleSettings);
|
||||||
globalMenu.AddItem((CefMenuCommand)MenuPlugins, "Plugins");
|
globalMenu.AddItem(MenuPlugins, TitlePlugins);
|
||||||
globalMenu.AddItem((CefMenuCommand)MenuAbout, "About "+Program.BrandName);
|
globalMenu.AddItem(MenuAbout, TitleAboutProgram);
|
||||||
|
|
||||||
|
AddDebugMenuItems(globalMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RemoveSeparatorIfLast(model);
|
||||||
|
|
||||||
|
form.InvokeAsyncSafe(form.AnalyticsFile.BrowserContextMenus.Trigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
|
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
|
||||||
@@ -65,49 +98,82 @@ namespace TweetDck.Core.Handling{
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch((int)commandId){
|
switch(commandId){
|
||||||
case (int)CefMenuCommand.Reload:
|
case CefMenuCommand.Reload:
|
||||||
frame.ExecuteJavaScriptAsync("window.location.href = 'https://tweetdeck.twitter.com'");
|
form.InvokeAsyncSafe(form.ReloadToTweetDeck);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuSettings:
|
case MenuSettings:
|
||||||
form.InvokeSafe(form.OpenSettings);
|
form.InvokeAsyncSafe(form.OpenSettings);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuAbout:
|
case MenuAbout:
|
||||||
form.InvokeSafe(form.OpenAbout);
|
form.InvokeAsyncSafe(form.OpenAbout);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuPlugins:
|
case MenuPlugins:
|
||||||
form.InvokeSafe(form.OpenPlugins);
|
form.InvokeAsyncSafe(form.OpenPlugins);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuMute:
|
case MenuMute:
|
||||||
form.InvokeSafe(() => {
|
form.InvokeAsyncSafe(ToggleMuteNotifications);
|
||||||
Program.UserConfig.MuteNotifications = !Program.UserConfig.MuteNotifications;
|
|
||||||
Program.UserConfig.Save();
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuOpenTweetUrl:
|
case MenuOpenTweetUrl:
|
||||||
BrowserUtils.OpenExternalBrowser(TweetDeckBridge.LastHighlightedTweet);
|
OpenBrowser(form, Context.Chirp.TweetUrl);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuCopyTweetUrl:
|
case MenuCopyTweetUrl:
|
||||||
Clipboard.SetText(TweetDeckBridge.LastHighlightedTweet, TextDataFormat.UnicodeText);
|
SetClipboardText(form, Context.Chirp.TweetUrl);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MenuScreenshotTweet:
|
||||||
|
form.InvokeAsyncSafe(form.TriggerTweetScreenshot);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuOpenQuotedTweetUrl:
|
case MenuOpenQuotedTweetUrl:
|
||||||
BrowserUtils.OpenExternalBrowser(TweetDeckBridge.LastHighlightedQuotedTweet);
|
OpenBrowser(form, Context.Chirp.QuoteUrl);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuCopyQuotedTweetUrl:
|
case MenuCopyQuotedTweetUrl:
|
||||||
Clipboard.SetText(TweetDeckBridge.LastHighlightedQuotedTweet, TextDataFormat.UnicodeText);
|
SetClipboardText(form, Context.Chirp.QuoteUrl);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case MenuWriteApplyROT13:
|
||||||
|
form.InvokeAsyncSafe(form.ApplyROT13);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MenuSearchInColumn:
|
||||||
|
string query = parameters.SelectionText;
|
||||||
|
form.InvokeAsyncSafe(() => form.AddSearchColumn(query));
|
||||||
|
DeselectAll(frame);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
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 = Config.MuteNotifications;
|
||||||
|
form.AnalyticsFile.BrowserContextMenus.Trigger();
|
||||||
|
};
|
||||||
|
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ToggleMuteNotifications(){
|
||||||
|
Config.MuteNotifications = !Config.MuteNotifications;
|
||||||
|
Config.Save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
14
Core/Handling/ContextMenuGuide.cs
Normal file
14
Core/Handling/ContextMenuGuide.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Core.Other.Analytics;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling{
|
||||||
|
sealed class ContextMenuGuide : ContextMenuBase{
|
||||||
|
public ContextMenuGuide(AnalyticsFile.IProvider analytics) : base(analytics){}
|
||||||
|
|
||||||
|
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
||||||
|
model.Clear();
|
||||||
|
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||||
|
AddDebugMenuItems(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,19 @@
|
|||||||
using System.Windows.Forms;
|
using CefSharp;
|
||||||
using CefSharp;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDck.Core.Controls;
|
using TweetDuck.Core.Notification;
|
||||||
|
|
||||||
namespace TweetDck.Core.Handling{
|
namespace TweetDuck.Core.Handling{
|
||||||
class ContextMenuNotification : ContextMenuBase{
|
sealed class ContextMenuNotification : ContextMenuBase{
|
||||||
private const int MenuSkipTweet = 26600;
|
private const CefMenuCommand MenuViewDetail = (CefMenuCommand)26600;
|
||||||
private const int MenuFreeze = 26601;
|
private const CefMenuCommand MenuSkipTweet = (CefMenuCommand)26601;
|
||||||
private const int MenuCopyTweetUrl = 26602;
|
private const CefMenuCommand MenuFreeze = (CefMenuCommand)26602;
|
||||||
private const int MenuCopyTweetEmbeddedUrl = 26603;
|
private const CefMenuCommand MenuCopyTweetUrl = (CefMenuCommand)26603;
|
||||||
|
private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand)26604;
|
||||||
|
|
||||||
private readonly FormNotification form;
|
private readonly FormNotificationBase form;
|
||||||
private readonly bool enableCustomMenu;
|
private readonly bool enableCustomMenu;
|
||||||
|
|
||||||
public ContextMenuNotification(FormNotification form, bool enableCustomMenu){
|
public ContextMenuNotification(FormNotificationBase form, bool enableCustomMenu) : base(form){
|
||||||
this.form = form;
|
this.form = form;
|
||||||
this.enableCustomMenu = enableCustomMenu;
|
this.enableCustomMenu = enableCustomMenu;
|
||||||
}
|
}
|
||||||
@@ -20,27 +21,39 @@ namespace TweetDck.Core.Handling{
|
|||||||
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
||||||
model.Clear();
|
model.Clear();
|
||||||
|
|
||||||
if (enableCustomMenu){
|
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){
|
||||||
model.AddItem((CefMenuCommand)MenuSkipTweet, "Skip tweet");
|
model.AddItem(CefMenuCommand.Copy, "Copy");
|
||||||
model.AddCheckItem((CefMenuCommand)MenuFreeze, "Freeze");
|
|
||||||
model.SetChecked((CefMenuCommand)MenuFreeze, form.FreezeTimer);
|
|
||||||
model.AddSeparator();
|
model.AddSeparator();
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(form.CurrentUrl)){
|
|
||||||
model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address");
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(TweetDeckBridge.NotificationTweetEmbedded)){
|
|
||||||
model.AddItem((CefMenuCommand)MenuCopyTweetEmbeddedUrl, "Copy quoted tweet address");
|
|
||||||
}
|
|
||||||
|
|
||||||
model.AddSeparator();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||||
|
|
||||||
|
if (enableCustomMenu){
|
||||||
|
if (form.CanViewDetail){
|
||||||
|
model.AddItem(MenuViewDetail, "View detail");
|
||||||
|
}
|
||||||
|
|
||||||
|
model.AddItem(MenuSkipTweet, "Skip tweet");
|
||||||
|
model.AddCheckItem(MenuFreeze, "Freeze");
|
||||||
|
model.SetChecked(MenuFreeze, form.FreezeTimer);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(form.CurrentTweetUrl)){
|
||||||
|
model.AddSeparator();
|
||||||
|
model.AddItem(MenuCopyTweetUrl, "Copy tweet address");
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(form.CurrentQuoteUrl)){
|
||||||
|
model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddDebugMenuItems(model);
|
||||||
RemoveSeparatorIfLast(model);
|
RemoveSeparatorIfLast(model);
|
||||||
|
|
||||||
form.InvokeSafe(() => form.ContextMenuOpen = true);
|
form.InvokeAsyncSafe(() => {
|
||||||
|
form.ContextMenuOpen = true;
|
||||||
|
form.AnalyticsFile.NotificationContextMenus.Trigger();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
|
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
|
||||||
@@ -48,21 +61,25 @@ namespace TweetDck.Core.Handling{
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch((int)commandId){
|
switch(commandId){
|
||||||
case MenuSkipTweet:
|
case MenuSkipTweet:
|
||||||
form.InvokeSafe(form.FinishCurrentTweet);
|
form.InvokeAsyncSafe(form.FinishCurrentNotification);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuFreeze:
|
case MenuFreeze:
|
||||||
form.InvokeSafe(() => form.FreezeTimer = !form.FreezeTimer);
|
form.InvokeAsyncSafe(() => form.FreezeTimer = !form.FreezeTimer);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MenuViewDetail:
|
||||||
|
form.InvokeSafe(form.ShowTweetDetail);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuCopyTweetUrl:
|
case MenuCopyTweetUrl:
|
||||||
Clipboard.SetText(form.CurrentUrl, TextDataFormat.UnicodeText);
|
SetClipboardText(form, form.CurrentTweetUrl);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuCopyTweetEmbeddedUrl:
|
case MenuCopyQuotedTweetUrl:
|
||||||
Clipboard.SetText(TweetDeckBridge.NotificationTweetEmbedded, TextDataFormat.UnicodeText);
|
SetClipboardText(form, form.CurrentQuoteUrl);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +88,7 @@ namespace TweetDck.Core.Handling{
|
|||||||
|
|
||||||
public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
|
public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
|
||||||
base.OnContextMenuDismissed(browserControl, browser, frame);
|
base.OnContextMenuDismissed(browserControl, browser, frame);
|
||||||
form.InvokeSafe(() => form.ContextMenuOpen = false);
|
form.InvokeAsyncSafe(() => form.ContextMenuOpen = false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
using CefSharp;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using TweetDck.Core.Controls;
|
|
||||||
|
|
||||||
namespace TweetDck.Core.Handling{
|
|
||||||
class DialogHandlerBrowser : IDialogHandler{
|
|
||||||
private readonly FormBrowser form;
|
|
||||||
|
|
||||||
public DialogHandlerBrowser(FormBrowser form){
|
|
||||||
this.form = form;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool OnFileDialog(IWebBrowser browserControl, IBrowser browser, CefFileDialogMode mode, string title, string defaultFilePath, List<string> acceptFilters, int selectedAcceptFilter, IFileDialogCallback callback){
|
|
||||||
if (!string.IsNullOrEmpty(TweetDeckBridge.ClipboardImagePath)){
|
|
||||||
callback.Continue(selectedAcceptFilter, new List<string>{ TweetDeckBridge.ClipboardImagePath });
|
|
||||||
|
|
||||||
form.InvokeSafe(() => {
|
|
||||||
TweetDeckBridge.ClipboardImagePath = string.Empty;
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
35
Core/Handling/DragHandlerBrowser.cs
Normal file
35
Core/Handling/DragHandlerBrowser.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using CefSharp;
|
||||||
|
using CefSharp.Enums;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.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.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, IList<DraggableRegion> regions){}
|
||||||
|
}
|
||||||
|
}
|
||||||
72
Core/Handling/Filters/ResponseFilterBase.cs
Normal file
72
Core/Handling/Filters/ResponseFilterBase.cs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using CefSharp;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling.Filters{
|
||||||
|
abstract class ResponseFilterBase : IResponseFilter{
|
||||||
|
private enum State{
|
||||||
|
Reading, Writing, Done
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Encoding encoding;
|
||||||
|
private byte[] responseData;
|
||||||
|
|
||||||
|
private State state;
|
||||||
|
private int offset;
|
||||||
|
|
||||||
|
protected ResponseFilterBase(int totalBytes, Encoding encoding){
|
||||||
|
this.responseData = new byte[totalBytes];
|
||||||
|
this.encoding = encoding;
|
||||||
|
this.state = State.Reading;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = encoding.GetBytes(ProcessResponse(encoding.GetString(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 abstract bool InitFilter();
|
||||||
|
protected abstract string ProcessResponse(string text);
|
||||||
|
public abstract void Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
20
Core/Handling/Filters/ResponseFilterVendor.cs
Normal file
20
Core/Handling/Filters/ResponseFilterVendor.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling.Filters{
|
||||||
|
sealed class ResponseFilterVendor : ResponseFilterBase{
|
||||||
|
private static readonly Regex RegexRestoreJQuery = new Regex(@"(\w+)\.fn=\1\.prototype", RegexOptions.Compiled);
|
||||||
|
|
||||||
|
public ResponseFilterVendor(int totalBytes) : base(totalBytes, Encoding.UTF8){}
|
||||||
|
|
||||||
|
public override bool InitFilter(){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string ProcessResponse(string text){
|
||||||
|
return RegexRestoreJQuery.Replace(text, "window.$$=$1;$&", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose(){}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
Core/Handling/General/BrowserProcessHandler.cs
Normal file
29
Core/Handling/General/BrowserProcessHandler.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Configuration;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling.General{
|
||||||
|
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(){}
|
||||||
|
}
|
||||||
|
}
|
||||||
61
Core/Handling/General/FileDialogHandler.cs
Normal file
61
Core/Handling/General/FileDialogHandler.cs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling.General{
|
||||||
|
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 new string[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type[0] == '.'){
|
||||||
|
return new string[]{ type };
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(type){
|
||||||
|
case "image/jpeg": return new string[]{ ".jpg", ".jpeg" };
|
||||||
|
case "image/png": return new string[]{ ".png" };
|
||||||
|
case "image/gif": return new string[]{ ".gif" };
|
||||||
|
case "image/webp": return new string[]{ ".webp" };
|
||||||
|
case "video/mp4": return new string[]{ ".mp4" };
|
||||||
|
case "video/quicktime": return new string[]{ ".mov", ".qt" };
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Diagnostics.Debugger.Break();
|
||||||
|
return new string[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
92
Core/Handling/General/JavaScriptDialogHandler.cs
Normal file
92
Core/Handling/General/JavaScriptDialogHandler.cs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Other;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling.General{
|
||||||
|
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){
|
||||||
|
browserControl.AsControl().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.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,17 +1,20 @@
|
|||||||
using CefSharp;
|
using CefSharp;
|
||||||
using TweetDck.Core.Utils;
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDck.Core.Handling{
|
namespace TweetDuck.Core.Handling.General{
|
||||||
class LifeSpanHandler : ILifeSpanHandler{
|
sealed 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){
|
private static bool IsPopupAllowed(string url){
|
||||||
newBrowser = null;
|
return url.StartsWith("https://twitter.com/teams/authorize?");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool HandleLinkClick(IWebBrowser browserControl, WindowOpenDisposition targetDisposition, string targetUrl){
|
||||||
switch(targetDisposition){
|
switch(targetDisposition){
|
||||||
case WindowOpenDisposition.NewBackgroundTab:
|
case WindowOpenDisposition.NewBackgroundTab:
|
||||||
case WindowOpenDisposition.NewForegroundTab:
|
case WindowOpenDisposition.NewForegroundTab:
|
||||||
case WindowOpenDisposition.NewPopup:
|
case WindowOpenDisposition.NewPopup when !IsPopupAllowed(targetUrl):
|
||||||
case WindowOpenDisposition.NewWindow:
|
case WindowOpenDisposition.NewWindow:
|
||||||
BrowserUtils.OpenExternalBrowser(targetUrl);
|
browserControl.AsControl().InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(targetUrl));
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -19,6 +22,11 @@ namespace TweetDck.Core.Handling{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
return HandleLinkClick(browserControl, targetDisposition, targetUrl);
|
||||||
|
}
|
||||||
|
|
||||||
public void OnAfterCreated(IWebBrowser browserControl, IBrowser browser){}
|
public void OnAfterCreated(IWebBrowser browserControl, IBrowser browser){}
|
||||||
|
|
||||||
public bool DoClose(IWebBrowser browserControl, IBrowser browser){
|
public bool DoClose(IWebBrowser browserControl, IBrowser browser){
|
||||||
47
Core/Handling/KeyboardHandlerBase.cs
Normal file
47
Core/Handling/KeyboardHandlerBase.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Other;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling{
|
||||||
|
class KeyboardHandlerBase : IKeyboardHandler{
|
||||||
|
protected virtual bool HandleRawKey(IWebBrowser browserControl, IBrowser browser, Keys key, CefEventFlags modifiers){
|
||||||
|
if (modifiers == (CefEventFlags.ControlDown | CefEventFlags.ShiftDown) && key == Keys.I){
|
||||||
|
if (BrowserUtils.HasDevTools){
|
||||||
|
browser.ShowDevTools();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
browserControl.AsControl().InvokeSafe(() => {
|
||||||
|
string extraMessage;
|
||||||
|
|
||||||
|
if (Program.IsPortable){
|
||||||
|
extraMessage = "Please download the portable installer, select the folder with your current installation of TweetDuck Portable, and tick 'Install dev tools' during the installation process.";
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
extraMessage = "Please download the installer, and tick 'Install dev tools' during the installation process. The installer will automatically find and update your current installation of TweetDuck.";
|
||||||
|
}
|
||||||
|
|
||||||
|
FormMessage.Information("Dev Tools", "You do not have dev tools installed. "+extraMessage, FormMessage.OK);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IKeyboardHandler.OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut){
|
||||||
|
if (type == KeyType.RawKeyDown && !browser.FocusedFrame.Url.StartsWith("chrome-devtools://")){
|
||||||
|
return HandleRawKey(browserControl, browser, (Keys)windowsKeyCode, modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
Core/Handling/KeyboardHandlerBrowser.cs
Normal file
20
Core/Handling/KeyboardHandlerBrowser.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling{
|
||||||
|
sealed class KeyboardHandlerBrowser : KeyboardHandlerBase{
|
||||||
|
private readonly FormBrowser form;
|
||||||
|
|
||||||
|
public KeyboardHandlerBrowser(FormBrowser form){
|
||||||
|
this.form = form;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool HandleRawKey(IWebBrowser browserControl, IBrowser browser, Keys key, CefEventFlags modifiers){
|
||||||
|
if (base.HandleRawKey(browserControl, browser, key, modifiers)){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return form.ProcessBrowserKey(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
Core/Handling/KeyboardHandlerNotification.cs
Normal file
44
Core/Handling/KeyboardHandlerNotification.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using CefSharp;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Notification;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling {
|
||||||
|
sealed class KeyboardHandlerNotification : KeyboardHandlerBase{
|
||||||
|
private readonly FormNotificationBase notification;
|
||||||
|
|
||||||
|
public KeyboardHandlerNotification(FormNotificationBase notification){
|
||||||
|
this.notification = notification;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TriggerKeyboardShortcutAnalytics(){
|
||||||
|
notification.InvokeAsyncSafe(notification.AnalyticsFile.NotificationKeyboardShortcuts.Trigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool HandleRawKey(IWebBrowser browserControl, IBrowser browser, Keys key, CefEventFlags modifiers){
|
||||||
|
if (base.HandleRawKey(browserControl, browser, key, modifiers)){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(key){
|
||||||
|
case Keys.Enter:
|
||||||
|
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
|
||||||
|
TriggerKeyboardShortcutAnalytics();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case Keys.Escape:
|
||||||
|
notification.InvokeAsyncSafe(notification.HideNotification);
|
||||||
|
TriggerKeyboardShortcutAnalytics();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case Keys.Space:
|
||||||
|
notification.InvokeAsyncSafe(() => notification.FreezeTimer = !notification.FreezeTimer);
|
||||||
|
TriggerKeyboardShortcutAnalytics();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
82
Core/Handling/RequestHandlerBase.cs
Normal file
82
Core/Handling/RequestHandlerBase.cs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using CefSharp;
|
||||||
|
using CefSharp.Handler;
|
||||||
|
using TweetDuck.Core.Handling.General;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetLib.Core.Utils;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling{
|
||||||
|
class RequestHandlerBase : DefaultRequestHandler{
|
||||||
|
private static readonly Regex TweetDeckResourceUrl = new Regex(@"/dist/(.*?)\.(.*?)\.(css|js)$", RegexOptions.Compiled);
|
||||||
|
private static readonly SortedList<string, string> TweetDeckHashes = new SortedList<string, string>(4);
|
||||||
|
|
||||||
|
public static void LoadResourceRewriteRules(string rules){
|
||||||
|
if (string.IsNullOrEmpty(rules)){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TweetDeckHashes.Clear();
|
||||||
|
|
||||||
|
foreach(string rule in rules.Replace(" ", "").ToLower().Split(',')){
|
||||||
|
var (key, hash) = StringUtils.SplitInTwo(rule, '=') ?? throw new ArgumentException("A rule must have one '=' character: " + rule);
|
||||||
|
|
||||||
|
if (hash.All(chr => char.IsDigit(chr) || (chr >= 'a' && chr <= 'f'))){
|
||||||
|
TweetDeckHashes.Add(key, hash);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
throw new ArgumentException("Invalid hash characters: " + rule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly bool autoReload;
|
||||||
|
|
||||||
|
public RequestHandlerBase(bool autoReload){
|
||||||
|
this.autoReload = autoReload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture){
|
||||||
|
return LifeSpanHandler.HandleLinkClick(browserControl, targetDisposition, targetUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){
|
||||||
|
if (BrowserUtils.HasDevTools){
|
||||||
|
NameValueCollection headers = request.Headers;
|
||||||
|
headers.Remove("x-devtools-emulate-network-conditions-client-id");
|
||||||
|
request.Headers = headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnResourceResponse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){
|
||||||
|
if ((request.ResourceType == ResourceType.Script || request.ResourceType == ResourceType.Stylesheet) && TweetDeckHashes.Count > 0){
|
||||||
|
string url = request.Url;
|
||||||
|
Match match = TweetDeckResourceUrl.Match(url);
|
||||||
|
|
||||||
|
if (match.Success && TweetDeckHashes.TryGetValue($"{match.Groups[1]}.{match.Groups[3]}", out string hash)){
|
||||||
|
if (match.Groups[2].Value == hash){
|
||||||
|
Program.Reporter.LogVerbose("[RequestHandlerBase] Accepting " + url);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
Program.Reporter.LogVerbose("[RequestHandlerBase] Replacing " + url + " hash with " + hash);
|
||||||
|
request.Url = TweetDeckResourceUrl.Replace(url, $"/dist/$1.{hash}.$3");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnResourceResponse(browserControl, browser, frame, request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){
|
||||||
|
if (autoReload){
|
||||||
|
browser.Reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
65
Core/Handling/RequestHandlerBrowser.cs
Normal file
65
Core/Handling/RequestHandlerBrowser.cs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
using System.Collections.Specialized;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Core.Handling.Filters;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling{
|
||||||
|
sealed class RequestHandlerBrowser : RequestHandlerBase{
|
||||||
|
private const string UrlVendorResource = "/dist/vendor";
|
||||||
|
private const string UrlLoadingSpinner = "/backgrounds/spinner_blue";
|
||||||
|
|
||||||
|
public string BlockNextUserNavUrl { get; set; }
|
||||||
|
|
||||||
|
public RequestHandlerBrowser() : base(true){}
|
||||||
|
|
||||||
|
public override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){
|
||||||
|
if (request.ResourceType == ResourceType.MainFrame){
|
||||||
|
if (request.Url.EndsWith("//twitter.com/")){
|
||||||
|
request.Url = TwitterUtils.TweetDeckURL; // redirect plain twitter.com requests, fixes bugs with login 2FA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (request.ResourceType == ResourceType.Script){
|
||||||
|
string url = request.Url;
|
||||||
|
|
||||||
|
if (url.Contains("analytics.")){
|
||||||
|
callback.Dispose();
|
||||||
|
return CefReturnValue.Cancel;
|
||||||
|
}
|
||||||
|
else if (url.Contains(UrlVendorResource)){
|
||||||
|
NameValueCollection headers = request.Headers;
|
||||||
|
headers["Accept-Encoding"] = "identity";
|
||||||
|
request.Headers = headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnBeforeBrowse(browserControl, browser, frame, request, userGesture, isRedirect);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnResourceResponse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){
|
||||||
|
if (request.ResourceType == ResourceType.Image && request.Url.Contains(UrlLoadingSpinner)){
|
||||||
|
request.Url = TwitterUtils.LoadingSpinner.Url;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnResourceResponse(browserControl, browser, frame, request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IResponseFilter GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){
|
||||||
|
if (request.ResourceType == ResourceType.Script && request.Url.Contains(UrlVendorResource) && int.TryParse(response.ResponseHeaders["Content-Length"], out int totalBytes)){
|
||||||
|
return new ResponseFilterVendor(totalBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.GetResourceResponseFilter(browserControl, browser, frame, request, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
Core/Handling/ResourceHandlerFactory.cs
Normal file
43
Core/Handling/ResourceHandlerFactory.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Data;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Handling{
|
||||||
|
sealed class ResourceHandlerFactory : IResourceHandlerFactory{
|
||||||
|
public bool HasHandlers => !handlers.IsEmpty;
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<string, IResourceHandler> handlers = new ConcurrentDictionary<string, IResourceHandler>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
public IResourceHandler GetResourceHandler(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request){
|
||||||
|
try{
|
||||||
|
return handlers.TryGetValue(request.Url, out IResourceHandler handler) ? handler : null;
|
||||||
|
}finally{
|
||||||
|
request.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// registration
|
||||||
|
|
||||||
|
public bool RegisterHandler(string url, IResourceHandler handler){
|
||||||
|
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){
|
||||||
|
handlers.AddOrUpdate(uri.AbsoluteUri, handler, (key, prev) => handler);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RegisterHandler(ResourceLink link){
|
||||||
|
return RegisterHandler(link.Url, link.Handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool UnregisterHandler(string url){
|
||||||
|
return handlers.TryRemove(url, out IResourceHandler _);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool UnregisterHandler(ResourceLink link){
|
||||||
|
return UnregisterHandler(link.Url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
63
Core/Handling/ResourceHandlerNotification.cs
Normal file
63
Core/Handling/ResourceHandlerNotification.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
using CefSharp;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.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.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,179 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Drawing.Imaging;
|
|
||||||
using System.IO;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using TweetDck.Core.Utils;
|
|
||||||
using TweetDck.Core.Controls;
|
|
||||||
|
|
||||||
namespace TweetDck.Core.Handling{
|
|
||||||
class TweetDeckBridge{
|
|
||||||
public static string LastRightClickedLink = string.Empty;
|
|
||||||
public static string LastHighlightedTweet = string.Empty;
|
|
||||||
public static string LastHighlightedQuotedTweet = string.Empty;
|
|
||||||
public static string NotificationTweetEmbedded = string.Empty;
|
|
||||||
public static string ClipboardImagePath = string.Empty;
|
|
||||||
|
|
||||||
private readonly FormBrowser form;
|
|
||||||
private readonly FormNotification notification;
|
|
||||||
|
|
||||||
public string BrandName{
|
|
||||||
get{
|
|
||||||
return Program.BrandName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string VersionTag{
|
|
||||||
get{
|
|
||||||
return Program.VersionTag;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool MuteNotifications{
|
|
||||||
get{
|
|
||||||
return Program.UserConfig.MuteNotifications;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ExpandLinksOnHover{
|
|
||||||
get{
|
|
||||||
return Program.UserConfig.ExpandLinksOnHover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool HasCustomBrowserCSS{
|
|
||||||
get{
|
|
||||||
return !string.IsNullOrEmpty(Program.UserConfig.CustomBrowserCSS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string CustomBrowserCSS{
|
|
||||||
get{
|
|
||||||
return Program.UserConfig.CustomBrowserCSS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public TweetDeckBridge(FormBrowser form, FormNotification notification){
|
|
||||||
this.form = form;
|
|
||||||
this.notification = notification;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadFontSizeClass(string fsClass){
|
|
||||||
form.InvokeSafe(() => {
|
|
||||||
TweetNotification.SetFontSizeClass(fsClass);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadNotificationHeadContents(string headContents){
|
|
||||||
form.InvokeSafe(() => {
|
|
||||||
TweetNotification.SetHeadTag(headContents);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetLastRightClickedLink(string link){
|
|
||||||
form.InvokeSafe(() => LastRightClickedLink = link);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetLastHighlightedTweet(string link, string quotedLink){
|
|
||||||
form.InvokeSafe(() => {
|
|
||||||
LastHighlightedTweet = link;
|
|
||||||
LastHighlightedQuotedTweet = quotedLink;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetNotificationTweetEmbedded(string link){
|
|
||||||
form.InvokeSafe(() => NotificationTweetEmbedded = link);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OpenSettingsMenu(){
|
|
||||||
form.InvokeSafe(form.OpenSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OpenPluginsMenu(){
|
|
||||||
form.InvokeSafe(form.OpenPlugins);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnTweetPopup(string tweetHtml, string tweetUrl, int tweetCharacters){
|
|
||||||
notification.InvokeSafe(() => {
|
|
||||||
form.OnTweetNotification();
|
|
||||||
notification.ShowNotification(new TweetNotification(tweetHtml, tweetUrl, tweetCharacters));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnTweetSound(){
|
|
||||||
form.InvokeSafe(form.OnTweetNotification);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnNotificationReady(){
|
|
||||||
if (!Program.UserConfig.NotificationLegacyLoad){
|
|
||||||
notification.InvokeSafe(notification.OnNotificationReady);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DisplayTooltip(string text, bool showInNotification){
|
|
||||||
if (showInNotification){
|
|
||||||
notification.InvokeSafe(() => notification.DisplayTooltip(text));
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
form.InvokeSafe(() => form.DisplayTooltip(text));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadNextNotification(){
|
|
||||||
notification.InvokeSafe(notification.FinishCurrentTweet);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void TryPasteImage(){
|
|
||||||
form.InvokeSafe(() => {
|
|
||||||
if (Clipboard.ContainsImage()){
|
|
||||||
Image img = Clipboard.GetImage();
|
|
||||||
if (img == null)return;
|
|
||||||
|
|
||||||
try{
|
|
||||||
Directory.CreateDirectory(Program.TemporaryPath);
|
|
||||||
|
|
||||||
ClipboardImagePath = Path.Combine(Program.TemporaryPath, "TD-Img-"+DateTime.Now.Ticks+".png");
|
|
||||||
img.Save(ClipboardImagePath, ImageFormat.Png);
|
|
||||||
|
|
||||||
form.OnImagePasted();
|
|
||||||
}catch(Exception e){
|
|
||||||
Program.Reporter.HandleException("Clipboard Image Error", "Could not paste image from clipboard.", true, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ClickUploadImage(int offsetX, int offsetY){
|
|
||||||
form.InvokeSafe(() => {
|
|
||||||
Point prevPos = Cursor.Position;
|
|
||||||
|
|
||||||
Cursor.Position = form.PointToScreen(new Point(offsetX, offsetY));
|
|
||||||
NativeMethods.SimulateMouseClick(NativeMethods.MouseButton.Left);
|
|
||||||
Cursor.Position = prevPos;
|
|
||||||
|
|
||||||
form.OnImagePastedFinish();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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,120 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace TweetDck.Core.Handling{
|
|
||||||
sealed class TweetNotification{
|
|
||||||
private static string FontSizeClass { get; set; }
|
|
||||||
private static string HeadTag { get; set; }
|
|
||||||
|
|
||||||
private static string DefaultFontSizeClass{
|
|
||||||
get{
|
|
||||||
return "medium";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string DefaultHeadTag{
|
|
||||||
get{
|
|
||||||
return @"<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'>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string CustomCSS{
|
|
||||||
get{
|
|
||||||
return @".scroll-styled-v::-webkit-scrollbar{width:8px}.scroll-styled-v::-webkit-scrollbar-thumb{border-radius:0}a[data-full-url]{word-break:break-all}#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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TweetNotification ExampleTweet{
|
|
||||||
get{
|
|
||||||
StringBuilder build = new StringBuilder();
|
|
||||||
build.Append(@"<article><div class='js-stream-item-content item-box js-show-detail'><div class='js-tweet tweet'>");
|
|
||||||
build.Append(@"<header class='tweet-header'>");
|
|
||||||
build.Append(@"<time class='tweet-timestamp js-timestamp pull-right txt-mute'><a target='_blank' rel='url' href='https://twitter.com/chylexmc' class='txt-small'>0s</a></time>");
|
|
||||||
build.Append(@"<a target='_blank' rel='user' href='https://twitter.com/chylexmc' class='account-link link-complex block'>");
|
|
||||||
build.Append(@"<div class='obj-left item-img tweet-img'><img width='48' height='48' alt='chylexmc's avatar' src='https://pbs.twimg.com/profile_images/765161905312980992/AhDP9iY-_normal.jpg' class='tweet-avatar avatar pull-right'></div>");
|
|
||||||
build.Append(@"<div class='nbfc'><span class='account-inline txt-ellipsis'><b class='fullname link-complex-target'>chylex</b> <span class='username txt-mute'>@chylexmc</span></span></div>");
|
|
||||||
build.Append(@"</a>");
|
|
||||||
build.Append(@"</header>");
|
|
||||||
build.Append(@"<div class='tweet-body'><p class='js-tweet-text tweet-text with-linebreaks'>This is an example tweet, which lets you test the location and duration of popup notifications.</p></div>");
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
build.Append(@"<div style='margin-top:64px'>Scrollbar test padding...</div>");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
build.Append(@"</div></div></article>");
|
|
||||||
|
|
||||||
return new TweetNotification(build.ToString(), "", 95, 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 enum Duration{
|
|
||||||
Short, Medium, Long, VeryLong
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Url{
|
|
||||||
get{
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly string html;
|
|
||||||
private readonly string url;
|
|
||||||
private readonly int characters;
|
|
||||||
private readonly bool isExample;
|
|
||||||
|
|
||||||
public TweetNotification(string html, string url, int characters) : this(html, url, characters, false){}
|
|
||||||
|
|
||||||
private TweetNotification(string html, string url, int characters, bool isExample){
|
|
||||||
this.html = html;
|
|
||||||
this.url = url;
|
|
||||||
this.characters = characters;
|
|
||||||
this.isExample = isExample;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetDisplayDuration(int value){
|
|
||||||
return 2000+Math.Max(1000, value*characters);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GenerateHtml(){
|
|
||||||
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).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='hearty'").Append(isExample ? " td-example-notification" : "").Append("><div class='app-columns-container'><div class='column scroll-styled-v' style='width:100%;overflow-y:auto'>");
|
|
||||||
build.Append(html);
|
|
||||||
build.Append("</div></div></body>");
|
|
||||||
build.Append("</html>");
|
|
||||||
return build.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
76
Core/Management/BrowserCache.cs
Normal file
76
Core/Management/BrowserCache.cs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Management{
|
||||||
|
static class BrowserCache{
|
||||||
|
public static string CacheFolder => Path.Combine(Program.StoragePath, "Cache");
|
||||||
|
|
||||||
|
private static bool ClearOnExit;
|
||||||
|
private static Timer AutoClearTimer;
|
||||||
|
|
||||||
|
private static long CalculateCacheSize(){
|
||||||
|
return new DirectoryInfo(CacheFolder).EnumerateFiles().Select(file => {
|
||||||
|
try{
|
||||||
|
return file.Length;
|
||||||
|
}catch{
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
}).Sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void GetCacheSize(Action<Task<long>> callbackBytes){
|
||||||
|
Task<long> task = new Task<long>(CalculateCacheSize);
|
||||||
|
task.ContinueWith(callbackBytes);
|
||||||
|
task.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RefreshTimer(){
|
||||||
|
bool shouldRun = Program.Config.System.ClearCacheAutomatically && !ClearOnExit;
|
||||||
|
|
||||||
|
if (!shouldRun && AutoClearTimer != null){
|
||||||
|
AutoClearTimer.Dispose();
|
||||||
|
AutoClearTimer = null;
|
||||||
|
}
|
||||||
|
else if (shouldRun && AutoClearTimer == null){
|
||||||
|
AutoClearTimer = new Timer(state => {
|
||||||
|
if (AutoClearTimer != null){
|
||||||
|
try{
|
||||||
|
if (CalculateCacheSize() >= Program.Config.System.ClearCacheThreshold*1024L*1024L){
|
||||||
|
SetClearOnExit();
|
||||||
|
}
|
||||||
|
}catch(Exception){
|
||||||
|
// TODO should probably log errors and report them at some point
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, null, TimeSpan.FromSeconds(30), TimeSpan.FromHours(4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetClearOnExit(){
|
||||||
|
ClearOnExit = true;
|
||||||
|
RefreshTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void TryClearNow(){
|
||||||
|
try{
|
||||||
|
Directory.Delete(CacheFolder, true);
|
||||||
|
}catch{
|
||||||
|
// welp, too bad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Exit(){
|
||||||
|
if (AutoClearTimer != null){
|
||||||
|
AutoClearTimer.Dispose();
|
||||||
|
AutoClearTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ClearOnExit){
|
||||||
|
TryClearNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
161
Core/Management/ContextInfo.cs
Normal file
161
Core/Management/ContextInfo.cs
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
using System;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetLib.Core.Utils;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Management{
|
||||||
|
sealed class ContextInfo{
|
||||||
|
private LinkInfo link;
|
||||||
|
private ChirpInfo? chirp;
|
||||||
|
|
||||||
|
public ContextInfo(){
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetLink(string type, string url){
|
||||||
|
link = string.IsNullOrEmpty(url) ? null : new LinkInfo(type, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetChirp(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
|
||||||
|
chirp = string.IsNullOrEmpty(tweetUrl) ? (ChirpInfo?)null : new ChirpInfo(tweetUrl, quoteUrl, chirpAuthors, chirpImages);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContextData Reset(){
|
||||||
|
link = null;
|
||||||
|
chirp = null;
|
||||||
|
return ContextData.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContextData Create(IContextMenuParams parameters){
|
||||||
|
ContextData.Builder builder = new ContextData.Builder();
|
||||||
|
builder.AddContext(parameters);
|
||||||
|
|
||||||
|
if (link != null){
|
||||||
|
builder.AddOverride(link.Type, link.Url);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chirp.HasValue){
|
||||||
|
builder.AddChirp(chirp.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data structures
|
||||||
|
|
||||||
|
private sealed class LinkInfo{
|
||||||
|
public string Type { get; }
|
||||||
|
public string Url { get; }
|
||||||
|
|
||||||
|
public LinkInfo(string type, string url){
|
||||||
|
this.Type = type;
|
||||||
|
this.Url = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct ChirpInfo{
|
||||||
|
public string TweetUrl { get; }
|
||||||
|
public string QuoteUrl { get; }
|
||||||
|
|
||||||
|
public string[] Authors => chirpAuthors?.Split(';') ?? StringUtils.EmptyArray;
|
||||||
|
public string[] Images => chirpImages?.Split(';') ?? StringUtils.EmptyArray;
|
||||||
|
|
||||||
|
private readonly string chirpAuthors;
|
||||||
|
private readonly string chirpImages;
|
||||||
|
|
||||||
|
public ChirpInfo(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
|
||||||
|
this.TweetUrl = tweetUrl;
|
||||||
|
this.QuoteUrl = quoteUrl;
|
||||||
|
this.chirpAuthors = chirpAuthors;
|
||||||
|
this.chirpImages = chirpImages;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructed context
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum ContextType{
|
||||||
|
Unknown = 0,
|
||||||
|
Link = 0b0001,
|
||||||
|
Image = 0b0010,
|
||||||
|
Video = 0b0100,
|
||||||
|
Chirp = 0b1000
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ContextData{
|
||||||
|
public static readonly ContextData Empty = new Builder().Build();
|
||||||
|
|
||||||
|
public ContextType Types { get; }
|
||||||
|
|
||||||
|
public string LinkUrl { get; }
|
||||||
|
public string UnsafeLinkUrl { get; }
|
||||||
|
public string MediaUrl { get; }
|
||||||
|
|
||||||
|
public ChirpInfo Chirp { get; }
|
||||||
|
|
||||||
|
public ContextData(ContextType types, string linkUrl, string unsafeLinkUrl, string mediaUrl, ChirpInfo chirp){
|
||||||
|
Types = types;
|
||||||
|
LinkUrl = linkUrl;
|
||||||
|
UnsafeLinkUrl = unsafeLinkUrl;
|
||||||
|
MediaUrl = mediaUrl;
|
||||||
|
Chirp = chirp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class Builder{
|
||||||
|
private ContextType types = ContextType.Unknown;
|
||||||
|
|
||||||
|
private string linkUrl = string.Empty;
|
||||||
|
private string unsafeLinkUrl = string.Empty;
|
||||||
|
private string mediaUrl = string.Empty;
|
||||||
|
|
||||||
|
private ChirpInfo chirp = default;
|
||||||
|
|
||||||
|
public void AddContext(IContextMenuParams parameters){
|
||||||
|
ContextMenuType flags = parameters.TypeFlags;
|
||||||
|
|
||||||
|
if (flags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents){
|
||||||
|
types |= ContextType.Image;
|
||||||
|
types &= ~ContextType.Video;
|
||||||
|
mediaUrl = parameters.SourceUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags.HasFlag(ContextMenuType.Link)){
|
||||||
|
types |= ContextType.Link;
|
||||||
|
linkUrl = parameters.LinkUrl;
|
||||||
|
unsafeLinkUrl = parameters.UnfilteredLinkUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddOverride(string type, string url){
|
||||||
|
switch(type){
|
||||||
|
case "link":
|
||||||
|
types |= ContextType.Link;
|
||||||
|
linkUrl = url;
|
||||||
|
unsafeLinkUrl = url;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "image":
|
||||||
|
types |= ContextType.Image;
|
||||||
|
types &= ~(ContextType.Video | ContextType.Link);
|
||||||
|
mediaUrl = url;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "video":
|
||||||
|
types |= ContextType.Video;
|
||||||
|
types &= ~(ContextType.Image | ContextType.Link);
|
||||||
|
mediaUrl = url;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddChirp(ChirpInfo chirp){
|
||||||
|
this.types |= ContextType.Chirp;
|
||||||
|
this.chirp = chirp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContextData Build(){
|
||||||
|
return new ContextData(types, linkUrl, unsafeLinkUrl, mediaUrl, chirp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
216
Core/Management/ProfileManager.cs
Normal file
216
Core/Management/ProfileManager.cs
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using TweetDuck.Core.Other;
|
||||||
|
using TweetDuck.Plugins;
|
||||||
|
using TweetLib.Core.Data;
|
||||||
|
using TweetLib.Core.Features.Plugins;
|
||||||
|
using TweetLib.Core.Features.Plugins.Enums;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Management{
|
||||||
|
sealed class ProfileManager{
|
||||||
|
private static readonly string CookiesPath = Path.Combine(Program.StoragePath, "Cookies");
|
||||||
|
private static readonly string TempCookiesPath = Path.Combine(Program.StoragePath, "CookiesTmp");
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum Items{
|
||||||
|
None = 0,
|
||||||
|
UserConfig = 1,
|
||||||
|
SystemConfig = 2,
|
||||||
|
Session = 4,
|
||||||
|
PluginData = 8,
|
||||||
|
All = UserConfig|SystemConfig|Session|PluginData
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly string file;
|
||||||
|
private readonly PluginManager plugins;
|
||||||
|
|
||||||
|
public ProfileManager(string file, PluginManager plugins){
|
||||||
|
this.file = file;
|
||||||
|
this.plugins = plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Export(Items items){
|
||||||
|
try{
|
||||||
|
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
|
||||||
|
if (items.HasFlag(Items.UserConfig)){
|
||||||
|
stream.WriteFile("config", Program.UserConfigFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.HasFlag(Items.SystemConfig)){
|
||||||
|
stream.WriteFile("system", Program.SystemConfigFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.HasFlag(Items.PluginData)){
|
||||||
|
stream.WriteFile("plugin.config", Program.PluginConfigFilePath);
|
||||||
|
|
||||||
|
foreach(Plugin plugin in plugins.Plugins){
|
||||||
|
foreach(PathInfo path in EnumerateFilesRelative(plugin.GetPluginFolder(PluginFolder.Data))){
|
||||||
|
try{
|
||||||
|
stream.WriteFile(new string[]{ "plugin.data", plugin.Identifier, path.Relative }, path.Full);
|
||||||
|
}catch(ArgumentOutOfRangeException e){
|
||||||
|
FormMessage.Warning("Export Profile", "Could not include a plugin file in the export. "+e.Message, FormMessage.OK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.HasFlag(Items.Session)){
|
||||||
|
stream.WriteFile("cookies", CookiesPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}catch(Exception e){
|
||||||
|
Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting TweetDuck profile.", true, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Items FindImportItems(){
|
||||||
|
Items items = Items.None;
|
||||||
|
|
||||||
|
try{
|
||||||
|
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))){
|
||||||
|
string key;
|
||||||
|
|
||||||
|
while((key = stream.SkipFile()) != null){
|
||||||
|
switch(key){
|
||||||
|
case "config":
|
||||||
|
items |= Items.UserConfig;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "system":
|
||||||
|
items |= Items.SystemConfig;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "plugin.config":
|
||||||
|
case "plugin.data":
|
||||||
|
items |= Items.PluginData;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "cookies":
|
||||||
|
items |= Items.Session;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch(Exception){
|
||||||
|
items = Items.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Import(Items items){
|
||||||
|
try{
|
||||||
|
HashSet<string> missingPlugins = new HashSet<string>();
|
||||||
|
|
||||||
|
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))){
|
||||||
|
CombinedFileStream.Entry entry;
|
||||||
|
|
||||||
|
while((entry = stream.ReadFile()) != null){
|
||||||
|
switch(entry.KeyName){
|
||||||
|
case "config":
|
||||||
|
if (items.HasFlag(Items.UserConfig)){
|
||||||
|
entry.WriteToFile(Program.UserConfigFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "system":
|
||||||
|
if (items.HasFlag(Items.SystemConfig)){
|
||||||
|
entry.WriteToFile(Program.SystemConfigFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "plugin.config":
|
||||||
|
if (items.HasFlag(Items.PluginData)){
|
||||||
|
entry.WriteToFile(Program.PluginConfigFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "plugin.data":
|
||||||
|
if (items.HasFlag(Items.PluginData)){
|
||||||
|
string[] value = entry.KeyValue;
|
||||||
|
|
||||||
|
entry.WriteToFile(Path.Combine(Program.PluginDataPath, value[0], value[1]), true);
|
||||||
|
|
||||||
|
if (!plugins.IsPluginInstalled(value[0])){
|
||||||
|
missingPlugins.Add(value[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "cookies":
|
||||||
|
if (items.HasFlag(Items.Session)){
|
||||||
|
entry.WriteToFile(Path.Combine(Program.StoragePath, TempCookiesPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missingPlugins.Count > 0){
|
||||||
|
FormMessage.Information("Profile Import", "Detected missing plugins when importing plugin data:\n"+string.Join("\n", missingPlugins), FormMessage.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}catch(Exception e){
|
||||||
|
Program.Reporter.HandleException("Profile Import", "An exception happened while importing TweetDuck profile.", true, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ImportCookies(){
|
||||||
|
if (File.Exists(TempCookiesPath)){
|
||||||
|
try{
|
||||||
|
if (File.Exists(CookiesPath)){
|
||||||
|
File.Delete(CookiesPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
File.Move(TempCookiesPath, CookiesPath);
|
||||||
|
}catch(Exception e){
|
||||||
|
Program.Reporter.HandleException("Profile Import Error", "Could not import the cookie file to restore login session.", true, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DeleteCookies(){
|
||||||
|
try{
|
||||||
|
if (File.Exists(CookiesPath)){
|
||||||
|
File.Delete(CookiesPath);
|
||||||
|
}
|
||||||
|
}catch(Exception e){
|
||||||
|
Program.Reporter.HandleException("Session Reset Error", "Could not remove the cookie file to reset the login session.", true, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<PathInfo> EnumerateFilesRelative(string root){
|
||||||
|
if (Directory.Exists(root)){
|
||||||
|
int rootLength = root.Length;
|
||||||
|
return Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo(fullPath, rootLength));
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return Enumerable.Empty<PathInfo>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class PathInfo{
|
||||||
|
public string Full { get; }
|
||||||
|
public string Relative { get; }
|
||||||
|
|
||||||
|
public PathInfo(string fullPath, int rootLength){
|
||||||
|
this.Full = fullPath;
|
||||||
|
this.Relative = fullPath.Substring(rootLength).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); // strip leading separator character
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
213
Core/Management/VideoPlayer.cs
Normal file
213
Core/Management/VideoPlayer.cs
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Configuration;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Other;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetLib.Communication;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Management{
|
||||||
|
sealed class VideoPlayer : IDisposable{
|
||||||
|
private static UserConfig Config => Program.Config.User;
|
||||||
|
|
||||||
|
public bool Running => currentInstance != null && currentInstance.Running;
|
||||||
|
|
||||||
|
public event EventHandler ProcessExited;
|
||||||
|
|
||||||
|
private readonly FormBrowser owner;
|
||||||
|
|
||||||
|
private Instance currentInstance;
|
||||||
|
private bool isClosing;
|
||||||
|
|
||||||
|
public VideoPlayer(FormBrowser owner){
|
||||||
|
this.owner = owner;
|
||||||
|
this.owner.FormClosing += owner_FormClosing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Launch(string url, string username){
|
||||||
|
if (Running){
|
||||||
|
Destroy();
|
||||||
|
isClosing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
DuplexPipe.Server pipe = DuplexPipe.CreateServer();
|
||||||
|
pipe.DataIn += pipe_DataIn;
|
||||||
|
|
||||||
|
Process process;
|
||||||
|
|
||||||
|
if ((process = Process.Start(new ProcessStartInfo{
|
||||||
|
FileName = Path.Combine(Program.ProgramPath, "TweetDuck.Video.exe"),
|
||||||
|
Arguments = $"{owner.Handle} {(int)Math.Floor(100F*owner.GetDPIScale())} {Config.VideoPlayerVolume} \"{url}\" \"{pipe.GenerateToken()}\"",
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true
|
||||||
|
})) != null){
|
||||||
|
currentInstance = new Instance(process, pipe, url, username);
|
||||||
|
|
||||||
|
process.EnableRaisingEvents = true;
|
||||||
|
process.Exited += process_Exited;
|
||||||
|
|
||||||
|
process.BeginOutputReadLine();
|
||||||
|
process.OutputDataReceived += process_OutputDataReceived;
|
||||||
|
|
||||||
|
pipe.DisposeToken();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
pipe.DataIn -= pipe_DataIn;
|
||||||
|
pipe.Dispose();
|
||||||
|
}
|
||||||
|
}catch(Exception e){
|
||||||
|
Program.Reporter.HandleException("Video Playback Error", "Error launching video player.", true, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendKeyEvent(Keys key){
|
||||||
|
currentInstance?.Pipe.Write("key", ((int)key).ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pipe_DataIn(object sender, DuplexPipe.PipeReadEventArgs e){
|
||||||
|
owner.InvokeSafe(() => {
|
||||||
|
switch(e.Key){
|
||||||
|
case "vol":
|
||||||
|
if (int.TryParse(e.Data, out int volume) && volume != Config.VideoPlayerVolume){
|
||||||
|
Config.VideoPlayerVolume = volume;
|
||||||
|
Config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "download":
|
||||||
|
if (currentInstance != null){
|
||||||
|
owner.AnalyticsFile.DownloadedVideos.Trigger();
|
||||||
|
TwitterUtils.DownloadVideo(currentInstance.Url, currentInstance.Username);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "rip":
|
||||||
|
currentInstance?.Dispose();
|
||||||
|
currentInstance = null;
|
||||||
|
|
||||||
|
isClosing = false;
|
||||||
|
TriggerProcessExitEventUnsafe();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close(){
|
||||||
|
if (currentInstance != null){
|
||||||
|
if (isClosing){
|
||||||
|
Destroy();
|
||||||
|
isClosing = false;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
isClosing = true;
|
||||||
|
currentInstance.Process.Exited -= process_Exited;
|
||||||
|
currentInstance.Pipe.Write("die");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose(){
|
||||||
|
ProcessExited = null;
|
||||||
|
|
||||||
|
isClosing = true;
|
||||||
|
Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Destroy(){
|
||||||
|
if (currentInstance != null){
|
||||||
|
currentInstance.KillAndDispose();
|
||||||
|
currentInstance = null;
|
||||||
|
|
||||||
|
TriggerProcessExitEventUnsafe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void owner_FormClosing(object sender, FormClosingEventArgs e){
|
||||||
|
if (currentInstance != null){
|
||||||
|
currentInstance.Process.Exited -= process_Exited;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void process_OutputDataReceived(object sender, DataReceivedEventArgs e){
|
||||||
|
if (!string.IsNullOrEmpty(e.Data)){
|
||||||
|
Program.Reporter.LogVerbose("[VideoPlayer] "+e.Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void process_Exited(object sender, EventArgs e){
|
||||||
|
if (currentInstance == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int exitCode = currentInstance.Process.ExitCode;
|
||||||
|
string url = currentInstance.Url;
|
||||||
|
|
||||||
|
currentInstance.Dispose();
|
||||||
|
currentInstance = null;
|
||||||
|
|
||||||
|
switch(exitCode){
|
||||||
|
case 3: // CODE_LAUNCH_FAIL
|
||||||
|
if (FormMessage.Error("Video Playback Error", "Error launching video player, this may be caused by missing Windows Media Player. Do you want to open the video in your browser?", FormMessage.Yes, FormMessage.No)){
|
||||||
|
BrowserUtils.OpenExternalBrowser(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4: // CODE_MEDIA_ERROR
|
||||||
|
if (FormMessage.Error("Video Playback Error", "The video could not be loaded, most likely due to unknown format. Do you want to open the video in your browser?", FormMessage.Yes, FormMessage.No)){
|
||||||
|
BrowserUtils.OpenExternalBrowser(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
owner.InvokeAsyncSafe(TriggerProcessExitEventUnsafe);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TriggerProcessExitEventUnsafe(){
|
||||||
|
ProcessExited?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class Instance : IDisposable{
|
||||||
|
public bool Running{
|
||||||
|
get{
|
||||||
|
Process.Refresh();
|
||||||
|
return !Process.HasExited;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Process Process { get; }
|
||||||
|
public DuplexPipe.Server Pipe { get; }
|
||||||
|
|
||||||
|
public string Url { get; }
|
||||||
|
public string Username { get; }
|
||||||
|
|
||||||
|
public Instance(Process process, DuplexPipe.Server pipe, string url, string username){
|
||||||
|
this.Process = process;
|
||||||
|
this.Pipe = pipe;
|
||||||
|
this.Url = url;
|
||||||
|
this.Username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void KillAndDispose(){
|
||||||
|
try{
|
||||||
|
Process.Kill();
|
||||||
|
}catch{
|
||||||
|
// kill me instead then
|
||||||
|
}
|
||||||
|
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose(){
|
||||||
|
Process.Dispose();
|
||||||
|
Pipe.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
68
Core/Notification/Example/FormNotificationExample.cs
Normal file
68
Core/Notification/Example/FormNotificationExample.cs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Plugins;
|
||||||
|
using TweetDuck.Resources;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Notification.Example{
|
||||||
|
sealed class FormNotificationExample : FormNotificationMain{
|
||||||
|
public override bool RequiresResize => true;
|
||||||
|
protected override bool CanDragWindow => Config.NotificationPosition == TweetNotification.Position.Custom;
|
||||||
|
|
||||||
|
protected override FormBorderStyle NotificationBorderStyle{
|
||||||
|
get{
|
||||||
|
if (Config.NotificationSize == TweetNotification.Size.Custom){
|
||||||
|
switch(base.NotificationBorderStyle){
|
||||||
|
case FormBorderStyle.FixedSingle: return FormBorderStyle.Sizable;
|
||||||
|
case FormBorderStyle.FixedToolWindow: return FormBorderStyle.SizableToolWindow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.NotificationBorderStyle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string BodyClasses => base.BodyClasses+" td-example";
|
||||||
|
|
||||||
|
public event EventHandler Ready;
|
||||||
|
|
||||||
|
private readonly TweetNotification exampleNotification;
|
||||||
|
|
||||||
|
public FormNotificationExample(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, false){
|
||||||
|
browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||||
|
|
||||||
|
string exampleTweetHTML = ScriptLoader.LoadResourceSilent("pages/example.html")?.Replace("{avatar}", TweetNotification.AppLogo.Url) ?? string.Empty;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
exampleTweetHTML = exampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
exampleNotification = new TweetNotification(string.Empty, string.Empty, "Home", exampleTweetHTML, 176, string.Empty, string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
|
||||||
|
if (!e.IsLoading){
|
||||||
|
Ready?.Invoke(this, EventArgs.Empty);
|
||||||
|
browser.LoadingStateChanged -= browser_LoadingStateChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void HideNotification(){
|
||||||
|
Location = ControlExtensions.InvisibleLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FinishCurrentNotification(){}
|
||||||
|
|
||||||
|
public void ShowExampleNotification(bool reset){
|
||||||
|
if (reset){
|
||||||
|
LoadTweet(exampleNotification);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
PrepareAndDisplayWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateTitle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
Core/Notification/FormNotificationBase.Designer.cs
generated
Normal file
52
Core/Notification/FormNotificationBase.Designer.cs
generated
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
namespace TweetDuck.Core.Notification {
|
||||||
|
partial class FormNotificationBase {
|
||||||
|
/// <summary>
|
||||||
|
/// Required designer variable.
|
||||||
|
/// </summary>
|
||||||
|
private System.ComponentModel.IContainer components = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clean up any resources being used.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||||
|
protected override void Dispose(bool disposing) {
|
||||||
|
if (disposing && (components != null)) {
|
||||||
|
components.Dispose();
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Windows Form Designer generated code
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Required method for Designer support - do not modify
|
||||||
|
/// the contents of this method with the code editor.
|
||||||
|
/// </summary>
|
||||||
|
private void InitializeComponent() {
|
||||||
|
this.components = new System.ComponentModel.Container();
|
||||||
|
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
||||||
|
this.SuspendLayout();
|
||||||
|
//
|
||||||
|
// FormNotification
|
||||||
|
//
|
||||||
|
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||||
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
|
this.BackColor = System.Drawing.SystemColors.Control;
|
||||||
|
this.ClientSize = new System.Drawing.Size(284, 122);
|
||||||
|
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
|
||||||
|
this.Location = TweetDuck.Core.Controls.ControlExtensions.InvisibleLocation;
|
||||||
|
this.MaximizeBox = false;
|
||||||
|
this.MinimizeBox = false;
|
||||||
|
this.Name = "FormNotification";
|
||||||
|
this.ShowIcon = false;
|
||||||
|
this.ShowInTaskbar = false;
|
||||||
|
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
|
||||||
|
this.ResumeLayout(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private System.Windows.Forms.ToolTip toolTip;
|
||||||
|
}
|
||||||
|
}
|
||||||
234
Core/Notification/FormNotificationBase.cs
Normal file
234
Core/Notification/FormNotificationBase.cs
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
using CefSharp.WinForms;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Configuration;
|
||||||
|
using TweetDuck.Core.Bridge;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Handling;
|
||||||
|
using TweetDuck.Core.Handling.General;
|
||||||
|
using TweetDuck.Core.Other.Analytics;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Notification{
|
||||||
|
abstract partial class FormNotificationBase : Form, AnalyticsFile.IProvider{
|
||||||
|
protected static UserConfig Config => Program.Config.User;
|
||||||
|
|
||||||
|
protected static int FontSizeLevel{
|
||||||
|
get => TweetDeckBridge.FontSize switch{
|
||||||
|
"largest" => 4,
|
||||||
|
"large" => 3,
|
||||||
|
"small" => 1,
|
||||||
|
"smallest" => 0,
|
||||||
|
_ => 2
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AnalyticsFile AnalyticsFile => owner.AnalyticsFile;
|
||||||
|
|
||||||
|
protected override bool ShowWithoutActivation => true;
|
||||||
|
|
||||||
|
protected float DpiScale { get; }
|
||||||
|
protected double SizeScale => DpiScale*Config.ZoomLevel/100.0;
|
||||||
|
|
||||||
|
protected readonly FormBrowser owner;
|
||||||
|
protected readonly ChromiumWebBrowser browser;
|
||||||
|
|
||||||
|
private readonly ResourceHandlerNotification resourceHandler = new ResourceHandlerNotification();
|
||||||
|
|
||||||
|
private TweetNotification currentNotification;
|
||||||
|
private int pauseCounter;
|
||||||
|
|
||||||
|
public string CurrentTweetUrl => currentNotification?.TweetUrl;
|
||||||
|
public string CurrentQuoteUrl => currentNotification?.QuoteUrl;
|
||||||
|
|
||||||
|
public bool CanViewDetail => currentNotification != null && !string.IsNullOrEmpty(currentNotification.ColumnId) && !string.IsNullOrEmpty(currentNotification.ChirpId);
|
||||||
|
|
||||||
|
protected bool IsPaused => pauseCounter > 0;
|
||||||
|
protected bool IsCursorOverBrowser => browser.Bounds.Contains(PointToClient(Cursor.Position));
|
||||||
|
|
||||||
|
public bool FreezeTimer { get; set; }
|
||||||
|
public bool ContextMenuOpen { get; set; }
|
||||||
|
|
||||||
|
protected FormNotificationBase(FormBrowser owner, bool enableContextMenu){
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
this.owner = owner;
|
||||||
|
this.owner.FormClosed += owner_FormClosed;
|
||||||
|
|
||||||
|
ResourceHandlerFactory resourceHandlerFactory = new ResourceHandlerFactory();
|
||||||
|
resourceHandlerFactory.RegisterHandler(TwitterUtils.TweetDeckURL, this.resourceHandler);
|
||||||
|
resourceHandlerFactory.RegisterHandler(TweetNotification.AppLogo);
|
||||||
|
|
||||||
|
this.browser = new ChromiumWebBrowser("about:blank"){
|
||||||
|
MenuHandler = new ContextMenuNotification(this, enableContextMenu),
|
||||||
|
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||||
|
LifeSpanHandler = new LifeSpanHandler(),
|
||||||
|
RequestHandler = new RequestHandlerBase(false),
|
||||||
|
ResourceHandlerFactory = resourceHandlerFactory
|
||||||
|
};
|
||||||
|
|
||||||
|
this.browser.Dock = DockStyle.None;
|
||||||
|
this.browser.ClientSize = ClientSize;
|
||||||
|
this.browser.SetupZoomEvents();
|
||||||
|
|
||||||
|
Controls.Add(browser);
|
||||||
|
|
||||||
|
Disposed += (sender, args) => {
|
||||||
|
this.browser.Dispose();
|
||||||
|
this.owner.FormClosed -= owner_FormClosed;
|
||||||
|
};
|
||||||
|
|
||||||
|
DpiScale = this.GetDPIScale();
|
||||||
|
|
||||||
|
// ReSharper disable once VirtualMemberCallInContructor
|
||||||
|
UpdateTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
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("about:blank");
|
||||||
|
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 abstract string GetTweetHTML(TweetNotification tweet);
|
||||||
|
|
||||||
|
protected virtual void LoadTweet(TweetNotification tweet){
|
||||||
|
currentNotification = tweet;
|
||||||
|
resourceHandler.SetHTML(GetTweetHTML(tweet));
|
||||||
|
|
||||||
|
browser.Load(TwitterUtils.TweetDeckURL);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,7 +1,5 @@
|
|||||||
using TweetDck.Core.Controls;
|
namespace TweetDuck.Core.Notification {
|
||||||
|
partial class FormNotificationMain {
|
||||||
namespace TweetDck.Core {
|
|
||||||
sealed partial class FormNotification {
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Required designer variable.
|
/// Required designer variable.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -26,22 +24,15 @@ namespace TweetDck.Core {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void InitializeComponent() {
|
private void InitializeComponent() {
|
||||||
this.components = new System.ComponentModel.Container();
|
this.components = new System.ComponentModel.Container();
|
||||||
this.panelBrowser = new System.Windows.Forms.Panel();
|
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.Core.Controls.FlatProgressBar();
|
||||||
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
|
||||||
this.SuspendLayout();
|
this.SuspendLayout();
|
||||||
//
|
//
|
||||||
// panelBrowser
|
// timerDisplayDelay
|
||||||
//
|
//
|
||||||
this.panelBrowser.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
this.timerDisplayDelay.Interval = 17;
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
this.timerDisplayDelay.Tick += new System.EventHandler(this.timerDisplayDelay_Tick);
|
||||||
this.panelBrowser.BackColor = System.Drawing.Color.White;
|
|
||||||
this.panelBrowser.Location = new System.Drawing.Point(0, 0);
|
|
||||||
this.panelBrowser.Margin = new System.Windows.Forms.Padding(0);
|
|
||||||
this.panelBrowser.Name = "panelBrowser";
|
|
||||||
this.panelBrowser.Size = new System.Drawing.Size(284, 118);
|
|
||||||
this.panelBrowser.TabIndex = 0;
|
|
||||||
//
|
//
|
||||||
// timerProgress
|
// timerProgress
|
||||||
//
|
//
|
||||||
@@ -68,15 +59,6 @@ namespace TweetDck.Core {
|
|||||||
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.Controls.Add(this.progressBarTimer);
|
this.Controls.Add(this.progressBarTimer);
|
||||||
this.Controls.Add(this.panelBrowser);
|
|
||||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
|
|
||||||
this.Location = new System.Drawing.Point(-32000, -32000);
|
|
||||||
this.MaximizeBox = false;
|
|
||||||
this.MinimizeBox = false;
|
|
||||||
this.Name = "FormNotification";
|
|
||||||
this.ShowIcon = false;
|
|
||||||
this.ShowInTaskbar = false;
|
|
||||||
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
|
|
||||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormNotification_FormClosing);
|
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormNotification_FormClosing);
|
||||||
this.ResumeLayout(false);
|
this.ResumeLayout(false);
|
||||||
|
|
||||||
@@ -84,9 +66,8 @@ namespace TweetDck.Core {
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private System.Windows.Forms.Panel panelBrowser;
|
private System.Windows.Forms.Timer timerDisplayDelay;
|
||||||
|
protected System.Windows.Forms.Timer timerProgress;
|
||||||
private Controls.FlatProgressBar progressBarTimer;
|
private Controls.FlatProgressBar progressBarTimer;
|
||||||
private System.Windows.Forms.Timer timerProgress;
|
|
||||||
private System.Windows.Forms.ToolTip toolTip;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
266
Core/Notification/FormNotificationMain.cs
Normal file
266
Core/Notification/FormNotificationMain.cs
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
using CefSharp;
|
||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Core.Bridge;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Handling;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetDuck.Plugins;
|
||||||
|
using TweetDuck.Resources;
|
||||||
|
using TweetLib.Core.Data;
|
||||||
|
using TweetLib.Core.Features.Plugins.Enums;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Notification{
|
||||||
|
abstract partial class FormNotificationMain : FormNotificationBase{
|
||||||
|
private readonly PluginManager plugins;
|
||||||
|
private readonly int timerBarHeight;
|
||||||
|
|
||||||
|
protected int timeLeft, totalTime;
|
||||||
|
protected bool pausedDuringNotification;
|
||||||
|
|
||||||
|
private readonly NativeMethods.HookProc mouseHookDelegate;
|
||||||
|
private IntPtr mouseHook;
|
||||||
|
private bool blockXButtonUp;
|
||||||
|
|
||||||
|
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{
|
||||||
|
TweetNotification.Size.Custom => Config.CustomNotificationSize.Width,
|
||||||
|
_ => BrowserUtils.Scale(284, SizeScale * (1.0 + 0.05 * FontSizeLevel))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private int BaseClientHeight{
|
||||||
|
get => Config.NotificationSize switch{
|
||||||
|
TweetNotification.Size.Custom => Config.CustomNotificationSize.Height,
|
||||||
|
_ => BrowserUtils.Scale(122, SizeScale * (1.0 + 0.08 * FontSizeLevel))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual string BodyClasses => IsCursorOverBrowser ? "td-notification td-hover" : "td-notification";
|
||||||
|
|
||||||
|
public Size BrowserSize => Config.DisplayNotificationTimer ? new Size(ClientSize.Width, ClientSize.Height-timerBarHeight) : ClientSize;
|
||||||
|
|
||||||
|
protected FormNotificationMain(FormBrowser owner, PluginManager pluginManager, bool enableContextMenu) : base(owner, enableContextMenu){
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
this.plugins = pluginManager;
|
||||||
|
this.timerBarHeight = BrowserUtils.Scale(4, DpiScale);
|
||||||
|
|
||||||
|
browser.KeyboardHandler = new KeyboardHandlerNotification(this);
|
||||||
|
browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge.Notification(owner, this));
|
||||||
|
|
||||||
|
browser.LoadingStateChanged += Browser_LoadingStateChanged;
|
||||||
|
browser.FrameLoadEnd += Browser_FrameLoadEnd;
|
||||||
|
|
||||||
|
plugins.Register(browser, PluginEnvironment.Notification);
|
||||||
|
|
||||||
|
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 && IsCursorOverBrowser){
|
||||||
|
browser.SendMouseWheelEvent(0, 0, 0, BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Config.NotificationScrollSpeed*0.01), 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;
|
||||||
|
this.InvokeAsyncSafe(AnalyticsFile.NotificationExtraMouseButtons.Trigger);
|
||||||
|
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 != "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){
|
||||||
|
IFrame frame = e.Frame;
|
||||||
|
|
||||||
|
if (frame.IsMain && browser.Address != "about:blank"){
|
||||||
|
frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification));
|
||||||
|
ScriptLoader.ExecuteFile(frame, "notification.js", this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = 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(TweetNotification 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 string GetTweetHTML(TweetNotification tweet){
|
||||||
|
string html = tweet.GenerateHtml(BodyClasses, this);
|
||||||
|
|
||||||
|
foreach(InjectedHTML injection in plugins.NotificationInjections){
|
||||||
|
html = injection.InjectInto(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadTweet(TweetNotification 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
MoveToVisibleLocation();
|
||||||
|
StartMouseHook();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnNotificationReady(){
|
||||||
|
PrepareAndDisplayWindow();
|
||||||
|
timerProgress.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
Core/Notification/FormNotificationTweet.Designer.cs
generated
Normal file
52
Core/Notification/FormNotificationTweet.Designer.cs
generated
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
namespace TweetDuck.Core.Notification {
|
||||||
|
partial class FormNotificationTweet {
|
||||||
|
/// <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.components = new System.ComponentModel.Container();
|
||||||
|
this.timerCursorCheck = new System.Windows.Forms.Timer(this.components);
|
||||||
|
this.timerIdlePauseCheck = new System.Windows.Forms.Timer(this.components);
|
||||||
|
this.SuspendLayout();
|
||||||
|
//
|
||||||
|
// timerCursorCheck
|
||||||
|
//
|
||||||
|
this.timerCursorCheck.Interval = 200;
|
||||||
|
this.timerCursorCheck.Tick += new System.EventHandler(this.timerCursorCheck_Tick);
|
||||||
|
//
|
||||||
|
// timerIdlePauseCheck
|
||||||
|
//
|
||||||
|
this.timerIdlePauseCheck.Interval = 750;
|
||||||
|
this.timerIdlePauseCheck.Tick += new System.EventHandler(this.timerIdlePauseCheck_Tick);
|
||||||
|
//
|
||||||
|
// FormNotificationTweet
|
||||||
|
//
|
||||||
|
this.ResumeLayout(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private System.Windows.Forms.Timer timerCursorCheck;
|
||||||
|
private System.Windows.Forms.Timer timerIdlePauseCheck;
|
||||||
|
}
|
||||||
|
}
|
||||||
165
Core/Notification/FormNotificationTweet.cs
Normal file
165
Core/Notification/FormNotificationTweet.cs
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using TweetDuck.Plugins;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Notification{
|
||||||
|
sealed partial class FormNotificationTweet : FormNotificationMain{
|
||||||
|
private const int NonIntrusiveIdleLimit = 30;
|
||||||
|
private const int TrimMinimum = 32;
|
||||||
|
|
||||||
|
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<TweetNotification> tweetQueue = new Queue<TweetNotification>(4);
|
||||||
|
private bool needsTrim;
|
||||||
|
private bool hasTemporarilyMoved;
|
||||||
|
|
||||||
|
public FormNotificationTweet(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, true){
|
||||||
|
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(TweetNotification notification){
|
||||||
|
tweetQueue.Enqueue(notification);
|
||||||
|
|
||||||
|
if (!IsPaused){
|
||||||
|
UpdateTitle();
|
||||||
|
|
||||||
|
if (totalTime == 0){
|
||||||
|
LoadNextNotification();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
needsTrim |= tweetQueue.Count >= TrimMinimum;
|
||||||
|
AnalyticsFile.DesktopNotifications.Trigger();
|
||||||
|
}
|
||||||
|
|
||||||
|
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,98 @@
|
|||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Drawing.Imaging;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Other;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetDuck.Plugins;
|
||||||
|
using TweetDuck.Resources;
|
||||||
|
using TweetLib.Core.Data;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Notification.Screenshot{
|
||||||
|
sealed class FormNotificationScreenshotable : FormNotificationBase{
|
||||||
|
protected override bool CanDragWindow => false;
|
||||||
|
|
||||||
|
private readonly PluginManager plugins;
|
||||||
|
private int height;
|
||||||
|
|
||||||
|
public FormNotificationScreenshotable(Action callback, FormBrowser owner, PluginManager pluginManager, string html, int width) : base(owner, false){
|
||||||
|
this.plugins = pluginManager;
|
||||||
|
|
||||||
|
int realWidth = BrowserUtils.Scale(width, DpiScale);
|
||||||
|
|
||||||
|
browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new ScreenshotBridge(this, SetScreenshotHeight, callback));
|
||||||
|
|
||||||
|
browser.LoadingStateChanged += (sender, args) => {
|
||||||
|
if (args.IsLoading){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string script = ScriptLoader.LoadResourceSilent("screenshot.js");
|
||||||
|
|
||||||
|
if (script == null){
|
||||||
|
this.InvokeAsyncSafe(callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using(IFrame frame = args.Browser.MainFrame){
|
||||||
|
ScriptLoader.ExecuteScript(frame, script.Replace("{width}", realWidth.ToString()).Replace("{frames}", TweetScreenshotManager.WaitFrames.ToString()), "gen:screenshot");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
SetNotificationSize(realWidth, 1024);
|
||||||
|
LoadTweet(new TweetNotification(string.Empty, string.Empty, string.Empty, html, 0, string.Empty, string.Empty));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string GetTweetHTML(TweetNotification tweet){
|
||||||
|
string html = tweet.GenerateHtml("td-screenshot", this);
|
||||||
|
|
||||||
|
foreach(InjectedHTML injection in plugins.NotificationInjections){
|
||||||
|
html = injection.InjectInto(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetScreenshotHeight(int browserHeight){
|
||||||
|
this.height = BrowserUtils.Scale(browserHeight, SizeScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TakeScreenshot(bool ignoreHeightError = false){
|
||||||
|
if (!ignoreHeightError){
|
||||||
|
if (height == 0){
|
||||||
|
FormMessage.Error("Screenshot Failed", "Could not detect screenshot size.", FormMessage.OK);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (height > ClientSize.Height){
|
||||||
|
FormMessage.Error("Screenshot Failed", $"Screenshot is too large: {height}px > {ClientSize.Height}px", FormMessage.OK);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!WindowsUtils.IsAeroEnabled){
|
||||||
|
MoveToVisibleLocation(); // TODO make this look nicer I guess
|
||||||
|
}
|
||||||
|
|
||||||
|
IntPtr context = NativeMethods.GetDC(this.Handle);
|
||||||
|
|
||||||
|
if (context == IntPtr.Zero){
|
||||||
|
FormMessage.Error("Screenshot Failed", "Could not retrieve a graphics context handle for the notification window to take the screenshot.", FormMessage.OK);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
using(Bitmap bmp = new Bitmap(ClientSize.Width, Math.Max(1, height), PixelFormat.Format32bppRgb)){
|
||||||
|
try{
|
||||||
|
NativeMethods.RenderSourceIntoBitmap(context, bmp);
|
||||||
|
}finally{
|
||||||
|
NativeMethods.ReleaseDC(this.Handle, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
Clipboard.SetImage(bmp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Core/Notification/Screenshot/ScreenshotBridge.cs
Normal file
28
Core/Notification/Screenshot/ScreenshotBridge.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
148
Core/Notification/Screenshot/TweetScreenshotManager.cs
Normal file
148
Core/Notification/Screenshot/TweetScreenshotManager.cs
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
#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.Windows.Forms;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Plugins;
|
||||||
|
|
||||||
|
#if GEN_SCREENSHOT_FRAMES
|
||||||
|
using System.Drawing.Imaging;
|
||||||
|
using System.IO;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.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();
|
||||||
|
|
||||||
|
#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 && screenshot.TakeScreenshot(true)){
|
||||||
|
try{
|
||||||
|
Clipboard.GetImage()?.Save(Path.Combine(DebugScreenshotPath, "frame_"+(++frameCounter)+".png"), ImageFormat.Png);
|
||||||
|
}catch{
|
||||||
|
System.Diagnostics.Debug.WriteLine("Failed generating frame "+frameCounter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
debugger.Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
48
Core/Notification/SoundNotification.cs
Normal file
48
Core/Notification/SoundNotification.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
using System.IO;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Other;
|
||||||
|
using TweetDuck.Core.Other.Settings;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Notification{
|
||||||
|
static class SoundNotification{
|
||||||
|
public const string SupportedFormats = "*.wav;*.ogg;*.mp3;*.flac;*.opus;*.weba;*.webm";
|
||||||
|
|
||||||
|
public static IResourceHandler 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 ResourceHandler.FromFilePath(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
74
Core/Notification/TweetNotification.cs
Normal file
74
Core/Notification/TweetNotification.cs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetDuck.Core.Bridge;
|
||||||
|
using TweetDuck.Data;
|
||||||
|
using TweetDuck.Resources;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Notification{
|
||||||
|
sealed class TweetNotification{
|
||||||
|
private const string DefaultHeadLayout = @"<html class=""scroll-v os-windows dark txt-size--14"" lang=""en-US"" id=""tduck"" data-td-font=""medium"" data-td-theme=""dark""><head><meta charset=""utf-8""><link href=""https://ton.twimg.com/tweetdeck-web/web/dist/bundle.4b1f87e09d.css"" rel=""stylesheet""><style type='text/css'>body { background: rgb(34, 36, 38) !important }</style>";
|
||||||
|
public static readonly ResourceLink AppLogo = new ResourceLink("https://ton.twimg.com/tduck/avatar", ResourceHandler.FromByteArray(Properties.Resources.avatar, "image/png"));
|
||||||
|
|
||||||
|
public enum Position{
|
||||||
|
TopLeft, TopRight, BottomLeft, BottomRight, Custom
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Size{
|
||||||
|
Auto, Custom
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ColumnId { get; }
|
||||||
|
public string ChirpId { get; }
|
||||||
|
|
||||||
|
public string ColumnTitle { get; }
|
||||||
|
public string TweetUrl { get; }
|
||||||
|
public string QuoteUrl { get; }
|
||||||
|
|
||||||
|
private readonly string html;
|
||||||
|
private readonly int characters;
|
||||||
|
|
||||||
|
public TweetNotification(string columnId, string chirpId, string title, string html, int characters, string tweetUrl, string quoteUrl){
|
||||||
|
this.ColumnId = columnId;
|
||||||
|
this.ChirpId = chirpId;
|
||||||
|
|
||||||
|
this.ColumnTitle = title;
|
||||||
|
this.TweetUrl = tweetUrl;
|
||||||
|
this.QuoteUrl = quoteUrl;
|
||||||
|
|
||||||
|
this.html = html;
|
||||||
|
this.characters = characters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetDisplayDuration(int value){
|
||||||
|
return 2000+Math.Max(1000, value*characters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GenerateHtml(string bodyClasses, Control sync){
|
||||||
|
string headLayout = TweetDeckBridge.NotificationHeadLayout ?? DefaultHeadLayout;
|
||||||
|
string mainCSS = ScriptLoader.LoadResource("styles/notification.css", sync) ?? string.Empty;
|
||||||
|
string customCSS = Program.Config.User.CustomNotificationCSS ?? string.Empty;
|
||||||
|
|
||||||
|
StringBuilder build = new StringBuilder(320 + headLayout.Length + mainCSS.Length + customCSS.Length + html.Length);
|
||||||
|
build.Append("<!DOCTYPE html>");
|
||||||
|
build.Append(headLayout);
|
||||||
|
build.Append("<style type='text/css'>").Append(mainCSS).Append("</style>");
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(customCSS)){
|
||||||
|
build.Append("<style type='text/css'>").Append(customCSS).Append("</style>");
|
||||||
|
}
|
||||||
|
|
||||||
|
build.Append("</head><body class='scroll-styled-v");
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(bodyClasses)){
|
||||||
|
build.Append(' ').Append(bodyClasses);
|
||||||
|
}
|
||||||
|
|
||||||
|
build.Append("'><div class='column' style='width:100%!important;min-height:100vh!important;height:auto!important;overflow:initial!important;'>");
|
||||||
|
build.Append(html);
|
||||||
|
build.Append("</div></body></html>");
|
||||||
|
return build.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
136
Core/Other/Analytics/AnalyticsFile.cs
Normal file
136
Core/Other/Analytics/AnalyticsFile.cs
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using TweetLib.Core.Serialization;
|
||||||
|
using TweetLib.Core.Serialization.Converters;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Other.Analytics{
|
||||||
|
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Local")]
|
||||||
|
sealed class AnalyticsFile{
|
||||||
|
private static readonly FileSerializer<AnalyticsFile> Serializer = new FileSerializer<AnalyticsFile>();
|
||||||
|
|
||||||
|
static AnalyticsFile(){
|
||||||
|
Serializer.RegisterTypeConverter(typeof(DateTime), new SingleTypeConverter<DateTime>{
|
||||||
|
ConvertToString = value => value.ToBinary().ToString(),
|
||||||
|
ConvertToObject = value => DateTime.FromBinary(long.Parse(value))
|
||||||
|
});
|
||||||
|
|
||||||
|
Serializer.RegisterTypeConverter(typeof(Counter), new SingleTypeConverter<Counter>{
|
||||||
|
ConvertToString = value => value.Value.ToString(),
|
||||||
|
ConvertToObject = value => int.Parse(value)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly AnalyticsFile Dummy = new AnalyticsFile();
|
||||||
|
|
||||||
|
// STATE PROPERTIES
|
||||||
|
|
||||||
|
public DateTime LastDataCollection { get; set; } = DateTime.MinValue;
|
||||||
|
public string LastCollectionVersion { get; set; } = null;
|
||||||
|
public string LastCollectionMessage { get; set; } = null;
|
||||||
|
|
||||||
|
// USAGE DATA
|
||||||
|
|
||||||
|
public Counter OpenOptions { get; private set; } = 0;
|
||||||
|
public Counter OpenPlugins { get; private set; } = 0;
|
||||||
|
public Counter OpenAbout { get; private set; } = 0;
|
||||||
|
public Counter OpenGuide { get; private set; } = 0;
|
||||||
|
|
||||||
|
public Counter DesktopNotifications { get; private set; } = 0;
|
||||||
|
public Counter SoundNotifications { get; private set; } = 0;
|
||||||
|
public Counter NotificationMutes { get; private set; } = 0;
|
||||||
|
|
||||||
|
public Counter BrowserContextMenus { get; private set; } = 0;
|
||||||
|
public Counter BrowserExtraMouseButtons { get; private set; } = 0;
|
||||||
|
public Counter NotificationContextMenus { get; private set; } = 0;
|
||||||
|
public Counter NotificationExtraMouseButtons { get; private set; } = 0;
|
||||||
|
public Counter NotificationKeyboardShortcuts { get; private set; } = 0;
|
||||||
|
|
||||||
|
public Counter BrowserReloads { get; private set; } = 0;
|
||||||
|
public Counter CopiedUsernames { get; private set; } = 0;
|
||||||
|
public Counter ViewedImages { get; private set; } = 0;
|
||||||
|
public Counter DownloadedImages { get; private set; } = 0;
|
||||||
|
public Counter DownloadedVideos { get; private set; } = 0;
|
||||||
|
public Counter UsedROT13 { get; private set; } = 0;
|
||||||
|
|
||||||
|
public Counter TweetScreenshots { get; private set; } = 0;
|
||||||
|
public Counter TweetDetails { get; private set; } = 0;
|
||||||
|
public Counter VideoPlays { get; private set; } = 0;
|
||||||
|
|
||||||
|
// END OF DATA
|
||||||
|
|
||||||
|
public event EventHandler PropertyChanged;
|
||||||
|
|
||||||
|
private readonly string file;
|
||||||
|
|
||||||
|
private AnalyticsFile(string file){
|
||||||
|
this.file = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AnalyticsFile(){
|
||||||
|
this.file = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupProperties(){
|
||||||
|
foreach(Counter counter in GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(prop => prop.PropertyType == typeof(Counter)).Select(prop => (Counter)prop.GetValue(this))){
|
||||||
|
counter.SetOwner(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnPropertyChanged(){
|
||||||
|
PropertyChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save(){
|
||||||
|
if (file == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
Serializer.Write(file, this);
|
||||||
|
}catch(Exception e){
|
||||||
|
Program.Reporter.HandleException("Analytics File Error", "Could not save the analytics file.", true, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AnalyticsFile Load(string file){
|
||||||
|
AnalyticsFile config = new AnalyticsFile(file);
|
||||||
|
|
||||||
|
try{
|
||||||
|
Serializer.ReadIfExists(file, config);
|
||||||
|
}catch(Exception e){
|
||||||
|
Program.Reporter.HandleException("Analytics File Error", "Could not open the analytics file.", true, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
config.SetupProperties();
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IProvider{
|
||||||
|
AnalyticsFile AnalyticsFile { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class Counter{
|
||||||
|
public int Value { get; private set; }
|
||||||
|
|
||||||
|
private AnalyticsFile owner;
|
||||||
|
|
||||||
|
public Counter(int value){
|
||||||
|
this.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetOwner(AnalyticsFile owner){
|
||||||
|
this.owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Trigger(){
|
||||||
|
++Value;
|
||||||
|
owner?.OnPropertyChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator int(Counter counter) => counter.Value;
|
||||||
|
public static implicit operator Counter(int value) => new Counter(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
160
Core/Other/Analytics/AnalyticsManager.cs
Normal file
160
Core/Other/Analytics/AnalyticsManager.cs
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
// Uncomment to debug reports locally
|
||||||
|
// #define ANALYTICS_LOCALHOST
|
||||||
|
// #define ANALYTICS_INSTANT
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Timers;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetDuck.Plugins;
|
||||||
|
using TweetLib.Core;
|
||||||
|
using TweetLib.Core.Utils;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Other.Analytics{
|
||||||
|
sealed class AnalyticsManager : IDisposable{
|
||||||
|
private static readonly TimeSpan CollectionInterval = TimeSpan.FromDays(14);
|
||||||
|
|
||||||
|
private static readonly Uri CollectionUrl = new Uri(
|
||||||
|
#if (DEBUG && ANALYTICS_LOCALHOST)
|
||||||
|
"http://localhost/newhome/tweetduck/~breadcrumb/request.php?type=report"
|
||||||
|
#else
|
||||||
|
"https://tweetduck.chylex.com/breadcrumb/report"
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
|
||||||
|
public AnalyticsFile File { get; }
|
||||||
|
|
||||||
|
private readonly FormBrowser browser;
|
||||||
|
private readonly PluginManager plugins;
|
||||||
|
private readonly Timer currentTimer, saveTimer;
|
||||||
|
|
||||||
|
public AnalyticsManager(FormBrowser browser, PluginManager plugins, string file){
|
||||||
|
this.browser = browser;
|
||||||
|
this.plugins = plugins;
|
||||||
|
|
||||||
|
this.File = AnalyticsFile.Load(file);
|
||||||
|
this.File.PropertyChanged += File_PropertyChanged;
|
||||||
|
|
||||||
|
this.currentTimer = new Timer{ SynchronizingObject = browser };
|
||||||
|
this.currentTimer.Elapsed += currentTimer_Elapsed;
|
||||||
|
|
||||||
|
this.saveTimer = new Timer{ SynchronizingObject = browser, Interval = 60000 };
|
||||||
|
this.saveTimer.Elapsed += saveTimer_Elapsed;
|
||||||
|
|
||||||
|
if (this.File.LastCollectionVersion != Program.VersionTag){
|
||||||
|
ScheduleReportIn(TimeSpan.FromHours(8), string.Empty);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
RestartTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if (DEBUG && ANALYTICS_INSTANT)
|
||||||
|
SendReport();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose(){
|
||||||
|
File.PropertyChanged -= File_PropertyChanged;
|
||||||
|
|
||||||
|
if (saveTimer.Enabled){
|
||||||
|
File.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTimer.Dispose();
|
||||||
|
saveTimer.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void File_PropertyChanged(object sender, EventArgs e){
|
||||||
|
saveTimer.Enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveTimer_Elapsed(object sender, ElapsedEventArgs e){
|
||||||
|
saveTimer.Stop();
|
||||||
|
File.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ScheduleReportIn(TimeSpan delay, string message = null){
|
||||||
|
SetLastDataCollectionTime(DateTime.Now.Subtract(CollectionInterval).Add(delay), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetLastDataCollectionTime(DateTime dt, string message = null){
|
||||||
|
File.LastDataCollection = new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0, dt.Kind);
|
||||||
|
File.LastCollectionVersion = Program.VersionTag;
|
||||||
|
File.LastCollectionMessage = message ?? dt.ToString("g", Lib.Culture);
|
||||||
|
|
||||||
|
File.Save();
|
||||||
|
RestartTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RestartTimer(){
|
||||||
|
TimeSpan diff = DateTime.Now.Subtract(File.LastDataCollection);
|
||||||
|
int minutesTillNext = (int)(CollectionInterval.TotalMinutes-Math.Floor(diff.TotalMinutes));
|
||||||
|
|
||||||
|
currentTimer.Interval = Math.Max(minutesTillNext, 2)*60000;
|
||||||
|
currentTimer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void currentTimer_Elapsed(object sender, ElapsedEventArgs e){
|
||||||
|
currentTimer.Stop();
|
||||||
|
|
||||||
|
TimeSpan diff = DateTime.Now.Subtract(File.LastDataCollection);
|
||||||
|
|
||||||
|
if (Math.Floor(diff.TotalMinutes) >= CollectionInterval.TotalMinutes){
|
||||||
|
SendReport();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
RestartTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendReport(){
|
||||||
|
AnalyticsReportGenerator.ExternalInfo info = AnalyticsReportGenerator.ExternalInfo.From(browser);
|
||||||
|
|
||||||
|
Task.Factory.StartNew(() => {
|
||||||
|
AnalyticsReport report = AnalyticsReportGenerator.Create(File, info, plugins);
|
||||||
|
|
||||||
|
#if (DEBUG && !ANALYTICS_INSTANT)
|
||||||
|
System.Diagnostics.Debugger.Break();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
WebUtils.NewClient(BrowserUtils.UserAgentVanilla).UploadValues(CollectionUrl, "POST", report.ToNameValueCollection());
|
||||||
|
}).ContinueWith(task => browser.InvokeAsyncSafe(() => {
|
||||||
|
if (task.Status == TaskStatus.RanToCompletion){
|
||||||
|
SetLastDataCollectionTime(DateTime.Now);
|
||||||
|
}
|
||||||
|
else if (task.Exception != null){
|
||||||
|
string message = null;
|
||||||
|
|
||||||
|
if (task.Exception.InnerException is WebException e){
|
||||||
|
switch(e.Status){
|
||||||
|
case WebExceptionStatus.ConnectFailure:
|
||||||
|
message = "Connection Error";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WebExceptionStatus.NameResolutionFailure:
|
||||||
|
message = "DNS Error";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WebExceptionStatus.ProtocolError:
|
||||||
|
HttpWebResponse response = e.Response as HttpWebResponse;
|
||||||
|
message = "HTTP Error "+(response != null ? $"{(int)response.StatusCode} ({response.StatusDescription})" : "(unknown code)");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
System.IO.Stream responseStream = e.Response.GetResponseStream();
|
||||||
|
|
||||||
|
if (responseStream != null){
|
||||||
|
System.Diagnostics.Debug.WriteLine(new System.IO.StreamReader(responseStream).ReadToEnd());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
ScheduleReportIn(TimeSpan.FromHours(4), message ?? "Error: "+(task.Exception.InnerException?.Message ?? task.Exception.Message));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
Core/Other/Analytics/AnalyticsReport.cs
Normal file
57
Core/Other/Analytics/AnalyticsReport.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Other.Analytics{
|
||||||
|
sealed class AnalyticsReport : IEnumerable{
|
||||||
|
private OrderedDictionary data = new OrderedDictionary(32);
|
||||||
|
private int separators;
|
||||||
|
|
||||||
|
public void Add(int ignored){ // adding separators to pretty print
|
||||||
|
data.Add((++separators).ToString(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(string key, string value){
|
||||||
|
data.Add(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AnalyticsReport FinalizeReport(){
|
||||||
|
if (!data.IsReadOnly){
|
||||||
|
data = data.AsReadOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator GetEnumerator(){
|
||||||
|
return data.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public NameValueCollection ToNameValueCollection(){
|
||||||
|
NameValueCollection collection = new NameValueCollection();
|
||||||
|
|
||||||
|
foreach(DictionaryEntry entry in data){
|
||||||
|
if (entry.Value != null){
|
||||||
|
collection.Add(((string)entry.Key).ToLower().Replace(' ', '_'), (string)entry.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString(){
|
||||||
|
StringBuilder build = new StringBuilder(625);
|
||||||
|
|
||||||
|
foreach(DictionaryEntry entry in data){
|
||||||
|
if (entry.Value == null){
|
||||||
|
build.AppendLine();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
build.AppendLine(entry.Key+": "+entry.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return build.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
322
Core/Other/Analytics/AnalyticsReportGenerator.cs
Normal file
322
Core/Other/Analytics/AnalyticsReportGenerator.cs
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.IO;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
using TweetDuck.Configuration;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Management;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using TweetDuck.Core.Notification;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetDuck.Plugins;
|
||||||
|
using TweetLib.Core;
|
||||||
|
using TweetLib.Core.Features.Plugins;
|
||||||
|
using TweetLib.Core.Features.Plugins.Enums;
|
||||||
|
using TweetLib.Core.Utils;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Other.Analytics{
|
||||||
|
static class AnalyticsReportGenerator{
|
||||||
|
public static AnalyticsReport Create(AnalyticsFile file, ExternalInfo info, PluginManager plugins){
|
||||||
|
Dictionary<string, string> editLayoutDesign = EditLayoutDesignPluginData;
|
||||||
|
|
||||||
|
return new AnalyticsReport{
|
||||||
|
{ "App Version" , Program.VersionTag },
|
||||||
|
{ "App Type" , Program.IsPortable ? "portable" : "installed" },
|
||||||
|
{ "App Dev Tools" , Bool(BrowserUtils.HasDevTools) },
|
||||||
|
0,
|
||||||
|
{ "System Name" , SystemName },
|
||||||
|
{ "System Edition" , SystemEdition },
|
||||||
|
{ "System Environment" , Environment.Is64BitOperatingSystem ? "64-bit" : "32-bit" },
|
||||||
|
{ "System Build" , SystemBuild },
|
||||||
|
{ "System Locale" , Lib.Culture.Name.ToLower() },
|
||||||
|
0,
|
||||||
|
{ "RAM" , Exact(RamSize) },
|
||||||
|
{ "GPU" , GpuVendor },
|
||||||
|
0,
|
||||||
|
{ "Screen Count" , Exact(Screen.AllScreens.Length) },
|
||||||
|
{ "Screen Resolution" , info.Resolution ?? "(unknown)" },
|
||||||
|
{ "Screen DPI" , info.DPI != null ? Exact(info.DPI.Value) : "(unknown)" },
|
||||||
|
0,
|
||||||
|
{ "Hardware Acceleration" , Bool(SysConfig.HardwareAcceleration) },
|
||||||
|
{ "Clear Cache Automatically" , Bool(SysConfig.ClearCacheAutomatically) },
|
||||||
|
{ "Clear Cache Threshold" , Exact(SysConfig.ClearCacheThreshold) },
|
||||||
|
0,
|
||||||
|
{ "Expand Links" , Bool(UserConfig.ExpandLinksOnHover) },
|
||||||
|
{ "Search In First Column" , Bool(UserConfig.OpenSearchInFirstColumn) },
|
||||||
|
{ "Keep Like Follow Dialogs Open" , Bool(UserConfig.KeepLikeFollowDialogsOpen) },
|
||||||
|
{ "Best Image Quality" , Bool(UserConfig.BestImageQuality) },
|
||||||
|
{ "Animated Images" , Bool(UserConfig.EnableAnimatedImages) },
|
||||||
|
0,
|
||||||
|
{ "Smooth Scrolling" , Bool(UserConfig.EnableSmoothScrolling) },
|
||||||
|
{ "Custom Browser" , CustomBrowser },
|
||||||
|
{ "Zoom" , Exact(UserConfig.ZoomLevel) },
|
||||||
|
0,
|
||||||
|
{ "Spell Check" , Bool(UserConfig.EnableSpellCheck) },
|
||||||
|
{ "Spell Check Language" , UserConfig.SpellCheckLanguage.ToLower() },
|
||||||
|
{ "Translation Target Language" , UserConfig.TranslationTarget },
|
||||||
|
0,
|
||||||
|
{ "Updates" , Bool(UserConfig.EnableUpdateCheck) },
|
||||||
|
{ "Update Dismissed" , Bool(!string.IsNullOrEmpty(UserConfig.DismissedUpdate)) },
|
||||||
|
0,
|
||||||
|
{ "Tray" , TrayMode },
|
||||||
|
{ "Tray Highlight" , Bool(UserConfig.EnableTrayHighlight) },
|
||||||
|
0,
|
||||||
|
{ "Notification Position" , NotificationPosition },
|
||||||
|
{ "Notification Size" , NotificationSize },
|
||||||
|
{ "Notification Timer" , NotificationTimer },
|
||||||
|
{ "Notification Timer Speed" , RoundUp(UserConfig.NotificationDurationValue, 5) },
|
||||||
|
{ "Notification Scroll Speed" , Exact(UserConfig.NotificationScrollSpeed) },
|
||||||
|
{ "Notification Column Title" , Bool(UserConfig.DisplayNotificationColumn) },
|
||||||
|
{ "Notification Media Previews" , Bool(UserConfig.NotificationMediaPreviews) },
|
||||||
|
{ "Notification Link Skip" , Bool(UserConfig.NotificationSkipOnLinkClick) },
|
||||||
|
{ "Notification Non-Intrusive" , Bool(UserConfig.NotificationNonIntrusiveMode) },
|
||||||
|
{ "Notification Idle Pause" , Exact(UserConfig.NotificationIdlePauseSeconds) },
|
||||||
|
{ "Custom Sound Notification" , string.IsNullOrEmpty(UserConfig.NotificationSoundPath) ? "off" : Path.GetExtension(UserConfig.NotificationSoundPath) },
|
||||||
|
{ "Custom Sound Notification Volume" , RoundUp(UserConfig.NotificationSoundVolume, 5) },
|
||||||
|
0,
|
||||||
|
{ "Program Arguments" , List(ProgramArguments) },
|
||||||
|
{ "Custom CEF Arguments" , RoundUp((UserConfig.CustomCefArgs ?? string.Empty).Length, 10) },
|
||||||
|
{ "Custom Browser CSS" , RoundUp((UserConfig.CustomBrowserCSS ?? string.Empty).Length, 50) },
|
||||||
|
{ "Custom Notification CSS" , RoundUp((UserConfig.CustomNotificationCSS ?? string.Empty).Length, 50) },
|
||||||
|
0,
|
||||||
|
{ "Plugins All" , List(plugins.Plugins.Select(Plugin)) },
|
||||||
|
{ "Plugins Enabled" , List(plugins.Plugins.Where(plugins.Config.IsEnabled).Select(Plugin)) },
|
||||||
|
0,
|
||||||
|
{ "Theme" , Dict(editLayoutDesign, "_theme", "light/def") },
|
||||||
|
{ "Column Width" , Dict(editLayoutDesign, "columnWidth", "310px/def") },
|
||||||
|
{ "Font Size" , Dict(editLayoutDesign, "fontSize", "12px/def") },
|
||||||
|
{ "Large Quote Font" , Dict(editLayoutDesign, "increaseQuoteTextSize", "false/def") },
|
||||||
|
{ "Small Compose Font" , Dict(editLayoutDesign, "smallComposeTextSize", "false/def") },
|
||||||
|
{ "Avatar Radius" , Dict(editLayoutDesign, "avatarRadius", "2/def") },
|
||||||
|
{ "Hide Tweet Actions" , Dict(editLayoutDesign, "hideTweetActions", "true/def") },
|
||||||
|
{ "Move Tweet Actions" , Dict(editLayoutDesign, "moveTweetActionsToRight", "true/def") },
|
||||||
|
{ "Theme Color Tweaks" , Dict(editLayoutDesign, "themeColorTweaks", "true/def") },
|
||||||
|
{ "Revert Icons" , Dict(editLayoutDesign, "revertIcons", "true/def") },
|
||||||
|
{ "Optimize Animations" , Dict(editLayoutDesign, "optimizeAnimations", "true/def") },
|
||||||
|
{ "Reply Account Mode" , ReplyAccountConfigFromPlugin },
|
||||||
|
{ "Template Count" , Exact(TemplateCountFromPlugin) },
|
||||||
|
0,
|
||||||
|
{ "Opened Options" , LogRound(file.OpenOptions, 4) },
|
||||||
|
{ "Opened Plugins" , LogRound(file.OpenPlugins, 4) },
|
||||||
|
{ "Opened About" , LogRound(file.OpenAbout, 4) },
|
||||||
|
{ "Opened Guide" , LogRound(file.OpenGuide, 4) },
|
||||||
|
{ "Desktop Notifications" , LogRound(file.DesktopNotifications, 5) },
|
||||||
|
{ "Sound Notifications" , LogRound(file.SoundNotifications, 5) },
|
||||||
|
{ "Notification Mutes" , LogRound(file.NotificationMutes, 2) },
|
||||||
|
{ "Browser Context Menus" , LogRound(file.BrowserContextMenus, 2) },
|
||||||
|
{ "Browser Extra Mouse Buttons" , LogRound(file.BrowserExtraMouseButtons, 2) },
|
||||||
|
{ "Notification Context Menus" , LogRound(file.NotificationContextMenus, 2) },
|
||||||
|
{ "Notification Extra Mouse Buttons" , LogRound(file.NotificationExtraMouseButtons, 2) },
|
||||||
|
{ "Notification Keyboard Shortcuts" , LogRound(file.NotificationKeyboardShortcuts, 2) },
|
||||||
|
{ "Browser Reloads" , LogRound(file.BrowserReloads, 2) },
|
||||||
|
{ "Copied Usernames" , LogRound(file.CopiedUsernames, 2) },
|
||||||
|
{ "Viewed Images" , LogRound(file.ViewedImages, 2) },
|
||||||
|
{ "Downloaded Images" , LogRound(file.DownloadedImages, 2) },
|
||||||
|
{ "Downloaded Videos" , LogRound(file.DownloadedVideos, 2) },
|
||||||
|
{ "Used ROT13" , LogRound(file.UsedROT13, 2) },
|
||||||
|
{ "Tweet Screenshots" , LogRound(file.TweetScreenshots, 2) },
|
||||||
|
{ "Tweet Details" , LogRound(file.TweetDetails, 2) },
|
||||||
|
{ "Video Plays" , LogRound(file.VideoPlays, 4) }
|
||||||
|
}.FinalizeReport();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UserConfig UserConfig => Program.Config.User;
|
||||||
|
private static SystemConfig SysConfig => Program.Config.System;
|
||||||
|
|
||||||
|
private static string Bool(bool value) => value ? "on" : "off";
|
||||||
|
private static string Exact(int value) => value.ToString();
|
||||||
|
private static string RoundUp(int value, int multiple) => (multiple*(int)Math.Ceiling((double)value/multiple)).ToString();
|
||||||
|
private static string LogRound(int value, int logBase) => (value <= 0 ? 0 : (int)Math.Pow(logBase, Math.Floor(Math.Log(value, logBase)))).ToString();
|
||||||
|
private static string Plugin(Plugin plugin) => plugin.Group.GetIdentifierPrefixShort()+plugin.Identifier.Substring(plugin.Group.GetIdentifierPrefix().Length);
|
||||||
|
private static string Dict(Dictionary<string, string> dict, string key, string def = "(unknown)") => dict.TryGetValue(key, out string value) ? value : def;
|
||||||
|
private static string List(IEnumerable<string> list) => string.Join("|", list.DefaultIfEmpty("(none)"));
|
||||||
|
|
||||||
|
private static string SystemName { get; }
|
||||||
|
private static string SystemEdition { get; }
|
||||||
|
private static string SystemBuild { get; }
|
||||||
|
private static int RamSize { get; }
|
||||||
|
private static string GpuVendor { get; }
|
||||||
|
private static string[] ProgramArguments { get; }
|
||||||
|
|
||||||
|
static AnalyticsReportGenerator(){
|
||||||
|
string osName, osEdition, osBuild;
|
||||||
|
|
||||||
|
try{
|
||||||
|
using(RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", false)){
|
||||||
|
// ReSharper disable once PossibleNullReferenceException
|
||||||
|
osName = key.GetValue("ProductName") as string;
|
||||||
|
osBuild = key.GetValue("CurrentBuild") as string;
|
||||||
|
osEdition = null;
|
||||||
|
|
||||||
|
if (osName != null){
|
||||||
|
Match match = Regex.Match(osName, @"^(.*?\d+(?:\.\d+)?) (.*)$");
|
||||||
|
|
||||||
|
if (match.Success){
|
||||||
|
osName = match.Groups[1].Value;
|
||||||
|
osEdition = match.Groups[2].Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch{
|
||||||
|
osName = osEdition = osBuild = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemName = osName ?? "Windows (unknown)";
|
||||||
|
SystemEdition = osEdition ?? "(unknown)";
|
||||||
|
SystemBuild = osBuild ?? "(unknown)";
|
||||||
|
|
||||||
|
try{
|
||||||
|
using(ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Capacity FROM Win32_PhysicalMemory")){
|
||||||
|
foreach(ManagementBaseObject obj in searcher.Get()){
|
||||||
|
RamSize += (int)((ulong)obj["Capacity"]/(1024L*1024L));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch{
|
||||||
|
RamSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
string gpu = null;
|
||||||
|
|
||||||
|
try{
|
||||||
|
using(ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Caption FROM Win32_VideoController")){
|
||||||
|
foreach(ManagementBaseObject obj in searcher.Get()){
|
||||||
|
string vendor = obj["Caption"] as string;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(vendor)){
|
||||||
|
gpu = vendor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch{
|
||||||
|
// rip
|
||||||
|
}
|
||||||
|
|
||||||
|
GpuVendor = gpu ?? "(unknown)";
|
||||||
|
|
||||||
|
Dictionary<string, string> args = new Dictionary<string, string>();
|
||||||
|
Arguments.GetCurrentClean().ToDictionary(args);
|
||||||
|
ProgramArguments = args.Keys.Select(key => key.TrimStart('-')).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string CustomBrowser{
|
||||||
|
get{
|
||||||
|
return Path.GetFileName(UserConfig.BrowserPath) ?? string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string TrayMode{
|
||||||
|
get => UserConfig.TrayBehavior switch{
|
||||||
|
TrayIcon.Behavior.DisplayOnly => "icon",
|
||||||
|
TrayIcon.Behavior.MinimizeToTray => "minimize",
|
||||||
|
TrayIcon.Behavior.CloseToTray => "close",
|
||||||
|
TrayIcon.Behavior.Combined => "combined",
|
||||||
|
_ => "off"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NotificationPosition{
|
||||||
|
get => UserConfig.NotificationPosition switch{
|
||||||
|
TweetNotification.Position.TopLeft => "top left",
|
||||||
|
TweetNotification.Position.TopRight => "top right",
|
||||||
|
TweetNotification.Position.BottomLeft => "bottom left",
|
||||||
|
TweetNotification.Position.BottomRight => "bottom right",
|
||||||
|
_ => "custom"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NotificationSize{
|
||||||
|
get => UserConfig.NotificationSize switch{
|
||||||
|
TweetNotification.Size.Auto => "auto",
|
||||||
|
_ => RoundUp(UserConfig.CustomNotificationSize.Width, 20) + "x" + RoundUp(UserConfig.CustomNotificationSize.Height, 20)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NotificationTimer{
|
||||||
|
get{
|
||||||
|
if (!UserConfig.DisplayNotificationTimer){
|
||||||
|
return "off";
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return UserConfig.NotificationTimerCountDown ? "count down" : "count up";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, string> EditLayoutDesignPluginData{
|
||||||
|
get{
|
||||||
|
Dictionary<string, string> dict = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
try{
|
||||||
|
string data = File.ReadAllText(Path.Combine(Program.PluginDataPath, "official", "edit-design", "config.json"));
|
||||||
|
|
||||||
|
foreach(Match match in Regex.Matches(data, "\"(\\w+?)\":(.*?)[,}]")){
|
||||||
|
dict[match.Groups[1].Value] = match.Groups[2].Value.Trim('"');
|
||||||
|
}
|
||||||
|
}catch{
|
||||||
|
// rip
|
||||||
|
}
|
||||||
|
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int TemplateCountFromPlugin{
|
||||||
|
get{
|
||||||
|
try{
|
||||||
|
string data = File.ReadAllText(Path.Combine(Program.PluginDataPath, "official", "templates", "config.json"));
|
||||||
|
return Math.Min(StringUtils.CountOccurrences(data, "{\"name\":"), StringUtils.CountOccurrences(data, ",\"contents\":"));
|
||||||
|
}catch{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ReplyAccountConfigFromPlugin{
|
||||||
|
get{
|
||||||
|
try{
|
||||||
|
string data = File.ReadAllText(Path.Combine(Program.PluginDataPath, "official", "reply-account", "configuration.js")).Replace(" ", "");
|
||||||
|
|
||||||
|
Match matchType = Regex.Match(data, "defaultAccount:\"([#@])(.*?)\"(?:,|$)");
|
||||||
|
Match matchAdvanced = Regex.Match(data, "useAdvancedSelector:(.*?)(?:,|$)", RegexOptions.Multiline);
|
||||||
|
|
||||||
|
if (!matchType.Success){
|
||||||
|
return data.Contains("defaultAccount:\"\"") ? "(legacy)" : "(unknown)";
|
||||||
|
}
|
||||||
|
|
||||||
|
string accType = matchType.Groups[1].Value == "#" ? matchType.Groups[2].Value : "account";
|
||||||
|
return matchAdvanced.Success && !matchAdvanced.Value.Contains("false") ? "advanced/"+accType : accType;
|
||||||
|
}catch{
|
||||||
|
return "(unknown)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ExternalInfo{
|
||||||
|
public static ExternalInfo From(Form form){
|
||||||
|
if (form == null){
|
||||||
|
return new ExternalInfo();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
Screen screen = Screen.FromControl(form); // works on multi-monitor setups even in tray
|
||||||
|
int dpi;
|
||||||
|
|
||||||
|
using(Graphics graphics = form.CreateGraphics()){
|
||||||
|
dpi = (int)graphics.DpiY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ExternalInfo{
|
||||||
|
Resolution = screen.Bounds.Width+"x"+screen.Bounds.Height,
|
||||||
|
DPI = dpi
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Resolution { get; private set; }
|
||||||
|
public int? DPI { get; private set; }
|
||||||
|
|
||||||
|
private ExternalInfo(){}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
Core/Other/FormAbout.Designer.cs
generated
42
Core/Other/FormAbout.Designer.cs
generated
@@ -1,6 +1,4 @@
|
|||||||
using TweetDck.Core.Controls;
|
namespace TweetDuck.Core.Other {
|
||||||
|
|
||||||
namespace TweetDck.Core.Other {
|
|
||||||
sealed partial class FormAbout {
|
sealed partial class FormAbout {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Required designer variable.
|
/// Required designer variable.
|
||||||
@@ -25,7 +23,6 @@ namespace TweetDck.Core.Other {
|
|||||||
/// the contents of this method with the code editor.
|
/// the contents of this method with the code editor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void InitializeComponent() {
|
private void InitializeComponent() {
|
||||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FormAbout));
|
|
||||||
this.pictureLogo = new System.Windows.Forms.PictureBox();
|
this.pictureLogo = new System.Windows.Forms.PictureBox();
|
||||||
this.labelDescription = new System.Windows.Forms.Label();
|
this.labelDescription = new System.Windows.Forms.Label();
|
||||||
this.labelTips = new System.Windows.Forms.LinkLabel();
|
this.labelTips = new System.Windows.Forms.LinkLabel();
|
||||||
@@ -40,11 +37,11 @@ namespace TweetDck.Core.Other {
|
|||||||
//
|
//
|
||||||
this.pictureLogo.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None;
|
this.pictureLogo.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None;
|
||||||
this.pictureLogo.ErrorImage = null;
|
this.pictureLogo.ErrorImage = null;
|
||||||
this.pictureLogo.Image = ((System.Drawing.Image)(resources.GetObject("pictureLogo.Image")));
|
|
||||||
this.pictureLogo.InitialImage = null;
|
this.pictureLogo.InitialImage = null;
|
||||||
this.pictureLogo.Location = new System.Drawing.Point(12, 12);
|
this.pictureLogo.Location = new System.Drawing.Point(12, 12);
|
||||||
this.pictureLogo.Name = "pictureLogo";
|
this.pictureLogo.Name = "pictureLogo";
|
||||||
this.pictureLogo.Size = new System.Drawing.Size(96, 96);
|
this.pictureLogo.Size = new System.Drawing.Size(96, 96);
|
||||||
|
this.pictureLogo.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
|
||||||
this.pictureLogo.TabIndex = 0;
|
this.pictureLogo.TabIndex = 0;
|
||||||
this.pictureLogo.TabStop = false;
|
this.pictureLogo.TabStop = false;
|
||||||
//
|
//
|
||||||
@@ -53,22 +50,22 @@ namespace TweetDck.Core.Other {
|
|||||||
this.labelDescription.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
this.labelDescription.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||||
| System.Windows.Forms.AnchorStyles.Left)
|
| System.Windows.Forms.AnchorStyles.Left)
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.labelDescription.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
this.labelDescription.Font = new System.Drawing.Font("Segoe UI", 9.75F);
|
||||||
this.labelDescription.Location = new System.Drawing.Point(114, 12);
|
this.labelDescription.Location = new System.Drawing.Point(114, 12);
|
||||||
this.labelDescription.Name = "labelDescription";
|
this.labelDescription.Name = "labelDescription";
|
||||||
this.labelDescription.Size = new System.Drawing.Size(232, 109);
|
this.labelDescription.Size = new System.Drawing.Size(232, 113);
|
||||||
this.labelDescription.TabIndex = 1;
|
this.labelDescription.TabIndex = 0;
|
||||||
//
|
//
|
||||||
// labelTips
|
// labelTips
|
||||||
//
|
//
|
||||||
this.labelTips.Dock = System.Windows.Forms.DockStyle.Fill;
|
this.labelTips.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||||
this.labelTips.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
this.labelTips.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular);
|
||||||
this.labelTips.LinkArea = new System.Windows.Forms.LinkArea(0, 0);
|
this.labelTips.LinkArea = new System.Windows.Forms.LinkArea(0, 0);
|
||||||
this.labelTips.Location = new System.Drawing.Point(117, 0);
|
this.labelTips.Location = new System.Drawing.Point(117, 0);
|
||||||
this.labelTips.Margin = new System.Windows.Forms.Padding(0);
|
this.labelTips.Margin = new System.Windows.Forms.Padding(0);
|
||||||
this.labelTips.Name = "labelTips";
|
this.labelTips.Name = "labelTips";
|
||||||
this.labelTips.Size = new System.Drawing.Size(99, 16);
|
this.labelTips.Size = new System.Drawing.Size(99, 18);
|
||||||
this.labelTips.TabIndex = 3;
|
this.labelTips.TabIndex = 1;
|
||||||
this.labelTips.Text = "Tips && Tricks";
|
this.labelTips.Text = "Tips && Tricks";
|
||||||
this.labelTips.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
this.labelTips.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||||
this.labelTips.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.OnLinkClicked);
|
this.labelTips.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.OnLinkClicked);
|
||||||
@@ -77,13 +74,13 @@ namespace TweetDck.Core.Other {
|
|||||||
//
|
//
|
||||||
this.labelWebsite.AutoSize = true;
|
this.labelWebsite.AutoSize = true;
|
||||||
this.labelWebsite.Dock = System.Windows.Forms.DockStyle.Fill;
|
this.labelWebsite.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||||
this.labelWebsite.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
this.labelWebsite.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular);
|
||||||
this.labelWebsite.LinkArea = new System.Windows.Forms.LinkArea(0, 0);
|
this.labelWebsite.LinkArea = new System.Windows.Forms.LinkArea(0, 0);
|
||||||
this.labelWebsite.Location = new System.Drawing.Point(0, 0);
|
this.labelWebsite.Location = new System.Drawing.Point(0, 0);
|
||||||
this.labelWebsite.Margin = new System.Windows.Forms.Padding(0);
|
this.labelWebsite.Margin = new System.Windows.Forms.Padding(0);
|
||||||
this.labelWebsite.Name = "labelWebsite";
|
this.labelWebsite.Name = "labelWebsite";
|
||||||
this.labelWebsite.Size = new System.Drawing.Size(117, 16);
|
this.labelWebsite.Size = new System.Drawing.Size(117, 18);
|
||||||
this.labelWebsite.TabIndex = 2;
|
this.labelWebsite.TabIndex = 0;
|
||||||
this.labelWebsite.Text = "Official Website";
|
this.labelWebsite.Text = "Official Website";
|
||||||
this.labelWebsite.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
|
this.labelWebsite.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
|
||||||
this.labelWebsite.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.OnLinkClicked);
|
this.labelWebsite.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.OnLinkClicked);
|
||||||
@@ -99,23 +96,23 @@ namespace TweetDck.Core.Other {
|
|||||||
this.tablePanelLinks.Controls.Add(this.labelIssues, 2, 0);
|
this.tablePanelLinks.Controls.Add(this.labelIssues, 2, 0);
|
||||||
this.tablePanelLinks.Controls.Add(this.labelWebsite, 0, 0);
|
this.tablePanelLinks.Controls.Add(this.labelWebsite, 0, 0);
|
||||||
this.tablePanelLinks.Controls.Add(this.labelTips, 1, 0);
|
this.tablePanelLinks.Controls.Add(this.labelTips, 1, 0);
|
||||||
this.tablePanelLinks.Location = new System.Drawing.Point(12, 124);
|
this.tablePanelLinks.Location = new System.Drawing.Point(12, 128);
|
||||||
this.tablePanelLinks.Name = "tablePanelLinks";
|
this.tablePanelLinks.Name = "tablePanelLinks";
|
||||||
this.tablePanelLinks.RowCount = 1;
|
this.tablePanelLinks.RowCount = 1;
|
||||||
this.tablePanelLinks.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
this.tablePanelLinks.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
||||||
this.tablePanelLinks.Size = new System.Drawing.Size(334, 16);
|
this.tablePanelLinks.Size = new System.Drawing.Size(334, 18);
|
||||||
this.tablePanelLinks.TabIndex = 4;
|
this.tablePanelLinks.TabIndex = 1;
|
||||||
//
|
//
|
||||||
// labelIssues
|
// labelIssues
|
||||||
//
|
//
|
||||||
this.labelIssues.Dock = System.Windows.Forms.DockStyle.Fill;
|
this.labelIssues.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||||
this.labelIssues.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
this.labelIssues.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular);
|
||||||
this.labelIssues.LinkArea = new System.Windows.Forms.LinkArea(0, 0);
|
this.labelIssues.LinkArea = new System.Windows.Forms.LinkArea(0, 0);
|
||||||
this.labelIssues.Location = new System.Drawing.Point(216, 0);
|
this.labelIssues.Location = new System.Drawing.Point(216, 0);
|
||||||
this.labelIssues.Margin = new System.Windows.Forms.Padding(0);
|
this.labelIssues.Margin = new System.Windows.Forms.Padding(0);
|
||||||
this.labelIssues.Name = "labelIssues";
|
this.labelIssues.Name = "labelIssues";
|
||||||
this.labelIssues.Size = new System.Drawing.Size(118, 16);
|
this.labelIssues.Size = new System.Drawing.Size(118, 18);
|
||||||
this.labelIssues.TabIndex = 4;
|
this.labelIssues.TabIndex = 2;
|
||||||
this.labelIssues.Text = "Report an Issue";
|
this.labelIssues.Text = "Report an Issue";
|
||||||
this.labelIssues.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
|
this.labelIssues.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
|
||||||
this.labelIssues.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.OnLinkClicked);
|
this.labelIssues.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.OnLinkClicked);
|
||||||
@@ -125,16 +122,19 @@ namespace TweetDck.Core.Other {
|
|||||||
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 = System.Drawing.Color.White;
|
this.BackColor = System.Drawing.Color.White;
|
||||||
this.ClientSize = new System.Drawing.Size(358, 152);
|
this.ClientSize = new System.Drawing.Size(358, 156);
|
||||||
this.Controls.Add(this.tablePanelLinks);
|
this.Controls.Add(this.tablePanelLinks);
|
||||||
this.Controls.Add(this.labelDescription);
|
this.Controls.Add(this.labelDescription);
|
||||||
this.Controls.Add(this.pictureLogo);
|
this.Controls.Add(this.pictureLogo);
|
||||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||||
|
this.HelpButton = true;
|
||||||
this.MaximizeBox = false;
|
this.MaximizeBox = false;
|
||||||
this.MinimizeBox = false;
|
this.MinimizeBox = false;
|
||||||
this.Name = "FormAbout";
|
this.Name = "FormAbout";
|
||||||
this.ShowIcon = false;
|
this.ShowIcon = false;
|
||||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||||
|
this.HelpButtonClicked += new System.ComponentModel.CancelEventHandler(this.FormAbout_HelpButtonClicked);
|
||||||
|
this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.FormAbout_HelpRequested);
|
||||||
((System.ComponentModel.ISupportInitialize)(this.pictureLogo)).EndInit();
|
((System.ComponentModel.ISupportInitialize)(this.pictureLogo)).EndInit();
|
||||||
this.tablePanelLinks.ResumeLayout(false);
|
this.tablePanelLinks.ResumeLayout(false);
|
||||||
this.tablePanelLinks.PerformLayout();
|
this.tablePanelLinks.PerformLayout();
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
using System.Windows.Forms;
|
using System.ComponentModel;
|
||||||
using TweetDck.Core.Utils;
|
using System.Drawing;
|
||||||
|
using System.IO;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDck.Core.Other{
|
namespace TweetDuck.Core.Other{
|
||||||
sealed partial class FormAbout : Form{
|
sealed partial class FormAbout : Form, FormManager.IAppDialog{
|
||||||
private const string TipsLink = "https://github.com/chylex/TweetDuck/wiki";
|
private const string TipsLink = "https://github.com/chylex/TweetDuck/wiki";
|
||||||
private const string IssuesLink = "https://github.com/chylex/TweetDuck/issues";
|
private const string IssuesLink = "https://github.com/chylex/TweetDuck/issues";
|
||||||
|
|
||||||
@@ -11,15 +14,33 @@ namespace TweetDck.Core.Other{
|
|||||||
|
|
||||||
Text = "About "+Program.BrandName+" "+Program.VersionTag;
|
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.";
|
labelDescription.Text = "TweetDuck 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));
|
labelWebsite.Links.Add(new LinkLabel.Link(0, labelWebsite.Text.Length, Program.Website));
|
||||||
labelTips.Links.Add(new LinkLabel.Link(0, labelTips.Text.Length, TipsLink));
|
labelTips.Links.Add(new LinkLabel.Link(0, labelTips.Text.Length, TipsLink));
|
||||||
labelIssues.Links.Add(new LinkLabel.Link(0, labelIssues.Text.Length, IssuesLink));
|
labelIssues.Links.Add(new LinkLabel.Link(0, labelIssues.Text.Length, IssuesLink));
|
||||||
|
|
||||||
|
MemoryStream logoStream = new MemoryStream(Properties.Resources.avatar);
|
||||||
|
pictureLogo.Image = Image.FromStream(logoStream);
|
||||||
|
Disposed += (sender, args) => logoStream.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e){
|
private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e){
|
||||||
BrowserUtils.OpenExternalBrowser(e.Link.LinkData as string);
|
BrowserUtils.OpenExternalBrowser(e.Link.LinkData as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void FormAbout_HelpRequested(object sender, HelpEventArgs hlpevent){
|
||||||
|
ShowGuide();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FormAbout_HelpButtonClicked(object sender, CancelEventArgs e){
|
||||||
|
e.Cancel = true;
|
||||||
|
ShowGuide();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowGuide(){
|
||||||
|
FormGuide.Show();
|
||||||
|
Close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
|
||||||
44
Core/Other/FormGuide.Designer.cs
generated
Normal file
44
Core/Other/FormGuide.Designer.cs
generated
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
namespace TweetDuck.Core.Other {
|
||||||
|
partial class FormGuide {
|
||||||
|
/// <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.SuspendLayout();
|
||||||
|
//
|
||||||
|
// FormGuide
|
||||||
|
//
|
||||||
|
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||||
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
|
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(34)))), ((int)(((byte)(34)))), ((int)(((byte)(34)))));
|
||||||
|
this.ClientSize = new System.Drawing.Size(424, 282);
|
||||||
|
this.Icon = global::TweetDuck.Properties.Resources.icon;
|
||||||
|
this.MinimumSize = new System.Drawing.Size(440, 320);
|
||||||
|
this.Name = "FormGuide";
|
||||||
|
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||||
|
this.ResumeLayout(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
122
Core/Other/FormGuide.cs
Normal file
122
Core/Other/FormGuide.cs
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
|
using CefSharp.WinForms;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Handling;
|
||||||
|
using TweetDuck.Core.Handling.General;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using TweetDuck.Data;
|
||||||
|
using TweetDuck.Resources;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Other{
|
||||||
|
sealed partial class FormGuide : Form, FormManager.IAppDialog{
|
||||||
|
private const string GuideUrl = "https://tweetduck.chylex.com/guide/v2/";
|
||||||
|
private const string GuidePathRegex = @"^guide(?:/v\d+)?(?:/(#.*))?";
|
||||||
|
|
||||||
|
private static readonly ResourceLink DummyPage = new ResourceLink("http://td/dummy", ResourceHandler.FromString(""));
|
||||||
|
|
||||||
|
public static bool CheckGuideUrl(string url, out string hash){
|
||||||
|
if (!url.Contains("//tweetduck.chylex.com/guide")){
|
||||||
|
hash = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string path = url.Substring(url.IndexOf("/guide")+1);
|
||||||
|
Match match = Regex.Match(path, GuidePathRegex);
|
||||||
|
|
||||||
|
if (match.Success){
|
||||||
|
hash = match.Groups[1].Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
hash = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Show(string hash = null){
|
||||||
|
string url = GuideUrl+(hash ?? string.Empty);
|
||||||
|
FormGuide guide = FormManager.TryFind<FormGuide>();
|
||||||
|
|
||||||
|
if (guide == null){
|
||||||
|
FormBrowser owner = FormManager.TryFind<FormBrowser>();
|
||||||
|
|
||||||
|
if (owner != null){
|
||||||
|
owner.AnalyticsFile.OpenGuide.Trigger();
|
||||||
|
new FormGuide(url, owner).Show(owner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
guide.Reload(url);
|
||||||
|
guide.Activate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly ChromiumWebBrowser browser;
|
||||||
|
private string nextUrl;
|
||||||
|
|
||||||
|
private FormGuide(string url, FormBrowser owner){
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
Text = Program.BrandName+" Guide";
|
||||||
|
Size = new Size(owner.Size.Width*3/4, owner.Size.Height*3/4);
|
||||||
|
VisibleChanged += (sender, args) => this.MoveToCenter(owner);
|
||||||
|
|
||||||
|
ResourceHandlerFactory resourceHandlerFactory = new ResourceHandlerFactory();
|
||||||
|
resourceHandlerFactory.RegisterHandler(DummyPage);
|
||||||
|
|
||||||
|
this.browser = new ChromiumWebBrowser(url){
|
||||||
|
MenuHandler = new ContextMenuGuide(owner),
|
||||||
|
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||||
|
KeyboardHandler = new KeyboardHandlerBase(),
|
||||||
|
LifeSpanHandler = new LifeSpanHandler(),
|
||||||
|
RequestHandler = new RequestHandlerBase(true),
|
||||||
|
ResourceHandlerFactory = resourceHandlerFactory
|
||||||
|
};
|
||||||
|
|
||||||
|
browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||||
|
browser.FrameLoadEnd += browser_FrameLoadEnd;
|
||||||
|
|
||||||
|
browser.BrowserSettings.BackgroundColor = (uint)BackColor.ToArgb();
|
||||||
|
browser.Dock = DockStyle.None;
|
||||||
|
browser.Location = ControlExtensions.InvisibleLocation;
|
||||||
|
browser.SetupZoomEvents();
|
||||||
|
|
||||||
|
Controls.Add(browser);
|
||||||
|
|
||||||
|
Disposed += (sender, args) => {
|
||||||
|
browser.Dispose();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Reload(string url){
|
||||||
|
nextUrl = url;
|
||||||
|
browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||||
|
browser.Dock = DockStyle.None;
|
||||||
|
browser.Location = ControlExtensions.InvisibleLocation;
|
||||||
|
browser.Load(DummyPage.Url);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
|
||||||
|
if (!e.IsLoading){
|
||||||
|
if (browser.Address == DummyPage.Url){
|
||||||
|
browser.Load(nextUrl);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
this.InvokeAsyncSafe(() => {
|
||||||
|
browser.Location = Point.Empty;
|
||||||
|
browser.Dock = DockStyle.Fill;
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.LoadingStateChanged -= browser_LoadingStateChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||||
|
ScriptLoader.ExecuteScript(e.Frame, "Array.prototype.forEach.call(document.getElementsByTagName('A'), ele => ele.addEventListener('click', e => { e.preventDefault(); window.open(ele.getAttribute('href')); }))", "gen:links");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
Core/Other/FormMessage.Designer.cs
generated
9
Core/Other/FormMessage.Designer.cs
generated
@@ -1,4 +1,4 @@
|
|||||||
namespace TweetDck.Core.Other {
|
namespace TweetDuck.Core.Other {
|
||||||
partial class FormMessage {
|
partial class FormMessage {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Required designer variable.
|
/// Required designer variable.
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
this.panelActions.Dock = System.Windows.Forms.DockStyle.Bottom;
|
this.panelActions.Dock = System.Windows.Forms.DockStyle.Bottom;
|
||||||
this.panelActions.Location = new System.Drawing.Point(0, 84);
|
this.panelActions.Location = new System.Drawing.Point(0, 84);
|
||||||
this.panelActions.Name = "panelActions";
|
this.panelActions.Name = "panelActions";
|
||||||
this.panelActions.Size = new System.Drawing.Size(233, 49);
|
this.panelActions.Size = new System.Drawing.Size(98, 49);
|
||||||
this.panelActions.TabIndex = 0;
|
this.panelActions.TabIndex = 0;
|
||||||
//
|
//
|
||||||
// labelMessage
|
// labelMessage
|
||||||
@@ -42,8 +42,8 @@
|
|||||||
| System.Windows.Forms.AnchorStyles.Left)
|
| System.Windows.Forms.AnchorStyles.Left)
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.labelMessage.AutoSize = true;
|
this.labelMessage.AutoSize = true;
|
||||||
this.labelMessage.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
this.labelMessage.Font = System.Drawing.SystemFonts.MessageBoxFont;
|
||||||
this.labelMessage.Location = new System.Drawing.Point(62, 33);
|
this.labelMessage.Location = new System.Drawing.Point(62, 34);
|
||||||
this.labelMessage.Margin = new System.Windows.Forms.Padding(53, 24, 27, 24);
|
this.labelMessage.Margin = new System.Windows.Forms.Padding(53, 24, 27, 24);
|
||||||
this.labelMessage.MaximumSize = new System.Drawing.Size(600, 0);
|
this.labelMessage.MaximumSize = new System.Drawing.Size(600, 0);
|
||||||
this.labelMessage.MinimumSize = new System.Drawing.Size(0, 24);
|
this.labelMessage.MinimumSize = new System.Drawing.Size(0, 24);
|
||||||
@@ -66,6 +66,7 @@
|
|||||||
this.Name = "FormMessage";
|
this.Name = "FormMessage";
|
||||||
this.ShowIcon = false;
|
this.ShowIcon = false;
|
||||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||||
|
this.SizeChanged += new System.EventHandler(this.FormMessage_SizeChanged);
|
||||||
this.ResumeLayout(false);
|
this.ResumeLayout(false);
|
||||||
this.PerformLayout();
|
this.PerformLayout();
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,80 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Other{
|
||||||
|
[Flags]
|
||||||
|
enum ControlType{
|
||||||
|
None = 0,
|
||||||
|
Accept = 1, // triggered by pressing enter when a non-button is focused
|
||||||
|
Cancel = 2, // triggered by closing the dialog without pressing a button
|
||||||
|
Focused = 4 // active control after the dialog is showed
|
||||||
|
}
|
||||||
|
|
||||||
namespace TweetDck.Core.Other{
|
|
||||||
sealed partial class FormMessage : Form{
|
sealed partial class FormMessage : Form{
|
||||||
|
public const string OK = "OK";
|
||||||
|
public const string Yes = "Yes";
|
||||||
|
public const string No = "No";
|
||||||
|
public const string Cancel = "Cancel";
|
||||||
|
public const string Retry = "Retry";
|
||||||
|
public const string Ignore = "Ignore";
|
||||||
|
public const string Exit = "Exit";
|
||||||
|
|
||||||
|
public static bool Information(string caption, string text, string buttonAccept, string buttonCancel = null){
|
||||||
|
return Show(caption, text, MessageBoxIcon.Information, buttonAccept, buttonCancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool Warning(string caption, string text, string buttonAccept, string buttonCancel = null){
|
||||||
|
return Show(caption, text, MessageBoxIcon.Warning, buttonAccept, buttonCancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool Error(string caption, string text, string buttonAccept, string buttonCancel = null){
|
||||||
|
return Show(caption, text, MessageBoxIcon.Error, buttonAccept, buttonCancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool Question(string caption, string text, string buttonAccept, string buttonCancel = null){
|
||||||
|
return Show(caption, text, MessageBoxIcon.Question, buttonAccept, buttonCancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool Show(string caption, string text, MessageBoxIcon icon, string button){
|
||||||
|
return Show(caption, text, icon, button, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool Show(string caption, string text, MessageBoxIcon icon, string buttonAccept, string buttonCancel){
|
||||||
|
using(FormMessage message = new FormMessage(caption, text, icon)){
|
||||||
|
if (buttonCancel == null){
|
||||||
|
message.AddButton(buttonAccept, DialogResult.OK, ControlType.Cancel | ControlType.Focused);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
message.AddButton(buttonCancel, DialogResult.Cancel, ControlType.Cancel);
|
||||||
|
message.AddButton(buttonAccept, DialogResult.OK, ControlType.Accept | ControlType.Focused);
|
||||||
|
}
|
||||||
|
|
||||||
|
return message.ShowDialog() == DialogResult.OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instance
|
||||||
|
|
||||||
public Button ClickedButton { get; private set; }
|
public Button ClickedButton { get; private set; }
|
||||||
|
|
||||||
|
public bool HasIcon => icon != null;
|
||||||
|
public int ActionPanelY => panelActions.Location.Y;
|
||||||
|
|
||||||
|
private int ClientWidth{
|
||||||
|
get => ClientSize.Width;
|
||||||
|
set => ClientSize = new Size(value, ClientSize.Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ButtonDistance{
|
||||||
|
get => BrowserUtils.Scale(96, dpiScale);
|
||||||
|
}
|
||||||
|
|
||||||
private readonly Icon icon;
|
private readonly Icon icon;
|
||||||
private readonly bool isReady;
|
private readonly bool isReady;
|
||||||
|
private readonly float dpiScale;
|
||||||
|
|
||||||
private int realFormWidth, minFormWidth;
|
private int realFormWidth, minFormWidth;
|
||||||
private int buttonCount;
|
private int buttonCount;
|
||||||
@@ -17,9 +84,11 @@ namespace TweetDck.Core.Other{
|
|||||||
public FormMessage(string caption, string text, MessageBoxIcon messageIcon){
|
public FormMessage(string caption, string text, MessageBoxIcon messageIcon){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
this.dpiScale = this.GetDPIScale();
|
||||||
|
|
||||||
this.prevLabelWidth = labelMessage.Width;
|
this.prevLabelWidth = labelMessage.Width;
|
||||||
this.prevLabelHeight = labelMessage.Height;
|
this.prevLabelHeight = labelMessage.Height;
|
||||||
this.minFormWidth = 18;
|
this.minFormWidth = BrowserUtils.Scale(42, dpiScale);
|
||||||
|
|
||||||
switch(messageIcon){
|
switch(messageIcon){
|
||||||
case MessageBoxIcon.Information:
|
case MessageBoxIcon.Information:
|
||||||
@@ -40,46 +109,80 @@ namespace TweetDck.Core.Other{
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
icon = null;
|
icon = null;
|
||||||
labelMessage.Location = new Point(labelMessage.Location.X-32, labelMessage.Location.Y);
|
labelMessage.Location = new Point(BrowserUtils.Scale(19, dpiScale), labelMessage.Location.Y); // 19 instead of 9 due to larger height
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isReady = true;
|
this.isReady = true;
|
||||||
|
|
||||||
this.Text = caption;
|
this.Text = caption;
|
||||||
this.labelMessage.Text = text;
|
this.labelMessage.Text = text.Replace("\r", "").Replace("\n", Environment.NewLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Button AddButton(string title){
|
private void FormMessage_SizeChanged(object sender, EventArgs e){
|
||||||
|
RecalculateButtonLocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Button AddButton(string title, ControlType type){
|
||||||
|
return AddButton(title, DialogResult.OK, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Button AddButton(string title, DialogResult result = DialogResult.OK, ControlType type = ControlType.None){
|
||||||
Button button = new Button{
|
Button button = new Button{
|
||||||
Anchor = AnchorStyles.Bottom | AnchorStyles.Right,
|
Anchor = AnchorStyles.Bottom,
|
||||||
Location = new Point(Width-112-buttonCount*96, 12),
|
Font = SystemFonts.MessageBoxFont,
|
||||||
Size = new Size(88, 26),
|
Location = new Point(0, 12),
|
||||||
TabIndex = buttonCount,
|
Size = new Size(BrowserUtils.Scale(88, dpiScale), BrowserUtils.Scale(26, dpiScale)),
|
||||||
|
TabIndex = 256-buttonCount,
|
||||||
Text = title,
|
Text = title,
|
||||||
UseVisualStyleBackColor = true
|
UseVisualStyleBackColor = true
|
||||||
};
|
};
|
||||||
|
|
||||||
button.Click += (sender, args) => {
|
button.Click += (sender, args) => {
|
||||||
ClickedButton = (Button)sender;
|
ClickedButton = (Button)sender;
|
||||||
DialogResult = DialogResult.OK;
|
DialogResult = result;
|
||||||
Close();
|
Close();
|
||||||
};
|
};
|
||||||
|
|
||||||
panelActions.Controls.Add(button);
|
panelActions.Controls.Add(button);
|
||||||
|
|
||||||
minFormWidth += 96;
|
|
||||||
Width = Math.Max(realFormWidth, minFormWidth);
|
|
||||||
|
|
||||||
++buttonCount;
|
++buttonCount;
|
||||||
|
|
||||||
|
minFormWidth += ButtonDistance;
|
||||||
|
ClientWidth = Math.Max(realFormWidth, minFormWidth);
|
||||||
|
RecalculateButtonLocation();
|
||||||
|
|
||||||
|
if (type.HasFlag(ControlType.Accept)){
|
||||||
|
AcceptButton = button;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.HasFlag(ControlType.Cancel)){
|
||||||
|
CancelButton = button;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.HasFlag(ControlType.Focused)){
|
||||||
|
ActiveControl = button;
|
||||||
|
}
|
||||||
|
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddActionControl(Control control){
|
public void AddActionControl(Control control){
|
||||||
panelActions.Controls.Add(control);
|
panelActions.Controls.Add(control);
|
||||||
|
|
||||||
|
control.Size = new Size(BrowserUtils.Scale(control.Width, dpiScale), BrowserUtils.Scale(control.Height, dpiScale));
|
||||||
|
|
||||||
minFormWidth += control.Width+control.Margin.Horizontal;
|
minFormWidth += control.Width+control.Margin.Horizontal;
|
||||||
Width = Math.Max(realFormWidth, minFormWidth);
|
ClientWidth = Math.Max(realFormWidth, minFormWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RecalculateButtonLocation(){
|
||||||
|
int dist = ButtonDistance;
|
||||||
|
int start = ClientWidth-dist;
|
||||||
|
|
||||||
|
for(int index = 0; index < buttonCount; index++){
|
||||||
|
Control control = panelActions.Controls[index];
|
||||||
|
control.Location = new Point(start-index*dist, control.Location.Y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void labelMessage_SizeChanged(object sender, EventArgs e){
|
private void labelMessage_SizeChanged(object sender, EventArgs e){
|
||||||
@@ -88,18 +191,19 @@ namespace TweetDck.Core.Other{
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool isMultiline = labelMessage.Height > labelMessage.MinimumSize.Height;
|
bool isMultiline = labelMessage.Height > labelMessage.MinimumSize.Height;
|
||||||
|
int labelOffset = BrowserUtils.Scale(8, dpiScale);
|
||||||
|
|
||||||
if (isMultiline && !wasLabelMultiline){
|
if (isMultiline && !wasLabelMultiline){
|
||||||
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y-8);
|
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y-labelOffset);
|
||||||
prevLabelHeight += 8;
|
prevLabelHeight += labelOffset;
|
||||||
}
|
}
|
||||||
else if (!isMultiline && wasLabelMultiline){
|
else if (!isMultiline && wasLabelMultiline){
|
||||||
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y+8);
|
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y+labelOffset);
|
||||||
prevLabelHeight -= 8;
|
prevLabelHeight -= labelOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
realFormWidth = Width-(icon == null ? 32+35+(labelMessage.Margin.Left-labelMessage.Margin.Right) : 0)+labelMessage.Margin.Right+labelMessage.Width-prevLabelWidth;
|
realFormWidth = ClientWidth-(icon == null ? BrowserUtils.Scale(50, dpiScale) : 0)+labelMessage.Width-prevLabelWidth;
|
||||||
Width = Math.Max(realFormWidth, minFormWidth);
|
ClientWidth = Math.Max(realFormWidth, minFormWidth);
|
||||||
Height += labelMessage.Height-prevLabelHeight;
|
Height += labelMessage.Height-prevLabelHeight;
|
||||||
|
|
||||||
prevLabelWidth = labelMessage.Width;
|
prevLabelWidth = labelMessage.Width;
|
||||||
@@ -109,7 +213,7 @@ namespace TweetDck.Core.Other{
|
|||||||
|
|
||||||
protected override void OnPaint(PaintEventArgs e){
|
protected override void OnPaint(PaintEventArgs e){
|
||||||
if (icon != null){
|
if (icon != null){
|
||||||
e.Graphics.DrawIcon(icon, 25, 26);
|
e.Graphics.DrawIcon(icon, BrowserUtils.Scale(25, dpiScale), 1+BrowserUtils.Scale(25, dpiScale));
|
||||||
}
|
}
|
||||||
|
|
||||||
base.OnPaint(e);
|
base.OnPaint(e);
|
||||||
|
|||||||
63
Core/Other/FormPlugins.Designer.cs
generated
63
Core/Other/FormPlugins.Designer.cs
generated
@@ -1,4 +1,6 @@
|
|||||||
namespace TweetDck.Core.Other {
|
using TweetDuck.Plugins;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Other {
|
||||||
partial class FormPlugins {
|
partial class FormPlugins {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Required designer variable.
|
/// Required designer variable.
|
||||||
@@ -23,20 +25,23 @@
|
|||||||
/// the contents of this method with the code editor.
|
/// the contents of this method with the code editor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void InitializeComponent() {
|
private void InitializeComponent() {
|
||||||
|
this.components = new System.ComponentModel.Container();
|
||||||
this.btnClose = new System.Windows.Forms.Button();
|
this.btnClose = new System.Windows.Forms.Button();
|
||||||
this.btnReload = new System.Windows.Forms.Button();
|
this.btnReload = new System.Windows.Forms.Button();
|
||||||
this.btnOpenFolder = new System.Windows.Forms.Button();
|
this.btnOpenFolder = new System.Windows.Forms.Button();
|
||||||
this.tabPanelPlugins = new TweetDck.Core.Controls.TabPanel();
|
this.flowLayoutPlugins = new PluginListFlowLayout();
|
||||||
|
this.timerLayout = new System.Windows.Forms.Timer(this.components);
|
||||||
this.SuspendLayout();
|
this.SuspendLayout();
|
||||||
//
|
//
|
||||||
// btnClose
|
// btnClose
|
||||||
//
|
//
|
||||||
this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.btnClose.AutoSize = true;
|
this.btnClose.AutoSize = true;
|
||||||
this.btnClose.Location = new System.Drawing.Point(643, 439);
|
this.btnClose.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.btnClose.Location = new System.Drawing.Point(642, 433);
|
||||||
this.btnClose.Name = "btnClose";
|
this.btnClose.Name = "btnClose";
|
||||||
this.btnClose.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
this.btnClose.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||||
this.btnClose.Size = new System.Drawing.Size(49, 23);
|
this.btnClose.Size = new System.Drawing.Size(50, 25);
|
||||||
this.btnClose.TabIndex = 1;
|
this.btnClose.TabIndex = 1;
|
||||||
this.btnClose.Text = "Close";
|
this.btnClose.Text = "Close";
|
||||||
this.btnClose.UseVisualStyleBackColor = true;
|
this.btnClose.UseVisualStyleBackColor = true;
|
||||||
@@ -46,10 +51,11 @@
|
|||||||
//
|
//
|
||||||
this.btnReload.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
this.btnReload.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||||
this.btnReload.AutoSize = true;
|
this.btnReload.AutoSize = true;
|
||||||
this.btnReload.Location = new System.Drawing.Point(131, 439);
|
this.btnReload.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.btnReload.Location = new System.Drawing.Point(141, 433);
|
||||||
this.btnReload.Name = "btnReload";
|
this.btnReload.Name = "btnReload";
|
||||||
this.btnReload.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
this.btnReload.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||||
this.btnReload.Size = new System.Drawing.Size(71, 23);
|
this.btnReload.Size = new System.Drawing.Size(74, 25);
|
||||||
this.btnReload.TabIndex = 2;
|
this.btnReload.TabIndex = 2;
|
||||||
this.btnReload.Text = "Reload All";
|
this.btnReload.Text = "Reload All";
|
||||||
this.btnReload.UseVisualStyleBackColor = true;
|
this.btnReload.UseVisualStyleBackColor = true;
|
||||||
@@ -59,36 +65,48 @@
|
|||||||
//
|
//
|
||||||
this.btnOpenFolder.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
this.btnOpenFolder.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||||
this.btnOpenFolder.AutoSize = true;
|
this.btnOpenFolder.AutoSize = true;
|
||||||
this.btnOpenFolder.Location = new System.Drawing.Point(12, 439);
|
this.btnOpenFolder.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.btnOpenFolder.Location = new System.Drawing.Point(12, 433);
|
||||||
this.btnOpenFolder.Name = "btnOpenFolder";
|
this.btnOpenFolder.Name = "btnOpenFolder";
|
||||||
this.btnOpenFolder.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
this.btnOpenFolder.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||||
this.btnOpenFolder.Size = new System.Drawing.Size(113, 23);
|
this.btnOpenFolder.Size = new System.Drawing.Size(123, 25);
|
||||||
this.btnOpenFolder.TabIndex = 3;
|
this.btnOpenFolder.TabIndex = 3;
|
||||||
this.btnOpenFolder.Text = "Open Plugin Folder";
|
this.btnOpenFolder.Text = "Open Plugin Folder";
|
||||||
this.btnOpenFolder.UseVisualStyleBackColor = true;
|
this.btnOpenFolder.UseVisualStyleBackColor = true;
|
||||||
this.btnOpenFolder.Click += new System.EventHandler(this.btnOpenFolder_Click);
|
this.btnOpenFolder.Click += new System.EventHandler(this.btnOpenFolder_Click);
|
||||||
//
|
//
|
||||||
// tabPanelPlugins
|
// flowLayoutPlugins
|
||||||
//
|
//
|
||||||
this.tabPanelPlugins.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
this.flowLayoutPlugins.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||||
| System.Windows.Forms.AnchorStyles.Left)
|
| System.Windows.Forms.AnchorStyles.Left)
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.tabPanelPlugins.Location = new System.Drawing.Point(12, 12);
|
this.flowLayoutPlugins.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
||||||
this.tabPanelPlugins.Name = "tabPanelPlugins";
|
this.flowLayoutPlugins.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
|
||||||
this.tabPanelPlugins.Size = new System.Drawing.Size(680, 421);
|
this.flowLayoutPlugins.Location = new System.Drawing.Point(12, 12);
|
||||||
this.tabPanelPlugins.TabIndex = 4;
|
this.flowLayoutPlugins.Name = "flowLayoutPlugins";
|
||||||
|
this.flowLayoutPlugins.Size = new System.Drawing.Size(680, 415);
|
||||||
|
this.flowLayoutPlugins.TabIndex = 0;
|
||||||
|
this.flowLayoutPlugins.WrapContents = false;
|
||||||
|
this.flowLayoutPlugins.Resize += new System.EventHandler(this.flowLayoutPlugins_Resize);
|
||||||
|
//
|
||||||
|
// timerLayout
|
||||||
|
//
|
||||||
|
this.timerLayout.Interval = 99;
|
||||||
|
this.timerLayout.Tick += new System.EventHandler(this.timerLayout_Tick);
|
||||||
//
|
//
|
||||||
// FormPlugins
|
// FormPlugins
|
||||||
//
|
//
|
||||||
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.ClientSize = new System.Drawing.Size(704, 474);
|
this.ClientSize = new System.Drawing.Size(704, 470);
|
||||||
this.Controls.Add(this.tabPanelPlugins);
|
this.Controls.Add(this.flowLayoutPlugins);
|
||||||
this.Controls.Add(this.btnOpenFolder);
|
this.Controls.Add(this.btnOpenFolder);
|
||||||
this.Controls.Add(this.btnReload);
|
this.Controls.Add(this.btnReload);
|
||||||
this.Controls.Add(this.btnClose);
|
this.Controls.Add(this.btnClose);
|
||||||
this.Icon = global::TweetDck.Properties.Resources.icon;
|
this.Icon = global::TweetDuck.Properties.Resources.icon;
|
||||||
this.MinimumSize = new System.Drawing.Size(480, 320);
|
this.MaximizeBox = false;
|
||||||
|
this.MinimizeBox = false;
|
||||||
|
this.MinimumSize = new System.Drawing.Size(640, 360);
|
||||||
this.Name = "FormPlugins";
|
this.Name = "FormPlugins";
|
||||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||||
this.ResumeLayout(false);
|
this.ResumeLayout(false);
|
||||||
@@ -101,6 +119,7 @@
|
|||||||
private System.Windows.Forms.Button btnClose;
|
private System.Windows.Forms.Button btnClose;
|
||||||
private System.Windows.Forms.Button btnReload;
|
private System.Windows.Forms.Button btnReload;
|
||||||
private System.Windows.Forms.Button btnOpenFolder;
|
private System.Windows.Forms.Button btnOpenFolder;
|
||||||
private TweetDck.Core.Controls.TabPanel tabPanelPlugins;
|
private PluginListFlowLayout flowLayoutPlugins;
|
||||||
|
private System.Windows.Forms.Timer timerLayout;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,19 +3,16 @@ using System.Diagnostics;
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDck.Core.Controls;
|
using TweetDuck.Configuration;
|
||||||
using TweetDck.Plugins;
|
using TweetDuck.Plugins;
|
||||||
using TweetDck.Plugins.Controls;
|
using TweetLib.Core.Features.Plugins;
|
||||||
using TweetDck.Plugins.Events;
|
|
||||||
|
namespace TweetDuck.Core.Other{
|
||||||
|
sealed partial class FormPlugins : Form, FormManager.IAppDialog{
|
||||||
|
private static UserConfig Config => Program.Config.User;
|
||||||
|
|
||||||
namespace TweetDck.Core.Other{
|
|
||||||
sealed partial class FormPlugins : Form{
|
|
||||||
private readonly PluginManager pluginManager;
|
private readonly PluginManager pluginManager;
|
||||||
private readonly TabButton tabBtnOfficial, tabBtnCustom;
|
|
||||||
private readonly PluginListFlowLayout flowLayoutPlugins;
|
|
||||||
|
|
||||||
private PluginGroup? selectedGroup;
|
|
||||||
|
|
||||||
public FormPlugins(){
|
public FormPlugins(){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
@@ -24,73 +21,66 @@ namespace TweetDck.Core.Other{
|
|||||||
|
|
||||||
public FormPlugins(PluginManager pluginManager) : this(){
|
public FormPlugins(PluginManager pluginManager) : this(){
|
||||||
this.pluginManager = pluginManager;
|
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.tabPanelPlugins.SelectTab(tabBtnOfficial);
|
|
||||||
this.pluginManager_Reloaded(pluginManager, null);
|
|
||||||
|
|
||||||
|
if (!Config.PluginsWindowSize.IsEmpty){
|
||||||
|
Size targetSize = Config.PluginsWindowSize;
|
||||||
|
Size = new Size(Math.Max(MinimumSize.Width, targetSize.Width), Math.Max(MinimumSize.Height, targetSize.Height));
|
||||||
|
}
|
||||||
|
|
||||||
Shown += (sender, args) => {
|
Shown += (sender, args) => {
|
||||||
Program.UserConfig.PluginsWindow.Restore(this, false);
|
ReloadPluginList();
|
||||||
};
|
};
|
||||||
|
|
||||||
FormClosed += (sender, args) => {
|
FormClosed += (sender, args) => {
|
||||||
Program.UserConfig.PluginsWindow.Save(this);
|
Config.PluginsWindowSize = Size;
|
||||||
Program.UserConfig.Save();
|
Config.Save();
|
||||||
|
};
|
||||||
|
|
||||||
|
ResizeEnd += (sender, args) => {
|
||||||
|
timerLayout.Start();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SelectGroup(PluginGroup group){
|
private int GetPluginOrderIndex(Plugin plugin){
|
||||||
if (selectedGroup.HasValue && selectedGroup == group)return;
|
return !plugin.CanRun ? 0 : pluginManager.Config.IsEnabled(plugin) ? 1 : 2;
|
||||||
|
|
||||||
selectedGroup = group;
|
|
||||||
|
|
||||||
ReloadPluginTab();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReloadPluginTab(){
|
private void ReloadPluginList(){
|
||||||
if (!selectedGroup.HasValue)return;
|
|
||||||
|
|
||||||
flowLayoutPlugins.SuspendLayout();
|
|
||||||
flowLayoutPlugins.Controls.Clear();
|
flowLayoutPlugins.Controls.Clear();
|
||||||
|
flowLayoutPlugins.SuspendLayout();
|
||||||
|
|
||||||
Plugin[] plugins = pluginManager.GetPluginsByGroup(selectedGroup.Value).OrderBy(plugin => !plugin.CanRun ? 0 : pluginManager.Config.IsEnabled(plugin) ? 1 : 2).ThenBy(plugin => plugin.Name).ToArray();
|
foreach(Plugin plugin in pluginManager.Plugins.OrderBy(GetPluginOrderIndex).ThenBy(plugin => plugin.Name)){
|
||||||
|
flowLayoutPlugins.Controls.Add(new PluginControl(pluginManager, plugin));
|
||||||
|
|
||||||
for(int index = 0; index < plugins.Length; index++){
|
flowLayoutPlugins.Controls.Add(new Panel{
|
||||||
flowLayoutPlugins.Controls.Add(new PluginControl(pluginManager, plugins[index]));
|
BackColor = Color.DimGray,
|
||||||
|
Margin = new Padding(0),
|
||||||
if (index < plugins.Length-1){
|
Size = new Size(1, 1)
|
||||||
flowLayoutPlugins.Controls.Add(new Panel{
|
});
|
||||||
BackColor = Color.DimGray,
|
|
||||||
Size = new Size(1, 1)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
flowLayoutPlugins_Resize(flowLayoutPlugins, new EventArgs());
|
|
||||||
flowLayoutPlugins.ResumeLayout(true);
|
flowLayoutPlugins.ResumeLayout(true);
|
||||||
|
|
||||||
|
timerLayout_Tick(null, EventArgs.Empty);
|
||||||
|
timerLayout.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pluginManager_Reloaded(object sender, PluginLoadEventArgs e){
|
private void timerLayout_Tick(object sender, EventArgs e){
|
||||||
tabBtnOfficial.Text = "Official: "+pluginManager.CountPluginByGroup(PluginGroup.Official);
|
timerLayout.Stop();
|
||||||
tabBtnCustom.Text = "Custom: "+pluginManager.CountPluginByGroup(PluginGroup.Custom);
|
|
||||||
|
// stupid WinForms scrollbars and panels
|
||||||
|
Padding = new Padding(Padding.Left, Padding.Top, Padding.Right+1, Padding.Bottom+1);
|
||||||
|
Padding = new Padding(Padding.Left, Padding.Top, Padding.Right-1, Padding.Bottom-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void flowLayoutPlugins_Resize(object sender, EventArgs e){
|
public void flowLayoutPlugins_Resize(object sender, EventArgs e){
|
||||||
if (flowLayoutPlugins.Controls.Count == 0){
|
Control lastPlugin = flowLayoutPlugins.Controls.OfType<PluginControl>().LastOrDefault();
|
||||||
|
|
||||||
|
if (lastPlugin == null){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Control lastControl = flowLayoutPlugins.Controls[flowLayoutPlugins.Controls.Count-1];
|
bool showScrollBar = lastPlugin.Location.Y+lastPlugin.Height+1 >= flowLayoutPlugins.Height;
|
||||||
bool showScrollBar = lastControl.Location.Y+lastControl.Height >= flowLayoutPlugins.Height;
|
|
||||||
int horizontalOffset = showScrollBar ? SystemInformation.VerticalScrollBarWidth : 0;
|
int horizontalOffset = showScrollBar ? SystemInformation.VerticalScrollBarWidth : 0;
|
||||||
|
|
||||||
flowLayoutPlugins.AutoScroll = showScrollBar;
|
flowLayoutPlugins.AutoScroll = showScrollBar;
|
||||||
@@ -99,18 +89,19 @@ namespace TweetDck.Core.Other{
|
|||||||
foreach(Control control in flowLayoutPlugins.Controls){
|
foreach(Control control in flowLayoutPlugins.Controls){
|
||||||
control.Width = flowLayoutPlugins.Width-control.Margin.Horizontal-horizontalOffset;
|
control.Width = flowLayoutPlugins.Width-control.Margin.Horizontal-horizontalOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flowLayoutPlugins.Controls[flowLayoutPlugins.Controls.Count-1].Visible = !showScrollBar;
|
||||||
flowLayoutPlugins.Focus();
|
flowLayoutPlugins.Focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnOpenFolder_Click(object sender, EventArgs e){
|
private void btnOpenFolder_Click(object sender, EventArgs e){
|
||||||
using(Process.Start("explorer.exe", "\""+pluginManager.PathCustomPlugins+"\"")){}
|
using(Process.Start("explorer.exe", '"'+pluginManager.PathCustomPlugins+'"')){}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnReload_Click(object sender, EventArgs e){
|
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){
|
if (FormMessage.Warning("Reloading Plugins", "This will also reload the browser window. Do you want to proceed?", FormMessage.Yes, FormMessage.No)){
|
||||||
pluginManager.Reload();
|
pluginManager.Reload();
|
||||||
ReloadPluginTab();
|
ReloadPluginList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<root>
|
|
||||||
<!--
|
|
||||||
Microsoft ResX Schema
|
|
||||||
|
|
||||||
Version 2.0
|
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
|
||||||
that is mostly human readable. The generation and parsing of the
|
|
||||||
various data types are done through the TypeConverter classes
|
|
||||||
associated with the data types.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
|
||||||
<resheader name="version">2.0</resheader>
|
|
||||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
|
||||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
|
||||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
|
||||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
|
||||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
|
||||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
|
||||||
</data>
|
|
||||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
|
||||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
|
||||||
<comment>This is a comment</comment>
|
|
||||||
</data>
|
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
|
||||||
name/value pairs.
|
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
|
||||||
text/value conversion through the TypeConverter architecture.
|
|
||||||
Classes that don't support this are serialized and stored with the
|
|
||||||
mimetype set.
|
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
|
||||||
read any of the formats listed below.
|
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
|
||||||
value : The object must be serialized with
|
|
||||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
|
||||||
: and then encoded with base64 encoding.
|
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
|
||||||
value : The object must be serialized with
|
|
||||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
|
||||||
: and then encoded with base64 encoding.
|
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
|
||||||
value : The object must be serialized into a byte array
|
|
||||||
: using a System.ComponentModel.TypeConverter
|
|
||||||
: and then encoded with base64 encoding.
|
|
||||||
-->
|
|
||||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
|
||||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
|
||||||
<xsd:element name="root" msdata:IsDataSet="true">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:choice maxOccurs="unbounded">
|
|
||||||
<xsd:element name="metadata">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
|
||||||
<xsd:attribute name="type" type="xsd:string" />
|
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
|
||||||
<xsd:attribute ref="xml:space" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
<xsd:element name="assembly">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:attribute name="alias" type="xsd:string" />
|
|
||||||
<xsd:attribute name="name" type="xsd:string" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
<xsd:element name="data">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
|
||||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
|
||||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
|
||||||
<xsd:attribute ref="xml:space" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
<xsd:element name="resheader">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
</xsd:schema>
|
|
||||||
<resheader name="resmimetype">
|
|
||||||
<value>text/microsoft-resx</value>
|
|
||||||
</resheader>
|
|
||||||
<resheader name="version">
|
|
||||||
<value>2.0</value>
|
|
||||||
</resheader>
|
|
||||||
<resheader name="reader">
|
|
||||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
|
||||||
</resheader>
|
|
||||||
<resheader name="writer">
|
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
|
||||||
</resheader>
|
|
||||||
</root>
|
|
||||||
80
Core/Other/FormSettings.Designer.cs
generated
80
Core/Other/FormSettings.Designer.cs
generated
@@ -1,4 +1,4 @@
|
|||||||
namespace TweetDck.Core.Other {
|
namespace TweetDuck.Core.Other {
|
||||||
sealed partial class FormSettings {
|
sealed partial class FormSettings {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Required designer variable.
|
/// Required designer variable.
|
||||||
@@ -24,52 +24,74 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void InitializeComponent() {
|
private void InitializeComponent() {
|
||||||
this.btnClose = new System.Windows.Forms.Button();
|
this.btnClose = new System.Windows.Forms.Button();
|
||||||
this.labelTip = new System.Windows.Forms.Label();
|
this.panelContents = new System.Windows.Forms.Panel();
|
||||||
this.tabPanel = new TweetDck.Core.Controls.TabPanel();
|
this.panelButtons = new System.Windows.Forms.Panel();
|
||||||
|
this.btnManageOptions = new System.Windows.Forms.Button();
|
||||||
this.SuspendLayout();
|
this.SuspendLayout();
|
||||||
//
|
//
|
||||||
// btnClose
|
// btnClose
|
||||||
//
|
//
|
||||||
this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.btnClose.AutoSize = true;
|
this.btnClose.AutoSize = true;
|
||||||
this.btnClose.Location = new System.Drawing.Point(443, 331);
|
this.btnClose.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||||
|
this.btnClose.Location = new System.Drawing.Point(747, 500);
|
||||||
this.btnClose.Name = "btnClose";
|
this.btnClose.Name = "btnClose";
|
||||||
this.btnClose.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
this.btnClose.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||||
this.btnClose.Size = new System.Drawing.Size(49, 23);
|
this.btnClose.Size = new System.Drawing.Size(50, 25);
|
||||||
this.btnClose.TabIndex = 4;
|
this.btnClose.TabIndex = 3;
|
||||||
this.btnClose.Text = "Close";
|
this.btnClose.Text = "Close";
|
||||||
this.btnClose.UseVisualStyleBackColor = true;
|
this.btnClose.UseVisualStyleBackColor = true;
|
||||||
this.btnClose.Click += new System.EventHandler(this.btnClose_Click);
|
this.btnClose.Click += new System.EventHandler(this.btnClose_Click);
|
||||||
//
|
//
|
||||||
// labelTip
|
// panelContents
|
||||||
//
|
//
|
||||||
this.labelTip.AutoSize = true;
|
this.panelContents.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||||
this.labelTip.Location = new System.Drawing.Point(12, 333);
|
|
||||||
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.Left)
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.tabPanel.Location = new System.Drawing.Point(12, 12);
|
this.panelContents.AutoScroll = true;
|
||||||
this.tabPanel.Name = "tabPanel";
|
this.panelContents.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
||||||
this.tabPanel.Size = new System.Drawing.Size(480, 313);
|
this.panelContents.Location = new System.Drawing.Point(135, 12);
|
||||||
this.tabPanel.TabIndex = 3;
|
this.panelContents.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3);
|
||||||
|
this.panelContents.Name = "panelContents";
|
||||||
|
this.panelContents.Size = new System.Drawing.Size(662, 482);
|
||||||
|
this.panelContents.TabIndex = 1;
|
||||||
|
//
|
||||||
|
// panelButtons
|
||||||
|
//
|
||||||
|
this.panelButtons.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||||
|
| System.Windows.Forms.AnchorStyles.Left)));
|
||||||
|
this.panelButtons.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
||||||
|
this.panelButtons.Location = new System.Drawing.Point(12, 12);
|
||||||
|
this.panelButtons.Margin = new System.Windows.Forms.Padding(3, 3, 0, 3);
|
||||||
|
this.panelButtons.Name = "panelButtons";
|
||||||
|
this.panelButtons.Size = new System.Drawing.Size(124, 482);
|
||||||
|
this.panelButtons.TabIndex = 0;
|
||||||
|
//
|
||||||
|
// btnManageOptions
|
||||||
|
//
|
||||||
|
this.btnManageOptions.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||||
|
this.btnManageOptions.AutoSize = true;
|
||||||
|
this.btnManageOptions.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||||
|
this.btnManageOptions.Location = new System.Drawing.Point(12, 500);
|
||||||
|
this.btnManageOptions.Name = "btnManageOptions";
|
||||||
|
this.btnManageOptions.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||||
|
this.btnManageOptions.Size = new System.Drawing.Size(109, 25);
|
||||||
|
this.btnManageOptions.TabIndex = 4;
|
||||||
|
this.btnManageOptions.Text = "Manage Options";
|
||||||
|
this.btnManageOptions.UseVisualStyleBackColor = true;
|
||||||
|
this.btnManageOptions.Click += new System.EventHandler(this.btnManageOptions_Click);
|
||||||
//
|
//
|
||||||
// FormSettings
|
// FormSettings
|
||||||
//
|
//
|
||||||
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.ClientSize = new System.Drawing.Size(504, 366);
|
this.ClientSize = new System.Drawing.Size(809, 537);
|
||||||
this.Controls.Add(this.labelTip);
|
this.Controls.Add(this.btnManageOptions);
|
||||||
|
this.Controls.Add(this.panelContents);
|
||||||
|
this.Controls.Add(this.panelButtons);
|
||||||
this.Controls.Add(this.btnClose);
|
this.Controls.Add(this.btnClose);
|
||||||
this.Controls.Add(this.tabPanel);
|
|
||||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
|
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
|
||||||
this.Icon = Properties.Resources.icon;
|
this.Icon = global::TweetDuck.Properties.Resources.icon;
|
||||||
this.MaximizeBox = false;
|
this.MaximizeBox = false;
|
||||||
this.MinimizeBox = false;
|
this.MinimizeBox = false;
|
||||||
this.Name = "FormSettings";
|
this.Name = "FormSettings";
|
||||||
@@ -81,9 +103,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private Controls.TabPanel tabPanel;
|
|
||||||
private System.Windows.Forms.Button btnClose;
|
private System.Windows.Forms.Button btnClose;
|
||||||
private System.Windows.Forms.Label labelTip;
|
private System.Windows.Forms.Panel panelContents;
|
||||||
|
private System.Windows.Forms.Panel panelButtons;
|
||||||
|
private System.Windows.Forms.Button btnManageOptions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,50 +1,103 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Drawing;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDck.Core.Other.Settings;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDck.Plugins;
|
using TweetDuck.Core.Handling.General;
|
||||||
using TweetDck.Updates;
|
using TweetDuck.Core.Notification.Example;
|
||||||
|
using TweetDuck.Core.Other.Analytics;
|
||||||
|
using TweetDuck.Core.Other.Settings;
|
||||||
|
using TweetDuck.Core.Other.Settings.Dialogs;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetDuck.Plugins;
|
||||||
|
using TweetLib.Core.Features.Updates;
|
||||||
|
|
||||||
namespace TweetDck.Core.Other{
|
namespace TweetDuck.Core.Other{
|
||||||
sealed partial class FormSettings : Form{
|
sealed partial class FormSettings : Form, FormManager.IAppDialog{
|
||||||
private readonly Dictionary<Type, BaseTabSettings> tabs = new Dictionary<Type, BaseTabSettings>(4);
|
public bool ShouldReloadBrowser { get; private set; }
|
||||||
|
|
||||||
public FormSettings(FormBrowser browserForm, PluginManager plugins, UpdateHandler updates){
|
private readonly FormBrowser browser;
|
||||||
|
private readonly PluginManager plugins;
|
||||||
|
|
||||||
|
private readonly int buttonHeight;
|
||||||
|
|
||||||
|
private readonly Dictionary<Type, SettingsTab> tabs = new Dictionary<Type, SettingsTab>(8);
|
||||||
|
private SettingsTab currentTab;
|
||||||
|
|
||||||
|
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, AnalyticsManager analytics, Type startTab){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName+" Settings";
|
Text = Program.BrandName+" Options";
|
||||||
|
|
||||||
this.tabPanel.SetupTabPanel(100);
|
this.browser = browser;
|
||||||
this.tabPanel.AddButton("General", SelectTab<TabSettingsGeneral>);
|
this.browser.PauseNotification();
|
||||||
this.tabPanel.AddButton("Notifications", () => SelectTab(() => new TabSettingsNotifications(browserForm.CreateNotificationForm(false))));
|
|
||||||
this.tabPanel.AddButton("Updates", () => SelectTab(() => new TabSettingsUpdates(updates)));
|
this.plugins = plugins;
|
||||||
this.tabPanel.AddButton("Advanced", () => SelectTab(() => new TabSettingsAdvanced(browserForm.ReloadBrowser, plugins)));
|
|
||||||
this.tabPanel.SelectTab(tabPanel.Buttons.First());
|
this.buttonHeight = BrowserUtils.Scale(39, this.GetDPIScale()) | 1;
|
||||||
|
|
||||||
|
PrepareLoad();
|
||||||
|
|
||||||
|
AddButton("General", () => new TabSettingsGeneral(this.browser.ReloadColumns, updates));
|
||||||
|
AddButton("Notifications", () => new TabSettingsNotifications(new FormNotificationExample(this.browser, this.plugins)));
|
||||||
|
AddButton("Sounds", () => new TabSettingsSounds(this.browser.PlaySoundNotification));
|
||||||
|
AddButton("Feedback", () => new TabSettingsFeedback(analytics, AnalyticsReportGenerator.ExternalInfo.From(this.browser), this.plugins));
|
||||||
|
AddButton("Advanced", () => new TabSettingsAdvanced(this.browser.ReinjectCustomCSS, this.browser.OpenDevTools));
|
||||||
|
|
||||||
|
SelectTab(tabs[startTab ?? typeof(TabSettingsGeneral)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SelectTab<T>() where T : BaseTabSettings, new(){
|
private void PrepareLoad(){
|
||||||
SelectTab(() => new T());
|
Program.Config.ProgramRestartRequested += Config_ProgramRestartRequested;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SelectTab<T>(Func<T> constructor) where T : BaseTabSettings{
|
private void PrepareUnload(){ // TODO refactor this further later
|
||||||
BaseTabSettings control;
|
currentTab.Control.OnClosing();
|
||||||
|
|
||||||
|
Program.Config.ProgramRestartRequested -= Config_ProgramRestartRequested;
|
||||||
|
Program.Config.SaveAll();
|
||||||
|
}
|
||||||
|
|
||||||
if (tabs.TryGetValue(typeof(T), out control)){
|
private void Config_ProgramRestartRequested(object sender, EventArgs e){
|
||||||
tabPanel.ReplaceContent(control);
|
if (FormMessage.Information("TweetDuck Options", "The application must restart for the option to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)){
|
||||||
}
|
Program.Restart();
|
||||||
else{
|
|
||||||
control = tabs[typeof(T)] = constructor();
|
|
||||||
control.Ready = true;
|
|
||||||
tabPanel.ReplaceContent(control);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FormSettings_FormClosing(object sender, FormClosingEventArgs e){
|
private void FormSettings_FormClosing(object sender, FormClosingEventArgs e){
|
||||||
Program.UserConfig.Save();
|
PrepareUnload();
|
||||||
|
|
||||||
foreach(BaseTabSettings control in tabs.Values){
|
foreach(SettingsTab tab in tabs.Values){
|
||||||
control.Dispose();
|
if (tab.IsInitialized){
|
||||||
|
tab.Control.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
browser.ResumeNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnManageOptions_Click(object sender, EventArgs e){
|
||||||
|
PrepareUnload();
|
||||||
|
|
||||||
|
using(DialogSettingsManage dialog = new DialogSettingsManage(plugins)){
|
||||||
|
FormClosing -= FormSettings_FormClosing;
|
||||||
|
|
||||||
|
if (dialog.ShowDialog() == DialogResult.OK){
|
||||||
|
if (!dialog.IsRestarting){
|
||||||
|
browser.ResumeNotification();
|
||||||
|
|
||||||
|
if (dialog.ShouldReloadBrowser){
|
||||||
|
BrowserProcessHandler.UpdatePrefs();
|
||||||
|
ShouldReloadBrowser = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
FormClosing += FormSettings_FormClosing;
|
||||||
|
PrepareLoad();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,10 +105,106 @@ namespace TweetDck.Core.Other{
|
|||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReloadUI(){
|
private void AddButton<T>(string title, Func<T> constructor) where T : BaseTabSettings{
|
||||||
tabs.Clear();
|
FlatButton btn = new FlatButton{
|
||||||
tabPanel.Content.Controls.Clear();
|
BackColor = SystemColors.Control,
|
||||||
tabPanel.ActiveButton.Callback();
|
FlatStyle = FlatStyle.Flat,
|
||||||
|
Font = SystemFonts.MessageBoxFont,
|
||||||
|
Location = new Point(0, (buttonHeight+1)*(panelButtons.Controls.Count/2)),
|
||||||
|
Margin = new Padding(0),
|
||||||
|
Size = new Size(panelButtons.Width, buttonHeight),
|
||||||
|
Text = title,
|
||||||
|
UseVisualStyleBackColor = true
|
||||||
|
};
|
||||||
|
|
||||||
|
btn.FlatAppearance.BorderSize = 0;
|
||||||
|
btn.FlatAppearance.MouseDownBackColor = Color.FromArgb(179, 213, 232);
|
||||||
|
btn.FlatAppearance.MouseOverBackColor = Color.FromArgb(216, 230, 237);
|
||||||
|
|
||||||
|
panelButtons.Controls.Add(btn);
|
||||||
|
|
||||||
|
panelButtons.Controls.Add(new Panel{
|
||||||
|
BackColor = Color.DimGray,
|
||||||
|
Location = new Point(0, panelButtons.Controls[panelButtons.Controls.Count-1].Location.Y+buttonHeight),
|
||||||
|
Margin = new Padding(0),
|
||||||
|
Size = new Size(panelButtons.Width, 1)
|
||||||
|
});
|
||||||
|
|
||||||
|
tabs.Add(typeof(T), new SettingsTab(btn, constructor));
|
||||||
|
|
||||||
|
btn.Click += (sender, args) => SelectTab<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectTab<T>() where T : BaseTabSettings{
|
||||||
|
SelectTab(tabs[typeof(T)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectTab(SettingsTab tab){
|
||||||
|
if (currentTab != null){
|
||||||
|
currentTab.Button.BackColor = SystemColors.Control;
|
||||||
|
currentTab.Control.OnClosing();
|
||||||
|
}
|
||||||
|
|
||||||
|
tab.Button.BackColor = tab.Button.FlatAppearance.MouseDownBackColor;
|
||||||
|
|
||||||
|
if (!tab.IsInitialized){
|
||||||
|
foreach(Control control in tab.Control.InteractiveControls){
|
||||||
|
if (control is ComboBox){
|
||||||
|
control.MouseLeave += control_MouseLeave;
|
||||||
|
}
|
||||||
|
else if (control is TrackBar){
|
||||||
|
control.MouseWheel += control_MouseWheel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tab.Control.Height < panelContents.Height-2){
|
||||||
|
tab.Control.Height = panelContents.Height-2; // fixes off-by-pixel error on high DPI
|
||||||
|
}
|
||||||
|
|
||||||
|
tab.Control.OnReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
panelContents.VerticalScroll.Enabled = false; // required to stop animation that would otherwise break everything
|
||||||
|
panelContents.PerformLayout();
|
||||||
|
|
||||||
|
panelContents.SuspendLayout();
|
||||||
|
panelContents.VerticalScroll.Value = 0; // https://gfycat.com/GrotesqueTastyAstarte
|
||||||
|
panelContents.Controls.Clear();
|
||||||
|
panelContents.Controls.Add(tab.Control);
|
||||||
|
panelContents.ResumeLayout(true);
|
||||||
|
|
||||||
|
panelContents.VerticalScroll.Enabled = true;
|
||||||
|
panelContents.Focus();
|
||||||
|
|
||||||
|
currentTab = tab;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void control_MouseLeave(object sender, EventArgs e){
|
||||||
|
if (sender is ComboBox cb && cb.DroppedDown){
|
||||||
|
return; // prevents comboboxes from closing when MouseLeave event triggers during opening animation
|
||||||
|
}
|
||||||
|
|
||||||
|
panelContents.Focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void control_MouseWheel(object sender, MouseEventArgs e){
|
||||||
|
((HandledMouseEventArgs)e).Handled = true;
|
||||||
|
panelContents.Focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class SettingsTab{
|
||||||
|
public Button Button { get; }
|
||||||
|
|
||||||
|
public BaseTabSettings Control => control ??= constructor();
|
||||||
|
public bool IsInitialized => control != null;
|
||||||
|
|
||||||
|
private readonly Func<BaseTabSettings> constructor;
|
||||||
|
private BaseTabSettings control;
|
||||||
|
|
||||||
|
public SettingsTab(Button button, Func<BaseTabSettings> constructor){
|
||||||
|
this.Button = button;
|
||||||
|
this.constructor = constructor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
Core/Other/Settings/BaseTabSettings.Designer.cs
generated
3
Core/Other/Settings/BaseTabSettings.Designer.cs
generated
@@ -1,3 +0,0 @@
|
|||||||
namespace TweetDck.Core.Other.Settings{
|
|
||||||
partial class BaseTabSettings{}
|
|
||||||
}
|
|
||||||
@@ -1,24 +1,36 @@
|
|||||||
using System.Windows.Forms;
|
using System.Collections.Generic;
|
||||||
using TweetDck.Configuration;
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Configuration;
|
||||||
|
|
||||||
namespace TweetDck.Core.Other.Settings{
|
namespace TweetDuck.Core.Other.Settings{
|
||||||
partial class BaseTabSettings : UserControl{
|
class BaseTabSettings : UserControl{
|
||||||
protected static UserConfig Config{
|
protected static UserConfig Config => Program.Config.User;
|
||||||
|
protected static SystemConfig SysConfig => Program.Config.System;
|
||||||
|
|
||||||
|
public IEnumerable<Control> InteractiveControls{
|
||||||
get{
|
get{
|
||||||
return Program.UserConfig;
|
IEnumerable<Control> FindInteractiveControls(Control parent){
|
||||||
|
foreach(Control control in parent.Controls){
|
||||||
|
if (control is Panel subPanel){
|
||||||
|
foreach(Control subControl in FindInteractiveControls(subPanel)){
|
||||||
|
yield return subControl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
yield return control;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FindInteractiveControls(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Ready { get; set; }
|
protected BaseTabSettings(){
|
||||||
|
|
||||||
public BaseTabSettings(){
|
|
||||||
Padding = new Padding(6);
|
Padding = new Padding(6);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void PromptRestart(){
|
public virtual void OnReady(){}
|
||||||
if (MessageBox.Show("The application must restart for the setting to take place. Do you want to restart now?", Program.BrandName+" Settings", MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.Yes){
|
public virtual void OnClosing(){}
|
||||||
Program.Restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
94
Core/Other/Settings/Dialogs/DialogSettingsAnalytics.Designer.cs
generated
Normal file
94
Core/Other/Settings/Dialogs/DialogSettingsAnalytics.Designer.cs
generated
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
namespace TweetDuck.Core.Other.Settings.Dialogs {
|
||||||
|
partial class DialogSettingsAnalytics {
|
||||||
|
/// <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.textBoxReport = new System.Windows.Forms.TextBox();
|
||||||
|
this.btnClose = new System.Windows.Forms.Button();
|
||||||
|
this.labelInfo = new System.Windows.Forms.Label();
|
||||||
|
this.SuspendLayout();
|
||||||
|
//
|
||||||
|
// textBoxReport
|
||||||
|
//
|
||||||
|
this.textBoxReport.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.textBoxReport.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.textBoxReport.Location = new System.Drawing.Point(12, 45);
|
||||||
|
this.textBoxReport.Multiline = true;
|
||||||
|
this.textBoxReport.Name = "textBoxReport";
|
||||||
|
this.textBoxReport.ReadOnly = true;
|
||||||
|
this.textBoxReport.ScrollBars = System.Windows.Forms.ScrollBars.Both;
|
||||||
|
this.textBoxReport.Size = new System.Drawing.Size(435, 474);
|
||||||
|
this.textBoxReport.TabIndex = 1;
|
||||||
|
//
|
||||||
|
// btnClose
|
||||||
|
//
|
||||||
|
this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.btnClose.AutoSize = true;
|
||||||
|
this.btnClose.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.btnClose.Location = new System.Drawing.Point(397, 525);
|
||||||
|
this.btnClose.Name = "btnClose";
|
||||||
|
this.btnClose.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||||
|
this.btnClose.Size = new System.Drawing.Size(50, 25);
|
||||||
|
this.btnClose.TabIndex = 2;
|
||||||
|
this.btnClose.Text = "Close";
|
||||||
|
this.btnClose.UseVisualStyleBackColor = true;
|
||||||
|
this.btnClose.Click += new System.EventHandler(this.btnClose_Click);
|
||||||
|
//
|
||||||
|
// labelInfo
|
||||||
|
//
|
||||||
|
this.labelInfo.AutoSize = true;
|
||||||
|
this.labelInfo.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.labelInfo.Location = new System.Drawing.Point(12, 9);
|
||||||
|
this.labelInfo.Margin = new System.Windows.Forms.Padding(3, 0, 3, 3);
|
||||||
|
this.labelInfo.Name = "labelInfo";
|
||||||
|
this.labelInfo.Size = new System.Drawing.Size(434, 30);
|
||||||
|
this.labelInfo.TabIndex = 0;
|
||||||
|
this.labelInfo.Text = "When enabled, this data will be sent over a secure network roughly every 14 days." +
|
||||||
|
"\r\nSome numbers in the report were made imprecise on purpose.";
|
||||||
|
//
|
||||||
|
// DialogSettingsAnalytics
|
||||||
|
//
|
||||||
|
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||||
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
|
this.ClientSize = new System.Drawing.Size(459, 562);
|
||||||
|
this.Controls.Add(this.labelInfo);
|
||||||
|
this.Controls.Add(this.btnClose);
|
||||||
|
this.Controls.Add(this.textBoxReport);
|
||||||
|
this.MinimumSize = new System.Drawing.Size(475, 340);
|
||||||
|
this.Name = "DialogSettingsAnalytics";
|
||||||
|
this.ShowIcon = false;
|
||||||
|
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||||
|
this.ResumeLayout(false);
|
||||||
|
this.PerformLayout();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private System.Windows.Forms.TextBox textBoxReport;
|
||||||
|
private System.Windows.Forms.Button btnClose;
|
||||||
|
private System.Windows.Forms.Label labelInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
22
Core/Other/Settings/Dialogs/DialogSettingsAnalytics.cs
Normal file
22
Core/Other/Settings/Dialogs/DialogSettingsAnalytics.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Other.Analytics;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||||
|
sealed partial class DialogSettingsAnalytics : Form{
|
||||||
|
public DialogSettingsAnalytics(AnalyticsReport report){
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
Text = Program.BrandName+" Options - Analytics Report";
|
||||||
|
|
||||||
|
textBoxReport.EnableMultilineShortcuts();
|
||||||
|
textBoxReport.Text = report.ToString().TrimEnd();
|
||||||
|
textBoxReport.Select(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnClose_Click(object sender, EventArgs e){
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace TweetDck.Core.Other.Settings.Dialogs {
|
namespace TweetDuck.Core.Other.Settings.Dialogs {
|
||||||
partial class DialogSettingsCSS {
|
partial class DialogSettingsCSS {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Required designer variable.
|
/// Required designer variable.
|
||||||
@@ -23,43 +23,46 @@
|
|||||||
/// the contents of this method with the code editor.
|
/// the contents of this method with the code editor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void InitializeComponent() {
|
private void InitializeComponent() {
|
||||||
|
this.components = new System.ComponentModel.Container();
|
||||||
this.textBoxBrowserCSS = new System.Windows.Forms.TextBox();
|
this.textBoxBrowserCSS = new System.Windows.Forms.TextBox();
|
||||||
this.btnCancel = new System.Windows.Forms.Button();
|
this.btnCancel = new System.Windows.Forms.Button();
|
||||||
this.btnApply = new System.Windows.Forms.Button();
|
this.btnApply = new System.Windows.Forms.Button();
|
||||||
this.splitContainer = new System.Windows.Forms.SplitContainer();
|
|
||||||
this.labelBrowser = new System.Windows.Forms.Label();
|
|
||||||
this.labelNotification = new System.Windows.Forms.Label();
|
|
||||||
this.textBoxNotificationCSS = new System.Windows.Forms.TextBox();
|
this.textBoxNotificationCSS = new System.Windows.Forms.TextBox();
|
||||||
this.labelWarning = new System.Windows.Forms.Label();
|
this.btnOpenDevTools = new System.Windows.Forms.Button();
|
||||||
((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit();
|
this.timerTestBrowser = new System.Windows.Forms.Timer(this.components);
|
||||||
this.splitContainer.Panel1.SuspendLayout();
|
this.tabPanel = new System.Windows.Forms.TabControl();
|
||||||
this.splitContainer.Panel2.SuspendLayout();
|
this.tabPageBrowser = new System.Windows.Forms.TabPage();
|
||||||
this.splitContainer.SuspendLayout();
|
this.tabPageNotification = new System.Windows.Forms.TabPage();
|
||||||
|
this.tabPanel.SuspendLayout();
|
||||||
|
this.tabPageBrowser.SuspendLayout();
|
||||||
|
this.tabPageNotification.SuspendLayout();
|
||||||
this.SuspendLayout();
|
this.SuspendLayout();
|
||||||
//
|
//
|
||||||
// textBoxBrowserCSS
|
// textBoxBrowserCSS
|
||||||
//
|
//
|
||||||
this.textBoxBrowserCSS.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
this.textBoxBrowserCSS.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||||
| System.Windows.Forms.AnchorStyles.Left)
|
this.textBoxBrowserCSS.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular);
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
this.textBoxBrowserCSS.Location = new System.Drawing.Point(3, 3);
|
||||||
this.textBoxBrowserCSS.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
|
||||||
this.textBoxBrowserCSS.Location = new System.Drawing.Point(0, 16);
|
|
||||||
this.textBoxBrowserCSS.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0);
|
this.textBoxBrowserCSS.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0);
|
||||||
this.textBoxBrowserCSS.Multiline = true;
|
this.textBoxBrowserCSS.Multiline = true;
|
||||||
this.textBoxBrowserCSS.Name = "textBoxBrowserCSS";
|
this.textBoxBrowserCSS.Name = "textBoxBrowserCSS";
|
||||||
this.textBoxBrowserCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both;
|
this.textBoxBrowserCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both;
|
||||||
this.textBoxBrowserCSS.Size = new System.Drawing.Size(373, 253);
|
this.textBoxBrowserCSS.Size = new System.Drawing.Size(426, 332);
|
||||||
this.textBoxBrowserCSS.TabIndex = 0;
|
this.textBoxBrowserCSS.TabIndex = 0;
|
||||||
this.textBoxBrowserCSS.WordWrap = false;
|
this.textBoxBrowserCSS.WordWrap = false;
|
||||||
|
this.textBoxBrowserCSS.KeyDown += new System.Windows.Forms.KeyEventHandler(this.textBoxCSS_KeyDown);
|
||||||
|
this.textBoxBrowserCSS.KeyUp += new System.Windows.Forms.KeyEventHandler(this.textBoxBrowserCSS_KeyUp);
|
||||||
//
|
//
|
||||||
// btnCancel
|
// btnCancel
|
||||||
//
|
//
|
||||||
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.btnCancel.Location = new System.Drawing.Point(654, 287);
|
this.btnCancel.AutoSize = true;
|
||||||
|
this.btnCancel.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||||
|
this.btnCancel.Location = new System.Drawing.Point(337, 384);
|
||||||
this.btnCancel.Name = "btnCancel";
|
this.btnCancel.Name = "btnCancel";
|
||||||
this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
this.btnCancel.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||||
this.btnCancel.Size = new System.Drawing.Size(56, 23);
|
this.btnCancel.Size = new System.Drawing.Size(57, 25);
|
||||||
this.btnCancel.TabIndex = 1;
|
this.btnCancel.TabIndex = 2;
|
||||||
this.btnCancel.Text = "Cancel";
|
this.btnCancel.Text = "Cancel";
|
||||||
this.btnCancel.UseVisualStyleBackColor = true;
|
this.btnCancel.UseVisualStyleBackColor = true;
|
||||||
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
|
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
|
||||||
@@ -67,103 +70,105 @@
|
|||||||
// btnApply
|
// btnApply
|
||||||
//
|
//
|
||||||
this.btnApply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
this.btnApply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.btnApply.Location = new System.Drawing.Point(716, 287);
|
this.btnApply.AutoSize = true;
|
||||||
|
this.btnApply.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||||
|
this.btnApply.Location = new System.Drawing.Point(400, 384);
|
||||||
this.btnApply.Name = "btnApply";
|
this.btnApply.Name = "btnApply";
|
||||||
this.btnApply.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
this.btnApply.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||||
this.btnApply.Size = new System.Drawing.Size(56, 23);
|
this.btnApply.Size = new System.Drawing.Size(52, 25);
|
||||||
this.btnApply.TabIndex = 2;
|
this.btnApply.TabIndex = 1;
|
||||||
this.btnApply.Text = "Apply";
|
this.btnApply.Text = "Apply";
|
||||||
this.btnApply.UseVisualStyleBackColor = true;
|
this.btnApply.UseVisualStyleBackColor = true;
|
||||||
this.btnApply.Click += new System.EventHandler(this.btnApply_Click);
|
this.btnApply.Click += new System.EventHandler(this.btnApply_Click);
|
||||||
//
|
//
|
||||||
// splitContainer
|
|
||||||
//
|
|
||||||
this.splitContainer.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.splitContainer.Location = new System.Drawing.Point(12, 12);
|
|
||||||
this.splitContainer.Name = "splitContainer";
|
|
||||||
//
|
|
||||||
// splitContainer.Panel1
|
|
||||||
//
|
|
||||||
this.splitContainer.Panel1.Controls.Add(this.labelBrowser);
|
|
||||||
this.splitContainer.Panel1.Controls.Add(this.textBoxBrowserCSS);
|
|
||||||
this.splitContainer.Panel1MinSize = 64;
|
|
||||||
//
|
|
||||||
// splitContainer.Panel2
|
|
||||||
//
|
|
||||||
this.splitContainer.Panel2.Controls.Add(this.labelNotification);
|
|
||||||
this.splitContainer.Panel2.Controls.Add(this.textBoxNotificationCSS);
|
|
||||||
this.splitContainer.Panel2MinSize = 64;
|
|
||||||
this.splitContainer.Size = new System.Drawing.Size(760, 269);
|
|
||||||
this.splitContainer.SplitterDistance = 373;
|
|
||||||
this.splitContainer.SplitterWidth = 5;
|
|
||||||
this.splitContainer.TabIndex = 5;
|
|
||||||
//
|
|
||||||
// labelBrowser
|
|
||||||
//
|
|
||||||
this.labelBrowser.AutoSize = true;
|
|
||||||
this.labelBrowser.Location = new System.Drawing.Point(-3, 0);
|
|
||||||
this.labelBrowser.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
|
|
||||||
this.labelBrowser.Name = "labelBrowser";
|
|
||||||
this.labelBrowser.Size = new System.Drawing.Size(45, 13);
|
|
||||||
this.labelBrowser.TabIndex = 1;
|
|
||||||
this.labelBrowser.Text = "Browser";
|
|
||||||
//
|
|
||||||
// labelNotification
|
|
||||||
//
|
|
||||||
this.labelNotification.AutoSize = true;
|
|
||||||
this.labelNotification.Location = new System.Drawing.Point(-3, 0);
|
|
||||||
this.labelNotification.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
|
|
||||||
this.labelNotification.Name = "labelNotification";
|
|
||||||
this.labelNotification.Size = new System.Drawing.Size(60, 13);
|
|
||||||
this.labelNotification.TabIndex = 2;
|
|
||||||
this.labelNotification.Text = "Notification";
|
|
||||||
//
|
|
||||||
// textBoxNotificationCSS
|
// textBoxNotificationCSS
|
||||||
//
|
//
|
||||||
this.textBoxNotificationCSS.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
this.textBoxNotificationCSS.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||||
| System.Windows.Forms.AnchorStyles.Left)
|
this.textBoxNotificationCSS.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular);
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
this.textBoxNotificationCSS.Location = new System.Drawing.Point(3, 3);
|
||||||
this.textBoxNotificationCSS.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
|
||||||
this.textBoxNotificationCSS.Location = new System.Drawing.Point(0, 16);
|
|
||||||
this.textBoxNotificationCSS.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0);
|
this.textBoxNotificationCSS.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0);
|
||||||
this.textBoxNotificationCSS.Multiline = true;
|
this.textBoxNotificationCSS.Multiline = true;
|
||||||
this.textBoxNotificationCSS.Name = "textBoxNotificationCSS";
|
this.textBoxNotificationCSS.Name = "textBoxNotificationCSS";
|
||||||
this.textBoxNotificationCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both;
|
this.textBoxNotificationCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both;
|
||||||
this.textBoxNotificationCSS.Size = new System.Drawing.Size(378, 253);
|
this.textBoxNotificationCSS.Size = new System.Drawing.Size(426, 332);
|
||||||
this.textBoxNotificationCSS.TabIndex = 1;
|
this.textBoxNotificationCSS.TabIndex = 0;
|
||||||
this.textBoxNotificationCSS.WordWrap = false;
|
this.textBoxNotificationCSS.WordWrap = false;
|
||||||
|
this.textBoxNotificationCSS.KeyDown += new System.Windows.Forms.KeyEventHandler(this.textBoxCSS_KeyDown);
|
||||||
//
|
//
|
||||||
// labelWarning
|
// btnOpenDevTools
|
||||||
//
|
//
|
||||||
this.labelWarning.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
this.btnOpenDevTools.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||||
this.labelWarning.AutoSize = true;
|
this.btnOpenDevTools.AutoSize = true;
|
||||||
this.labelWarning.Location = new System.Drawing.Point(9, 292);
|
this.btnOpenDevTools.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||||
this.labelWarning.Name = "labelWarning";
|
this.btnOpenDevTools.Location = new System.Drawing.Point(12, 384);
|
||||||
this.labelWarning.Size = new System.Drawing.Size(341, 13);
|
this.btnOpenDevTools.Name = "btnOpenDevTools";
|
||||||
this.labelWarning.TabIndex = 6;
|
this.btnOpenDevTools.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||||
this.labelWarning.Text = "The code is not validated, please make sure there are no syntax errors.";
|
this.btnOpenDevTools.Size = new System.Drawing.Size(104, 25);
|
||||||
|
this.btnOpenDevTools.TabIndex = 3;
|
||||||
|
this.btnOpenDevTools.Text = "Open Dev Tools";
|
||||||
|
this.btnOpenDevTools.UseVisualStyleBackColor = true;
|
||||||
|
this.btnOpenDevTools.Click += new System.EventHandler(this.btnOpenDevTools_Click);
|
||||||
|
//
|
||||||
|
// timerTestBrowser
|
||||||
|
//
|
||||||
|
this.timerTestBrowser.Interval = 400;
|
||||||
|
this.timerTestBrowser.Tick += new System.EventHandler(this.timerTestBrowser_Tick);
|
||||||
|
//
|
||||||
|
// 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.Controls.Add(this.tabPageBrowser);
|
||||||
|
this.tabPanel.Controls.Add(this.tabPageNotification);
|
||||||
|
this.tabPanel.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.tabPanel.Location = new System.Drawing.Point(12, 12);
|
||||||
|
this.tabPanel.Name = "tabPanel";
|
||||||
|
this.tabPanel.SelectedIndex = 0;
|
||||||
|
this.tabPanel.Size = new System.Drawing.Size(440, 366);
|
||||||
|
this.tabPanel.TabIndex = 0;
|
||||||
|
this.tabPanel.SelectedIndexChanged += new System.EventHandler(this.tabPanel_SelectedIndexChanged);
|
||||||
|
//
|
||||||
|
// tabPageBrowser
|
||||||
|
//
|
||||||
|
this.tabPageBrowser.Controls.Add(this.textBoxBrowserCSS);
|
||||||
|
this.tabPageBrowser.Location = new System.Drawing.Point(4, 24);
|
||||||
|
this.tabPageBrowser.Name = "tabPageBrowser";
|
||||||
|
this.tabPageBrowser.Padding = new System.Windows.Forms.Padding(3);
|
||||||
|
this.tabPageBrowser.Size = new System.Drawing.Size(432, 338);
|
||||||
|
this.tabPageBrowser.TabIndex = 0;
|
||||||
|
this.tabPageBrowser.Text = "Browser";
|
||||||
|
this.tabPageBrowser.UseVisualStyleBackColor = true;
|
||||||
|
//
|
||||||
|
// tabPageNotification
|
||||||
|
//
|
||||||
|
this.tabPageNotification.Controls.Add(this.textBoxNotificationCSS);
|
||||||
|
this.tabPageNotification.Location = new System.Drawing.Point(4, 24);
|
||||||
|
this.tabPageNotification.Name = "tabPageNotification";
|
||||||
|
this.tabPageNotification.Padding = new System.Windows.Forms.Padding(3);
|
||||||
|
this.tabPageNotification.Size = new System.Drawing.Size(432, 338);
|
||||||
|
this.tabPageNotification.TabIndex = 1;
|
||||||
|
this.tabPageNotification.Text = "Notification";
|
||||||
|
this.tabPageNotification.UseVisualStyleBackColor = true;
|
||||||
//
|
//
|
||||||
// DialogSettingsCSS
|
// DialogSettingsCSS
|
||||||
//
|
//
|
||||||
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.ClientSize = new System.Drawing.Size(784, 322);
|
this.ClientSize = new System.Drawing.Size(464, 421);
|
||||||
this.Controls.Add(this.labelWarning);
|
this.Controls.Add(this.tabPanel);
|
||||||
this.Controls.Add(this.splitContainer);
|
this.Controls.Add(this.btnOpenDevTools);
|
||||||
this.Controls.Add(this.btnApply);
|
this.Controls.Add(this.btnApply);
|
||||||
this.Controls.Add(this.btnCancel);
|
this.Controls.Add(this.btnCancel);
|
||||||
this.MinimumSize = new System.Drawing.Size(500, 160);
|
this.MinimumSize = new System.Drawing.Size(320, 240);
|
||||||
this.Name = "DialogSettingsCSS";
|
this.Name = "DialogSettingsCSS";
|
||||||
this.ShowIcon = false;
|
this.ShowIcon = false;
|
||||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||||
this.splitContainer.Panel1.ResumeLayout(false);
|
this.tabPanel.ResumeLayout(false);
|
||||||
this.splitContainer.Panel1.PerformLayout();
|
this.tabPageBrowser.ResumeLayout(false);
|
||||||
this.splitContainer.Panel2.ResumeLayout(false);
|
this.tabPageBrowser.PerformLayout();
|
||||||
this.splitContainer.Panel2.PerformLayout();
|
this.tabPageNotification.ResumeLayout(false);
|
||||||
((System.ComponentModel.ISupportInitialize)(this.splitContainer)).EndInit();
|
this.tabPageNotification.PerformLayout();
|
||||||
this.splitContainer.ResumeLayout(false);
|
|
||||||
this.ResumeLayout(false);
|
this.ResumeLayout(false);
|
||||||
this.PerformLayout();
|
this.PerformLayout();
|
||||||
|
|
||||||
@@ -174,10 +179,11 @@
|
|||||||
private System.Windows.Forms.TextBox textBoxBrowserCSS;
|
private System.Windows.Forms.TextBox textBoxBrowserCSS;
|
||||||
private System.Windows.Forms.Button btnCancel;
|
private System.Windows.Forms.Button btnCancel;
|
||||||
private System.Windows.Forms.Button btnApply;
|
private System.Windows.Forms.Button btnApply;
|
||||||
private System.Windows.Forms.SplitContainer splitContainer;
|
|
||||||
private System.Windows.Forms.TextBox textBoxNotificationCSS;
|
private System.Windows.Forms.TextBox textBoxNotificationCSS;
|
||||||
private System.Windows.Forms.Label labelBrowser;
|
private System.Windows.Forms.Button btnOpenDevTools;
|
||||||
private System.Windows.Forms.Label labelNotification;
|
private System.Windows.Forms.Timer timerTestBrowser;
|
||||||
private System.Windows.Forms.Label labelWarning;
|
private System.Windows.Forms.TabControl tabPanel;
|
||||||
|
private System.Windows.Forms.TabPage tabPageBrowser;
|
||||||
|
private System.Windows.Forms.TabPage tabPageNotification;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,31 +1,132 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDck.Core.Other.Settings.Dialogs{
|
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||||
sealed partial class DialogSettingsCSS : Form{
|
sealed partial class DialogSettingsCSS : Form{
|
||||||
public string BrowserCSS{
|
public string BrowserCSS => textBoxBrowserCSS.Text;
|
||||||
get{
|
public string NotificationCSS => textBoxNotificationCSS.Text;
|
||||||
return textBoxBrowserCSS.Text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string NotificationCSS{
|
|
||||||
get{
|
|
||||||
return textBoxNotificationCSS.Text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public DialogSettingsCSS(){
|
private readonly Action<string> reinjectBrowserCSS;
|
||||||
|
private readonly Action openDevTools;
|
||||||
|
|
||||||
|
public DialogSettingsCSS(string browserCSS, string notificationCSS, Action<string> reinjectBrowserCSS, Action openDevTools){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName+" Settings - CSS";
|
Text = Program.BrandName+" Options - CSS";
|
||||||
|
|
||||||
|
this.reinjectBrowserCSS = reinjectBrowserCSS;
|
||||||
|
this.openDevTools = openDevTools;
|
||||||
|
|
||||||
textBoxBrowserCSS.EnableMultilineShortcuts();
|
textBoxBrowserCSS.EnableMultilineShortcuts();
|
||||||
textBoxBrowserCSS.Text = Program.UserConfig.CustomBrowserCSS ?? "";
|
textBoxBrowserCSS.Text = browserCSS ?? "";
|
||||||
|
|
||||||
textBoxNotificationCSS.EnableMultilineShortcuts();
|
textBoxNotificationCSS.EnableMultilineShortcuts();
|
||||||
textBoxNotificationCSS.Text = Program.UserConfig.CustomNotificationCSS ?? "";
|
textBoxNotificationCSS.Text = notificationCSS ?? "";
|
||||||
|
|
||||||
|
if (!BrowserUtils.HasDevTools){
|
||||||
|
btnOpenDevTools.Enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActiveControl = textBoxBrowserCSS;
|
||||||
|
textBoxBrowserCSS.Select(textBoxBrowserCSS.TextLength, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tabPanel_SelectedIndexChanged(object sender, EventArgs e){
|
||||||
|
TextBox tb = tabPanel.SelectedTab.Controls.OfType<TextBox>().FirstOrDefault();
|
||||||
|
|
||||||
|
if (tb != null){
|
||||||
|
tb.Focus();
|
||||||
|
tb.Select(tb.TextLength, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void textBoxCSS_KeyDown(object sender, KeyEventArgs e){
|
||||||
|
TextBox tb = (TextBox)sender;
|
||||||
|
string text = tb.Text;
|
||||||
|
|
||||||
|
if (e.KeyCode == Keys.Back && e.Modifiers == Keys.Control){
|
||||||
|
e.SuppressKeyPress = true;
|
||||||
|
|
||||||
|
int deleteTo = tb.SelectionStart;
|
||||||
|
|
||||||
|
if (deleteTo > 0){
|
||||||
|
char initialChar = text[--deleteTo];
|
||||||
|
bool shouldDeleteAlphanumeric = char.IsLetterOrDigit(initialChar);
|
||||||
|
|
||||||
|
while(--deleteTo >= 0){
|
||||||
|
if ((shouldDeleteAlphanumeric && !char.IsLetterOrDigit(text[deleteTo])) ||
|
||||||
|
(!shouldDeleteAlphanumeric && text[deleteTo] != initialChar)){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(deleteTo < text.Length-1 && text[deleteTo] == '\r' && text[deleteTo+1] == '\n')){
|
||||||
|
++deleteTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
tb.Select(deleteTo, tb.SelectionLength+tb.SelectionStart-deleteTo);
|
||||||
|
tb.SelectedText = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (e.KeyCode == Keys.Back && e.Modifiers == Keys.None){
|
||||||
|
int deleteTo = tb.SelectionStart;
|
||||||
|
|
||||||
|
if (deleteTo > 1 && text[deleteTo-1] == ' ' && text[deleteTo-2] == ' '){
|
||||||
|
e.SuppressKeyPress = true;
|
||||||
|
|
||||||
|
tb.Select(deleteTo-2, 2);
|
||||||
|
tb.SelectedText = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (e.KeyCode == Keys.Enter && e.Modifiers == Keys.None && tb.SelectionLength == 0){
|
||||||
|
int insertAt = tb.SelectionStart, cursorOffset = 0;
|
||||||
|
string insertText;
|
||||||
|
|
||||||
|
if (insertAt == 0){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (text[insertAt-1] == '{'){
|
||||||
|
insertText = Environment.NewLine+" ";
|
||||||
|
|
||||||
|
int nextBracket = insertAt < text.Length ? text.IndexOfAny(new char[]{ '{', '}' }, insertAt+1) : -1;
|
||||||
|
|
||||||
|
if (nextBracket == -1 || text[nextBracket] == '{'){
|
||||||
|
string insertExtra = Environment.NewLine+"}";
|
||||||
|
insertText += insertExtra;
|
||||||
|
cursorOffset -= insertExtra.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
int lineStart = text.LastIndexOf('\n', tb.SelectionStart-1);
|
||||||
|
|
||||||
|
Match match = Regex.Match(text.Substring(lineStart == -1 ? 0 : lineStart+1), "^([ \t]+)");
|
||||||
|
insertText = match.Success ? Environment.NewLine+match.Groups[1].Value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(insertText)){
|
||||||
|
e.SuppressKeyPress = true;
|
||||||
|
tb.Text = text.Insert(insertAt, insertText);
|
||||||
|
tb.SelectionStart = insertAt+cursorOffset+insertText.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void textBoxBrowserCSS_KeyUp(object sender, KeyEventArgs e){
|
||||||
|
timerTestBrowser.Stop();
|
||||||
|
timerTestBrowser.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void timerTestBrowser_Tick(object sender, EventArgs e){
|
||||||
|
reinjectBrowserCSS(textBoxBrowserCSS.Text);
|
||||||
|
timerTestBrowser.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnOpenDevTools_Click(object sender, EventArgs e){
|
||||||
|
openDevTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnApply_Click(object sender, EventArgs e){
|
private void btnApply_Click(object sender, EventArgs e){
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace TweetDck.Core.Other.Settings.Dialogs {
|
namespace TweetDuck.Core.Other.Settings.Dialogs {
|
||||||
partial class DialogSettingsCefArgs {
|
partial class DialogSettingsCefArgs {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Required designer variable.
|
/// Required designer variable.
|
||||||
@@ -35,21 +35,23 @@
|
|||||||
this.textBoxArgs.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
this.textBoxArgs.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||||
| System.Windows.Forms.AnchorStyles.Left)
|
| System.Windows.Forms.AnchorStyles.Left)
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.textBoxArgs.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
this.textBoxArgs.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular);
|
||||||
this.textBoxArgs.Location = new System.Drawing.Point(12, 28);
|
this.textBoxArgs.Location = new System.Drawing.Point(12, 30);
|
||||||
this.textBoxArgs.Multiline = true;
|
this.textBoxArgs.Multiline = true;
|
||||||
this.textBoxArgs.Name = "textBoxArgs";
|
this.textBoxArgs.Name = "textBoxArgs";
|
||||||
this.textBoxArgs.Size = new System.Drawing.Size(460, 193);
|
this.textBoxArgs.Size = new System.Drawing.Size(480, 189);
|
||||||
this.textBoxArgs.TabIndex = 0;
|
this.textBoxArgs.TabIndex = 1;
|
||||||
//
|
//
|
||||||
// btnCancel
|
// btnCancel
|
||||||
//
|
//
|
||||||
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.btnCancel.Location = new System.Drawing.Point(354, 227);
|
this.btnCancel.AutoSize = true;
|
||||||
|
this.btnCancel.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.btnCancel.Location = new System.Drawing.Point(377, 225);
|
||||||
this.btnCancel.Name = "btnCancel";
|
this.btnCancel.Name = "btnCancel";
|
||||||
this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
this.btnCancel.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||||
this.btnCancel.Size = new System.Drawing.Size(56, 23);
|
this.btnCancel.Size = new System.Drawing.Size(57, 25);
|
||||||
this.btnCancel.TabIndex = 1;
|
this.btnCancel.TabIndex = 3;
|
||||||
this.btnCancel.Text = "Cancel";
|
this.btnCancel.Text = "Cancel";
|
||||||
this.btnCancel.UseVisualStyleBackColor = true;
|
this.btnCancel.UseVisualStyleBackColor = true;
|
||||||
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
|
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
|
||||||
@@ -57,10 +59,12 @@
|
|||||||
// btnApply
|
// btnApply
|
||||||
//
|
//
|
||||||
this.btnApply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
this.btnApply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.btnApply.Location = new System.Drawing.Point(416, 227);
|
this.btnApply.AutoSize = true;
|
||||||
|
this.btnApply.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.btnApply.Location = new System.Drawing.Point(440, 225);
|
||||||
this.btnApply.Name = "btnApply";
|
this.btnApply.Name = "btnApply";
|
||||||
this.btnApply.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
this.btnApply.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||||
this.btnApply.Size = new System.Drawing.Size(56, 23);
|
this.btnApply.Size = new System.Drawing.Size(52, 25);
|
||||||
this.btnApply.TabIndex = 2;
|
this.btnApply.TabIndex = 2;
|
||||||
this.btnApply.Text = "Apply";
|
this.btnApply.Text = "Apply";
|
||||||
this.btnApply.UseVisualStyleBackColor = true;
|
this.btnApply.UseVisualStyleBackColor = true;
|
||||||
@@ -70,11 +74,12 @@
|
|||||||
//
|
//
|
||||||
this.btnHelp.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
this.btnHelp.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||||
this.btnHelp.AutoSize = true;
|
this.btnHelp.AutoSize = true;
|
||||||
this.btnHelp.Location = new System.Drawing.Point(12, 227);
|
this.btnHelp.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.btnHelp.Location = new System.Drawing.Point(12, 225);
|
||||||
this.btnHelp.Name = "btnHelp";
|
this.btnHelp.Name = "btnHelp";
|
||||||
this.btnHelp.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
this.btnHelp.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||||
this.btnHelp.Size = new System.Drawing.Size(124, 23);
|
this.btnHelp.Size = new System.Drawing.Size(141, 25);
|
||||||
this.btnHelp.TabIndex = 3;
|
this.btnHelp.TabIndex = 4;
|
||||||
this.btnHelp.Text = "List of Chromium Args";
|
this.btnHelp.Text = "List of Chromium Args";
|
||||||
this.btnHelp.UseVisualStyleBackColor = true;
|
this.btnHelp.UseVisualStyleBackColor = true;
|
||||||
this.btnHelp.Click += new System.EventHandler(this.btnHelp_Click);
|
this.btnHelp.Click += new System.EventHandler(this.btnHelp_Click);
|
||||||
@@ -82,11 +87,12 @@
|
|||||||
// labelWarning
|
// labelWarning
|
||||||
//
|
//
|
||||||
this.labelWarning.AutoSize = true;
|
this.labelWarning.AutoSize = true;
|
||||||
|
this.labelWarning.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
this.labelWarning.Location = new System.Drawing.Point(12, 9);
|
this.labelWarning.Location = new System.Drawing.Point(12, 9);
|
||||||
this.labelWarning.Margin = new System.Windows.Forms.Padding(3, 0, 3, 3);
|
this.labelWarning.Margin = new System.Windows.Forms.Padding(3, 0, 3, 3);
|
||||||
this.labelWarning.Name = "labelWarning";
|
this.labelWarning.Name = "labelWarning";
|
||||||
this.labelWarning.Size = new System.Drawing.Size(423, 13);
|
this.labelWarning.Size = new System.Drawing.Size(478, 15);
|
||||||
this.labelWarning.TabIndex = 4;
|
this.labelWarning.TabIndex = 0;
|
||||||
this.labelWarning.Text = "Warning: Some arguments may cause the program to stop working, edit at your own r" +
|
this.labelWarning.Text = "Warning: Some arguments may cause the program to stop working, edit at your own r" +
|
||||||
"isk.";
|
"isk.";
|
||||||
//
|
//
|
||||||
@@ -94,13 +100,13 @@
|
|||||||
//
|
//
|
||||||
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.ClientSize = new System.Drawing.Size(484, 262);
|
this.ClientSize = new System.Drawing.Size(504, 262);
|
||||||
this.Controls.Add(this.labelWarning);
|
this.Controls.Add(this.labelWarning);
|
||||||
this.Controls.Add(this.btnHelp);
|
this.Controls.Add(this.btnHelp);
|
||||||
this.Controls.Add(this.btnApply);
|
this.Controls.Add(this.btnApply);
|
||||||
this.Controls.Add(this.btnCancel);
|
this.Controls.Add(this.btnCancel);
|
||||||
this.Controls.Add(this.textBoxArgs);
|
this.Controls.Add(this.textBoxArgs);
|
||||||
this.MinimumSize = new System.Drawing.Size(500, 160);
|
this.MinimumSize = new System.Drawing.Size(520, 160);
|
||||||
this.Name = "DialogSettingsCefArgs";
|
this.Name = "DialogSettingsCefArgs";
|
||||||
this.ShowIcon = false;
|
this.ShowIcon = false;
|
||||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||||
|
|||||||
@@ -1,23 +1,22 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDck.Core.Controls;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDck.Core.Utils;
|
using TweetDuck.Core.Utils;
|
||||||
|
using TweetLib.Core.Collections;
|
||||||
|
|
||||||
namespace TweetDck.Core.Other.Settings.Dialogs{
|
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||||
sealed partial class DialogSettingsCefArgs : Form{
|
sealed partial class DialogSettingsCefArgs : Form{
|
||||||
public string CefArgs{
|
public string CefArgs => textBoxArgs.Text;
|
||||||
get{
|
|
||||||
return textBoxArgs.Text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public DialogSettingsCefArgs(){
|
private readonly string initialArgs;
|
||||||
|
|
||||||
|
public DialogSettingsCefArgs(string args){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName+" Settings - CEF Arguments";
|
Text = Program.BrandName+" Options - CEF Arguments";
|
||||||
|
|
||||||
textBoxArgs.EnableMultilineShortcuts();
|
textBoxArgs.EnableMultilineShortcuts();
|
||||||
textBoxArgs.Text = Program.UserConfig.CustomCefArgs ?? "";
|
textBoxArgs.Text = initialArgs = args ?? "";
|
||||||
textBoxArgs.Select(textBoxArgs.Text.Length, 0);
|
textBoxArgs.Select(textBoxArgs.Text.Length, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,18 +25,16 @@ namespace TweetDck.Core.Other.Settings.Dialogs{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void btnApply_Click(object sender, EventArgs e){
|
private void btnApply_Click(object sender, EventArgs e){
|
||||||
string prevArgs = Program.UserConfig.CustomCefArgs;
|
if (CefArgs == initialArgs){
|
||||||
|
|
||||||
if (CefArgs == prevArgs){
|
|
||||||
DialogResult = DialogResult.Cancel;
|
DialogResult = DialogResult.Cancel;
|
||||||
Close();
|
Close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int count = CommandLineArgsParser.ReadCefArguments(CefArgs).Count;
|
int count = CommandLineArgs.ReadCefArguments(CefArgs).Count;
|
||||||
string prompt = count == 0 && !string.IsNullOrWhiteSpace(prevArgs) ? "All arguments will be removed from the settings. Continue?" : count+(count == 1 ? " argument" : " arguments")+" will be added to the settings. Continue?";
|
string prompt = count == 0 && !string.IsNullOrWhiteSpace(initialArgs) ? "All current arguments will be removed. Continue?" : count+(count == 1 ? " argument was" : " arguments were")+" detected. Continue?";
|
||||||
|
|
||||||
if (MessageBox.Show(prompt, "Confirm CEF Arguments", MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.OK){
|
if (FormMessage.Question("Confirm CEF Arguments", prompt, FormMessage.OK, FormMessage.Cancel)){
|
||||||
DialogResult = DialogResult.OK;
|
DialogResult = DialogResult.OK;
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|||||||
241
Core/Other/Settings/Dialogs/DialogSettingsManage.Designer.cs
generated
Normal file
241
Core/Other/Settings/Dialogs/DialogSettingsManage.Designer.cs
generated
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
namespace TweetDuck.Core.Other.Settings.Dialogs {
|
||||||
|
partial class DialogSettingsManage {
|
||||||
|
/// <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.components = new System.ComponentModel.Container();
|
||||||
|
this.btnCancel = new System.Windows.Forms.Button();
|
||||||
|
this.btnContinue = new System.Windows.Forms.Button();
|
||||||
|
this.cbProgramConfig = new System.Windows.Forms.CheckBox();
|
||||||
|
this.cbSession = new System.Windows.Forms.CheckBox();
|
||||||
|
this.cbPluginData = new System.Windows.Forms.CheckBox();
|
||||||
|
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
||||||
|
this.cbSystemConfig = new System.Windows.Forms.CheckBox();
|
||||||
|
this.panelSelection = new System.Windows.Forms.FlowLayoutPanel();
|
||||||
|
this.panelDecision = new System.Windows.Forms.FlowLayoutPanel();
|
||||||
|
this.radioImport = new System.Windows.Forms.RadioButton();
|
||||||
|
this.radioExport = new System.Windows.Forms.RadioButton();
|
||||||
|
this.radioReset = new System.Windows.Forms.RadioButton();
|
||||||
|
this.panelSelection.SuspendLayout();
|
||||||
|
this.panelDecision.SuspendLayout();
|
||||||
|
this.SuspendLayout();
|
||||||
|
//
|
||||||
|
// btnCancel
|
||||||
|
//
|
||||||
|
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.btnCancel.AutoSize = true;
|
||||||
|
this.btnCancel.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.btnCancel.Location = new System.Drawing.Point(165, 92);
|
||||||
|
this.btnCancel.Name = "btnCancel";
|
||||||
|
this.btnCancel.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||||
|
this.btnCancel.Size = new System.Drawing.Size(57, 25);
|
||||||
|
this.btnCancel.TabIndex = 4;
|
||||||
|
this.btnCancel.Text = "Cancel";
|
||||||
|
this.btnCancel.UseVisualStyleBackColor = true;
|
||||||
|
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
|
||||||
|
//
|
||||||
|
// btnContinue
|
||||||
|
//
|
||||||
|
this.btnContinue.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.btnContinue.AutoSize = true;
|
||||||
|
this.btnContinue.Enabled = false;
|
||||||
|
this.btnContinue.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.btnContinue.Location = new System.Drawing.Point(114, 92);
|
||||||
|
this.btnContinue.Name = "btnContinue";
|
||||||
|
this.btnContinue.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||||
|
this.btnContinue.Size = new System.Drawing.Size(45, 25);
|
||||||
|
this.btnContinue.TabIndex = 3;
|
||||||
|
this.btnContinue.Text = "Next";
|
||||||
|
this.btnContinue.UseVisualStyleBackColor = true;
|
||||||
|
this.btnContinue.Click += new System.EventHandler(this.btnContinue_Click);
|
||||||
|
//
|
||||||
|
// cbProgramConfig
|
||||||
|
//
|
||||||
|
this.cbProgramConfig.AutoSize = true;
|
||||||
|
this.cbProgramConfig.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.cbProgramConfig.Location = new System.Drawing.Point(3, 3);
|
||||||
|
this.cbProgramConfig.Margin = new System.Windows.Forms.Padding(3, 3, 3, 2);
|
||||||
|
this.cbProgramConfig.Name = "cbProgramConfig";
|
||||||
|
this.cbProgramConfig.Size = new System.Drawing.Size(117, 19);
|
||||||
|
this.cbProgramConfig.TabIndex = 0;
|
||||||
|
this.cbProgramConfig.Text = "Program Options";
|
||||||
|
this.toolTip.SetToolTip(this.cbProgramConfig, "Interface, notification, and update options.");
|
||||||
|
this.cbProgramConfig.UseVisualStyleBackColor = true;
|
||||||
|
this.cbProgramConfig.CheckedChanged += new System.EventHandler(this.checkBoxSelection_CheckedChanged);
|
||||||
|
//
|
||||||
|
// cbSession
|
||||||
|
//
|
||||||
|
this.cbSession.AutoSize = true;
|
||||||
|
this.cbSession.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.cbSession.Location = new System.Drawing.Point(3, 51);
|
||||||
|
this.cbSession.Margin = new System.Windows.Forms.Padding(3, 3, 3, 2);
|
||||||
|
this.cbSession.Name = "cbSession";
|
||||||
|
this.cbSession.Size = new System.Drawing.Size(98, 19);
|
||||||
|
this.cbSession.TabIndex = 2;
|
||||||
|
this.cbSession.Text = "Login Session";
|
||||||
|
this.toolTip.SetToolTip(this.cbSession, "A token that allows logging into the\r\ncurrent TweetDeck account.");
|
||||||
|
this.cbSession.UseVisualStyleBackColor = true;
|
||||||
|
this.cbSession.CheckedChanged += new System.EventHandler(this.checkBoxSelection_CheckedChanged);
|
||||||
|
//
|
||||||
|
// cbPluginData
|
||||||
|
//
|
||||||
|
this.cbPluginData.AutoSize = true;
|
||||||
|
this.cbPluginData.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.cbPluginData.Location = new System.Drawing.Point(3, 75);
|
||||||
|
this.cbPluginData.Margin = new System.Windows.Forms.Padding(3, 3, 3, 2);
|
||||||
|
this.cbPluginData.Name = "cbPluginData";
|
||||||
|
this.cbPluginData.Size = new System.Drawing.Size(87, 19);
|
||||||
|
this.cbPluginData.TabIndex = 3;
|
||||||
|
this.cbPluginData.Text = "Plugin Data";
|
||||||
|
this.toolTip.SetToolTip(this.cbPluginData, "Data files generated by plugins.\r\nDoes not include the plugins themselves.");
|
||||||
|
this.cbPluginData.UseVisualStyleBackColor = true;
|
||||||
|
this.cbPluginData.CheckedChanged += new System.EventHandler(this.checkBoxSelection_CheckedChanged);
|
||||||
|
//
|
||||||
|
// cbSystemConfig
|
||||||
|
//
|
||||||
|
this.cbSystemConfig.AutoSize = true;
|
||||||
|
this.cbSystemConfig.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.cbSystemConfig.Location = new System.Drawing.Point(3, 27);
|
||||||
|
this.cbSystemConfig.Margin = new System.Windows.Forms.Padding(3, 3, 3, 2);
|
||||||
|
this.cbSystemConfig.Name = "cbSystemConfig";
|
||||||
|
this.cbSystemConfig.Size = new System.Drawing.Size(109, 19);
|
||||||
|
this.cbSystemConfig.TabIndex = 1;
|
||||||
|
this.cbSystemConfig.Text = "System Options";
|
||||||
|
this.toolTip.SetToolTip(this.cbSystemConfig, "Hardware acceleration and cache options.");
|
||||||
|
this.cbSystemConfig.UseVisualStyleBackColor = true;
|
||||||
|
this.cbSystemConfig.CheckedChanged += new System.EventHandler(this.checkBoxSelection_CheckedChanged);
|
||||||
|
//
|
||||||
|
// panelSelection
|
||||||
|
//
|
||||||
|
this.panelSelection.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||||
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.panelSelection.Controls.Add(this.cbProgramConfig);
|
||||||
|
this.panelSelection.Controls.Add(this.cbSystemConfig);
|
||||||
|
this.panelSelection.Controls.Add(this.cbSession);
|
||||||
|
this.panelSelection.Controls.Add(this.cbPluginData);
|
||||||
|
this.panelSelection.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
|
||||||
|
this.panelSelection.Location = new System.Drawing.Point(12, 12);
|
||||||
|
this.panelSelection.Name = "panelSelection";
|
||||||
|
this.panelSelection.Size = new System.Drawing.Size(210, 97);
|
||||||
|
this.panelSelection.TabIndex = 2;
|
||||||
|
this.panelSelection.Visible = false;
|
||||||
|
this.panelSelection.WrapContents = false;
|
||||||
|
//
|
||||||
|
// panelDecision
|
||||||
|
//
|
||||||
|
this.panelDecision.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||||
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.panelDecision.Controls.Add(this.radioImport);
|
||||||
|
this.panelDecision.Controls.Add(this.radioExport);
|
||||||
|
this.panelDecision.Controls.Add(this.radioReset);
|
||||||
|
this.panelDecision.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
|
||||||
|
this.panelDecision.Location = new System.Drawing.Point(12, 12);
|
||||||
|
this.panelDecision.Name = "panelDecision";
|
||||||
|
this.panelDecision.Size = new System.Drawing.Size(210, 75);
|
||||||
|
this.panelDecision.TabIndex = 0;
|
||||||
|
this.panelDecision.WrapContents = false;
|
||||||
|
//
|
||||||
|
// radioImport
|
||||||
|
//
|
||||||
|
this.radioImport.AutoSize = true;
|
||||||
|
this.radioImport.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.radioImport.Location = new System.Drawing.Point(3, 3);
|
||||||
|
this.radioImport.Margin = new System.Windows.Forms.Padding(3, 3, 3, 2);
|
||||||
|
this.radioImport.Name = "radioImport";
|
||||||
|
this.radioImport.Size = new System.Drawing.Size(98, 19);
|
||||||
|
this.radioImport.TabIndex = 0;
|
||||||
|
this.radioImport.TabStop = true;
|
||||||
|
this.radioImport.Text = "Import Profile";
|
||||||
|
this.radioImport.UseVisualStyleBackColor = true;
|
||||||
|
this.radioImport.CheckedChanged += new System.EventHandler(this.radioDecision_CheckedChanged);
|
||||||
|
//
|
||||||
|
// radioExport
|
||||||
|
//
|
||||||
|
this.radioExport.AutoSize = true;
|
||||||
|
this.radioExport.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.radioExport.Location = new System.Drawing.Point(3, 27);
|
||||||
|
this.radioExport.Margin = new System.Windows.Forms.Padding(3, 3, 3, 2);
|
||||||
|
this.radioExport.Name = "radioExport";
|
||||||
|
this.radioExport.Size = new System.Drawing.Size(95, 19);
|
||||||
|
this.radioExport.TabIndex = 1;
|
||||||
|
this.radioExport.TabStop = true;
|
||||||
|
this.radioExport.Text = "Export Profile";
|
||||||
|
this.radioExport.UseVisualStyleBackColor = true;
|
||||||
|
this.radioExport.CheckedChanged += new System.EventHandler(this.radioDecision_CheckedChanged);
|
||||||
|
//
|
||||||
|
// radioReset
|
||||||
|
//
|
||||||
|
this.radioReset.AutoSize = true;
|
||||||
|
this.radioReset.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.radioReset.Location = new System.Drawing.Point(3, 51);
|
||||||
|
this.radioReset.Margin = new System.Windows.Forms.Padding(3, 3, 3, 2);
|
||||||
|
this.radioReset.Name = "radioReset";
|
||||||
|
this.radioReset.Size = new System.Drawing.Size(110, 19);
|
||||||
|
this.radioReset.TabIndex = 2;
|
||||||
|
this.radioReset.TabStop = true;
|
||||||
|
this.radioReset.Text = "Restore Defaults";
|
||||||
|
this.radioReset.UseVisualStyleBackColor = true;
|
||||||
|
this.radioReset.CheckedChanged += new System.EventHandler(this.radioDecision_CheckedChanged);
|
||||||
|
//
|
||||||
|
// DialogSettingsManage
|
||||||
|
//
|
||||||
|
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||||
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
|
this.ClientSize = new System.Drawing.Size(234, 129);
|
||||||
|
this.Controls.Add(this.btnContinue);
|
||||||
|
this.Controls.Add(this.btnCancel);
|
||||||
|
this.Controls.Add(this.panelDecision);
|
||||||
|
this.Controls.Add(this.panelSelection);
|
||||||
|
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
|
||||||
|
this.MaximizeBox = false;
|
||||||
|
this.MinimizeBox = false;
|
||||||
|
this.MinimumSize = new System.Drawing.Size(250, 167);
|
||||||
|
this.Name = "DialogSettingsManage";
|
||||||
|
this.ShowIcon = false;
|
||||||
|
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||||
|
this.Text = "Manage Options";
|
||||||
|
this.panelSelection.ResumeLayout(false);
|
||||||
|
this.panelSelection.PerformLayout();
|
||||||
|
this.panelDecision.ResumeLayout(false);
|
||||||
|
this.panelDecision.PerformLayout();
|
||||||
|
this.ResumeLayout(false);
|
||||||
|
this.PerformLayout();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private System.Windows.Forms.Button btnCancel;
|
||||||
|
private System.Windows.Forms.Button btnContinue;
|
||||||
|
private System.Windows.Forms.CheckBox cbProgramConfig;
|
||||||
|
private System.Windows.Forms.CheckBox cbSystemConfig;
|
||||||
|
private System.Windows.Forms.CheckBox cbSession;
|
||||||
|
private System.Windows.Forms.CheckBox cbPluginData;
|
||||||
|
private System.Windows.Forms.ToolTip toolTip;
|
||||||
|
private System.Windows.Forms.FlowLayoutPanel panelSelection;
|
||||||
|
private System.Windows.Forms.FlowLayoutPanel panelDecision;
|
||||||
|
private System.Windows.Forms.RadioButton radioReset;
|
||||||
|
private System.Windows.Forms.RadioButton radioExport;
|
||||||
|
private System.Windows.Forms.RadioButton radioImport;
|
||||||
|
}
|
||||||
|
}
|
||||||
244
Core/Other/Settings/Dialogs/DialogSettingsManage.cs
Normal file
244
Core/Other/Settings/Dialogs/DialogSettingsManage.cs
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Configuration;
|
||||||
|
using TweetDuck.Core.Management;
|
||||||
|
using TweetDuck.Plugins;
|
||||||
|
using TweetLib.Core.Utils;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||||
|
sealed partial class DialogSettingsManage : Form{
|
||||||
|
private enum State{
|
||||||
|
Deciding, Reset, Import, Export
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProfileManager.Items SelectedItems{
|
||||||
|
get => _selectedItems;
|
||||||
|
|
||||||
|
set{
|
||||||
|
// this will call events and SetFlag, which also updates the UI
|
||||||
|
foreach(KeyValuePair<CheckBox, ProfileManager.Items> kvp in checkBoxMap){
|
||||||
|
kvp.Key.Checked = value.HasFlag(kvp.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool SelectedItemsForceRestart{
|
||||||
|
get => _selectedItems.HasFlag(ProfileManager.Items.Session);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsRestarting { get; private set; }
|
||||||
|
public bool ShouldReloadBrowser { get; private set; }
|
||||||
|
|
||||||
|
private readonly PluginManager plugins;
|
||||||
|
private readonly Dictionary<CheckBox, ProfileManager.Items> checkBoxMap = new Dictionary<CheckBox, ProfileManager.Items>(4);
|
||||||
|
private readonly bool openImportImmediately;
|
||||||
|
|
||||||
|
private State currentState;
|
||||||
|
private ProfileManager importManager;
|
||||||
|
private bool requestedRestartFromConfig;
|
||||||
|
|
||||||
|
private ProfileManager.Items _selectedItems = ProfileManager.Items.None;
|
||||||
|
|
||||||
|
public DialogSettingsManage(PluginManager plugins, bool openImportImmediately = false){
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
this.plugins = plugins;
|
||||||
|
this.currentState = State.Deciding;
|
||||||
|
|
||||||
|
this.checkBoxMap[cbProgramConfig] = ProfileManager.Items.UserConfig;
|
||||||
|
this.checkBoxMap[cbSystemConfig] = ProfileManager.Items.SystemConfig;
|
||||||
|
this.checkBoxMap[cbSession] = ProfileManager.Items.Session;
|
||||||
|
this.checkBoxMap[cbPluginData] = ProfileManager.Items.PluginData;
|
||||||
|
|
||||||
|
this.openImportImmediately = openImportImmediately;
|
||||||
|
|
||||||
|
if (openImportImmediately){
|
||||||
|
radioImport.Checked = true;
|
||||||
|
btnContinue_Click(null, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void radioDecision_CheckedChanged(object sender, EventArgs e){
|
||||||
|
btnContinue.Enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkBoxSelection_CheckedChanged(object sender, EventArgs e){
|
||||||
|
CheckBox cb = (CheckBox)sender;
|
||||||
|
SetFlag(checkBoxMap[cb], cb.Checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnContinue_Click(object sender, EventArgs e){
|
||||||
|
string file;
|
||||||
|
|
||||||
|
switch(currentState){
|
||||||
|
case State.Deciding:
|
||||||
|
// Reset
|
||||||
|
if (radioReset.Checked){
|
||||||
|
currentState = State.Reset;
|
||||||
|
|
||||||
|
Text = "Restore Defaults";
|
||||||
|
SelectedItems = ProfileManager.Items.UserConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import
|
||||||
|
else if (radioImport.Checked){
|
||||||
|
using(OpenFileDialog dialog = new OpenFileDialog{
|
||||||
|
AutoUpgradeEnabled = true,
|
||||||
|
DereferenceLinks = true,
|
||||||
|
Title = "Import TweetDuck Profile",
|
||||||
|
Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings"
|
||||||
|
}){
|
||||||
|
if (dialog.ShowDialog() != DialogResult.OK){
|
||||||
|
if (openImportImmediately){
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
file = dialog.FileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
importManager = new ProfileManager(file, plugins);
|
||||||
|
currentState = State.Import;
|
||||||
|
|
||||||
|
Text = "Import Profile";
|
||||||
|
SelectedItems = importManager.FindImportItems();
|
||||||
|
|
||||||
|
foreach(CheckBox cb in checkBoxMap.Keys){
|
||||||
|
cb.Enabled = cb.Checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export
|
||||||
|
else if (radioExport.Checked){
|
||||||
|
currentState = State.Export;
|
||||||
|
|
||||||
|
Text = "Export Profile";
|
||||||
|
btnContinue.Text = "Export Profile";
|
||||||
|
SelectedItems = ProfileManager.Items.UserConfig | ProfileManager.Items.PluginData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue...
|
||||||
|
panelDecision.Visible = false;
|
||||||
|
panelSelection.Visible = true;
|
||||||
|
Height += panelSelection.Height-panelDecision.Height;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case State.Reset:
|
||||||
|
if (FormMessage.Warning("Reset TweetDuck Options", "This will reset the selected items. Are you sure you want to proceed?", FormMessage.Yes, FormMessage.No)){
|
||||||
|
Program.Config.ProgramRestartRequested += Config_ProgramRestartRequested;
|
||||||
|
|
||||||
|
if (SelectedItems.HasFlag(ProfileManager.Items.UserConfig)){
|
||||||
|
Program.Config.User.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SelectedItems.HasFlag(ProfileManager.Items.SystemConfig)){
|
||||||
|
Program.Config.System.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
Program.Config.ProgramRestartRequested -= Config_ProgramRestartRequested;
|
||||||
|
|
||||||
|
if (SelectedItems.HasFlag(ProfileManager.Items.PluginData)){
|
||||||
|
Program.Config.Plugins.Reset();
|
||||||
|
|
||||||
|
try{
|
||||||
|
Directory.Delete(Program.PluginDataPath, true);
|
||||||
|
}catch(Exception ex){
|
||||||
|
Program.Reporter.HandleException("Profile Reset", "Could not delete plugin data.", true, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SelectedItemsForceRestart){
|
||||||
|
RestartProgram(SelectedItems.HasFlag(ProfileManager.Items.Session) ? new string[]{ Arguments.ArgDeleteCookies } : StringUtils.EmptyArray);
|
||||||
|
}
|
||||||
|
else if (requestedRestartFromConfig){
|
||||||
|
if (FormMessage.Information("Profile Reset", "The application must restart for some of the restored options to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)){
|
||||||
|
RestartProgram();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ShouldReloadBrowser = true;
|
||||||
|
|
||||||
|
DialogResult = DialogResult.OK;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case State.Import:
|
||||||
|
if (importManager.Import(SelectedItems)){
|
||||||
|
Program.Config.ProgramRestartRequested += Config_ProgramRestartRequested;
|
||||||
|
Program.Config.ReloadAll();
|
||||||
|
Program.Config.ProgramRestartRequested -= Config_ProgramRestartRequested;
|
||||||
|
|
||||||
|
if (SelectedItemsForceRestart){
|
||||||
|
RestartProgram(SelectedItems.HasFlag(ProfileManager.Items.Session) ? new string[]{ Arguments.ArgImportCookies } : StringUtils.EmptyArray);
|
||||||
|
}
|
||||||
|
else if (requestedRestartFromConfig){
|
||||||
|
if (FormMessage.Information("Profile Import", "The application must restart for some of the imported options to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)){
|
||||||
|
RestartProgram();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ShouldReloadBrowser = true;
|
||||||
|
|
||||||
|
DialogResult = DialogResult.OK;
|
||||||
|
Close();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case State.Export:
|
||||||
|
using(SaveFileDialog dialog = new SaveFileDialog{
|
||||||
|
AddExtension = true,
|
||||||
|
AutoUpgradeEnabled = true,
|
||||||
|
OverwritePrompt = true,
|
||||||
|
DefaultExt = "tdsettings",
|
||||||
|
FileName = "TweetDuck.tdsettings",
|
||||||
|
Title = "Export TweetDuck Profile",
|
||||||
|
Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings"
|
||||||
|
}){
|
||||||
|
if (dialog.ShowDialog() != DialogResult.OK){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
file = dialog.FileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
new ProfileManager(file, plugins).Export(SelectedItems);
|
||||||
|
|
||||||
|
DialogResult = DialogResult.OK;
|
||||||
|
Close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnCancel_Click(object sender, EventArgs e){
|
||||||
|
DialogResult = DialogResult.Cancel;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Config_ProgramRestartRequested(object sender, EventArgs e){
|
||||||
|
requestedRestartFromConfig = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetFlag(ProfileManager.Items flag, bool enable){
|
||||||
|
_selectedItems = enable ? _selectedItems | flag : _selectedItems & ~flag;
|
||||||
|
btnContinue.Enabled = _selectedItems != ProfileManager.Items.None;
|
||||||
|
|
||||||
|
if (currentState == State.Import){
|
||||||
|
btnContinue.Text = SelectedItemsForceRestart ? "Import && Restart" : "Import Profile";
|
||||||
|
}
|
||||||
|
else if (currentState == State.Reset){
|
||||||
|
btnContinue.Text = SelectedItemsForceRestart ? "Restore && Restart" : "Restore Defaults";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RestartProgram(params string[] extraArgs){
|
||||||
|
IsRestarting = true;
|
||||||
|
Program.Restart(extraArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
176
Core/Other/Settings/Dialogs/DialogSettingsRestart.Designer.cs
generated
Normal file
176
Core/Other/Settings/Dialogs/DialogSettingsRestart.Designer.cs
generated
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
namespace TweetDuck.Core.Other.Settings.Dialogs {
|
||||||
|
partial class DialogSettingsRestart {
|
||||||
|
/// <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.components = new System.ComponentModel.Container();
|
||||||
|
this.btnCancel = new System.Windows.Forms.Button();
|
||||||
|
this.btnRestart = new System.Windows.Forms.Button();
|
||||||
|
this.cbLogging = new System.Windows.Forms.CheckBox();
|
||||||
|
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
||||||
|
this.tbDataFolder = new System.Windows.Forms.TextBox();
|
||||||
|
this.tbShortcutTarget = new System.Windows.Forms.TextBox();
|
||||||
|
this.labelDataFolder = new System.Windows.Forms.Label();
|
||||||
|
this.labelShortcutTarget = new System.Windows.Forms.Label();
|
||||||
|
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
|
||||||
|
this.flowPanel.SuspendLayout();
|
||||||
|
this.SuspendLayout();
|
||||||
|
//
|
||||||
|
// btnCancel
|
||||||
|
//
|
||||||
|
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.btnCancel.AutoSize = true;
|
||||||
|
this.btnCancel.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.btnCancel.Location = new System.Drawing.Point(215, 146);
|
||||||
|
this.btnCancel.Name = "btnCancel";
|
||||||
|
this.btnCancel.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||||
|
this.btnCancel.Size = new System.Drawing.Size(57, 25);
|
||||||
|
this.btnCancel.TabIndex = 2;
|
||||||
|
this.btnCancel.Text = "Cancel";
|
||||||
|
this.btnCancel.UseVisualStyleBackColor = true;
|
||||||
|
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
|
||||||
|
//
|
||||||
|
// btnRestart
|
||||||
|
//
|
||||||
|
this.btnRestart.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.btnRestart.AutoSize = true;
|
||||||
|
this.btnRestart.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.btnRestart.Location = new System.Drawing.Point(152, 146);
|
||||||
|
this.btnRestart.Name = "btnRestart";
|
||||||
|
this.btnRestart.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||||
|
this.btnRestart.Size = new System.Drawing.Size(57, 25);
|
||||||
|
this.btnRestart.TabIndex = 1;
|
||||||
|
this.btnRestart.Text = "Restart";
|
||||||
|
this.btnRestart.UseVisualStyleBackColor = true;
|
||||||
|
this.btnRestart.Click += new System.EventHandler(this.btnRestart_Click);
|
||||||
|
//
|
||||||
|
// cbLogging
|
||||||
|
//
|
||||||
|
this.cbLogging.AutoSize = true;
|
||||||
|
this.cbLogging.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.cbLogging.Location = new System.Drawing.Point(3, 3);
|
||||||
|
this.cbLogging.Margin = new System.Windows.Forms.Padding(3, 3, 3, 2);
|
||||||
|
this.cbLogging.Name = "cbLogging";
|
||||||
|
this.cbLogging.Size = new System.Drawing.Size(70, 19);
|
||||||
|
this.cbLogging.TabIndex = 0;
|
||||||
|
this.cbLogging.Text = "Logging";
|
||||||
|
this.toolTip.SetToolTip(this.cbLogging, "Logs JavaScript output into TD_Console.txt file in the data folder.");
|
||||||
|
this.cbLogging.UseVisualStyleBackColor = true;
|
||||||
|
//
|
||||||
|
// tbDataFolder
|
||||||
|
//
|
||||||
|
this.tbDataFolder.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.tbDataFolder.Location = new System.Drawing.Point(3, 54);
|
||||||
|
this.tbDataFolder.Name = "tbDataFolder";
|
||||||
|
this.tbDataFolder.Size = new System.Drawing.Size(260, 23);
|
||||||
|
this.tbDataFolder.TabIndex = 2;
|
||||||
|
this.toolTip.SetToolTip(this.tbDataFolder, "Path to the data folder. Must be either an absolute path,\r\nor a simple folder nam" +
|
||||||
|
"e that will be created in LocalAppData.");
|
||||||
|
//
|
||||||
|
// tbShortcutTarget
|
||||||
|
//
|
||||||
|
this.tbShortcutTarget.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||||
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.tbShortcutTarget.Cursor = System.Windows.Forms.Cursors.Hand;
|
||||||
|
this.tbShortcutTarget.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.tbShortcutTarget.Location = new System.Drawing.Point(3, 110);
|
||||||
|
this.tbShortcutTarget.Name = "tbShortcutTarget";
|
||||||
|
this.tbShortcutTarget.ReadOnly = true;
|
||||||
|
this.tbShortcutTarget.Size = new System.Drawing.Size(260, 23);
|
||||||
|
this.tbShortcutTarget.TabIndex = 4;
|
||||||
|
this.tbShortcutTarget.Click += new System.EventHandler(this.tbShortcutTarget_Click);
|
||||||
|
//
|
||||||
|
// labelDataFolder
|
||||||
|
//
|
||||||
|
this.labelDataFolder.AutoSize = true;
|
||||||
|
this.labelDataFolder.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.labelDataFolder.Location = new System.Drawing.Point(3, 36);
|
||||||
|
this.labelDataFolder.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
|
||||||
|
this.labelDataFolder.Name = "labelDataFolder";
|
||||||
|
this.labelDataFolder.Size = new System.Drawing.Size(67, 15);
|
||||||
|
this.labelDataFolder.TabIndex = 1;
|
||||||
|
this.labelDataFolder.Text = "Data Folder";
|
||||||
|
//
|
||||||
|
// labelShortcutTarget
|
||||||
|
//
|
||||||
|
this.labelShortcutTarget.AutoSize = true;
|
||||||
|
this.labelShortcutTarget.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.labelShortcutTarget.Location = new System.Drawing.Point(3, 92);
|
||||||
|
this.labelShortcutTarget.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
|
||||||
|
this.labelShortcutTarget.Name = "labelShortcutTarget";
|
||||||
|
this.labelShortcutTarget.Size = new System.Drawing.Size(171, 15);
|
||||||
|
this.labelShortcutTarget.TabIndex = 3;
|
||||||
|
this.labelShortcutTarget.Text = "Shortcut Target (click to select)";
|
||||||
|
//
|
||||||
|
// flowPanel
|
||||||
|
//
|
||||||
|
this.flowPanel.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.flowPanel.Controls.Add(this.cbLogging);
|
||||||
|
this.flowPanel.Controls.Add(this.labelDataFolder);
|
||||||
|
this.flowPanel.Controls.Add(this.tbDataFolder);
|
||||||
|
this.flowPanel.Controls.Add(this.labelShortcutTarget);
|
||||||
|
this.flowPanel.Controls.Add(this.tbShortcutTarget);
|
||||||
|
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
|
||||||
|
this.flowPanel.Location = new System.Drawing.Point(9, 9);
|
||||||
|
this.flowPanel.Margin = new System.Windows.Forms.Padding(0);
|
||||||
|
this.flowPanel.Name = "flowPanel";
|
||||||
|
this.flowPanel.Size = new System.Drawing.Size(266, 136);
|
||||||
|
this.flowPanel.TabIndex = 0;
|
||||||
|
this.flowPanel.WrapContents = false;
|
||||||
|
//
|
||||||
|
// DialogSettingsRestart
|
||||||
|
//
|
||||||
|
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||||
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
|
this.ClientSize = new System.Drawing.Size(284, 183);
|
||||||
|
this.Controls.Add(this.flowPanel);
|
||||||
|
this.Controls.Add(this.btnRestart);
|
||||||
|
this.Controls.Add(this.btnCancel);
|
||||||
|
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
|
||||||
|
this.MaximizeBox = false;
|
||||||
|
this.MinimizeBox = false;
|
||||||
|
this.Name = "DialogSettingsRestart";
|
||||||
|
this.ShowIcon = false;
|
||||||
|
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||||
|
this.flowPanel.ResumeLayout(false);
|
||||||
|
this.flowPanel.PerformLayout();
|
||||||
|
this.ResumeLayout(false);
|
||||||
|
this.PerformLayout();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private System.Windows.Forms.Button btnCancel;
|
||||||
|
private System.Windows.Forms.Button btnRestart;
|
||||||
|
private System.Windows.Forms.CheckBox cbLogging;
|
||||||
|
private System.Windows.Forms.ToolTip toolTip;
|
||||||
|
private System.Windows.Forms.Label labelDataFolder;
|
||||||
|
private System.Windows.Forms.TextBox tbDataFolder;
|
||||||
|
private System.Windows.Forms.TextBox tbShortcutTarget;
|
||||||
|
private System.Windows.Forms.Label labelShortcutTarget;
|
||||||
|
private System.Windows.Forms.FlowLayoutPanel flowPanel;
|
||||||
|
}
|
||||||
|
}
|
||||||
61
Core/Other/Settings/Dialogs/DialogSettingsRestart.cs
Normal file
61
Core/Other/Settings/Dialogs/DialogSettingsRestart.cs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Configuration;
|
||||||
|
using TweetLib.Core.Collections;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||||
|
sealed partial class DialogSettingsRestart : Form{
|
||||||
|
public CommandLineArgs Args { get; private set; }
|
||||||
|
|
||||||
|
public DialogSettingsRestart(CommandLineArgs currentArgs){
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
cbLogging.Checked = currentArgs.HasFlag(Arguments.ArgLogging);
|
||||||
|
cbLogging.CheckedChanged += control_Change;
|
||||||
|
|
||||||
|
if (Program.IsPortable){
|
||||||
|
tbDataFolder.Text = "Not available in portable version";
|
||||||
|
tbDataFolder.Enabled = false;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder) ?? string.Empty;
|
||||||
|
tbDataFolder.TextChanged += control_Change;
|
||||||
|
}
|
||||||
|
|
||||||
|
control_Change(this, EventArgs.Empty);
|
||||||
|
|
||||||
|
Text = Program.BrandName+" Arguments";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void control_Change(object sender, EventArgs e){
|
||||||
|
Args = new CommandLineArgs();
|
||||||
|
|
||||||
|
if (cbLogging.Checked){
|
||||||
|
Args.AddFlag(Arguments.ArgLogging);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(tbDataFolder.Text) && tbDataFolder.Enabled){
|
||||||
|
Args.SetValue(Arguments.ArgDataFolder, tbDataFolder.Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
tbShortcutTarget.Text = $@"""{Application.ExecutablePath}""{(Args.Count > 0 ? " " : "")}{Args}";
|
||||||
|
tbShortcutTarget.Select(tbShortcutTarget.Text.Length, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tbShortcutTarget_Click(object sender, EventArgs e){
|
||||||
|
if (tbShortcutTarget.SelectionLength == 0){
|
||||||
|
tbShortcutTarget.SelectAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnRestart_Click(object sender, EventArgs e){
|
||||||
|
DialogResult = DialogResult.OK;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnCancel_Click(object sender, EventArgs e){
|
||||||
|
DialogResult = DialogResult.Cancel;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
109
Core/Other/Settings/Dialogs/DialogSettingsSearchEngine.Designer.cs
generated
Normal file
109
Core/Other/Settings/Dialogs/DialogSettingsSearchEngine.Designer.cs
generated
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
namespace TweetDuck.Core.Other.Settings.Dialogs {
|
||||||
|
partial class DialogSettingsSearchEngine {
|
||||||
|
/// <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.textBoxUrl = new System.Windows.Forms.TextBox();
|
||||||
|
this.btnCancel = new System.Windows.Forms.Button();
|
||||||
|
this.btnApply = new System.Windows.Forms.Button();
|
||||||
|
this.labelInfo = new System.Windows.Forms.Label();
|
||||||
|
this.SuspendLayout();
|
||||||
|
//
|
||||||
|
// textBoxUrl
|
||||||
|
//
|
||||||
|
this.textBoxUrl.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.textBoxUrl.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.textBoxUrl.Location = new System.Drawing.Point(12, 30);
|
||||||
|
this.textBoxUrl.Name = "textBoxUrl";
|
||||||
|
this.textBoxUrl.Size = new System.Drawing.Size(310, 23);
|
||||||
|
this.textBoxUrl.TabIndex = 1;
|
||||||
|
//
|
||||||
|
// btnCancel
|
||||||
|
//
|
||||||
|
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.btnCancel.AutoSize = true;
|
||||||
|
this.btnCancel.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.btnCancel.Location = new System.Drawing.Point(207, 59);
|
||||||
|
this.btnCancel.Name = "btnCancel";
|
||||||
|
this.btnCancel.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||||
|
this.btnCancel.Size = new System.Drawing.Size(57, 25);
|
||||||
|
this.btnCancel.TabIndex = 3;
|
||||||
|
this.btnCancel.Text = "Cancel";
|
||||||
|
this.btnCancel.UseVisualStyleBackColor = true;
|
||||||
|
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
|
||||||
|
//
|
||||||
|
// btnApply
|
||||||
|
//
|
||||||
|
this.btnApply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.btnApply.AutoSize = true;
|
||||||
|
this.btnApply.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.btnApply.Location = new System.Drawing.Point(270, 59);
|
||||||
|
this.btnApply.Name = "btnApply";
|
||||||
|
this.btnApply.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||||
|
this.btnApply.Size = new System.Drawing.Size(52, 25);
|
||||||
|
this.btnApply.TabIndex = 2;
|
||||||
|
this.btnApply.Text = "Apply";
|
||||||
|
this.btnApply.UseVisualStyleBackColor = true;
|
||||||
|
this.btnApply.Click += new System.EventHandler(this.btnApply_Click);
|
||||||
|
//
|
||||||
|
// labelInfo
|
||||||
|
//
|
||||||
|
this.labelInfo.AutoSize = true;
|
||||||
|
this.labelInfo.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular);
|
||||||
|
this.labelInfo.Location = new System.Drawing.Point(12, 9);
|
||||||
|
this.labelInfo.Margin = new System.Windows.Forms.Padding(3, 0, 3, 3);
|
||||||
|
this.labelInfo.Name = "labelInfo";
|
||||||
|
this.labelInfo.Size = new System.Drawing.Size(287, 15);
|
||||||
|
this.labelInfo.TabIndex = 0;
|
||||||
|
this.labelInfo.Text = "The search query will be added at the end of the URL.";
|
||||||
|
//
|
||||||
|
// DialogSettingsSearchEngine
|
||||||
|
//
|
||||||
|
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||||
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
|
this.ClientSize = new System.Drawing.Size(334, 96);
|
||||||
|
this.Controls.Add(this.labelInfo);
|
||||||
|
this.Controls.Add(this.btnApply);
|
||||||
|
this.Controls.Add(this.btnCancel);
|
||||||
|
this.Controls.Add(this.textBoxUrl);
|
||||||
|
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
|
||||||
|
this.MaximizeBox = false;
|
||||||
|
this.MinimizeBox = false;
|
||||||
|
this.Name = "DialogSettingsSearchEngine";
|
||||||
|
this.ShowIcon = false;
|
||||||
|
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||||
|
this.ResumeLayout(false);
|
||||||
|
this.PerformLayout();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private System.Windows.Forms.TextBox textBoxUrl;
|
||||||
|
private System.Windows.Forms.Button btnCancel;
|
||||||
|
private System.Windows.Forms.Button btnApply;
|
||||||
|
private System.Windows.Forms.Label labelInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
27
Core/Other/Settings/Dialogs/DialogSettingsSearchEngine.cs
Normal file
27
Core/Other/Settings/Dialogs/DialogSettingsSearchEngine.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||||
|
sealed partial class DialogSettingsSearchEngine : Form{
|
||||||
|
public string Url => textBoxUrl.Text;
|
||||||
|
|
||||||
|
public DialogSettingsSearchEngine(){
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
Text = Program.BrandName+" Options - Custom Search Engine";
|
||||||
|
|
||||||
|
textBoxUrl.Text = Program.Config.User.SearchEngineUrl ?? "";
|
||||||
|
textBoxUrl.Select(textBoxUrl.Text.Length, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnApply_Click(object sender, EventArgs e){
|
||||||
|
DialogResult = DialogResult.OK;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnCancel_Click(object sender, EventArgs e){
|
||||||
|
DialogResult = DialogResult.Cancel;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using TweetDck.Plugins;
|
|
||||||
|
|
||||||
namespace TweetDck.Core.Other.Settings.Export{
|
|
||||||
sealed class ExportManager{
|
|
||||||
private static readonly string CookiesPath = Path.Combine(Program.StoragePath, "Cookies");
|
|
||||||
private static readonly string TempCookiesPath = Path.Combine(Program.StoragePath, "CookiesTmp");
|
|
||||||
|
|
||||||
public bool IsRestarting { get; private set; }
|
|
||||||
public Exception LastException { get; private set; }
|
|
||||||
|
|
||||||
private readonly string file;
|
|
||||||
private readonly PluginManager plugins;
|
|
||||||
|
|
||||||
public ExportManager(string file, PluginManager plugins){
|
|
||||||
this.file = file;
|
|
||||||
this.plugins = plugins;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Export(bool includeSession){
|
|
||||||
try{
|
|
||||||
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
|
|
||||||
stream.WriteFile("config", Program.ConfigFilePath);
|
|
||||||
|
|
||||||
foreach(PathInfo path in EnumerateFilesRelative(plugins.PathOfficialPlugins)){
|
|
||||||
string[] split = path.Relative.Split(CombinedFileStream.KeySeparator);
|
|
||||||
|
|
||||||
if (split.Length < 3){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if (split.Length == 3){
|
|
||||||
if (split[2].Equals(".meta", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
split[2].Equals("browser.js", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
split[2].Equals("notification.js", StringComparison.OrdinalIgnoreCase)){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
|
||||||
stream.WriteFile("plugin.off"+path.Relative, path.Full);
|
|
||||||
}catch(ArgumentOutOfRangeException e){
|
|
||||||
MessageBox.Show("Could not include a file in the export. "+e.Message, "Export Profile", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach(PathInfo path in EnumerateFilesRelative(plugins.PathCustomPlugins)){
|
|
||||||
try{
|
|
||||||
stream.WriteFile("plugin.usr"+path.Relative, path.Full);
|
|
||||||
}catch(ArgumentOutOfRangeException e){
|
|
||||||
MessageBox.Show("Could not include a file in the export. "+e.Message, "Export Profile", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (includeSession){
|
|
||||||
stream.WriteFile("cookies", CookiesPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
stream.Flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}catch(Exception e){
|
|
||||||
LastException = e;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Import(){
|
|
||||||
try{
|
|
||||||
bool updatedPlugins = false;
|
|
||||||
|
|
||||||
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))){
|
|
||||||
CombinedFileStream.Entry entry;
|
|
||||||
|
|
||||||
while((entry = stream.ReadFile()) != null){
|
|
||||||
switch(entry.KeyName){
|
|
||||||
case "config":
|
|
||||||
entry.WriteToFile(Program.ConfigFilePath);
|
|
||||||
Program.ReloadConfig();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "plugin.off":
|
|
||||||
string root = Path.Combine(plugins.PathOfficialPlugins, entry.Identifier.Split(CombinedFileStream.KeySeparator)[1]);
|
|
||||||
|
|
||||||
if (Directory.Exists(root)){
|
|
||||||
entry.WriteToFile(Path.Combine(plugins.PathOfficialPlugins, entry.Identifier.Substring(entry.KeyName.Length+1)), true);
|
|
||||||
updatedPlugins = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "plugin.usr":
|
|
||||||
entry.WriteToFile(Path.Combine(plugins.PathCustomPlugins, entry.Identifier.Substring(entry.KeyName.Length+1)), true);
|
|
||||||
updatedPlugins = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "cookies":
|
|
||||||
if (MessageBox.Show("Do you want to import the login session? This will restart "+Program.BrandName+".", "Importing "+Program.BrandName+" Settings", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
|
|
||||||
entry.WriteToFile(Path.Combine(Program.StoragePath, TempCookiesPath));
|
|
||||||
|
|
||||||
// okay to and restart, 'cookies' is always the last entry
|
|
||||||
IsRestarting = true;
|
|
||||||
Program.Restart(new string[]{ "-importcookies" });
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updatedPlugins){
|
|
||||||
plugins.Reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}catch(Exception e){
|
|
||||||
LastException = e;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ImportCookies(){
|
|
||||||
if (File.Exists(TempCookiesPath)){
|
|
||||||
try{
|
|
||||||
if (File.Exists(CookiesPath)){
|
|
||||||
File.Delete(CookiesPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
File.Move(TempCookiesPath, CookiesPath);
|
|
||||||
}catch(Exception e){
|
|
||||||
Program.Reporter.HandleException("Profile Import Error", "Could not import the cookie file to restore login session.", true, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<PathInfo> EnumerateFilesRelative(string root){
|
|
||||||
return Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo{
|
|
||||||
Full = fullPath,
|
|
||||||
Relative = fullPath.Substring(root.Length).Replace(Path.DirectorySeparatorChar, CombinedFileStream.KeySeparator) // includes leading separator character
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PathInfo{
|
|
||||||
public string Full { get; set; }
|
|
||||||
public string Relative { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
365
Core/Other/Settings/TabSettingsAdvanced.Designer.cs
generated
365
Core/Other/Settings/TabSettingsAdvanced.Designer.cs
generated
@@ -1,4 +1,4 @@
|
|||||||
namespace TweetDck.Core.Other.Settings {
|
namespace TweetDuck.Core.Other.Settings {
|
||||||
partial class TabSettingsAdvanced {
|
partial class TabSettingsAdvanced {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Required designer variable.
|
/// Required designer variable.
|
||||||
@@ -25,203 +25,264 @@
|
|||||||
private void InitializeComponent() {
|
private void InitializeComponent() {
|
||||||
this.components = new System.ComponentModel.Container();
|
this.components = new System.ComponentModel.Container();
|
||||||
this.btnClearCache = new System.Windows.Forms.Button();
|
this.btnClearCache = new System.Windows.Forms.Button();
|
||||||
this.checkHardwareAcceleration = new System.Windows.Forms.CheckBox();
|
|
||||||
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
||||||
this.btnEditCefArgs = new System.Windows.Forms.Button();
|
this.btnEditCefArgs = new System.Windows.Forms.Button();
|
||||||
this.btnEditCSS = new System.Windows.Forms.Button();
|
this.btnEditCSS = new System.Windows.Forms.Button();
|
||||||
this.btnReset = new System.Windows.Forms.Button();
|
this.btnRestartArgs = new System.Windows.Forms.Button();
|
||||||
this.btnImport = new System.Windows.Forms.Button();
|
|
||||||
this.btnExport = new System.Windows.Forms.Button();
|
|
||||||
this.groupPerformance = new System.Windows.Forms.GroupBox();
|
|
||||||
this.groupConfiguration = new System.Windows.Forms.GroupBox();
|
|
||||||
this.groupApp = new System.Windows.Forms.GroupBox();
|
|
||||||
this.btnRestartLog = new System.Windows.Forms.Button();
|
|
||||||
this.btnRestart = new System.Windows.Forms.Button();
|
this.btnRestart = new System.Windows.Forms.Button();
|
||||||
this.groupPerformance.SuspendLayout();
|
this.btnOpenAppFolder = new System.Windows.Forms.Button();
|
||||||
this.groupConfiguration.SuspendLayout();
|
this.btnOpenDataFolder = new System.Windows.Forms.Button();
|
||||||
this.groupApp.SuspendLayout();
|
this.numClearCacheThreshold = new TweetDuck.Core.Controls.NumericUpDownEx();
|
||||||
|
this.checkClearCacheAuto = new System.Windows.Forms.CheckBox();
|
||||||
|
this.labelApp = new System.Windows.Forms.Label();
|
||||||
|
this.panelAppButtons = new System.Windows.Forms.Panel();
|
||||||
|
this.labelCache = new System.Windows.Forms.Label();
|
||||||
|
this.panelClearCacheAuto = new System.Windows.Forms.Panel();
|
||||||
|
this.panelConfiguration = new System.Windows.Forms.Panel();
|
||||||
|
this.labelConfiguration = new System.Windows.Forms.Label();
|
||||||
|
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
|
||||||
|
((System.ComponentModel.ISupportInitialize)(this.numClearCacheThreshold)).BeginInit();
|
||||||
|
this.panelAppButtons.SuspendLayout();
|
||||||
|
this.panelClearCacheAuto.SuspendLayout();
|
||||||
|
this.panelConfiguration.SuspendLayout();
|
||||||
|
this.flowPanel.SuspendLayout();
|
||||||
this.SuspendLayout();
|
this.SuspendLayout();
|
||||||
//
|
//
|
||||||
// btnClearCache
|
// btnClearCache
|
||||||
//
|
//
|
||||||
this.btnClearCache.Location = new System.Drawing.Point(6, 44);
|
this.btnClearCache.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||||
|
this.btnClearCache.Location = new System.Drawing.Point(5, 135);
|
||||||
|
this.btnClearCache.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
|
||||||
this.btnClearCache.Name = "btnClearCache";
|
this.btnClearCache.Name = "btnClearCache";
|
||||||
this.btnClearCache.Size = new System.Drawing.Size(171, 23);
|
this.btnClearCache.Size = new System.Drawing.Size(143, 25);
|
||||||
this.btnClearCache.TabIndex = 14;
|
this.btnClearCache.TabIndex = 3;
|
||||||
this.btnClearCache.Text = "Clear Cache (calculating)";
|
this.btnClearCache.Text = "Clear Cache (...)";
|
||||||
this.toolTip.SetToolTip(this.btnClearCache, "Clearing cache will free up space taken by downloaded images and other resources." +
|
|
||||||
"");
|
|
||||||
this.btnClearCache.UseVisualStyleBackColor = true;
|
this.btnClearCache.UseVisualStyleBackColor = true;
|
||||||
this.btnClearCache.Click += new System.EventHandler(this.btnClearCache_Click);
|
|
||||||
//
|
|
||||||
// checkHardwareAcceleration
|
|
||||||
//
|
|
||||||
this.checkHardwareAcceleration.AutoSize = true;
|
|
||||||
this.checkHardwareAcceleration.Location = new System.Drawing.Point(6, 21);
|
|
||||||
this.checkHardwareAcceleration.Margin = new System.Windows.Forms.Padding(3, 5, 3, 3);
|
|
||||||
this.checkHardwareAcceleration.Name = "checkHardwareAcceleration";
|
|
||||||
this.checkHardwareAcceleration.Size = new System.Drawing.Size(134, 17);
|
|
||||||
this.checkHardwareAcceleration.TabIndex = 12;
|
|
||||||
this.checkHardwareAcceleration.Text = "Hardware Acceleration";
|
|
||||||
this.toolTip.SetToolTip(this.checkHardwareAcceleration, "Uses your graphics card to improve performance.\r\nDisable if you experience issues" +
|
|
||||||
" with rendering.");
|
|
||||||
this.checkHardwareAcceleration.UseVisualStyleBackColor = true;
|
|
||||||
this.checkHardwareAcceleration.CheckedChanged += new System.EventHandler(this.checkHardwareAcceleration_CheckedChanged);
|
|
||||||
//
|
//
|
||||||
// btnEditCefArgs
|
// btnEditCefArgs
|
||||||
//
|
//
|
||||||
this.btnEditCefArgs.Location = new System.Drawing.Point(6, 19);
|
this.btnEditCefArgs.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||||
|
this.btnEditCefArgs.Location = new System.Drawing.Point(5, 3);
|
||||||
|
this.btnEditCefArgs.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
|
||||||
this.btnEditCefArgs.Name = "btnEditCefArgs";
|
this.btnEditCefArgs.Name = "btnEditCefArgs";
|
||||||
this.btnEditCefArgs.Size = new System.Drawing.Size(171, 23);
|
this.btnEditCefArgs.Size = new System.Drawing.Size(143, 25);
|
||||||
this.btnEditCefArgs.TabIndex = 15;
|
this.btnEditCefArgs.TabIndex = 0;
|
||||||
this.btnEditCefArgs.Text = "Edit CEF Arguments";
|
this.btnEditCefArgs.Text = "Edit CEF Arguments";
|
||||||
this.toolTip.SetToolTip(this.btnEditCefArgs, "Set custom command line arguments for Chromium Embedded Framework.");
|
|
||||||
this.btnEditCefArgs.UseVisualStyleBackColor = true;
|
this.btnEditCefArgs.UseVisualStyleBackColor = true;
|
||||||
this.btnEditCefArgs.Click += new System.EventHandler(this.btnEditCefArgs_Click);
|
|
||||||
//
|
//
|
||||||
// btnEditCSS
|
// btnEditCSS
|
||||||
//
|
//
|
||||||
this.btnEditCSS.Location = new System.Drawing.Point(6, 48);
|
this.btnEditCSS.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||||
|
this.btnEditCSS.Location = new System.Drawing.Point(154, 3);
|
||||||
this.btnEditCSS.Name = "btnEditCSS";
|
this.btnEditCSS.Name = "btnEditCSS";
|
||||||
this.btnEditCSS.Size = new System.Drawing.Size(171, 23);
|
this.btnEditCSS.Size = new System.Drawing.Size(143, 25);
|
||||||
this.btnEditCSS.TabIndex = 16;
|
this.btnEditCSS.TabIndex = 1;
|
||||||
this.btnEditCSS.Text = "Edit CSS";
|
this.btnEditCSS.Text = "Edit CSS";
|
||||||
this.toolTip.SetToolTip(this.btnEditCSS, "Set custom CSS for browser and notification windows.");
|
|
||||||
this.btnEditCSS.UseVisualStyleBackColor = true;
|
this.btnEditCSS.UseVisualStyleBackColor = true;
|
||||||
this.btnEditCSS.Click += new System.EventHandler(this.btnEditCSS_Click);
|
|
||||||
//
|
//
|
||||||
// btnReset
|
// btnRestartArgs
|
||||||
//
|
//
|
||||||
this.btnReset.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
this.btnRestartArgs.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||||
this.btnReset.AutoSize = true;
|
this.btnRestartArgs.Location = new System.Drawing.Point(154, 32);
|
||||||
this.btnReset.Location = new System.Drawing.Point(190, 250);
|
this.btnRestartArgs.Name = "btnRestartArgs";
|
||||||
this.btnReset.Name = "btnReset";
|
this.btnRestartArgs.Size = new System.Drawing.Size(143, 25);
|
||||||
this.btnReset.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
this.btnRestartArgs.TabIndex = 3;
|
||||||
this.btnReset.Size = new System.Drawing.Size(102, 23);
|
this.btnRestartArgs.Text = "Restart with Arguments";
|
||||||
this.btnReset.TabIndex = 17;
|
this.btnRestartArgs.UseVisualStyleBackColor = true;
|
||||||
this.btnReset.Text = "Restore Defaults";
|
|
||||||
this.btnReset.UseVisualStyleBackColor = true;
|
|
||||||
this.btnReset.Click += new System.EventHandler(this.btnReset_Click);
|
|
||||||
//
|
|
||||||
// btnImport
|
|
||||||
//
|
|
||||||
this.btnImport.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
|
||||||
this.btnImport.AutoSize = true;
|
|
||||||
this.btnImport.Location = new System.Drawing.Point(100, 250);
|
|
||||||
this.btnImport.Name = "btnImport";
|
|
||||||
this.btnImport.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
|
||||||
this.btnImport.Size = new System.Drawing.Size(84, 23);
|
|
||||||
this.btnImport.TabIndex = 16;
|
|
||||||
this.btnImport.Text = "Import Profile";
|
|
||||||
this.btnImport.UseVisualStyleBackColor = true;
|
|
||||||
this.btnImport.Click += new System.EventHandler(this.btnImport_Click);
|
|
||||||
//
|
|
||||||
// btnExport
|
|
||||||
//
|
|
||||||
this.btnExport.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
|
||||||
this.btnExport.AutoSize = true;
|
|
||||||
this.btnExport.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
|
|
||||||
this.btnExport.Location = new System.Drawing.Point(9, 250);
|
|
||||||
this.btnExport.Name = "btnExport";
|
|
||||||
this.btnExport.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
|
||||||
this.btnExport.Size = new System.Drawing.Size(85, 23);
|
|
||||||
this.btnExport.TabIndex = 15;
|
|
||||||
this.btnExport.Text = "Export Profile";
|
|
||||||
this.btnExport.UseVisualStyleBackColor = true;
|
|
||||||
this.btnExport.Click += new System.EventHandler(this.btnExport_Click);
|
|
||||||
//
|
|
||||||
// groupPerformance
|
|
||||||
//
|
|
||||||
this.groupPerformance.Controls.Add(this.checkHardwareAcceleration);
|
|
||||||
this.groupPerformance.Controls.Add(this.btnClearCache);
|
|
||||||
this.groupPerformance.Location = new System.Drawing.Point(9, 9);
|
|
||||||
this.groupPerformance.Name = "groupPerformance";
|
|
||||||
this.groupPerformance.Size = new System.Drawing.Size(183, 74);
|
|
||||||
this.groupPerformance.TabIndex = 18;
|
|
||||||
this.groupPerformance.TabStop = false;
|
|
||||||
this.groupPerformance.Text = "Performance";
|
|
||||||
//
|
|
||||||
// groupConfiguration
|
|
||||||
//
|
|
||||||
this.groupConfiguration.Controls.Add(this.btnEditCSS);
|
|
||||||
this.groupConfiguration.Controls.Add(this.btnEditCefArgs);
|
|
||||||
this.groupConfiguration.Location = new System.Drawing.Point(9, 89);
|
|
||||||
this.groupConfiguration.Name = "groupConfiguration";
|
|
||||||
this.groupConfiguration.Size = new System.Drawing.Size(183, 77);
|
|
||||||
this.groupConfiguration.TabIndex = 19;
|
|
||||||
this.groupConfiguration.TabStop = false;
|
|
||||||
this.groupConfiguration.Text = "Configuration";
|
|
||||||
//
|
|
||||||
// groupApp
|
|
||||||
//
|
|
||||||
this.groupApp.Controls.Add(this.btnRestartLog);
|
|
||||||
this.groupApp.Controls.Add(this.btnRestart);
|
|
||||||
this.groupApp.Location = new System.Drawing.Point(198, 9);
|
|
||||||
this.groupApp.Name = "groupApp";
|
|
||||||
this.groupApp.Size = new System.Drawing.Size(183, 77);
|
|
||||||
this.groupApp.TabIndex = 20;
|
|
||||||
this.groupApp.TabStop = false;
|
|
||||||
this.groupApp.Text = "App";
|
|
||||||
//
|
|
||||||
// btnRestartLog
|
|
||||||
//
|
|
||||||
this.btnRestartLog.Location = new System.Drawing.Point(6, 48);
|
|
||||||
this.btnRestartLog.Name = "btnRestartLog";
|
|
||||||
this.btnRestartLog.Size = new System.Drawing.Size(171, 23);
|
|
||||||
this.btnRestartLog.TabIndex = 17;
|
|
||||||
this.btnRestartLog.Text = "Restart with Logging";
|
|
||||||
this.toolTip.SetToolTip(this.btnRestartLog, "Restarts the program and enables logging\r\ninto a debug.txt file in the installati" +
|
|
||||||
"on folder.");
|
|
||||||
this.btnRestartLog.UseVisualStyleBackColor = true;
|
|
||||||
this.btnRestartLog.Click += new System.EventHandler(this.btnRestartLog_Click);
|
|
||||||
//
|
//
|
||||||
// btnRestart
|
// btnRestart
|
||||||
//
|
//
|
||||||
this.btnRestart.Location = new System.Drawing.Point(6, 19);
|
this.btnRestart.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||||
|
this.btnRestart.Location = new System.Drawing.Point(154, 3);
|
||||||
this.btnRestart.Name = "btnRestart";
|
this.btnRestart.Name = "btnRestart";
|
||||||
this.btnRestart.Size = new System.Drawing.Size(171, 23);
|
this.btnRestart.Size = new System.Drawing.Size(143, 25);
|
||||||
this.btnRestart.TabIndex = 16;
|
this.btnRestart.TabIndex = 2;
|
||||||
this.btnRestart.Text = "Restart the Program";
|
this.btnRestart.Text = "Restart the Program";
|
||||||
this.toolTip.SetToolTip(this.btnRestart, "Restarts the program using the same command\r\nline arguments that were used at lau" +
|
|
||||||
"nch.");
|
|
||||||
this.btnRestart.UseVisualStyleBackColor = true;
|
this.btnRestart.UseVisualStyleBackColor = true;
|
||||||
this.btnRestart.Click += new System.EventHandler(this.btnRestart_Click);
|
//
|
||||||
|
// btnOpenAppFolder
|
||||||
|
//
|
||||||
|
this.btnOpenAppFolder.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||||
|
this.btnOpenAppFolder.Location = new System.Drawing.Point(5, 3);
|
||||||
|
this.btnOpenAppFolder.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
|
||||||
|
this.btnOpenAppFolder.Name = "btnOpenAppFolder";
|
||||||
|
this.btnOpenAppFolder.Size = new System.Drawing.Size(143, 25);
|
||||||
|
this.btnOpenAppFolder.TabIndex = 0;
|
||||||
|
this.btnOpenAppFolder.Text = "Open Program Folder";
|
||||||
|
this.btnOpenAppFolder.UseVisualStyleBackColor = true;
|
||||||
|
//
|
||||||
|
// btnOpenDataFolder
|
||||||
|
//
|
||||||
|
this.btnOpenDataFolder.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||||
|
this.btnOpenDataFolder.Location = new System.Drawing.Point(5, 32);
|
||||||
|
this.btnOpenDataFolder.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
|
||||||
|
this.btnOpenDataFolder.Name = "btnOpenDataFolder";
|
||||||
|
this.btnOpenDataFolder.Size = new System.Drawing.Size(143, 25);
|
||||||
|
this.btnOpenDataFolder.TabIndex = 1;
|
||||||
|
this.btnOpenDataFolder.Text = "Open Data Folder";
|
||||||
|
this.btnOpenDataFolder.UseVisualStyleBackColor = true;
|
||||||
|
//
|
||||||
|
// numClearCacheThreshold
|
||||||
|
//
|
||||||
|
this.numClearCacheThreshold.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||||
|
this.numClearCacheThreshold.Increment = new decimal(new int[] {
|
||||||
|
50,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0});
|
||||||
|
this.numClearCacheThreshold.Location = new System.Drawing.Point(210, 5);
|
||||||
|
this.numClearCacheThreshold.Maximum = new decimal(new int[] {
|
||||||
|
1000,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0});
|
||||||
|
this.numClearCacheThreshold.Minimum = new decimal(new int[] {
|
||||||
|
100,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0});
|
||||||
|
this.numClearCacheThreshold.Name = "numClearCacheThreshold";
|
||||||
|
this.numClearCacheThreshold.Size = new System.Drawing.Size(89, 23);
|
||||||
|
this.numClearCacheThreshold.TabIndex = 1;
|
||||||
|
this.numClearCacheThreshold.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
|
||||||
|
this.numClearCacheThreshold.TextSuffix = " MB";
|
||||||
|
this.numClearCacheThreshold.Value = new decimal(new int[] {
|
||||||
|
250,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0});
|
||||||
|
//
|
||||||
|
// checkClearCacheAuto
|
||||||
|
//
|
||||||
|
this.checkClearCacheAuto.AutoSize = true;
|
||||||
|
this.checkClearCacheAuto.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||||
|
this.checkClearCacheAuto.Location = new System.Drawing.Point(6, 6);
|
||||||
|
this.checkClearCacheAuto.Margin = new System.Windows.Forms.Padding(6, 6, 0, 2);
|
||||||
|
this.checkClearCacheAuto.Name = "checkClearCacheAuto";
|
||||||
|
this.checkClearCacheAuto.Size = new System.Drawing.Size(201, 19);
|
||||||
|
this.checkClearCacheAuto.TabIndex = 0;
|
||||||
|
this.checkClearCacheAuto.Text = "Clear Automatically When Above";
|
||||||
|
this.checkClearCacheAuto.UseVisualStyleBackColor = true;
|
||||||
|
//
|
||||||
|
// labelApp
|
||||||
|
//
|
||||||
|
this.labelApp.AutoSize = true;
|
||||||
|
this.labelApp.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
|
||||||
|
this.labelApp.Location = new System.Drawing.Point(0, 0);
|
||||||
|
this.labelApp.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1);
|
||||||
|
this.labelApp.Name = "labelApp";
|
||||||
|
this.labelApp.Size = new System.Drawing.Size(97, 19);
|
||||||
|
this.labelApp.TabIndex = 0;
|
||||||
|
this.labelApp.Text = "APPLICATION";
|
||||||
|
//
|
||||||
|
// panelAppButtons
|
||||||
|
//
|
||||||
|
this.panelAppButtons.Controls.Add(this.btnOpenDataFolder);
|
||||||
|
this.panelAppButtons.Controls.Add(this.btnOpenAppFolder);
|
||||||
|
this.panelAppButtons.Controls.Add(this.btnRestart);
|
||||||
|
this.panelAppButtons.Controls.Add(this.btnRestartArgs);
|
||||||
|
this.panelAppButtons.Location = new System.Drawing.Point(0, 20);
|
||||||
|
this.panelAppButtons.Margin = new System.Windows.Forms.Padding(0);
|
||||||
|
this.panelAppButtons.Name = "panelAppButtons";
|
||||||
|
this.panelAppButtons.Size = new System.Drawing.Size(300, 62);
|
||||||
|
this.panelAppButtons.TabIndex = 1;
|
||||||
|
//
|
||||||
|
// labelCache
|
||||||
|
//
|
||||||
|
this.labelCache.AutoSize = true;
|
||||||
|
this.labelCache.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
|
||||||
|
this.labelCache.Location = new System.Drawing.Point(0, 112);
|
||||||
|
this.labelCache.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1);
|
||||||
|
this.labelCache.Name = "labelCache";
|
||||||
|
this.labelCache.Size = new System.Drawing.Size(123, 19);
|
||||||
|
this.labelCache.TabIndex = 2;
|
||||||
|
this.labelCache.Text = "BROWSER CACHE";
|
||||||
|
//
|
||||||
|
// panelClearCacheAuto
|
||||||
|
//
|
||||||
|
this.panelClearCacheAuto.Controls.Add(this.checkClearCacheAuto);
|
||||||
|
this.panelClearCacheAuto.Controls.Add(this.numClearCacheThreshold);
|
||||||
|
this.panelClearCacheAuto.Location = new System.Drawing.Point(0, 163);
|
||||||
|
this.panelClearCacheAuto.Margin = new System.Windows.Forms.Padding(0);
|
||||||
|
this.panelClearCacheAuto.Name = "panelClearCacheAuto";
|
||||||
|
this.panelClearCacheAuto.Size = new System.Drawing.Size(300, 28);
|
||||||
|
this.panelClearCacheAuto.TabIndex = 4;
|
||||||
|
//
|
||||||
|
// panelConfiguration
|
||||||
|
//
|
||||||
|
this.panelConfiguration.Controls.Add(this.btnEditCSS);
|
||||||
|
this.panelConfiguration.Controls.Add(this.btnEditCefArgs);
|
||||||
|
this.panelConfiguration.Location = new System.Drawing.Point(0, 241);
|
||||||
|
this.panelConfiguration.Margin = new System.Windows.Forms.Padding(0);
|
||||||
|
this.panelConfiguration.Name = "panelConfiguration";
|
||||||
|
this.panelConfiguration.Size = new System.Drawing.Size(300, 31);
|
||||||
|
this.panelConfiguration.TabIndex = 6;
|
||||||
|
//
|
||||||
|
// labelConfiguration
|
||||||
|
//
|
||||||
|
this.labelConfiguration.AutoSize = true;
|
||||||
|
this.labelConfiguration.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
|
||||||
|
this.labelConfiguration.Location = new System.Drawing.Point(0, 221);
|
||||||
|
this.labelConfiguration.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1);
|
||||||
|
this.labelConfiguration.Name = "labelConfiguration";
|
||||||
|
this.labelConfiguration.Size = new System.Drawing.Size(123, 19);
|
||||||
|
this.labelConfiguration.TabIndex = 5;
|
||||||
|
this.labelConfiguration.Text = "CONFIGURATION";
|
||||||
|
//
|
||||||
|
// flowPanel
|
||||||
|
//
|
||||||
|
this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||||
|
| System.Windows.Forms.AnchorStyles.Left)));
|
||||||
|
this.flowPanel.Controls.Add(this.labelApp);
|
||||||
|
this.flowPanel.Controls.Add(this.panelAppButtons);
|
||||||
|
this.flowPanel.Controls.Add(this.labelCache);
|
||||||
|
this.flowPanel.Controls.Add(this.btnClearCache);
|
||||||
|
this.flowPanel.Controls.Add(this.panelClearCacheAuto);
|
||||||
|
this.flowPanel.Controls.Add(this.labelConfiguration);
|
||||||
|
this.flowPanel.Controls.Add(this.panelConfiguration);
|
||||||
|
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
|
||||||
|
this.flowPanel.Location = new System.Drawing.Point(9, 9);
|
||||||
|
this.flowPanel.Name = "flowPanel";
|
||||||
|
this.flowPanel.Size = new System.Drawing.Size(300, 462);
|
||||||
|
this.flowPanel.TabIndex = 0;
|
||||||
|
this.flowPanel.WrapContents = false;
|
||||||
//
|
//
|
||||||
// TabSettingsAdvanced
|
// TabSettingsAdvanced
|
||||||
//
|
//
|
||||||
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.Controls.Add(this.groupApp);
|
this.Controls.Add(this.flowPanel);
|
||||||
this.Controls.Add(this.groupConfiguration);
|
|
||||||
this.Controls.Add(this.groupPerformance);
|
|
||||||
this.Controls.Add(this.btnReset);
|
|
||||||
this.Controls.Add(this.btnImport);
|
|
||||||
this.Controls.Add(this.btnExport);
|
|
||||||
this.Name = "TabSettingsAdvanced";
|
this.Name = "TabSettingsAdvanced";
|
||||||
this.Size = new System.Drawing.Size(478, 282);
|
this.Size = new System.Drawing.Size(631, 480);
|
||||||
this.groupPerformance.ResumeLayout(false);
|
((System.ComponentModel.ISupportInitialize)(this.numClearCacheThreshold)).EndInit();
|
||||||
this.groupPerformance.PerformLayout();
|
this.panelAppButtons.ResumeLayout(false);
|
||||||
this.groupConfiguration.ResumeLayout(false);
|
this.panelClearCacheAuto.ResumeLayout(false);
|
||||||
this.groupApp.ResumeLayout(false);
|
this.panelClearCacheAuto.PerformLayout();
|
||||||
|
this.panelConfiguration.ResumeLayout(false);
|
||||||
|
this.flowPanel.ResumeLayout(false);
|
||||||
|
this.flowPanel.PerformLayout();
|
||||||
this.ResumeLayout(false);
|
this.ResumeLayout(false);
|
||||||
this.PerformLayout();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private System.Windows.Forms.Button btnClearCache;
|
private System.Windows.Forms.Button btnClearCache;
|
||||||
private System.Windows.Forms.CheckBox checkHardwareAcceleration;
|
|
||||||
private System.Windows.Forms.ToolTip toolTip;
|
private System.Windows.Forms.ToolTip toolTip;
|
||||||
private System.Windows.Forms.Button btnReset;
|
|
||||||
private System.Windows.Forms.Button btnImport;
|
|
||||||
private System.Windows.Forms.Button btnExport;
|
|
||||||
private System.Windows.Forms.GroupBox groupPerformance;
|
|
||||||
private System.Windows.Forms.GroupBox groupConfiguration;
|
|
||||||
private System.Windows.Forms.Button btnEditCefArgs;
|
private System.Windows.Forms.Button btnEditCefArgs;
|
||||||
private System.Windows.Forms.Button btnEditCSS;
|
private System.Windows.Forms.Button btnEditCSS;
|
||||||
private System.Windows.Forms.GroupBox groupApp;
|
private System.Windows.Forms.Button btnRestartArgs;
|
||||||
private System.Windows.Forms.Button btnRestartLog;
|
|
||||||
private System.Windows.Forms.Button btnRestart;
|
private System.Windows.Forms.Button btnRestart;
|
||||||
|
private System.Windows.Forms.Button btnOpenAppFolder;
|
||||||
|
private System.Windows.Forms.Button btnOpenDataFolder;
|
||||||
|
private System.Windows.Forms.Label labelApp;
|
||||||
|
private System.Windows.Forms.Panel panelAppButtons;
|
||||||
|
private System.Windows.Forms.Label labelCache;
|
||||||
|
private System.Windows.Forms.Panel panelClearCacheAuto;
|
||||||
|
private System.Windows.Forms.Panel panelConfiguration;
|
||||||
|
private System.Windows.Forms.Label labelConfiguration;
|
||||||
|
private Controls.NumericUpDownEx numClearCacheThreshold;
|
||||||
|
private System.Windows.Forms.CheckBox checkClearCacheAuto;
|
||||||
|
private System.Windows.Forms.FlowLayoutPanel flowPanel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,167 +1,157 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDck.Core.Controls;
|
using TweetDuck.Configuration;
|
||||||
using TweetDck.Core.Other.Settings.Dialogs;
|
using TweetDuck.Core.Controls;
|
||||||
using TweetDck.Core.Other.Settings.Export;
|
using TweetDuck.Core.Management;
|
||||||
using TweetDck.Core.Utils;
|
using TweetDuck.Core.Other.Settings.Dialogs;
|
||||||
using TweetDck.Plugins;
|
using TweetDuck.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDck.Core.Other.Settings{
|
namespace TweetDuck.Core.Other.Settings{
|
||||||
partial class TabSettingsAdvanced : BaseTabSettings{
|
sealed partial class TabSettingsAdvanced : BaseTabSettings{
|
||||||
private readonly Action browserReloadAction;
|
private readonly Action<string> reinjectBrowserCSS;
|
||||||
private readonly PluginManager plugins;
|
private readonly Action openDevTools;
|
||||||
|
|
||||||
public TabSettingsAdvanced(Action browserReloadAction, PluginManager plugins){
|
public TabSettingsAdvanced(Action<string> reinjectBrowserCSS, Action openDevTools){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
this.browserReloadAction = browserReloadAction;
|
this.reinjectBrowserCSS = reinjectBrowserCSS;
|
||||||
this.plugins = plugins;
|
this.openDevTools = openDevTools;
|
||||||
|
|
||||||
checkHardwareAcceleration.Checked = HardwareAcceleration.IsEnabled;
|
// application
|
||||||
|
|
||||||
|
toolTip.SetToolTip(btnOpenAppFolder, "Opens the folder where the app is located.");
|
||||||
|
toolTip.SetToolTip(btnOpenDataFolder, "Opens the folder where your profile data is located.");
|
||||||
|
toolTip.SetToolTip(btnRestart, "Restarts the program using the same command\r\nline arguments that were used at launch.");
|
||||||
|
toolTip.SetToolTip(btnRestartArgs, "Restarts the program with customizable\r\ncommand line arguments.");
|
||||||
|
|
||||||
BrowserCache.CalculateCacheSize(bytes => this.InvokeSafe(() => {
|
// browser cache
|
||||||
if (bytes == -1L){
|
|
||||||
btnClearCache.Text = "Clear Cache (unknown size)";
|
toolTip.SetToolTip(btnClearCache, "Clearing cache will free up space taken by downloaded images and other resources.");
|
||||||
}
|
toolTip.SetToolTip(checkClearCacheAuto, "Automatically clears cache when its size exceeds the set threshold. Note that cache can only be cleared when closing TweetDuck.");
|
||||||
else{
|
|
||||||
btnClearCache.Text = "Clear Cache ("+(int)Math.Ceiling(bytes/(1024.0*1024.0))+" MB)";
|
checkClearCacheAuto.Checked = SysConfig.ClearCacheAutomatically;
|
||||||
}
|
numClearCacheThreshold.Enabled = checkClearCacheAuto.Checked;
|
||||||
}));
|
numClearCacheThreshold.SetValueSafe(SysConfig.ClearCacheThreshold);
|
||||||
|
|
||||||
|
BrowserCache.GetCacheSize(task => {
|
||||||
|
string text = task.Status == TaskStatus.RanToCompletion ? (int)Math.Ceiling(task.Result/(1024.0*1024.0))+" MB" : "unknown";
|
||||||
|
this.InvokeSafe(() => btnClearCache.Text = $"Clear Cache ({text})");
|
||||||
|
});
|
||||||
|
|
||||||
|
// configuration
|
||||||
|
|
||||||
|
toolTip.SetToolTip(btnEditCefArgs, "Set custom command line arguments for Chromium Embedded Framework.");
|
||||||
|
toolTip.SetToolTip(btnEditCSS, "Set custom CSS for browser and notification windows.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnClearCache_Click(object sender, EventArgs e){
|
public override void OnReady(){
|
||||||
if (!Ready)return;
|
btnOpenAppFolder.Click += btnOpenAppFolder_Click;
|
||||||
|
btnOpenDataFolder.Click += btnOpenDataFolder_Click;
|
||||||
|
btnRestart.Click += btnRestart_Click;
|
||||||
|
btnRestartArgs.Click += btnRestartArgs_Click;
|
||||||
|
|
||||||
btnClearCache.Enabled = false;
|
btnClearCache.Click += btnClearCache_Click;
|
||||||
BrowserCache.SetClearOnExit();
|
checkClearCacheAuto.CheckedChanged += checkClearCacheAuto_CheckedChanged;
|
||||||
|
|
||||||
MessageBox.Show("Cache will be automatically cleared when "+Program.BrandName+" exits.", "Clear Cache", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
btnEditCefArgs.Click += btnEditCefArgs_Click;
|
||||||
|
btnEditCSS.Click += btnEditCSS_Click;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkHardwareAcceleration_CheckedChanged(object sender, EventArgs e){
|
public override void OnClosing(){
|
||||||
if (!Ready)return;
|
SysConfig.ClearCacheAutomatically = checkClearCacheAuto.Checked;
|
||||||
|
SysConfig.ClearCacheThreshold = (int)numClearCacheThreshold.Value;
|
||||||
bool succeeded = false;
|
|
||||||
|
|
||||||
if (checkHardwareAcceleration.Checked){
|
|
||||||
if (HardwareAcceleration.CanEnable){
|
|
||||||
succeeded = HardwareAcceleration.Enable();
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
MessageBox.Show("Cannot enable hardware acceleration, the libraries libEGL.dll and libGLESv2.dll could not be restored.", Program.BrandName+" Settings", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
succeeded = HardwareAcceleration.Disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (succeeded){
|
|
||||||
PromptRestart();
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
checkHardwareAcceleration.CheckedChanged -= checkHardwareAcceleration_CheckedChanged;
|
|
||||||
checkHardwareAcceleration.Checked = HardwareAcceleration.IsEnabled;
|
|
||||||
checkHardwareAcceleration.CheckedChanged += checkHardwareAcceleration_CheckedChanged;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnEditCefArgs_Click(object sender, EventArgs e){
|
#region Application
|
||||||
DialogSettingsCefArgs form = new DialogSettingsCefArgs();
|
|
||||||
|
private void btnOpenAppFolder_Click(object sender, EventArgs e){
|
||||||
if (form.ShowDialog(ParentForm) == DialogResult.OK){
|
using(Process.Start("explorer.exe", "\""+Program.ProgramPath+"\"")){}
|
||||||
Config.CustomCefArgs = form.CefArgs;
|
|
||||||
form.Dispose();
|
|
||||||
PromptRestart();
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
form.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnEditCSS_Click(object sender, EventArgs e){
|
private void btnOpenDataFolder_Click(object sender, EventArgs e){
|
||||||
using(DialogSettingsCSS form = new DialogSettingsCSS()){
|
using(Process.Start("explorer.exe", "\""+Program.StoragePath+"\"")){}
|
||||||
if (form.ShowDialog(ParentForm) == DialogResult.OK){
|
|
||||||
bool hasChangedBrowser = form.BrowserCSS != Config.CustomBrowserCSS;
|
|
||||||
|
|
||||||
Config.CustomBrowserCSS = form.BrowserCSS;
|
|
||||||
Config.CustomNotificationCSS = form.NotificationCSS;
|
|
||||||
|
|
||||||
if (hasChangedBrowser && MessageBox.Show("The browser CSS has changed, do you want to reload it?", "Browser CSS Changed", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
|
|
||||||
browserReloadAction();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void btnExport_Click(object sender, EventArgs e){
|
|
||||||
DialogResult resultSaveCredentials = MessageBox.Show("Do you want to include your login session? This will not save your password into the file, but it will allow anyone with the file to login into TweetDeck as you.", "Export "+Program.BrandName+" Settings", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button3);
|
|
||||||
if (resultSaveCredentials == DialogResult.Cancel)return;
|
|
||||||
|
|
||||||
bool saveCredentials = resultSaveCredentials == DialogResult.Yes;
|
|
||||||
string file;
|
|
||||||
|
|
||||||
using(SaveFileDialog dialog = new SaveFileDialog{
|
|
||||||
AddExtension = true,
|
|
||||||
AutoUpgradeEnabled = true,
|
|
||||||
OverwritePrompt = true,
|
|
||||||
DefaultExt = "tdsettings",
|
|
||||||
FileName = Program.BrandName+".tdsettings",
|
|
||||||
Title = "Export "+Program.BrandName+" Settings",
|
|
||||||
Filter = Program.BrandName+" Settings (*.tdsettings)|*.tdsettings"
|
|
||||||
}){
|
|
||||||
file = dialog.ShowDialog() == DialogResult.OK ? dialog.FileName : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file != null){
|
|
||||||
Program.UserConfig.Save();
|
|
||||||
|
|
||||||
ExportManager manager = new ExportManager(file, plugins);
|
|
||||||
|
|
||||||
if (!manager.Export(saveCredentials)){
|
|
||||||
Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting "+Program.BrandName+" settings.", true, manager.LastException);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void btnImport_Click(object sender, EventArgs e){
|
|
||||||
string file;
|
|
||||||
|
|
||||||
using(OpenFileDialog dialog = new OpenFileDialog{
|
|
||||||
AutoUpgradeEnabled = true,
|
|
||||||
DereferenceLinks = true,
|
|
||||||
Title = "Import "+Program.BrandName+" Settings",
|
|
||||||
Filter = Program.BrandName+" Settings (*.tdsettings)|*.tdsettings"
|
|
||||||
}){
|
|
||||||
file = dialog.ShowDialog() == DialogResult.OK ? dialog.FileName : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file != null){
|
|
||||||
ExportManager manager = new ExportManager(file, plugins);
|
|
||||||
|
|
||||||
if (manager.Import()){
|
|
||||||
if (!manager.IsRestarting){
|
|
||||||
((FormSettings)ParentForm).ReloadUI();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
Program.Reporter.HandleException("Profile Import Error", "An exception happened while importing "+Program.BrandName+" settings.", true, manager.LastException);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void btnReset_Click(object sender, EventArgs e){
|
|
||||||
if (MessageBox.Show("This will reset all of your settings, including disabled plugins. Do you want to proceed?", "Reset "+Program.BrandName+" Settings", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
|
|
||||||
Program.ResetConfig();
|
|
||||||
((FormSettings)ParentForm).ReloadUI();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnRestart_Click(object sender, EventArgs e){
|
private void btnRestart_Click(object sender, EventArgs e){
|
||||||
Program.Restart();
|
Program.Restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnRestartLog_Click(object sender, EventArgs e){
|
private void btnRestartArgs_Click(object sender, EventArgs e){
|
||||||
Program.Restart(new string[]{ "-log" });
|
using(DialogSettingsRestart dialog = new DialogSettingsRestart(Arguments.GetCurrentClean())){
|
||||||
|
if (dialog.ShowDialog() == DialogResult.OK){
|
||||||
|
Program.RestartWithArgs(dialog.Args);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
#region Browser Cache
|
||||||
|
|
||||||
|
private void btnClearCache_Click(object sender, EventArgs e){
|
||||||
|
btnClearCache.Enabled = false;
|
||||||
|
BrowserCache.SetClearOnExit();
|
||||||
|
FormMessage.Information("Clear Cache", "Cache will be automatically cleared when TweetDuck exits.", FormMessage.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkClearCacheAuto_CheckedChanged(object sender, EventArgs e){
|
||||||
|
numClearCacheThreshold.Enabled = checkClearCacheAuto.Checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
#region Configuration
|
||||||
|
|
||||||
|
private void btnEditCefArgs_Click(object sender, EventArgs e){
|
||||||
|
DialogSettingsCefArgs form = new DialogSettingsCefArgs(Config.CustomCefArgs);
|
||||||
|
|
||||||
|
form.VisibleChanged += (sender2, args2) => {
|
||||||
|
form.MoveToCenter(ParentForm);
|
||||||
|
};
|
||||||
|
|
||||||
|
form.FormClosed += (sender2, args2) => {
|
||||||
|
RestoreParentForm();
|
||||||
|
|
||||||
|
if (form.DialogResult == DialogResult.OK){
|
||||||
|
Config.CustomCefArgs = form.CefArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.Dispose();
|
||||||
|
};
|
||||||
|
|
||||||
|
form.Show(ParentForm);
|
||||||
|
NativeMethods.SetFormDisabled(ParentForm, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnEditCSS_Click(object sender, EventArgs e){
|
||||||
|
DialogSettingsCSS form = new DialogSettingsCSS(Config.CustomBrowserCSS, Config.CustomNotificationCSS, reinjectBrowserCSS, openDevTools);
|
||||||
|
|
||||||
|
form.VisibleChanged += (sender2, args2) => {
|
||||||
|
form.MoveToCenter(ParentForm);
|
||||||
|
};
|
||||||
|
|
||||||
|
form.FormClosed += (sender2, args2) => {
|
||||||
|
RestoreParentForm();
|
||||||
|
|
||||||
|
if (form.DialogResult == DialogResult.OK){
|
||||||
|
Config.CustomBrowserCSS = form.BrowserCSS;
|
||||||
|
Config.CustomNotificationCSS = form.NotificationCSS;
|
||||||
|
}
|
||||||
|
|
||||||
|
reinjectBrowserCSS(Config.CustomBrowserCSS); // reinject on cancel too, because the CSS is updated while typing
|
||||||
|
form.Dispose();
|
||||||
|
};
|
||||||
|
|
||||||
|
form.Show(ParentForm);
|
||||||
|
NativeMethods.SetFormDisabled(ParentForm, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RestoreParentForm(){
|
||||||
|
if (ParentForm != null){ // when the parent is closed first, ParentForm is null in FormClosed event
|
||||||
|
NativeMethods.SetFormDisabled(ParentForm, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user