1
0
mirror of https://github.com/chylex/TweetDuck.git synced 2025-09-14 19:32:10 +02:00

Compare commits

..

1725 Commits

Author SHA1 Message Date
1b5304efb7 Release 1.16.3 2018-11-07 11:55:37 +01:00
d59375308f Work around clear-columns plugin reappearing after being disabled
Closes #240
2018-11-07 11:16:44 +01:00
8c9509a906 Fix broken colors of plugin elements with dark theme 2018-11-07 10:27:08 +01:00
fb86d8f3a8 Fix broken black theme colors 2018-11-07 10:18:24 +01:00
50e909cb3d Move debug TweetDeck resource freezing and update it to support CSS 2018-11-07 05:45:20 +01:00
2f54edf7e7 Fix missing <body> margin reset 2018-11-07 00:26:43 +01:00
c251603e1e Fix broken Arial font override 2018-11-07 00:26:22 +01:00
4476edb6c3 Release 1.16.2 2018-10-18 22:31:28 +02:00
28fc67660f Fix border color of large timeline cards 2018-10-18 22:30:11 +02:00
6e8b5a5ce5 Bypass t.co in new timeline cards 2018-10-18 21:26:03 +02:00
e53681416f Fix TweetDeck update breaking theme setting in edit-design plugin 2018-10-18 20:44:09 +02:00
acb5e184e8 Update styles for new timeline cards (rounded borders, black theme colors) 2018-10-18 20:36:42 +02:00
bdbafb3e5c Fix rounded borders in media badges and sensitive media overlay 2018-10-02 01:18:43 +02:00
ac70cf87c6 Fix slight misalignments in tweet composer elements when using old icons 2018-09-27 22:03:00 +02:00
93de835ab4 Fix rounded borders in composer (media elements & media description dialog) 2018-09-27 22:01:34 +02:00
2ea38b88ce Release 1.16.1 2018-08-28 21:11:31 +02:00
2c4f2be57d Support system font in notifications, but revert to Arial everywhere by default 2018-08-28 21:06:53 +02:00
fa4beea425 Reset script cache when holding Shift during browser reload 2018-08-24 16:34:55 +02:00
7a976edc82 Ignore cached files for updates and viewed images if their contents are empty 2018-08-23 20:08:34 +02:00
bb22c35221 Fix broken context menu options for images in DMs
Closes #238
2018-08-23 19:51:43 +02:00
ff3dc59016 Move 'View image in photo viewer' handling to TwitterUtils 2018-08-23 19:32:20 +02:00
2e4dd3df3e Fix column icon alignment & clear icon not being hidden for certain columns 2018-08-18 14:55:08 +02:00
b82e5d33f9 Add .column to data-td-icon attribute selectors (very minor optimization) 2018-08-18 14:41:30 +02:00
65d56b336b Release 1.16 2018-08-17 20:09:15 +02:00
7836d61173 Rewrite hovered column/tweet detection again to improve consistency & performance 2018-08-17 16:13:09 +02:00
898437720b Refactor and optimize the plugin configuration file 2018-08-17 11:09:07 +02:00
d9a80d1085 Make it easier to modify custom values for column width and font size 2018-08-17 08:09:28 +02:00
ab3d8b0ae2 Load edit-design plugin configuration dialog lazily 2018-08-17 07:53:43 +02:00
b865074c32 Mark DMs as read when replying to them 2018-08-17 07:02:22 +02:00
2b8ca77c0d Add a safeguard and a TODO to DM notification fix 2018-08-16 20:36:12 +02:00
fa8b4e1e7f Remove broken feature that showed missed notifications after reload 2018-08-16 20:33:31 +02:00
a310c5bcc1 Fix a broken workaround for DM notifications not showing if the convo is open 2018-08-16 19:53:48 +02:00
b5dccd6b91 Merge branch 'master' of https://github.com/chylex/TweetDuck 2018-08-15 15:55:23 +02:00
c2bcb39b38 Fix broken hover color on column filter icons 2018-08-15 15:55:02 +02:00
4515add0a0 Update README.md 2018-08-12 16:06:38 +02:00
5b6aaec48c Reorganize installer batch files 2018-08-12 15:56:14 +02:00
810e56ca31 Update CefSharp to 67 (pre01) 2018-08-12 15:36:48 +02:00
5bcc8ac2e0 Remove ITweetDeckBrowser 2018-08-10 03:18:26 +02:00
f5bfb35867 Add a notification script to the debug plugin for testing 2018-08-10 03:14:39 +02:00
9088b8cd07 Finish refactoring context menu structures & fix bugs from previous commits 2018-08-10 00:58:44 +02:00
a7d90dc708 Allow RT & Add to List/Collection dialogs to be smaller
Closes #236
2018-08-09 20:20:08 +02:00
5968b57a01 Add account context menu items to follow notifications 2018-08-09 20:07:51 +02:00
94946a9ed6 Refactor context menu link handling 2018-08-09 19:55:53 +02:00
561aec5ef0 Refactor highlighted column/tweet variables 2018-08-09 18:13:45 +02:00
e31696d843 Update README.md 2018-07-31 14:30:05 +02:00
8b33fd2002 Update README.md 2018-07-31 14:23:25 +02:00
064e43750f Rewrite PostBuild into F# with optional compilation for performance 2018-07-31 12:48:07 +02:00
29d2f4f681 Why the fuck is Copy-Item file exclusion not working 2018-07-31 03:01:15 +02:00
c11b40b94a Tweak appearance of list-related notifications 2018-07-31 02:33:51 +02:00
e874e1d798 Refactor StringBuilder usage 2018-07-30 01:58:23 +02:00
01244ec632 Replace td-example-notification attribute with .td-example body class 2018-07-30 01:49:38 +02:00
671657e2b0 Remove user & system config properties from Program class & fix field visibility 2018-07-29 10:16:29 +02:00
dff7278e2e Refactor zoom config events & fix notification zoom not updating when outside TweetDeck 2018-07-29 09:41:52 +02:00
0881328636 Release 1.15.2 2018-07-27 22:13:16 +02:00
a951c3a7c9 Tweak Options visuals (labels, spacing, colors) & fix off-by-pixel bug on high DPI 2018-07-27 19:29:21 +02:00
b25fae54fe Update PostCefUpdate script to remove x64/AnyCPU from .targets file 2018-07-27 05:59:27 +02:00
a87bc4609e Add a cue text to Sounds tab in Options 2018-07-27 05:32:17 +02:00
3e68026949 Redesign Options to reduce amount of tabs and organize code 2018-07-27 05:12:17 +02:00
78d6d285cd Remove GDI charset from fonts in designer files 2018-07-26 20:17:53 +02:00
72e3596a3c Tweak heading font in Options and fix minor issues (tab order, overflowing text) 2018-07-26 19:37:07 +02:00
643ebcaab4 Prompt restart after profile import/reset only if an updated option requires it 2018-07-25 22:01:37 +02:00
52ef6cd95a Unify configs (backups, behavior, error handling) & make future development easier 2018-07-25 17:25:20 +02:00
4aec2f3260 Refactor FileSerializer to attempt error recovery & tweak StreamWriter usage 2018-07-25 03:38:44 +02:00
73549515eb Remove old user configuration fix 2018-07-24 06:16:52 +02:00
d83d2660cf Initial Options dialog refactoring to use an event for restart requests 2018-07-24 04:04:44 +02:00
8de913172c Minor visual fixes (old column icon alignment, TweetDeck reply triangle bug) 2018-07-23 20:14:54 +02:00
4acfd64cff Update xUnit 2018-07-23 20:13:04 +02:00
b56f90ed52 Add README instructions to fix broken MSBuild task 2018-07-23 14:20:07 +02:00
1375630727 Safeguard ResponseFilterBase in case input stream is null
Closes #234 (hopefully)
2018-07-23 12:37:57 +02:00
668825bc01 Add option to toggle touch adjustment & disable it by default
Closes #230
2018-07-23 01:36:49 +02:00
90414ae579 Release 1.15.1 2018-07-13 08:28:44 +02:00
41c8caa2a4 Make avatars in DM recipient list square 2018-07-12 15:23:01 +02:00
15305ecabe Revert TweetDeck style changes to the 'Add column' dialog
Closes #233
2018-07-12 08:37:44 +02:00
50090effd4 Refactor image/video download code and fix video download error message 2018-07-10 18:31:39 +02:00
6dffdcd1ed Add update notifications outside TweetDeck, tweak changelog styling 2018-07-10 08:50:09 +02:00
db9daf2714 Refactor update notification into a bridge object & tweak installer pre-downloading 2018-07-10 08:17:42 +02:00
502ac4ebc1 Enable high DPI awareness for video player process
Closes #232
2018-07-09 18:14:58 +02:00
3f44f3bab4 Fix notification scrolling issue by disabling threaded scrolling & latching
Closes #221
2018-07-09 17:47:20 +02:00
746eba185b Release 1.15 2018-07-09 14:06:13 +02:00
82dec92510 Fix wrong screenshot width on high DPI 2018-07-09 14:06:07 +02:00
2edc93df47 Update CefSharp to the newest build 2018-07-09 13:23:13 +02:00
c5a6738199 Fix a missing 'Clear columns' button after re-enabling the clear-columns plugin 2018-07-09 13:20:58 +02:00
dfd578165b Revert custom throttling, it is not ready due to issues w/ notifications 2018-07-09 13:04:55 +02:00
285d400f69 Refactor ScriptLoader to cache files on its own & change method signatures 2018-07-09 12:22:29 +02:00
47a3444ace Remove unnecessary styles and tweak formatting in black theme CSS 2018-07-09 10:21:44 +02:00
0ec227da52 Redesign Edit CSS dialog, add 'Open Dev Tools' button and better editor 2018-07-08 12:57:22 +02:00
f9cf582306 ... 2018-07-08 10:09:44 +02:00
b0883229bf Move HasDevTools to BrowserUtils 2018-07-08 10:06:40 +02:00
31b5d9a4c0 Protect code.js from uncaught errors & add custom 'throw if' statement 2018-07-08 09:53:42 +02:00
6841a78556 Rewrite clear-columns plugin to work w/o reload & fix broken Clear icon 2018-07-08 08:09:29 +02:00
9d2089a8ce Extract markup from introduction.js into an imported file & fix import regex 2018-07-08 06:14:33 +02:00
6d6bb79199 Massive JS formatting refactoring 2018-07-08 06:09:51 +02:00
937c8e22c4 Force UTF-8 in VS Code workspace settings 2018-07-08 05:16:31 +02:00
34c8d44dfe Project tweaks (make update installer on build, stopwatch resource swap, formatting) 2018-07-08 05:03:30 +02:00
2dbf778e56 Change the new DM limit behavior b/c older DMs don't load when scrolled to bottom 2018-07-06 22:09:09 +02:00
873fe2b393 Remove legacy HW acceleration support check & update tooltip 2018-07-06 09:37:25 +02:00
0c53bc6f32 Move icons and images to a separate folder 2018-07-06 07:26:22 +02:00
fa407e150b Add a quick way to import profile from the login page
Closes #228
2018-07-06 07:03:39 +02:00
4e52102c5c Add option for browser throttling w/ default when covered by larger window 2018-07-06 06:26:50 +02:00
0355a5c646 Add VS Code workspace file & fix some warnings 2018-07-06 06:04:10 +02:00
377d9c3554 Make an interface for dialogs to avoid hard-coding them in FormManager 2018-07-06 05:30:12 +02:00
20b1b3c895 Add more safety checks to code.js 2018-07-05 09:05:20 +02:00
41bbe7c51b Decrease amount of loaded DMs to lower performance hit from re-opening DMs 2018-07-05 07:39:26 +02:00
27fa6aefd3 Improve CSS minification 2018-07-05 06:32:45 +02:00
a63c3232da Update CefSharp to 67 (early) 2018-07-05 06:30:41 +02:00
146908a115 Include files for importing in the project to let VS detect changes 2018-07-04 17:27:49 +02:00
48b0f35fee Move template plugin HTML and CSS into a separate file 2018-07-04 17:24:32 +02:00
6c435ebe26 Extract styles from update.js into an imported CSS file 2018-07-04 07:03:48 +02:00
b8d0b721a2 Extract styles from introduction.js into an imported CSS file 2018-07-04 07:00:47 +02:00
a6d5957f46 Extract styles from twitter.js into imported CSS files 2018-07-04 06:55:10 +02:00
9e5f676e23 Move imported files into structured folder for easier handling & tweak syntax 2018-07-04 06:53:31 +02:00
414f3a1f9d Import shared plugin setup code into individual environment files 2018-07-04 06:26:41 +02:00
fedf9c60ee Hack up a simple import system in the build process 2018-07-04 06:25:22 +02:00
b6385d9622 Change unit test namespaces 2018-07-04 03:07:45 +02:00
76d25a712d Move old IO test project in preparation for future refactoring 2018-07-04 03:07:15 +02:00
2c6d935273 Rewrite unit tests for CommandLineArgs into xUnit 2018-07-04 02:45:11 +02:00
830d98a101 Rewrite unit tests for TwoKeyDictionary into xUnit 2018-07-03 21:23:34 +02:00
d7378bd75a Add leading slashes to URL strings in RequestHandlerBrowser 2018-07-02 20:32:23 +02:00
0f41cb9dbc Add response filter to restore global jQuery & unfreeze TweetDeck resources 2018-07-02 20:27:54 +02:00
77bc922d93 Add base class for response filters to safely modify intercepted files 2018-07-02 19:57:24 +02:00
c91b635132 Fix TDPF_createCustomStyle's remove function depending on jQuery 2018-07-01 16:51:57 +02:00
e5521de34a Split icon design files & update 256x256 icon 2018-07-01 15:45:33 +02:00
7c0f8d0f24 Remove temporarily forced update checking 2018-07-01 03:10:47 +02:00
f60d5f650f Add new program and tray icons to show when notifications are muted 2018-06-30 21:28:41 +02:00
ca67f2fe0a Redo app icon background as vector & update all icons 2018-06-30 19:39:16 +02:00
16cce8be1d Remove unnecessary code for disabling TweetDeck update notification 2018-06-30 15:02:46 +02:00
770619d948 Make update notification work without jQuery & optimize slide animation 2018-06-30 14:30:45 +02:00
cfedb7d6b1 Improve logged error message when update checking fails 2018-06-30 13:22:37 +02:00
738557b3a2 Add exception type that adds (potentially sensitive) details to error log 2018-06-30 13:20:25 +02:00
38b01deec1 Make it easier to freeze TweetDeck resources in the future 2018-06-30 11:49:46 +02:00
1a31e69ec9 Release 1.14.4.1 2018-06-29 19:56:00 +02:00
e065983c95 Delay the apocalypse (freeze TweetDeck JS resources, force update checking) 2018-06-29 19:41:02 +02:00
30a169171a Apparently deserializing @TryMyAwesomeApp sometimes causes a hidden crash 2018-06-29 17:10:11 +02:00
8d1900362e Update PostCefUpdate script to remove invalid build configurations (x64, AnyCPU) 2018-06-29 17:05:53 +02:00
e154189de1 Cleanup TweetDuck.csproj and fix names in TestResult unit test 2018-06-28 14:06:03 +02:00
b0f147de24 Fix misaligned 'Add column' icons when using old icon design 2018-06-28 08:10:48 +02:00
979b3548db Release 1.14.4 2018-06-28 07:54:38 +02:00
05d6c578b3 Move InjectedHTML unit tests to xUnit and rename Inject method 2018-06-26 11:19:44 +02:00
a117559063 Minor formatting tweaks 2018-06-26 10:05:53 +02:00
f87c649b09 Fix Twitter experiment causing crash in notifications and subsequent render corruption 2018-06-26 09:49:37 +02:00
6504dc9184 Add unit tests for Result and a few utility methods & fix edge case in StringUtils 2018-06-24 21:41:02 +02:00
25a8ddffd4 Rewrite and tweak existing Core namespace unit tests into xUnit 2018-06-24 19:29:24 +02:00
fa0f9b89cf Remove 'Release' configuration from UnitTests project 2018-06-24 16:16:11 +02:00
4d00a67891 Add a new F# xUnit test project 2018-06-24 16:09:21 +02:00
bd2c43e1f4 Fix analytics not counting applying ROT13 on non-editable text 2018-06-19 21:35:35 +02:00
c7279eaa34 Fix bug with falsely detecting symlinks in plugins if a file/folder doesn't exist 2018-06-19 21:32:21 +02:00
fd523e552c Symlinks/junctions in plugin folders can go to hell 2018-06-13 22:20:10 +02:00
cb877b8b23 Fix broken desktop notifications for retweets with sensitive media 2018-06-10 23:53:01 +02:00
ed1bee8b89 Release 1.14.3 2018-06-10 20:13:05 +02:00
a8e1492056 Push pin icon to the repository 2018-06-08 10:16:58 +02:00
5587216c01 Fix one more case of breaking overlays (account dialog) 2018-06-07 17:48:52 +02:00
86569261ad Add a visual response when hovering filter icons under column header 2018-06-07 14:52:39 +02:00
4a9049c7aa Fix CSS change in dialog overlays breaking some cases 2018-06-07 14:09:35 +02:00
75d60a8182 Work around browser redirection when dragging links into a scrolling column 2018-06-07 13:23:19 +02:00
14d4dc2ed9 Fix more instances of cut off badges 2018-06-06 16:04:15 +02:00
fd0e1740a5 Rename SetLastRightClickInfo bridge method and make it browser-only 2018-06-06 15:11:07 +02:00
70ca890bef Fix not stripping t.co in notifications when dragging & sometimes when copying 2018-06-06 15:10:52 +02:00
b9318dfd8e Minor visual fixes (Edit List modal, set minimum column width) 2018-06-05 04:25:05 +02:00
995642a719 Add support for 1 or 2 columns on screen in edit-design plugin 2018-06-05 03:48:40 +02:00
d14de4ac9e Lower minimum width of browser window & fix modals breaking in small windows 2018-06-05 02:51:21 +02:00
b7f325a241 Minor RequestHandlerBase refactoring 2018-06-05 02:05:04 +02:00
27c55718c2 Release 1.14.2.1 2018-06-04 13:01:44 +02:00
421ff0654b Temporarily work around buggy notification scrolling 2018-06-04 12:35:34 +02:00
ed947458f9 Update fallback HTML in desktop notifications 2018-06-04 12:09:02 +02:00
9cdb20ba84 Fix several broken column types in 'Add column' modal 2018-06-04 11:02:52 +02:00
d8774b735f Release 1.14.2 2018-06-04 08:59:32 +02:00
adcb42695f Update CEF so I don't have to workaround wrong dev tools version
Closes #220
2018-06-01 22:32:44 +02:00
dd77b5bcbb Revert smooth scrolling fix and rewrite horizontal scrolling fix to avoid column jumping 2018-06-01 21:48:26 +02:00
d2445be155 Fix missing GIF previews in tweet screenshots 2018-05-31 03:07:51 +02:00
10254c8af7 Fix tweet screenshots with Aero disabled by making the window visible
Closes #223
2018-05-31 02:58:25 +02:00
d7e830badf Slightly increase default notification height for larger font sizes 2018-05-31 01:11:17 +02:00
b445a3a9b8 Fix broken reply-account advanced selector arguments after a TweetDeck update 2018-05-31 00:52:08 +02:00
97f42ead66 Make it easier to debug screenshots 2018-05-30 19:50:11 +02:00
03730fafb9 ...because I can't be bothered 2018-05-29 15:34:02 +02:00
0be9465dca Fix column icons being hidden by title with clear-columns or edit-design features on 2018-05-29 15:23:41 +02:00
d7f1df4995 Release 1.14.1 2018-05-16 13:07:47 +02:00
3cb0f90706 Prevent an unlikely crash when showing an error message in context menu 2018-05-16 13:05:59 +02:00
a3e9b15a8a Add 'Apply ROT13' to non-editable selections to allow decoding tweets 2018-05-16 12:40:49 +02:00
00bfa68a57 Fix UI issues after recent TweetDeck updates 2018-05-16 07:48:47 +02:00
c311e24f08 Make it easier to update devtools file after updating CEF 2018-05-15 12:42:57 +02:00
1cdd4e4455 Update CefSharp to 66 (early) to fix memory leak 2018-05-15 10:10:03 +02:00
8078c0081a Add a script to fix browser project references after updating CEF 2018-05-15 10:04:08 +02:00
a867e1fc40 Optimize speed and memory usage of build process for app & installers 2018-05-08 20:20:02 +02:00
61da36ac1c Update README.md 2018-05-08 19:37:06 +02:00
720ca2a018 Update README.md 2018-05-08 19:36:08 +02:00
b39c593552 Update README.md 2018-05-08 06:28:07 +02:00
c808952a45 Update README.md 2018-05-08 00:03:08 +02:00
b468d7a766 Update README.md 2018-05-07 20:03:50 +02:00
28578f60be Include plugin files in the project & update README 2018-05-07 19:35:08 +02:00
92a39e2527 Push debug configuration start arguments (-datafolder TweetDuckDebug) 2018-05-07 18:11:56 +02:00
1bce5e4342 Release 1.14 2018-05-07 17:28:34 +02:00
68f586e104 Fix wrong info in the analytics dialog 2018-05-07 15:42:41 +02:00
d27a66202e Remove "Show this thread" in quoted tweets from notifications and screenshots 2018-05-07 03:01:24 +02:00
07de2f450c Fix broken notification scrolling in CEF 65 properly 2018-05-07 02:43:40 +02:00
3c03726634 Remove old and no longer necessary code from update installer 2018-05-06 22:47:58 +02:00
6fb2643063 Slightly increase installer compression level 2018-05-06 22:24:55 +02:00
5eef6c8196 Kill stubborn app processes when installing updates 2018-05-06 21:18:23 +02:00
829c332e13 Add a global function for mustache injection & fix broken clear-columns mustache 2018-05-05 11:47:19 +02:00
47eec14bca Fix freshly broken GIF previews in notifications 2018-05-04 21:07:33 +02:00
e7ee1d6be7 Revert "Fix broken notification scrolling in CEF 65"
This reverts commit 1029ea5840.
2018-05-04 14:00:41 +02:00
e41b5e5ff7 Replace generated license files with just one and include CEF license 2018-05-04 13:30:54 +02:00
ba1bacd08c Fix minor formatting and method modifier issues 2018-05-04 13:04:23 +02:00
1029ea5840 Fix broken notification scrolling in CEF 65 2018-05-04 11:41:20 +02:00
339eaf0195 Fix button appearance in introduction dialog and some plugins 2018-05-03 16:02:44 +02:00
aa1e1549d8 Fix Twitter's broken Cancel button when logging out 2018-05-03 15:04:09 +02:00
1f8ae9ef80 Update CefSharp to 65 (pre01) and fix blank example notification
* Update CefSharp to 65 (pre01)

* Fix blank example notification on first load
2018-05-03 14:05:56 +02:00
65165de060 Release 1.13.6 2018-05-02 16:27:23 +02:00
485836d2ce Replace about:blank in FormGuide with a dummy page 2018-05-02 15:21:23 +02:00
64c07c14d9 Revert "Update to CefSharp 65 (early) (#215)"
This reverts commit b6a599f8a6.
2018-05-02 15:19:01 +02:00
b6313c2b72 Update CefSharp to 65 (pre01) 2018-05-02 00:05:08 +02:00
58124b5821 Force Chrome UA on TweetDeck and remove -chromeagent argument 2018-05-01 19:31:39 +02:00
b6a599f8a6 Update to CefSharp 65 (early) (#215) 2018-05-01 19:20:13 +02:00
19a6bc0dbd Improve performance of PostBuild.ps1 2018-05-01 16:37:06 +02:00
8cb81d44ee Fix update installer changing uninstaller name and remove /MERGETASKS parameter 2018-05-01 14:36:51 +02:00
22d0a372d8 Add dev tools to the installer as an optional component 2018-05-01 14:30:56 +02:00
988fae75c3 Add a command line argument to use Chrome user agent 2018-04-30 21:02:15 +02:00
a82b0e3622 Release 1.13.5 2018-04-29 20:55:33 +02:00
bc6cacacf9 Fix portable install not recognizing itself after importing login session 2018-04-29 19:00:17 +02:00
03ad1b3cbc Update instructions for reply-account plugin and TDPF_getColumnName 2018-04-29 14:29:33 +02:00
eac300627f Fix broken column names again and make getColumnName accessible to plugins 2018-04-29 13:17:00 +02:00
12525ac386 Fix screenshots with zoom & try to fix rendering issues 2018-04-28 20:36:49 +02:00
7558551859 Add a debug flag to generate individual screenshot frames 2018-04-28 18:15:25 +02:00
a9cce13eef Fix visual inconsistencies with new icons 2018-04-28 15:34:15 +02:00
5bb2c43dd0 Fix edit-design plugin not loading when enabled after a restart 2018-04-28 15:22:55 +02:00
5b1dcc88cc Make Plugins form always show Configure button when configurable 2018-04-28 15:19:58 +02:00
5c8fc1d136 Fix clear-columns plugin button disappearing when adding/removing columns 2018-04-27 21:13:38 +02:00
82c2ab3448 Fix broken smooth and horizontal scrolling after a TweetDeck update 2018-04-27 19:33:32 +02:00
b05c8d180f Remove UpdaterSettings and fix not restarting the timer after a dismissed update 2018-04-27 19:21:42 +02:00
87109e5d01 Fix a few visual issues with high DPI 2018-04-27 18:29:02 +02:00
be1a809098 Update all forms and dialogs to use the 'Segoe UI' font 2018-04-27 18:06:45 +02:00
ba0e3f1bd4 Continue redesign of Plugins form (tweak visuals, position, and size limits) 2018-04-27 14:59:36 +02:00
27d41e6164 Begin redesign of the Plugins form (layout reorganization, fixes, optimization) 2018-04-27 13:47:29 +02:00
1ce5ddfd98 Rewrite names and descriptions of plugins & update debug plugin 2018-04-26 21:54:40 +02:00
0096a1a4ef Move debug configuration build events to PostBuild script 2018-04-26 15:37:02 +02:00
d2a6560a90 Measure PostBuild script duration and fix formatting 2018-04-25 19:35:57 +02:00
4d7c048139 Remove versions from official plugins and make them only work on one app version 2018-04-25 19:35:04 +02:00
1d78bd2655 Release 1.13.4.1 2018-04-24 18:16:13 +02:00
9250f1907c Quick semi-temporary fix for removed column.isOfType 2018-04-24 18:10:22 +02:00
a63e210b88 Release 1.13.4 2018-04-15 19:59:35 +02:00
06bd65b7f8 Fix wrong behavior when an update is canceled during download & multiple check errors in some cases 2018-04-15 19:01:39 +02:00
b6c17eb05e Remove unused selectors and classes from styles & add a related TODO note 2018-04-15 18:08:37 +02:00
a3d40fdc2b Push a quick utility to detect unused selectors and classes 2018-04-15 18:08:03 +02:00
c064ef7a30 Improve screenshot reliability 2018-04-15 16:24:26 +02:00
762717da1e Move clear-columns plugin nav button next to 'Add column' button & add isClearable safeguard 2018-04-15 14:39:59 +02:00
b7d3758bea Add error handling when checking updates, and remove unnecessary TODO 2018-04-15 14:03:11 +02:00
d20541fd24 Fix clear-columns plugin to hide the Clear button on scheduled & collection columns 2018-04-14 20:28:48 +02:00
2c2f860f26 Fix issues caused by recent TweetDeck update (notifications, column styles, reply account)
Closes #211
2018-04-14 19:40:51 +02:00
d1db3aa673 Remove command line argument for pre-releases & reorganize restart dialog 2018-04-11 10:39:17 +02:00
cedc52cdf5 Move update notification trigger code to TweetDeckBrowser 2018-04-11 10:01:55 +02:00
33f8eafbcf Remove unused VC120 NuGet package 2018-04-11 09:59:10 +02:00
ad45cf8c72 Begin rewriting update checker to run within C# 2018-04-11 09:59:00 +02:00
f99d035621 Add a Result class that acts as an Either monad for a value or exception 2018-04-10 19:45:41 +02:00
f3072caea8 Fix broken element resizing in the Edit CSS dialog 2018-04-07 13:42:36 +02:00
1410974292 Release 1.13.3 2018-04-07 11:56:36 +02:00
44413fa96c Swap order of 'Search in' items in selection context menu 2018-04-07 11:15:14 +02:00
342a4b4067 Minor code formatting tweaks 2018-04-07 03:46:10 +02:00
4356dde92d Fix wrong c# language version setting for Release builds 2018-04-06 16:36:27 +02:00
21e64a18d8 Fix screenshots to work properly with combinations of DPI and zoom settings 2018-04-06 07:28:57 +02:00
5a305a6740 Fix wrong screenshot size when browser zoom is not 100% 2018-04-06 03:05:06 +02:00
44595bad40 Refactor plugin loading and validation 2018-04-05 21:34:35 +02:00
7fc9edc9cb Fix wrong namespace in update event classes 2018-04-05 09:58:52 +02:00
93e191f522 Reorganize hot swap code & add support for hot swapping plugins 2018-04-05 03:34:11 +02:00
8d8355e792 Rewrite PluginManager setup scripts to use a custom array-based dictionary 2018-04-05 02:09:51 +02:00
a5379d290c Add resource hotswap for easier debugging 2018-04-04 23:13:44 +02:00
caea8d4315 Move most of post build event (copying, cleanup) to PostBuild.ps1 2018-04-04 20:05:59 +02:00
24224ab4c6 Increase height of Options form to avoid scrollbars in General tab 2018-04-04 07:43:00 +02:00
4dbc02360c Add context menu options to search selected text in a column or browser
Closes #209
2018-04-04 05:22:41 +02:00
aa7a29af0c Fix combo box issues (closing while opening, minor browser selection bug) 2018-04-04 03:33:14 +02:00
296d0c6199 Fix ScriptLoader showing multiple errors at once sometimes & change error title 2018-04-03 23:46:00 +02:00
812a034e8d Include version header in ScriptLoader files to detect failed installs 2018-04-03 23:44:43 +02:00
e9de789b79 Minor refactoring, including removal of unnecessary enableCustomCSS parameter 2018-04-03 20:49:21 +02:00
cfbc1b9575 Enable custom CSS in screenshots and move styles from code.js to notification.css 2018-04-03 20:48:33 +02:00
e39e85e4dd Add 'td-notification' body class to desktop notifications & update notification.css 2018-04-03 20:26:54 +02:00
3f0b161cd0 Move screenshot height calculation to the screenshot window 2018-04-03 18:26:33 +02:00
ebe3868720 Fix ScriptLoader crash when showing error message from another thread 2018-04-03 18:19:39 +02:00
ffd0f5e986 Rewrite screenshot rendering to fix current and future visual issues 2018-04-03 02:05:22 +02:00
217535a3ba Make td-notification-padded styles available in screenshots 2018-04-03 02:02:15 +02:00
7abfbea2da Fix "Replying to" user link not using black theme in screenshots
Closes #208
2018-04-03 01:10:53 +02:00
86ffeaac9a Remove no longer supported keycap emoji from the emoji keyboard
Closes #207
2018-04-03 00:04:18 +02:00
ab915b7115 Move accounts above hashtags in search results 2018-04-02 23:26:51 +02:00
705b5d38cf Add design files for logo and video player buttons 2018-04-01 19:35:12 +02:00
fc2acb00b3 Add a batch file to build update installer only 2018-03-16 18:50:37 +01:00
5add8a1d0e Move ITweetDeckBrowser and refactor some things 2018-03-16 18:48:41 +01:00
063d3a2637 Remove unnecessary null fallback in SetClipboardText 2018-03-09 14:43:59 +01:00
f1f90a2ee3 Refactor code to avoid nulls (#206)
* Ensure potential nulls have a fallback value & add/remove null checks

* Refactor update check code to avoid nulls

* Refactor ProfileManager exception handling to avoid nulls

* Refactor a few more various classes and fix nulls in ContextInfo

* Force c#7 everywhere and revert usage of newer features from cherry-picked commits

* Remove unused #pragma declaration
2018-03-07 14:37:03 +01:00
ed317a4e46 Refactor VideoPlayer 2018-03-06 21:17:22 +01:00
cca16f3bb1 Release 1.13.2 2018-03-06 18:38:24 +01:00
aba156cb3b Fix typo from refactoring breaking context menu for some links 2018-03-02 06:00:53 +01:00
cd4e4d7095 Fix hashtags and search links being recognized as account links 2018-03-02 05:59:20 +01:00
8fbb639430 Refactor & optimize context menu, send last tweet info only on right-click 2018-03-02 05:24:45 +01:00
d5bf8ec558 Fix missing image/video context menu for tweets that have both media and a quote 2018-03-02 00:15:28 +01:00
b6cff40f1e Warn when checking updates outside TweetDeck & fix visual and unlikely issues 2018-02-28 11:03:55 +01:00
833e42f455 Add a IsTweetDeckWebsite bool to ITweetDeckBrowser 2018-02-28 07:24:40 +01:00
8134843dad Fix background color & twitter.com hooks not applying quickly enough sometimes 2018-02-28 02:34:29 +01:00
1f92d5e633 Remove 'Shift Selects Multiple Accounts' option & fix refocusing after switching account 2018-02-28 01:43:44 +01:00
dc51c0ae85 Remove unnecessary console logging in debug builds 2018-02-26 17:45:56 +01:00
45c79643d6 Why the fuck is TLS 1.2 disabled by default in .NET on some computers 2018-02-25 23:15:00 +01:00
9041bfc627 Tweak search input font size and icon position 2018-02-21 22:17:51 +01:00
0b3b3dd0be Fix a crash when downloading tweet images with no username
Happens when someone accidentally or through dev tools gets to
twitter.com and tries downloading an image.
2018-02-21 19:48:00 +01:00
89e92dab59 Fix middle-clicking on links in desktop notifications not skipping them or stripping t.co 2018-02-19 17:31:28 +01:00
8c168c9ad7 Fix emoji keyboard button size & tweak composer button layout 2018-02-17 12:21:26 +01:00
9f63357a92 Pre-check desktop icon option in installer when not updating 2018-02-17 10:52:19 +01:00
d91b4bd1f3 Release 1.13.1 2018-02-14 18:43:02 +01:00
c0c64f6d62 Remove old TweetDeck installation check from the installer & tweak formatting 2018-02-14 17:45:19 +01:00
1a5d2af779 Decrease post-update analytics report to 8h and increase startup delay to 2m 2018-02-14 17:10:24 +01:00
f40a33192b Revert and fix various changes from recent TweetDeck update 2018-02-14 15:39:27 +01:00
ca4900aff0 Fix 'Manage templates' button after a recent TweetDeck update 2018-02-14 15:17:58 +01:00
56fc9e2d40 Fix black theme issues (mismatched detection & rare bug with wrong notification background color) 2018-02-13 16:51:40 +01:00
d2174c0b69 Fix misaligned avatars in activity columns 2018-02-13 15:59:21 +01:00
9f76754ad3 Force full install from 1.13 to 1.13.0.1 2018-02-13 13:37:30 +01:00
118ceaec35 Release 1.13.0.1 2018-02-13 13:27:52 +01:00
5a57d28a7d Fix crash by checking and downloading for VC++ 2015 in the installer
Closes #205
2018-02-13 13:17:40 +01:00
07af99f862 Fix wrong background color in tweet screenshots when using black theme 2018-02-13 12:42:25 +01:00
59fba7fba0 Fix a hidden crash that prevented desktop notifications from showing 2018-02-13 12:37:11 +01:00
dd4edc4249 Update CefSharp to latest 64 and remove VC++ 2012 2018-02-13 11:28:48 +01:00
856226473a Update README.md 2018-02-13 05:52:03 +01:00
8d1c07d6b2 Release 1.13 2018-02-12 18:48:42 +01:00
c32462cc9e Update TweetDeck color selectors in CSS for black theme 2018-02-12 18:23:16 +01:00
ec94ea3273 Refactor PluginManager to use ITweetDeckBrowser & do some cleanup 2018-02-12 15:26:21 +01:00
41acd8c15b Refactor UpdateHandler to reference ITweetDeckBrowser 2018-02-12 11:35:39 +01:00
155a79f2ec Add ITweetDeckBrowser for refactoring 2018-02-12 11:34:23 +01:00
9197cb9be6 Add support for 'Configure' button to edit-design plugin 2018-02-12 11:26:50 +01:00
03d50c847b Add 'Configure' button to plugins with a configure() method & close dialog afterwards 2018-02-12 10:40:00 +01:00
bf45c40365 Make analytics debugging easier & tweak Counter serialization 2018-02-12 06:13:08 +01:00
679e126194 Reset all analytics counters 2018-02-12 05:41:03 +01:00
50e39164bd Update and add analytics data points & increase report interval to 14 days 2018-02-11 20:01:57 +01:00
cb9f75e968 Refactor AnalyticsFile events and usage 2018-02-11 16:59:02 +01:00
aa7f6cc3b1 Fix loading spinner sometimes being visible before getting replaced 2018-02-10 23:20:34 +01:00
fe601aed41 Redesign favorite/retweet notifications to be more compact and show full text 2018-02-10 13:09:09 +01:00
2282a9df28 Move 'Show this thread' in desktop notifications above media/quotes & fix hover color w/ black theme 2018-02-10 08:29:59 +01:00
2b54627750 Tweak media size and margins in desktop notifications 2018-02-10 07:54:50 +01:00
16051a0d25 Forgot this 2018-02-10 07:13:56 +01:00
66d5f0d790 Refactor IResourceHandler usage 2018-02-10 07:07:11 +01:00
07d29207f0 Restore loading background color and spinner from before the TweetDeck update 2018-02-10 06:50:52 +01:00
a60be2afcc More Visual Studio shit 2018-02-07 21:58:21 +01:00
027f3ee253 Remove recently added follow notification 2018-02-07 03:22:50 +01:00
04774815e4 Fix bad padding in introduction modal 2018-02-07 03:20:58 +01:00
61a73c055b Fix weird alignment of stuff in notification columns 2018-02-07 00:39:05 +01:00
7731534ffc Save some space in edit-design plugin 2018-02-07 00:21:57 +01:00
ed7bf99610 Prevent dev tools from leaking info in all request headers 2018-02-06 21:10:29 +01:00
cbe4272556 Hide unused items in TweetDeck Settings modal (startup notifications, gif autoplay) 2018-02-06 20:43:18 +01:00
8f5e3dfdcc Merge pull request #203 from chylex/cefsharp64
Update CefSharp to 64 & re-enable mp3s in sound notifications
2018-02-06 18:40:23 +01:00
35500c51f1 Allow export/import/restoring system options & refactor Manage Options dialog 2018-02-06 18:35:36 +01:00
629f873bb2 Add a debugger trigger shortcut to debug plugin 2018-02-06 18:25:11 +01:00
a44cb884c4 Fix a crash when restarting after importing/resetting profile & refactor 2018-02-06 17:04:32 +01:00
d5ad1d0daa Fix loading spinners, and links in notifications when using black theme
Closes #202
2018-02-06 04:38:57 +01:00
61ae7e3b6a Fix 'Show this thread' being too close to media thumbnails in notifications 2018-02-06 04:37:45 +01:00
01583e424f Re-add mp3 support in sound notifications 2018-02-06 04:10:03 +01:00
5c0aa1b3da Update CefSharp to 64 (early build) 2018-02-06 04:06:18 +01:00
07391efa70 Fix more visual issues (remove DM reply button background w/ black theme) 2018-02-02 23:13:43 +01:00
b80f1bfc7c Fix more visual issues (remove disabled button border w/ black theme) 2018-02-02 22:31:01 +01:00
ad310db86c Fix more visual issues (profile modal w/ black theme, timeline input shadow) 2018-02-02 21:57:28 +01:00
4ce0122a29 Fix hover/click effects on buttons under reply input box 2018-02-02 19:15:44 +01:00
a8894f7054 Fix visual issues with search input and buttons 2018-02-02 18:01:11 +01:00
1d1515351b Release 1.12.5.1 2018-02-02 16:57:28 +01:00
2a9ddd4468 Fix edit-design modal, black theme quote border, and dark theme scrollbar color 2018-02-02 16:56:06 +01:00
0f9a944775 Square-ify border of reply box & fix notification background 2018-02-02 15:54:22 +01:00
34ee9ebd66 Release 1.12.5 2018-02-02 15:24:19 +01:00
43f632b555 Allow detecting custom edit-design themes in analytics 2018-02-02 15:19:25 +01:00
7cf3f1d32c Add option for the old dark theme in edit-design plugin 2018-02-02 14:59:33 +01:00
e51e87647e Remove unknown property error in FileSerializer & refactor reading 2018-02-02 13:49:10 +01:00
b8aae88b11 Fix broken Shift swap when selecting accounts after a recent TweetDeck update 2018-01-31 00:28:19 +01:00
d06e29db15 Get rid of string.Split in FileSerializer
string.Split is not suitable for potentially very large strings, so this
decently improves memory usage
2018-01-30 15:45:19 +01:00
62449450f3 Release 1.12.4 2018-01-29 08:40:38 +01:00
b290c94635 Fix a video player crash caused by not handling remote commands on UI thread 2018-01-29 08:20:33 +01:00
f909b887d9 Fix screenshot issues (wrong media size and margin, hide "Show this thread") 2018-01-28 21:48:57 +01:00
5cf4843212 Fix broken screenshots of tweets with just one line of text 2018-01-28 21:40:18 +01:00
b3d1e1bfac Fix example notification timer breaking on skip (forward mouse button or Enter) 2018-01-28 20:54:49 +01:00
df47499a28 Update example notification (username, avatar, improve text) 2018-01-28 20:50:18 +01:00
421475ec87 Add a notification and way to follow the new TweetDuck account 2018-01-28 20:22:13 +01:00
29d999b8eb Remove old data collection notification that was shown after updating 2018-01-28 20:05:50 +01:00
acacd9a5e5 Fix reversed button tab order in FormMessage 2018-01-28 19:39:12 +01:00
b81c26f93f Add an option to ignore tracking URL warnings (t.co) 2018-01-28 19:38:40 +01:00
00b212944c Implement top tier account bamboozle scheme 2018-01-28 19:12:32 +01:00
70ba006e4d Reorganize logo file resources and remove about.png 2018-01-26 17:44:41 +01:00
118e0cae62 Compress the logo in the About form 2018-01-26 17:36:15 +01:00
c003bb4e71 Add a way to display the TweetDuck logo in the browser 2018-01-26 17:32:54 +01:00
e9b2fa7603 Release 1.12.3.1 2018-01-26 15:58:17 +01:00
35afaa105d Fix text alignment in the Feedback tab in Options 2018-01-26 15:49:44 +01:00
2e300a7b8f Fix broken stylesheets in notifications after a recent TweetDeck update
Closes #199
2018-01-26 15:43:52 +01:00
f3f5b88550 Refactor resource handler related extension methods 2018-01-22 14:53:38 +01:00
22f491d98a Release 1.12.3 2018-01-22 07:13:50 +01:00
7908c8ebd9 Goddammit VS 2018-01-22 06:54:58 +01:00
e114a93714 Refactor and move BrowserCache, VideoPlayer, and ExportManager 2018-01-22 06:41:20 +01:00
931761600f Move and refactor browser list options a bit more again 2018-01-22 05:17:50 +01:00
e5b4b03e1a Meh 2018-01-21 09:11:40 +01:00
f1e8b3fbf0 Move option for custom program for opening links at the end for better accessibility 2018-01-21 06:33:50 +01:00
4d64243a07 Turn WindowsUtils.Browser fields into get-only properties 2018-01-21 04:25:06 +01:00
3422b4d4d6 Fix height, tab order, and recently broken scroll focus handling in Options 2018-01-21 03:29:11 +01:00
b170d529fd Add an option to disable smooth scrolling 2018-01-21 03:11:12 +01:00
83741db5aa Fix broken smooth & horizontal scrolling with cursor above columns
Closes #192
2018-01-21 01:18:59 +01:00
c4b2b3ab25 Add verbose error logging to video player & tweak Reporter.Log 2018-01-19 23:37:45 +01:00
676df44985 Fix dialog title inconsistencies 2018-01-19 22:29:53 +01:00
037adc6b5c Add a way to select a custom program for opening links
References #185
2018-01-19 20:08:48 +01:00
186d17dd98 Add an option to select an installed browser to open links in
Closes #185
2018-01-19 19:19:40 +01:00
ab9ff980ef Fix dragging twitter links over columns from some sources or w/ url parameters not working 2018-01-19 06:05:46 +01:00
f297cb2623 Add line escaping to FileSerializer for easier manual file editing 2018-01-18 20:37:29 +01:00
b53c672768 Refactor Program.ResetConfig & Program.RestartWithArgs 2018-01-18 10:58:58 +01:00
1a2b967749 Move Chromium data from LocalAppData/CEF to TweetDuck storage folder 2018-01-18 10:47:16 +01:00
6ba30c48cf Remove BrowserUtils.HeaderAcceptLanguage and use default value instead 2018-01-18 10:37:43 +01:00
1af9ee9ced Release 1.12.2 2018-01-17 16:19:28 +01:00
e50480aa35 Fix edit-design plugin modal labels changing margins with different themes 2018-01-16 22:55:21 +01:00
6943c7813f Fix hovering scrollbars not changing their color with edit-design plugin enabled 2018-01-16 22:54:53 +01:00
7c9b4382ca Fix Follow dialog closing when clicking any but the first Follow button 2018-01-16 19:20:14 +01:00
3187f97592 Rewrite 'Keep Like/Follow dialogs open' code after TD removed the old way 2018-01-15 21:25:43 +01:00
b71a367052 Merge pull request #196 from chylex/delet_audio_lib
Remove audio library
2018-01-14 11:13:31 +01:00
2d4bbf2a6f Fix sound notification extension detection and add warning to mp3 files 2018-01-14 00:08:43 +01:00
6e59dfddcc Remove audio library 2018-01-13 23:38:30 +01:00
bd92fc6ee0 Use <audio> for custom sound notifications & allow volume control for default one
Closes #195
2018-01-13 22:59:34 +01:00
2f61de7025 Add GetHandlerFactory extension method to BrowserUtils 2018-01-13 22:37:24 +01:00
8fcec7ec7c Merge remote-tracking branch 'refs/remotes/origin/master' into delet_audio_lib 2018-01-13 19:50:13 +01:00
33d9ba3871 Refactor UserConfig event invocations into a generic method 2018-01-13 19:49:16 +01:00
4f8c778ba0 Ignore errors in automatic cache clearing
Closes #194
2018-01-13 15:35:20 +01:00
804c739038 Fix broken element dragging (timeline tweets and maybe more) 2018-01-13 15:27:07 +01:00
a0445fbb12 Release 1.12.1.1 2018-01-12 12:48:14 +01:00
7ab5d7b796 Release 1.12.1 2018-01-12 12:40:09 +01:00
7f83a7773b Fix 'Show this thread' not working >_> 2018-01-12 12:39:46 +01:00
fc9e8a808f Fix emoji inline tweak not working in DMs 2018-01-11 21:48:00 +01:00
5ab8976bc7 Fix broken links in guide because Chromium is an idiot 2018-01-11 21:06:31 +01:00
e2a28f2811 Allow typing unambiguous :emoji: in inline replies 2018-01-11 20:15:56 +01:00
137a20ed0e Fix notification tooltip not disappearing when skipping/closing without moving mouse 2018-01-11 00:06:18 +01:00
f956f696f4 Limit some $TD functions to browser/notification, change displayTooltip params 2018-01-10 23:53:37 +01:00
bb7cbde38f Make 'Show this thread' in notification open tweet detail
Closes #191
2018-01-10 22:47:50 +01:00
8c452d3fa2 Fix clipboard html styles when copying text from notifications 2018-01-10 22:47:06 +01:00
f65c33c432 Add newly added option to analytics report 2018-01-10 22:17:30 +01:00
da2758ccb1 Add option to keep Like/Follow dialogs open
Closes #193
2018-01-10 14:22:47 +01:00
de10112b7f Remove non-english locale files from the installation 2018-01-03 16:23:56 +01:00
301d4fb171 Replace app locale option with spell check language & use correct lang list 2018-01-03 16:04:11 +01:00
f0a79add14 Fix broken example notification after closing it and then changing options 2018-01-03 14:52:47 +01:00
d33bc9fe25 Release 1.12 2018-01-01 01:48:48 +01:00
a2a5dfd435 Reset the official plugins folder in the update installer 2018-01-01 01:27:58 +01:00
f3d7c8d4c3 Set language header to match app locale 2017-12-31 14:21:21 +01:00
67f60dd787 Merge pull request #190 from chylex/locale_magic
Add options for app locale (remove argument) & target translation language
2017-12-31 14:03:15 +01:00
62310ce4a4 Add an option to set target language for tweet translations 2017-12-31 13:59:19 +01:00
3a27089364 Add an option to set app locale in a new Options tab & remove locale argument 2017-12-31 13:29:38 +01:00
a05460f562 Make BrowserCache.CacheFolder a property 2017-12-31 12:14:45 +01:00
390872c305 Merge pull request #189 from chylex/settings_ui_plz 2017-12-31 10:26:23 +01:00
594d12df79 Reorganize all Options tabs into FlowLayoutPanels 2017-12-31 10:20:42 +01:00
c42c12c72b Move Options tooltips outside designer files & reorder options in code 2017-12-31 08:10:24 +01:00
c37f4fe365 Merge branch 'master' of https://github.com/chylex/TweetDuck 2017-12-31 07:02:20 +01:00
8d37d68770 Fix dragging links not stripping t.co shortener
Closes #183
2017-12-31 06:59:17 +01:00
1b3d1fb36a Tweak readme wording 2017-12-31 05:36:32 +01:00
2f352ef9bb Update CefSharp to 63-pre01 and update documentation 2017-12-31 05:34:36 +01:00
527f3cab4c Fix reinstantiating AnalyticsManager when restoring from tray & test stuff 2017-12-31 05:01:23 +01:00
f67bf27db2 Add an option to automatically clear cache after exceeding a set size
Closes #182
2017-12-30 02:06:36 +01:00
a8bb3ba349 Improve performance and safety of cache size checking & clearing 2017-12-28 04:52:53 +01:00
321ab12213 WTF is AnyCPU doing in the project files 2017-12-28 00:50:51 +01:00
4cab18e557 Merge pull request #187 from chylex/cefsharp63
Update CefSharp to 63 (early build) & delete GC reload
2017-12-28 00:39:07 +01:00
c15ea97a36 Update CEF again 2017-12-28 00:32:26 +01:00
a0cc4109df Delete GC reload in CEF 63 (#186) 2017-12-28 00:03:24 +01:00
f66ff1000a Replace AbstractRequestHandler with CefSharp's implementation 2017-12-21 22:45:29 +01:00
51a9bb6d3c Update CefSharp to 63 pre-release 2017-12-21 20:25:36 +01:00
07017bd29b Fix cut off usernames in Messages column 2017-12-21 20:15:02 +01:00
45b6f49a08 Fix middle-click and ctrl-click handling in CEF 62 2017-12-07 20:07:15 +01:00
103ad72788 Update CefSharp to 62 (early build) 2017-12-07 19:50:43 +01:00
543259f29f Release 1.11.2 2017-11-21 07:07:24 +01:00
98799734c5 Add a box shadow to main menu in the guide 2017-11-21 06:59:54 +01:00
96f491a666 Redo the introduction dialog (add main menu image & follow link, rewrite text) 2017-11-21 06:11:49 +01:00
29e541dbef Fix comment formatting in notification.css 2017-11-20 18:01:17 +01:00
1343b9c113 Fix username alignment (follow notifications & all types in desktop notifications) 2017-11-20 18:01:02 +01:00
94920fd459 Ensure only one guide window is open and fix webkit element outline 2017-11-20 17:44:28 +01:00
b2f3b245b7 Open TweetDuck guide links directly in the app 2017-11-20 17:25:25 +01:00
15bc6c1d73 Fix idiot chromium being unable to figure out window size while loading guide 2017-11-20 17:21:46 +01:00
2c175b8d3a Fix incorrect default config value for notification scroll speed 2017-11-18 15:31:36 +01:00
a48c17a769 Update analytics (fix system edition, add dev tools and other feature collection) 2017-11-18 07:15:18 +01:00
03465c4ab0 Remove dismissed update config entry after accepting an update 2017-11-18 02:53:25 +01:00
b4e936c530 Minor refactoring of notification classes & remove no longer needed CSS 2017-11-14 19:06:05 +01:00
fb1482370a Fix issues from TweetDeck updates (long usernames, badge in detail view, notification media previews) 2017-11-14 18:42:34 +01:00
e831bc2bea Fix broken compose text size in edit-design plugin after a TweetDeck update 2017-11-11 17:32:20 +01:00
c74c168c96 Release 1.11.1 2017-11-10 14:59:48 +01:00
40b53fa40c Fix broken update notification CSS after a TweetDeck update 2017-11-10 14:52:32 +01:00
3481cc0349 Fix broken CSS in browser and plugins after a TweetDeck update 2017-11-10 14:44:47 +01:00
09abd889e9 Add ID to <html> for selector priority 2017-11-10 14:14:50 +01:00
330bbfbb31 Fix broken CSS in notifications after a TweetDeck update 2017-11-10 13:35:29 +01:00
6b7b690476 Remove Log method from the bridge object 2017-11-09 21:43:22 +01:00
cea72801a7 Fix video player process sometimes taking too long to close 2017-11-09 19:29:01 +01:00
04369e22a7 Add option to disable animated avatars (general GIF toggle)
Note: this breaks when disable-extensions is used, so it was changed to
disable-pdf-extension instead
2017-11-09 18:18:22 +01:00
f1b16eab9a Add a global function (including one for plugins) to reload columns 2017-11-09 18:15:58 +01:00
13646d9c90 Increase quality of media previews in desktop notifications 2017-11-09 17:08:39 +01:00
17d762ce91 Make spell check option not require a restart 2017-11-09 16:51:40 +01:00
edb40adaa1 Release 1.11 2017-11-09 14:40:09 +01:00
bc0809994a Fix import & restart overwriting some imported settings such as zoom, add a TODO for system config import/export 2017-11-08 12:23:58 +01:00
a3e3d517b0 Fix high DPI for dialogs displayed before the main window 2017-11-08 11:39:32 +01:00
d8b63a54ca Update the guide 2017-11-08 10:56:05 +01:00
b81e7583eb Implement feature usage analytics 2017-11-08 08:15:45 +01:00
51f9ba3642 Add option to always show character count to edit-design plugin 2017-11-08 06:40:36 +01:00
296626f7c7 Implement a notification about previously enabled anonymous data sending 2017-11-07 21:37:41 +01:00
5b2daf9746 Refactor method order and return types in config file classes 2017-11-07 18:36:52 +01:00
9a6b615174 Cleanup FileSerializer Write/Read calls & change exception for empty files 2017-11-07 18:10:38 +01:00
18f8d5b269 Tweak key format in analytics request 2017-11-04 14:07:24 +01:00
2867a875c9 "Fix" an uncommon video player crash when closing short videos
Closes #177
2017-11-04 14:03:28 +01:00
ee2f5ae8cb Disable default TweetDeck update notification 2017-11-04 03:30:50 +01:00
bd5c301fb9 Refactor old icon style declaration in edit-design and add !important 2017-11-02 16:45:26 +01:00
6df68629f7 Implement system config export/import/reset (without UI) 2017-11-02 13:34:51 +01:00
be08fd4445 Remove UserConfig.ZoomMultiplier and old migration code 2017-11-02 13:20:19 +01:00
6f1afb94fb Add a global function to reprioritize events and refactor stuff 2017-11-02 11:02:00 +01:00
7401b8a52d Fix search input not being unfocused after opening search externally 2017-11-02 10:10:04 +01:00
c83b62ebaa Make searching while holding Ctrl or middle-clicking search icon open search externally 2017-11-02 09:46:33 +01:00
108cf8923e Implement analytics (#176)
* Implement analytics report generation w/ user config and basic system info

* Add HW and plugin info to analytics report generation

* Add a way of displaying the full analytics report

* Fix issues in analytics report and include design theme

* Ensure tab config is saved when switching tabs

* Fix compilation error in TabSettingsFeedback and safeguard nulls

* Add locale to analytics report

* Work on analytics (utils, last collection label, dependency refactoring)

* Add analytics state file and implement sending reports every week

* Send an analytics report after each update
2017-11-02 03:08:43 +01:00
4e26fd9d56 Random refactoring 2017-11-01 04:02:44 +01:00
8c9168a4bf Temporarily move notification with Alt and restore by middle-clicking title bar
Closes #175
2017-10-31 23:40:56 +01:00
97da0b79e4 Fix compile error after removing TimerBarHeight constant 2017-10-31 23:34:00 +01:00
d7e5f6876b Increase notification timer height with higher DPI 2017-10-31 23:10:08 +01:00
1b92b112e2 Refactor FormNotificationBase.GetBorderStyle 2017-10-31 12:50:34 +01:00
ca55119531 Rewrite CanMoveWindow and CanResizeWindow in notification classes 2017-10-31 12:36:46 +01:00
d9da14b5dc Make the example notification its own class 2017-10-31 12:01:53 +01:00
512b5666ac Fix 'Escape' key not clearing notification tweet queue & refactor HideNotification 2017-10-31 11:31:01 +01:00
64977964e8 Bump project versions 2017-10-31 10:53:42 +01:00
2bc13e0de6 Remove subprocess dependency on communication lib & remove Comms class 2017-10-31 10:51:33 +01:00
b90c5f17cf Fix misaligned Plugins form controls when reloading plugins after scrolling down 2017-10-31 07:18:38 +01:00
7d8d0bd43b Refactor awful plugin loading and management code 2017-10-31 07:13:17 +01:00
Alexander
54c1137927 Fix warning about possible null reference in audio playback error event (#174)
* Fix V3083 warnings from PVS-Studio Static Analyzer
2017-10-29 19:32:18 +01:00
e6655219ee Add a context menu item to apply ROT13 to text selection in inputs 2017-10-29 04:39:28 +01:00
5896f8e35a Allow only px values in custom items in edit-design plugin 2017-10-25 22:12:28 +02:00
934cba7251 Fix link expansion & tooltips not working with long domains (ellipsis in front)
Closes #172
2017-10-25 19:23:02 +02:00
9cc1a11bef Fix a rare issue where GIFs open in browser due to a missing source URL 2017-10-23 00:25:23 +02:00
c1bc956d6d Move TrayIcon to a different namespace 2017-10-21 15:46:52 +02:00
351b174b86 Refactor TweetDeck browser into a separate class 2017-10-21 15:35:46 +02:00
0b4aaf80dc Remove unnecessary usings and code 2017-10-21 14:53:50 +02:00
c10c185817 Release 1.10.3 2017-10-19 00:43:06 +02:00
327ef1cbee Restart TweetDuck if user declines UAC when updating and improve error handling 2017-10-19 00:30:37 +02:00
15eb823c7f Replace OpenExternalBrowserUnsafe with the new OpenAssociatedProgram 2017-10-19 00:24:40 +02:00
54613e5242 Handle errors when opening images in associated viewer 2017-10-19 00:21:53 +02:00
df1352cbe3 Update README.md 2017-10-18 23:50:23 +02:00
0559afd972 Make middle-click tweet actions work in detail view 2017-10-18 16:50:34 +02:00
afffca020e Add context menu item to view images in photo viewer 2017-10-18 13:50:20 +02:00
d663cc3f64 Add username to default video download filename & tweak playback error message 2017-10-17 19:39:35 +02:00
110d41e393 Add function to trigger video playback for plugins 2017-10-17 13:16:19 +02:00
1a8823f592 Fix clipboard html stripping crashing with no text data
Closes #171
2017-10-17 11:22:38 +02:00
6374a852b0 Merge branch 'master' of https://github.com/chylex/TweetDuck 2017-10-16 16:37:24 +02:00
a10c7dd7c3 Fix README heading links 2017-10-16 16:36:30 +02:00
547c7ea417 Update README heading links (still bork on GitHub) 2017-10-16 16:33:41 +02:00
760607995a Update README and post build log message 2017-10-16 16:30:55 +02:00
4704197c09 Add option for larger quote font size to edit-design & update modal layout 2017-10-16 15:38:19 +02:00
093ac1ac40 Make middle-click instant quote work with reply-account plugin 2017-10-16 09:32:52 +02:00
9ed8b0d904 Fix used account in updated middle-click actions 2017-10-15 23:48:56 +02:00
7346ce370d Add middle-click actions (reply popout, RT quote, fav modal) & fix popout in temp columns 2017-10-15 22:59:56 +02:00
adefdadc19 Fix border-radius when quoting a tweet 2017-10-15 22:29:44 +02:00
703bce2d00 Fix PostBuild.ps1 errors not causing failed build & refactor 2017-10-13 13:09:50 +02:00
97928ecd84 Check emoji-ordering.txt for carriage return on build 2017-10-13 12:54:15 +02:00
be9ea7f64a Fuck everything about gitattributes 2017-10-13 12:39:03 +02:00
ec2aaa8789 Add exact emoji name match detection to emoji keyboard 2017-10-13 12:20:54 +02:00
ab14b72526 Force LF in emoji-ordering.txt and other txt/js files 2017-10-13 12:16:39 +02:00
d8e304f3c1 Release 1.10.2 2017-10-12 13:01:47 +02:00
ea53ce361f Bump edit-design plugin version 2017-10-12 10:58:12 +02:00
2fce80b347 Fix custom font size in edit-design & time font in example notification 2017-10-11 20:42:42 +02:00
373c0b1cc3 Fix separator before 'Open dev tools' in example notification context menu 2017-10-11 20:34:50 +02:00
e5e1b7e608 Fix TweetDeck being unable to detect OS name 2017-10-11 20:33:39 +02:00
7e9221c9e0 Merge branch 'master' of https://github.com/chylex/TweetDuck 2017-10-11 20:24:09 +02:00
6b849f854e Rewrite font size & theme handling, add <html> attributes to notifications 2017-10-11 20:22:32 +02:00
831f6bc744 Add support section to readme 2017-10-10 12:25:52 +02:00
d282a7a537 Fix border-radius in media upload previews 2017-10-08 05:43:54 +02:00
fb2f1e3031 Release 1.10.1 2017-10-08 05:27:38 +02:00
00a0da3df3 Fix detail view screenshots & tweak screenshot timings for iframes separately 2017-10-08 01:42:53 +02:00
8c447b1ffb Maybe fix dragged tweet links not being recognized from certain drag sources 2017-09-29 18:04:44 +02:00
a4841175e8 Fix more border-radius, "Ready to Tweet" alignment, and docked reply close icon position 2017-09-29 16:52:39 +02:00
9b139132a1 Disable reply middle-click in temporary columns & fix random reloads from middle-clicks 2017-09-29 14:58:58 +02:00
4a404ecabc Fix weird button styles in inline replies in modal dialogs 2017-09-29 12:48:00 +02:00
aee758b559 Update reply-account docs & fix error with temporary columns and advanced selector 2017-09-29 12:47:13 +02:00
be060d0386 Add new info about emoji usage to the guide 2017-09-28 17:29:34 +02:00
0195378c10 Release 1.10 2017-09-28 17:09:46 +02:00
bc804c6a53 Allow linking to items in the guide 2017-09-28 13:33:54 +02:00
76b15f1971 Fix wrong emoji names & issues with emoji keyboard on while using :emoji_name: 2017-09-28 01:00:03 +02:00
c4d43c9d5b Fix bugs in :emoji_name: update (character count & duplicate keyboard modal) 2017-09-27 23:56:12 +02:00
e8d3e530de Update guide with a screenshot of the main menu 2017-09-27 17:08:51 +02:00
e145adec58 Bump emoji keyboard plugin version 2017-09-27 14:55:08 +02:00
e2dad3e477 Automatically convert :emoji_name: in emoji keyboard plugin
Closes #160
2017-09-27 13:25:05 +02:00
27bdbde171 Optimize images and scripts in the guide 2017-09-25 21:39:04 +02:00
e9ec27169c Improve guide for small width screens and screen readers 2017-09-25 21:12:47 +02:00
2e24cb634c Add favicon and meta tags to the guide 2017-09-25 19:47:00 +02:00
beb9046055 Add a button to send feedback / bug report to Feedback tab in Options 2017-09-25 17:23:16 +02:00
e57301952c Rewrite introduction text and update styles 2017-09-25 15:59:19 +02:00
7411279e48 Update guide URL 2017-09-25 14:22:19 +02:00
16acfa85b5 Bump project and plugin versions 2017-09-25 14:21:51 +02:00
41ef37f3f0 Fix GIFs in detail view not having the pointer cursor 2017-09-24 23:41:39 +02:00
00d8538726 Fix middle-clicking GIFs not opening the tweet externally
Closes #169
2017-09-24 23:35:31 +02:00
6eeb3f9895 Fix error when hovering a quoted tweet with a video 2017-09-24 23:22:20 +02:00
d19dca6ea5 Add a (currently unimplemented) anonymous data collection option 2017-09-24 15:31:44 +02:00
2008ccdaa4 Add an introduction modal & guide 2017-09-23 23:33:06 +02:00
ba2e62de3a Remove a warning in the reply-account plugin's default config 2017-09-23 10:17:55 +02:00
2b62eb254d Fix quoted tweet and media border radius in notifications & screenshots 2017-09-21 13:39:51 +02:00
31f72b7957 Move browser CSS to a file & optimize CSS injection 2017-09-21 13:19:21 +02:00
fdc4616875 Minify CSS files after build 2017-09-21 10:50:13 +02:00
b7de261d25 Move notification CSS to a file, refactor FontSizeLevel & TweetDeckBridge props 2017-09-21 10:34:57 +02:00
ae78a5a026 Fix screenshots of reply threads 2017-09-21 09:50:48 +02:00
fd2cf5d4d7 Make memory cleanup detect recent activity and fix modals 2017-09-21 09:06:55 +02:00
9f0997be1a Refactor some JS 2017-09-21 08:51:41 +02:00
dbade7f854 Fix crash when clicking video overlay after a playback error 2017-09-17 19:06:12 +02:00
3cdc1e190a Fix configuration.default.js files being minified on build 2017-09-14 19:40:41 +02:00
36bede7211 Tweak build instructions (wording and formatting) 2017-09-12 23:06:40 +02:00
46689bb700 Rewrite build and setup instructions 2017-09-12 22:51:59 +02:00
13e1a6543c Release 1.9.2 2017-09-12 20:29:40 +02:00
820ce9e845 Fix uploading files with uppercase extensions 2017-09-11 18:27:39 +02:00
f17806f4e8 Allow dragging tweet links over columns to open them in detail view
Closes #167
2017-09-10 23:03:03 +02:00
3f5ffc9e10 Fix t.co bypass when middle-clicking links in tweets 2017-09-09 19:16:08 +02:00
aeb0842ab4 Move system tray options to a separate tab 2017-09-08 11:36:34 +02:00
38837ae84c Add option to open search columns before the first column 2017-09-07 15:22:14 +02:00
a4eb6935af Fix 'Theme color tweaks' changing color of some scrollbars it shouldn't change 2017-09-06 12:57:43 +02:00
52f1f4c4eb Release 1.9.1 2017-09-05 22:55:32 +02:00
6c1782a038 Fix some twitter links (/signup, /tos, /privacy) having context menu for accounts 2017-09-05 22:43:07 +02:00
8b8f5f5473 Fix login page links opening in the app instead of external browser 2017-09-05 22:32:11 +02:00
61d3ed891a Update t.co bypass to work for media and bio urls 2017-09-05 21:47:22 +02:00
b1abf87320 Revert TweetDeck scrollbar color & fix notification scrollbar with 'Theme color tweaks' on 2017-09-05 14:55:29 +02:00
9aedfc2799 Fix scrollbar in Options not disappearing when switching tabs while animating 2017-09-04 17:52:01 +02:00
ad6240a067 Refactor delegating multiple events at once in code.js 2017-09-04 03:02:30 +02:00
9539eb076a Fix heart icon size and animation 2017-09-03 01:45:59 +02:00
c808e7bd83 Fix calling OpenExternalBrowser from non-UI threads, causing crashes or errors 2017-09-02 21:49:45 +02:00
13ea388f5e Fix upload dialog to include 'All Supported Formats' instead of separating them 2017-09-02 20:51:58 +02:00
c46dc0f1a3 Fix 'Open link in browser' not bypassing t.co 2017-09-02 20:30:54 +02:00
2ae311007d Make https scheme check first because https rocks 2017-09-02 13:54:54 +02:00
9344e02bff Add a privacy warning when opening a t.co link in case the bypass fails 2017-09-02 13:47:43 +02:00
40ad836fc3 Bypass t.co on click & fix right-clicking t.co links in notifications 2017-09-02 13:07:40 +02:00
e8604a261d Replace 'new EventArgs()' with 'EventArgs.Empty' 2017-09-01 14:34:23 +02:00
2a41d21a29 Add unit tests for UserConfig 2017-08-31 21:38:26 +02:00
4c62aa067b Update unit test generated file names 2017-08-31 19:45:56 +02:00
49db3074c6 Rewrite IO handling in unit tests 2017-08-31 19:33:24 +02:00
f5e3b34f30 Tweak border radius on inputs in column settings and custom timelines 2017-08-31 16:03:38 +02:00
f0affa4aec Implement 'Save all images as...' for images in quoted tweets 2017-08-31 15:34:05 +02:00
4f5075ac54 Fix 'Save image as...' usernames in quoted tweets & more refactoring 2017-08-31 15:26:03 +02:00
20f0445b10 Replace FormNotificationBase.ChirpId with CanViewDetail that checks ColumnId too 2017-08-31 14:52:38 +02:00
c77c974455 Rename more parameters and fields in TweetDeckBridge 2017-08-31 14:40:10 +02:00
44397b2d45 Fix parameter name in TweetDeckBridge 2017-08-31 14:07:46 +02:00
943d4d4d72 Release 1.9 2017-08-30 21:40:11 +02:00
6468c03465 Fix 'Restore Defaults' not resetting plugin status and import/reset not closing Plugins form 2017-08-30 21:34:12 +02:00
8141a5a5c5 Fix TrackBar labels being above focus cues 2017-08-30 21:14:58 +02:00
26a1779310 Fix 'Restart with Arguments' including disabled data folder message in shortcut 2017-08-30 21:00:59 +02:00
45d18ffafe Set volume slider SmallChange to 1 and increase width of video player volume slider 2017-08-30 21:00:11 +02:00
5f1c30609c Fix typo in error message in FileSerializer 2017-08-30 20:34:02 +02:00
7266d705d3 Fix video player UI for small videos & increase FormBrowser min size 2017-08-30 19:15:45 +02:00
ee6bb782d6 Tweak the download icon in video player 2017-08-30 16:59:01 +02:00
8ae6e2c886 Bump project and plugin versions 2017-08-30 16:51:53 +02:00
dd3a0d3890 Tweak emoji warning message 2017-08-30 14:57:36 +02:00
8d8e2da57e Make Enter key in emoji search insert the first available emoji 2017-08-30 14:53:43 +02:00
e60d204302 Refocus tweet input after closing emoji keyboard via icon & remove unused code 2017-08-30 14:50:39 +02:00
3d642d8ad2 Tweak emoji search to only select query on click and refocus it after clicking emoji 2017-08-30 14:48:43 +02:00
8db6e8a090 Remove unused custom emoji keyboard code 2017-08-30 14:09:32 +02:00
8153fcde85 Minor refactoring 2017-08-30 13:35:47 +02:00
96469cfca5 Rewrite config reload & fix some options breaking after import or reset 2017-08-30 12:53:10 +02:00
7601645c12 Fix some config options not being committed before opening Manage Options 2017-08-30 12:41:54 +02:00
c28615d548 Add options to reset session and plugin data when restoring defaults 2017-08-29 14:28:33 +02:00
b515add94e Rewrite browser/plugin reload handling when importing a profile 2017-08-29 14:26:42 +02:00
9fd5e9443d Make 'Manage Options' dialog close options after a successful operation 2017-08-29 14:22:20 +02:00
b2ddb1fab2 Disable 'Tray Highlight' option when the icon is disabled 2017-08-29 00:02:41 +02:00
fdac42947c Only activate parent form in video player if the player window itself is active 2017-08-28 23:41:46 +02:00
eeaf6949c5 Delay 'View detail' if the website is reloading 2017-08-28 22:46:06 +02:00
d7ad62d476 Make TweetNotification use persistent column ID 2017-08-28 22:38:11 +02:00
cd87a329fc Add a network error notification if the device goes offline
Closes #145
2017-08-28 22:14:07 +02:00
8c0d306823 Rewrite sound notification hook to be hopefully more reliable 2017-08-28 20:05:49 +02:00
d5c3ea0862 'View detail' errors now ask user if they want to open the tweet in a browser 2017-08-28 19:55:10 +02:00
83c962a7a4 Add support for icons in alert/confirm/prompt JS functions 2017-08-28 19:40:32 +02:00
40ef9a42dd Fix unsealed classes 2017-08-28 18:46:14 +02:00
868af5ac6a Goodbye, sweet rant 2017-08-28 18:19:32 +02:00
625227d0ce Rewrite audio library & add notification volume option for WMP impl 2017-08-28 18:16:13 +02:00
064627961e Fix zoom option label overlapping the slider 2017-08-28 17:16:04 +02:00
de0321cb2d Tweak video player label rendering & add label to volume slider 2017-08-28 15:31:27 +02:00
0d71a33b28 Add close/download/fullscreen buttons to video player 2017-08-28 13:31:19 +02:00
6d779f17b3 Fix video player tooltip going outside Form bounds 2017-08-28 10:37:18 +02:00
05510d7bc1 Add tooltip to seek bar in video player 2017-08-28 10:36:53 +02:00
8e162fe031 Add a custom tooltip to be used for video player controls 2017-08-27 23:13:39 +02:00
7ea7366a43 Change default CultureInfo in video player 2017-08-27 23:12:52 +02:00
445e6fcec0 Make Escape key in video player exit fullscreen or close the player 2017-08-27 21:03:57 +02:00
42f4d97d5d Rewrite key handling in video player 2017-08-27 20:46:10 +02:00
6357708533 Finish implementing 'View detail' context menu option in notifications
Closes #152
2017-08-27 20:11:56 +02:00
59c9801437 Address code analysis and remove unused code 2017-08-27 18:48:54 +02:00
d691bef1fb Add video context menu items and update video service check 2017-08-27 18:23:50 +02:00
442d74d0cb Refactor context menu handling and make adding new types of context easier 2017-08-27 18:18:30 +02:00
588bb9a093 Refactor FormNotificationBase to store TweetNotification instead of copying data 2017-08-27 13:40:49 +02:00
380e580d65 Fix cut off badge icon in notifications in notifications 2017-08-27 13:35:02 +02:00
4e306661f8 Fix cut off badge icon in notifications 2017-08-24 14:45:20 +02:00
9f3f33da93 More power! 2017-08-23 07:28:08 +02:00
69cd96a37c Add 'View detail' context menu item in notifications (currently loaded tweets only) 2017-08-22 11:59:34 +02:00
1293a2a533 Harness the incredible power of return-if statements 2017-08-22 10:10:46 +02:00
d24b7bbcb9 Implement return-if transpiler for JS files 2017-08-22 09:51:27 +02:00
b55b47b689 Refactor postbuild js/html processing script 2017-08-22 09:48:03 +02:00
c4c032b4d5 Bump TweetDuck.Video project version 2017-08-22 08:16:28 +02:00
970cd21964 Move TweetDuck.Video project folder 2017-08-22 08:13:49 +02:00
8ca9d242b2 Fix tab order in restart dialog 2017-08-22 07:30:17 +02:00
6f0518edcc Disable text input in locale drop-down in restart dialog 2017-08-22 07:22:09 +02:00
e2d15dd7e3 Add a shortcut target field to restart dialog 2017-08-22 07:20:40 +02:00
5c310e8647 Disable data folder in restart dialog for portable installs, and fix up tooltips 2017-08-22 06:24:02 +02:00
01dca0bc66 Fix sensitive media preference being ignored in notification previews 2017-08-22 04:59:55 +02:00
8b54fbdb2f Remove GC reload & threshold option migration code 2017-08-22 03:53:59 +02:00
663d0a633e Remove redundant Config.Save() call in TabSettingsGeneral 2017-08-22 03:44:13 +02:00
ccd5edb0e4 Remove legacy config file upgrade code 2017-08-22 03:23:53 +02:00
c6190db918 Rewrite update event args and update dismissal handling 2017-08-22 03:22:44 +02:00
3d4cec3b22 Remove update code that handles unsupported system check 2017-08-22 02:45:51 +02:00
5ed970b5a0 Remove resx file on FormUpdateDownload 2017-08-21 19:18:12 +02:00
c22934336b Remove Program.VersionFull and refactor plugin version checks 2017-08-21 18:47:26 +02:00
a3a52e0a1c Release 1.8.7 2017-08-21 14:32:15 +02:00
68dca6e3d9 Fix spacebar not toggling video pause when the main app was focused 2017-08-21 14:14:38 +02:00
017f883e0b Disable custom emoji input, fix selection handling and support twemoji font if installed 2017-08-21 13:37:21 +02:00
77b5c95f75 Add basic js minification (trim whitespace and remove single line comments) 2017-08-21 09:41:15 +02:00
9d052c8339 Update close button fix to only affect New Tweet drawer 2017-08-21 02:17:48 +02:00
d67623a657 Tweak follow notification padding in the browser 2017-08-21 01:52:19 +02:00
c740b3dd46 What the fuck are you doing Twitter 2017-08-21 01:35:53 +02:00
2ef5f7f96f Fix border radius on media previews in tweet detail 2017-08-16 18:27:44 +02:00
404568d795 Fix pre-build powershell command causing build error 2017-08-16 18:23:38 +02:00
b5a6337a0c Update custom CSS to work better with recent TweetDeck changes 2017-08-14 17:15:18 +02:00
82170c3fbd Fix sensitive media in notification previews and tweak follow notification padding 2017-08-14 16:12:34 +02:00
e6d6275fcc Work on emoji keyboard contenteditable fixes (selection, focus, editor migration) 2017-08-14 15:37:55 +02:00
97c865a127 Make emoji editor only show after adding emoji, fix minor UI issues 2017-08-14 04:22:13 +02:00
1ff21f0ee0 Make emoji keyboard replace tweet input with one that displays emoji
Closes #146
2017-08-14 00:47:08 +02:00
2a3dca4467 Rewrite video player to use duplex pipe for process communication 2017-08-13 17:52:46 +02:00
d4ecfcceec Tweak DuplexPipe to set key instead of data when separator is missing 2017-08-13 17:31:58 +02:00
ec5d503e4d Make DuplexPipe data serialized as key/value pairs 2017-08-13 17:23:23 +02:00
346391ca2d Remove unused 'using' statements for the billionth time 2017-08-13 16:55:08 +02:00
9074cdf340 Add a hover effect to video player seek bar 2017-08-13 16:46:33 +02:00
2fcf3604a8 Move video player form controls to a different namespace 2017-08-13 16:14:46 +02:00
34e5185fa1 Fuck localized .NET exceptions 2017-08-13 15:53:39 +02:00
e09e0e69ca Fuck browser process when building the project 2017-08-13 15:50:43 +02:00
963c98e588 Move interprocess comms to a separate project & implement duplex pipe 2017-08-13 15:20:04 +02:00
92acb823a4 Implement a duplex anonymous pipe in TweetLib.Communication 2017-08-13 15:14:17 +02:00
b967b1288f Move process communication to a separate project 2017-08-13 13:54:34 +02:00
1db271ce90 Fix spacebar triggering fullscreen in video player 2017-08-13 00:23:08 +02:00
58c64025e3 Fix level 2 lists and links in update changelog modal 2017-08-12 23:52:38 +02:00
643a7a87aa Release 1.8.6 2017-08-12 23:39:41 +02:00
5e9ed5d713 Improve video player startup and ensure it's always closed with the main app 2017-08-12 23:36:14 +02:00
78e492c764 Tweak 'stay open' pin position and tooltip 2017-08-12 20:33:52 +02:00
59c2a3642b Bump version of subprocess exe (should have been done a long time ago) 2017-08-12 19:10:38 +02:00
40ca923745 Cleanup FormPlayer code and set sync timer interval to 15 instead of 10 2017-08-12 19:08:17 +02:00
03af6cecaa Replace 'Stay open' checkbox with a pin icon
Closes #154
2017-08-12 17:49:27 +02:00
3992e447f4 Change tooltip border radius to be almost square 2017-08-12 17:47:55 +02:00
14a9edeb73 Fix various focus issues with video player and fix double-clicking control panel 2017-08-12 15:12:54 +02:00
92f1e9f7ec Make video player progress bar seek on mouse down instead of up 2017-08-12 14:31:46 +02:00
19c294c53e Terminate video player when pressing back mouse button over it 2017-08-12 13:43:53 +02:00
fe88ea5c05 Fix ctrl key not opening gifs externally 2017-08-12 03:37:24 +02:00
c9d551213a Remove license screen from installers 2017-08-12 03:15:51 +02:00
1e86a33ceb Hide video player overlay when video process exits gracelessly 2017-08-12 03:12:50 +02:00
551dd229f5 Make back mouse button hide video player and overlay 2017-08-12 03:04:24 +02:00
5ecf3c4147 Fix video player going past the end of a video when paused near the end 2017-08-12 02:26:52 +02:00
91bb2f4df0 Fix video player control panel not disappearing & improve error handling 2017-08-12 01:02:09 +02:00
ae3a0ae83d Fix crash when trying to update with 'Edit CSS' or 'Edit CEF Arguments' open 2017-08-12 00:05:56 +02:00
63ce7523de Fix oversight from previous commit 2017-08-12 00:01:13 +02:00
9e3b92bfc1 Move PluginManager initialization and move Form manipulation to FormManager 2017-08-11 23:57:44 +02:00
bc1767fb84 Change namespace of BrowserProcesses, MemoryUsageTracker, VideoPlayer 2017-08-11 23:50:16 +02:00
f917096cc7 Refactor plugin execution code 2017-08-11 23:32:47 +02:00
308926a2ae Add video player volume sync with user config 2017-08-11 20:58:37 +02:00
76f2b1a454 Make video player volume slider constant width 2017-08-11 20:20:07 +02:00
d899e4b38b Refactor video player control outside designer for dev convenience 2017-08-11 20:14:45 +02:00
e1422e35cc Add seeking + current time and duration to video player 2017-08-11 16:49:23 +02:00
2c00c6bb81 Expand the video player control panel and add progress bar 2017-08-11 16:21:31 +02:00
7e56ba6408 Make custom video player triggerable in tweet detail 2017-08-11 15:52:20 +02:00
8ceb70e67d Fix back button and context menu handling with a video playing 2017-08-11 15:22:45 +02:00
37d5efef1d Add an icon to TweetDuck.Video.exe 2017-08-11 15:06:38 +02:00
924065c26e Change video play icon color and handle playback errors 2017-08-11 13:59:05 +02:00
58cc7ea10d Add WIP video player for MP4s 2017-08-11 13:27:15 +02:00
f93e275ddf Add a volume slider to video player 2017-08-11 13:22:12 +02:00
06d2a5f715 Make video player pause/unpause when pressing space 2017-08-11 13:20:50 +02:00
3a7455eafe Fix video player cursor & pause/unpause on click 2017-08-11 12:33:34 +02:00
8b676fe6ce Implement video player in TweetDeck 2017-08-11 11:56:19 +02:00
54d12686af Tweak video player UI handling 2017-08-11 11:32:20 +02:00
f231256402 Improve player UI handling (cursor, position, setting owner handle) 2017-08-11 10:31:23 +02:00
410ead66f8 Add video player args and adjust location and size to owner window 2017-08-11 09:36:29 +02:00
c833a810af Add TweetDuck.Video project for video playback 2017-08-11 08:22:12 +02:00
50f1336b1d Tweak headings in update changelog renderer 2017-08-10 16:33:45 +02:00
60ed0b8cde Release 1.8.5.1 2017-08-10 16:25:55 +02:00
cc55a81c1b Remove emoji-instructions.txt during an update 2017-08-10 16:25:49 +02:00
f832e04e9e Remove unnecessary resx files and cleanup csproj 2017-08-10 15:09:15 +02:00
fc760b9a0c Fix another case in duplicate DM notifications 2017-08-10 13:46:13 +02:00
9addff0521 Exclude emoji-instructions.txt from build 2017-08-10 13:45:32 +02:00
dcaa3aab19 Work around duplicate DM notifications and rewrite recent tweet check 2017-08-10 00:51:38 +02:00
628785c68c Move _postbuild.bat to an MSBuild target directly in the project file 2017-08-10 00:20:58 +02:00
a5aa396fda Fix image quality setting not working in columns with large previews 2017-08-09 18:48:36 +02:00
f53a9f05e3 Fix image download filename for avatars and add more unit tests 2017-08-07 14:48:20 +02:00
7749b14156 Increment emoji keyboard plugin version 2017-08-06 20:32:13 +02:00
c15f339718 Fix emoji keyboard not disappearing after pressing ctrl+enter to tweet 2017-08-06 20:31:44 +02:00
775f590bfa Release 1.8.5 2017-08-06 15:58:28 +02:00
76408ea56f Increment verison of edit-design and emoji-keyboard plugins 2017-08-06 15:58:23 +02:00
a391d8ee83 Fix image pasting allowing more than 1 image in DMs 2017-08-05 21:52:38 +02:00
48c38f6e1d Include tweet author and quality in image download filename 2017-08-05 21:32:07 +02:00
37c5fba162 Change text color of sound notification file option for invalid paths 2017-08-05 19:50:30 +02:00
23e99b1d44 Update GC memory threshold defaults, also GC reload is enabled by default 2017-08-05 19:42:10 +02:00
8432240a47 Update HW acceleration & GC reload tooltips to note they won't be exported 2017-08-05 19:37:14 +02:00
a4bab743d6 Remove notification warning in GC reload option tooltip 2017-08-05 19:34:20 +02:00
60766789ab Move GC reload options to SystemConfig 2017-08-05 19:27:20 +02:00
ca014f881c Rewrite unknown property handling in FileSerializer 2017-08-05 19:23:42 +02:00
886eabe26c Show notifications that were missed during a browser reload 2017-08-05 18:43:57 +02:00
65b7167b5f Rewrite browser reload to save column notification state in session data 2017-08-05 18:36:17 +02:00
abbdde851e Make quoted tweets and RT account selectors square, fix RT account selector heading 2017-08-05 18:30:42 +02:00
54ac54aba6 Add session data that persists across browser reloads 2017-08-05 18:08:22 +02:00
184340f400 Increase delay for clearing recent notifications to prevent duplicates 2017-08-05 17:06:02 +02:00
93dd6813e8 Fix old icon alignment in 'Add column' dialog 2017-08-05 14:44:49 +02:00
b689b08711 Make follow notification button less visible when not hovered 2017-08-05 12:56:18 +02:00
1479a097d6 Fix alignment of old icons on buttons after TweetDeck update 2017-08-05 02:39:23 +02:00
b2be530f6b Remove legacy config file upgrade code 2017-08-01 19:29:01 +02:00
e4967ea46d Add paragraphs and level 1-2 headings to update notification markdown renderer 2017-08-01 17:33:47 +02:00
3f28f18fb4 Release 1.8.4.1 2017-08-01 17:11:34 +02:00
1b90e0f65e Slightly increase default notification height 2017-08-01 17:05:31 +02:00
756ed649e6 Change default avatar shape to square, rename 'Default' to 'Legacy' 2017-08-01 17:03:29 +02:00
fbc423e2a7 Fix like/retweet notifications having invisible space with notification media previews disabled 2017-08-01 16:59:25 +02:00
f04cdb6a13 Fix PropertyBridge not updating properly 2017-08-01 16:58:46 +02:00
63b58b1cfe Release 1.8.4 2017-08-01 15:07:03 +02:00
77e656d8e4 Tweak JS prompt dialog layout on high DPI 2017-08-01 15:06:09 +02:00
a673957bd0 Tweak JS prompt dialog layout 2017-08-01 14:54:21 +02:00
c99a0c9974 Add Layout & Design plugin button to the TweetDeck settings modal 2017-08-01 13:45:44 +02:00
0fb06d0ff2 Remove reply revert option from edit-design plugin 2017-08-01 12:28:52 +02:00
c51eebfe22 Add new unit tests for TwitterUtils and CombinedFileStream 2017-07-31 22:27:02 +02:00
a51b34b48f Move CommandLineArgsParser code to CommandLineArgs 2017-07-31 22:26:48 +02:00
1b239bada1 Delay screenshots again due to iframes 2017-07-31 21:17:31 +02:00
50ab1a6ac3 Improve login/logout page design 2017-07-31 20:29:07 +02:00
f181f1fadc Refactor PropertyBridge 2017-07-31 19:58:23 +02:00
c686349922 Refactor Program (tweak properties, move locking code) 2017-07-31 18:04:04 +02:00
5f44a1f4ad Fix semicolons in code.js 2017-07-31 14:58:42 +02:00
a968938832 Move square scrollbars from edit-design plugin to code.js 2017-07-31 14:55:31 +02:00
8d67f3dfdc Move code.js notification setup and fix dropdown border radius 2017-07-31 14:42:26 +02:00
973ae8cb5d Move twitter account regex to TwitterUtils 2017-07-31 14:31:32 +02:00
a4747b0d7b Add JS dialog handler to notifications 2017-07-31 14:25:00 +02:00
f07640cc84 Reorganize CEF handlers 2017-07-31 14:24:42 +02:00
c235c55b19 Add option to show media previews in notification 2017-07-31 14:12:24 +02:00
485ef684be Prevent notification keyboard controls from triggering in dev tools 2017-07-31 13:36:44 +02:00
7caca22e57 Remove 'TweetDuck' from JS dialog captions 2017-07-31 01:42:22 +02:00
f1d9e32bf5 Add keyboard controls to notifications
Closes #153
2017-07-31 01:23:57 +02:00
23d5fa3107 Tweak emoji keyboard border radius and character count width 2017-07-30 23:58:35 +02:00
4e7d8aba1c Improve FormMessage to match MessageBox closer and look better on high DPI 2017-07-30 23:50:24 +02:00
98ba871a71 Fix back mouse button ignoring columns inside User modals
Closes #155
2017-07-30 21:38:38 +02:00
3ff23c0264 Remove unnecessary TweetDeck logo CSS rule 2017-07-30 21:29:02 +02:00
e21f89477b Fix ISerializedObject not being removed from unit tests and csproj file 2017-07-30 21:28:26 +02:00
f177f514f5 Fix column type icons jumping when opening column settings 2017-07-30 21:19:03 +02:00
af30f3b348 Square-ify many elements of TweetDeck (buttons, inputs, dialogs, menus, previews) 2017-07-30 21:15:39 +02:00
82df618429 Fix code.js after refactoring CSS insertion 2017-07-30 21:13:45 +02:00
bb3538e270 Refocus tweet textarea after selecting a different account
Closes #156
2017-07-30 20:36:17 +02:00
71925e1126 Refactor parts of code.js (make code shorter, use 'let') 2017-07-30 20:19:59 +02:00
93c1cbd231 Update SystemConfig to use FileSerializer and migrate old files 2017-07-30 19:54:28 +02:00
894b890fe5 Tweak serialization code and remove ISerializedObject 2017-07-30 19:28:03 +02:00
8e9e8f7fad Fix magic number and add a comment 2017-07-30 19:02:30 +02:00
2a0461a76f Add safeguards for accessing TweetDeckBridge.LastHighlightedTweetImages 2017-07-21 12:43:10 +02:00
85f923a6fc Add StringUtils.EmptyArray and use it instead of new string[0] 2017-07-21 12:37:30 +02:00
b35e4d4d01 Add "Save all images as..." context menu option for tweets with multiple images 2017-07-21 12:14:15 +02:00
cb24a859f4 Fix file type description in Save image dialog 2017-07-21 11:16:47 +02:00
b1ef00746f Hide open/copy link context menu items for media previews 2017-07-21 11:07:40 +02:00
aebe82e3a7 Add context menu for image previews that use background-image 2017-07-21 10:46:28 +02:00
7c87856b4d Show waiting cursor while taking a tweet screenshot 2017-07-20 16:29:39 +02:00
d1b1dd539f Add an option to use :orig image links in context menu 2017-07-17 05:39:59 +02:00
55eea88ace Add twitter image link & download methods to TwitterUtils 2017-07-17 05:10:06 +02:00
a70f64e1f6 Move some stuff from BrowserUtils to a new TwitterUtils class 2017-07-17 02:09:20 +02:00
fa0cb120a7 Add a 'Close' button to the modal dialog in the template plugin
Closes #143
2017-07-13 05:57:12 +02:00
e3080d07dc Ensure plugin config exists after first run, fixes profile export crash
Closes #147
2017-07-13 05:21:22 +02:00
34726c533e Release 1.8.3 2017-07-09 20:17:33 +02:00
4a0d72d2cc Fix FormMessage icon position on high DPI 2017-07-09 17:24:01 +02:00
fe3fc5c9f7 Add WindowsUtils.CreateDirectoryForFile and use it 2017-07-09 14:12:27 +02:00
441228e2b0 Stop using BrandName in msg dialogs, update msg titles, fix mistakes from prev commits 2017-07-09 04:21:33 +02:00
7538aee4f2 Replace all MessageBox.Show calls with FormMessage 2017-07-09 03:50:04 +02:00
acf809268e Add many helper methods to FormMessage 2017-07-09 03:45:35 +02:00
4ebc0c10b6 Forgot something! 2017-07-09 02:55:48 +02:00
a453888ca2 Tweak new lines in FormMessage, add ControlType enum for FormMessage buttons 2017-07-09 02:40:37 +02:00
530b44762b Make \n the only new line character in FormMessage 2017-07-09 01:52:44 +02:00
f85587fb0b Bump emoji keyboard plugin version 2017-07-09 00:36:22 +02:00
edb8799b1a Update emoji keyboard w/ emoji 9.0, instructions, and code tweaks 2017-07-09 00:30:03 +02:00
e47aeb37f0 Designer, why 2017-07-08 20:19:22 +02:00
776e9968dc Fix tab order in Advanced tab in Options 2017-07-08 19:25:20 +02:00
1898bf4731 Add a tooltip to browser GC reload checkbox 2017-07-08 19:21:36 +02:00
78df020737 Add a modal with release info to update notifications
Closes #139
2017-07-08 18:00:00 +02:00
b93f9a4b9a Fix compose textarea not being focused after pasting an image in a reply 2017-07-08 03:17:20 +02:00
748b230ef5 Fix missing BrowserProcesses in project file after merge 2017-07-08 02:55:45 +02:00
deb8dde9e1 Merge pull request #141 from chylex/memory
Merge browser process identification & GC reload with memory threshold
2017-07-08 02:50:03 +02:00
dbb2f10754 Update from master 2017-07-08 02:49:21 +02:00
0ded03ab92 Fix more analysis violations (exceptions, native method pointers, form disposal) 2017-07-08 00:21:41 +02:00
2198e84f3b Fix subprocess NativeMethods to use pointers instead of value types 2017-07-07 23:58:45 +02:00
14d44528b0 Fuck CultureInfo some more and fix analysis violations (dispose pattern, lang features) 2017-07-07 23:53:04 +02:00
eb8159ca0f Add a tooltip to text box in the Sounds tab in Options 2017-07-07 23:49:57 +02:00
9811f40a53 Go fuck yourself CurrentCulture and stop messing with string interpolation 2017-07-07 22:56:36 +02:00
8de7e13aa3 Reorganize and refactor UserConfig and PluginConfig 2017-07-07 19:22:33 +02:00
c63e6a1e49 More refactoring (seal classes, fix names and comments) 2017-07-07 16:15:10 +02:00
5a21d2cb10 Add StringUtils with unit tests and use it 2017-07-07 15:52:13 +02:00
424c0e596c Add legacy config detection and replace UserConfig serialization with FileSerializer 2017-07-07 02:56:02 +02:00
d431b63c27 Add SingleTypeConverter and update names in FileSerializer 2017-07-07 01:47:14 +02:00
38c2781cd3 Add an enum test to FileSerializer unit test 2017-07-07 00:53:19 +02:00
796fb348a3 Add classes for serializing objects to/from text files 2017-07-07 00:48:00 +02:00
71b306d5fd Fix unit test project file after refactoring 2017-07-06 21:26:43 +02:00
4c610ea32d Move TweetDeck URL into a constant 2017-07-06 20:58:40 +02:00
4bff006743 Refactor (move files into different namespaces) 2017-07-06 20:58:06 +02:00
1645079bc0 Allow plugins to modify screenshot css and include a 'td-screenshot' body class 2017-07-06 03:47:59 +02:00
9afb58e4a7 Remove unused 'using' statement 2017-07-06 03:30:15 +02:00
2820fc8acf Fix some modals not closing when pressing the back button 2017-07-04 22:01:33 +02:00
4d77a498f6 Add a WIP memory tracker that runs GC reload, and fix config 2017-07-04 22:00:03 +02:00
d77de3bb12 Remove debug code 2017-06-30 23:53:36 +02:00
29e7ad6ce6 Add a way to track browser process IDs 2017-06-30 23:46:52 +02:00
1712b5120e Merge remote-tracking branch 'refs/remotes/origin/master' into memory 2017-06-30 20:47:22 +02:00
06c0153cf5 Fix tray restoration from another process if the original process is hung 2017-06-30 20:44:39 +02:00
44f7ecda6d Merge remote-tracking branch 'refs/remotes/origin/master' into memory 2017-06-30 20:17:21 +02:00
fb94bf1b80 Add WindowsUtils.IsChildProcess to check process parent 2017-06-30 20:14:49 +02:00
4818652582 Add current PID into WindowsUtils.CurrentProcessID and use it 2017-06-30 17:07:37 +02:00
c69b9784fc Add option to enable GC reload with a custom memory threshold (currently unused) 2017-06-30 16:47:31 +02:00
0ac244a3ea Merge remote-tracking branch 'refs/remotes/origin/master' into memory 2017-06-30 00:00:33 +02:00
19a445fdab Add a NumericUpDown control with a text suffix 2017-06-30 00:00:20 +02:00
c90a18a2c0 Merge remote-tracking branch 'refs/remotes/origin/master' into memory 2017-06-29 23:47:00 +02:00
502310c413 Prevent TrackBar from stealing focus when scrolling 2017-06-29 23:34:00 +02:00
6f9424d4ec Force GC cleanup when clicking 'Reload browser' 2017-06-29 18:21:09 +02:00
bb379fe667 Expose gc() in JS 2017-06-29 04:01:50 +02:00
0fd86bf214 Move CEF argument setup to BrowserUtils 2017-06-29 03:52:55 +02:00
29b75d4391 Release 1.8.2 2017-06-29 02:25:07 +02:00
a7124e5449 Fix FormMessage not scaling well with high DPI 2017-06-29 02:21:39 +02:00
a714f3480a Refactor Program.Restart and UpdaterSettings initialization 2017-06-28 23:09:49 +02:00
c10e0df898 Fix user profile modals not closing with back mouse button 2017-06-27 22:05:03 +02:00
fba734fd5a Fix new lines not being recognized in CEF argument parser 2017-06-27 20:28:55 +02:00
27e2372097 Add an option for custom notification window size 2017-06-27 17:54:52 +02:00
7f5b99495c Move some code (config, notifications, settings) 2017-06-27 16:49:37 +02:00
1efe2a56af Fix Win+Arrow and Win+Shift+Arrow not saving new window location
Closes #135
2017-06-26 20:23:32 +02:00
850873aec8 Add a delay to notification duration option to fix example notification flicker
Closes #136
2017-06-26 19:40:20 +02:00
d9e6afbf36 Replace Math.Round calls with a more convenient custom method 2017-06-26 17:33:07 +02:00
7f3bd2715c Enable high DPI support and fix all known DPI-related UI issues
Closes #99
2017-06-26 17:08:51 +02:00
c81cb393e9 Allow scroll speed option to use increments of 5 instead of 25 2017-06-26 16:22:52 +02:00
4800faa783 Fix a race condition crash in update checker events 2017-06-24 12:10:25 +02:00
1087b5e1d1 Release 1.8.1 2017-06-24 11:49:25 +02:00
5dc2e71976 Update timeline-polls plugin to work better with recent TweetDeck update 2017-06-23 22:01:54 +02:00
28eb7d0810 Fix scrollbar in Options not working after focusing certain controls 2017-06-23 19:01:09 +02:00
c641a92d89 Add a 'Remind me later' button to update notifications 2017-06-22 12:32:06 +02:00
3e57cc045f Redesign update notification buttons 2017-06-22 12:31:48 +02:00
c60a8ddf66 Rewrite and begin redesigning update dialog 2017-06-22 09:48:50 +02:00
8becef3e45 Reorganize Notifications tab in Options 2017-06-22 08:31:24 +02:00
3237634e3b Tweak button positions in Options 2017-06-22 08:11:59 +02:00
2a4a659e39 Make retweets lowercase again 2017-06-21 20:35:32 +02:00
fcaa47c0a8 Move 'Custom' notification location option 2017-06-21 17:52:02 +02:00
68ea17ccbd Move some notification options into a separate panel 2017-06-21 13:59:49 +02:00
80308c7102 Redesign and cleanup parts of Options 2017-06-21 11:27:56 +02:00
6f4a99a7cb Remove drop down button from follow notifications 2017-06-21 10:13:40 +02:00
ac245f5128 Fix retarded scroll bar behavior in Options 2017-06-21 09:07:50 +02:00
7f9e9e27a0 Move import/export/restore to a single dialog in FormSettings 2017-06-21 08:33:36 +02:00
abf58a4aec Add an option to change notification scroll speed 2017-06-21 05:56:10 +02:00
5d9a700a6e Move TrackBar value alignment code to ControlExtensions 2017-06-21 05:52:40 +02:00
3662b1eb1d Fix activated & notification icons in edit-design plugin
Closes #134
2017-06-21 04:59:08 +02:00
34449da2b8 Redo Options tabs to fit the new layout 2017-06-20 20:42:17 +02:00
42f367f822 Fix mouse wheel not working in Options form 2017-06-20 20:40:37 +02:00
781ca0bb77 Tweak Options form design 2017-06-20 18:37:46 +02:00
ebe5d50dae Remove TabButton and TabPanel 2017-06-20 16:36:29 +02:00
00d6dc5626 Rewrite Options form to have tabs on the left 2017-06-20 16:29:50 +02:00
a1648c307f Remove (now) redundant call to plugin panel resize event 2017-06-20 14:35:54 +02:00
b8f170ae39 Fix visual issues in Plugins form 2017-06-20 13:02:34 +02:00
073f1da5b4 Redesign Plugins form 2017-06-20 11:58:33 +02:00
6310711136 Add a vertical label control 2017-06-20 11:48:44 +02:00
fcac7a4ce1 Remove unused 'using' statement 2017-06-20 09:50:31 +02:00
c2b1aef810 Show official & custom plugins on one page 2017-06-20 09:50:11 +02:00
c8ab26275c Fix column list overflow after hiding app title 2017-06-19 23:32:15 +02:00
a1fd6a2b6b Bump edit-design plugin version 2017-06-19 23:15:51 +02:00
561c08e0cc Fix debug plugin after hiding app title 2017-06-19 23:06:21 +02:00
4658e30e89 Fix Twitter messing with retweet icon in edit-design plugin 2017-06-19 23:05:14 +02:00
225e6b369a Make selected theme an attribute in <html> for plugins 2017-06-19 16:18:48 +02:00
0e9094a19f Rewrite timeline-polls plugin (fix issues, support quoted tweets) 2017-06-19 16:18:42 +02:00
2da0e03c6c Fix emoji keyboard search not resetting when hiding the keyboard 2017-06-19 11:05:33 +02:00
9a6fac5fc8 Add extra height to tweet footer with old icons in edit-design plugin 2017-06-17 18:44:25 +02:00
b541f0a896 Redesign header (hide logo, tweak positions & border radius, fix compose btn icon) 2017-06-17 18:24:52 +02:00
cec7cce077 Fix dynamic column width in edit-design plugin for recent TweetDeck update 2017-06-17 17:40:18 +02:00
fb13695ca5 Release 1.8 2017-06-16 19:56:57 +02:00
20c76d06f7 Fix templates not triggering textarea change 2017-06-16 19:43:48 +02:00
339a11f649 Remove empty line in unit test code 2017-06-16 19:43:25 +02:00
0989400d87 Fix column header icon position with old icons in edit-design plugin 2017-06-16 18:32:19 +02:00
52aacf602d Tweak formatting of edit-design plugin 2017-06-16 18:30:36 +02:00
54d70a6a17 Fix edit-design plugin to revert icons in notifications too 2017-06-16 08:03:22 +02:00
d980e09e0f Fix {ajax} token resolve order in template plugin 2017-06-16 04:22:44 +02:00
2e4cb12817 Add template name check and safeguard empty ajax token in template plugin 2017-06-16 03:50:16 +02:00
7b91cb2e96 Fix escaping and update modal text in template plugin 2017-06-16 03:34:30 +02:00
95c04a8abc Work on template plugin (keep button active, tweak code) 2017-06-16 02:04:44 +02:00
25822fefdb Add option to revert icons to edit-design plugin 2017-06-16 00:40:23 +02:00
d800ee2d28 Fix template plugin to handle combining {ajax} and {cursor} together 2017-06-11 01:05:15 +02:00
2a51371aca Add {ajax} token to template plugin 2017-06-11 00:25:42 +02:00
ee5d1a47dc Add {cursor} token to template plugin 2017-06-10 23:26:04 +02:00
b330b74347 Implement token reading in template plugin 2017-06-10 23:25:55 +02:00
11fa13f0bb Tweak google analytics detection to work on twitter.com 2017-06-08 18:43:46 +02:00
21400d72b3 Block TweetDeck's google analytics script 2017-06-08 18:39:40 +02:00
a710cb9d4f Make middle click on tweet reply icon open the compose drawer 2017-06-08 14:17:46 +02:00
3326ad52ce Work on template plugin (basic template impl, modal tweaks) 2017-06-07 19:25:38 +02:00
c9560df851 Implement template management, buttons, and persistency in template plugin 2017-06-07 17:22:31 +02:00
74cb45118e Force tweet actions to stay visible when replying in edit-design plugin 2017-06-07 14:41:39 +02:00
c79bf19e51 Add a section with advanced tips to template plugin and update layout 2017-06-07 00:34:27 +02:00
961bec0a2f Add basic layout and styles for the template plugin 2017-06-06 23:23:06 +02:00
89e4977cd1 Bump version of emoji keyboard plugin 2017-06-06 11:11:57 +02:00
bfe16475db Tweak update installer to explicitly list needed files instead of excluding unneeded ones 2017-06-06 04:41:17 +02:00
915d36867c Fix emoji keyboard position and leaking outside the window 2017-06-06 03:27:30 +02:00
48435af407 Add emoji search 2017-06-05 23:07:31 +02:00
86b6ec5212 Tweak emoji names to only use one array when loading file 2017-06-05 21:56:43 +02:00
775e70bc45 Rewrite HTML generation in emoji-keyboard & add emoji names to elements 2017-06-05 21:54:39 +02:00
9f565447d0 Remove emoji characters from emoji-ordering.txt 2017-06-05 19:35:41 +02:00
88d27bc29d Add instructions for updating the emoji order file in emoji-keyboard plugin 2017-06-05 19:32:04 +02:00
172ae87ac6 Merge pull request #129 from chylex/remove_legacy
Remove legacy code (plugins and installers)
2017-06-05 17:49:19 +02:00
91d572235e Make $id and $token properties in plugin objects unmodifiable 2017-06-05 17:28:45 +02:00
64d32dcb75 Delete plugin properties when disabling them 2017-06-05 17:28:01 +02:00
564b4283b6 Rewrite plugin reloading when enabled/disabled and refactor core plugin scripts 2017-06-05 14:49:34 +02:00
ca4d374a81 Fix errors in edit-design plugin when disabling/enabling 2017-06-05 14:43:14 +02:00
a753806d7b Fix 'Edit CEF arguments' restart prompt and tweak dialog text 2017-06-03 18:03:34 +02:00
bd1692cea3 Rename Settings to Options 2017-06-03 17:24:43 +02:00
b7ce089f08 Fix audio playback error leading to the wrong tab in Settings 2017-06-03 17:03:33 +02:00
8a6b47c5db Fix naming and tooltips in profile export/import UI 2017-06-03 16:37:23 +02:00
9f1fc4df18 Decrease compression level in update installer 2017-06-03 16:03:31 +02:00
c018a2a7bc Move sound notification handling to a separate library 2017-06-03 15:32:18 +02:00
a1aebab114 Update installers (remove outdated code, exclude .txt files in updater) 2017-06-03 15:30:10 +02:00
e30702e1d8 Move CefSharp license to bld/Resources/ 2017-06-03 13:01:39 +02:00
008ff4b055 Fix edit-design plugin resetting TweetDeck settings on first run 2017-05-31 18:33:21 +02:00
d7bba22e19 Remove legacy configuration for list of disabled plugins 2017-05-31 17:57:55 +02:00
2b9a910533 Remove legacy installation data from installers 2017-05-31 17:17:47 +02:00
118ebcc627 Fix update installer removing devtools_resources.pak 2017-05-28 18:19:37 +02:00
c741767b11 Release 1.7.7 2017-05-28 18:12:34 +02:00
4a09358e14 Make browser process LARGEADDRESSAWARE to fix crash with high memory usage 2017-05-28 03:33:09 +02:00
3f4ea1af08 Remove old plugin config migration code 2017-05-27 13:30:57 +02:00
35bb196832 Refactor file and folder paths in Program 2017-05-27 13:26:02 +02:00
cb5b50dd42 Fix one pixel line between column header and content 2017-05-26 16:34:42 +02:00
8652272526 Fix column header icons not showing tooltips with mouse near their edge 2017-05-26 16:14:02 +02:00
0f32504fde Add tooltips to clear-columns plugin 2017-05-26 15:50:07 +02:00
4735c21fc0 Rename "Reset" to "Restore" in clear-columns plugin and code.js 2017-05-26 15:17:12 +02:00
ecbcbcaed4 Release 1.7.6 2017-05-24 20:31:50 +02:00
1677b73ff8 Add protection to prevent code.js from crashing if Twitter changes something again 2017-05-24 19:21:13 +02:00
5929067a3d Add a function to code.js that checks if an object contains a nested property 2017-05-24 18:52:55 +02:00
d06834617b Remove code that broke after TweetDeck updated
Closes #125
2017-05-24 18:32:28 +02:00
9d048efe06 Cleanup code in TweetNotification 2017-05-24 18:31:42 +02:00
6a379bc2cd Make hardware acceleration setting use a new system config file
Closes #123
2017-05-24 14:06:10 +02:00
9f415b11b5 Fuck you and your stupid cmd line MS 2017-05-24 13:20:41 +02:00
b9b9193222 Push devtools_resources.pak 2017-05-19 19:46:06 +02:00
867c2d1632 Make static regexes lazily initialized 2017-05-19 15:53:26 +02:00
5447afc3f5 Make dev tools work in release if devtools_resources.pak is present 2017-05-19 15:14:46 +02:00
b5e58db242 Rewrite pre-login page skip to be instant 2017-05-19 14:24:51 +02:00
8ab99619d6 Fix project file 2017-05-19 14:24:29 +02:00
4c7660ee65 Restire browser window when the browser process is killed 2017-05-19 13:23:52 +02:00
c1b9bde7b0 Add an abstract request handler 2017-05-19 04:14:58 +02:00
0e8c6c066f Rewrite update system to predownload update installers 2017-05-17 18:21:06 +02:00
9e44a86be0 Make BrowserUtils.DownloadFileAsync return WebClient and delete the file when cancelled 2017-05-17 17:25:44 +02:00
b61479f84f Add WindowsUtils.TryDeleteFolderWhenAble for async folder deletion 2017-05-17 15:09:07 +02:00
e3c709b005 Add success action parameter to BrowserUtils.DownloadFileAsync 2017-05-17 14:19:35 +02:00
b2b3dba504 Make 'Edit CEF' and 'Edit CSS' dialogs not block the browser window 2017-05-17 14:05:23 +02:00
4d05441aa8 Rename root namespace to TweetDuck 2017-05-17 13:00:17 +02:00
419b3ee850 Make browser subprocess a custom project with custom assembly info 2017-05-17 00:54:15 +02:00
4c31e72d29 Release 1.7.5 2017-05-16 21:18:22 +02:00
e3b2ff7f0e Make bottom scrollbar slightly taller when rounded scrollbars are disabled 2017-05-16 21:18:15 +02:00
4c5f5e2cce Fix DM notifications not showing if the conversation is open 2017-05-16 20:23:08 +02:00
39ae9b8ba0 Fix multiple notifications showing for the same tweet in multiple columns 2017-05-16 17:15:02 +02:00
5c7eb0535d Fix formatting and insertRule call 2017-05-16 16:35:46 +02:00
235718390b Move reply icon tweaks to edit-design plugin, fix notification layout 2017-05-16 15:46:27 +02:00
5d4b72f224 Make notifications neater and more compact, fix list notification layout 2017-05-16 15:27:38 +02:00
dc76ae9d1f Make theme-based color tweaks in edit-design plugin optional 2017-05-16 02:13:48 +02:00
e44f4bb003 Move 'optimize animations' option in edit-design plugin into the leftmost column 2017-05-16 02:06:42 +02:00
1fc1370d41 Prevent notification windows from showing in Alt+Tab menu on Win 8/10 2017-05-16 02:04:43 +02:00
80a669c989 Add theme-based tweaks to edit-design plugin (background/scrollbar color) 2017-05-15 15:18:06 +02:00
801c9eba2d Move edit-design focus/blur events to ready() to avoid errors 2017-05-15 15:15:34 +02:00
f9704d2836 Move column container scrollbar to bottom to fit updated TweetDeck style 2017-05-15 15:01:38 +02:00
39687171e9 Close all dialog windows after starting an update download
Closes #120
2017-05-13 16:34:11 +02:00
1d73691ef4 Make edit-design plugin animation optimizations active only when focused 2017-05-13 16:02:29 +02:00
f8678d2515 Add a GetIdleSeconds method to the bridge object 2017-05-13 14:58:30 +02:00
fb108ea18d Release 1.7.4 2017-05-08 22:49:47 +02:00
a7229a0677 Merge branch 'master' of https://github.com/chylex/TweetDuck 2017-05-08 22:38:31 +02:00
d0dd112b98 Update README.md 2017-05-08 22:38:34 +02:00
1e07120eb5 Update CefSharp to 57 2017-05-08 22:21:49 +02:00
fcd1f76cff Fix background color in example notification when not logged in 2017-04-29 01:58:56 +02:00
e400d86d75 Add an option to toggle Shift key switch in account selectors
Closes #119
2017-04-28 20:06:53 +02:00
dca3410a5b Change border style on Windows 8/10 2017-04-28 19:08:10 +02:00
9b314e2953 Make sure the edit-design plugin creates a config file on startup
This prevents an issue when importing a profile where the file was
missing.
2017-04-28 18:35:24 +02:00
5635daf66d Update 'Restore Defaults' button dialog 2017-04-28 18:26:31 +02:00
7e2e1645e9 Move plugin config into a separate file
Closes #121
2017-04-28 17:29:08 +02:00
4990afcdbb Remove plugin.CanRun call from PluginConfig.IsEnabled 2017-04-28 16:18:46 +02:00
c11f36dfef Remove and sort usings in the whole project 2017-04-28 13:51:06 +02:00
abddf61c88 Update TweetDuck codebase to C# 7.0 2017-04-28 13:29:45 +02:00
37fec7e952 Release 1.7.3 2017-04-22 14:27:23 +02:00
0f6a93ae8f Update verified badge style to fix it getting cut off when zoomed in 2017-04-22 00:10:25 +02:00
25eae334b0 Fix usernames being a bit too low 2017-04-22 00:01:50 +02:00
74377d01ce Hide broken tooltips in the TweetDeck menu 2017-04-21 23:54:19 +02:00
6e78ba1e7b Add forward/back mouse button handling to notifications (skip/close function) 2017-04-21 23:21:02 +02:00
39e0dedf27 Make magic WndProc values constants in NativeMethods 2017-04-21 22:42:57 +02:00
bbe2c88802 Fix TweetDeck logo on certain zoom levels 2017-04-21 22:05:55 +02:00
586b31b63e Show notification duration tooltip on the trackbar control too 2017-04-21 22:02:17 +02:00
041abe6d7e Add an option to change the zoom level 2017-04-21 22:01:56 +02:00
a69b3cd05f Fix the NO_HIDE_SCREENSHOTS debug flag not moving screenshot window into view 2017-04-21 21:43:53 +02:00
b48213e79e Fix a weird border in the scrollbar area of the column container 2017-04-21 20:58:39 +02:00
5bbc14aca5 Fix trackbar heights and Display combo box location in Notifications tab 2017-04-21 20:28:11 +02:00
8ccbf502e8 Fix a recent TweetDuck bug with a white bar on the bottom 2017-04-21 20:05:14 +02:00
c426ca97e2 Fix label position in Notifications tab in Settings 2017-04-21 20:02:34 +02:00
d9eef86a8b Increase screenshot timeout interval 2017-04-21 17:48:58 +02:00
3fed921748 Add exception handling to SoundPlayerImplWMP.Play 2017-04-20 19:37:18 +02:00
02827d53a2 Add a reset prompt to Notifications tab if custom location is out of view 2017-04-18 19:05:18 +02:00
1ad5fde9ae Fix broken custom notification location in Settings
Closes #118
2017-04-18 18:29:44 +02:00
909d5ed99c Move the check if a Form is not visible anywhere to ControlExtensions 2017-04-18 18:06:09 +02:00
b9096df218 Release 1.7.2 2017-04-15 23:42:31 +02:00
1137485c55 Fix not remembering position and not closing into tray when moved/closed before browser loads 2017-04-15 16:02:27 +02:00
61b1155a03 Completely rewrite all image pasting code 2017-04-15 14:29:55 +02:00
35624bcb1c Add some comments to code.js 2017-04-14 18:18:36 +02:00
6e262334ed Refactor -1 in hook to NativeMethods.HOOK_HANDLED & include example.html in project file 2017-04-14 14:24:37 +02:00
829d69485a Add an option to skip current notification when clicking a link inside it 2017-04-13 19:42:07 +02:00
0d32c168eb Fix invalid links (such as account names a tweet replied to) being clickable in notifications 2017-04-13 19:31:55 +02:00
38d6d93f65 Fix recently introduced crash when opening dev tools in notification window 2017-04-13 19:10:14 +02:00
9a280492c2 Refactor PropertyBridge and remove empty BaseTabSettings designer file 2017-04-13 18:09:07 +02:00
d49ee79c90 Increase height of Settings window 2017-04-13 17:41:49 +02:00
73b83af6b1 Remove an unused class from notification body element 2017-04-12 14:26:26 +02:00
064673ef23 Rewrite notification handling (better URL and duration handling, remove hacky code) 2017-04-12 00:56:28 +02:00
81bf93e5ab Use custom ResourceHandler for notifications and tweak notification code 2017-04-12 00:08:01 +02:00
26d5a8ce08 Remove debug code 2017-04-11 21:02:25 +02:00
c6f5c8d91f Fix notification duration not counting length of quoted tweets 2017-04-11 20:10:21 +02:00
63a1928468 Update mouse hook to respect nCode value and also to stop further processing 2017-04-10 17:55:50 +02:00
27c2aee8b9 Add tweet queue trimming when it exceeds a set size 2017-04-09 14:59:02 +02:00
5219d29aca Tweak child form handling in FormBrowser 2017-04-09 13:30:25 +02:00
84955352dd Why do I even bother with these comments 2017-04-09 03:23:19 +02:00
8e05c30063 Optimize memory usage of the emoji keyboard plugin 2017-04-08 23:57:07 +02:00
6f98bcafec Remove empty designer files 2017-04-08 14:02:16 +02:00
4afac91bce Release 1.7.1 2017-04-07 00:32:27 +02:00
6e6312f6d7 Fix occasional twitter.js crash due to it loading too early 2017-04-07 00:18:59 +02:00
df4c4e443d Customize login and logout pages 2017-04-06 23:16:58 +02:00
ff40474f92 Fix DM reply input not getting focused after opening a conversation 2017-04-06 18:39:47 +02:00
aca06ee805 Add an option to display column name in the notification title 2017-04-06 18:11:52 +02:00
ba8e29a9f8 Make emoji keyboard bring focus into the input when closed 2017-04-06 18:03:44 +02:00
583da2bd9f Remove an empty designer file for PluginListFlowLayout 2017-04-06 15:21:07 +02:00
0ea07016b4 Fix TabIndex, Margin, and Location of controls in forms and dialogs 2017-04-06 14:57:27 +02:00
616421db9a Remove the 'Include Border In Screenshots' option 2017-04-06 14:29:22 +02:00
293ddacd19 Disable broken TweetDeck metrics 2017-04-06 14:07:48 +02:00
13945ec937 Minor code refactoring (remove TD_APP_READY, remove a TODO, fix formatting) 2017-04-06 13:40:26 +02:00
4ea6f336f8 Fix debug plugin to simulate notifications more reliably 2017-04-05 23:19:49 +02:00
58296aa266 Fix emoji keyboard to append emoji at caret instead of the end 2017-04-05 23:08:52 +02:00
742df9dff3 Make the default theme/column/font settings hidden if edit-design plugin is enabled 2017-04-05 01:06:52 +02:00
011e1f5922 Release 1.7 2017-04-04 21:35:36 +02:00
8f67d69325 Rewrite notification scrolling to work around a strange CEF scrolling bug 2017-04-04 16:50:44 +02:00
9ac133b605 Move TweetNotification.FixedCSS to code.js 2017-04-04 16:46:47 +02:00
538b2d26cd Tweak notification scroll hook to not trigger when main window is focused 2017-04-04 16:43:22 +02:00
cc3895c423 Remove the browser container panel from FormNotificationBase 2017-04-04 05:22:11 +02:00
79454bfc3b Remove 1px border on top of the notification (fixes empty space between scrollbar and the top) 2017-04-04 05:17:00 +02:00
734c7572bb Tweak update installer to take CEF version from the file instead of hardcoding it 2017-04-03 18:51:02 +02:00
9f93fbb161 Fix edit-design plugin modal to not break with a custom font size 2017-04-03 18:21:05 +02:00
c78c63285e Add a "splash screen" (show window immediately and seamlessly display browser when ready)
Closes #114
2017-04-03 18:03:46 +02:00
c8cbf70a28 Optimize notification <head> tag to not include the disabled stylesheet 2017-04-03 03:50:08 +02:00
2b116d6756 Revert "Revert "Update CefSharp to 57.0.0-pre01""
This reverts commit da611153cf.
2017-04-03 03:05:03 +02:00
da611153cf Revert "Update CefSharp to 57.0.0-pre01"
This reverts commit dbfebf6a32.
2017-04-02 20:26:19 +02:00
fd9bf4468a Disable hosting process because it fucks up rebuilding too often 2017-04-02 20:25:50 +02:00
fa234eb9d6 Fix a recently introduced bug that broke the forward mouse button on DMs 2017-04-02 18:51:33 +02:00
dbfebf6a32 Update CefSharp to 57.0.0-pre01 2017-04-02 18:16:44 +02:00
7b91e31485 Update screenshot border setting tooltip to point out possible glitchiness 2017-04-02 18:11:37 +02:00
e882fc8b5e Disable screenshot border setting by default 2017-04-02 18:07:26 +02:00
5a54195cac Add code to help with screenshot debugging 2017-04-02 17:52:07 +02:00
a442adf8d5 Fix screenshot margins for videos and image grids in detail view 2017-04-02 17:50:32 +02:00
38466878db Dispose screenshot windows after using them to save memory 2017-04-02 17:22:31 +02:00
7c86e4e743 Add CEF command line args to disable extensions and plugins 2017-04-02 17:01:57 +02:00
41cbfb8d39 Update design-revert deletion in installers (delete saved config, move from upd to full/port) 2017-04-02 14:45:50 +02:00
7c394f4b20 Update the required app version in edit-design plugin 2017-04-02 03:47:50 +02:00
c1a35e1053 Fix missing border-radius style on some scrollbars in edit-design plugin 2017-04-02 01:48:16 +02:00
cddce8596f Add an option to pause new notifications when idle
Closes #96
2017-04-02 01:27:56 +02:00
c75058b1da Update edit-design plugin to use notification injection instead of notification.js 2017-04-01 19:42:27 +02:00
1a73fcdb39 Allow plugins to inject HTML into notifications before they're shown
Closes #112
2017-04-01 19:27:05 +02:00
8e0c4f5308 Fix post-build events not deleting the 'scripts' and 'plugins' folder when not empty 2017-04-01 19:19:41 +02:00
51e2791cc7 Add InnerValues property to TwoKeyDictionary 2017-04-01 19:14:36 +02:00
130159f06c Add InjectedHTML utility class with unit tests 2017-04-01 18:43:23 +02:00
42d1140b55 Make edit-design plugin reload if the reply revert feature is enabled 2017-04-01 16:23:45 +02:00
dfd987041a Tweak plugin system to not trigger enabled()/disabled() if requiresPageReload is true 2017-04-01 16:22:23 +02:00
790d1787fd Add an option to revert replies in edit-design plugin 2017-04-01 15:58:18 +02:00
2a6a607c7b Reorder checkboxes in edit-design and use sentence case for labels 2017-04-01 13:25:23 +02:00
b3521d2a18 Add basic support for custom values in the edit-design plugin 2017-03-31 18:59:11 +02:00
dee99caa7d Fix some dialogs not having default enter/escape actions 2017-03-30 23:24:00 +02:00
cf525a3929 Fix JS dialogs to focus on a default button (and text input for prompts) 2017-03-30 23:13:57 +02:00
18d658f7e1 Add window.prompt support to JavaScriptDialogHandler 2017-03-30 22:35:49 +02:00
1c42ab77d8 Fix up FormMessage again (increase leftmost btn margin, use client width, fix calculations) 2017-03-30 22:26:43 +02:00
33d5638bb0 Add a fallback sound notification impl if WMP is unavailable 2017-03-30 01:55:07 +02:00
8ce92df87a Make sure SoundNotification doesn't release null COM references 2017-03-30 01:06:42 +02:00
80654449af Fix a crash in an event handler in the edit-design plugin 2017-03-29 19:23:28 +02:00
eee1622801 Fix TweetDeck not showing previews for youtu.be links with https 2017-03-29 18:18:09 +02:00
4c54876ecf Increase the delay before taking a screenshot 2017-03-29 16:11:51 +02:00
1cbcd5c2da Update timeline-polls to use td-screenshot-remove & fix poll margin in screenshot 2017-03-29 16:09:47 +02:00
55253e284e Add support for 'td-screenshot-remove' class to remove elements from screenshots 2017-03-29 16:01:36 +02:00
e9795cd697 Fix various margin issues in tweet screenshots 2017-03-29 15:48:40 +02:00
be76d9a6dc Fix not removing date when screenshotting tweets with location data present 2017-03-29 15:08:20 +02:00
d7cdaf2870 Make update download form double buffered & tweak cancelling 2017-03-29 14:30:51 +02:00
48ed0e01d1 Remove 'internal' modifier on NativeCoreAudio class 2017-03-27 04:02:17 +02:00
dca31dedde Synchronize SoundNotification volume with sound mixer volume 2017-03-27 03:59:31 +02:00
ab7356b991 Make sure COM objects in SoundNotification event handlers are released 2017-03-27 03:58:14 +02:00
a46a673cf8 Rename 'sound' to 'soundNotification' in TabSettingsSounds 2017-03-27 03:52:54 +02:00
c8d52539ca Mark assembly as CLS non-compliant 2017-03-26 21:31:19 +02:00
ed9267b6ba Move sound settings to a separate tab, add a Play button, update sound file dialog 2017-03-26 17:47:48 +02:00
b7c02d1cf8 Rewrite SoundNotification to use WMPLib 2017-03-26 15:56:49 +02:00
fb66beb29f Move SoundNotification error handling into an event 2017-03-26 00:13:03 +01:00
8c3bf6bbc3 Fix notification settings tab event not triggering after a recent change 2017-03-26 00:06:36 +01:00
b943078132 Rewrite example tweet in TweetNotification to use a file resource 2017-03-25 21:47:14 +01:00
e727617bf1 Add animation optimization to edit-design plugin and fix label margin 2017-03-25 19:56:20 +01:00
ffaea6dcbe Fix square scrollbars option in edit-design causing visual bugs in some places 2017-03-25 16:16:35 +01:00
bf1f72a2a4 Cleanup some code in the edit-design plugin 2017-03-25 16:12:25 +01:00
be0df7c5b0 Tweak radio and checkbox margins in edit-design plugin 2017-03-25 16:05:57 +01:00
5ab769e74d Add several toggles to edit-design plugin (most from revert-design) 2017-03-25 15:06:54 +01:00
d22ddb1731 Remove the design-revert plugin before replacing it with edit-design 2017-03-25 15:05:41 +01:00
9fed8c022b Add checkbox support to edit-design plugin and delay theme change 2017-03-25 14:40:28 +01:00
a315ed90af Redesign the edit-design modal 2017-03-25 14:30:24 +01:00
1ebf3c9af2 Fix media previews showing up in quoted tweet notifications 2017-03-25 01:55:47 +01:00
0e4c923c23 Move regexes in WindowsUtils into an inner class to delay their construction 2017-03-24 16:25:46 +01:00
63835b9f99 Dispose TrayIcon object properly 2017-03-24 16:10:19 +01:00
ff17f7c132 Tweak TrayIcon to load (and reset) icon only when becoming visible 2017-03-24 16:00:44 +01:00
f4631c9b38 Fix icon size bug in edit-design plugin 2017-03-24 14:12:30 +01:00
35931023ae Refactor plugin events and config reloading, fix config reset not reloading plugins 2017-03-24 13:24:20 +01:00
6d93381760 Add column width and font size options to edit-design plugin 2017-03-23 23:53:37 +01:00
f1bdd5f1b2 Work on edit-design plugin code ('select' support, config & css updates) 2017-03-23 23:21:44 +01:00
1c3e2fbad7 Oops 2017-03-23 21:53:15 +01:00
29a02db07d Fix button positioning in FormMessage 2017-03-23 20:34:56 +01:00
f1db1ba708 Refactor FormMessage uses with the new DialogResult parameter 2017-03-23 16:11:42 +01:00
c1420bac88 Add optional DialogResult for FormMessage buttons 2017-03-23 16:01:44 +01:00
8e527fbbdf Add extra new lines to plugin load/execution error messages 2017-03-23 15:46:49 +01:00
5ec1bcfe3f Add plugin execution event with an error message if the script files fail to load 2017-03-23 15:33:32 +01:00
7226461cd0 Add avatar style selection to edit-design plugin 2017-03-23 14:40:55 +01:00
6d6f383c92 Add notification CSS handling to edit-design plugin 2017-03-23 14:40:05 +01:00
e1a6328d09 Update edit-design plugin with modal event handling 2017-03-23 13:59:31 +01:00
6e4153911a Push a WIP edit-design plugin with a basic modal dialog 2017-03-23 03:14:37 +01:00
342f74646e Replace the only remaining use of WindowsUtils.CreateSingleTickTimer 2017-03-22 23:31:54 +01:00
fe5191d3b5 Remove NativeMethods.SimulateMouseClick 2017-03-22 23:28:47 +01:00
504cf97c6c Replace hacky code that unfocuses Settings notification with better code 2017-03-22 23:09:59 +01:00
f8c494c9c1 Remove NotificationFlags and tweak handling of the Settings notification 2017-03-22 22:44:55 +01:00
b90d7f721a Prevent 'Aborted' code from showing up as a connection error 2017-03-22 22:29:35 +01:00
7936af6c9a Rewrite notification hook to scroll without stealing cursor and focus 2017-03-22 18:35:40 +01:00
52d01e3dd7 Fix typos and disable some warnings in NativeMethods 2017-03-22 18:33:56 +01:00
d30d70395a Reorder conditions in notification mouse hook to improve performance 2017-03-22 18:06:17 +01:00
491a3ae525 Add 'Copy account username' to context menu for Twitter account links 2017-03-22 16:57:09 +01:00
1eae380b08 Fix missing tweet context menu in notification column & when moving across columns
Recent bug caused by changes to the handler, not present in previous
release
2017-03-22 16:18:42 +01:00
f091b2526e Refactor Settings tabs (replace IsReady, remove SelectTab with no parameters) 2017-03-22 13:19:19 +01:00
7548e2e202 Fix timer checkbox being updated instead of non-intrusive popups checkbox in Settings 2017-03-22 13:15:47 +01:00
d9b9afbf2d Refactor UpdateHandler to not expose UpdaterSettings 2017-03-22 12:49:10 +01:00
8036659003 Remove isLoaded check from several events in FormBrowser 2017-03-22 12:35:01 +01:00
f9fb4668c2 Refactor browser reloading in ContextMenuBrowser to use ReloadToTweetDeck 2017-03-21 22:45:16 +01:00
7047924947 Disable UseMnemonic on PluginControl labels to allow single ampersands 2017-03-21 21:14:08 +01:00
8f6be3911a Rename 'Non-Intrusive Popup' to 'Non-Intrusive Popups' 2017-03-21 20:35:22 +01:00
0d95b8eb44 Rewrite hacky link handling for the error page to be not as hacky 2017-03-21 20:24:41 +01:00
61d2d124ff Add a custom load error screen to FormBrowser 2017-03-21 19:05:42 +01:00
7b218b2544 Remove the last trace of debug.js in the project file 2017-03-21 18:56:54 +01:00
87ac7daf76 Add BrowserUtils.ConvertPascalCaseToScreamingSnakeCase with unit tests 2017-03-21 18:56:22 +01:00
6b4817df36 Minor FormBrowser code cleanup 2017-03-21 17:32:48 +01:00
22d99da2e1 Fix _postbuild.bat to not delete the subprocess exe when ran multiple times 2017-03-21 16:57:31 +01:00
47b6cf7068 Cleanup gitignore and push missing 'RUN BUILD.bat' with updated README 2017-03-21 16:55:59 +01:00
606c9512f8 Move debug.js into the debug plugin 2017-03-21 16:39:02 +01:00
aef9c591e9 Minor refactoring of log file path variables 2017-03-21 15:04:28 +01:00
71f67e9191 Fix new TweetDeck Settings context menu position 2017-03-21 14:50:59 +01:00
636f2b3017 Rewrite TrayIcon to use ContextMenu instead of ContextMenuStrip for native rendering 2017-03-21 02:03:38 +01:00
2de5b5c6e4 Make the TweetDuck entry in TweetDeck Settings show the context menu 2017-03-21 01:28:13 +01:00
0cbcc8c9f3 Move the 'Updates' tab into 'General' 2017-03-21 00:17:43 +01:00
96146e3dc8 Add a setting for non-intrusive popups to avoid accidental clicks 2017-03-21 00:11:13 +01:00
5aaae51be1 Move packages.config to root and remove app.config 2017-03-20 13:44:18 +01:00
b98625fdbc Remove legacy config binder to update TweetNotification.Position namespace 2017-03-20 12:33:42 +01:00
09a748e9dc Remove legacy cache cleanup code 2017-03-20 12:26:30 +01:00
87b07c6d5b Rewrite highlighted column & tweet handling, enable tweet context menu on likes and RTs 2017-03-20 02:23:00 +01:00
f39e668f8d Make BrowserUtils.OpenExternalBrowser ignore empty urls 2017-03-19 10:13:24 +01:00
6ea95342a0 Add a 'Restart with Arguments' button with a dialog to pick command line args
Closes #109
2017-03-18 15:51:40 +01:00
c594bf5757 Change default locale to an empty string instead of 'en', which was invalid anyway 2017-03-18 14:12:41 +01:00
cd3b198c6f Disable minimize and maximize buttons on the Import/Export Profile dialog 2017-03-18 13:43:16 +01:00
b249b5f46e Rewrite handling of program arguments & add Program.RestartWithArgs 2017-03-18 12:57:42 +01:00
bbe3b48bcc Replace restart warning when importing session with "Import & Restart" button text 2017-03-18 11:20:37 +01:00
3bcd056197 Release 1.6.7 2017-03-18 09:16:06 +01:00
6387ab41b3 Delay initial tab selection in the Plugin form until after the window is fully shown 2017-03-16 20:50:57 +01:00
4df16b7f15 Fix 'Reload All' button in Plugins form hiding the panel scrollbar resized 2017-03-16 20:42:18 +01:00
ed387a2873 Add a validity check when opening URLs from the internet and plugins 2017-03-16 18:37:24 +01:00
9e225530a6 Add BrowserUtils.IsValidUrl for http(s)/ftp/mailto url checking with unit tests 2017-03-16 18:36:31 +01:00
7b23686dc6 Remove a mailto TODO comment as it's no longer necessary 2017-03-16 18:02:29 +01:00
4de31453fd Update reply-account plugin to fix a search column issue due to a TweetDeck update 2017-03-16 12:19:39 +01:00
4c59526e39 Minor code refactoring, fix potential event memory leaks 2017-03-14 23:47:30 +01:00
9ec1764194 Update tweet detail screenshot code to work with recent TweetDeck changes 2017-03-13 23:38:50 +01:00
47afa32902 Minor code tweak in update.js to avoid a redeclaration 2017-03-13 22:55:35 +01:00
2a09487b55 Remove non-gendered duplicate emoji 2017-03-13 22:17:18 +01:00
563c856dd3 Rewrite tweet screenshot functionality to use native methods 2017-03-13 21:40:15 +01:00
69ea242408 More refactoring of notifications, cache notification scripts 2017-03-13 16:13:32 +01:00
d6e0e0726f Completely refactor FormNotification into multiple classes 2017-03-13 02:06:31 +01:00
73d460d40a Add a compact skin tone selector to emoji keyboard 2017-03-12 21:30:16 +01:00
1f27d96ac9 Release 1.6.6 2017-03-10 16:52:16 +01:00
93e9f28d69 Make update installer download the portable version for portable installations 2017-03-10 16:52:09 +01:00
ec2e26752a Fix link clicking bug caused by a CefSharp bug 2017-03-10 16:50:41 +01:00
fadd95f3e6 Fix installation path detection via registry in update installer 2017-03-10 16:00:00 +01:00
00acc677e6 Release 1.6.5 2017-03-10 14:34:16 +01:00
1a799881e8 Protect against accessing MainWindowHandle on locking process when already existed 2017-03-10 14:07:09 +01:00
f75677593a Update build tools to remove/ignore .pdb files 2017-03-10 11:37:22 +01:00
19e3bd19f0 Update build guide in readme 2017-03-10 11:23:51 +01:00
85701b0a3c Update CefSharp to 55 2017-03-10 10:59:01 +01:00
014cb18dcb Remove unused 'using' statement from FormUpdateDownload 2017-03-09 20:40:21 +01:00
e71e1c853f Refactor FormBrowser.ReloadBrowser 2017-03-09 20:39:12 +01:00
ee9d9196f5 Rewrite image paste click simulation to use CEF events instead of WinAPI 2017-03-09 19:46:12 +01:00
53c8272e01 Remove decimal point in update download label 2017-03-09 19:13:17 +01:00
7f7b6b1e2a Minor code changes, including InvokeAsyncSafe in a couple more places 2017-03-09 19:08:33 +01:00
405777e0f5 Fix tray restoration code to no longer restore windows of all existing TweetDuck processes
Closes #108
2017-03-09 13:59:37 +01:00
df2b624cb5 Update Program to use TrySleepUntil 2017-03-09 13:47:47 +01:00
8a48d5c2f9 Update LockManager to use TrySleepUntil 2017-03-09 13:35:18 +01:00
c55ee71442 Add WindowsUtils.TrySleepUntil to make timeoutable waiting easier 2017-03-09 13:23:13 +01:00
3f82745f5b Improve main window detection and skip kill if already exited in LockManager 2017-03-09 03:06:47 +01:00
404187a1ae Rewrite tray restoration code to detect deadlocked process and allow killing it 2017-03-09 02:54:19 +01:00
2b7b3f586b Allow LockManager to forcibly kill the process if the attempt to close it times out 2017-03-09 02:52:04 +01:00
04959a3493 Make the update check run at the beginning of each hour instead of each hour after startup 2017-03-09 01:17:03 +01:00
97cf4932ae Move a comment in Program.cs 2017-03-09 00:56:36 +01:00
b0d88a0a37 Add a safeguard to updater to open browser if the update installer is missing 2017-03-09 00:52:12 +01:00
67a2e40622 Ninja fix deadlock when exiting after update 2017-03-08 22:10:06 +01:00
3a28556c7f Release 1.6.4 2017-03-08 21:33:39 +01:00
9ecc92b9a5 Fix emoji keyboard separators only working for the first case 2017-03-08 21:18:58 +01:00
ca023be98a Change default installation directory in portable installer 2017-03-08 21:16:36 +01:00
11a1423f76 Make sure the app is loaded before hooking account selectors 2017-03-08 13:06:50 +01:00
79f6df121b Swap shift key functionality in drawer and retweet account selectors 2017-03-08 13:01:48 +01:00
71eade7e86 Fix unsupported video tweaks for actual embedded video elements 2017-03-07 22:47:54 +01:00
5f81d29036 Finish basic emoji keyboard (enable/disable functionality, layout fix, screenshot pasting fix)
Closes #102
2017-03-07 20:32:30 +01:00
ec1cb5dc5f Final optimizations for emoji keyboard 2017-03-07 20:05:40 +01:00
fd969e2d55 Further cut down size of emoji-ordering.txt by wildcarding emojis with skin tones 2017-03-07 18:54:51 +01:00
37e33b77ff Cut down size of emoji-ordering.txt file 2017-03-07 18:36:07 +01:00
f7ed7703b4 Rewrite plugin cache to use tokens and local paths as multikeys 2017-03-07 18:31:58 +01:00
4bb35295ca Add a debug plugin to unit test plugin features 2017-03-07 18:11:13 +01:00
1e4f673f9e Add a TwoKeyDictionary collection with unit tests 2017-03-07 17:45:13 +01:00
7cadb1c403 Add an option (disabled by default) to revert New Tweet font size in design-revert plugin 2017-03-07 16:39:06 +01:00
37148f5093 Make design-revert plugin features configurable
Closes #107
2017-03-07 16:32:08 +01:00
f6bc26789f Rework emoji keyboard using official ordering, fix loading, add separators, tweak styles 2017-03-07 15:32:34 +01:00
b3f5a88525 Set red play button on unsupported videos instead of replacing them
Closes #104
2017-03-07 01:15:33 +01:00
1e538d2b28 Move sound notification code to a separate class 2017-03-05 14:27:47 +01:00
7d7bfb7b01 Refactor FormSettings to take initial tab index in constructor and remove public SelectTab 2017-03-05 14:27:35 +01:00
41d86ba440 Remove (hopefully) unnecessary user link target fix 2017-03-04 13:11:33 +01:00
3df474a8a5 Refactor ready state handling in code.js 2017-03-04 13:03:30 +01:00
a50d6e8f47 Disable resizing for the settings export dialog 2017-02-25 19:07:21 +01:00
6081e5b9c1 Add & use ControlExtensions.InvokeAsyncSafe for improved performance 2017-02-20 13:02:24 +01:00
66ccea920c Hide emoji keyboard on escape or click outside 2017-01-30 16:54:50 +01:00
470d63093f Add combined emoji to the emoji keyboard plugin 2017-01-30 16:21:09 +01:00
eae0507831 Add a WIP emoji keyboard plugin 2017-01-30 15:32:28 +01:00
92af85d3bb Release 1.6.3 2017-01-28 18:49:23 +01:00
7635af5730 Add an AppName suffix for portable and update installers 2017-01-28 18:49:14 +01:00
a838e89695 Fix custom sound notification textbox not setting color when the control is created 2017-01-28 18:12:30 +01:00
b22289a8b9 Work around Alt freezing the app since W10 Anniversary Update
Get fucked, Microsoft
2017-01-28 17:44:30 +01:00
45b3ff52c6 Tweak FormBrowser.ShowChildForm to use VisibleChanged instead of Shown event for reliability 2017-01-28 01:08:31 +01:00
4464991f4c Prevent automatic Settings tab selection from triggering autoclick in Notification tab 2017-01-28 01:07:57 +01:00
b0d2f77583 Merge pull request #101 from chylex/ipc
Replace WCF with native chromium IPC
2017-01-28 00:01:22 +01:00
b211a4405d Set CefSharpSettings.WcfEnabled to false 2017-01-27 23:59:01 +01:00
8823016d2c Make custom sound notification textbox font red when the file doesn't exist 2017-01-27 23:56:51 +01:00
859fdc7ec1 Rewrite custom sound notification to show an error message on failure instead of hiding it 2017-01-27 23:56:00 +01:00
028d5ed01f Improve debug script for easier extendibility, add sound notification simulation 2017-01-27 21:48:57 +01:00
5fd5a2a436 Use and test RegisterAsyncJsObject in FormBrowser 2017-01-27 18:51:14 +01:00
79a7e7470c Use and test RegisterAsyncJsObject in FormNotification 2017-01-27 17:00:09 +01:00
9ecef78aed Fix DismissedUpdate not being set after toggling updates 2017-01-27 16:21:36 +01:00
65a837a6e1 Move TweetDeckBridge properties to a separate JS object 2017-01-27 16:13:17 +01:00
6e4db4acea Rewrite custom CSS injection and automatically inject it while typing 2017-01-26 15:35:40 +01:00
26fb977d05 Remove unnecessary properties from TweetDeckBridge 2017-01-26 06:51:51 +01:00
b42cd1c048 Tweak screenshot notification script (minor edit) 2017-01-26 06:46:19 +01:00
467f7cd12f Rewrite update system to use RegisterAsyncJsObject 2017-01-26 06:41:20 +01:00
66699ce9df Change update progress form to show kB instead of MB 2017-01-26 06:39:46 +01:00
cf7d903932 Move updater event args to a separate namespace 2017-01-26 04:09:04 +01:00
a7ab67925c Allow moving the notification window when holding Alt in debug builds 2017-01-23 01:13:15 +01:00
a474ba4260 Fix incorrect cursor when hovering over quoted tweet in notification
Closes #97
2017-01-23 01:00:16 +01:00
09e5636e86 Remove unused 'using' statement 2017-01-23 00:59:19 +01:00
2295a875be Fix 'Copy' context menu item (separator in wrong place in browser, missing in notification) 2017-01-23 00:53:15 +01:00
82a2455afc Release 1.6.2 2017-01-23 00:33:06 +01:00
268de676ee Add NativeMethods.GetIdleSeconds for idle time detection 2017-01-22 16:00:54 +01:00
8fe26c07f1 Preserve plaintext when stripping HTML styles from clipboard text 2017-01-17 18:29:09 +01:00
da3921b1ca Add safeguards for clipboard update methods
Closes #91
2017-01-17 18:19:39 +01:00
4dd2e787d1 Remove unnecessary IsSystemSupported check in UpdateHandler 2017-01-17 02:48:17 +01:00
ce005ae6c2 Add unit tests for CombinedFileStream 2017-01-17 02:33:47 +01:00
1513f46a11 Add a safety net to CombinedFileStream.Entry.WriteToFile with createDirectory 2017-01-17 02:27:03 +01:00
7543eeb0f4 Add more methods to TestUtils and fix cleanup code not running 2017-01-17 01:38:55 +01:00
873242120c Add a TestUtils class for easy file manipulation and cleanup in unit tests 2017-01-17 01:20:12 +01:00
98f8095a65 Add unit tests for CommandLineArgsParser 2017-01-16 22:46:01 +01:00
785571a550 Add unit tests for BrowserUtils and CommandLineArgs 2017-01-16 22:06:04 +01:00
0c4bd4044e Add ReSharper code coverage settings and cleanup the test project 2017-01-16 20:56:36 +01:00
0319543dce Add a unit test project 2017-01-16 19:36:29 +01:00
82d70b2d7f Stealthfix a bug with CommandLineArgs.ToString causing an exception if there are no args 2017-01-10 21:58:17 +01:00
62d18e010a Release 1.6.1 2017-01-10 21:24:54 +01:00
fc77b85083 Remove HTML styles after copying selected text to clipboard 2017-01-08 16:36:49 +01:00
50a8893f4f Add an option to disable screenshot window border 2017-01-08 02:47:47 +01:00
9252b3040e Fix screenshot functionality broken by previous refactoring 2017-01-08 02:26:09 +01:00
d5141ed020 Redo OnNotificationReady call to use LoadingStateChanged with a delay 2017-01-08 02:16:40 +01:00
7ff9e23283 Remove legacy notification loading option 2017-01-08 01:33:48 +01:00
89854d527c Fix notification position config after changing namespace and remove TweetNotification.Duration 2017-01-03 18:43:36 +01:00
6ff0cad2a8 Pre-release 1.6 2017-01-03 17:45:52 +01:00
349cfbd2d5 Set FormNotification.Visible to false if the form is out of view 2017-01-03 17:08:15 +01:00
40303ef74a Move FormBrowser.CreateNotificationForm next to other notification related methods 2017-01-03 00:34:45 +01:00
6c652122c2 Replace FormBrowser notification property with methods and use pause in TweetScreenshotManager 2017-01-03 00:30:36 +01:00
3658e3a2aa Update CEF version in update installer script 2017-01-02 22:24:11 +01:00
2b20fcfcd1 Pause notifications while the Settings window is open 2017-01-02 20:17:53 +01:00
554d427fef Improve notification muting by pausing instead of clearing it 2017-01-02 20:17:35 +01:00
7cf5b23306 Add debug.js and implement notification simulation in it 2017-01-02 18:29:28 +01:00
b26a6098eb Add a HORRIBLE HACK to unfocus example notification in Settings 2017-01-02 15:44:18 +01:00
7ad927bdaf Use CreateSingleTickTimer in TweetScreenshotManager 2017-01-02 04:29:45 +01:00
4ed30b3619 Add WindowsUtils.CreateSingleTickTimer 2017-01-02 04:28:57 +01:00
edfa9264d5 Add a timeout to TweetScreenshotManager in case of failure 2017-01-01 23:36:18 +01:00
f7516b593f Add JavaScript dialog handler that uses FormMessage for alerts and confirmations 2017-01-01 21:38:46 +01:00
83ff998f9d Rename DialogHandlerBrowser to FileDialogHandler 2017-01-01 21:34:24 +01:00
47381e0df4 Fix alignment of FormMessage text with no message icon 2017-01-01 21:30:45 +01:00
ba62d57485 Fix invalid context menu items due to bridge properties not being cleared after reload 2017-01-01 00:34:52 +01:00
c014c4bc24 Refactor notifications (move namespaces, move screenshot methods to a separate class) 2016-12-29 02:50:16 +01:00
5d1a3fede2 Fix the export/import button not getting disabled if no option is selected 2016-12-29 00:47:05 +01:00
53b584fe45 Add a button to open wiki to CSS dialog 2016-12-29 00:32:46 +01:00
f53d974400 Add tooltip to export/import dialog and uncheck session export by default 2016-12-29 00:18:03 +01:00
c4b4ef19cd Add profile import file flag detection 2016-12-29 00:01:55 +01:00
3bfc360362 Add a SkipFile method to CombinedFileStream to skip through key names 2016-12-29 00:00:36 +01:00
584f16d375 Fix export dialog design and event handling 2016-12-28 23:59:41 +01:00
b3d84c3217 Update clear-columns plugin version 2016-12-28 23:00:36 +01:00
dd14ad470e Add WIP export/import selection dialog 2016-12-28 21:16:53 +01:00
85b90574b8 Fix visual issues with screenshots (reply avatars, media margins, poll cleanup) 2016-12-25 13:52:34 +01:00
ee5d172468 Fix timeline-polls plugin adding extra margin to tweets without polls 2016-12-25 13:51:15 +01:00
7ca4b94361 Cleanup unnecessary TODO in code.js 2016-12-25 13:06:27 +01:00
31f1546483 Make TweetNotification properties constants instead 2016-12-25 13:02:56 +01:00
d8a88a19af Separate notification CSS fixes (fixes badge and urls in screenshots) 2016-12-25 12:58:02 +01:00
12af79de05 Refactor tweet screenshot code to a separate class and work around window disposal issues 2016-12-24 20:35:13 +01:00
2260dd419d Update tweet screenshot to work with detail view, tweak the bottom padding 2016-12-24 17:04:56 +01:00
61a940cc82 Fix highlighted tweet context menu for tweet detail view 2016-12-24 16:57:30 +01:00
1bbc1e0d7e Include script files in the project to make Visual Studio detect changes 2016-12-24 16:35:47 +01:00
921294eeb3 Add support for custom wav notification sounds
Closes #3
2016-12-24 15:59:29 +01:00
baaa90f49d Remove widevine drm plugin 2016-12-24 12:05:19 +01:00
4e25381770 Fix retweeted retweet icon color and size mismatch 2016-12-24 01:52:01 +01:00
272877d0ed Add Open Data Folder button and fix log tooltip in Advanced Settings tab 2016-12-24 00:53:14 +01:00
555b947bf7 Add DM participants column to back mouse button handling 2016-12-24 00:48:28 +01:00
da29811b16 Fix td-hover class missing after clicking the Skip button and not moving the cursor 2016-12-24 00:44:34 +01:00
241f67fd4d Add column reset functionality when holding Shift to code.js 2016-12-23 23:52:33 +01:00
eb4ce18e31 Refactor -32000 location to a static Point object 2016-12-23 23:27:37 +01:00
ae99fee440 Fix clear-columns button style 2016-12-23 16:23:26 +01:00
d116ac5e56 Reorder notification context menu to place relevant items on top
Closes #81
2016-12-23 16:15:43 +01:00
28db1f4253 Add NotificationFlags.TopMost and disable it for Settings form 2016-12-23 16:00:59 +01:00
034312e676 Add dev tools to context menus when debugging 2016-12-23 15:43:11 +01:00
da83d73ba6 Merge pull request #85 from chylex/screenshot
Add tweet screenshot functionality & update CEF
2016-12-23 15:31:15 +01:00
02e8dc3440 Fix screenshot script modifying original elements and missing html classes 2016-12-23 15:29:33 +01:00
cac6d1f889 Swap notification windows when taking a screenshot, and make screenshot window unmovable 2016-12-23 15:13:34 +01:00
68fa3294d4 Update CefSharp version in README 2016-12-23 14:56:16 +01:00
9b983de8c9 Ensure notification window visibility when taking screenshots, refactor MoveToVisibleLocation 2016-12-23 14:52:48 +01:00
3a37ee719b Fix csproj file after previous refactor commit 2016-12-23 14:29:23 +01:00
61359c2faa Refactor NotificationFlags and inner screenshot bridge class to a separate namespace 2016-12-23 14:26:59 +01:00
7f7c5ab35b Update CefSharp to 53.0.1 2016-12-23 14:07:47 +01:00
a1b483d20a Implement printscreen simulation and a timeout to screenshot notification 2016-12-23 13:40:30 +01:00
04cd662d78 Release 1.5.1 2016-11-23 05:03:49 +01:00
da597f076f Fix quote escaping in updater arguments 2016-11-23 04:57:13 +01:00
fab3efdcf5 Fix update checker running outside of TweetDeck website 2016-11-23 04:55:58 +01:00
a55509a34d Add an old TweetDeck profile detection & warning message to the full installer 2016-11-23 03:51:58 +01:00
84fb1c5b2b Make update installer use TweetDuck's initial command line arguments 2016-11-23 03:20:38 +01:00
391a90e1df Add a -debugupdates command line argument to allow prereleases in update checker 2016-11-23 02:08:33 +01:00
e0fe39195d Add HasValue method to CommandLineArgs 2016-11-23 02:06:41 +01:00
385fead81a Fix updater calling onUpdateCheckFinished when eventID parameter is undefined 2016-11-23 02:05:44 +01:00
648d1b9aa9 Rewrite lock system to be more reliable and handle exceptions better 2016-11-19 05:57:55 +01:00
3f0028913d Move unhandled exception handler from Program to Reporter class 2016-11-19 03:11:37 +01:00
45e6ec8b0f Fix FormMessage fonts 2016-11-18 20:28:00 +01:00
a3fbaa0b34 Make program restarts as reliable as possible
Closes #80
2016-11-18 19:59:21 +01:00
7102cbfb3b Add a retry button to the warning message when TweetDuck takes too long to restart 2016-11-18 19:35:02 +01:00
c3db3ce0f2 Push WIP tweet screenshot functionality 2016-11-16 18:39:30 +01:00
7a1e7637ff Add a parameter to toggle custom CSS in TweetNotification.GenerateHtml 2016-11-16 18:33:30 +01:00
04a78a02d3 Add NotificationFlags for easier configuration of the notification window 2016-11-16 18:33:01 +01:00
cb61dc742f Push minor tweak in ExecuteScriptAsync in image pasting code 2016-11-16 18:29:35 +01:00
cd53f6e757 Disable 'Show Error Log' button if the logging failed 2016-11-16 14:51:47 +01:00
c64f7daa8d Cleanup browser subprocess path code 2016-11-16 14:25:52 +01:00
e70d792654 Fix plugin status not updating from new config after importing profile
Closes #79
2016-11-16 04:10:17 +01:00
9ae533f907 Remove TweetDick config file compatibility 2016-11-15 19:26:23 +01:00
cfe92f18e3 Remove all TweetDeck and other migration code 2016-11-15 19:20:43 +01:00
e2a34ea28e Remove original CheckFolderPermission and replace it with the lazy workaround 2016-11-15 18:10:25 +01:00
ec8000360e Windows file permissions can go to hell 2016-11-15 01:01:41 +01:00
57b0821e19 Revert "Rewrite folder write permission check to hopefully make it more reliable"
This reverts commit 1f9db3bda6.
2016-11-15 00:47:15 +01:00
09d39df15a Release 1.5 2016-11-15 00:19:24 +01:00
1f9db3bda6 Rewrite folder write permission check to hopefully make it more reliable 2016-11-14 23:32:45 +01:00
b7104c8828 Remove privilege requirement from update & portable installer, handle updater privileges within TweetDuck 2016-11-14 20:53:56 +01:00
5da02b4092 Make the portable installer fully autonomous 2016-11-14 20:52:11 +01:00
802f1e3042 Refactor Process.Start uses (missing using statement, use WindowsUtils for elevation) 2016-11-14 19:39:26 +01:00
66db0df45a Add WindowsUtils.StartProcess for easier elevated process starting 2016-11-14 19:38:36 +01:00
650c2e2eb7 Remove redundant null check from WindowsUtils 2016-11-14 18:54:58 +01:00
6ab3754129 Update write permission check to use the storage folder 2016-11-14 14:06:15 +01:00
7dca0a8cab Add plugin config migration to the new data folder 2016-11-14 14:01:22 +01:00
7cd0b4ad54 Fix highlighted tweets staying in context menu after logging out of TweetDeck 2016-11-14 10:35:58 +01:00
97acb41eee Fix console errors caused by running browser scripts even outside of TweetDeck website 2016-11-14 10:35:42 +01:00
b916b9726e Add a method to check if a frame has a TweetDeck URL to BrowserUtils 2016-11-14 10:34:52 +01:00
32d3990ace Rewrite plugin data export and combined file stream identifiers, add missing plugin warning 2016-11-14 10:15:21 +01:00
cb1fd109cc Prevent missing plugin folders from causing a crash 2016-11-14 10:12:22 +01:00
0e68dd6185 Fix Configure button in PluginControl causing issues with mixed slash types 2016-11-14 09:47:56 +01:00
fb6502bc65 Rename plugin data folder to TD_Plugins for consistency 2016-11-14 09:39:48 +01:00
c7e7403781 Update plugin config to use the data folder instead of plugin root 2016-11-14 06:14:38 +01:00
bf224408a3 Rewrite PluginBridge to accommodate the functions to the new plugin folder handling 2016-11-14 06:14:14 +01:00
84b7078873 Assign a data folder to each plugin and add new folder handling functions 2016-11-14 06:09:05 +01:00
89e5943d8f Add a PluginFolder enum and a plugin data root path to Program 2016-11-14 05:43:30 +01:00
b78c4cb8f0 Move PluginEnvironment and PluginGroup to a separate Enums package 2016-11-14 05:08:18 +01:00
976ba074a8 Add a warning for outdated config in reply-account plugin 2016-11-13 15:18:10 +01:00
5205d59b96 Rewrite custom selector in reply-account plugin to fix and futureproof TweetDeck changes 2016-11-13 15:06:51 +01:00
e8394b9c08 Add browser console logging to debug output 2016-11-13 13:45:10 +01:00
9cd00239f9 Fix update installer creating Start Menu entry in portable mode (applied to 1.4.3) 2016-10-23 00:48:23 +02:00
b6b26142f8 Release 1.4.3 2016-10-22 22:13:15 +02:00
4ee99376fd Add a portable installer that uses the full installer with a custom flag 2016-10-22 21:49:31 +02:00
b0261342ff Add portable functionality to update installer 2016-10-22 21:11:48 +02:00
87fd2a521e Fix quoted tweet link in notification window not resetting
Closes #75
2016-10-21 06:50:34 +02:00
334793c6f6 Add full installer error/abort handling to the update installer
- Will abort and cleanup if full installer fails
- Fixed uninstallation files getting deleted if the full installer could
not be started
2016-10-20 18:48:35 +02:00
3e70d991bb Fix installer unable to run TD when TweetDuck.exe requires admin privileges 2016-10-20 18:45:10 +02:00
feec96fc5c Pass installation path to the updater to allow portability
Closes #77
2016-10-20 18:23:48 +02:00
765984709e Make sure TweetDeck uninstaller runs elevated, add safety nets and shield icons to buttons 2016-10-20 04:23:48 +02:00
c7c9931f68 Add an extension method to add UAC shield to a button 2016-10-18 16:21:08 +02:00
ae64573510 Remove old debug.log and ChromeDWriteFontCache on update install 2016-10-18 16:05:34 +02:00
d675af5aa4 Rename the debug log again to TD_Console.txt for consistency 2016-10-18 16:04:31 +02:00
9480d17cfc Change CEF debug file to jsconsole.log in storage path 2016-10-18 15:55:25 +02:00
5ac1df2283 Fix LockManager not finding correct process in debug 2016-10-18 15:48:55 +02:00
20119db883 Release 1.4.2 2016-10-09 16:04:38 +02:00
a4006deb8c Rewrite extra mouse button handling and fix skipping 'Back to Tweet'
Closes #74
2016-10-09 15:49:08 +02:00
25fa3cefab Fix notification tweet footer displaying in some tweets (after removing it in a prev commit) 2016-10-09 15:13:01 +02:00
bb5161eb34 Fix notifications only displaying the last one when multiple were enqueued at the same time 2016-10-09 14:58:11 +02:00
1bfc403a98 Fix typos in installer script comments 2016-10-09 14:42:13 +02:00
720d10e543 Fix update installer version cleanup issue and move idpDownloadAfter to InitializeWizard 2016-10-09 13:33:38 +02:00
30c117672e Make full installer not automatically run TweetDuck when in silent mode 2016-10-09 13:32:28 +02:00
6d1b5c77d1 Fix program arguments for the full installer execution in update installer 2016-10-09 00:58:13 +02:00
d1cbf608e0 Add WIP full package download to update installation file if CEF needs updating 2016-10-09 00:54:37 +02:00
7e3014c52d Refactor installation files (move .NET Framework check to a function) 2016-10-09 00:53:59 +02:00
82beb1f5a7 Fix context menu state changing when moving mouse quickly
Closes #70
2016-10-08 17:43:55 +02:00
657dc81300 Include ISS installer scripts and resources 2016-10-08 17:36:20 +02:00
8e22192dd3 Update gitignore to include some files from 'bld' folder 2016-10-08 17:35:28 +02:00
dc0b7d58e3 Add an Open Program Folder button to Settings - Advanced 2016-10-08 16:20:52 +02:00
6919e5bdb0 Fix hardware acceleration only being partial 2016-10-08 16:13:50 +02:00
9728a62efa Remove unnecessary code from notification html builder 2016-09-30 23:56:27 +02:00
276e070759 Fix recent TweetDeck change breaking media in notifications 2016-09-30 23:43:46 +02:00
fadea54f8d Remove legacy update notification warnings 2016-09-30 15:12:18 +02:00
523d340ade Remove the disabled() event handler in timeline-polls plugin 2016-09-30 15:09:52 +02:00
96fa7efb66 Fix a crash in reply-account plugin on Popout 2016-09-27 21:30:34 +02:00
03591f8317 Release 1.4.1 2016-09-27 18:25:58 +02:00
28cc60d007 Tweak CommandLineArgsParser to slightly improve reliability with quoted strings 2016-09-27 18:11:50 +02:00
1fa69bdb3b Add invalid data folder error reporting and env variable expansion for absolute paths 2016-09-27 18:00:05 +02:00
11f5f9b72e Add Reporter.HandleEarlyFailure for non-recoverable errors before Reporter is setup 2016-09-27 17:58:13 +02:00
61f6543041 Change Source Code link to Tips & Tricks 2016-09-27 17:37:19 +02:00
0c9ab32f37 Add additional functionality to the back mouse button (inline and drawer composer, fix order) 2016-09-27 04:16:50 +02:00
fde984d02b Add a hover class to Notification body element and only display skip button when over 2016-09-27 03:48:14 +02:00
f23db31306 Increase default size of Edit CSS dialog 2016-09-26 17:20:25 +02:00
8dce99b8b3 Add support for ctrl+a shortcut in multiline textboxes 2016-09-26 17:20:10 +02:00
342ac51cda Tweak the skip button position in Notification window 2016-09-26 17:14:17 +02:00
ba31f7ae01 Update TweetDuck website to https 2016-09-26 16:31:06 +02:00
8d0fa030f8 Remove d3dcompiler_43.dll that used to be required for Win XP 2016-09-26 16:16:20 +02:00
d030a79c81 Fix Release build compliation error 2016-09-26 16:10:21 +02:00
6690efc4d9 Address code analysis issues 2016-09-26 15:53:08 +02:00
afa8098463 Add two restart buttons to Settings - Advanced 2016-09-26 14:28:14 +02:00
c064e579d2 Add Program.Restart and fix restarting causing loss of initial program arguments 2016-09-26 14:05:44 +02:00
01dc4e4714 Update CommandLineArgsParser to use CommandLineArgs 2016-09-26 13:58:59 +02:00
6fbc246b12 Use CommandLineArgs in Program and fix broken -locale parameter 2016-09-26 13:56:13 +02:00
1efe2a02f7 Add a CommandLineArgs class for easy arg management 2016-09-26 13:52:55 +02:00
ab2ab06f60 Reorganize utility classes and methods in TweetDeck Migration code 2016-09-26 04:19:50 +02:00
a71d889682 Replace FormMigrationQuestion with a custom FormMessage 2016-09-25 23:10:18 +02:00
2252d85b27 Add MessageBoxIcon.Question support to FormMessage 2016-09-25 23:09:36 +02:00
3f09100177 Update version of the design-revert plugin 2016-09-25 19:01:39 +02:00
6b79c89f42 Add a button to skip current notification 2016-09-25 16:53:00 +02:00
5f249d4603 Add a <body> td-example-notification attribute for example notifications 2016-09-25 16:52:22 +02:00
fa64309909 Hide the old TweetDeck column Clear button when clear-columns plugin is enabled 2016-09-25 15:35:15 +02:00
c46aafdab6 Add column resetting to clear-columns plugin 2016-09-25 15:31:18 +02:00
3e9c397494 Fix inconsistency with loadConfigurationFile's onFailure parameter 2016-09-25 03:21:44 +02:00
47935165db Update current plugins to use the new CSS functionality 2016-09-25 02:37:16 +02:00
be77f753e7 Add a global function to easily add custom styles in plugins 2016-09-25 02:34:19 +02:00
2c30613279 Fix reply-account plugin for docked replies 2016-09-23 22:09:06 +02:00
d83eaec987 Update reply-account plugin version 2016-09-23 21:14:53 +02:00
e6f199a224 Implement #last in reply-account and fix bugs in its code 2016-09-23 21:13:54 +02:00
6636a2aa9e Add an experimental timeline-polls plugin 2016-09-23 19:49:56 +02:00
221bdc58fe Tweak support for old configs in reply-account plugin 2016-09-23 16:05:53 +02:00
e48a068e9d Redesign reply-account plugin configuration and add WIP last account setting 2016-09-23 15:55:13 +02:00
3371c31e63 Release 1.4 2016-09-22 00:58:27 +02:00
aa2c60f7e9 Move cookie import code to ExportManager 2016-09-20 17:15:34 +02:00
f7dc200684 Kill process instead of Environment.FailFast if possible 2016-09-20 17:11:36 +02:00
ad306c56c7 Move log file to storage path 2016-09-20 16:58:00 +02:00
86aba1eb52 Update all exception handlers 2016-09-20 16:55:51 +02:00
826f1aba7a Tweak exception handling in Reporter (use Environment.FailFast and change log button title) 2016-09-20 16:51:29 +02:00
8abf4364c6 Replace calls to Program.Log 2016-09-20 16:39:31 +02:00
4c45d91d4e Move logging to Reporter and make a static instance of it in Program 2016-09-20 16:38:15 +02:00
3176b6cb8f Create a Reporter class with improved HandleException code 2016-09-20 16:34:56 +02:00
27971e09cd Add new Control related functionality to FormMessage 2016-09-20 16:31:53 +02:00
bf95ae00de Tweak the message for missing write permission 2016-09-20 16:10:46 +02:00
0dbfa7e101 Improve reliability of directory write perm checking 2016-09-20 16:00:41 +02:00
85d09c4b5e Add a FormMessage class for custom message boxes 2016-09-20 07:47:16 +02:00
4f9bc40476 Update cache clearing for CEF 53 and clear old cache files on first startup 2016-09-19 14:03:37 +02:00
757ccbf9d3 Switch locale to English and add spell check setting
Closes #62
2016-09-19 13:33:44 +02:00
4cf9730130 Make sure the config is not loaded before old process exits when restarting 2016-09-19 13:24:23 +02:00
1dd0d70ab9 Reorganize Program.LogFile from property to readonly field 2016-09-19 13:08:27 +02:00
340eaece0f Update CefSharp to 53 and update the readme 2016-09-19 00:12:48 +02:00
c151e7cd37 Remove everything TweetDick related 2016-09-18 23:24:11 +02:00
ca0baae832 Release 1.3.3 2016-09-18 22:23:48 +02:00
478e6ed8df Move config loading and migration execution to an earlier point 2016-09-18 22:19:02 +02:00
7388eb07ca Increase process close timeouts to 20 seconds from 10 2016-09-18 21:49:41 +02:00
c38d507e50 Hopefully fix an occasional crash when importing session and restarting 2016-09-18 21:48:40 +02:00
5aee087a57 Add a Program Files warning if not running as administrator 2016-09-18 21:17:30 +02:00
0cda3702ea Change notification scroll trigger cursor position for windows without side borders 2016-09-18 21:08:09 +02:00
ce55226d0c Add error handling to logging 2016-09-18 20:50:43 +02:00
0bab0a9963 Update reply-account plugin metadata and default config warning 2016-09-18 20:29:49 +02:00
34955b7083 Rewrite Plugin default config handling and config error reporting 2016-09-18 20:29:31 +02:00
ba6ce072ac Add support for a -datafolder command line argument 2016-09-18 19:42:00 +02:00
f39fd00697 Add a TweetDick warning and migration prompt 2016-09-18 19:41:37 +02:00
58fc1be1d5 Add CefSharp version to NuGet instructions 2016-09-12 14:29:48 +02:00
1fdf9bffb6 Disable global context menu items when in textarea or text selection 2016-09-07 21:47:31 +02:00
2ad179ef8e Rename configuration file in reply-account plugin 2016-09-07 15:03:06 +02:00
d42cc5b762 Remove .js file block in Plugin folders 2016-09-07 15:01:08 +02:00
403658f622 Add config file to reply-account plugin metadata 2016-09-07 14:43:17 +02:00
6ca35685db Add button for easy Plugin config file access 2016-09-07 14:42:21 +02:00
bfc6822f69 Fix Plugins window title 2016-09-07 14:20:35 +02:00
b0386937d7 Remove lnk updater from the TweetDeck migration system 2016-09-05 21:25:25 +02:00
9436540b2f Add CultureInfo.CurrentCulture to time in logger 2016-09-05 20:45:39 +02:00
45b0ece342 Update Plugin sorting and design (separator, colors) 2016-09-05 20:44:58 +02:00
5c147d3648 Fix missing and unfocused scrollbar in Plugins window 2016-09-05 20:44:07 +02:00
8fa68c428f Add 'Open (quoted) tweet in browser' context menu items and move them down 2016-09-05 16:17:23 +02:00
e2be90191e Standardize context menu items using Chrome's style 2016-09-05 14:30:07 +02:00
339da2f1a9 Add unsupported system notification
Closes #58
2016-09-04 20:30:35 +02:00
b2cc5d50bd Address code analysis issues and fix unused parameter in CombinedFileStream.WriteToFile 2016-09-04 19:32:33 +02:00
f38b188046 Add a safety net to plugin file export in case the path is too long 2016-09-04 18:10:50 +02:00
9e45628e87 Rename 'Export/Import Settings' to Profile and add Plugin exporting 2016-09-04 16:58:03 +02:00
bf76398627 Optimize icon loading and size in Forms, fix Plugins Form title inconsistency 2016-09-04 15:32:00 +02:00
81d5728964 Add scrollbars to Edit CSS window 2016-09-04 13:57:38 +02:00
d76027558b Add a custom alert function with customizable icon and no text length limit 2016-09-04 13:19:52 +02:00
1450cc24a3 Reorganize global context menu items into a sub-menu 2016-09-04 13:10:32 +02:00
f41523c1b2 Remove redundant 'using' directives 2016-09-04 04:53:37 +02:00
4019463e68 Fix occasional incorrect location of child windows 2016-09-04 04:52:59 +02:00
5e2e239f5e Force Reload All button in Plugins window to trigger the event even without changes 2016-09-04 04:44:08 +02:00
bc7856b6c0 Tweak wording in reply-account configuration file 2016-09-04 04:39:19 +02:00
e2bba8d9e1 Update reply-account plugin to use default config and swap event order when enabled late 2016-09-04 04:36:01 +02:00
520db2c32e Rewrite loadConfigurationFile in plugins.js to accept default config file 2016-09-04 04:33:55 +02:00
da71f2de2b Add CheckFileExists function to PluginBridge 2016-09-04 04:33:13 +02:00
6dd2c6678b Add custom exception messages to file reading in PluginBridge 2016-09-04 04:01:40 +02:00
fb3d9e6d6b Add a reply-account plugin with config 2016-09-04 04:01:00 +02:00
36473c2df9 Add a global plugins.js file with loadConfigurationFile utility function 2016-09-04 03:59:37 +02:00
81aa30b2ec Allow custom .js files in plugin folders when prefixed by 'user.' 2016-09-04 03:50:35 +02:00
85d5160782 Add plugin identifier to the object itself 2016-09-04 03:38:42 +02:00
44bf7b870e Rewrote notification mouse hook to only be active if the window is visible
Closes #57
2016-09-04 00:59:13 +02:00
44da2e6082 Add debug example notification padding for scrollbar testing 2016-09-04 00:58:47 +02:00
d576bc3972 Work around Scheduled column update delay bug in TweetDeck
Closes #53
2016-09-03 16:15:28 +02:00
dbeb4c7205 Reorganize path constants and allow the program to be portable 2016-09-02 20:59:03 +02:00
e5223a852e Fix profile picture in example tweet 2016-09-02 20:58:18 +02:00
f3884315c0 Fix visual issues with clear-columns plugin (icon in temp columns, misaligned Messages icons) 2016-09-02 20:46:54 +02:00
53cd9dc9a6 Move script files to /scripts/ folder and exclude them from Visual Studio 2016-09-02 19:05:48 +02:00
a3666a7ab2 Fix formatting in JavaScript files 2016-09-02 17:35:21 +02:00
ca4eb17308 Add a clear-columns plugins 2016-09-02 17:28:32 +02:00
b729dca2e5 Seal PluginConfig and add a method to disable official plugins from config 2016-09-02 17:25:25 +02:00
21354e675a Fix TweetDeck keyboard shortcut list alignment 2016-09-02 17:17:08 +02:00
2085ddd347 Update website in design-revert plugin to https 2016-09-02 16:41:52 +02:00
31a475861b Reformat (missing space after comma), minor code tweaks 2016-09-02 13:34:41 +02:00
45bdd95dc8 Release 1.3.2 2016-08-13 13:20:13 +02:00
569fdec380 Fix middle-clicking on certain user links opening the page in TweetDuck window 2016-08-13 03:26:00 +02:00
3f15ff1c06 Fix missing default value for NotificationDurationValue causing crash with fresh install 2016-08-13 03:16:28 +02:00
506cd52255 Change the Reload context menu action to return to TD website 2016-08-12 22:30:36 +02:00
300c0c6195 Minor TweetDeck UI fixes (cut off account names and verified badge icons) 2016-08-10 17:16:39 +02:00
7689fe97b0 Fix unimplemented cache param in plugin bridge and clear cache on plugin reload 2016-08-10 16:53:26 +02:00
99ed077dbc Fix missing url word break in notifications 2016-08-10 15:48:50 +02:00
65aeb6656f Add a very basic setting for custom CSS in browser and notifications 2016-08-10 15:45:56 +02:00
e4f5766ffb Change font and check for null in CEF argument dialog 2016-08-10 15:03:15 +02:00
353ac7c76b Add setting for CEF command line args
Closes #52
2016-08-10 14:46:24 +02:00
8073fa206b Add a safety net to track bar value setting in case the config file is faulty 2016-08-10 12:47:56 +02:00
1a1cfa2220 Force visibility for fav/rt tweet actions in design-revert in detail view 2016-08-10 01:27:38 +02:00
5e93d866ad Fix missing fav/rt indicators in design-revert plugin
Closes #51
2016-08-10 01:17:26 +02:00
f29e03e250 Release 1.3.1 2016-08-06 03:35:59 +02:00
3775b5968d Fix potential memory leaks with FlatProgressBar color brush 2016-08-04 13:43:32 +02:00
ba6242e09d Change notification duration setting to a track bar 2016-07-28 21:58:42 +02:00
f6b0ddddb9 Add FlatButton for generic buttons without double border on focus 2016-07-28 21:51:49 +02:00
9e8d5c6768 Unify group boxes and layout in Settings tab 2016-07-28 15:21:29 +02:00
ef78496d5d Add notification count down toggle option 2016-07-28 14:24:43 +02:00
41a45a14df Change tray highlight to trigger on any notification and reset on window focus
Closes #33
2016-07-27 04:48:09 +02:00
5d3721ad04 Move Import/Export/Reset buttons to Advanced tab and replace them with a hint label 2016-07-26 23:45:20 +02:00
f75bcb505c Unify Settings tab size in Designer for easier manipulation 2016-07-26 23:43:48 +02:00
fa365794a0 Rewrite plugin file methods for the JS promise system 2016-07-24 21:26:24 +02:00
a747ab700e Fix blank space in Plugin Control description when none is provided 2016-07-24 19:28:17 +02:00
b51b017005 Fix plugin script generator causing JS syntax error 2016-07-24 18:14:56 +02:00
b9e1dd5950 Add combined tray icon behavior option
Closes #46
2016-07-24 16:48:26 +02:00
56bf33229b Add a legacy notification load system option 2016-07-24 16:30:58 +02:00
5740dd8c8a Add plugin bridge object to script variable list 2016-07-24 15:35:17 +02:00
d3f205287c Reset Notification Settings example timer when switching tabs 2016-07-24 15:17:08 +02:00
53518cd6e6 Change Notification Settings labels and add edge distance label 2016-07-24 15:13:51 +02:00
0ff3896d51 Add tooltips to Settings items 2016-07-24 14:27:00 +02:00
c64e16471b Add plugin utility bridge with file functionality and a plugin token system 2016-07-23 18:36:58 +02:00
2f2e2b82b8 Reset notification mouse hook variable when disposing 2016-07-11 15:55:18 +02:00
af9a503f3c Fix call order with notification scroll focus 2016-07-10 17:33:21 +02:00
971affa607 Fix unreliable unfocused notification window scroll wheel hook 2016-07-09 21:33:46 +02:00
2de5d6206e Change notification window scrollbar width and remove border radius 2016-07-09 20:54:13 +02:00
34e0bcc56a Focus notification window when mouse wheel is scrolled above it 2016-07-09 19:37:27 +02:00
bb9f09c11f Make sure all Settings controls are disposed when closing 2016-07-09 16:46:08 +02:00
3f5cc24a10 Release 1.3 2016-07-06 15:51:08 +02:00
109a330c54 Address ReSharper inspections and refactor FormBrowser constructor 2016-07-06 15:38:47 +02:00
3e4cd3c92f Re-add the link click event to notification.js to fix certain link types 2016-07-06 02:11:45 +02:00
c6aaa4fb04 Revert "Hopefully fix a completely random bug that opened links in notifications in the popup window?"
This reverts commit 2de9570c38.
2016-07-06 02:04:26 +02:00
2de9570c38 Hopefully fix a completely random bug that opened links in notifications in the popup window? 2016-07-06 00:33:00 +02:00
3d88f57f8a Fix video element replacement generating unclickable <a> tags 2016-07-06 00:19:10 +02:00
23606f6d6c Prevent Updates Settings tab from triggering the event for update checks with ID 0 2016-07-05 21:37:15 +02:00
5a1ba1b0dc Add -log program argument to enable JS logging 2016-07-05 20:08:28 +02:00
ced5a82e07 Fix example notification in Settings not showing up the first time 2016-07-05 19:07:51 +02:00
b4fc522f37 Rewrite notification bridge handling and add OnNotificationReady to show notification after it has loaded 2016-07-05 18:31:29 +02:00
a7e222f2e7 Fix display timer setting that was broken in previous update and optimize notification resizing 2016-07-04 17:23:52 +02:00
511e5c483c Make Settings tab element location consistent 2016-07-04 16:42:40 +02:00
a28fddbc4d Replace non-functioning videos on the website with links 2016-07-04 15:37:02 +02:00
780908f777 Refactor some code and replace the only RichTextLabel use with Label 2016-07-04 03:53:44 +02:00
88e5681155 Add import, export, and reset buttons to FormSettings 2016-07-03 23:12:41 +02:00
bc197e6942 Allow Settings and Plugins context options to show up even when not logged in 2016-07-03 23:05:41 +02:00
afbfea6d43 Add methods for config reloading and resetting 2016-07-03 23:05:14 +02:00
662ee7382c Add ActiveButton property to TabPanel 2016-07-03 23:01:26 +02:00
12144a2bda Always display example notification if the Settings Notifications tab is active 2016-07-03 18:25:31 +02:00
e11f2e54b4 Push automatic designer file changes in TabSettingsNotifications 2016-07-03 18:22:33 +02:00
01cb4e32eb Rename the browser subprocess 2016-07-03 18:04:35 +02:00
1ca3541b34 Redesign and rewrite FormSettings to use TabPanel 2016-07-03 14:56:56 +02:00
e209085960 Fix errors in ControlExtensions.InvokeSafe after the previous commit 2016-07-03 14:16:14 +02:00
d1a90612c2 Change ControlExtensions.InvokeSafe to use Control instead of Form 2016-07-03 14:11:42 +02:00
0a21c61535 Rewrite FormPlugins to use the new TabPanel and remove PluginListControl 2016-07-03 03:43:42 +02:00
8583a55424 Add TabPanel and TabButton user controls 2016-07-03 03:34:22 +02:00
3a7a0f63de Redo external link handling using ILifeSpanHandler instead of hacky JS code 2016-07-02 22:39:16 +02:00
2e13d08018 Remove unnecessary code from plugins.notification.js 2016-07-02 00:05:57 +02:00
f13515ba16 Add includeDisabled parameter to PluginManager.ExecutePlugins and update calls 2016-07-02 00:05:22 +02:00
6f1884451b Fix project file after separating plugins.js and breaking solution building 2016-07-02 00:04:36 +02:00
d9321a9acb Split plugins.js into separate browser and notification scripts and refactor notification script execution code 2016-07-01 23:53:08 +02:00
19f9614c74 Add ScriptLoader.GetRootIdentifier and PluginManager.HasAnyPlugin(environment) and use them 2016-07-01 23:47:06 +02:00
66ce355571 Update Plugins Form (change default size, store state, fix title) 2016-06-30 20:00:00 +02:00
5108649ec6 Fix WindowState saving and remove IsMaximized from config 2016-06-30 19:59:23 +02:00
9308478a59 Make WindowState.Restore firstTimeFullscreen argument required 2016-06-30 19:48:57 +02:00
97d778c31c Add a firstTimeFullscreen parameter to WindowState.Restore 2016-06-30 19:47:28 +02:00
740195146b Update FormBrowser and configuration to use WindowState 2016-06-30 19:43:38 +02:00
6e0717bf22 Add a WindowState class for easier Form saving and restoring 2016-06-30 19:39:49 +02:00
b6a683dfe1 Rewrite plugins.js, plugin state handling and script execution (separate instead of combining) 2016-06-30 16:31:02 +02:00
dc78c68f12 Change starting line in script processor to 1 instead of 0 for easier debugging 2016-06-30 15:49:14 +02:00
8e01d5ec84 Minor refactoring of ScriptLoader.ExecuteFile to generate identifier automatically 2016-06-30 15:34:54 +02:00
b531016c7b Refactor ScriptLoader for easier script identifier handling 2016-06-30 03:15:46 +02:00
188369e922 Implement optional minimum TweetDuck version tag for plugins 2016-06-26 23:18:46 +02:00
347c5054f9 Fix plugin load error text containing incorrect plugin names 2016-06-26 22:55:58 +02:00
6729b597c0 Restore program from tray if ran twice
Closes #39
2016-06-26 22:28:48 +02:00
7bdbfe937c Close modal dialogs with the back mouse button
Closes #37
2016-06-26 21:59:10 +02:00
d955713335 Finish plugin reloading and add plugin counts to the UI 2016-06-25 19:42:20 +02:00
8bb69ef0ee Rework plugin manager events (move to a different namespace and change LoadError to Load) 2016-06-25 19:38:20 +02:00
d258583a7f Disable page reload in design-revert plugin 2016-06-25 18:53:03 +02:00
13d0e10dcd Implement basic plugin loading and management 2016-06-25 18:53:03 +02:00
5557f79fe7 Fix image pasting in private message reply box
Closes #38
2016-06-22 17:53:13 +02:00
44ccaa0d26 Fix link expansion in notification popup
Closes #36
2016-06-22 17:42:13 +02:00
49f12abeb3 Add plugin author and website labels and adjust height based on description 2016-06-05 02:11:17 +02:00
b1859774bc Add a website entry to design-revert plugin metadata 2016-06-05 02:10:03 +02:00
4283403f0e Add a base for plugin management including basic UI 2016-06-04 23:24:46 +02:00
379191751c Move TweetDeck design reversion into a plugin for future 2016-06-03 15:06:01 +02:00
9bf396f2a8 Prepare plugin git handling and build event scripts 2016-06-03 15:04:56 +02:00
87af4c96c4 Reorganize resources in .csproj and move scripts to Resources\Scripts\ 2016-05-28 23:05:17 +02:00
dd78a7c9de Push .csproj file after the refactoring 2016-05-28 23:02:47 +02:00
40e534090d Refactor MigrationManager and FormBackgroundWork 2016-05-28 22:53:35 +02:00
973e0c489e Address code analysis issues 2016-05-28 22:34:40 +02:00
5eb64829a5 Release 1.2.3 2016-05-28 13:43:11 +02:00
5648e9e806 Add 'Copy quoted tweet address' to context menu
Closes #29
2016-05-27 14:30:11 +02:00
2a65e20fb9 Rewrite update handling and add an update check button to Settings 2016-05-26 14:13:30 +02:00
1162e9b655 Reorganize layout elements in Settings Form for easier modifications 2016-05-25 18:49:11 +02:00
b8a5c2fb9b Add a delay to link expansion and fix tooltip issues 2016-05-25 18:34:59 +02:00
eccd37a3eb Fix clicking on tweet links both opening browser and showing the tweet in app 2016-05-25 18:21:49 +02:00
ee282713f8 Fix program not closing when updating if 'Close to tray' is enabled
Closes #34
2016-05-22 15:12:45 +02:00
c5edad9c4b Add a button to clear cache to the Settings Form 2016-05-14 15:18:57 +02:00
293c01b12f Rewrite exit procedure to only run once and refactor 2016-05-13 16:25:22 +02:00
162bbc3221 Add option to disable HTML5 hardware acceleration
Closes #30
2016-05-13 14:01:11 +02:00
9f27020993 Release 1.2.2 2016-05-12 21:09:55 +02:00
c540cf45ca Push unsaved change that was supposed to be in previous commit 2016-05-12 18:21:04 +02:00
8bf4789646 Make updates search for .Update.exe file to allow for smaller and faster updates 2016-05-12 16:58:17 +02:00
fd97c20d37 Add second tray icon for unread tweets when notifications are muted
Closes #7
2016-05-12 15:46:39 +02:00
7238e17b86 Add link expanding and tooltip support to popup notifications 2016-05-12 15:10:22 +02:00
4c44da7f4a Refactor and optimize event handling in notification.js 2016-05-12 14:25:00 +02:00
af7657e3f8 Add EventTarget.addBubbledEventListener to notification.js for convenience 2016-05-11 17:54:07 +02:00
bea158b0d9 Fix example notification not running notification.js script (breaking links) 2016-05-11 17:40:41 +02:00
40acb49011 Add scrollbar to large popup notifications and display quoted tweets in the notification
Closes #9
2016-05-11 16:59:06 +02:00
cc7fc7e994 Resize notification Form based on font size 2016-05-10 18:21:56 +02:00
ddec715dda Add support for back/forward mouse buttons 2016-05-10 13:31:18 +02:00
a1365a98c0 Pause notification timer when context menu is open, and optimize Settings notification tick 2016-05-09 20:14:23 +02:00
9a29d4ff18 Add context menu item to copy tweet address without focusing date link
Closes #24
2016-05-09 20:00:36 +02:00
39816eae76 Fix case in 'Mute notifications' context menu item 2016-05-09 18:13:01 +02:00
45a38d9570 Add tooltip for collapsed links when expanding is disabled
Closes #25
2016-05-09 14:44:04 +02:00
a230258bec Remove FormBrowser.InvokeSafe and use extension method instead 2016-05-09 13:32:20 +02:00
8543205b85 Remove 'Copy' option from TweetDeck Migration and update lnk description 2016-05-08 21:45:57 +02:00
1d169005da Hide default icon in Forms that do not have custom one properly 2016-05-08 20:57:53 +02:00
f5212d88f3 Redesign the About screen 2016-05-08 20:53:08 +02:00
03a93ad3f3 Move native methods to a NativeMethods class 2016-05-08 18:33:42 +02:00
3a66fa28ab Do minor code refactoring 2016-05-08 14:59:01 +02:00
5f79b286f1 Fix t.co bypass not working in popup notifications 2016-05-08 14:48:11 +02:00
f6a4e39cfc Add 'Skip tweet' and 'Freeze' context menu options to notifications 2016-05-08 14:24:53 +02:00
24d8444043 Tweak context menu (fix and add separators) 2016-05-08 13:08:56 +02:00
0b8205dc41 Fix simultaneous popup notification order
Closes #26
2016-05-08 12:47:36 +02:00
98197d1e86 Fix right-aligned actions menu in Direct Messages
Closes #27
2016-05-08 12:16:41 +02:00
294 changed files with 29032 additions and 4742 deletions

5
.gitattributes vendored Normal file
View File

@@ -0,0 +1,5 @@
# Auto detect text files and perform LF normalization
* text=auto
*.txt text eof=lf
*.cs diff=csharp

105
.gitignore vendored
View File

@@ -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/
@@ -47,7 +45,6 @@ artifacts/
*_p.c *_p.c
*_i.h *_i.h
*.ilk *.ilk
*.meta
*.obj *.obj
*.pch *.pch
*.pdb *.pdb
@@ -68,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
@@ -115,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
@@ -154,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/
@@ -186,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 ;-)
@@ -197,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/

View File

@@ -0,0 +1,47 @@
using System;
using TweetDuck.Data;
namespace TweetDuck.Configuration{
static class Arguments{
// public args
public const string ArgDataFolder = "-datafolder";
public const string ArgLogging = "-log";
public const string ArgIgnoreGDPR = "-nogdpr";
// 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, string defaultValue){
return Current.GetValue(key, defaultValue);
}
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("\"", "::");
}
}
}

View File

@@ -0,0 +1,128 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using TweetDuck.Configuration.Instance;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Data.Serialization;
namespace TweetDuck.Configuration{
sealed class ConfigManager{
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 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(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();
}
private void TriggerProgramRestartRequested(){
ProgramRestartRequested?.Invoke(this, EventArgs.Empty);
}
private IConfigInstance<BaseConfig> GetInstanceInfo(BaseConfig instance){
Type instanceType = instance.GetType();
return Array.Find(infoList, info => info.Instance.GetType() == instanceType); // TODO handle null
}
public abstract class BaseConfig{
private readonly ConfigManager configManager;
protected BaseConfig(ConfigManager configManager){
this.configManager = configManager;
}
// Management
public void Save(){
configManager.GetInstanceInfo(this).Save();
}
public void Reload(){
configManager.GetInstanceInfo(this).Reload();
}
public void Reset(){
configManager.GetInstanceInfo(this).Reset();
}
// Construction methods
public T ConstructWithDefaults<T>() where T : BaseConfig{
return ConstructWithDefaults(configManager) as T;
}
protected abstract BaseConfig ConstructWithDefaults(ConfigManager configManager);
// Utility methods
protected void UpdatePropertyWithEvent<T>(ref T field, T value, EventHandler eventHandler){
if (!EqualityComparer<T>.Default.Equals(field, value)){
field = value;
eventHandler?.Invoke(this, EventArgs.Empty);
}
}
protected void UpdatePropertyWithRestartRequest<T>(ref T field, T value){
if (!EqualityComparer<T>.Default.Equals(field, value)){
field = value;
configManager.TriggerProgramRestartRequested();
}
}
}
}
}

View File

@@ -0,0 +1,104 @@
using System;
using System.IO;
using TweetDuck.Data.Serialization;
namespace TweetDuck.Configuration.Instance{
sealed class FileConfigInstance<T> : IConfigInstance<T> where T : ConfigManager.BaseConfig{
private const string ErrorTitle = "Configuration Error";
public T Instance { get; }
public FileSerializer<T> Serializer { get; }
private readonly string filenameMain;
private readonly string filenameBackup;
private readonly string errorIdentifier;
public FileConfigInstance(string filename, T instance, string errorIdentifier){
this.filenameMain = filename;
this.filenameBackup = filename+".bak";
this.errorIdentifier = errorIdentifier;
this.Instance = instance;
this.Serializer = new FileSerializer<T>();
}
private void LoadInternal(bool backup){
Serializer.Read(backup ? filenameBackup : filenameMain, Instance);
}
public void Load(){
Exception firstException = null;
for(int attempt = 0; attempt < 2; attempt++){
try{
LoadInternal(attempt > 0);
if (firstException != null){ // silently log exception that caused a backup restore
Program.Reporter.Log(firstException.ToString());
}
return;
}catch(FileNotFoundException){
}catch(DirectoryNotFoundException){
break;
}catch(Exception e){
if (firstException == null){
firstException = e;
}
}
}
if (firstException is FormatException){
Program.Reporter.HandleException(ErrorTitle, "The configuration file for "+errorIdentifier+" is outdated or corrupted. If you continue, your "+errorIdentifier+" will be reset.", true, firstException);
}
else if (firstException is SerializationSoftException sse){
Program.Reporter.HandleException(ErrorTitle, $"{sse.Errors.Count} error{(sse.Errors.Count == 1 ? " was" : "s were")} encountered while loading the configuration file for "+errorIdentifier+". If you continue, some of your "+errorIdentifier+" will be reset.", true, firstException);
}
else if (firstException != null){
Program.Reporter.HandleException(ErrorTitle, "Could not open the configuration file for "+errorIdentifier+". If you continue, your "+errorIdentifier+" will be reset.", true, firstException);
}
}
public void Save(){
try{
if (File.Exists(filenameMain)){
File.Delete(filenameBackup);
File.Move(filenameMain, filenameBackup);
}
Serializer.Write(filenameMain, Instance);
}catch(SerializationSoftException e){
Program.Reporter.HandleException(ErrorTitle, $"{e.Errors.Count} error{(e.Errors.Count == 1 ? " was" : "s were")} encountered while saving the configuration file for "+errorIdentifier+".", true, e);
}catch(Exception e){
Program.Reporter.HandleException(ErrorTitle, "Could not save the configuration file for "+errorIdentifier+".", true, e);
}
}
public void Reload(){
try{
LoadInternal(false);
}catch(FileNotFoundException){
try{
Serializer.Write(filenameMain, Instance.ConstructWithDefaults<T>());
LoadInternal(false);
}catch(Exception e){
Program.Reporter.HandleException(ErrorTitle, "Could not regenerate the configuration file for "+errorIdentifier+".", true, e);
}
}catch(Exception e){
Program.Reporter.HandleException(ErrorTitle, "Could not reload the configuration file for "+errorIdentifier+".", true, e);
}
}
public void Reset(){
try{
File.Delete(filenameMain);
File.Delete(filenameBackup);
}catch(Exception e){
Program.Reporter.HandleException(ErrorTitle, "Could not delete configuration files to reset "+errorIdentifier+".", true, e);
return;
}
Reload();
}
}
}

View File

@@ -0,0 +1,9 @@
namespace TweetDuck.Configuration.Instance{
interface IConfigInstance<out T>{
T Instance { get; }
void Save();
void Reload();
void Reset();
}
}

View File

@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace TweetDuck.Configuration.Instance{
class PluginConfigInstance : IConfigInstance<PluginConfig>{
public PluginConfig Instance { get; }
private readonly string filename;
public PluginConfigInstance(string filename, PluginConfig instance){
this.filename = filename;
this.Instance = instance;
}
public void Load(){
try{
using(StreamReader reader = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), Encoding.UTF8)){
string line = reader.ReadLine();
if (line == "#Disabled"){
HashSet<string> newDisabled = new HashSet<string>();
while((line = reader.ReadLine()) != null){
newDisabled.Add(line);
}
Instance.ReloadSilently(newDisabled);
}
}
}catch(FileNotFoundException){
}catch(DirectoryNotFoundException){
}catch(Exception e){
Program.Reporter.HandleException("Plugin Configuration Error", "Could not read the plugin configuration file. If you continue, the list of disabled plugins will be reset to default.", true, e);
}
}
public void Save(){
try{
using(StreamWriter writer = new StreamWriter(new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None), Encoding.UTF8)){
writer.WriteLine("#Disabled");
foreach(string identifier in Instance.DisabledPlugins){
writer.WriteLine(identifier);
}
}
}catch(Exception e){
Program.Reporter.HandleException("Plugin Configuration Error", "Could not save the plugin configuration file.", true, e);
}
}
public void Reload(){
Load();
}
public void Reset(){
try{
File.Delete(filename);
Instance.ReloadSilently(Instance.ConstructWithDefaults<PluginConfig>().DisabledPlugins);
}catch(Exception e){
Program.Reporter.HandleException("Plugin Configuration Error", "Could not delete the plugin configuration file.", true, e);
return;
}
Reload();
}
}
}

View File

@@ -1,10 +1,18 @@
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 TweetDuck.Configuration{
sealed class LockManager{
private const int RetryDelay = 250;
public enum Result{
Success, HasProcess, Fail
}
namespace TweetDck.Configuration{
class LockManager{
private readonly string file; private readonly string file;
private FileStream lockStream; private FileStream lockStream;
private Process lockingProcess; private Process lockingProcess;
@@ -13,93 +21,141 @@ namespace TweetDck.Configuration{
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.");
}
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;
}catch(Exception){
if (lockStream != null){
lockStream.Close();
lockStream.Dispose(); lockStream.Dispose();
lockStream = null;
return true;
} }
else{
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{
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{ try{
Process foundProcess = Process.GetProcessById(pid); Process foundProcess = Process.GetProcessById(pid);
using(Process currentProcess = Process.GetCurrentProcess()){ using(Process currentProcess = Process.GetCurrentProcess()){
if (foundProcess.ProcessName == currentProcess.ProcessName){ if (foundProcess.MainModule.FileVersionInfo.InternalName == currentProcess.MainModule.FileVersionInfo.InternalName){
lockingProcess = foundProcess; lockingProcess = foundProcess;
} }
else{
currentProcess.Close(); foundProcess.Close();
} }
}catch(ArgumentException){}
return lockingProcess == null && CreateLockFile();
}catch(DirectoryNotFoundException){
string dir = Path.GetDirectoryName(file);
if (dir != null){
Directory.CreateDirectory(dir);
return CreateLockFile();
} }
}catch(FileNotFoundException){ }catch{
return CreateLockFile(); // GetProcessById throws ArgumentException if the process is missing
}catch(Exception){ // Process.MainModule can throw exceptions in some cases
return false;
} }
return false; return lockingProcess == null ? Result.Fail : Result.HasProcess;
}catch{
return Result.Fail;
}
} }
public void Unlock(){ return initialResult;
if (lockStream != null){ }
lockStream.Dispose();
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(){
if (ReleaseLockFileStream()){
try{
File.Delete(file); File.Delete(file);
}catch(Exception e){
lockStream = null; Program.Reporter.Log(e.ToString());
return false;
} }
} }
public bool CloseLockingProcess(int timeout){ return true;
}
// Locking process
public bool RestoreLockingProcess(int failTimeout){
if (lockingProcess != null && lockingProcess.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
NativeMethods.BroadcastMessage(Program.WindowRestoreMessage, (uint)lockingProcess.Id, 0);
if (WindowsUtils.TrySleepUntil(() => CheckLockingProcessExited() || (lockingProcess.MainWindowHandle != IntPtr.Zero && lockingProcess.Responding), failTimeout, RetryDelay)){
return true;
}
}
return false;
}
public bool CloseLockingProcess(int closeTimeout, int killTimeout){
if (lockingProcess != null){ if (lockingProcess != null){
lockingProcess.CloseMainWindow(); try{
if (lockingProcess.CloseMainWindow()){
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, closeTimeout, RetryDelay);
}
for(int waited = 0; waited < timeout && !lockingProcess.HasExited;){ if (!lockingProcess.HasExited){
lockingProcess.Refresh(); lockingProcess.Kill();
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, killTimeout, RetryDelay);
Thread.Sleep(100);
waited += 100;
} }
if (lockingProcess.HasExited){ if (lockingProcess.HasExited){
@@ -107,9 +163,21 @@ namespace TweetDck.Configuration{
lockingProcess = null; lockingProcess = null;
return true; return true;
} }
}catch(Exception ex) when (ex is InvalidOperationException || ex is Win32Exception){
if (lockingProcess != null){
bool hasExited = CheckLockingProcessExited();
lockingProcess.Dispose();
return hasExited;
}
}
} }
return false; return false;
} }
private bool CheckLockingProcessExited(){
lockingProcess.Refresh();
return lockingProcess.HasExited;
}
} }
} }

View File

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using TweetDuck.Plugins;
using TweetDuck.Plugins.Events;
namespace TweetDuck.Configuration{
sealed class PluginConfig : ConfigManager.BaseConfig, IPluginConfig{
private static readonly string[] DefaultDisabled = {
"official/clear-columns",
"official/reply-account"
};
// CONFIGURATION
public IEnumerable<string> DisabledPlugins => disabled;
public event EventHandler<PluginChangedStateEventArgs> PluginChangedState;
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);
}
public void ReloadSilently(IEnumerable<string> newDisabled){
disabled.Clear();
disabled.UnionWith(newDisabled);
}
private readonly HashSet<string> disabled = new HashSet<string>(DefaultDisabled);
// END OF CONFIG
public PluginConfig(ConfigManager configManager) : base(configManager){}
protected override ConfigManager.BaseConfig ConstructWithDefaults(ConfigManager configManager){
return new PluginConfig(configManager);
}
}
}

View File

@@ -0,0 +1,26 @@
namespace TweetDuck.Configuration{
sealed class SystemConfig : ConfigManager.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(ConfigManager configManager) : base(configManager){}
protected override ConfigManager.BaseConfig ConstructWithDefaults(ConfigManager configManager){
return new SystemConfig(configManager);
}
}
}

View File

@@ -1,199 +1,143 @@
using System; using System;
using System.Drawing; using System.Drawing;
using System.IO; using TweetDuck.Core.Controls;
using System.Runtime.Serialization; using TweetDuck.Core.Notification;
using System.Runtime.Serialization.Formatters.Binary; using TweetDuck.Core.Other;
using TweetDck.Core; using TweetDuck.Core.Utils;
using TweetDck.Core.Handling; using TweetDuck.Data;
namespace TweetDck.Configuration{ namespace TweetDuck.Configuration{
[Serializable] sealed class UserConfig : ConfigManager.BaseConfig{
sealed class UserConfig{
private static readonly IFormatter Formatter = new BinaryFormatter(){
Binder = new SerializationCompatibilityHandler()
};
private const int CurrentFileVersion = 2; // CONFIGURATION DATA
// START OF CONFIGURATION public bool FirstRun { get; set; } = true;
public bool AllowDataCollection { get; set; } = false;
public bool IgnoreMigration { get; set; } public WindowState BrowserWindow { get; set; } = new WindowState();
public bool IgnoreUninstallCheck { get; set; } public Size PluginsWindowSize { get; set; } = Size.Empty;
public bool IsMaximized { get; set; } public bool ExpandLinksOnHover { get; set; } = true;
public Point WindowLocation { get; set; } public bool OpenSearchInFirstColumn { get; set; } = true;
public Size WindowSize { get; set; } public bool KeepLikeFollowDialogsOpen { get; set; } = true;
public bool DisplayNotificationTimer { get; set; } public bool BestImageQuality { get; set; } = true;
public bool EnableAnimatedImages { get; set; } = true;
public TweetNotification.Duration NotificationDuration { get; set; } private bool _enableSmoothScrolling = true;
public TweetNotification.Position NotificationPosition { get; set; } private bool _enableTouchAdjustment = false;
public Point CustomNotificationPosition { get; set; } private string _customCefArgs = null;
public int NotificationEdgeDistance { get; set; }
public int NotificationDisplay { get; set; }
public bool EnableUpdateCheck { get; set; } public string BrowserPath { get; set; } = null;
public string DismissedUpdate { get; set; } public bool IgnoreTrackingUrlWarning { get; set; } = false;
public bool ExpandLinksOnHover { get; set; } public string SearchEngineUrl { get; set; } = null;
private int _zoomLevel = 100;
public bool IsCustomWindowLocationSet{ public int VideoPlayerVolume { get; set; } = 50;
get{
return WindowLocation.X != -32000 && WindowLocation.X != 32000; public bool EnableSpellCheck { get; set; } = false;
} private string _spellCheckLanguage = "en-US";
public string TranslationTarget { get; set; } = "en";
private TrayIcon.Behavior _trayBehavior = TrayIcon.Behavior.Disabled;
public bool EnableTrayHighlight { get; set; } = true;
public bool EnableUpdateCheck { get; set; } = true;
public string DismissedUpdate { get; set; } = null;
public bool DisplayNotificationColumn { get; set; } = false;
public bool NotificationMediaPreviews { get; set; } = true;
public bool NotificationSkipOnLinkClick { get; set; } = false;
public bool NotificationNonIntrusiveMode { get; set; } = true;
public int NotificationIdlePauseSeconds { get; set; } = 0;
public bool DisplayNotificationTimer { get; set; } = true;
public bool NotificationTimerCountDown { get; set; } = false;
public int NotificationDurationValue { get; set; } = 25;
public 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 TwitterUtils.ImageQuality TwitterImageQuality => BestImageQuality ? TwitterUtils.ImageQuality.Orig : TwitterUtils.ImageQuality.Default;
public string NotificationSoundPath{
get => _notificationSoundPath ?? string.Empty;
set => UpdatePropertyWithEvent(ref _notificationSoundPath, value, SoundNotificationChanged);
} }
public bool IsCustomNotificationPositionSet{ public int NotificationSoundVolume{
get{ get => _notificationSoundVolume;
return CustomNotificationPosition.X != -32000 && CustomNotificationPosition.X != 32000; set => UpdatePropertyWithEvent(ref _notificationSoundVolume, value, SoundNotificationChanged);
}
} }
public bool MuteNotifications{ public bool MuteNotifications{
get{ get => _muteNotifications;
return muteNotifications; set => UpdatePropertyWithEvent(ref _muteNotifications, value, MuteToggled);
} }
set{ public int ZoomLevel{
if (muteNotifications == value)return; get => _zoomLevel;
set => UpdatePropertyWithEvent(ref _zoomLevel, value, ZoomLevelChanged);
muteNotifications = value;
if (MuteToggled != null){
MuteToggled(this,new EventArgs());
}
}
} }
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; public UserConfig(ConfigManager configManager) : base(configManager){}
private bool muteNotifications;
private TrayIcon.Behavior trayBehavior;
private UserConfig(string file){ protected override ConfigManager.BaseConfig ConstructWithDefaults(ConfigManager configManager){
this.file = file; return new UserConfig(configManager);
IsMaximized = true;
DisplayNotificationTimer = true;
WindowLocation = new Point(-32000,-32000);
NotificationDuration = TweetNotification.Duration.Medium;
NotificationPosition = TweetNotification.Position.TopRight;
CustomNotificationPosition = new Point(-32000,-32000);
NotificationEdgeDistance = 8;
EnableUpdateCheck = true;
ExpandLinksOnHover = true;
}
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;
}
// 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.HandleException("Could not save the configuration file.",e);
return false;
}
}
public static UserConfig Load(string file){
UserConfig config = null;
for(int attempt = 0; attempt < 2; attempt++){
try{
using(Stream stream = new FileStream(attempt == 0 ? file : GetBackupFile(file),FileMode.Open,FileAccess.Read,FileShare.Read)){
if ((config = Formatter.Deserialize(stream) as UserConfig) != null){
config.file = file;
}
}
if (config != null){
config.UpgradeFile();
}
break;
}catch(FileNotFoundException){
}catch(Exception e){
Program.HandleException("Could not open the configuration file.",e);
}
}
return config ?? new UserConfig(file);
}
private static string GetBackupFile(string file){
return file+".bak";
}
private class SerializationCompatibilityHandler : SerializationBinder{
public override Type BindToType(string assemblyName, string typeName){
#if DUCK
assemblyName = assemblyName.Replace("TweetDick","TweetDuck");
#else
assemblyName = assemblyName.Replace("TweetDuck","TweetDick");
#endif
typeName = typeName.Replace("TweetDick","TweetDck");
return Type.GetType(string.Format("{0}, {1}",typeName,assemblyName));
}
} }
} }
} }

View File

@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0,Profile=Client"/></startup></configuration>

View File

@@ -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.2623.1396" targetFramework="net40-Client" />
<package id="cef.redist.x86" version="3.2623.1396" targetFramework="net40-Client" />
<package id="CefSharp.Common" version="49.0.0-pre02" targetFramework="net40-Client" />
<package id="CefSharp.WinForms" version="49.0.0-pre02" targetFramework="net40-Client" />
<package id="Microsoft.VC120.CRT.JetBrains" version="12.0.21005.2" targetFramework="net40-Client" />
</packages>

View File

@@ -0,0 +1,34 @@
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.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();
}
}
}

View File

@@ -0,0 +1,139 @@
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{
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;
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;
}
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
}
}
}

View File

@@ -0,0 +1,68 @@
using System;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Updates;
namespace TweetDuck.Core.Bridge{
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;
}
}
}

View File

@@ -1,18 +1,35 @@
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 void InvokeSafe(this Form form, Action func){ public static readonly Point InvisibleLocation = new Point(-32000, -32000);
if (form.InvokeRequired){
form.Invoke(func); public static void InvokeSafe(this Control control, Action func){
if (control.InvokeRequired){
control.Invoke(func);
} }
else{ else{
func(); func();
} }
} }
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);
} }
@@ -28,5 +45,35 @@ namespace TweetDck.Core.Controls{
bar.Value = value; bar.Value = value;
} }
} }
public static void SetValueSafe(this NumericUpDown numUpDown, int value){
if (value >= numUpDown.Minimum && value <= numUpDown.Maximum){
numUpDown.Value = value;
}
}
public static void SetValueSafe(this TrackBar trackBar, int value){
if (value >= trackBar.Minimum && value <= trackBar.Maximum){
trackBar.Value = value;
}
}
public static bool AlignValueToTick(this TrackBar trackBar){
if (trackBar.Value % trackBar.SmallChange != 0){
trackBar.Value = trackBar.SmallChange*(int)Math.Floor(((double)trackBar.Value/trackBar.SmallChange)+0.5);
return false;
}
else return true;
}
public static void EnableMultilineShortcuts(this TextBox textBox){
textBox.KeyDown += (sender, args) => {
if (args.Control && args.KeyCode == Keys.A){
((TextBox)sender).SelectAll();
args.SuppressKeyPress = true;
args.Handled = true;
}
};
}
} }
} }

View File

@@ -0,0 +1,16 @@
using System;
using System.Windows.Forms;
namespace TweetDuck.Core.Controls{
sealed class FlatButton : Button{
protected override bool ShowFocusCues => false;
public FlatButton(){
GotFocus += FlatButton_GotFocus;
}
private void FlatButton_GotFocus(object sender, EventArgs e){ // removes extra border when focused
NotifyDefault(false);
}
}
}

View File

@@ -1,33 +0,0 @@
namespace TweetDck.Core.Controls {
partial class FlatProgressBar {
/// <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 (brush != null)brush.Dispose();
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() {
components = new System.ComponentModel.Container();
}
#endregion
}
}

View File

@@ -1,27 +1,38 @@
using System.Drawing; using System;
using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
namespace TweetDck.Core.Controls{ namespace TweetDuck.Core.Controls{
public partial class FlatProgressBar : ProgressBar{ sealed class FlatProgressBar : ProgressBar{
private SolidBrush brush; private readonly SolidBrush brush;
public FlatProgressBar(){ public FlatProgressBar(){
brush = new SolidBrush(Color.White);
SetStyle(ControlStyles.UserPaint, true); SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true); SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
} }
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){
if (brush == null || brush.Color != ForeColor){ if (brush.Color != ForeColor){
brush = new SolidBrush(ForeColor); brush.Color = ForeColor;
} }
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){
base.Dispose(disposing);
if (disposing){
brush.Dispose();
}
}
} }
} }

View 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;
}
}
}
}
}

View 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;
}
}
}
}

View File

@@ -1,58 +0,0 @@
using System;
using System.Windows.Forms;
namespace TweetDck.Core.Controls{
public partial class RichTextLabel : RichTextBox{
/// <summary>
/// Wraps the body of a RTF formatted string with default tags and formatting.
/// </summary>
public static string Wrap(string str){
string rtf = @"{\rtf1\ansi\ansicpg1250\deff0\deflang1029{\fonttbl{\f0\fnil\fcharset0 Microsoft Sans Serif;}}";
rtf += @"{\*\generator Msftedit 4.20.69.1337;}\viewkind4\uc1\pard\sa200\sl276\slmult1\lang1036\f0\fs16 ";
rtf += str;
return rtf;
}
/// <summary>
/// Wraps URL tags around a link.
/// </summary>
public static string AddLink(string url){
return @"{\field{\*\fldinst{HYPERLINK """+url+@"""}}{\fldrslt{\ul\cf1 "+url+@"}}}";
}
/// <summary>
/// Uses v5 of RichTextBox, which fixes URLs and other crap.
/// </summary>
protected override CreateParams CreateParams{
get{
CreateParams createParams = base.CreateParams;
if (Program.LoadLibrary("msftedit.dll") != IntPtr.Zero){
createParams.ClassName = "RICHEDIT50W";
}
return createParams;
}
}
public RichTextLabel(){
InitializeComponent();
SetStyle(ControlStyles.Selectable,false);
SetStyle(ControlStyles.UserMouse,true);
SetStyle(ControlStyles.SupportsTransparentBackColor,true);
}
private void RichTextLabel_MouseEnter(object sender, EventArgs e){
Cursor = Cursors.Default;
}
protected override void WndProc(ref Message m){
if (m.Msg == 0x204 || m.Msg == 0x205){ // WM_RBUTTONDOWN, WM_RBUTTONUP
return;
}
base.WndProc(ref m);
}
}
}

View File

@@ -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.
@@ -23,26 +23,33 @@
/// 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(FormBrowser)); 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.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.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.ClientSize = new System.Drawing.Size(1008, 730);
this.Location = new System.Drawing.Point(-32000, -32000); this.Icon = Properties.Resources.icon;
this.MinimumSize = new System.Drawing.Size(340, 424); this.Location = TweetDuck.Core.Controls.ControlExtensions.InvisibleLocation;
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.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);
@@ -50,7 +57,9 @@
#endregion #endregion
private TrayIcon trayIcon; private TweetDuck.Core.Other.TrayIcon trayIcon;
private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.Timer timerResize;
} }
} }

View File

@@ -1,108 +1,168 @@
using System; using System;
using System.Linq; using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp; using TweetDuck.Configuration;
using CefSharp.WinForms; using TweetDuck.Core.Bridge;
using TweetDck.Configuration; using TweetDuck.Core.Controls;
using TweetDck.Core.Handling; using TweetDuck.Core.Handling;
using TweetDck.Core.Other; using TweetDuck.Core.Handling.General;
using TweetDck.Resources; using TweetDuck.Core.Management;
using TweetDck.Core.Utils; using TweetDuck.Core.Notification;
using TweetDck.Core.Controls; 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.Plugins.Events;
using TweetDuck.Resources;
using TweetDuck.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;
private readonly ChromiumWebBrowser browser; public AnalyticsFile AnalyticsFile => analytics?.File ?? AnalyticsFile.Dummy;
private readonly TweetDeckBridge bridge;
private readonly FormNotification notification; private readonly TweetDeckBrowser browser;
private readonly PluginManager plugins;
private readonly UpdateHandler updates;
private readonly FormNotificationTweet notification;
private readonly ContextMenu contextMenu;
private readonly UpdateBridge updateBridge;
private FormSettings currentFormSettings;
private FormAbout currentFormAbout;
private bool isLoaded; private bool isLoaded;
private FormWindowState prevState; private FormWindowState prevState;
private TweetScreenshotManager notificationScreenshotManager;
private VideoPlayer videoPlayer;
private AnalyticsManager analytics;
public FormBrowser(){ public FormBrowser(){
InitializeComponent(); InitializeComponent();
Text = Program.BrandName; Text = Program.BrandName;
bridge = new TweetDeckBridge(this); this.plugins = new PluginManager(Program.Config.Plugins, Program.PluginPath);
this.plugins.Reloaded += plugins_Reloaded;
this.plugins.Executed += plugins_Executed;
this.plugins.Reload();
browser = new ChromiumWebBrowser("https://tweetdeck.twitter.com/"){ this.notification = new FormNotificationTweet(this, plugins);
MenuHandler = new ContextMenuBrowser(this), this.notification.Show();
DialogHandler = new DialogHandlerBrowser(this)
this.updates = new UpdateHandler(Program.InstallerPath);
this.updates.CheckFinished += updates_CheckFinished;
this.updateBridge = new UpdateBridge(updates, this);
this.updateBridge.UpdateAccepted += updateBridge_UpdateAccepted;
this.updateBridge.UpdateDelayed += updateBridge_UpdateDelayed;
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();
}; };
browser.LoadingStateChanged += Browser_LoadingStateChanged; Config.MuteToggled += Config_MuteToggled;
browser.FrameLoadEnd += Browser_FrameLoadEnd;
browser.RegisterJsObject("$TD",bridge);
Controls.Add(browser); this.trayIcon.ClickRestore += trayIcon_ClickRestore;
this.trayIcon.ClickClose += trayIcon_ClickClose;
Disposed += (sender, args) => browser.Dispose();
trayIcon.ClickRestore += trayIcon_ClickRestore;
trayIcon.ClickClose += trayIcon_ClickClose;
Config.TrayBehaviorChanged += Config_TrayBehaviorChanged; Config.TrayBehaviorChanged += Config_TrayBehaviorChanged;
UpdateTrayIcon(); UpdateTray();
notification = new FormNotification(this,bridge,true){ CanMoveWindow = () => false }; if (Config.MuteNotifications){
notification.Show(); 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.MoveToCenter(this); }
public void ForceClose(){
trayIcon.Visible = false; // checked in FormClosing event
Close();
} }
// window setup // window setup
private void SetupWindow(){ private void RestoreWindow(){
if (Config.IsCustomWindowLocationSet){ Config.BrowserWindow.Restore(this, true);
Location = Config.WindowLocation;
Size = Config.WindowSize;
WindowState = Config.IsMaximized ? FormWindowState.Maximized : FormWindowState.Normal;
}
if (!Config.IsCustomWindowLocationSet || !Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(Bounds))){
Location = Screen.PrimaryScreen.WorkingArea.Location;
Size = Screen.PrimaryScreen.WorkingArea.Size;
WindowState = FormWindowState.Maximized;
}
prevState = WindowState; prevState = WindowState;
isLoaded = true; isLoaded = true;
} }
private void UpdateTrayIcon(){ private void UpdateFormIcon(){ // TODO fix to show icon in taskbar too
trayIcon.Visible = Config.TrayBehavior != TrayIcon.Behavior.Disabled; Icon = Config.MuteNotifications ? Properties.Resources.icon_muted : Properties.Resources.icon;
} }
// active event handlers private void UpdateTray(){
trayIcon.Visible = Config.TrayBehavior.ShouldDisplayIcon();
private void Browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
if (!e.IsLoading){
Invoke(new Action(SetupWindow));
browser.LoadingStateChanged -= Browser_LoadingStateChanged;
}
} }
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){ // event handlers
if (e.Frame.IsMain){
foreach(string js in ScriptLoader.LoadResources("code.js","update.js").Where(js => js != null)){ private void timerResize_Tick(object sender, EventArgs e){
browser.ExecuteScriptAsync(js); FormBrowser_ResizeEnd(this, e); // also stops timer
} }
private void FormBrowser_Activated(object sender, EventArgs e){
if (!isLoaded)return;
trayIcon.HasNotifications = false;
if (!browser.Enabled){ // when taking a screenshot, the window is unfocused and
browser.Enabled = true; // the browser is disabled; if the user clicks back into
} // the window, enable the browser again
} }
private void FormBrowser_LocationChanged(object sender, EventArgs e){
if (!isLoaded)return;
timerResize.Stop();
timerResize.Start();
} }
private void FormBrowser_Resize(object sender, EventArgs e){ private void FormBrowser_Resize(object sender, EventArgs e){
@@ -112,7 +172,7 @@ namespace TweetDck.Core{
prevState = WindowState; prevState = WindowState;
if (WindowState == FormWindowState.Minimized){ if (WindowState == FormWindowState.Minimized){
if (Config.TrayBehavior == TrayIcon.Behavior.MinimizeToTray){ if (Config.TrayBehavior.ShouldHideOnMinimize()){
Hide(); // hides taskbar too?! welp that works I guess Hide(); // hides taskbar too?! welp that works I guess
} }
} }
@@ -120,19 +180,19 @@ 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();
Config.IsMaximized = WindowState == FormWindowState.Maximized;
if (WindowState == FormWindowState.Normal || (WindowState == FormWindowState.Maximized && !Screen.FromControl(this).Equals(Screen.FromPoint(Config.WindowLocation)))){
Config.WindowLocation = Location;
Config.WindowSize = Size;
}
if (Location != ControlExtensions.InvisibleLocation){
Config.BrowserWindow.Save(this);
Config.Save(); Config.Save();
} }
} }
@@ -140,108 +200,375 @@ namespace TweetDck.Core{
private void FormBrowser_FormClosing(object sender, FormClosingEventArgs e){ private void FormBrowser_FormClosing(object sender, FormClosingEventArgs e){
if (!isLoaded)return; if (!isLoaded)return;
if (Config.TrayBehavior == TrayIcon.Behavior.CloseToTray && trayIcon.Visible && e.CloseReason == CloseReason.UserClosing){ if (Config.TrayBehavior.ShouldHideOnClose() && trayIcon.Visible && e.CloseReason == CloseReason.UserClosing){
Hide(); // hides taskbar too?! welp that works I guess Hide(); // hides taskbar too?! welp that works I guess
e.Cancel = true; e.Cancel = true;
} }
} }
private void Config_TrayBehaviorChanged(object sender, EventArgs e){ private void FormBrowser_FormClosed(object sender, FormClosedEventArgs e){
if (!isLoaded)return; if (isLoaded && UpdateInstallerPath == null){
updateBridge.Cleanup();
}
}
UpdateTrayIcon(); private void Config_MuteToggled(object sender, EventArgs e){
UpdateFormIcon();
AnalyticsFile.NotificationMutes.Trigger();
}
private void Config_TrayBehaviorChanged(object sender, EventArgs e){
UpdateTray();
} }
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();
}
trayIcon.Visible = false; // checked in FormClosing event private void plugins_Reloaded(object sender, PluginErrorEventArgs e){
Close(); 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);
}
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{
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){
if (isLoaded && m.Msg == Program.WindowRestoreMessage){
if (WindowsUtils.CurrentProcessID == m.WParam.ToInt32()){
trayIcon_ClickRestore(trayIcon, EventArgs.Empty);
}
return;
}
if (browser.Ready && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN){
if (videoPlayer != null && videoPlayer.Running){
videoPlayer.Close();
}
else{
browser.OnMouseClickExtra(m.WParam);
AnalyticsFile.BrowserExtraMouseButtons.Trigger();
}
return;
}
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 InvokeSafe(Action func){ public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){
ControlExtensions.InvokeSafe(this,func); 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); FormSettings form = new FormSettings(this, plugins, updates, analytics, startTab);
currentFormSettings.FormClosed += (sender, args) => {
currentFormSettings = null;
form.FormClosed += (sender, args) => {
if (!prevEnableUpdateCheck && Config.EnableUpdateCheck){ if (!prevEnableUpdateCheck && Config.EnableUpdateCheck){
Config.DismissedUpdate = string.Empty; Config.DismissedUpdate = null;
Config.Save(); Config.Save();
browser.ExecuteScriptAsync("TDGF_runUpdateCheck",new object[0]); updates.Check(true);
} }
if (!Config.EnableTrayHighlight){
trayIcon.HasNotifications = false;
}
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();
}; };
ShowChildForm(currentFormSettings); 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 OnTweetPopup(TweetNotification tweet){ public void OpenPlugins(){
notification.ShowNotification(tweet); if (!FormManager.TryBringToFront<FormPlugins>()){
AnalyticsFile.OpenPlugins.Trigger();
ShowChildForm(new FormPlugins(plugins));
}
}
public void OpenProfileImport(){
FormManager.TryFind<FormSettings>()?.Close();
using(DialogSettingsManage dialog = new DialogSettingsManage(plugins, true)){
if (dialog.ShowDialog() == DialogResult.OK && !dialog.IsRestarting){
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){
trayIcon.HasNotifications = true;
}
} }
public void OnTweetSound(){ public void OnTweetSound(){
AnalyticsFile.SoundNotifications.Trigger();
} }
public void OnImagePasted(){ public void PlayVideo(string url, string username){
browser.ExecuteScriptAsync("TDGF_tryPasteImage",new object[0]); if (string.IsNullOrEmpty(url)){
videoPlayer?.Close();
return;
} }
public void OnImagePastedFinish(){ if (videoPlayer == null){
browser.ExecuteScriptAsync("TDGF_tryPasteImageFinish",new object[0]); videoPlayer = new VideoPlayer(this);
videoPlayer.ProcessExited += (sender, args) => {
browser.HideVideoOverlay(true);
};
} }
public void BeginUpdateProcess(string versionTag, string downloadUrl){ videoPlayer.Launch(url, username);
Hide(); AnalyticsFile.VideoPlays.Trigger();
FormUpdateDownload downloadForm = new FormUpdateDownload(new UpdateInfo(versionTag,downloadUrl));
downloadForm.MoveToCenter(this);
downloadForm.ShowDialog();
if (downloadForm.UpdateStatus == FormUpdateDownload.Status.Succeeded){
UpdateInstallerPath = downloadForm.InstallerPath;
Close();
} }
else if (downloadForm.UpdateStatus == FormUpdateDownload.Status.Manual){
Close(); 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){
if (string.IsNullOrEmpty(text)){
toolTip.Hide(this);
} }
else{ else{
Show(); Point position = PointToClient(Cursor.Position);
position.Offset(20, 10);
toolTip.Show(text, this, position);
} }
} }
} }

View File

@@ -120,261 +120,10 @@
<metadata name="trayIcon.TrayLocation" type="System.Drawing.Point, 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">
<value>17, 17</value> <value>17, 17</value>
</metadata> </metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> <metadata name="toolTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <value>112, 17</value>
<value> </metadata>
AAABAAMAMDAAAAEAIACoJQAANgAAACAgAAABACAAqBAAAN4lAAAQEAAAAQAgAGgEAACGNgAAKAAAADAA <metadata name="timerResize.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
AABgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA <value>202, 17</value>
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqHg5IrF+OlS+hjyMw4g7tcmM </metadata>
PM/MjTzezY485s2OPObMjjzfyIw80MOJPLe8hDyNs387WKx8OyQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApHU4FrB8OGXGijzAzo889dKR
PP/SkTz/0pE8/9GRPP/RkDz/0JA8/9CQPP/RkDz/0ZE8/9KRPP/TkTz/0pE8/86PPPbGijzCt4E8bKp6
OxoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKN1OB+9hTuLzI486NOR
PP/SkTz/0ZA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GQ
PP/SkTz/0pE8/82OPOu9hTuOq3w8IgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACidTkFuoM8csyN
POrTkjz/0ZE8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GRPP/Tkjz/zY887bqEPHimeDsHAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKp6
OynDiDvA05I8/9GRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0ZA8/9ORPP/GijzFr308LgAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAArXw7UM2OPOvTkTz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/SkTz/zo887rOAPFYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAACxfjxm0ZA8/NGRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0ZE8/9GQPP64gjxtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAALaBPGXRkTz/0ZE8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GQPP/SkTz/uIM8bAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqnk6TtCQPPzRkTz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkDz/0ZA8/rSA
PFUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACtfDwmzo887dKRPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0ZE8/86PPPGtfDwrAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKZ4PAXFijzC0pE8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CURf/TpGT/2baJ/9/GpP/l0bf/59W//+fVvf/kz7P/3sOg/9mz
hP/ToWH/0JND/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9KRPP/EiTvJo3Y7CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALiC
PG/Tkjz/0JA8/9CQPP/QkDz/0JA8/9CSQf/Vq3P/5tO7//j07v//////////////////////////////
////////////////////////9vHq/+TQtf/UqG7/0JA+/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/Tkjz/uYM8dgAAAAAAAAAAAAAAAAAA
AAAAAAAArXw8Gc2OPOzRkTz/0JA8/9CQPP/QlEX/27qP//fz7P//////////////////////////////
////////////////////////////////////////////////////////8ObZ/9auev/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkTz/zI488J1x
OB8AAAAAAAAAAAAAAAAAAAAAuoM7h9OSPP/QkDz/0JA8/9GXTf/TpGb/27uS/9u6kP/ewZz/59W9//Xv
5///////////////////////////////////////////////////////////////////////////////
///r3cr/0ZxV/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/1JI8/7mDO5AAAAAAAAAAAAAAAADFj0cR0pNB6s+PO//Ojjv/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CRQP/TpGf/5NC2//38+v//////////////////////////////////////////////
////////////////////////+vf0/9auev/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0ZE8/8uOPO6SajYVAAAAAAAAAADzr1df7qxV/+WkT//ZmUT/0JA9/86O
O//Pjzv/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GaUv/l07r/////////////////////////
///////////////////////////////////////////////////buo//0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9OSPP+ldjdoAAAAAAAAAADurFW67qxV//Ct
Vv/vrVb/6adR/92dSP/Skz//zo47/8+PO//QkDz/0JA8/9CQPP/QkDz/0JJB/9Smaf/dv5j/+fXw////
////////////////////////////////////////////////////////////////////////2riM/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9KRPP/FiTzGoHQ6Ae6s
VRrurFXz7qxV/+6sVf/urFX/761W//CuVv/sqlT/4qFM/9aVQv/Pjzv/zo47/9CQPP/VqnH/8+vh////
////////////////////////////////////////////////////////////////////////////////
/////////////9aueP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GQ
PP/Ojzz4oHM5Iu6sVU3urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/vrVX/8K5W/+6sVf/mpU//2plF/9q6
j///////////////////////////////////////////////////////////////////////////////
//////////////////////////////n18P/Rm1P/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/SkTz/rXs6Vu6sVYPurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/wrVb/6r2B////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////o18L/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/v4Y8j+6sVa3urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFb/9Onb///////+/v3/////////////////////////////////////////
////////////////////////////////////////////////////////////////////////1qx2/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/wYc7uO6sVcrurFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/trlr/67Zy/+m+h//y5NP/////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////7+TW/9CQPf/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkTz/yYw80e6s
VdvurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/6sme//v49P//////////////
////////////////////////////////////////////////////////////////////////////////
/////////////////////////////9arc//QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/RkDz/zI083+6sVeLurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/u2b3/////////
////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////+fVvv/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/zo885u6sVeLurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+vL
of//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////r28v/Rl0r/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/zo885u6sVdrurFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7LBj//r28f//////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
///Vq3P/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkDz/zI083+6sVcrurFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/6cKO////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
///////////////////ewp7/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkTz/yYw80O6s
VazurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7NOy//z59v/u2b7/+fPr////////////////////
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////n1sD/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/SkTz/xIo8uO6sVYHurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/67Zx/+2uWf/qy6H/////////
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////v5dj/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/SkTz/vYU8je6sVUvurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+nC
kP///////////////////////////////////////////////////////fz6//fu5P/w4Mv/793F//7+
/v/////////////////////////////////////////////////////////////////17eT/0JA//86O
Ov/Pjzv/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/tYA8Vu6sVRnurFXy7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7a5b//fw5///////////////////////////////////////+/jz/+7av//pwIr/67Jn/+6t
Vv/urFX/6rd1////////////////////////////////////////////////////////////////////
///48ej/6KhU/92cR//Tkj7/zo47/8+PO//QkDz/0JA8/9GQPP/Pjzz3rXw8IAAAAADurFW37qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/6b2D//////////////////////////////////nz6//qyZ3/7K9h/+6s
Vf/urFX/7qxV/+6sVf/urFX/6rl6////////////////////////////////////////////////////
///////////////////9/Pr/6r6C//CuV//sqlT/4aBL/9aWQv/Pjzv/zo47/9GRPP/GijzCp3g8AQAA
AADurFVb7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/68yk///////////////////////+/fz/68+q/+2v
XP/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/6rNr//7+/f//////////////////////////////
/////////////////////////////////////////////+nEk//vrVX/8K5W/+6sVf/lpE//2ZlF/9KR
Pf+zfzpoAAAAAAAAAADurFUP7qxV5u6sVf/urFX/7qxV/+6sVf/urFX/7NGu//////////////////fu
5P/quHb/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxW//Xr3///////////////
///////////////////////////////////////////////////7+PP/+vXu///////pxJL/7qxV/+6s
Vf/wrVb/8K1W/+mnUevUmUsUAAAAAAAAAAAAAAAA7qxVgO6sVf/urFX/7qxV/+6sVf/urFX/6sqg////
////////8eLO/+yvX//urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+nF
lf/////////////////////////////////////////////////////////////////8+vf/6cWV/+q5
ev/s07H/6rd1/+6sVf/urFX/7qxV/++tVogAAAAAAAAAAAAAAAAAAAAA7qxVFe6sVejurFX/7qxV/+6s
Vf/urFX/6bp8///////w4Mn/7a1Z/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/x4cz////////////////////////////////////////////////////+//7+
/v/////////+/+m8gP/urFX/7a1Y/+6sVf/urFX/7qxV7O6sVRoAAAAAAAAAAAAAAAAAAAAAAAAAAO6s
VWburFX/7qxV/+6sVf/urFX/7a5b/+zTs//sr1//7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/sr1//8uPQ////////////////////////////////////
////////6suj/+qzav/pxpj/9Onb//n07f/rr2D/7qxV/+6sVf/urFX/7qxVbwAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAO6sVQPurFW67qxV/+6sVf/urFX/7qxV/+6sVv/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7axY/+nGl//27uP/////////
/////////fz6//Lj0P/qunv/7qxV/+6sVf/urFX/7qxV/+m9gv/qt3X/7qxV/+6sVf/urFXB7qxVBQAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADurFUg7qxV6O6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/qtnP/6sqg/+3WuP/s07P/6cSS/+uyZf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVv/trVr/7qxV/+6s
VezurFUlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVRu6sVfjurFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV++6sVUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6s
VVzurFX97qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxVYwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAADurFVd7qxV+O6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVfvurFVjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVR+6sVeXurFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV6O6sVU0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVSPurFW37qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFW77qxVJgAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AADurFUB7qxVae6sVeTurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV5+6sVW3urFUDAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVRjurFWA7qxV4u6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVeTurFWE7qxVGwAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVEu6sVV7urFW17qxV7+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVfDurFW47qxVX+6s
VRMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA7qxVG+6sVUzurFWA7qxVrO6sVcnurFXa7qxV4u6sVeLurFXa7qxVye6sVa3urFWC7qxVTe6s
VR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAP//gAH//6gr//wAAD//qCv/8AAAD/+oK//AAAAD/6gr/4AAAAH/qCv/AAAAAP+oK/4A
AAAAf6gr/AAAAAA/qCv4AAAAAB+oK/AAAAAAD6gr4AAAAAAHqCvgAAAAAAeoK8AAAAAAA6grwAAAAAAD
qCuAAAAAAAGoK4AAAAAAAagrgAAAAAAAqCsAAAAAAACoKwAAAAAAAKgrAAAAAAAAqCsAAAAAAACoKwAA
AAAAAKgrAAAAAAAAqCsAAAAAAACoKwAAAAAAAKgrAAAAAAAAqCsAAAAAAACoKwAAAAAAAKgrAAAAAAAA
qCsAAAAAAACoKwAAAAAAAKgrgAAAAAAAqCuAAAAAAAGoK4AAAAAAAagrwAAAAAADqCvAAAAAAAOoK+AA
AAAAB6gr4AAAAAAHqCvwAAAAAA+oK/gAAAAAH6gr/AAAAAA/qCv+AAAAAH+oK/8AAAAA/6gr/4AAAAH/
qCv/wAAAA/+oK//wAAAP/6gr//wAAD//qCv//4AB//+oKygAAAAgAAAAQAAAAAEAIAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApHY4FrR/
Ole+hjuVxos7w8uNPNvOjzznzo8858uNPNzGijzEvoU7l7WBO1mpejsYAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqHg5Kr6F
O5XLjTvo0pE8/9ORPP/SkTz/0ZE8/9CQPP/QkDz/0ZE8/9KRPP/TkTz/0pE8/82OPOq/hjyZrXw7LAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo3U6CbuE
O4bPjzz005I8/9GRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0ZE8/9OS
PP/PkDz1vYU8iah6PAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKR3
OibGizvG05E8/9GQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9GQPP/TkTz/yIs8yqx8PCkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AACsezsxy40849KRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/zI485rKAPTUAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAApXc6JMuNPOTSkTz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/zI485659PCcAAAAAAAAAAAAA
AAAAAAAAAAAAAKd4OwnHizzJ0pE8/9CQPP/QkDz/0JA9/9OgYP/YtIX/38Sh/+LMr//iy67/3sOf/9iy
gv/Tn13/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/x4s8zaV4
OwsAAAAAAAAAAAAAAAAAAAAAu4Q8hNOSPP/QkDz/0JA8/9GXS//YtIX/6NfC//Xv5//8+vj/////////
///8+vf/9O3k/+fVvv/YsYH/0JRF/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/Tkjz/u4Q7iQAAAAAAAAAAAAAAAKx7OyTPjzz10ZA8/9GXS//Ztoj/8une////////////////////
///////////////////////////////////38uz/3L2V/9CRP//QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9GRPP/Ojzz3onU5KQAAAAAAAAAAxo1BlNGQO//Ojjv/0JNC/9GWSP/QlET/0ZpR/9au
eP/n1b3//v79////////////////////////////////////////////7+TV/9GdWP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9SSPP+4gjqaAAAAAPGuVhHvrVXq5qRO/9qZRP/QkT3/zo46/8+P
O//QkDz/0JA8/9CQPP/To2P/8+vh////////////////////////////////////////////+fXw/9Oh
Yf/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0pE8/8qMO+2WbTYV7qxVUe6sVf/wrVb/761W/+mo
Uv/enUj/05M//86PO//PkD7/3cCZ//Lp3v/7+PT/////////////////////////////////////////
////////+PTv/9GcVf/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/05I8/699OljurFWR7qxV/+6s
Vf/urFX/761W//CuV//sqlT/4qJQ/+zdyf//////////////////////////////////////////////
////////////////////////7+TV/9CRP//QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/Tkjz/vIQ7mO6s
Vb/urFX/7qxV/+6sVf/urFX/7qxV/++tVf/ry5///v79//7+/f//////////////////////////////
////////////////////////////////////////2rqO/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9KR
PP/GijvF7qxV2u6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+u1bv/t1bb//fv4////////////////////
///////////////////////////////////////////////////48+3/0ZRG/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0ZA8/8uNPNzurFXl7qxV/+6sVf/urFX/7qxV/+6sVf/rsWP/9OjZ////////////////////
///////////////////////////////////////////////////////////////////YtIX/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/zo886O6sVeXurFX/7qxV/+6sVf/urFX/7qxW//Hiz///////////////
/////////////////////////////////////////////////////////////////////////////+rb
yP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/Ojzzo7qxV2e6sVf/urFX/7qxV/+6sVf/quHb/////////
////////////////////////////////////////////////////////////////////////////////
////////+fXw/9CURf/QkDz/0JA8/9CQPP/QkDz/0ZA8/8yOPNzurFW/7qxV/+6sVf/urFX/7qxV/+nA
if/s1LT/9Onb////////////////////////////////////////////////////////////////////
////////////////////////0Z5b/9CQPP/QkDz/0JA8/9CQPP/SkTz/x4s8xe6sVY/urFX/7qxV/+6s
Vf/urFX/7qxV/+nEk//+/fz////////////////////////////+/v3/+PHo//To2f/+/v7/////////
///////////////////////////////////VqGz/zo46/8+PO//QkDz/0JA8/9KRPP+/hjyX7qxVUO6s
Vf/urFX/7qxV/+6sVf/ssGH//Pr2///////////////////////y49D/6cOR/+qyaf/urVb/6rJp////
/////////////////////////////////////////////+e7f//goEr/1ZVB/8+PO//Ojzv/0pE8/7WB
PFfurFUQ7qxV6O6sVf/urFX/7qxV/+m+hv/////////////////z5tX/6rl6/+6sVf/urFX/7qxV/+6s
Vf/qs2r////+////////////////////////////////////////////+PLp/+y4c//urFX/5aRO/9qZ
RP/Mjjztp3g6FQAAAADurFWQ7qxV/+6sVf/urFX/6cKP////////////682m/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+2sV//17N////////////////////////////////////////7+/v/06Nn/8uTT/+qz
a//wrVb/761W/+alUZYAAAAAAAAAAO6sVSHurFX07qxV/+6sVf/quXj//////+nDkf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+m+hP////////////////////////////////////////////jy
6v/qtnH/67Fj/+6sVf/urFX2761WJQAAAAAAAAAAAAAAAO6sVX7urFX/7qxV/+2vW//pvID/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+nEkv/7+PT//////////////////fz6/+rJ
n//rtW7/682n/+zTs//urFb/7qxV/+6sVYQAAAAAAAAAAAAAAAAAAAAA7qxVB+6sVcTurFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+u0bP/qy6H/7da4/+vN
pv/qtnP/7qxV/+6sVf/urFX/7K9f/+2sWP/urFXI7qxVCQAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVIO6s
VeDurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV4+6sVSMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA7qxVLO6sVd/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVeLurFUwAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA7qxVIu6sVcHurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFXE7qxVJAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVBu6sVX/urFXw7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFXy7qxVgu6sVQgAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVSXurFWP7qxV5e6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFXl7qxVke6sVScAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AADurFUS7qxVUO6sVY7urFW97qxV2O6sVeXurFXl7qxV2O6sVb7urFWP7qxVUu6sVRMAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AD//8AAP/8AAA/+AAAH/AAAA/gAAAHwAAAA8AA
AAOAAAABgAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAA
AAGAAAABwAAAA8AAAAPgAAAH8AAAD/gAAB/8AAA//wAA///AA/8oAAAAEAAAACAAAAABACAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKx7Oja9hTqTyYw8zs6PPOfOjzznyYw8zr6G
PJSvfjw3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo3Y7CbyEO5PRkDz805I8/9GRPP/QkDz/0JA8/9GR
PP/TkTz/0ZA8/b+GPJWrfDwKAAAAAAAAAAAAAAAAp3g8CcSJPLfTkjz/0ZA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/Tkjz/xoo8uqx8PQoAAAAAAAAAAL2FPJPUk0D/06Rm/92/mf/izK//4sut/9u6
j//Snlr/0JA8/9CQPP/QkDz/0JA8/9OSPP+9hTyWAAAAALiEQDLQkDz90JpS/93Amf/u4tP/////////
/////////fv5/+DIqP/QkkH/0JA8/9CQPP/RkDz/0JA8/qN1OTTtq1WS56VP/9qaRf/RkT3/0JdO/+rc
yP//////////////////////693L/9CSQf/QkDz/0JA8/9SSPP+3gTqW761Vz++tVv/wrVb/6K5h//fy
6v/////////////////////////////////gx6b/0JA8/9CQPP/RkTz/yYw80e6sVefurFX/7qxV/+q+
hP/69vD//////////////////////////////////fz7/9GbU//QkDz/0JA8/8+PPOnurFXn7qxV/+yw
Y//79/P////////////////////////////////////////////ZuIz/0JA8/9CQPP/Pjzzp7qxVz+6s
Vf/sr2H/8eLO/////////////v7+//n07f/+/v7/////////////////48yu/8+PO//Qjzv/yYs80e6s
VZLurFX/67Jm//7+/f/8+fb/68yl/+qza//rsmf//v37//////////////////Pn2P/ip1n/2phC/8CH
PJXurFUw7qxV/eq2c//169//6rNr/+6sVf/urFX/7q1W//Pm1f/////////////////8+fX/67+G/++t
Vf3lpVEyAAAAAO6sVZHtrVr/7a9c/+6sVf/urFX/7qxV/+6sVf/rsWP/7de5//Lj0P/pxJL/67Zy/+qz
av/urFWTAAAAAAAAAADurFUI7qxVtO6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFW37qxVCQAAAAAAAAAAAAAAAO6sVQjurFWQ7qxV++6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
VfvurFWS7qxVCQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVTPurFWQ7qxVzO6sVeburFXm7qxVzO6s
VZDurFU0AAAAAAAAAAAAAAAAAAAAAPAPrEHAA6xBgAGsQYABrEEAAKxBAACsQQAArEEAAKxBAACsQQAA
rEEAAKxBAACsQYABrEGAAaxBwAOsQfAPrEE=
</value>
</data>
</root> </root>

30
Core/FormManager.cs Normal file
View File

@@ -0,0 +1,30 @@
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 bool HasAnyDialogs => Application.OpenForms.OfType<IAppDialog>().Any();
public static void CloseAllDialogs(){
foreach(IAppDialog dialog in Application.OpenForms.OfType<IAppDialog>().Reverse()){
((Form)dialog).Close();
}
}
public interface IAppDialog{}
}
}

View File

@@ -1,226 +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;
namespace TweetDck.Core{
sealed partial class FormNotification : Form{
public Func<bool> CanMoveWindow = () => true;
private readonly Form owner;
private readonly ChromiumWebBrowser browser;
private readonly Queue<TweetNotification> tweetQueue = new Queue<TweetNotification>(4);
private readonly bool autoHide;
private int timeLeft, totalTime;
private readonly string notificationJS;
protected override bool ShowWithoutActivation{
get{
return true;
}
}
public FormNotification(Form owner, TweetDeckBridge bridge, bool autoHide){
InitializeComponent();
Text = Program.BrandName;
this.owner = owner;
this.autoHide = autoHide;
owner.FormClosed += (sender, args) => Close();
notificationJS = ScriptLoader.LoadResource("notification.js");
browser = new ChromiumWebBrowser("about:blank"){ MenuHandler = new ContextMenuNotification() };
browser.FrameLoadEnd += Browser_FrameLoadEnd;
if (bridge != null){
browser.RegisterJsObject("$TD",bridge);
}
panelBrowser.Controls.Add(browser);
if (autoHide){
Program.UserConfig.MuteToggled += Config_MuteToggled;
Disposed += (sender, args) => Program.UserConfig.MuteToggled -= Config_MuteToggled;
}
Disposed += (sender, args) => browser.Dispose();
}
public FormNotification(Form owner, bool autoHide) : this(owner,null,autoHide){}
protected override void WndProc(ref Message m){
if (m.Msg == 0x0112 && (m.WParam.ToInt32() & 0xFFF0) == 0xF010 && !CanMoveWindow()){ // WM_SYSCOMMAND, SC_MOVE
return;
}
base.WndProc(ref m);
}
// event handlers
private void timerHideProgress_Tick(object sender, EventArgs e){
if (Bounds.Contains(Cursor.Position))return;
timeLeft -= timerProgress.Interval;
progressBarTimer.SetValueInstant((int)Math.Min(1000,Math.Round(1050.0*(totalTime-timeLeft)/totalTime)));
if (timeLeft <= 0){
if (tweetQueue.Count > 0){
LoadNextNotification();
}
else if (autoHide){
HideNotification();
}
}
}
private void Config_MuteToggled(object sender, EventArgs e){
if (Program.UserConfig.MuteNotifications){
HideNotification();
}
else{
if (tweetQueue.Count > 0){
MoveToVisibleLocation();
LoadNextNotification();
}
}
}
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
if (e.Frame.IsMain && notificationJS != null && browser.Address != "about:blank"){
browser.ExecuteScriptAsync(notificationJS);
}
}
private void FormNotification_FormClosing(object sender, FormClosingEventArgs e){
if (e.CloseReason == CloseReason.UserClosing){
HideNotification();
tweetQueue.Clear();
e.Cancel = true;
}
}
// notification methods
public void ShowNotification(TweetNotification notification){
if (Program.UserConfig.MuteNotifications){
tweetQueue.Enqueue(notification);
}
else{
MoveToVisibleLocation();
tweetQueue.Enqueue(notification);
UpdateTitle();
if (!timerProgress.Enabled){
LoadNextNotification();
}
}
}
public void ShowNotificationForSettings(bool reset){
if (browser.Address == "about:blank"){
browser.Load("about:blank"); // required, otherwise shit breaks
reset = true;
}
if (reset){
browser.LoadHtml(TweetNotification.ExampleTweet.GenerateHtml(),"http://tweetdeck.twitter.com/");
totalTime = timeLeft = TweetNotification.ExampleTweet.GetDisplayDuration(Program.UserConfig.NotificationDuration);
timerProgress.Start();
}
MoveToVisibleLocation();
}
public void HideNotification(){
browser.LoadHtml("","about:blank");
Location = new Point(-32000,-32000);
timerProgress.Stop();
}
private void LoadNextNotification(){
TweetNotification tweet = tweetQueue.Dequeue();
if (browser.Address == "about:blank"){
browser.Load("about:blank"); // required, otherwise shit breaks
}
browser.LoadHtml(tweet.GenerateHtml(),"http://tweetdeck.twitter.com/");
totalTime = timeLeft = tweet.GetDisplayDuration(Program.UserConfig.NotificationDuration);
timerProgress.Stop();
timerProgress.Start();
UpdateTitle();
}
private void MoveToVisibleLocation(){
bool needsReactivating = Location.X == -32000;
UserConfig config = Program.UserConfig;
Screen screen = Screen.FromControl(owner);
if (config.DisplayNotificationTimer){
ClientSize = new Size(ClientSize.Width,122);
progressBarTimer.Visible = true;
}
else{
ClientSize = new Size(ClientSize.Width,118);
progressBarTimer.Visible = false;
}
if (config.NotificationDisplay > 0 && config.NotificationDisplay <= Screen.AllScreens.Length){
screen = Screen.AllScreens[config.NotificationDisplay-1];
}
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){
Program.SetWindowPos(Handle.ToInt32(),-1,Left,Top,Width,Height,0x0010); // HWND_TOPMOST, SWP_NOACTIVATE
}
}
private void UpdateTitle(){
Text = tweetQueue.Count > 0 ? Program.BrandName+" ("+tweetQueue.Count+" more left)" : Program.BrandName;
}
}
}

View File

@@ -1,110 +1,229 @@
using CefSharp; using System;
using System.IO; using System.Text.RegularExpressions;
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;
namespace TweetDck.Core.Handling{ namespace TweetDuck.Core.Handling{
abstract class ContextMenuBase : IContextMenuHandler{ abstract class ContextMenuBase : IContextMenuHandler{
private const int MenuOpenUrlInBrowser = 26500; protected static UserConfig Config => Program.Config.User;
private const int MenuCopyUrl = 26501;
private const int MenuOpenImageInBrowser = 26502; private static TwitterUtils.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){
RemoveSeparatorIfLast(model); if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
Context = TweetDeckBridge.ContextInfo.Reset();
}
else{
Context = TweetDeckBridge.ContextInfo.Create(parameters);
}
if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && !parameters.UnfilteredLinkUrl.EndsWith("tweetdeck.twitter.com/#")){ if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection) && !parameters.TypeFlags.HasFlag(ContextMenuType.Editable)){
model.AddItem((CefMenuCommand)MenuOpenUrlInBrowser,"Open in browser"); model.AddItem(MenuSearchInBrowser, "Search in browser");
model.AddItem((CefMenuCommand)MenuCopyUrl,"Copy link address"); model.AddSeparator();
model.AddItem(MenuReadApplyROT13, "Apply ROT13");
model.AddSeparator(); model.AddSeparator();
} }
if (parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents){ string TextOpen(string name) => "Open "+name+" in browser";
model.AddItem((CefMenuCommand)MenuOpenImageInBrowser,"Open image in browser"); string TextCopy(string name) => "Copy "+name+" address";
model.AddItem((CefMenuCommand)MenuSaveImage,"Save image as..."); string TextSave(string name) => "Save "+name+" as...";
model.AddItem((CefMenuCommand)MenuCopyImageUrl,"Copy image URL");
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();
}
if (Context.Types.HasFlag(ContextInfo.ContextType.Video)){
model.AddItem(MenuOpenMediaUrl, TextOpen("video"));
model.AddItem(MenuCopyMediaUrl, TextCopy("video"));
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();
case MenuOpenUrlInBrowser:
BrowserUtils.OpenExternalBrowser(parameters.LinkUrl); switch(commandId){
case MenuOpenLinkUrl:
OpenBrowser(control, Context.LinkUrl);
break; break;
case MenuCopyUrl: case MenuCopyLinkUrl:
Clipboard.SetText(string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedLink) ? parameters.UnfilteredLinkUrl : TweetDeckBridge.LastRightClickedLink,TextDataFormat.UnicodeText); SetClipboardText(control, Context.UnsafeLinkUrl);
break; break;
case MenuOpenImageInBrowser: 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; break;
case MenuSaveImage:
string fileName = GetImageFileName(parameters.SourceUrl);
string extension = Path.GetExtension(fileName);
string saveTarget;
using(SaveFileDialog dialog = new SaveFileDialog{
AutoUpgradeEnabled = true,
OverwritePrompt = true,
Title = "Save image",
FileName = fileName,
Filter = "Image ("+(string.IsNullOrEmpty(extension) ? "unknown" : extension)+")|*.*"
}){
saveTarget = dialog.ShowDialog() == DialogResult.OK ? dialog.FileName : null;
} }
if (saveTarget != null){ case MenuOpenMediaUrl:
BrowserUtils.DownloadFileAsync(parameters.SourceUrl,saveTarget,ex => { OpenBrowser(control, TwitterUtils.GetMediaLink(Context.MediaUrl, ImageQuality));
MessageBox.Show("An error occurred while downloading the image: "+ex.Message,Program.BrandName+" Has Failed :(",MessageBoxButtons.OK,MessageBoxIcon.Error); break;
case MenuCopyMediaUrl:
SetClipboardText(control, TwitterUtils.GetMediaLink(Context.MediaUrl, ImageQuality));
break;
case MenuViewImage: {
string url = Context.MediaUrl;
control.InvokeAsyncSafe(() => {
TwitterUtils.ViewImage(url, ImageQuality);
analytics.AnalyticsFile.ViewedImages.Trigger();
}); });
}
break; break;
}
case MenuCopyImageUrl: case MenuSaveMedia: {
Clipboard.SetText(parameters.SourceUrl,TextDataFormat.UnicodeText); 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 void RemoveSeparatorIfFirst(IMenuModel model){ 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){
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);
} }
} }
protected void RemoveSeparatorIfLast(IMenuModel model){ protected static void AddSeparator(IMenuModel model){
if (model.Count > 0 && model.GetTypeAt(model.Count-1) == MenuItemType.Separator){ if (model.Count > 0 && model.GetTypeAt(model.Count-1) != MenuItemType.Separator){ // do not add separators if there is nothing to separate
model.RemoveAt(model.Count-1); 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";
} }
} }
} }

View File

@@ -1,37 +1,96 @@
using CefSharp; using CefSharp;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
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 MenuSettings = 26600; private const CefMenuCommand MenuGlobal = (CefMenuCommand)26600;
private const int MenuAbout = 26601; private const CefMenuCommand MenuMute = (CefMenuCommand)26601;
private const int MenuMute = 26602; private const CefMenuCommand MenuSettings = (CefMenuCommand)26602;
private const CefMenuCommand MenuPlugins = (CefMenuCommand)26003;
private const CefMenuCommand MenuAbout = (CefMenuCommand)26604;
private const CefMenuCommand MenuOpenTweetUrl = (CefMenuCommand)26610;
private const CefMenuCommand MenuCopyTweetUrl = (CefMenuCommand)26611;
private const CefMenuCommand MenuOpenQuotedTweetUrl = (CefMenuCommand)26612;
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);
RemoveSeparatorIfFirst(model); if (isSelecting){
if (isEditing){
model.AddSeparator();
model.AddItem(MenuWriteApplyROT13, "Apply ROT13");
}
model.AddSeparator();
}
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model); base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
model.AddItem(CefMenuCommand.Reload,"Reload"); if (isSelecting && !isEditing && TwitterUtils.IsTweetDeckWebsite(frame)){
model.AddCheckItem((CefMenuCommand)MenuMute,"Mute Notifications"); InsertSelectionSearchItem(model, MenuSearchInColumn, "Search in a column");
model.SetChecked((CefMenuCommand)MenuMute,Program.UserConfig.MuteNotifications);
model.AddSeparator();
if (TweetNotification.IsReady){
model.AddItem((CefMenuCommand)MenuSettings,"Settings");
} }
model.AddItem((CefMenuCommand)MenuAbout,"About "+Program.BrandName); 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);
IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu(MenuGlobal, Program.BrandName);
globalMenu.AddItem(CefMenuCommand.Reload, TitleReloadBrowser);
globalMenu.AddCheckItem(MenuMute, TitleMuteNotifications);
globalMenu.SetChecked(MenuMute, Config.MuteNotifications);
globalMenu.AddSeparator();
globalMenu.AddItem(MenuSettings, TitleSettings);
globalMenu.AddItem(MenuPlugins, TitlePlugins);
globalMenu.AddItem(MenuAbout, TitleAboutProgram);
AddDebugMenuItems(globalMenu);
}
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){
@@ -39,31 +98,82 @@ namespace TweetDck.Core.Handling{
return true; return true;
} }
switch((int)commandId){ switch(commandId){
case MenuSettings: case CefMenuCommand.Reload:
form.InvokeSafe(() => { form.InvokeAsyncSafe(form.ReloadToTweetDeck);
form.OpenSettings(); return true;
});
case MenuSettings:
form.InvokeAsyncSafe(form.OpenSettings);
return true; return true;
case MenuAbout: case MenuAbout:
form.InvokeSafe(() => { form.InvokeAsyncSafe(form.OpenAbout);
form.OpenAbout(); return true;
});
case MenuPlugins:
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:
OpenBrowser(form, Context.Chirp.TweetUrl);
return true;
case MenuCopyTweetUrl:
SetClipboardText(form, Context.Chirp.TweetUrl);
return true;
case MenuScreenshotTweet:
form.InvokeAsyncSafe(form.TriggerTweetScreenshot);
return true;
case MenuOpenQuotedTweetUrl:
OpenBrowser(form, Context.Chirp.QuoteUrl);
return true;
case MenuCopyQuotedTweetUrl:
SetClipboardText(form, Context.Chirp.QuoteUrl);
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();
}
} }
} }

View 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);
}
}
}

View File

@@ -1,12 +1,94 @@
using CefSharp; using CefSharp;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification;
namespace TweetDuck.Core.Handling{
sealed class ContextMenuNotification : ContextMenuBase{
private const CefMenuCommand MenuViewDetail = (CefMenuCommand)26600;
private const CefMenuCommand MenuSkipTweet = (CefMenuCommand)26601;
private const CefMenuCommand MenuFreeze = (CefMenuCommand)26602;
private const CefMenuCommand MenuCopyTweetUrl = (CefMenuCommand)26603;
private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand)26604;
private readonly FormNotificationBase form;
private readonly bool enableCustomMenu;
public ContextMenuNotification(FormNotificationBase form, bool enableCustomMenu) : base(form){
this.form = form;
this.enableCustomMenu = enableCustomMenu;
}
namespace TweetDck.Core.Handling{
class ContextMenuNotification : ContextMenuBase{
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 (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){
model.AddItem(CefMenuCommand.Copy, "Copy");
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.InvokeAsyncSafe(() => {
form.ContextMenuOpen = true;
form.AnalyticsFile.NotificationContextMenus.Trigger();
});
}
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
if (base.OnContextMenuCommand(browserControl, browser, frame, parameters, commandId, eventFlags)){
return true;
}
switch(commandId){
case MenuSkipTweet:
form.InvokeAsyncSafe(form.FinishCurrentNotification);
return true;
case MenuFreeze:
form.InvokeAsyncSafe(() => form.FreezeTimer = !form.FreezeTimer);
return true;
case MenuViewDetail:
form.InvokeSafe(form.ShowTweetDetail);
return true;
case MenuCopyTweetUrl:
SetClipboardText(form, form.CurrentTweetUrl);
return true;
case MenuCopyQuotedTweetUrl:
SetClipboardText(form, form.CurrentQuoteUrl);
return true;
}
return false;
}
public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
base.OnContextMenuDismissed(browserControl, browser, frame);
form.InvokeAsyncSafe(() => form.ContextMenuOpen = false);
} }
} }
} }

View File

@@ -1,26 +0,0 @@
using CefSharp;
using System.Collections.Generic;
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;
}
}
}

View 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){}
}
}

View 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();
}
}

View 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(){}
}
}

View 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(){}
}
}

View File

@@ -0,0 +1,40 @@
using System;
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.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);
callback.Continue(acceptFilters.FindIndex(filter => filter.Equals(ext, StringComparison.OrdinalIgnoreCase)), dialog.FileNames.ToList());
}
else{
callback.Cancel();
}
callback.Dispose();
}
return true;
}
else{
callback.Dispose();
return false;
}
}
}
}

View File

@@ -0,0 +1,90 @@
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){
switch(text.Substring(0, pipe)){
case "error": icon = MessageBoxIcon.Error; break;
case "warning": icon = MessageBoxIcon.Warning; break;
case "info": icon = MessageBoxIcon.Information; break;
case "question": icon = MessageBoxIcon.Question; break;
default: return new FormMessage(caption, text, icon);
}
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){}
}
}

View File

@@ -0,0 +1,34 @@
using CefSharp;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Handling.General{
sealed class LifeSpanHandler : ILifeSpanHandler{
public static bool HandleLinkClick(IWebBrowser browserControl, WindowOpenDisposition targetDisposition, string targetUrl){
switch(targetDisposition){
case WindowOpenDisposition.NewBackgroundTab:
case WindowOpenDisposition.NewForegroundTab:
case WindowOpenDisposition.NewPopup:
case WindowOpenDisposition.NewWindow:
browserControl.AsControl().InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(targetUrl));
return true;
default:
return false;
}
}
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 bool DoClose(IWebBrowser browserControl, IBrowser browser){
return false;
}
public void OnBeforeClose(IWebBrowser browserControl, IBrowser browser){}
}
}

View File

@@ -0,0 +1,20 @@
using System.Windows.Forms;
using CefSharp;
namespace TweetDuck.Core.Handling{
sealed class KeyboardHandlerBrowser : IKeyboardHandler{
private readonly FormBrowser form;
public KeyboardHandlerBrowser(FormBrowser form){
this.form = form;
}
bool IKeyboardHandler.OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut){
return type == KeyType.RawKeyDown && form.ProcessBrowserKey((Keys)windowsKeyCode);
}
bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey){
return false;
}
}
}

View File

@@ -0,0 +1,45 @@
using CefSharp;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification;
namespace TweetDuck.Core.Handling {
sealed class KeyboardHandlerNotification : IKeyboardHandler{
private readonly FormNotificationBase notification;
public KeyboardHandlerNotification(FormNotificationBase notification){
this.notification = notification;
}
private void TriggerKeyboardShortcutAnalytics(){
notification.InvokeAsyncSafe(notification.AnalyticsFile.NotificationKeyboardShortcuts.Trigger);
}
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://")){
switch((Keys)windowsKeyCode){
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;
}
}
return false;
}
bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey){
return false;
}
}
}

View File

@@ -0,0 +1,75 @@
// Uncomment to force TweetDeck to load a predefined version of the vendor/bundle scripts and stylesheets
// #define FREEZE_TWEETDECK_RESOURCES
using System.Collections.Specialized;
using CefSharp;
using CefSharp.Handler;
using TweetDuck.Core.Handling.General;
using TweetDuck.Core.Utils;
#if FREEZE_TWEETDECK_RESOURCES
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.RegularExpressions;
#endif
namespace TweetDuck.Core.Handling{
class RequestHandlerBase : DefaultRequestHandler{
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 void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){
if (autoReload){
browser.Reload();
}
}
#if FREEZE_TWEETDECK_RESOURCES
private static readonly Regex TweetDeckResourceUrl = new Regex(@"/dist/(.*?)\.(.*?)\.(css|js)$", RegexOptions.Compiled);
private static readonly SortedList<string, string> TweetDeckHashes = new SortedList<string, string>(2){
{ "vendor.js", "d897f6b9ed" },
{ "bundle.js", "851d3877b9" },
{ "vendor.css", "ce7cdd10b6" },
{ "bundle.css", "c339f07047" }
};
public override bool OnResourceResponse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){
if (request.ResourceType == ResourceType.Script || request.ResourceType == ResourceType.Stylesheet){
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){
Debug.WriteLine($"Accepting {url}");
}
else{
Debug.WriteLine($"Rewriting {url} hash to {hash}");
request.Url = TweetDeckResourceUrl.Replace(url, $"/dist/$1.{hash}.$3");
return true;
}
}
}
return base.OnResourceResponse(browserControl, browser, frame, request, response);
}
#endif
}
}

View File

@@ -0,0 +1,63 @@
using System.Collections.Specialized;
using CefSharp;
using TweetDuck.Core.Handling.Filters;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Handling{
sealed class RequestHandlerBrowser : RequestHandlerBase{
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.Script && request.Url.Contains("analytics.")){
callback.Dispose();
return CefReturnValue.Cancel;
}
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("/backgrounds/spinner_blue")){
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("/dist/vendor")){
NameValueCollection headers = response.ResponseHeaders;
if (int.TryParse(headers["x-ton-expected-size"], out int totalBytes)){
return new ResponseFilterVendor(totalBytes);
}
#if DEBUG
else{
System.Diagnostics.Debug.WriteLine($"Missing uncompressed size header in {request.Url}");
foreach(string key in headers){
System.Diagnostics.Debug.WriteLine($" {key}: {headers[key]}");
}
System.Diagnostics.Debugger.Break();
}
#endif
}
return base.GetResourceResponseFilter(browserControl, browser, frame, request, response);
}
}
}

View 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(){}
}
}

View File

@@ -1,145 +0,0 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Forms;
using TweetDck.Core.Utils;
namespace TweetDck.Core.Handling{
class TweetDeckBridge{
public static string LastRightClickedLink = string.Empty;
public static string ClipboardImagePath = string.Empty;
private readonly FormBrowser form;
public string BrandName{
get{
return Program.BrandName;
}
}
public string VersionTag{
get{
return Program.VersionTag;
}
}
public bool MuteNotifications{
get{
return Program.UserConfig.MuteNotifications;
}
}
public bool UpdateCheckEnabled{
get{
return Program.UserConfig.EnableUpdateCheck;
}
}
public bool ExpandLinksOnHover{
get{
return Program.UserConfig.ExpandLinksOnHover;
}
}
public string DismissedVersionTag{
get{
return Program.UserConfig.DismissedUpdate ?? string.Empty;
}
}
public TweetDeckBridge(FormBrowser form){
this.form = form;
}
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 OpenSettingsMenu(){
form.InvokeSafe(() => {
form.OpenSettings();
});
}
public void OnTweetPopup(string tweetHtml, int tweetCharacters){
form.InvokeSafe(() => {
form.OnTweetPopup(new TweetNotification(tweetHtml,tweetCharacters));
});
}
public void OnTweetSound(){
form.InvokeSafe(() => {
form.OnTweetSound();
});
}
public void OnUpdateAccepted(string versionTag, string downloadUrl){
form.InvokeSafe(() => {
form.BeginUpdateProcess(versionTag,downloadUrl);
});
}
public void OnUpdateDismissed(string versionTag){
form.InvokeSafe(() => {
Program.UserConfig.DismissedUpdate = versionTag;
Program.UserConfig.Save();
});
}
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.HandleException("Could not paste image from clipboard.",e);
}
}
});
}
public void ClickUploadImage(int offsetX, int offsetY){
form.InvokeSafe(() => {
Point prevPos = Cursor.Position;
Cursor.Position = form.PointToScreen(new Point(offsetX,offsetY));
Program.mouse_event(SystemInformation.MouseButtonsSwapped ? 0x08 : 0x02,Cursor.Position.X,Cursor.Position.Y,0,0); // MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_LEFTDOWN
Program.mouse_event(SystemInformation.MouseButtonsSwapped ? 0x10 : 0x04,Cursor.Position.X,Cursor.Position.Y,0,0); // MOUSEEVENTF_RIGHTUP | MOUSEEVENTF_LEFTUP
Cursor.Position = prevPos;
form.OnImagePastedFinish();
});
}
public void OpenBrowser(string url){
BrowserUtils.OpenExternalBrowser(url);
}
public void Log(string data){
System.Diagnostics.Debug.WriteLine(data);
}
}
}

View File

@@ -1,82 +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; }
public static bool IsReady{
get{
return FontSizeClass != null && HeadTag != null;
}
}
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/645532929930608642/J56NBJVY_normal.png' 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>");
build.Append(@"</div></div></article>");
return new TweetNotification(build.ToString(),95);
}
}
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
}
private readonly string html;
private readonly int characters;
public TweetNotification(string html, int characters){
this.html = html;
this.characters = characters;
}
public int GetDisplayDuration(Duration modifier){
int multiplier;
switch(modifier){
case Duration.Short: multiplier = 15; break;
case Duration.Long: multiplier = 35; break;
case Duration.VeryLong: multiplier = 45; break;
default: multiplier = 25; break;
}
return 2000+Math.Max(1000,multiplier*characters);
}
public string GenerateHtml(){
StringBuilder build = new StringBuilder();
build.Append("<!DOCTYPE html>");
build.Append("<html class='os-windows txt-base-").Append(FontSizeClass).Append("'>");
build.Append("<head>").Append(HeadTag).Append("</head>");
build.Append("<body class='hearty'><div class='app-columns-container'><div class='column' style='width:100%'>");
build.Append(html);
build.Append("</div></div></body>");
build.Append("</html>");
return build.ToString();
}
}
}

View File

@@ -0,0 +1,72 @@
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 Exit(){
if (AutoClearTimer != null){
AutoClearTimer.Dispose();
AutoClearTimer = null;
}
if (ClearOnExit){
try{
Directory.Delete(CacheFolder, true);
}catch{
// welp, too bad
}
}
}
}
}

View File

@@ -0,0 +1,161 @@
using System;
using CefSharp;
using TweetDuck.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(ChirpInfo);
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);
}
}
}
}
}

View File

@@ -0,0 +1,215 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using TweetDuck.Core.Other;
using TweetDuck.Data;
using TweetDuck.Plugins;
using TweetDuck.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
}
}
}
}

View 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} {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.Log("[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();
}
}
}
}

View 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();
}
}
}

View 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;
}
}

View 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{
switch(TweetDeckBridge.FontSize){
case "largest": return 4;
case "large": return 3;
case "small": return 1;
case "smallest": return 0;
default: return 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;
this.browser = new ChromiumWebBrowser("about:blank"){
MenuHandler = new ContextMenuNotification(this, enableContextMenu),
JsDialogHandler = new JavaScriptDialogHandler(),
LifeSpanHandler = new LifeSpanHandler(),
RequestHandler = new RequestHandlerBase(false)
};
this.browser.Dock = DockStyle.None;
this.browser.ClientSize = ClientSize;
this.browser.SetupResourceHandler(TwitterUtils.TweetDeckURL, this.resourceHandler);
this.browser.SetupResourceHandler(TweetNotification.AppLogo);
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);
}
}
}
}

View File

@@ -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,21 +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.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
// //
@@ -49,6 +41,8 @@ namespace TweetDck.Core {
// //
// progressBarTimer // progressBarTimer
// //
this.progressBarTimer.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.progressBarTimer.BackColor = System.Drawing.SystemColors.Control; this.progressBarTimer.BackColor = System.Drawing.SystemColors.Control;
this.progressBarTimer.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(28)))), ((int)(((byte)(99)))), ((int)(((byte)(153))))); this.progressBarTimer.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(28)))), ((int)(((byte)(99)))), ((int)(((byte)(153)))));
this.progressBarTimer.Location = new System.Drawing.Point(0, 118); this.progressBarTimer.Location = new System.Drawing.Point(0, 118);
@@ -65,14 +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.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);
@@ -80,8 +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;
} }
} }

View File

@@ -0,0 +1,276 @@
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.Data;
using TweetDuck.Plugins;
using TweetDuck.Plugins.Enums;
using TweetDuck.Resources;
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{
switch(Config.NotificationSize){
default:
return BrowserUtils.Scale(284, SizeScale*(1.0+0.05*FontSizeLevel));
case TweetNotification.Size.Custom:
return Config.CustomNotificationSize.Width;
}
}
}
private int BaseClientHeight{
get{
switch(Config.NotificationSize){
default:
return BrowserUtils.Scale(122, SizeScale*(1.0+0.08*FontSizeLevel));
case TweetNotification.Size.Custom:
return Config.CustomNotificationSize.Height;
}
}
}
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, this);
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();
}
}
}

View 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;
}
}

View 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();
}
}
}

View File

@@ -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.Data;
using TweetDuck.Plugins;
using TweetDuck.Resources;
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;
}
}
}
}
}

View File

@@ -0,0 +1,26 @@
using System;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
namespace TweetDuck.Core.Notification.Screenshot{
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);
}
}
}

View 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
}
}

View File

@@ -0,0 +1,50 @@
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;
switch(Path.GetExtension(path)){
case ".weba":
case ".webm": mimeType = "audio/webm"; break;
case ".wav": mimeType = "audio/wav"; break;
case ".ogg": mimeType = "audio/ogg"; break;
case ".mp3": mimeType = "audio/mp3"; break;
case ".flac": mimeType = "audio/flac"; break;
case ".opus": mimeType = "audio/ogg; codecs=opus"; break;
default: mimeType = null; break;
}
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;
}
}
}
}

View 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();
}
}
}

View File

@@ -0,0 +1,135 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using TweetDuck.Data.Serialization;
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);
}
}
}

View File

@@ -0,0 +1,158 @@
// 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;
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", Program.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
BrowserUtils.CreateWebClient().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));
}
}));
}
}
}

View 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();
}
}
}

View File

@@ -0,0 +1,325 @@
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 TweetDuck.Plugins.Enums;
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" , Program.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(plugin => plugins.Config.IsEnabled(plugin)).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{
switch(UserConfig.TrayBehavior){
case TrayIcon.Behavior.DisplayOnly: return "icon";
case TrayIcon.Behavior.MinimizeToTray: return "minimize";
case TrayIcon.Behavior.CloseToTray: return "close";
case TrayIcon.Behavior.Combined: return "combined";
default: return "off";
}
}
}
private static string NotificationPosition{
get{
switch(UserConfig.NotificationPosition){
case TweetNotification.Position.TopLeft: return "top left";
case TweetNotification.Position.TopRight: return "top right";
case TweetNotification.Position.BottomLeft: return "bottom left";
case TweetNotification.Position.BottomRight: return "bottom right";
default: return "custom";
}
}
}
private static string NotificationSize{
get{
switch(UserConfig.NotificationSize){
case TweetNotification.Size.Auto: return "auto";
default: return 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(){}
}
}
}

View File

@@ -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,44 +23,133 @@ 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() {
this.labelAbout = new TweetDck.Core.Controls.RichTextLabel(); this.pictureLogo = new System.Windows.Forms.PictureBox();
this.labelDescription = new System.Windows.Forms.Label();
this.labelTips = new System.Windows.Forms.LinkLabel();
this.labelWebsite = new System.Windows.Forms.LinkLabel();
this.tablePanelLinks = new System.Windows.Forms.TableLayoutPanel();
this.labelIssues = new System.Windows.Forms.LinkLabel();
((System.ComponentModel.ISupportInitialize)(this.pictureLogo)).BeginInit();
this.tablePanelLinks.SuspendLayout();
this.SuspendLayout(); this.SuspendLayout();
// //
// labelAbout // pictureLogo
// //
this.labelAbout.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) this.pictureLogo.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None;
this.pictureLogo.ErrorImage = null;
this.pictureLogo.InitialImage = null;
this.pictureLogo.Location = new System.Drawing.Point(12, 12);
this.pictureLogo.Name = "pictureLogo";
this.pictureLogo.Size = new System.Drawing.Size(96, 96);
this.pictureLogo.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
this.pictureLogo.TabIndex = 0;
this.pictureLogo.TabStop = false;
//
// labelDescription
//
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.labelAbout.BackColor = System.Drawing.Color.White; this.labelDescription.Font = new System.Drawing.Font("Segoe UI", 9.75F);
this.labelAbout.BorderStyle = System.Windows.Forms.BorderStyle.None; this.labelDescription.Location = new System.Drawing.Point(114, 12);
this.labelAbout.Location = new System.Drawing.Point(12, 12); this.labelDescription.Name = "labelDescription";
this.labelAbout.Name = "labelAbout"; this.labelDescription.Size = new System.Drawing.Size(232, 113);
this.labelAbout.ReadOnly = true; this.labelDescription.TabIndex = 0;
this.labelAbout.ScrollBars = System.Windows.Forms.RichTextBoxScrollBars.None; //
this.labelAbout.Size = new System.Drawing.Size(360, 126); // labelTips
this.labelAbout.TabIndex = 0; //
this.labelAbout.TabStop = false; this.labelTips.Dock = System.Windows.Forms.DockStyle.Fill;
this.labelAbout.Text = ""; this.labelTips.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular);
this.labelAbout.Click += new System.EventHandler(this.labelAbout_Click); this.labelTips.LinkArea = new System.Windows.Forms.LinkArea(0, 0);
this.labelTips.Location = new System.Drawing.Point(117, 0);
this.labelTips.Margin = new System.Windows.Forms.Padding(0);
this.labelTips.Name = "labelTips";
this.labelTips.Size = new System.Drawing.Size(99, 18);
this.labelTips.TabIndex = 1;
this.labelTips.Text = "Tips && Tricks";
this.labelTips.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.labelTips.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.OnLinkClicked);
//
// labelWebsite
//
this.labelWebsite.AutoSize = true;
this.labelWebsite.Dock = System.Windows.Forms.DockStyle.Fill;
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.Location = new System.Drawing.Point(0, 0);
this.labelWebsite.Margin = new System.Windows.Forms.Padding(0);
this.labelWebsite.Name = "labelWebsite";
this.labelWebsite.Size = new System.Drawing.Size(117, 18);
this.labelWebsite.TabIndex = 0;
this.labelWebsite.Text = "Official Website";
this.labelWebsite.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
this.labelWebsite.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.OnLinkClicked);
//
// tablePanelLinks
//
this.tablePanelLinks.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.tablePanelLinks.ColumnCount = 3;
this.tablePanelLinks.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 35.16F));
this.tablePanelLinks.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 29.7F));
this.tablePanelLinks.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 35.14F));
this.tablePanelLinks.Controls.Add(this.labelIssues, 2, 0);
this.tablePanelLinks.Controls.Add(this.labelWebsite, 0, 0);
this.tablePanelLinks.Controls.Add(this.labelTips, 1, 0);
this.tablePanelLinks.Location = new System.Drawing.Point(12, 128);
this.tablePanelLinks.Name = "tablePanelLinks";
this.tablePanelLinks.RowCount = 1;
this.tablePanelLinks.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tablePanelLinks.Size = new System.Drawing.Size(334, 18);
this.tablePanelLinks.TabIndex = 1;
//
// labelIssues
//
this.labelIssues.Dock = System.Windows.Forms.DockStyle.Fill;
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.Location = new System.Drawing.Point(216, 0);
this.labelIssues.Margin = new System.Windows.Forms.Padding(0);
this.labelIssues.Name = "labelIssues";
this.labelIssues.Size = new System.Drawing.Size(118, 18);
this.labelIssues.TabIndex = 2;
this.labelIssues.Text = "Report an Issue";
this.labelIssues.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
this.labelIssues.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.OnLinkClicked);
// //
// FormAbout // FormAbout
// //
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(384, 150); this.ClientSize = new System.Drawing.Size(358, 156);
this.Controls.Add(this.labelAbout); this.Controls.Add(this.tablePanelLinks);
this.Controls.Add(this.labelDescription);
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.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();
this.tablePanelLinks.ResumeLayout(false);
this.tablePanelLinks.PerformLayout();
this.ResumeLayout(false); this.ResumeLayout(false);
} }
#endregion #endregion
private Controls.RichTextLabel labelAbout; private System.Windows.Forms.PictureBox pictureLogo;
private System.Windows.Forms.Label labelDescription;
private System.Windows.Forms.LinkLabel labelTips;
private System.Windows.Forms.LinkLabel labelWebsite;
private System.Windows.Forms.TableLayoutPanel tablePanelLinks;
private System.Windows.Forms.LinkLabel labelIssues;
} }
} }

View File

@@ -1,43 +1,46 @@
using System; using System.ComponentModel;
using System.Text; using System.Drawing;
using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDck.Core.Controls; using TweetDuck.Core.Utils;
using TweetDck.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 GitHubLink = "https://github.com/chylex/TweetDuck"; private const string TipsLink = "https://github.com/chylex/TweetDuck/wiki";
private const string IssuesLink = "https://github.com/chylex/TweetDuck/issues";
public FormAbout(){ public FormAbout(){
InitializeComponent(); InitializeComponent();
Text = "About "+Program.BrandName+" "+Program.VersionTag; Text = "About "+Program.BrandName+" "+Program.VersionTag;
StringBuilder build = new StringBuilder(); 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.";
build.Append(@"\fs22").Append(Program.BrandName).Append(@" was created by chylex as a replacement to the discontinued TweetDeck client for Windows, and is released under the MIT license.\par ");
build.Append(@"Official Website: ").Append(RichTextLabel.AddLink(Program.Website)).Append(@"\line ");
build.Append(@"Source Code: ").Append(RichTextLabel.AddLink(GitHubLink));
labelAbout.Rtf = RichTextLabel.Wrap(build.ToString()); labelWebsite.Links.Add(new LinkLabel.Link(0, labelWebsite.Text.Length, Program.Website));
labelTips.Links.Add(new LinkLabel.Link(0, labelTips.Text.Length, TipsLink));
labelIssues.Links.Add(new LinkLabel.Link(0, labelIssues.Text.Length, IssuesLink));
MemoryStream logoStream = new MemoryStream(Properties.Resources.avatar);
pictureLogo.Image = Image.FromStream(logoStream);
Disposed += (sender, args) => logoStream.Dispose();
} }
private void labelAbout_Click(object sender, EventArgs e){ // LinkClicked isn't working so fuck that private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e){
if (Cursor.Current != Cursors.Hand)return; BrowserUtils.OpenExternalBrowser(e.Link.LinkData as string);
// I don't even give a fuck, someone else PR a proper fix please
int index = labelAbout.GetCharIndexFromPosition(((MouseEventArgs)e).Location);
if (IsClickingOn(index,Program.Website)){
BrowserUtils.OpenExternalBrowser(Program.Website);
}
else if (IsClickingOn(index,GitHubLink)){
BrowserUtils.OpenExternalBrowser(GitHubLink);
}
} }
private bool IsClickingOn(int index, string substringSearch){ private void FormAbout_HelpRequested(object sender, HelpEventArgs hlpevent){
int substringIndex = labelAbout.Text.IndexOf(substringSearch,StringComparison.Ordinal); ShowGuide();
return index >= substringIndex && index <= substringIndex+substringSearch.Length; }
private void FormAbout_HelpButtonClicked(object sender, CancelEventArgs e){
e.Cancel = true;
ShowGuide();
}
private void ShowGuide(){
FormGuide.Show();
Close();
} }
} }
} }

View File

@@ -1,78 +0,0 @@
using TweetDck.Core.Controls;
namespace TweetDck.Core.Other {
partial class FormBackgroundWork {
/// <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.progressBarUseless = new System.Windows.Forms.ProgressBar();
this.labelDescription = new RichTextLabel();
this.SuspendLayout();
//
// progressBarUseless
//
this.progressBarUseless.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.progressBarUseless.Location = new System.Drawing.Point(12, 49);
this.progressBarUseless.MarqueeAnimationSpeed = 10;
this.progressBarUseless.Name = "progressBarUseless";
this.progressBarUseless.Size = new System.Drawing.Size(480, 23);
this.progressBarUseless.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
this.progressBarUseless.TabIndex = 0;
//
// labelDescription
//
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.Right)));
this.labelDescription.BorderStyle = System.Windows.Forms.BorderStyle.None;
this.labelDescription.Location = new System.Drawing.Point(12, 12);
this.labelDescription.Name = "labelDescription";
this.labelDescription.ReadOnly = true;
this.labelDescription.ScrollBars = System.Windows.Forms.RichTextBoxScrollBars.None;
this.labelDescription.Size = new System.Drawing.Size(480, 31);
this.labelDescription.TabIndex = 1;
this.labelDescription.TabStop = false;
this.labelDescription.Text = "";
//
// FormBackgroundWork
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(504, 84);
this.Controls.Add(this.labelDescription);
this.Controls.Add(this.progressBarUseless);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.Name = "FormBackgroundWork";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "TweetDeck Migration";
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.ProgressBar progressBarUseless;
private RichTextLabel labelDescription;
}
}

View File

@@ -1,18 +0,0 @@
using System;
using System.Windows.Forms;
using TweetDck.Core.Controls;
namespace TweetDck.Core.Other{
partial class FormBackgroundWork : Form{
public FormBackgroundWork(){
InitializeComponent();
labelDescription.Rtf = RichTextLabel.Wrap(@"Please, watch this informationless progress bar showcase while some magic happens in the background...");
}
public void ShowWorkDialog(Action onBegin){
Shown += (sender, args) => onBegin();
ShowDialog();
}
}
}

44
Core/Other/FormGuide.Designer.cs generated Normal file
View 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
}
}

119
Core/Other/FormGuide.cs Normal file
View File

@@ -0,0 +1,119 @@
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);
this.browser = new ChromiumWebBrowser(url){
MenuHandler = new ContextMenuGuide(owner),
JsDialogHandler = new JavaScriptDialogHandler(),
LifeSpanHandler = new LifeSpanHandler(),
RequestHandler = new RequestHandlerBase(true)
};
browser.LoadingStateChanged += browser_LoadingStateChanged;
browser.FrameLoadEnd += browser_FrameLoadEnd;
browser.BrowserSettings.BackgroundColor = (uint)BackColor.ToArgb();
browser.Dock = DockStyle.None;
browser.Location = ControlExtensions.InvisibleLocation;
browser.SetupResourceHandler(DummyPage);
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");
}
}
}

80
Core/Other/FormMessage.Designer.cs generated Normal file
View File

@@ -0,0 +1,80 @@
namespace TweetDuck.Core.Other {
partial class FormMessage {
/// <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.panelActions = new System.Windows.Forms.Panel();
this.labelMessage = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// panelActions
//
this.panelActions.BackColor = System.Drawing.SystemColors.Control;
this.panelActions.Dock = System.Windows.Forms.DockStyle.Bottom;
this.panelActions.Location = new System.Drawing.Point(0, 84);
this.panelActions.Name = "panelActions";
this.panelActions.Size = new System.Drawing.Size(98, 49);
this.panelActions.TabIndex = 0;
//
// labelMessage
//
this.labelMessage.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.labelMessage.AutoSize = true;
this.labelMessage.Font = System.Drawing.SystemFonts.MessageBoxFont;
this.labelMessage.Location = new System.Drawing.Point(62, 34);
this.labelMessage.Margin = new System.Windows.Forms.Padding(53, 24, 27, 24);
this.labelMessage.MaximumSize = new System.Drawing.Size(600, 0);
this.labelMessage.MinimumSize = new System.Drawing.Size(0, 24);
this.labelMessage.Name = "labelMessage";
this.labelMessage.Size = new System.Drawing.Size(0, 24);
this.labelMessage.TabIndex = 1;
this.labelMessage.SizeChanged += new System.EventHandler(this.labelMessage_SizeChanged);
//
// FormMessage
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.White;
this.ClientSize = new System.Drawing.Size(98, 133);
this.Controls.Add(this.labelMessage);
this.Controls.Add(this.panelActions);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "FormMessage";
this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.SizeChanged += new System.EventHandler(this.FormMessage_SizeChanged);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Panel panelActions;
private System.Windows.Forms.Label labelMessage;
}
}

222
Core/Other/FormMessage.cs Normal file
View File

@@ -0,0 +1,222 @@
using System;
using System.Drawing;
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
}
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 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 bool isReady;
private readonly float dpiScale;
private int realFormWidth, minFormWidth;
private int buttonCount;
private int prevLabelWidth, prevLabelHeight;
private bool wasLabelMultiline;
public FormMessage(string caption, string text, MessageBoxIcon messageIcon){
InitializeComponent();
this.dpiScale = this.GetDPIScale();
this.prevLabelWidth = labelMessage.Width;
this.prevLabelHeight = labelMessage.Height;
this.minFormWidth = BrowserUtils.Scale(42, dpiScale);
switch(messageIcon){
case MessageBoxIcon.Information:
icon = SystemIcons.Information;
break;
case MessageBoxIcon.Warning:
icon = SystemIcons.Warning;
break;
case MessageBoxIcon.Error:
icon = SystemIcons.Error;
break;
case MessageBoxIcon.Question:
icon = SystemIcons.Question;
break;
default:
icon = null;
labelMessage.Location = new Point(BrowserUtils.Scale(19, dpiScale), labelMessage.Location.Y); // 19 instead of 9 due to larger height
break;
}
this.isReady = true;
this.Text = caption;
this.labelMessage.Text = text.Replace("\r", "").Replace("\n", Environment.NewLine);
}
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{
Anchor = AnchorStyles.Bottom,
Font = SystemFonts.MessageBoxFont,
Location = new Point(0, 12),
Size = new Size(BrowserUtils.Scale(88, dpiScale), BrowserUtils.Scale(26, dpiScale)),
TabIndex = 256-buttonCount,
Text = title,
UseVisualStyleBackColor = true
};
button.Click += (sender, args) => {
ClickedButton = (Button)sender;
DialogResult = result;
Close();
};
panelActions.Controls.Add(button);
++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;
}
public void AddActionControl(Control 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;
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){
if (!isReady){
return;
}
bool isMultiline = labelMessage.Height > labelMessage.MinimumSize.Height;
int labelOffset = BrowserUtils.Scale(8, dpiScale);
if (isMultiline && !wasLabelMultiline){
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y-labelOffset);
prevLabelHeight += labelOffset;
}
else if (!isMultiline && wasLabelMultiline){
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y+labelOffset);
prevLabelHeight -= labelOffset;
}
realFormWidth = ClientWidth-(icon == null ? BrowserUtils.Scale(50, dpiScale) : 0)+labelMessage.Width-prevLabelWidth;
ClientWidth = Math.Max(realFormWidth, minFormWidth);
Height += labelMessage.Height-prevLabelHeight;
prevLabelWidth = labelMessage.Width;
prevLabelHeight = labelMessage.Height;
wasLabelMultiline = isMultiline;
}
protected override void OnPaint(PaintEventArgs e){
if (icon != null){
e.Graphics.DrawIcon(icon, BrowserUtils.Scale(25, dpiScale), 1+BrowserUtils.Scale(25, dpiScale));
}
base.OnPaint(e);
}
}
}

123
Core/Other/FormPlugins.Designer.cs generated Normal file
View File

@@ -0,0 +1,123 @@
namespace TweetDuck.Core.Other {
partial class FormPlugins {
/// <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.btnClose = new System.Windows.Forms.Button();
this.btnReload = new System.Windows.Forms.Button();
this.btnOpenFolder = new System.Windows.Forms.Button();
this.flowLayoutPlugins = new TweetDuck.Plugins.Controls.PluginListFlowLayout();
this.timerLayout = new System.Windows.Forms.Timer(this.components);
this.SuspendLayout();
//
// btnClose
//
this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnClose.AutoSize = true;
this.btnClose.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.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.btnClose.Size = new System.Drawing.Size(50, 25);
this.btnClose.TabIndex = 1;
this.btnClose.Text = "Close";
this.btnClose.UseVisualStyleBackColor = true;
this.btnClose.Click += new System.EventHandler(this.btnClose_Click);
//
// btnReload
//
this.btnReload.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.btnReload.AutoSize = true;
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.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.btnReload.Size = new System.Drawing.Size(74, 25);
this.btnReload.TabIndex = 2;
this.btnReload.Text = "Reload All";
this.btnReload.UseVisualStyleBackColor = true;
this.btnReload.Click += new System.EventHandler(this.btnReload_Click);
//
// btnOpenFolder
//
this.btnOpenFolder.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.btnOpenFolder.AutoSize = true;
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.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.btnOpenFolder.Size = new System.Drawing.Size(123, 25);
this.btnOpenFolder.TabIndex = 3;
this.btnOpenFolder.Text = "Open Plugin Folder";
this.btnOpenFolder.UseVisualStyleBackColor = true;
this.btnOpenFolder.Click += new System.EventHandler(this.btnOpenFolder_Click);
//
// flowLayoutPlugins
//
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.Right)));
this.flowLayoutPlugins.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.flowLayoutPlugins.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowLayoutPlugins.Location = new System.Drawing.Point(12, 12);
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
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(704, 470);
this.Controls.Add(this.flowLayoutPlugins);
this.Controls.Add(this.btnOpenFolder);
this.Controls.Add(this.btnReload);
this.Controls.Add(this.btnClose);
this.Icon = global::TweetDuck.Properties.Resources.icon;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.MinimumSize = new System.Drawing.Size(640, 360);
this.Name = "FormPlugins";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button btnClose;
private System.Windows.Forms.Button btnReload;
private System.Windows.Forms.Button btnOpenFolder;
private Plugins.Controls.PluginListFlowLayout flowLayoutPlugins;
private System.Windows.Forms.Timer timerLayout;
}
}

112
Core/Other/FormPlugins.cs Normal file
View File

@@ -0,0 +1,112 @@
using System;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using TweetDuck.Configuration;
using TweetDuck.Plugins;
using TweetDuck.Plugins.Controls;
namespace TweetDuck.Core.Other{
sealed partial class FormPlugins : Form, FormManager.IAppDialog{
private static UserConfig Config => Program.Config.User;
private readonly PluginManager pluginManager;
public FormPlugins(){
InitializeComponent();
Text = Program.BrandName+" Plugins";
}
public FormPlugins(PluginManager pluginManager) : this(){
this.pluginManager = pluginManager;
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) => {
ReloadPluginList();
};
FormClosed += (sender, args) => {
Config.PluginsWindowSize = Size;
Config.Save();
};
ResizeEnd += (sender, args) => {
timerLayout.Start();
};
}
private int GetPluginOrderIndex(Plugin plugin){
return !plugin.CanRun ? 0 : pluginManager.Config.IsEnabled(plugin) ? 1 : 2;
}
private void ReloadPluginList(){
flowLayoutPlugins.Controls.Clear();
flowLayoutPlugins.SuspendLayout();
foreach(Plugin plugin in pluginManager.Plugins.OrderBy(GetPluginOrderIndex).ThenBy(plugin => plugin.Name)){
flowLayoutPlugins.Controls.Add(new PluginControl(pluginManager, plugin));
flowLayoutPlugins.Controls.Add(new Panel{
BackColor = Color.DimGray,
Margin = new Padding(0),
Size = new Size(1, 1)
});
}
flowLayoutPlugins.ResumeLayout(true);
timerLayout_Tick(null, EventArgs.Empty);
timerLayout.Start();
}
private void timerLayout_Tick(object sender, EventArgs e){
timerLayout.Stop();
// 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);
}
public void flowLayoutPlugins_Resize(object sender, EventArgs e){
Control lastPlugin = flowLayoutPlugins.Controls.OfType<PluginControl>().LastOrDefault();
if (lastPlugin == null){
return;
}
bool showScrollBar = lastPlugin.Location.Y+lastPlugin.Height+1 >= flowLayoutPlugins.Height;
int horizontalOffset = showScrollBar ? SystemInformation.VerticalScrollBarWidth : 0;
flowLayoutPlugins.AutoScroll = showScrollBar;
flowLayoutPlugins.VerticalScroll.Visible = showScrollBar;
foreach(Control control in flowLayoutPlugins.Controls){
control.Width = flowLayoutPlugins.Width-control.Margin.Horizontal-horizontalOffset;
}
flowLayoutPlugins.Controls[flowLayoutPlugins.Controls.Count-1].Visible = !showScrollBar;
flowLayoutPlugins.Focus();
}
private void btnOpenFolder_Click(object sender, EventArgs e){
using(Process.Start("explorer.exe", '"'+pluginManager.PathCustomPlugins+'"')){}
}
private void btnReload_Click(object sender, EventArgs e){
if (FormMessage.Warning("Reloading Plugins", "This will also reload the browser window. Do you want to proceed?", FormMessage.Yes, FormMessage.No)){
pluginManager.Reload();
ReloadPluginList();
}
}
private void btnClose_Click(object sender, EventArgs e){
Close();
}
}
}

View File

@@ -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.
@@ -23,393 +23,89 @@
/// 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(FormSettings)); this.btnClose = new System.Windows.Forms.Button();
this.groupNotificationLocation = new System.Windows.Forms.GroupBox(); this.panelContents = new System.Windows.Forms.Panel();
this.labelDisplay = new System.Windows.Forms.Label(); this.panelButtons = new System.Windows.Forms.Panel();
this.comboBoxDisplay = new System.Windows.Forms.ComboBox(); this.btnManageOptions = new System.Windows.Forms.Button();
this.labelEdgeDistance = new System.Windows.Forms.Label();
this.trackBarEdgeDistance = new System.Windows.Forms.TrackBar();
this.radioLocCustom = new System.Windows.Forms.RadioButton();
this.radioLocBR = new System.Windows.Forms.RadioButton();
this.radioLocBL = new System.Windows.Forms.RadioButton();
this.radioLocTR = new System.Windows.Forms.RadioButton();
this.radioLocTL = new System.Windows.Forms.RadioButton();
this.tableLayout = new System.Windows.Forms.TableLayoutPanel();
this.tableColumn2Panel = new System.Windows.Forms.Panel();
this.groupUserInterface = new System.Windows.Forms.GroupBox();
this.checkExpandLinks = new System.Windows.Forms.CheckBox();
this.comboBoxTrayType = new System.Windows.Forms.ComboBox();
this.labelTrayType = new System.Windows.Forms.Label();
this.checkUpdateNotifications = new System.Windows.Forms.CheckBox();
this.checkNotificationTimer = new System.Windows.Forms.CheckBox();
this.groupNotificationDuration = new System.Windows.Forms.GroupBox();
this.radioDurVeryLong = new System.Windows.Forms.RadioButton();
this.radioDurLong = new System.Windows.Forms.RadioButton();
this.radioDurMedium = new System.Windows.Forms.RadioButton();
this.radioDurShort = new System.Windows.Forms.RadioButton();
this.groupNotificationLocation.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.trackBarEdgeDistance)).BeginInit();
this.tableLayout.SuspendLayout();
this.tableColumn2Panel.SuspendLayout();
this.groupUserInterface.SuspendLayout();
this.groupNotificationDuration.SuspendLayout();
this.SuspendLayout(); this.SuspendLayout();
// //
// groupNotificationLocation // btnClose
// //
this.groupNotificationLocation.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 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);
this.btnClose.Location = new System.Drawing.Point(747, 500);
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 = 3;
this.btnClose.Text = "Close";
this.btnClose.UseVisualStyleBackColor = true;
this.btnClose.Click += new System.EventHandler(this.btnClose_Click);
//
// panelContents
//
this.panelContents.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.groupNotificationLocation.Controls.Add(this.labelDisplay); this.panelContents.AutoScroll = true;
this.groupNotificationLocation.Controls.Add(this.comboBoxDisplay); this.panelContents.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.groupNotificationLocation.Controls.Add(this.labelEdgeDistance); this.panelContents.Location = new System.Drawing.Point(135, 12);
this.groupNotificationLocation.Controls.Add(this.trackBarEdgeDistance); this.panelContents.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3);
this.groupNotificationLocation.Controls.Add(this.radioLocCustom); this.panelContents.Name = "panelContents";
this.groupNotificationLocation.Controls.Add(this.radioLocBR); this.panelContents.Size = new System.Drawing.Size(662, 482);
this.groupNotificationLocation.Controls.Add(this.radioLocBL); this.panelContents.TabIndex = 1;
this.groupNotificationLocation.Controls.Add(this.radioLocTR);
this.groupNotificationLocation.Controls.Add(this.radioLocTL);
this.groupNotificationLocation.Location = new System.Drawing.Point(6, 6);
this.groupNotificationLocation.Name = "groupNotificationLocation";
this.groupNotificationLocation.Size = new System.Drawing.Size(183, 278);
this.groupNotificationLocation.TabIndex = 0;
this.groupNotificationLocation.TabStop = false;
this.groupNotificationLocation.Text = "Notification Location";
// //
// labelDisplay // panelButtons
// //
this.labelDisplay.AutoSize = true; this.panelButtons.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
this.labelDisplay.Location = new System.Drawing.Point(6, 148); | System.Windows.Forms.AnchorStyles.Left)));
this.labelDisplay.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0); this.panelButtons.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.labelDisplay.Name = "labelDisplay"; this.panelButtons.Location = new System.Drawing.Point(12, 12);
this.labelDisplay.Size = new System.Drawing.Size(41, 13); this.panelButtons.Margin = new System.Windows.Forms.Padding(3, 3, 0, 3);
this.labelDisplay.TabIndex = 8; this.panelButtons.Name = "panelButtons";
this.labelDisplay.Text = "Display"; this.panelButtons.Size = new System.Drawing.Size(124, 482);
this.panelButtons.TabIndex = 0;
// //
// comboBoxDisplay // btnManageOptions
// //
this.comboBoxDisplay.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) this.btnManageOptions.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
| System.Windows.Forms.AnchorStyles.Right))); this.btnManageOptions.AutoSize = true;
this.comboBoxDisplay.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.btnManageOptions.Font = new System.Drawing.Font("Segoe UI", 9F);
this.comboBoxDisplay.FormattingEnabled = true; this.btnManageOptions.Location = new System.Drawing.Point(12, 500);
this.comboBoxDisplay.Location = new System.Drawing.Point(9, 164); this.btnManageOptions.Name = "btnManageOptions";
this.comboBoxDisplay.Name = "comboBoxDisplay"; this.btnManageOptions.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.comboBoxDisplay.Size = new System.Drawing.Size(168, 21); this.btnManageOptions.Size = new System.Drawing.Size(109, 25);
this.comboBoxDisplay.TabIndex = 7; this.btnManageOptions.TabIndex = 4;
this.comboBoxDisplay.SelectedValueChanged += new System.EventHandler(this.comboBoxDisplay_SelectedValueChanged); this.btnManageOptions.Text = "Manage Options";
// this.btnManageOptions.UseVisualStyleBackColor = true;
// labelEdgeDistance this.btnManageOptions.Click += new System.EventHandler(this.btnManageOptions_Click);
//
this.labelEdgeDistance.AutoSize = true;
this.labelEdgeDistance.Location = new System.Drawing.Point(6, 197);
this.labelEdgeDistance.Margin = new System.Windows.Forms.Padding(3, 9, 3, 0);
this.labelEdgeDistance.Name = "labelEdgeDistance";
this.labelEdgeDistance.Size = new System.Drawing.Size(103, 13);
this.labelEdgeDistance.TabIndex = 6;
this.labelEdgeDistance.Text = "Distance From Edge";
//
// trackBarEdgeDistance
//
this.trackBarEdgeDistance.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.trackBarEdgeDistance.LargeChange = 8;
this.trackBarEdgeDistance.Location = new System.Drawing.Point(6, 213);
this.trackBarEdgeDistance.Maximum = 40;
this.trackBarEdgeDistance.Minimum = 8;
this.trackBarEdgeDistance.Name = "trackBarEdgeDistance";
this.trackBarEdgeDistance.Size = new System.Drawing.Size(171, 45);
this.trackBarEdgeDistance.SmallChange = 2;
this.trackBarEdgeDistance.TabIndex = 5;
this.trackBarEdgeDistance.TickFrequency = 2;
this.trackBarEdgeDistance.Value = 8;
this.trackBarEdgeDistance.ValueChanged += new System.EventHandler(this.trackBarEdgeDistance_ValueChanged);
//
// radioLocCustom
//
this.radioLocCustom.AutoSize = true;
this.radioLocCustom.Location = new System.Drawing.Point(7, 116);
this.radioLocCustom.Name = "radioLocCustom";
this.radioLocCustom.Size = new System.Drawing.Size(60, 17);
this.radioLocCustom.TabIndex = 4;
this.radioLocCustom.TabStop = true;
this.radioLocCustom.Text = "Custom";
this.radioLocCustom.UseVisualStyleBackColor = true;
this.radioLocCustom.CheckedChanged += new System.EventHandler(this.radioLoc_CheckedChanged);
this.radioLocCustom.Click += new System.EventHandler(this.radioLoc_Click);
//
// radioLocBR
//
this.radioLocBR.AutoSize = true;
this.radioLocBR.Location = new System.Drawing.Point(7, 92);
this.radioLocBR.Name = "radioLocBR";
this.radioLocBR.Size = new System.Drawing.Size(86, 17);
this.radioLocBR.TabIndex = 3;
this.radioLocBR.TabStop = true;
this.radioLocBR.Text = "Bottom Right";
this.radioLocBR.UseVisualStyleBackColor = true;
this.radioLocBR.CheckedChanged += new System.EventHandler(this.radioLoc_CheckedChanged);
this.radioLocBR.Click += new System.EventHandler(this.radioLoc_Click);
//
// radioLocBL
//
this.radioLocBL.AutoSize = true;
this.radioLocBL.Location = new System.Drawing.Point(7, 68);
this.radioLocBL.Name = "radioLocBL";
this.radioLocBL.Size = new System.Drawing.Size(79, 17);
this.radioLocBL.TabIndex = 2;
this.radioLocBL.TabStop = true;
this.radioLocBL.Text = "Bottom Left";
this.radioLocBL.UseVisualStyleBackColor = true;
this.radioLocBL.CheckedChanged += new System.EventHandler(this.radioLoc_CheckedChanged);
this.radioLocBL.Click += new System.EventHandler(this.radioLoc_Click);
//
// radioLocTR
//
this.radioLocTR.AutoSize = true;
this.radioLocTR.Location = new System.Drawing.Point(7, 44);
this.radioLocTR.Name = "radioLocTR";
this.radioLocTR.Size = new System.Drawing.Size(72, 17);
this.radioLocTR.TabIndex = 1;
this.radioLocTR.TabStop = true;
this.radioLocTR.Text = "Top Right";
this.radioLocTR.UseVisualStyleBackColor = true;
this.radioLocTR.CheckedChanged += new System.EventHandler(this.radioLoc_CheckedChanged);
this.radioLocTR.Click += new System.EventHandler(this.radioLoc_Click);
//
// radioLocTL
//
this.radioLocTL.AutoSize = true;
this.radioLocTL.Location = new System.Drawing.Point(7, 20);
this.radioLocTL.Name = "radioLocTL";
this.radioLocTL.Size = new System.Drawing.Size(65, 17);
this.radioLocTL.TabIndex = 0;
this.radioLocTL.TabStop = true;
this.radioLocTL.Text = "Top Left";
this.radioLocTL.UseVisualStyleBackColor = true;
this.radioLocTL.CheckedChanged += new System.EventHandler(this.radioLoc_CheckedChanged);
this.radioLocTL.Click += new System.EventHandler(this.radioLoc_Click);
//
// tableLayout
//
this.tableLayout.ColumnCount = 2;
this.tableLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.tableLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.tableLayout.Controls.Add(this.tableColumn2Panel, 1, 0);
this.tableLayout.Controls.Add(this.groupNotificationLocation, 0, 0);
this.tableLayout.Dock = System.Windows.Forms.DockStyle.Fill;
this.tableLayout.GrowStyle = System.Windows.Forms.TableLayoutPanelGrowStyle.FixedSize;
this.tableLayout.Location = new System.Drawing.Point(0, 0);
this.tableLayout.Name = "tableLayout";
this.tableLayout.Padding = new System.Windows.Forms.Padding(3);
this.tableLayout.RowCount = 1;
this.tableLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayout.Size = new System.Drawing.Size(384, 290);
this.tableLayout.TabIndex = 2;
//
// tableColumn2Panel
//
this.tableColumn2Panel.Controls.Add(this.groupUserInterface);
this.tableColumn2Panel.Controls.Add(this.groupNotificationDuration);
this.tableColumn2Panel.Dock = System.Windows.Forms.DockStyle.Fill;
this.tableColumn2Panel.Location = new System.Drawing.Point(192, 3);
this.tableColumn2Panel.Margin = new System.Windows.Forms.Padding(0);
this.tableColumn2Panel.Name = "tableColumn2Panel";
this.tableColumn2Panel.Size = new System.Drawing.Size(189, 284);
this.tableColumn2Panel.TabIndex = 3;
//
// groupUserInterface
//
this.groupUserInterface.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.groupUserInterface.Controls.Add(this.checkExpandLinks);
this.groupUserInterface.Controls.Add(this.comboBoxTrayType);
this.groupUserInterface.Controls.Add(this.labelTrayType);
this.groupUserInterface.Controls.Add(this.checkUpdateNotifications);
this.groupUserInterface.Controls.Add(this.checkNotificationTimer);
this.groupUserInterface.Location = new System.Drawing.Point(3, 128);
this.groupUserInterface.Name = "groupUserInterface";
this.groupUserInterface.Size = new System.Drawing.Size(183, 153);
this.groupUserInterface.TabIndex = 3;
this.groupUserInterface.TabStop = false;
this.groupUserInterface.Text = "User Interface";
//
// checkExpandLinks
//
this.checkExpandLinks.AutoSize = true;
this.checkExpandLinks.Location = new System.Drawing.Point(6, 45);
this.checkExpandLinks.Margin = new System.Windows.Forms.Padding(3, 4, 3, 3);
this.checkExpandLinks.Name = "checkExpandLinks";
this.checkExpandLinks.Size = new System.Drawing.Size(166, 17);
this.checkExpandLinks.TabIndex = 11;
this.checkExpandLinks.Text = "Expand Links When Hovered";
this.checkExpandLinks.UseVisualStyleBackColor = true;
this.checkExpandLinks.CheckedChanged += new System.EventHandler(this.checkExpandLinks_CheckedChanged);
//
// comboBoxTrayType
//
this.comboBoxTrayType.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.comboBoxTrayType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxTrayType.FormattingEnabled = true;
this.comboBoxTrayType.Location = new System.Drawing.Point(9, 116);
this.comboBoxTrayType.Margin = new System.Windows.Forms.Padding(3, 3, 3, 12);
this.comboBoxTrayType.Name = "comboBoxTrayType";
this.comboBoxTrayType.Size = new System.Drawing.Size(168, 21);
this.comboBoxTrayType.TabIndex = 10;
this.comboBoxTrayType.SelectedIndexChanged += new System.EventHandler(this.comboBoxTrayType_SelectedIndexChanged);
//
// labelTrayType
//
this.labelTrayType.AutoSize = true;
this.labelTrayType.Location = new System.Drawing.Point(6, 100);
this.labelTrayType.Margin = new System.Windows.Forms.Padding(3, 11, 3, 0);
this.labelTrayType.Name = "labelTrayType";
this.labelTrayType.Size = new System.Drawing.Size(52, 13);
this.labelTrayType.TabIndex = 9;
this.labelTrayType.Text = "Tray Icon";
//
// checkUpdateNotifications
//
this.checkUpdateNotifications.AutoSize = true;
this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 69);
this.checkUpdateNotifications.Margin = new System.Windows.Forms.Padding(3, 4, 3, 3);
this.checkUpdateNotifications.Name = "checkUpdateNotifications";
this.checkUpdateNotifications.Size = new System.Drawing.Size(115, 17);
this.checkUpdateNotifications.TabIndex = 5;
this.checkUpdateNotifications.Text = "Check for Updates";
this.checkUpdateNotifications.UseVisualStyleBackColor = true;
this.checkUpdateNotifications.CheckedChanged += new System.EventHandler(this.checkUpdateNotifications_CheckedChanged);
//
// checkNotificationTimer
//
this.checkNotificationTimer.AutoSize = true;
this.checkNotificationTimer.Location = new System.Drawing.Point(6, 21);
this.checkNotificationTimer.Margin = new System.Windows.Forms.Padding(3, 5, 3, 3);
this.checkNotificationTimer.Name = "checkNotificationTimer";
this.checkNotificationTimer.Size = new System.Drawing.Size(145, 17);
this.checkNotificationTimer.TabIndex = 4;
this.checkNotificationTimer.Text = "Display Notification Timer";
this.checkNotificationTimer.UseVisualStyleBackColor = true;
this.checkNotificationTimer.CheckedChanged += new System.EventHandler(this.checkNotificationTimer_CheckedChanged);
//
// groupNotificationDuration
//
this.groupNotificationDuration.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.groupNotificationDuration.Controls.Add(this.radioDurVeryLong);
this.groupNotificationDuration.Controls.Add(this.radioDurLong);
this.groupNotificationDuration.Controls.Add(this.radioDurMedium);
this.groupNotificationDuration.Controls.Add(this.radioDurShort);
this.groupNotificationDuration.Location = new System.Drawing.Point(3, 3);
this.groupNotificationDuration.Name = "groupNotificationDuration";
this.groupNotificationDuration.Size = new System.Drawing.Size(183, 119);
this.groupNotificationDuration.TabIndex = 2;
this.groupNotificationDuration.TabStop = false;
this.groupNotificationDuration.Text = "Notification Duration";
//
// radioDurVeryLong
//
this.radioDurVeryLong.AutoSize = true;
this.radioDurVeryLong.Location = new System.Drawing.Point(6, 92);
this.radioDurVeryLong.Name = "radioDurVeryLong";
this.radioDurVeryLong.Size = new System.Drawing.Size(73, 17);
this.radioDurVeryLong.TabIndex = 3;
this.radioDurVeryLong.TabStop = true;
this.radioDurVeryLong.Text = "Very Long";
this.radioDurVeryLong.UseVisualStyleBackColor = true;
this.radioDurVeryLong.CheckedChanged += new System.EventHandler(this.radioDur_CheckedChanged);
this.radioDurVeryLong.Click += new System.EventHandler(this.radioDur_Click);
//
// radioDurLong
//
this.radioDurLong.AutoSize = true;
this.radioDurLong.Location = new System.Drawing.Point(6, 68);
this.radioDurLong.Name = "radioDurLong";
this.radioDurLong.Size = new System.Drawing.Size(49, 17);
this.radioDurLong.TabIndex = 2;
this.radioDurLong.TabStop = true;
this.radioDurLong.Text = "Long";
this.radioDurLong.UseVisualStyleBackColor = true;
this.radioDurLong.CheckedChanged += new System.EventHandler(this.radioDur_CheckedChanged);
this.radioDurLong.Click += new System.EventHandler(this.radioDur_Click);
//
// radioDurMedium
//
this.radioDurMedium.AutoSize = true;
this.radioDurMedium.Location = new System.Drawing.Point(6, 44);
this.radioDurMedium.Name = "radioDurMedium";
this.radioDurMedium.Size = new System.Drawing.Size(62, 17);
this.radioDurMedium.TabIndex = 1;
this.radioDurMedium.TabStop = true;
this.radioDurMedium.Text = "Medium";
this.radioDurMedium.UseVisualStyleBackColor = true;
this.radioDurMedium.CheckedChanged += new System.EventHandler(this.radioDur_CheckedChanged);
this.radioDurMedium.Click += new System.EventHandler(this.radioDur_Click);
//
// radioDurShort
//
this.radioDurShort.AutoSize = true;
this.radioDurShort.Location = new System.Drawing.Point(6, 20);
this.radioDurShort.Name = "radioDurShort";
this.radioDurShort.Size = new System.Drawing.Size(50, 17);
this.radioDurShort.TabIndex = 0;
this.radioDurShort.TabStop = true;
this.radioDurShort.Text = "Short";
this.radioDurShort.UseVisualStyleBackColor = true;
this.radioDurShort.CheckedChanged += new System.EventHandler(this.radioDur_CheckedChanged);
this.radioDurShort.Click += new System.EventHandler(this.radioDur_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(384, 290); this.ClientSize = new System.Drawing.Size(809, 537);
this.Controls.Add(this.tableLayout); this.Controls.Add(this.btnManageOptions);
this.Controls.Add(this.panelContents);
this.Controls.Add(this.panelButtons);
this.Controls.Add(this.btnClose);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.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";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormSettings_FormClosing); this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormSettings_FormClosing);
this.groupNotificationLocation.ResumeLayout(false);
this.groupNotificationLocation.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.trackBarEdgeDistance)).EndInit();
this.tableLayout.ResumeLayout(false);
this.tableColumn2Panel.ResumeLayout(false);
this.groupUserInterface.ResumeLayout(false);
this.groupUserInterface.PerformLayout();
this.groupNotificationDuration.ResumeLayout(false);
this.groupNotificationDuration.PerformLayout();
this.ResumeLayout(false); this.ResumeLayout(false);
this.PerformLayout();
} }
#endregion #endregion
private System.Windows.Forms.Button btnClose;
private System.Windows.Forms.GroupBox groupNotificationLocation; private System.Windows.Forms.Panel panelContents;
private System.Windows.Forms.RadioButton radioLocCustom; private System.Windows.Forms.Panel panelButtons;
private System.Windows.Forms.RadioButton radioLocBR; private System.Windows.Forms.Button btnManageOptions;
private System.Windows.Forms.RadioButton radioLocBL;
private System.Windows.Forms.RadioButton radioLocTR;
private System.Windows.Forms.RadioButton radioLocTL;
private System.Windows.Forms.Label labelEdgeDistance;
private System.Windows.Forms.TrackBar trackBarEdgeDistance;
private System.Windows.Forms.Label labelDisplay;
private System.Windows.Forms.ComboBox comboBoxDisplay;
private System.Windows.Forms.TableLayoutPanel tableLayout;
private System.Windows.Forms.Panel tableColumn2Panel;
private System.Windows.Forms.GroupBox groupUserInterface;
private System.Windows.Forms.GroupBox groupNotificationDuration;
private System.Windows.Forms.RadioButton radioDurVeryLong;
private System.Windows.Forms.RadioButton radioDurLong;
private System.Windows.Forms.RadioButton radioDurMedium;
private System.Windows.Forms.RadioButton radioDurShort;
private System.Windows.Forms.CheckBox checkNotificationTimer;
private System.Windows.Forms.CheckBox checkUpdateNotifications;
private System.Windows.Forms.ComboBox comboBoxTrayType;
private System.Windows.Forms.Label labelTrayType;
private System.Windows.Forms.CheckBox checkExpandLinks;
} }
} }

View File

@@ -1,153 +1,210 @@
using System; using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDck.Configuration; using TweetDuck.Core.Controls;
using TweetDck.Core.Handling; using TweetDuck.Core.Handling.General;
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 TweetDuck.Updates;
namespace TweetDck.Core.Other{ namespace TweetDuck.Core.Other{
sealed partial class FormSettings : Form{ sealed partial class FormSettings : Form, FormManager.IAppDialog{
private static UserConfig Config{ public bool ShouldReloadBrowser { get; private set; }
get{
return Program.UserConfig;
}
}
private readonly FormNotification notification; private readonly FormBrowser browser;
private bool isLoaded; private readonly PluginManager plugins;
public FormSettings(FormBrowser browserForm){ 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();
Shown += (sender, args) => isLoaded = true;
Text = Program.BrandName+" Settings"; Text = Program.BrandName+" Options";
notification = new FormNotification(browserForm,false){ CanMoveWindow = () => radioLocCustom.Checked }; this.browser = browser;
this.browser.PauseNotification();
notification.Move += (sender, args) => { this.plugins = plugins;
if (radioLocCustom.Checked){
Config.CustomNotificationPosition = notification.Location;
}
};
notification.Show(this); this.buttonHeight = BrowserUtils.Scale(39, this.GetDPIScale()) | 1;
switch(Config.NotificationPosition){ PrepareLoad();
case TweetNotification.Position.TopLeft: radioLocTL.Checked = true; break;
case TweetNotification.Position.TopRight: radioLocTR.Checked = true; break; AddButton("General", () => new TabSettingsGeneral(this.browser.ReloadColumns, updates));
case TweetNotification.Position.BottomLeft: radioLocBL.Checked = true; break; AddButton("Notifications", () => new TabSettingsNotifications(new FormNotificationExample(this.browser, this.plugins)));
case TweetNotification.Position.BottomRight: radioLocBR.Checked = true; break; AddButton("Sounds", () => new TabSettingsSounds(this.browser.PlaySoundNotification));
case TweetNotification.Position.Custom: radioLocCustom.Checked = true; break; 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)]);
} }
switch(Config.NotificationDuration){ private void PrepareLoad(){
case TweetNotification.Duration.Short: radioDurShort.Checked = true; break; Program.Config.ProgramRestartRequested += Config_ProgramRestartRequested;
case TweetNotification.Duration.Medium: radioDurMedium.Checked = true; break;
case TweetNotification.Duration.Long: radioDurLong.Checked = true; break;
case TweetNotification.Duration.VeryLong: radioDurVeryLong.Checked = true; break;
} }
comboBoxDisplay.Items.Add("(Same As "+Program.BrandName+")"); private void PrepareUnload(){ // TODO refactor this further later
currentTab.Control.OnClosing();
foreach(Screen screen in Screen.AllScreens){ Program.Config.ProgramRestartRequested -= Config_ProgramRestartRequested;
comboBoxDisplay.Items.Add(screen.DeviceName+" ("+screen.Bounds.Width+"x"+screen.Bounds.Height+")"); Program.Config.SaveAll();
} }
comboBoxDisplay.SelectedIndex = Math.Min(comboBoxDisplay.Items.Count-1,Config.NotificationDisplay); private void Config_ProgramRestartRequested(object sender, EventArgs e){
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)){
comboBoxTrayType.Items.Add("Disabled"); Program.Restart();
comboBoxTrayType.Items.Add("Display Icon Only"); }
comboBoxTrayType.Items.Add("Minimize to Tray");
comboBoxTrayType.Items.Add("Close to Tray");
comboBoxTrayType.SelectedIndex = Math.Min(Math.Max((int)Config.TrayBehavior,0),comboBoxTrayType.Items.Count-1);
trackBarEdgeDistance.Value = Config.NotificationEdgeDistance;
checkNotificationTimer.Checked = Config.DisplayNotificationTimer;
checkExpandLinks.Checked = Config.ExpandLinksOnHover;
checkUpdateNotifications.Checked = Config.EnableUpdateCheck;
} }
private void FormSettings_FormClosing(object sender, FormClosingEventArgs e){ private void FormSettings_FormClosing(object sender, FormClosingEventArgs e){
Config.Save(); PrepareUnload();
foreach(SettingsTab tab in tabs.Values){
if (tab.IsInitialized){
tab.Control.Dispose();
}
} }
private void radioLoc_CheckedChanged(object sender, EventArgs e){ browser.ResumeNotification();
if (!isLoaded)return;
if (radioLocTL.Checked)Config.NotificationPosition = TweetNotification.Position.TopLeft;
else if (radioLocTR.Checked)Config.NotificationPosition = TweetNotification.Position.TopRight;
else if (radioLocBL.Checked)Config.NotificationPosition = TweetNotification.Position.BottomLeft;
else if (radioLocBR.Checked)Config.NotificationPosition = TweetNotification.Position.BottomRight;
else if (radioLocCustom.Checked){
if (!Config.IsCustomNotificationPositionSet){
Config.CustomNotificationPosition = notification.Location;
} }
Config.NotificationPosition = TweetNotification.Position.Custom; 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;
}
} }
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = !radioLocCustom.Checked; Close();
notification.ShowNotificationForSettings(false); }
else{
FormClosing += FormSettings_FormClosing;
PrepareLoad();
}
}
}
private void btnClose_Click(object sender, EventArgs e){
Close();
}
private void AddButton<T>(string title, Func<T> constructor) where T : BaseTabSettings{
FlatButton btn = new FlatButton{
BackColor = SystemColors.Control,
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 ?? (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;
} }
private void radioLoc_Click(object sender, EventArgs e){
if (!isLoaded)return;
notification.ShowNotificationForSettings(false);
}
private void comboBoxDisplay_SelectedValueChanged(object sender, EventArgs e){
if (!isLoaded)return;
Config.NotificationDisplay = comboBoxDisplay.SelectedIndex;
notification.ShowNotificationForSettings(false);
}
private void trackBarEdgeDistance_ValueChanged(object sender, EventArgs e){
if (!isLoaded)return;
Config.NotificationEdgeDistance = trackBarEdgeDistance.Value;
notification.ShowNotificationForSettings(false);
}
private void radioDur_CheckedChanged(object sender, EventArgs e){
if (!isLoaded)return;
if (radioDurShort.Checked)Config.NotificationDuration = TweetNotification.Duration.Short;
else if (radioDurMedium.Checked)Config.NotificationDuration = TweetNotification.Duration.Medium;
else if (radioDurLong.Checked)Config.NotificationDuration = TweetNotification.Duration.Long;
else if (radioDurVeryLong.Checked)Config.NotificationDuration = TweetNotification.Duration.VeryLong;
notification.ShowNotificationForSettings(true);
}
private void radioDur_Click(object sender, EventArgs e){
if (!isLoaded)return;
notification.ShowNotificationForSettings(true);
}
private void comboBoxTrayType_SelectedIndexChanged(object sender, EventArgs e){
if (!isLoaded)return;
Config.TrayBehavior = (TrayIcon.Behavior)comboBoxTrayType.SelectedIndex;
}
private void checkNotificationTimer_CheckedChanged(object sender, EventArgs e){
if (!isLoaded)return;
Config.DisplayNotificationTimer = checkNotificationTimer.Checked;
notification.ShowNotificationForSettings(true);
}
private void checkExpandLinks_CheckedChanged(object sender, EventArgs e){
if (!isLoaded)return;
Config.ExpandLinksOnHover = checkExpandLinks.Checked;
}
private void checkUpdateNotifications_CheckedChanged(object sender, EventArgs e){
if (!isLoaded)return;
Config.EnableUpdateCheck = checkUpdateNotifications.Checked;
} }
} }
} }

View File

@@ -1,377 +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="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
AAABAAMAMDAAAAEAIACoJQAANgAAACAgAAABACAAqBAAAN4lAAAQEAAAAQAgAGgEAACGNgAAKAAAADAA
AABgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqHg5IrF+OlS+hjyMw4g7tcmM
PM/MjTzezY485s2OPObMjjzfyIw80MOJPLe8hDyNs387WKx8OyQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApHU4FrB8OGXGijzAzo889dKR
PP/SkTz/0pE8/9GRPP/RkDz/0JA8/9CQPP/RkDz/0ZE8/9KRPP/TkTz/0pE8/86PPPbGijzCt4E8bKp6
OxoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKN1OB+9hTuLzI486NOR
PP/SkTz/0ZA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GQ
PP/SkTz/0pE8/82OPOu9hTuOq3w8IgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACidTkFuoM8csyN
POrTkjz/0ZE8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GRPP/Tkjz/zY887bqEPHimeDsHAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKp6
OynDiDvA05I8/9GRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0ZA8/9ORPP/GijzFr308LgAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAArXw7UM2OPOvTkTz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/SkTz/zo887rOAPFYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAACxfjxm0ZA8/NGRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0ZE8/9GQPP64gjxtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAALaBPGXRkTz/0ZE8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GQPP/SkTz/uIM8bAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqnk6TtCQPPzRkTz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkDz/0ZA8/rSA
PFUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACtfDwmzo887dKRPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0ZE8/86PPPGtfDwrAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKZ4PAXFijzC0pE8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CURf/TpGT/2baJ/9/GpP/l0bf/59W//+fVvf/kz7P/3sOg/9mz
hP/ToWH/0JND/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9KRPP/EiTvJo3Y7CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALiC
PG/Tkjz/0JA8/9CQPP/QkDz/0JA8/9CSQf/Vq3P/5tO7//j07v//////////////////////////////
////////////////////////9vHq/+TQtf/UqG7/0JA+/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/Tkjz/uYM8dgAAAAAAAAAAAAAAAAAA
AAAAAAAArXw8Gc2OPOzRkTz/0JA8/9CQPP/QlEX/27qP//fz7P//////////////////////////////
////////////////////////////////////////////////////////8ObZ/9auev/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkTz/zI488J1x
OB8AAAAAAAAAAAAAAAAAAAAAuoM7h9OSPP/QkDz/0JA8/9GXTf/TpGb/27uS/9u6kP/ewZz/59W9//Xv
5///////////////////////////////////////////////////////////////////////////////
///r3cr/0ZxV/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/1JI8/7mDO5AAAAAAAAAAAAAAAADFj0cR0pNB6s+PO//Ojjv/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CRQP/TpGf/5NC2//38+v//////////////////////////////////////////////
////////////////////////+vf0/9auev/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0ZE8/8uOPO6SajYVAAAAAAAAAADzr1df7qxV/+WkT//ZmUT/0JA9/86O
O//Pjzv/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GaUv/l07r/////////////////////////
///////////////////////////////////////////////////buo//0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9OSPP+ldjdoAAAAAAAAAADurFW67qxV//Ct
Vv/vrVb/6adR/92dSP/Skz//zo47/8+PO//QkDz/0JA8/9CQPP/QkDz/0JJB/9Smaf/dv5j/+fXw////
////////////////////////////////////////////////////////////////////////2riM/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9KRPP/FiTzGoHQ6Ae6s
VRrurFXz7qxV/+6sVf/urFX/761W//CuVv/sqlT/4qFM/9aVQv/Pjzv/zo47/9CQPP/VqnH/8+vh////
////////////////////////////////////////////////////////////////////////////////
/////////////9aueP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GQ
PP/Ojzz4oHM5Iu6sVU3urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/vrVX/8K5W/+6sVf/mpU//2plF/9q6
j///////////////////////////////////////////////////////////////////////////////
//////////////////////////////n18P/Rm1P/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/SkTz/rXs6Vu6sVYPurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/wrVb/6r2B////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////o18L/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/v4Y8j+6sVa3urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFb/9Onb///////+/v3/////////////////////////////////////////
////////////////////////////////////////////////////////////////////////1qx2/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/wYc7uO6sVcrurFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/trlr/67Zy/+m+h//y5NP/////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////7+TW/9CQPf/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkTz/yYw80e6s
VdvurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/6sme//v49P//////////////
////////////////////////////////////////////////////////////////////////////////
/////////////////////////////9arc//QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/RkDz/zI083+6sVeLurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/u2b3/////////
////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////+fVvv/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/zo885u6sVeLurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+vL
of//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////r28v/Rl0r/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/zo885u6sVdrurFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7LBj//r28f//////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
///Vq3P/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkDz/zI083+6sVcrurFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/6cKO////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
///////////////////ewp7/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkTz/yYw80O6s
VazurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7NOy//z59v/u2b7/+fPr////////////////////
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////n1sD/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/SkTz/xIo8uO6sVYHurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/67Zx/+2uWf/qy6H/////////
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////v5dj/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/SkTz/vYU8je6sVUvurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+nC
kP///////////////////////////////////////////////////////fz6//fu5P/w4Mv/793F//7+
/v/////////////////////////////////////////////////////////////////17eT/0JA//86O
Ov/Pjzv/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/tYA8Vu6sVRnurFXy7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7a5b//fw5///////////////////////////////////////+/jz/+7av//pwIr/67Jn/+6t
Vv/urFX/6rd1////////////////////////////////////////////////////////////////////
///48ej/6KhU/92cR//Tkj7/zo47/8+PO//QkDz/0JA8/9GQPP/Pjzz3rXw8IAAAAADurFW37qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/6b2D//////////////////////////////////nz6//qyZ3/7K9h/+6s
Vf/urFX/7qxV/+6sVf/urFX/6rl6////////////////////////////////////////////////////
///////////////////9/Pr/6r6C//CuV//sqlT/4aBL/9aWQv/Pjzv/zo47/9GRPP/GijzCp3g8AQAA
AADurFVb7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/68yk///////////////////////+/fz/68+q/+2v
XP/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/6rNr//7+/f//////////////////////////////
/////////////////////////////////////////////+nEk//vrVX/8K5W/+6sVf/lpE//2ZlF/9KR
Pf+zfzpoAAAAAAAAAADurFUP7qxV5u6sVf/urFX/7qxV/+6sVf/urFX/7NGu//////////////////fu
5P/quHb/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxW//Xr3///////////////
///////////////////////////////////////////////////7+PP/+vXu///////pxJL/7qxV/+6s
Vf/wrVb/8K1W/+mnUevUmUsUAAAAAAAAAAAAAAAA7qxVgO6sVf/urFX/7qxV/+6sVf/urFX/6sqg////
////////8eLO/+yvX//urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+nF
lf/////////////////////////////////////////////////////////////////8+vf/6cWV/+q5
ev/s07H/6rd1/+6sVf/urFX/7qxV/++tVogAAAAAAAAAAAAAAAAAAAAA7qxVFe6sVejurFX/7qxV/+6s
Vf/urFX/6bp8///////w4Mn/7a1Z/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/x4cz////////////////////////////////////////////////////+//7+
/v/////////+/+m8gP/urFX/7a1Y/+6sVf/urFX/7qxV7O6sVRoAAAAAAAAAAAAAAAAAAAAAAAAAAO6s
VWburFX/7qxV/+6sVf/urFX/7a5b/+zTs//sr1//7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/sr1//8uPQ////////////////////////////////////
////////6suj/+qzav/pxpj/9Onb//n07f/rr2D/7qxV/+6sVf/urFX/7qxVbwAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAO6sVQPurFW67qxV/+6sVf/urFX/7qxV/+6sVv/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7axY/+nGl//27uP/////////
/////////fz6//Lj0P/qunv/7qxV/+6sVf/urFX/7qxV/+m9gv/qt3X/7qxV/+6sVf/urFXB7qxVBQAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADurFUg7qxV6O6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/qtnP/6sqg/+3WuP/s07P/6cSS/+uyZf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVv/trVr/7qxV/+6s
VezurFUlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVRu6sVfjurFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV++6sVUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6s
VVzurFX97qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxVYwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAADurFVd7qxV+O6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVfvurFVjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVR+6sVeXurFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV6O6sVU0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVSPurFW37qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFW77qxVJgAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AADurFUB7qxVae6sVeTurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV5+6sVW3urFUDAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVRjurFWA7qxV4u6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVeTurFWE7qxVGwAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVEu6sVV7urFW17qxV7+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVfDurFW47qxVX+6s
VRMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA7qxVG+6sVUzurFWA7qxVrO6sVcnurFXa7qxV4u6sVeLurFXa7qxVye6sVa3urFWC7qxVTe6s
VR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAP//gAH//6gr//wAAD//qCv/8AAAD/+oK//AAAAD/6gr/4AAAAH/qCv/AAAAAP+oK/4A
AAAAf6gr/AAAAAA/qCv4AAAAAB+oK/AAAAAAD6gr4AAAAAAHqCvgAAAAAAeoK8AAAAAAA6grwAAAAAAD
qCuAAAAAAAGoK4AAAAAAAagrgAAAAAAAqCsAAAAAAACoKwAAAAAAAKgrAAAAAAAAqCsAAAAAAACoKwAA
AAAAAKgrAAAAAAAAqCsAAAAAAACoKwAAAAAAAKgrAAAAAAAAqCsAAAAAAACoKwAAAAAAAKgrAAAAAAAA
qCsAAAAAAACoKwAAAAAAAKgrgAAAAAAAqCuAAAAAAAGoK4AAAAAAAagrwAAAAAADqCvAAAAAAAOoK+AA
AAAAB6gr4AAAAAAHqCvwAAAAAA+oK/gAAAAAH6gr/AAAAAA/qCv+AAAAAH+oK/8AAAAA/6gr/4AAAAH/
qCv/wAAAA/+oK//wAAAP/6gr//wAAD//qCv//4AB//+oKygAAAAgAAAAQAAAAAEAIAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApHY4FrR/
Ole+hjuVxos7w8uNPNvOjzznzo8858uNPNzGijzEvoU7l7WBO1mpejsYAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqHg5Kr6F
O5XLjTvo0pE8/9ORPP/SkTz/0ZE8/9CQPP/QkDz/0ZE8/9KRPP/TkTz/0pE8/82OPOq/hjyZrXw7LAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo3U6CbuE
O4bPjzz005I8/9GRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0ZE8/9OS
PP/PkDz1vYU8iah6PAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKR3
OibGizvG05E8/9GQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9GQPP/TkTz/yIs8yqx8PCkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AACsezsxy40849KRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/zI485rKAPTUAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAApXc6JMuNPOTSkTz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/zI485659PCcAAAAAAAAAAAAA
AAAAAAAAAAAAAKd4OwnHizzJ0pE8/9CQPP/QkDz/0JA9/9OgYP/YtIX/38Sh/+LMr//iy67/3sOf/9iy
gv/Tn13/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/x4s8zaV4
OwsAAAAAAAAAAAAAAAAAAAAAu4Q8hNOSPP/QkDz/0JA8/9GXS//YtIX/6NfC//Xv5//8+vj/////////
///8+vf/9O3k/+fVvv/YsYH/0JRF/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/Tkjz/u4Q7iQAAAAAAAAAAAAAAAKx7OyTPjzz10ZA8/9GXS//Ztoj/8une////////////////////
///////////////////////////////////38uz/3L2V/9CRP//QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9GRPP/Ojzz3onU5KQAAAAAAAAAAxo1BlNGQO//Ojjv/0JNC/9GWSP/QlET/0ZpR/9au
eP/n1b3//v79////////////////////////////////////////////7+TV/9GdWP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9SSPP+4gjqaAAAAAPGuVhHvrVXq5qRO/9qZRP/QkT3/zo46/8+P
O//QkDz/0JA8/9CQPP/To2P/8+vh////////////////////////////////////////////+fXw/9Oh
Yf/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0pE8/8qMO+2WbTYV7qxVUe6sVf/wrVb/761W/+mo
Uv/enUj/05M//86PO//PkD7/3cCZ//Lp3v/7+PT/////////////////////////////////////////
////////+PTv/9GcVf/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/05I8/699OljurFWR7qxV/+6s
Vf/urFX/761W//CuV//sqlT/4qJQ/+zdyf//////////////////////////////////////////////
////////////////////////7+TV/9CRP//QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/Tkjz/vIQ7mO6s
Vb/urFX/7qxV/+6sVf/urFX/7qxV/++tVf/ry5///v79//7+/f//////////////////////////////
////////////////////////////////////////2rqO/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9KR
PP/GijvF7qxV2u6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+u1bv/t1bb//fv4////////////////////
///////////////////////////////////////////////////48+3/0ZRG/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0ZA8/8uNPNzurFXl7qxV/+6sVf/urFX/7qxV/+6sVf/rsWP/9OjZ////////////////////
///////////////////////////////////////////////////////////////////YtIX/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/zo886O6sVeXurFX/7qxV/+6sVf/urFX/7qxW//Hiz///////////////
/////////////////////////////////////////////////////////////////////////////+rb
yP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/Ojzzo7qxV2e6sVf/urFX/7qxV/+6sVf/quHb/////////
////////////////////////////////////////////////////////////////////////////////
////////+fXw/9CURf/QkDz/0JA8/9CQPP/QkDz/0ZA8/8yOPNzurFW/7qxV/+6sVf/urFX/7qxV/+nA
if/s1LT/9Onb////////////////////////////////////////////////////////////////////
////////////////////////0Z5b/9CQPP/QkDz/0JA8/9CQPP/SkTz/x4s8xe6sVY/urFX/7qxV/+6s
Vf/urFX/7qxV/+nEk//+/fz////////////////////////////+/v3/+PHo//To2f/+/v7/////////
///////////////////////////////////VqGz/zo46/8+PO//QkDz/0JA8/9KRPP+/hjyX7qxVUO6s
Vf/urFX/7qxV/+6sVf/ssGH//Pr2///////////////////////y49D/6cOR/+qyaf/urVb/6rJp////
/////////////////////////////////////////////+e7f//goEr/1ZVB/8+PO//Ojzv/0pE8/7WB
PFfurFUQ7qxV6O6sVf/urFX/7qxV/+m+hv/////////////////z5tX/6rl6/+6sVf/urFX/7qxV/+6s
Vf/qs2r////+////////////////////////////////////////////+PLp/+y4c//urFX/5aRO/9qZ
RP/Mjjztp3g6FQAAAADurFWQ7qxV/+6sVf/urFX/6cKP////////////682m/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+2sV//17N////////////////////////////////////////7+/v/06Nn/8uTT/+qz
a//wrVb/761W/+alUZYAAAAAAAAAAO6sVSHurFX07qxV/+6sVf/quXj//////+nDkf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+m+hP////////////////////////////////////////////jy
6v/qtnH/67Fj/+6sVf/urFX2761WJQAAAAAAAAAAAAAAAO6sVX7urFX/7qxV/+2vW//pvID/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+nEkv/7+PT//////////////////fz6/+rJ
n//rtW7/682n/+zTs//urFb/7qxV/+6sVYQAAAAAAAAAAAAAAAAAAAAA7qxVB+6sVcTurFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+u0bP/qy6H/7da4/+vN
pv/qtnP/7qxV/+6sVf/urFX/7K9f/+2sWP/urFXI7qxVCQAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVIO6s
VeDurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV4+6sVSMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA7qxVLO6sVd/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVeLurFUwAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA7qxVIu6sVcHurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFXE7qxVJAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVBu6sVX/urFXw7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFXy7qxVgu6sVQgAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVSXurFWP7qxV5e6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFXl7qxVke6sVScAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AADurFUS7qxVUO6sVY7urFW97qxV2O6sVeXurFXl7qxV2O6sVb7urFWP7qxVUu6sVRMAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AD//8AAP/8AAA/+AAAH/AAAA/gAAAHwAAAA8AA
AAOAAAABgAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAA
AAGAAAABwAAAA8AAAAPgAAAH8AAAD/gAAB/8AAA//wAA///AA/8oAAAAEAAAACAAAAABACAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKx7Oja9hTqTyYw8zs6PPOfOjzznyYw8zr6G
PJSvfjw3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo3Y7CbyEO5PRkDz805I8/9GRPP/QkDz/0JA8/9GR
PP/TkTz/0ZA8/b+GPJWrfDwKAAAAAAAAAAAAAAAAp3g8CcSJPLfTkjz/0ZA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/Tkjz/xoo8uqx8PQoAAAAAAAAAAL2FPJPUk0D/06Rm/92/mf/izK//4sut/9u6
j//Snlr/0JA8/9CQPP/QkDz/0JA8/9OSPP+9hTyWAAAAALiEQDLQkDz90JpS/93Amf/u4tP/////////
/////////fv5/+DIqP/QkkH/0JA8/9CQPP/RkDz/0JA8/qN1OTTtq1WS56VP/9qaRf/RkT3/0JdO/+rc
yP//////////////////////693L/9CSQf/QkDz/0JA8/9SSPP+3gTqW761Vz++tVv/wrVb/6K5h//fy
6v/////////////////////////////////gx6b/0JA8/9CQPP/RkTz/yYw80e6sVefurFX/7qxV/+q+
hP/69vD//////////////////////////////////fz7/9GbU//QkDz/0JA8/8+PPOnurFXn7qxV/+yw
Y//79/P////////////////////////////////////////////ZuIz/0JA8/9CQPP/Pjzzp7qxVz+6s
Vf/sr2H/8eLO/////////////v7+//n07f/+/v7/////////////////48yu/8+PO//Qjzv/yYs80e6s
VZLurFX/67Jm//7+/f/8+fb/68yl/+qza//rsmf//v37//////////////////Pn2P/ip1n/2phC/8CH
PJXurFUw7qxV/eq2c//169//6rNr/+6sVf/urFX/7q1W//Pm1f/////////////////8+fX/67+G/++t
Vf3lpVEyAAAAAO6sVZHtrVr/7a9c/+6sVf/urFX/7qxV/+6sVf/rsWP/7de5//Lj0P/pxJL/67Zy/+qz
av/urFWTAAAAAAAAAADurFUI7qxVtO6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFW37qxVCQAAAAAAAAAAAAAAAO6sVQjurFWQ7qxV++6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
VfvurFWS7qxVCQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVTPurFWQ7qxVzO6sVeburFXm7qxVzO6s
VZDurFU0AAAAAAAAAAAAAAAAAAAAAPAPrEHAA6xBgAGsQYABrEEAAKxBAACsQQAArEEAAKxBAACsQQAA
rEEAAKxBAACsQYABrEGAAaxBwAOsQfAPrEE=
</value>
</data>
</root>

View File

@@ -1,108 +0,0 @@
using System;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Windows.Forms;
using TweetDck.Core.Controls;
using TweetDck.Core.Utils;
namespace TweetDck.Core.Other{
sealed partial class FormUpdateDownload : Form{
public string InstallerPath{
get{
return Path.Combine(Path.GetTempPath(),updateInfo.FileName);
}
}
public enum Status{
Waiting, Failed, Cancelled, Manual, Succeeded
}
public Status UpdateStatus { get; private set; }
private readonly WebClient webClient;
private readonly UpdateInfo updateInfo;
public FormUpdateDownload(UpdateInfo info){
InitializeComponent();
this.webClient = new WebClient{ Proxy = null };
this.webClient.Headers[HttpRequestHeader.UserAgent] = BrowserUtils.HeaderUserAgent;
this.updateInfo = info;
this.UpdateStatus = Status.Waiting;
Disposed += (sender, args) => webClient.Dispose();
webClient.DownloadProgressChanged += webClient_DownloadProgressChanged;
webClient.DownloadFileCompleted += webClient_DownloadFileCompleted;
Text = "Updating "+Program.BrandName;
labelDescription.Text = "Downloading version "+info.VersionTag+"...";
}
private void FormUpdateDownload_Shown(object sender, EventArgs e){
webClient.DownloadFileAsync(new Uri(updateInfo.DownloadUrl),InstallerPath);
}
private void btnCancel_Click(object sender, EventArgs e){
webClient.CancelAsync();
btnCancel.Enabled = false;
}
private void FormUpdateDownload_FormClosing(object sender, FormClosingEventArgs e){
if (UpdateStatus == Status.Waiting){
e.Cancel = true;
webClient.CancelAsync();
UpdateStatus = e.CloseReason == CloseReason.UserClosing ? Status.Cancelled : Status.Manual; // manual will exit the app
}
}
private void webClient_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e){
this.InvokeSafe(() => {
if (e.TotalBytesToReceive == -1){
if (progressDownload.Style != ProgressBarStyle.Marquee){
progressDownload.Style = ProgressBarStyle.Continuous;
progressDownload.SetValueInstant(1000);
}
labelStatus.Text = (e.BytesReceived/(1024.0*1024.0)).ToString("0.0")+" MB";
}
else{
if (progressDownload.Style != ProgressBarStyle.Continuous){
progressDownload.Style = ProgressBarStyle.Continuous;
}
progressDownload.SetValueInstant(e.ProgressPercentage*10);
labelStatus.Text = (e.BytesReceived/(1024.0*1024.0)).ToString("0.0")+" / "+(e.TotalBytesToReceive/(1024.0*1024.0)).ToString("0.0")+" MB";
}
});
}
private void webClient_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e){
this.InvokeSafe(() => {
if (e.Cancelled){
if (UpdateStatus == Status.Waiting){
UpdateStatus = Status.Cancelled;
}
}
else if (e.Error != null){
Program.Log(e.Error.ToString());
if (MessageBox.Show("Could not download the update: "+e.Error.Message+"\r\n\r\nDo you want to open the website and try downloading the update manually?","Update Has Failed",MessageBoxButtons.YesNo,MessageBoxIcon.Error,MessageBoxDefaultButton.Button1) == DialogResult.Yes){
BrowserUtils.OpenExternalBrowser(Program.Website);
UpdateStatus = Status.Manual;
}
else{
UpdateStatus = Status.Failed;
}
}
else{
UpdateStatus = Status.Succeeded;
}
Close();
});
}
}
}

View File

@@ -1,377 +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="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
AAABAAMAMDAAAAEAIACoJQAANgAAACAgAAABACAAqBAAAN4lAAAQEAAAAQAgAGgEAACGNgAAKAAAADAA
AABgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqHg5IrF+OlS+hjyMw4g7tcmM
PM/MjTzezY485s2OPObMjjzfyIw80MOJPLe8hDyNs387WKx8OyQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApHU4FrB8OGXGijzAzo889dKR
PP/SkTz/0pE8/9GRPP/RkDz/0JA8/9CQPP/RkDz/0ZE8/9KRPP/TkTz/0pE8/86PPPbGijzCt4E8bKp6
OxoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKN1OB+9hTuLzI486NOR
PP/SkTz/0ZA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GQ
PP/SkTz/0pE8/82OPOu9hTuOq3w8IgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACidTkFuoM8csyN
POrTkjz/0ZE8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GRPP/Tkjz/zY887bqEPHimeDsHAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKp6
OynDiDvA05I8/9GRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0ZA8/9ORPP/GijzFr308LgAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAArXw7UM2OPOvTkTz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/SkTz/zo887rOAPFYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAACxfjxm0ZA8/NGRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0ZE8/9GQPP64gjxtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAALaBPGXRkTz/0ZE8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GQPP/SkTz/uIM8bAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqnk6TtCQPPzRkTz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkDz/0ZA8/rSA
PFUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACtfDwmzo887dKRPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0ZE8/86PPPGtfDwrAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKZ4PAXFijzC0pE8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CURf/TpGT/2baJ/9/GpP/l0bf/59W//+fVvf/kz7P/3sOg/9mz
hP/ToWH/0JND/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9KRPP/EiTvJo3Y7CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALiC
PG/Tkjz/0JA8/9CQPP/QkDz/0JA8/9CSQf/Vq3P/5tO7//j07v//////////////////////////////
////////////////////////9vHq/+TQtf/UqG7/0JA+/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/Tkjz/uYM8dgAAAAAAAAAAAAAAAAAA
AAAAAAAArXw8Gc2OPOzRkTz/0JA8/9CQPP/QlEX/27qP//fz7P//////////////////////////////
////////////////////////////////////////////////////////8ObZ/9auev/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkTz/zI488J1x
OB8AAAAAAAAAAAAAAAAAAAAAuoM7h9OSPP/QkDz/0JA8/9GXTf/TpGb/27uS/9u6kP/ewZz/59W9//Xv
5///////////////////////////////////////////////////////////////////////////////
///r3cr/0ZxV/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/1JI8/7mDO5AAAAAAAAAAAAAAAADFj0cR0pNB6s+PO//Ojjv/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CRQP/TpGf/5NC2//38+v//////////////////////////////////////////////
////////////////////////+vf0/9auev/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0ZE8/8uOPO6SajYVAAAAAAAAAADzr1df7qxV/+WkT//ZmUT/0JA9/86O
O//Pjzv/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GaUv/l07r/////////////////////////
///////////////////////////////////////////////////buo//0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9OSPP+ldjdoAAAAAAAAAADurFW67qxV//Ct
Vv/vrVb/6adR/92dSP/Skz//zo47/8+PO//QkDz/0JA8/9CQPP/QkDz/0JJB/9Smaf/dv5j/+fXw////
////////////////////////////////////////////////////////////////////////2riM/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9KRPP/FiTzGoHQ6Ae6s
VRrurFXz7qxV/+6sVf/urFX/761W//CuVv/sqlT/4qFM/9aVQv/Pjzv/zo47/9CQPP/VqnH/8+vh////
////////////////////////////////////////////////////////////////////////////////
/////////////9aueP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9GQ
PP/Ojzz4oHM5Iu6sVU3urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/vrVX/8K5W/+6sVf/mpU//2plF/9q6
j///////////////////////////////////////////////////////////////////////////////
//////////////////////////////n18P/Rm1P/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/SkTz/rXs6Vu6sVYPurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/wrVb/6r2B////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////o18L/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/v4Y8j+6sVa3urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFb/9Onb///////+/v3/////////////////////////////////////////
////////////////////////////////////////////////////////////////////////1qx2/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/wYc7uO6sVcrurFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/trlr/67Zy/+m+h//y5NP/////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////7+TW/9CQPf/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkTz/yYw80e6s
VdvurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/6sme//v49P//////////////
////////////////////////////////////////////////////////////////////////////////
/////////////////////////////9arc//QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/RkDz/zI083+6sVeLurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/u2b3/////////
////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////+fVvv/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/zo885u6sVeLurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+vL
of//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////r28v/Rl0r/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/zo885u6sVdrurFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7LBj//r28f//////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
///Vq3P/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkDz/zI083+6sVcrurFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/6cKO////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
///////////////////ewp7/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/RkTz/yYw80O6s
VazurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7NOy//z59v/u2b7/+fPr////////////////////
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////n1sD/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/SkTz/xIo8uO6sVYHurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/67Zx/+2uWf/qy6H/////////
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////v5dj/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/SkTz/vYU8je6sVUvurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+nC
kP///////////////////////////////////////////////////////fz6//fu5P/w4Mv/793F//7+
/v/////////////////////////////////////////////////////////////////17eT/0JA//86O
Ov/Pjzv/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/tYA8Vu6sVRnurFXy7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7a5b//fw5///////////////////////////////////////+/jz/+7av//pwIr/67Jn/+6t
Vv/urFX/6rd1////////////////////////////////////////////////////////////////////
///48ej/6KhU/92cR//Tkj7/zo47/8+PO//QkDz/0JA8/9GQPP/Pjzz3rXw8IAAAAADurFW37qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/6b2D//////////////////////////////////nz6//qyZ3/7K9h/+6s
Vf/urFX/7qxV/+6sVf/urFX/6rl6////////////////////////////////////////////////////
///////////////////9/Pr/6r6C//CuV//sqlT/4aBL/9aWQv/Pjzv/zo47/9GRPP/GijzCp3g8AQAA
AADurFVb7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/68yk///////////////////////+/fz/68+q/+2v
XP/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/6rNr//7+/f//////////////////////////////
/////////////////////////////////////////////+nEk//vrVX/8K5W/+6sVf/lpE//2ZlF/9KR
Pf+zfzpoAAAAAAAAAADurFUP7qxV5u6sVf/urFX/7qxV/+6sVf/urFX/7NGu//////////////////fu
5P/quHb/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxW//Xr3///////////////
///////////////////////////////////////////////////7+PP/+vXu///////pxJL/7qxV/+6s
Vf/wrVb/8K1W/+mnUevUmUsUAAAAAAAAAAAAAAAA7qxVgO6sVf/urFX/7qxV/+6sVf/urFX/6sqg////
////////8eLO/+yvX//urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+nF
lf/////////////////////////////////////////////////////////////////8+vf/6cWV/+q5
ev/s07H/6rd1/+6sVf/urFX/7qxV/++tVogAAAAAAAAAAAAAAAAAAAAA7qxVFe6sVejurFX/7qxV/+6s
Vf/urFX/6bp8///////w4Mn/7a1Z/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/x4cz////////////////////////////////////////////////////+//7+
/v/////////+/+m8gP/urFX/7a1Y/+6sVf/urFX/7qxV7O6sVRoAAAAAAAAAAAAAAAAAAAAAAAAAAO6s
VWburFX/7qxV/+6sVf/urFX/7a5b/+zTs//sr1//7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/sr1//8uPQ////////////////////////////////////
////////6suj/+qzav/pxpj/9Onb//n07f/rr2D/7qxV/+6sVf/urFX/7qxVbwAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAO6sVQPurFW67qxV/+6sVf/urFX/7qxV/+6sVv/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7axY/+nGl//27uP/////////
/////////fz6//Lj0P/qunv/7qxV/+6sVf/urFX/7qxV/+m9gv/qt3X/7qxV/+6sVf/urFXB7qxVBQAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADurFUg7qxV6O6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/qtnP/6sqg/+3WuP/s07P/6cSS/+uyZf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVv/trVr/7qxV/+6s
VezurFUlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVRu6sVfjurFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV++6sVUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6s
VVzurFX97qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxVYwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAADurFVd7qxV+O6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVfvurFVjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVR+6sVeXurFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV6O6sVU0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVSPurFW37qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFW77qxVJgAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AADurFUB7qxVae6sVeTurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV5+6sVW3urFUDAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVRjurFWA7qxV4u6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVeTurFWE7qxVGwAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVEu6sVV7urFW17qxV7+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVfDurFW47qxVX+6s
VRMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA7qxVG+6sVUzurFWA7qxVrO6sVcnurFXa7qxV4u6sVeLurFXa7qxVye6sVa3urFWC7qxVTe6s
VR0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAP//gAH//6gr//wAAD//qCv/8AAAD/+oK//AAAAD/6gr/4AAAAH/qCv/AAAAAP+oK/4A
AAAAf6gr/AAAAAA/qCv4AAAAAB+oK/AAAAAAD6gr4AAAAAAHqCvgAAAAAAeoK8AAAAAAA6grwAAAAAAD
qCuAAAAAAAGoK4AAAAAAAagrgAAAAAAAqCsAAAAAAACoKwAAAAAAAKgrAAAAAAAAqCsAAAAAAACoKwAA
AAAAAKgrAAAAAAAAqCsAAAAAAACoKwAAAAAAAKgrAAAAAAAAqCsAAAAAAACoKwAAAAAAAKgrAAAAAAAA
qCsAAAAAAACoKwAAAAAAAKgrgAAAAAAAqCuAAAAAAAGoK4AAAAAAAagrwAAAAAADqCvAAAAAAAOoK+AA
AAAAB6gr4AAAAAAHqCvwAAAAAA+oK/gAAAAAH6gr/AAAAAA/qCv+AAAAAH+oK/8AAAAA/6gr/4AAAAH/
qCv/wAAAA/+oK//wAAAP/6gr//wAAD//qCv//4AB//+oKygAAAAgAAAAQAAAAAEAIAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApHY4FrR/
Ole+hjuVxos7w8uNPNvOjzznzo8858uNPNzGijzEvoU7l7WBO1mpejsYAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqHg5Kr6F
O5XLjTvo0pE8/9ORPP/SkTz/0ZE8/9CQPP/QkDz/0ZE8/9KRPP/TkTz/0pE8/82OPOq/hjyZrXw7LAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo3U6CbuE
O4bPjzz005I8/9GRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0ZE8/9OS
PP/PkDz1vYU8iah6PAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKR3
OibGizvG05E8/9GQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9GQPP/TkTz/yIs8yqx8PCkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AACsezsxy40849KRPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/zI485rKAPTUAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAApXc6JMuNPOTSkTz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/zI485659PCcAAAAAAAAAAAAA
AAAAAAAAAAAAAKd4OwnHizzJ0pE8/9CQPP/QkDz/0JA9/9OgYP/YtIX/38Sh/+LMr//iy67/3sOf/9iy
gv/Tn13/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/SkTz/x4s8zaV4
OwsAAAAAAAAAAAAAAAAAAAAAu4Q8hNOSPP/QkDz/0JA8/9GXS//YtIX/6NfC//Xv5//8+vj/////////
///8+vf/9O3k/+fVvv/YsYH/0JRF/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/Tkjz/u4Q7iQAAAAAAAAAAAAAAAKx7OyTPjzz10ZA8/9GXS//Ztoj/8une////////////////////
///////////////////////////////////38uz/3L2V/9CRP//QkDz/0JA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9GRPP/Ojzz3onU5KQAAAAAAAAAAxo1BlNGQO//Ojjv/0JNC/9GWSP/QlET/0ZpR/9au
eP/n1b3//v79////////////////////////////////////////////7+TV/9GdWP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/0JA8/9SSPP+4gjqaAAAAAPGuVhHvrVXq5qRO/9qZRP/QkT3/zo46/8+P
O//QkDz/0JA8/9CQPP/To2P/8+vh////////////////////////////////////////////+fXw/9Oh
Yf/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/0pE8/8qMO+2WbTYV7qxVUe6sVf/wrVb/761W/+mo
Uv/enUj/05M//86PO//PkD7/3cCZ//Lp3v/7+PT/////////////////////////////////////////
////////+PTv/9GcVf/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/QkDz/05I8/699OljurFWR7qxV/+6s
Vf/urFX/761W//CuV//sqlT/4qJQ/+zdyf//////////////////////////////////////////////
////////////////////////7+TV/9CRP//QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/Tkjz/vIQ7mO6s
Vb/urFX/7qxV/+6sVf/urFX/7qxV/++tVf/ry5///v79//7+/f//////////////////////////////
////////////////////////////////////////2rqO/9CQPP/QkDz/0JA8/9CQPP/QkDz/0JA8/9KR
PP/GijvF7qxV2u6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+u1bv/t1bb//fv4////////////////////
///////////////////////////////////////////////////48+3/0ZRG/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0ZA8/8uNPNzurFXl7qxV/+6sVf/urFX/7qxV/+6sVf/rsWP/9OjZ////////////////////
///////////////////////////////////////////////////////////////////YtIX/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/QkDz/zo886O6sVeXurFX/7qxV/+6sVf/urFX/7qxW//Hiz///////////////
/////////////////////////////////////////////////////////////////////////////+rb
yP/QkDz/0JA8/9CQPP/QkDz/0JA8/9CQPP/Ojzzo7qxV2e6sVf/urFX/7qxV/+6sVf/quHb/////////
////////////////////////////////////////////////////////////////////////////////
////////+fXw/9CURf/QkDz/0JA8/9CQPP/QkDz/0ZA8/8yOPNzurFW/7qxV/+6sVf/urFX/7qxV/+nA
if/s1LT/9Onb////////////////////////////////////////////////////////////////////
////////////////////////0Z5b/9CQPP/QkDz/0JA8/9CQPP/SkTz/x4s8xe6sVY/urFX/7qxV/+6s
Vf/urFX/7qxV/+nEk//+/fz////////////////////////////+/v3/+PHo//To2f/+/v7/////////
///////////////////////////////////VqGz/zo46/8+PO//QkDz/0JA8/9KRPP+/hjyX7qxVUO6s
Vf/urFX/7qxV/+6sVf/ssGH//Pr2///////////////////////y49D/6cOR/+qyaf/urVb/6rJp////
/////////////////////////////////////////////+e7f//goEr/1ZVB/8+PO//Ojzv/0pE8/7WB
PFfurFUQ7qxV6O6sVf/urFX/7qxV/+m+hv/////////////////z5tX/6rl6/+6sVf/urFX/7qxV/+6s
Vf/qs2r////+////////////////////////////////////////////+PLp/+y4c//urFX/5aRO/9qZ
RP/Mjjztp3g6FQAAAADurFWQ7qxV/+6sVf/urFX/6cKP////////////682m/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+2sV//17N////////////////////////////////////////7+/v/06Nn/8uTT/+qz
a//wrVb/761W/+alUZYAAAAAAAAAAO6sVSHurFX07qxV/+6sVf/quXj//////+nDkf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+m+hP////////////////////////////////////////////jy
6v/qtnH/67Fj/+6sVf/urFX2761WJQAAAAAAAAAAAAAAAO6sVX7urFX/7qxV/+2vW//pvID/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+nEkv/7+PT//////////////////fz6/+rJ
n//rtW7/682n/+zTs//urFb/7qxV/+6sVYQAAAAAAAAAAAAAAAAAAAAA7qxVB+6sVcTurFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+u0bP/qy6H/7da4/+vN
pv/qtnP/7qxV/+6sVf/urFX/7K9f/+2sWP/urFXI7qxVCQAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVIO6s
VeDurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV4+6sVSMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA7qxVLO6sVd/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVeLurFUwAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA7qxVIu6sVcHurFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFXE7qxVJAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7qxVBu6sVX/urFXw7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFXy7qxVgu6sVQgAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVSXurFWP7qxV5e6s
Vf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFXl7qxVke6sVScAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AADurFUS7qxVUO6sVY7urFW97qxV2O6sVeXurFXl7qxV2O6sVb7urFWP7qxVUu6sVRMAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AD//8AAP/8AAA/+AAAH/AAAA/gAAAHwAAAA8AA
AAOAAAABgAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAA
AAGAAAABwAAAA8AAAAPgAAAH8AAAD/gAAB/8AAA//wAA///AA/8oAAAAEAAAACAAAAABACAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKx7Oja9hTqTyYw8zs6PPOfOjzznyYw8zr6G
PJSvfjw3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo3Y7CbyEO5PRkDz805I8/9GRPP/QkDz/0JA8/9GR
PP/TkTz/0ZA8/b+GPJWrfDwKAAAAAAAAAAAAAAAAp3g8CcSJPLfTkjz/0ZA8/9CQPP/QkDz/0JA8/9CQ
PP/QkDz/0JA8/9CQPP/Tkjz/xoo8uqx8PQoAAAAAAAAAAL2FPJPUk0D/06Rm/92/mf/izK//4sut/9u6
j//Snlr/0JA8/9CQPP/QkDz/0JA8/9OSPP+9hTyWAAAAALiEQDLQkDz90JpS/93Amf/u4tP/////////
/////////fv5/+DIqP/QkkH/0JA8/9CQPP/RkDz/0JA8/qN1OTTtq1WS56VP/9qaRf/RkT3/0JdO/+rc
yP//////////////////////693L/9CSQf/QkDz/0JA8/9SSPP+3gTqW761Vz++tVv/wrVb/6K5h//fy
6v/////////////////////////////////gx6b/0JA8/9CQPP/RkTz/yYw80e6sVefurFX/7qxV/+q+
hP/69vD//////////////////////////////////fz7/9GbU//QkDz/0JA8/8+PPOnurFXn7qxV/+yw
Y//79/P////////////////////////////////////////////ZuIz/0JA8/9CQPP/Pjzzp7qxVz+6s
Vf/sr2H/8eLO/////////////v7+//n07f/+/v7/////////////////48yu/8+PO//Qjzv/yYs80e6s
VZLurFX/67Jm//7+/f/8+fb/68yl/+qza//rsmf//v37//////////////////Pn2P/ip1n/2phC/8CH
PJXurFUw7qxV/eq2c//169//6rNr/+6sVf/urFX/7q1W//Pm1f/////////////////8+fX/67+G/++t
Vf3lpVEyAAAAAO6sVZHtrVr/7a9c/+6sVf/urFX/7qxV/+6sVf/rsWP/7de5//Lj0P/pxJL/67Zy/+qz
av/urFWTAAAAAAAAAADurFUI7qxVtO6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
Vf/urFW37qxVCQAAAAAAAAAAAAAAAO6sVQjurFWQ7qxV++6sVf/urFX/7qxV/+6sVf/urFX/7qxV/+6s
VfvurFWS7qxVCQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO6sVTPurFWQ7qxVzO6sVeburFXm7qxVzO6s
VZDurFU0AAAAAAAAAAAAAAAAAAAAAPAPrEHAA6xBgAGsQYABrEEAAKxBAACsQQAArEEAAKxBAACsQQAA
rEEAAKxBAACsQYABrEGAAaxBwAOsQfAPrEE=
</value>
</data>
</root>

View File

@@ -0,0 +1,36 @@
using System.Collections.Generic;
using System.Windows.Forms;
using TweetDuck.Configuration;
namespace TweetDuck.Core.Other.Settings{
class BaseTabSettings : UserControl{
protected static UserConfig Config => Program.Config.User;
protected static SystemConfig SysConfig => Program.Config.System;
public IEnumerable<Control> InteractiveControls{
get{
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);
}
}
protected BaseTabSettings(){
Padding = new Padding(6);
}
public virtual void OnReady(){}
public virtual void OnClosing(){}
}
}

View 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;
}
}

View File

@@ -0,0 +1,24 @@
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 string CefArgs => textBoxReport.Text;
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();
}
}
}

View File

@@ -0,0 +1,189 @@
namespace TweetDuck.Core.Other.Settings.Dialogs {
partial class DialogSettingsCSS {
/// <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.textBoxBrowserCSS = new System.Windows.Forms.TextBox();
this.btnCancel = new System.Windows.Forms.Button();
this.btnApply = new System.Windows.Forms.Button();
this.textBoxNotificationCSS = new System.Windows.Forms.TextBox();
this.btnOpenDevTools = new System.Windows.Forms.Button();
this.timerTestBrowser = new System.Windows.Forms.Timer(this.components);
this.tabPanel = new System.Windows.Forms.TabControl();
this.tabPageBrowser = new System.Windows.Forms.TabPage();
this.tabPageNotification = new System.Windows.Forms.TabPage();
this.tabPanel.SuspendLayout();
this.tabPageBrowser.SuspendLayout();
this.tabPageNotification.SuspendLayout();
this.SuspendLayout();
//
// textBoxBrowserCSS
//
this.textBoxBrowserCSS.Dock = System.Windows.Forms.DockStyle.Fill;
this.textBoxBrowserCSS.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular);
this.textBoxBrowserCSS.Location = new System.Drawing.Point(3, 3);
this.textBoxBrowserCSS.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0);
this.textBoxBrowserCSS.Multiline = true;
this.textBoxBrowserCSS.Name = "textBoxBrowserCSS";
this.textBoxBrowserCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.textBoxBrowserCSS.Size = new System.Drawing.Size(426, 332);
this.textBoxBrowserCSS.TabIndex = 0;
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
//
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, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.btnCancel.Location = new System.Drawing.Point(337, 384);
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);
//
// 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, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.btnApply.Location = new System.Drawing.Point(400, 384);
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 = 1;
this.btnApply.Text = "Apply";
this.btnApply.UseVisualStyleBackColor = true;
this.btnApply.Click += new System.EventHandler(this.btnApply_Click);
//
// textBoxNotificationCSS
//
this.textBoxNotificationCSS.Dock = System.Windows.Forms.DockStyle.Fill;
this.textBoxNotificationCSS.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular);
this.textBoxNotificationCSS.Location = new System.Drawing.Point(3, 3);
this.textBoxNotificationCSS.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0);
this.textBoxNotificationCSS.Multiline = true;
this.textBoxNotificationCSS.Name = "textBoxNotificationCSS";
this.textBoxNotificationCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.textBoxNotificationCSS.Size = new System.Drawing.Size(426, 332);
this.textBoxNotificationCSS.TabIndex = 0;
this.textBoxNotificationCSS.WordWrap = false;
this.textBoxNotificationCSS.KeyDown += new System.Windows.Forms.KeyEventHandler(this.textBoxCSS_KeyDown);
//
// btnOpenDevTools
//
this.btnOpenDevTools.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.btnOpenDevTools.AutoSize = true;
this.btnOpenDevTools.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.btnOpenDevTools.Location = new System.Drawing.Point(12, 384);
this.btnOpenDevTools.Name = "btnOpenDevTools";
this.btnOpenDevTools.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
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
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(464, 421);
this.Controls.Add(this.tabPanel);
this.Controls.Add(this.btnOpenDevTools);
this.Controls.Add(this.btnApply);
this.Controls.Add(this.btnCancel);
this.MinimumSize = new System.Drawing.Size(320, 240);
this.Name = "DialogSettingsCSS";
this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.tabPanel.ResumeLayout(false);
this.tabPageBrowser.ResumeLayout(false);
this.tabPageBrowser.PerformLayout();
this.tabPageNotification.ResumeLayout(false);
this.tabPageNotification.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.TextBox textBoxBrowserCSS;
private System.Windows.Forms.Button btnCancel;
private System.Windows.Forms.Button btnApply;
private System.Windows.Forms.TextBox textBoxNotificationCSS;
private System.Windows.Forms.Button btnOpenDevTools;
private System.Windows.Forms.Timer timerTestBrowser;
private System.Windows.Forms.TabControl tabPanel;
private System.Windows.Forms.TabPage tabPageBrowser;
private System.Windows.Forms.TabPage tabPageNotification;
}
}

View File

@@ -0,0 +1,142 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsCSS : Form{
public string BrowserCSS => textBoxBrowserCSS.Text;
public string NotificationCSS => textBoxNotificationCSS.Text;
private readonly Action<string> reinjectBrowserCSS;
private readonly Action openDevTools;
public DialogSettingsCSS(string browserCSS, string notificationCSS, Action<string> reinjectBrowserCSS, Action openDevTools){
InitializeComponent();
Text = Program.BrandName+" Options - CSS";
this.reinjectBrowserCSS = reinjectBrowserCSS;
this.openDevTools = openDevTools;
textBoxBrowserCSS.EnableMultilineShortcuts();
textBoxBrowserCSS.Text = browserCSS ?? "";
textBoxNotificationCSS.EnableMultilineShortcuts();
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){
DialogResult = DialogResult.OK;
Close();
}
private void btnCancel_Click(object sender, EventArgs e){
DialogResult = DialogResult.Cancel;
Close();
}
}
}

View File

@@ -0,0 +1,126 @@
namespace TweetDuck.Core.Other.Settings.Dialogs {
partial class DialogSettingsCefArgs {
/// <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.textBoxArgs = new System.Windows.Forms.TextBox();
this.btnCancel = new System.Windows.Forms.Button();
this.btnApply = new System.Windows.Forms.Button();
this.btnHelp = new System.Windows.Forms.Button();
this.labelWarning = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// textBoxArgs
//
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.Right)));
this.textBoxArgs.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular);
this.textBoxArgs.Location = new System.Drawing.Point(12, 30);
this.textBoxArgs.Multiline = true;
this.textBoxArgs.Name = "textBoxArgs";
this.textBoxArgs.Size = new System.Drawing.Size(480, 189);
this.textBoxArgs.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(377, 225);
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(440, 225);
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);
//
// btnHelp
//
this.btnHelp.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.btnHelp.AutoSize = true;
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.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.btnHelp.Size = new System.Drawing.Size(141, 25);
this.btnHelp.TabIndex = 4;
this.btnHelp.Text = "List of Chromium Args";
this.btnHelp.UseVisualStyleBackColor = true;
this.btnHelp.Click += new System.EventHandler(this.btnHelp_Click);
//
// labelWarning
//
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.Margin = new System.Windows.Forms.Padding(3, 0, 3, 3);
this.labelWarning.Name = "labelWarning";
this.labelWarning.Size = new System.Drawing.Size(478, 15);
this.labelWarning.TabIndex = 0;
this.labelWarning.Text = "Warning: Some arguments may cause the program to stop working, edit at your own r" +
"isk.";
//
// DialogSettingsCefArgs
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(504, 262);
this.Controls.Add(this.labelWarning);
this.Controls.Add(this.btnHelp);
this.Controls.Add(this.btnApply);
this.Controls.Add(this.btnCancel);
this.Controls.Add(this.textBoxArgs);
this.MinimumSize = new System.Drawing.Size(520, 160);
this.Name = "DialogSettingsCefArgs";
this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.TextBox textBoxArgs;
private System.Windows.Forms.Button btnCancel;
private System.Windows.Forms.Button btnApply;
private System.Windows.Forms.Button btnHelp;
private System.Windows.Forms.Label labelWarning;
}
}

View File

@@ -0,0 +1,48 @@
using System;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
namespace TweetDuck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsCefArgs : Form{
public string CefArgs => textBoxArgs.Text;
private readonly string initialArgs;
public DialogSettingsCefArgs(string args){
InitializeComponent();
Text = Program.BrandName+" Options - CEF Arguments";
textBoxArgs.EnableMultilineShortcuts();
textBoxArgs.Text = initialArgs = args ?? "";
textBoxArgs.Select(textBoxArgs.Text.Length, 0);
}
private void btnHelp_Click(object sender, EventArgs e){
BrowserUtils.OpenExternalBrowser("http://peter.sh/experiments/chromium-command-line-switches/");
}
private void btnApply_Click(object sender, EventArgs e){
if (CefArgs == initialArgs){
DialogResult = DialogResult.Cancel;
Close();
return;
}
int count = CommandLineArgs.ReadCefArguments(CefArgs).Count;
string prompt = count == 0 && !string.IsNullOrWhiteSpace(initialArgs) ? "All current arguments will be removed. Continue?" : count+(count == 1 ? " argument was" : " arguments were")+" detected. Continue?";
if (FormMessage.Question("Confirm CEF Arguments", prompt, FormMessage.OK, FormMessage.Cancel)){
DialogResult = DialogResult.OK;
Close();
}
}
private void btnCancel_Click(object sender, EventArgs e){
DialogResult = DialogResult.Cancel;
Close();
}
}
}

View 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;
}
}

View File

@@ -0,0 +1,237 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
using TweetDuck.Configuration;
using TweetDuck.Core.Management;
using TweetDuck.Core.Utils;
using TweetDuck.Plugins;
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 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;
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){
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);
}
}
}

View 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;
}
}

View File

@@ -0,0 +1,61 @@
using System;
using System.Windows.Forms;
using TweetDuck.Configuration;
using TweetDuck.Data;
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();
}
}
}

View 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;
}
}

View 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();
}
}
}

View File

@@ -0,0 +1,288 @@
namespace TweetDuck.Core.Other.Settings {
partial class TabSettingsAdvanced {
/// <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.components = new System.ComponentModel.Container();
this.btnClearCache = new System.Windows.Forms.Button();
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.btnEditCefArgs = new System.Windows.Forms.Button();
this.btnEditCSS = new System.Windows.Forms.Button();
this.btnRestartArgs = new System.Windows.Forms.Button();
this.btnRestart = new System.Windows.Forms.Button();
this.btnOpenAppFolder = new System.Windows.Forms.Button();
this.btnOpenDataFolder = new System.Windows.Forms.Button();
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();
//
// btnClearCache
//
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.Size = new System.Drawing.Size(143, 25);
this.btnClearCache.TabIndex = 3;
this.btnClearCache.Text = "Clear Cache (...)";
this.btnClearCache.UseVisualStyleBackColor = true;
//
// btnEditCefArgs
//
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.Size = new System.Drawing.Size(143, 25);
this.btnEditCefArgs.TabIndex = 0;
this.btnEditCefArgs.Text = "Edit CEF Arguments";
this.btnEditCefArgs.UseVisualStyleBackColor = true;
//
// btnEditCSS
//
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.Size = new System.Drawing.Size(143, 25);
this.btnEditCSS.TabIndex = 1;
this.btnEditCSS.Text = "Edit CSS";
this.btnEditCSS.UseVisualStyleBackColor = true;
//
// btnRestartArgs
//
this.btnRestartArgs.Font = new System.Drawing.Font("Segoe UI", 9F);
this.btnRestartArgs.Location = new System.Drawing.Point(154, 32);
this.btnRestartArgs.Name = "btnRestartArgs";
this.btnRestartArgs.Size = new System.Drawing.Size(143, 25);
this.btnRestartArgs.TabIndex = 3;
this.btnRestartArgs.Text = "Restart with Arguments";
this.btnRestartArgs.UseVisualStyleBackColor = true;
//
// btnRestart
//
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.Size = new System.Drawing.Size(143, 25);
this.btnRestart.TabIndex = 2;
this.btnRestart.Text = "Restart the Program";
this.btnRestart.UseVisualStyleBackColor = true;
//
// 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
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.flowPanel);
this.Name = "TabSettingsAdvanced";
this.Size = new System.Drawing.Size(631, 480);
((System.ComponentModel.ISupportInitialize)(this.numClearCacheThreshold)).EndInit();
this.panelAppButtons.ResumeLayout(false);
this.panelClearCacheAuto.ResumeLayout(false);
this.panelClearCacheAuto.PerformLayout();
this.panelConfiguration.ResumeLayout(false);
this.flowPanel.ResumeLayout(false);
this.flowPanel.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Button btnClearCache;
private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.Button btnEditCefArgs;
private System.Windows.Forms.Button btnEditCSS;
private System.Windows.Forms.Button btnRestartArgs;
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;
}
}

View File

@@ -0,0 +1,157 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows.Forms;
using TweetDuck.Configuration;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Management;
using TweetDuck.Core.Other.Settings.Dialogs;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Other.Settings{
sealed partial class TabSettingsAdvanced : BaseTabSettings{
private readonly Action<string> reinjectBrowserCSS;
private readonly Action openDevTools;
public TabSettingsAdvanced(Action<string> reinjectBrowserCSS, Action openDevTools){
InitializeComponent();
this.reinjectBrowserCSS = reinjectBrowserCSS;
this.openDevTools = openDevTools;
// 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.");
// browser cache
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.");
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.");
}
public override void OnReady(){
btnOpenAppFolder.Click += btnOpenAppFolder_Click;
btnOpenDataFolder.Click += btnOpenDataFolder_Click;
btnRestart.Click += btnRestart_Click;
btnRestartArgs.Click += btnRestartArgs_Click;
btnClearCache.Click += btnClearCache_Click;
checkClearCacheAuto.CheckedChanged += checkClearCacheAuto_CheckedChanged;
btnEditCefArgs.Click += btnEditCefArgs_Click;
btnEditCSS.Click += btnEditCSS_Click;
}
public override void OnClosing(){
SysConfig.ClearCacheAutomatically = checkClearCacheAuto.Checked;
SysConfig.ClearCacheThreshold = (int)numClearCacheThreshold.Value;
}
#region Application
private void btnOpenAppFolder_Click(object sender, EventArgs e){
using(Process.Start("explorer.exe", "\""+Program.ProgramPath+"\"")){}
}
private void btnOpenDataFolder_Click(object sender, EventArgs e){
using(Process.Start("explorer.exe", "\""+Program.StoragePath+"\"")){}
}
private void btnRestart_Click(object sender, EventArgs e){
Program.Restart();
}
private void btnRestartArgs_Click(object sender, EventArgs e){
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
}
}

View File

@@ -0,0 +1,182 @@
namespace TweetDuck.Core.Other.Settings {
partial class TabSettingsFeedback {
/// <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.components = new System.ComponentModel.Container();
this.panelDataCollection = new System.Windows.Forms.Panel();
this.labelDataCollectionLink = new System.Windows.Forms.LinkLabel();
this.checkDataCollection = new System.Windows.Forms.CheckBox();
this.labelDataCollectionMessage = new System.Windows.Forms.Label();
this.btnViewReport = new System.Windows.Forms.Button();
this.btnSendFeedback = new System.Windows.Forms.Button();
this.labelDataCollection = new System.Windows.Forms.Label();
this.labelFeedback = new System.Windows.Forms.Label();
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
this.panelDataCollection.SuspendLayout();
this.flowPanel.SuspendLayout();
this.SuspendLayout();
//
// panelDataCollection
//
this.panelDataCollection.Controls.Add(this.labelDataCollectionLink);
this.panelDataCollection.Controls.Add(this.checkDataCollection);
this.panelDataCollection.Location = new System.Drawing.Point(0, 78);
this.panelDataCollection.Margin = new System.Windows.Forms.Padding(0);
this.panelDataCollection.Name = "panelDataCollection";
this.panelDataCollection.Size = new System.Drawing.Size(300, 28);
this.panelDataCollection.TabIndex = 3;
//
// labelDataCollectionLink
//
this.labelDataCollectionLink.AutoSize = true;
this.labelDataCollectionLink.Font = new System.Drawing.Font("Segoe UI", 9F);
this.labelDataCollectionLink.LinkArea = new System.Windows.Forms.LinkArea(1, 10);
this.labelDataCollectionLink.LinkBehavior = System.Windows.Forms.LinkBehavior.HoverUnderline;
this.labelDataCollectionLink.Location = new System.Drawing.Point(153, 4);
this.labelDataCollectionLink.Margin = new System.Windows.Forms.Padding(0);
this.labelDataCollectionLink.Name = "labelDataCollectionLink";
this.labelDataCollectionLink.Size = new System.Drawing.Size(71, 21);
this.labelDataCollectionLink.TabIndex = 1;
this.labelDataCollectionLink.TabStop = true;
this.labelDataCollectionLink.Text = "(learn more)";
this.labelDataCollectionLink.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
this.labelDataCollectionLink.UseCompatibleTextRendering = true;
//
// checkDataCollection
//
this.checkDataCollection.AutoSize = true;
this.checkDataCollection.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkDataCollection.Location = new System.Drawing.Point(6, 6);
this.checkDataCollection.Margin = new System.Windows.Forms.Padding(6, 6, 0, 2);
this.checkDataCollection.Name = "checkDataCollection";
this.checkDataCollection.Size = new System.Drawing.Size(147, 19);
this.checkDataCollection.TabIndex = 0;
this.checkDataCollection.Text = "Send Anonymous Data";
this.checkDataCollection.UseVisualStyleBackColor = true;
//
// labelDataCollectionMessage
//
this.labelDataCollectionMessage.Font = new System.Drawing.Font("Segoe UI", 9F);
this.labelDataCollectionMessage.Location = new System.Drawing.Point(6, 143);
this.labelDataCollectionMessage.Margin = new System.Windows.Forms.Padding(6);
this.labelDataCollectionMessage.Name = "labelDataCollectionMessage";
this.labelDataCollectionMessage.Size = new System.Drawing.Size(288, 67);
this.labelDataCollectionMessage.TabIndex = 5;
//
// btnViewReport
//
this.btnViewReport.AutoSize = true;
this.btnViewReport.Font = new System.Drawing.Font("Segoe UI", 9F);
this.btnViewReport.Location = new System.Drawing.Point(5, 109);
this.btnViewReport.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.btnViewReport.Name = "btnViewReport";
this.btnViewReport.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.btnViewReport.Size = new System.Drawing.Size(155, 25);
this.btnViewReport.TabIndex = 4;
this.btnViewReport.Text = "View My Analytics Report";
this.btnViewReport.UseVisualStyleBackColor = true;
//
// btnSendFeedback
//
this.btnSendFeedback.AutoSize = true;
this.btnSendFeedback.Font = new System.Drawing.Font("Segoe UI", 9F);
this.btnSendFeedback.Location = new System.Drawing.Point(5, 23);
this.btnSendFeedback.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.btnSendFeedback.Name = "btnSendFeedback";
this.btnSendFeedback.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.btnSendFeedback.Size = new System.Drawing.Size(170, 25);
this.btnSendFeedback.TabIndex = 1;
this.btnSendFeedback.Text = "Send Feedback / Bug Report";
this.btnSendFeedback.UseVisualStyleBackColor = true;
//
// labelDataCollection
//
this.labelDataCollection.AutoSize = true;
this.labelDataCollection.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
this.labelDataCollection.Location = new System.Drawing.Point(3, 63);
this.labelDataCollection.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelDataCollection.Name = "labelDataCollection";
this.labelDataCollection.Size = new System.Drawing.Size(88, 15);
this.labelDataCollection.TabIndex = 2;
this.labelDataCollection.Text = "Data Collection";
//
// labelFeedback
//
this.labelFeedback.AutoSize = true;
this.labelFeedback.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelFeedback.Location = new System.Drawing.Point(0, 0);
this.labelFeedback.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1);
this.labelFeedback.Name = "labelFeedback";
this.labelFeedback.Size = new System.Drawing.Size(75, 19);
this.labelFeedback.TabIndex = 0;
this.labelFeedback.Text = "FEEDBACK";
//
// 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.labelFeedback);
this.flowPanel.Controls.Add(this.btnSendFeedback);
this.flowPanel.Controls.Add(this.labelDataCollection);
this.flowPanel.Controls.Add(this.panelDataCollection);
this.flowPanel.Controls.Add(this.btnViewReport);
this.flowPanel.Controls.Add(this.labelDataCollectionMessage);
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;
//
// TabSettingsFeedback
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.flowPanel);
this.Name = "TabSettingsFeedback";
this.Size = new System.Drawing.Size(631, 480);
this.panelDataCollection.ResumeLayout(false);
this.panelDataCollection.PerformLayout();
this.flowPanel.ResumeLayout(false);
this.flowPanel.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Panel panelDataCollection;
private System.Windows.Forms.CheckBox checkDataCollection;
private System.Windows.Forms.Label labelDataCollection;
private System.Windows.Forms.Label labelFeedback;
private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.LinkLabel labelDataCollectionLink;
private System.Windows.Forms.Button btnSendFeedback;
private System.Windows.Forms.Button btnViewReport;
private System.Windows.Forms.Label labelDataCollectionMessage;
private System.Windows.Forms.FlowLayoutPanel flowPanel;
}
}

View File

@@ -0,0 +1,60 @@
using System;
using System.Windows.Forms;
using TweetDuck.Core.Other.Analytics;
using TweetDuck.Core.Other.Settings.Dialogs;
using TweetDuck.Core.Utils;
using TweetDuck.Plugins;
namespace TweetDuck.Core.Other.Settings{
sealed partial class TabSettingsFeedback : BaseTabSettings{
private readonly AnalyticsFile analyticsFile;
private readonly AnalyticsReportGenerator.ExternalInfo analyticsInfo;
private readonly PluginManager plugins;
public TabSettingsFeedback(AnalyticsManager analytics, AnalyticsReportGenerator.ExternalInfo analyticsInfo, PluginManager plugins){
InitializeComponent();
this.analyticsFile = analytics?.File ?? AnalyticsFile.Load(Program.AnalyticsFilePath);
this.analyticsInfo = analyticsInfo;
this.plugins = plugins;
// feedback
checkDataCollection.Checked = Config.AllowDataCollection;
if (analytics != null){
string collectionTime = analyticsFile.LastCollectionMessage;
labelDataCollectionMessage.Text = string.IsNullOrEmpty(collectionTime) ? "No collection yet" : "Last collection: "+collectionTime;
}
}
public override void OnReady(){
btnSendFeedback.Click += btnSendFeedback_Click;
checkDataCollection.CheckedChanged += checkDataCollection_CheckedChanged;
labelDataCollectionLink.LinkClicked += labelDataCollectionLink_LinkClicked;
btnViewReport.Click += btnViewReport_Click;
}
#region Feedback
private void btnSendFeedback_Click(object sender, EventArgs e){
BrowserUtils.OpenExternalBrowser("https://github.com/chylex/TweetDuck/issues/new");
}
private void checkDataCollection_CheckedChanged(object sender, EventArgs e){
Config.AllowDataCollection = checkDataCollection.Checked;
}
private void labelDataCollectionLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e){
BrowserUtils.OpenExternalBrowser("https://github.com/chylex/TweetDuck/wiki/Send-anonymous-data");
}
private void btnViewReport_Click(object sender, EventArgs e){
using(DialogSettingsAnalytics dialog = new DialogSettingsAnalytics(AnalyticsReportGenerator.Create(analyticsFile, analyticsInfo, plugins))){
dialog.ShowDialog();
}
}
#endregion
}
}

View File

@@ -0,0 +1,552 @@
namespace TweetDuck.Core.Other.Settings {
partial class TabSettingsGeneral {
/// <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.components = new System.ComponentModel.Container();
this.checkExpandLinks = new System.Windows.Forms.CheckBox();
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.checkUpdateNotifications = new System.Windows.Forms.CheckBox();
this.btnCheckUpdates = new System.Windows.Forms.Button();
this.labelZoomValue = new System.Windows.Forms.Label();
this.checkBestImageQuality = new System.Windows.Forms.CheckBox();
this.checkOpenSearchInFirstColumn = new System.Windows.Forms.CheckBox();
this.trackBarZoom = new System.Windows.Forms.TrackBar();
this.labelZoom = new System.Windows.Forms.Label();
this.zoomUpdateTimer = new System.Windows.Forms.Timer(this.components);
this.labelUI = new System.Windows.Forms.Label();
this.panelZoom = new System.Windows.Forms.Panel();
this.checkAnimatedAvatars = new System.Windows.Forms.CheckBox();
this.labelUpdates = new System.Windows.Forms.Label();
this.flowPanelLeft = new System.Windows.Forms.FlowLayoutPanel();
this.checkKeepLikeFollowDialogsOpen = new System.Windows.Forms.CheckBox();
this.labelTray = new System.Windows.Forms.Label();
this.comboBoxTrayType = new System.Windows.Forms.ComboBox();
this.labelTrayIcon = new System.Windows.Forms.Label();
this.checkTrayHighlight = new System.Windows.Forms.CheckBox();
this.labelBrowserSettings = new System.Windows.Forms.Label();
this.checkSmoothScrolling = new System.Windows.Forms.CheckBox();
this.checkTouchAdjustment = new System.Windows.Forms.CheckBox();
this.labelBrowserPath = new System.Windows.Forms.Label();
this.comboBoxBrowserPath = new System.Windows.Forms.ComboBox();
this.labelSearchEngine = new System.Windows.Forms.Label();
this.comboBoxSearchEngine = new System.Windows.Forms.ComboBox();
this.flowPanelRight = new System.Windows.Forms.FlowLayoutPanel();
this.checkHardwareAcceleration = new System.Windows.Forms.CheckBox();
this.labelLocales = new System.Windows.Forms.Label();
this.checkSpellCheck = new System.Windows.Forms.CheckBox();
this.labelSpellCheckLanguage = new System.Windows.Forms.Label();
this.comboBoxSpellCheckLanguage = new System.Windows.Forms.ComboBox();
this.labelTranslationTarget = new System.Windows.Forms.Label();
this.comboBoxTranslationTarget = new System.Windows.Forms.ComboBox();
this.panelSeparator = new System.Windows.Forms.Panel();
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).BeginInit();
this.panelZoom.SuspendLayout();
this.flowPanelLeft.SuspendLayout();
this.flowPanelRight.SuspendLayout();
this.SuspendLayout();
//
// checkExpandLinks
//
this.checkExpandLinks.AutoSize = true;
this.checkExpandLinks.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkExpandLinks.Location = new System.Drawing.Point(6, 26);
this.checkExpandLinks.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2);
this.checkExpandLinks.Name = "checkExpandLinks";
this.checkExpandLinks.Size = new System.Drawing.Size(176, 19);
this.checkExpandLinks.TabIndex = 1;
this.checkExpandLinks.Text = "Expand Links When Hovered";
this.checkExpandLinks.UseVisualStyleBackColor = true;
//
// checkUpdateNotifications
//
this.checkUpdateNotifications.AutoSize = true;
this.checkUpdateNotifications.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 393);
this.checkUpdateNotifications.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2);
this.checkUpdateNotifications.Name = "checkUpdateNotifications";
this.checkUpdateNotifications.Size = new System.Drawing.Size(182, 19);
this.checkUpdateNotifications.TabIndex = 13;
this.checkUpdateNotifications.Text = "Check Updates Automatically";
this.checkUpdateNotifications.UseVisualStyleBackColor = true;
//
// btnCheckUpdates
//
this.btnCheckUpdates.AutoSize = true;
this.btnCheckUpdates.Font = new System.Drawing.Font("Segoe UI", 9F);
this.btnCheckUpdates.Location = new System.Drawing.Point(5, 417);
this.btnCheckUpdates.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.btnCheckUpdates.Name = "btnCheckUpdates";
this.btnCheckUpdates.Padding = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.btnCheckUpdates.Size = new System.Drawing.Size(128, 25);
this.btnCheckUpdates.TabIndex = 14;
this.btnCheckUpdates.Text = "Check Updates Now";
this.btnCheckUpdates.UseVisualStyleBackColor = true;
//
// labelZoomValue
//
this.labelZoomValue.BackColor = System.Drawing.Color.Transparent;
this.labelZoomValue.Font = new System.Drawing.Font("Segoe UI", 9F);
this.labelZoomValue.Location = new System.Drawing.Point(147, 4);
this.labelZoomValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.labelZoomValue.Name = "labelZoomValue";
this.labelZoomValue.Size = new System.Drawing.Size(38, 13);
this.labelZoomValue.TabIndex = 1;
this.labelZoomValue.Text = "100%";
this.labelZoomValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
//
// checkBestImageQuality
//
this.checkBestImageQuality.AutoSize = true;
this.checkBestImageQuality.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkBestImageQuality.Location = new System.Drawing.Point(6, 98);
this.checkBestImageQuality.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
this.checkBestImageQuality.Name = "checkBestImageQuality";
this.checkBestImageQuality.Size = new System.Drawing.Size(125, 19);
this.checkBestImageQuality.TabIndex = 4;
this.checkBestImageQuality.Text = "Best Image Quality";
this.checkBestImageQuality.UseVisualStyleBackColor = true;
//
// checkOpenSearchInFirstColumn
//
this.checkOpenSearchInFirstColumn.AutoSize = true;
this.checkOpenSearchInFirstColumn.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkOpenSearchInFirstColumn.Location = new System.Drawing.Point(6, 50);
this.checkOpenSearchInFirstColumn.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
this.checkOpenSearchInFirstColumn.Name = "checkOpenSearchInFirstColumn";
this.checkOpenSearchInFirstColumn.Size = new System.Drawing.Size(245, 19);
this.checkOpenSearchInFirstColumn.TabIndex = 2;
this.checkOpenSearchInFirstColumn.Text = "Add Search Columns Before First Column";
this.checkOpenSearchInFirstColumn.UseVisualStyleBackColor = true;
//
// trackBarZoom
//
this.trackBarZoom.AutoSize = false;
this.trackBarZoom.BackColor = System.Drawing.SystemColors.Control;
this.trackBarZoom.LargeChange = 25;
this.trackBarZoom.Location = new System.Drawing.Point(3, 3);
this.trackBarZoom.Maximum = 200;
this.trackBarZoom.Minimum = 50;
this.trackBarZoom.Name = "trackBarZoom";
this.trackBarZoom.Size = new System.Drawing.Size(148, 30);
this.trackBarZoom.SmallChange = 5;
this.trackBarZoom.TabIndex = 0;
this.trackBarZoom.TickFrequency = 25;
this.trackBarZoom.Value = 100;
//
// labelZoom
//
this.labelZoom.AutoSize = true;
this.labelZoom.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
this.labelZoom.Location = new System.Drawing.Point(3, 155);
this.labelZoom.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelZoom.Name = "labelZoom";
this.labelZoom.Size = new System.Drawing.Size(39, 15);
this.labelZoom.TabIndex = 6;
this.labelZoom.Text = "Zoom";
//
// zoomUpdateTimer
//
this.zoomUpdateTimer.Interval = 250;
this.zoomUpdateTimer.Tick += new System.EventHandler(this.zoomUpdateTimer_Tick);
//
// labelUI
//
this.labelUI.AutoSize = true;
this.labelUI.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelUI.Location = new System.Drawing.Point(0, 0);
this.labelUI.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1);
this.labelUI.Name = "labelUI";
this.labelUI.Size = new System.Drawing.Size(118, 19);
this.labelUI.TabIndex = 0;
this.labelUI.Text = "USER INTERFACE";
//
// panelZoom
//
this.panelZoom.Controls.Add(this.trackBarZoom);
this.panelZoom.Controls.Add(this.labelZoomValue);
this.panelZoom.Location = new System.Drawing.Point(0, 171);
this.panelZoom.Margin = new System.Windows.Forms.Padding(0, 1, 0, 0);
this.panelZoom.Name = "panelZoom";
this.panelZoom.Size = new System.Drawing.Size(300, 35);
this.panelZoom.TabIndex = 7;
//
// checkAnimatedAvatars
//
this.checkAnimatedAvatars.AutoSize = true;
this.checkAnimatedAvatars.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkAnimatedAvatars.Location = new System.Drawing.Point(6, 122);
this.checkAnimatedAvatars.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
this.checkAnimatedAvatars.Name = "checkAnimatedAvatars";
this.checkAnimatedAvatars.Size = new System.Drawing.Size(158, 19);
this.checkAnimatedAvatars.TabIndex = 5;
this.checkAnimatedAvatars.Text = "Enable Animated Avatars";
this.checkAnimatedAvatars.UseVisualStyleBackColor = true;
//
// labelUpdates
//
this.labelUpdates.AutoSize = true;
this.labelUpdates.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelUpdates.Location = new System.Drawing.Point(0, 367);
this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1);
this.labelUpdates.Name = "labelUpdates";
this.labelUpdates.Size = new System.Drawing.Size(69, 19);
this.labelUpdates.TabIndex = 12;
this.labelUpdates.Text = "UPDATES";
//
// flowPanelLeft
//
this.flowPanelLeft.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)));
this.flowPanelLeft.Controls.Add(this.labelUI);
this.flowPanelLeft.Controls.Add(this.checkExpandLinks);
this.flowPanelLeft.Controls.Add(this.checkOpenSearchInFirstColumn);
this.flowPanelLeft.Controls.Add(this.checkKeepLikeFollowDialogsOpen);
this.flowPanelLeft.Controls.Add(this.checkBestImageQuality);
this.flowPanelLeft.Controls.Add(this.checkAnimatedAvatars);
this.flowPanelLeft.Controls.Add(this.labelZoom);
this.flowPanelLeft.Controls.Add(this.panelZoom);
this.flowPanelLeft.Controls.Add(this.labelTray);
this.flowPanelLeft.Controls.Add(this.comboBoxTrayType);
this.flowPanelLeft.Controls.Add(this.labelTrayIcon);
this.flowPanelLeft.Controls.Add(this.checkTrayHighlight);
this.flowPanelLeft.Controls.Add(this.labelUpdates);
this.flowPanelLeft.Controls.Add(this.checkUpdateNotifications);
this.flowPanelLeft.Controls.Add(this.btnCheckUpdates);
this.flowPanelLeft.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanelLeft.Location = new System.Drawing.Point(9, 9);
this.flowPanelLeft.Name = "flowPanelLeft";
this.flowPanelLeft.Size = new System.Drawing.Size(300, 462);
this.flowPanelLeft.TabIndex = 0;
this.flowPanelLeft.WrapContents = false;
//
// checkKeepLikeFollowDialogsOpen
//
this.checkKeepLikeFollowDialogsOpen.AutoSize = true;
this.checkKeepLikeFollowDialogsOpen.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkKeepLikeFollowDialogsOpen.Location = new System.Drawing.Point(6, 74);
this.checkKeepLikeFollowDialogsOpen.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
this.checkKeepLikeFollowDialogsOpen.Name = "checkKeepLikeFollowDialogsOpen";
this.checkKeepLikeFollowDialogsOpen.Size = new System.Drawing.Size(190, 19);
this.checkKeepLikeFollowDialogsOpen.TabIndex = 3;
this.checkKeepLikeFollowDialogsOpen.Text = "Keep Like/Follow Dialogs Open";
this.checkKeepLikeFollowDialogsOpen.UseVisualStyleBackColor = true;
//
// labelTray
//
this.labelTray.AutoSize = true;
this.labelTray.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelTray.Location = new System.Drawing.Point(0, 236);
this.labelTray.Margin = new System.Windows.Forms.Padding(0, 30, 0, 1);
this.labelTray.Name = "labelTray";
this.labelTray.Size = new System.Drawing.Size(99, 19);
this.labelTray.TabIndex = 8;
this.labelTray.Text = "SYSTEM TRAY";
//
// comboBoxTrayType
//
this.comboBoxTrayType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxTrayType.Font = new System.Drawing.Font("Segoe UI", 9F);
this.comboBoxTrayType.FormattingEnabled = true;
this.comboBoxTrayType.Location = new System.Drawing.Point(5, 260);
this.comboBoxTrayType.Margin = new System.Windows.Forms.Padding(5, 4, 3, 3);
this.comboBoxTrayType.Name = "comboBoxTrayType";
this.comboBoxTrayType.Size = new System.Drawing.Size(144, 23);
this.comboBoxTrayType.TabIndex = 9;
//
// labelTrayIcon
//
this.labelTrayIcon.AutoSize = true;
this.labelTrayIcon.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
this.labelTrayIcon.Location = new System.Drawing.Point(3, 295);
this.labelTrayIcon.Margin = new System.Windows.Forms.Padding(3, 9, 3, 0);
this.labelTrayIcon.Name = "labelTrayIcon";
this.labelTrayIcon.Size = new System.Drawing.Size(56, 15);
this.labelTrayIcon.TabIndex = 10;
this.labelTrayIcon.Text = "Tray Icon";
//
// checkTrayHighlight
//
this.checkTrayHighlight.AutoSize = true;
this.checkTrayHighlight.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkTrayHighlight.Location = new System.Drawing.Point(6, 316);
this.checkTrayHighlight.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2);
this.checkTrayHighlight.Name = "checkTrayHighlight";
this.checkTrayHighlight.Size = new System.Drawing.Size(114, 19);
this.checkTrayHighlight.TabIndex = 11;
this.checkTrayHighlight.Text = "Enable Highlight";
this.checkTrayHighlight.UseVisualStyleBackColor = true;
//
// labelBrowserSettings
//
this.labelBrowserSettings.AutoSize = true;
this.labelBrowserSettings.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelBrowserSettings.Location = new System.Drawing.Point(0, 0);
this.labelBrowserSettings.Margin = new System.Windows.Forms.Padding(0, 0, 0, 1);
this.labelBrowserSettings.Name = "labelBrowserSettings";
this.labelBrowserSettings.Size = new System.Drawing.Size(143, 19);
this.labelBrowserSettings.TabIndex = 0;
this.labelBrowserSettings.Text = "BROWSER SETTINGS";
//
// checkSmoothScrolling
//
this.checkSmoothScrolling.AutoSize = true;
this.checkSmoothScrolling.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkSmoothScrolling.Location = new System.Drawing.Point(6, 26);
this.checkSmoothScrolling.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2);
this.checkSmoothScrolling.Name = "checkSmoothScrolling";
this.checkSmoothScrolling.Size = new System.Drawing.Size(117, 19);
this.checkSmoothScrolling.TabIndex = 1;
this.checkSmoothScrolling.Text = "Smooth Scrolling";
this.checkSmoothScrolling.UseVisualStyleBackColor = true;
//
// checkTouchAdjustment
//
this.checkTouchAdjustment.AutoSize = true;
this.checkTouchAdjustment.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkTouchAdjustment.Location = new System.Drawing.Point(6, 50);
this.checkTouchAdjustment.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
this.checkTouchAdjustment.Name = "checkTouchAdjustment";
this.checkTouchAdjustment.Size = new System.Drawing.Size(163, 19);
this.checkTouchAdjustment.TabIndex = 2;
this.checkTouchAdjustment.Text = "Touch Screen Adjustment";
this.checkTouchAdjustment.UseVisualStyleBackColor = true;
//
// labelBrowserPath
//
this.labelBrowserPath.AutoSize = true;
this.labelBrowserPath.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
this.labelBrowserPath.Location = new System.Drawing.Point(3, 107);
this.labelBrowserPath.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelBrowserPath.Name = "labelBrowserPath";
this.labelBrowserPath.Size = new System.Drawing.Size(104, 15);
this.labelBrowserPath.TabIndex = 4;
this.labelBrowserPath.Text = "Open Links With...";
//
// comboBoxBrowserPath
//
this.comboBoxBrowserPath.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxBrowserPath.Font = new System.Drawing.Font("Segoe UI", 9F);
this.comboBoxBrowserPath.FormattingEnabled = true;
this.comboBoxBrowserPath.Location = new System.Drawing.Point(5, 126);
this.comboBoxBrowserPath.Margin = new System.Windows.Forms.Padding(5, 4, 3, 3);
this.comboBoxBrowserPath.Name = "comboBoxBrowserPath";
this.comboBoxBrowserPath.Size = new System.Drawing.Size(173, 23);
this.comboBoxBrowserPath.TabIndex = 5;
//
// labelSearchEngine
//
this.labelSearchEngine.AutoSize = true;
this.labelSearchEngine.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
this.labelSearchEngine.Location = new System.Drawing.Point(3, 164);
this.labelSearchEngine.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelSearchEngine.Name = "labelSearchEngine";
this.labelSearchEngine.Size = new System.Drawing.Size(82, 15);
this.labelSearchEngine.TabIndex = 6;
this.labelSearchEngine.Text = "Search Engine";
//
// comboBoxSearchEngine
//
this.comboBoxSearchEngine.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxSearchEngine.Font = new System.Drawing.Font("Segoe UI", 9F);
this.comboBoxSearchEngine.FormattingEnabled = true;
this.comboBoxSearchEngine.Location = new System.Drawing.Point(5, 183);
this.comboBoxSearchEngine.Margin = new System.Windows.Forms.Padding(5, 4, 3, 3);
this.comboBoxSearchEngine.Name = "comboBoxSearchEngine";
this.comboBoxSearchEngine.Size = new System.Drawing.Size(173, 23);
this.comboBoxSearchEngine.TabIndex = 7;
//
// flowPanelRight
//
this.flowPanelRight.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)));
this.flowPanelRight.Controls.Add(this.labelBrowserSettings);
this.flowPanelRight.Controls.Add(this.checkSmoothScrolling);
this.flowPanelRight.Controls.Add(this.checkTouchAdjustment);
this.flowPanelRight.Controls.Add(this.checkHardwareAcceleration);
this.flowPanelRight.Controls.Add(this.labelBrowserPath);
this.flowPanelRight.Controls.Add(this.comboBoxBrowserPath);
this.flowPanelRight.Controls.Add(this.labelSearchEngine);
this.flowPanelRight.Controls.Add(this.comboBoxSearchEngine);
this.flowPanelRight.Controls.Add(this.labelLocales);
this.flowPanelRight.Controls.Add(this.checkSpellCheck);
this.flowPanelRight.Controls.Add(this.labelSpellCheckLanguage);
this.flowPanelRight.Controls.Add(this.comboBoxSpellCheckLanguage);
this.flowPanelRight.Controls.Add(this.labelTranslationTarget);
this.flowPanelRight.Controls.Add(this.comboBoxTranslationTarget);
this.flowPanelRight.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanelRight.Location = new System.Drawing.Point(322, 9);
this.flowPanelRight.Name = "flowPanelRight";
this.flowPanelRight.Size = new System.Drawing.Size(300, 462);
this.flowPanelRight.TabIndex = 1;
this.flowPanelRight.WrapContents = false;
//
// checkHardwareAcceleration
//
this.checkHardwareAcceleration.AutoSize = true;
this.checkHardwareAcceleration.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkHardwareAcceleration.Location = new System.Drawing.Point(6, 74);
this.checkHardwareAcceleration.Margin = new System.Windows.Forms.Padding(6, 3, 3, 2);
this.checkHardwareAcceleration.Name = "checkHardwareAcceleration";
this.checkHardwareAcceleration.Size = new System.Drawing.Size(146, 19);
this.checkHardwareAcceleration.TabIndex = 3;
this.checkHardwareAcceleration.Text = "Hardware Acceleration";
this.checkHardwareAcceleration.UseVisualStyleBackColor = true;
//
// labelLocales
//
this.labelLocales.AutoSize = true;
this.labelLocales.Font = new System.Drawing.Font("Segoe UI Semibold", 10.5F, System.Drawing.FontStyle.Bold);
this.labelLocales.Location = new System.Drawing.Point(0, 236);
this.labelLocales.Margin = new System.Windows.Forms.Padding(0, 27, 0, 1);
this.labelLocales.Name = "labelLocales";
this.labelLocales.Size = new System.Drawing.Size(67, 19);
this.labelLocales.TabIndex = 8;
this.labelLocales.Text = "LOCALES";
//
// checkSpellCheck
//
this.checkSpellCheck.AutoSize = true;
this.checkSpellCheck.Font = new System.Drawing.Font("Segoe UI", 9F);
this.checkSpellCheck.Location = new System.Drawing.Point(6, 262);
this.checkSpellCheck.Margin = new System.Windows.Forms.Padding(6, 6, 3, 2);
this.checkSpellCheck.Name = "checkSpellCheck";
this.checkSpellCheck.Size = new System.Drawing.Size(125, 19);
this.checkSpellCheck.TabIndex = 9;
this.checkSpellCheck.Text = "Enable Spell Check";
this.checkSpellCheck.UseVisualStyleBackColor = true;
//
// labelSpellCheckLanguage
//
this.labelSpellCheckLanguage.AutoSize = true;
this.labelSpellCheckLanguage.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
this.labelSpellCheckLanguage.Location = new System.Drawing.Point(3, 295);
this.labelSpellCheckLanguage.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelSpellCheckLanguage.Name = "labelSpellCheckLanguage";
this.labelSpellCheckLanguage.Size = new System.Drawing.Size(123, 15);
this.labelSpellCheckLanguage.TabIndex = 10;
this.labelSpellCheckLanguage.Text = "Spell Check Language";
//
// comboBoxSpellCheckLanguage
//
this.comboBoxSpellCheckLanguage.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxSpellCheckLanguage.Font = new System.Drawing.Font("Segoe UI", 9F);
this.comboBoxSpellCheckLanguage.FormattingEnabled = true;
this.comboBoxSpellCheckLanguage.Location = new System.Drawing.Point(5, 314);
this.comboBoxSpellCheckLanguage.Margin = new System.Windows.Forms.Padding(5, 4, 3, 3);
this.comboBoxSpellCheckLanguage.Name = "comboBoxSpellCheckLanguage";
this.comboBoxSpellCheckLanguage.Size = new System.Drawing.Size(290, 23);
this.comboBoxSpellCheckLanguage.TabIndex = 11;
//
// labelTranslationTarget
//
this.labelTranslationTarget.AutoSize = true;
this.labelTranslationTarget.Font = new System.Drawing.Font("Segoe UI Semibold", 9F, System.Drawing.FontStyle.Bold);
this.labelTranslationTarget.Location = new System.Drawing.Point(3, 352);
this.labelTranslationTarget.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelTranslationTarget.Name = "labelTranslationTarget";
this.labelTranslationTarget.Size = new System.Drawing.Size(142, 15);
this.labelTranslationTarget.TabIndex = 12;
this.labelTranslationTarget.Text = "Bing Translator Language";
//
// comboBoxTranslationTarget
//
this.comboBoxTranslationTarget.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxTranslationTarget.Font = new System.Drawing.Font("Segoe UI", 9F);
this.comboBoxTranslationTarget.FormattingEnabled = true;
this.comboBoxTranslationTarget.Location = new System.Drawing.Point(5, 371);
this.comboBoxTranslationTarget.Margin = new System.Windows.Forms.Padding(5, 4, 3, 3);
this.comboBoxTranslationTarget.Name = "comboBoxTranslationTarget";
this.comboBoxTranslationTarget.Size = new System.Drawing.Size(290, 23);
this.comboBoxTranslationTarget.TabIndex = 13;
//
// panelSeparator
//
this.panelSeparator.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)));
this.panelSeparator.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(200)))), ((int)(((byte)(200)))), ((int)(((byte)(200)))));
this.panelSeparator.Location = new System.Drawing.Point(312, 0);
this.panelSeparator.Margin = new System.Windows.Forms.Padding(0, 0, 6, 0);
this.panelSeparator.Name = "panelSeparator";
this.panelSeparator.Size = new System.Drawing.Size(1, 480);
this.panelSeparator.TabIndex = 2;
//
// TabSettingsGeneral
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.panelSeparator);
this.Controls.Add(this.flowPanelRight);
this.Controls.Add(this.flowPanelLeft);
this.Name = "TabSettingsGeneral";
this.Size = new System.Drawing.Size(631, 480);
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).EndInit();
this.panelZoom.ResumeLayout(false);
this.flowPanelLeft.ResumeLayout(false);
this.flowPanelLeft.PerformLayout();
this.flowPanelRight.ResumeLayout(false);
this.flowPanelRight.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.CheckBox checkExpandLinks;
private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.CheckBox checkUpdateNotifications;
private System.Windows.Forms.Button btnCheckUpdates;
private System.Windows.Forms.Label labelZoom;
private System.Windows.Forms.Label labelZoomValue;
private System.Windows.Forms.TrackBar trackBarZoom;
private System.Windows.Forms.Timer zoomUpdateTimer;
private System.Windows.Forms.Label labelUI;
private System.Windows.Forms.Panel panelZoom;
private System.Windows.Forms.Label labelUpdates;
private System.Windows.Forms.CheckBox checkBestImageQuality;
private System.Windows.Forms.CheckBox checkOpenSearchInFirstColumn;
private System.Windows.Forms.CheckBox checkAnimatedAvatars;
private System.Windows.Forms.FlowLayoutPanel flowPanelLeft;
private System.Windows.Forms.CheckBox checkKeepLikeFollowDialogsOpen;
private System.Windows.Forms.Label labelBrowserPath;
private System.Windows.Forms.ComboBox comboBoxBrowserPath;
private System.Windows.Forms.Label labelBrowserSettings;
private System.Windows.Forms.CheckBox checkSmoothScrolling;
private System.Windows.Forms.Label labelSearchEngine;
private System.Windows.Forms.ComboBox comboBoxSearchEngine;
private System.Windows.Forms.CheckBox checkTouchAdjustment;
private System.Windows.Forms.FlowLayoutPanel flowPanelRight;
private System.Windows.Forms.Panel panelSeparator;
private System.Windows.Forms.Label labelTray;
private System.Windows.Forms.ComboBox comboBoxTrayType;
private System.Windows.Forms.Label labelTrayIcon;
private System.Windows.Forms.CheckBox checkTrayHighlight;
private System.Windows.Forms.Label labelLocales;
private System.Windows.Forms.CheckBox checkSpellCheck;
private System.Windows.Forms.Label labelSpellCheckLanguage;
private System.Windows.Forms.ComboBox comboBoxSpellCheckLanguage;
private System.Windows.Forms.Label labelTranslationTarget;
private System.Windows.Forms.ComboBox comboBoxTranslationTarget;
private System.Windows.Forms.CheckBox checkHardwareAcceleration;
}
}

Some files were not shown because too many files have changed in this diff Show More