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

Compare commits

..

499 Commits
1.3.3 ... 1.7.4

Author SHA1 Message Date
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
167 changed files with 10360 additions and 3215 deletions

104
.gitignore vendored
View File

@@ -1,6 +1,12 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# TweetDuck installer builds
bld/*
!bld/*.iss
!bld/*.bat
!bld/Resources
# User-specific files
*.suo
*.user
@@ -17,14 +23,11 @@
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
@@ -34,11 +37,6 @@ bld/
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
artifacts/
@@ -67,27 +65,12 @@ artifacts/
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
@@ -114,33 +97,9 @@ nCrunchTemp_*
*.mm.*
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
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
*.nupkg
# The packages folder can be ignored because of Package Restore
@@ -153,17 +112,6 @@ publish/
*.nuget.props
*.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
AppPackages/
BundleArtifacts/
@@ -185,9 +133,6 @@ ClientBin/
node_modules/
orleans.codegen.cs
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
@@ -196,40 +141,3 @@ Backup*/
UpgradeLog*.XML
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,34 @@
using System;
using TweetDck.Core.Utils;
namespace TweetDck.Configuration{
static class Arguments{
// public args
public const string ArgDataFolder = "-datafolder";
public const string ArgLocale = "-locale";
public const string ArgLogging = "-log";
public const string ArgDebugUpdates = "-debugupdates";
// internal args
public const string ArgRestart = "-restart";
public const string ArgImportCookies = "-importcookies";
// 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);
return args;
}
}
}

View File

@@ -1,10 +1,15 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Threading;
using TweetDck.Core.Utils;
namespace TweetDck.Configuration{
sealed class LockManager{
public enum Result{
Success, HasProcess, Fail
}
public Process LockingProcess { get; private set; }
private readonly string file;
@@ -14,111 +19,158 @@ namespace TweetDck.Configuration{
this.file = file;
}
private bool CreateLockFile(){
private void CreateLockFileStream(){
lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
WriteIntToStream(lockStream, GetCurrentProcessId());
lockStream.Flush(true);
}
private bool ReleaseLockFileStream(){
if (lockStream != null){
lockStream.Dispose();
lockStream = null;
return true;
}
else{
return false;
}
}
private Result TryCreateLockFile(){
if (lockStream != null){
throw new InvalidOperationException("Lock file already exists.");
}
try{
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;
CreateLockFileStream();
return Result.Success;
}catch(DirectoryNotFoundException){
try{
CreateLockFileStream();
return Result.Success;
}catch{
ReleaseLockFileStream();
return Result.Fail;
}
return true;
}catch(Exception){
if (lockStream != null){
lockStream.Close();
lockStream.Dispose();
}
return false;
}catch(IOException){
return Result.HasProcess;
}catch{
ReleaseLockFileStream();
return Result.Fail;
}
}
public bool Lock(){
if (lockStream != null)return true;
try{
byte[] bytes = new byte[4];
using(FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){
fileStream.Read(bytes, 0, 4);
}
int pid = BitConverter.ToInt32(bytes, 0);
try{
Process foundProcess = Process.GetProcessById(pid);
using(Process currentProcess = Process.GetCurrentProcess()){
if (foundProcess.ProcessName == currentProcess.ProcessName){
LockingProcess = foundProcess;
}
}
}catch(ArgumentException){}
return LockingProcess == null && CreateLockFile();
}catch(DirectoryNotFoundException){
string dir = Path.GetDirectoryName(file);
if (dir != null){
Directory.CreateDirectory(dir);
return CreateLockFile();
}
}catch(FileNotFoundException){
return CreateLockFile();
}catch(Exception){
return false;
public Result Lock(){
if (lockStream != null){
return Result.Success;
}
return false;
Result initialResult = TryCreateLockFile();
if (initialResult == Result.HasProcess){
try{
int pid;
using(FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){
pid = ReadIntFromStream(fileStream);
}
try{
Process foundProcess = Process.GetProcessById(pid);
using(Process currentProcess = Process.GetCurrentProcess()){
if (foundProcess.MainModule.FileVersionInfo.InternalName == currentProcess.MainModule.FileVersionInfo.InternalName){
LockingProcess = foundProcess;
}
else{
foundProcess.Close();
}
}
}catch{
// GetProcessById throws ArgumentException if the process is missing
// Process.MainModule can throw exceptions in some cases
}
return LockingProcess == null ? Result.Fail : Result.HasProcess;
}catch{
return Result.Fail;
}
}
return initialResult;
}
public bool Unlock(){
bool result = true;
if (lockStream != null){
lockStream.Dispose();
if (ReleaseLockFileStream()){
try{
File.Delete(file);
}catch(Exception e){
Program.Log(e.ToString());
Program.Reporter.Log(e.ToString());
result = false;
}
lockStream = null;
}
return result;
}
public bool CloseLockingProcess(int timeout){
public bool CloseLockingProcess(int closeTimeout, int killTimeout){
if (LockingProcess != null){
LockingProcess.CloseMainWindow();
try{
if (LockingProcess.CloseMainWindow()){
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, closeTimeout, 250);
}
for(int waited = 0; waited < timeout && !LockingProcess.HasExited;){
LockingProcess.Refresh();
if (!LockingProcess.HasExited){
LockingProcess.Kill();
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, killTimeout, 250);
}
Thread.Sleep(100);
waited += 100;
}
if (LockingProcess.HasExited){
LockingProcess.Dispose();
LockingProcess = null;
return true;
}
}catch(Exception ex){
if (ex is InvalidOperationException || ex is Win32Exception){
if (LockingProcess != null){
LockingProcess.Refresh();
if (LockingProcess.HasExited){
LockingProcess.Dispose();
LockingProcess = null;
return true;
bool hasExited = LockingProcess.HasExited;
LockingProcess.Dispose();
return hasExited;
}
}
else throw;
}
}
return false;
}
private bool CheckLockingProcessExited(){
LockingProcess.Refresh();
return LockingProcess.HasExited;
}
// Utility functions
private static void WriteIntToStream(Stream stream, int value){
byte[] id = BitConverter.GetBytes(value);
stream.Write(id, 0, id.Length);
}
private static int ReadIntFromStream(Stream stream){
byte[] bytes = new byte[4];
stream.Read(bytes, 0, 4);
return BitConverter.ToInt32(bytes, 0);
}
private static int GetCurrentProcessId(){
using(Process process = Process.GetCurrentProcess()){
return process.Id;
}
}
}
}

View File

@@ -1,87 +1,90 @@
using System;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using TweetDck.Core;
using TweetDck.Core.Handling;
using TweetDck.Core.Controls;
using TweetDck.Core.Notification;
using TweetDck.Core.Utils;
using TweetDck.Plugins;
namespace TweetDck.Configuration{
[Serializable]
sealed class UserConfig{
private static readonly IFormatter Formatter = new BinaryFormatter{
Binder = new SerializationCompatibilityHandler()
};
private static readonly IFormatter Formatter = new BinaryFormatter();
private const int CurrentFileVersion = 5;
private const int CurrentFileVersion = 9;
// START OF CONFIGURATION
public bool IgnoreMigration { get; set; }
public bool IgnoreUninstallCheck { get; set; }
public WindowState BrowserWindow { get; set; }
public bool DisplayNotificationColumn { get; set; }
public bool DisplayNotificationTimer { get; set; }
public bool NotificationTimerCountDown { get; set; }
public bool NotificationSkipOnLinkClick { get; set; }
public bool NotificationNonIntrusiveMode { get; set; }
public TweetNotification.Duration NotificationDuration { get; set; }
public TweetNotification.Position NotificationPosition { get; set; }
public Point CustomNotificationPosition { get; set; }
public int NotificationEdgeDistance { get; set; }
public int NotificationDisplay { get; set; }
public int NotificationIdlePauseSeconds { get; set; }
public int NotificationDurationValue { get; set; }
public bool NotificationLegacyLoad { get; set; }
public bool EnableSpellCheck { get; set; }
public bool ExpandLinksOnHover { get; set; }
public bool SwitchAccountSelectors { get; set; }
public bool EnableTrayHighlight { get; set; }
public bool EnableUpdateCheck { get; set; }
public string DismissedUpdate { get; set; }
public PluginConfig Plugins { get; private set; }
[Obsolete] public PluginConfig Plugins { get; set; } // TODO remove eventually
public WindowState PluginsWindow { get; set; }
public string CustomCefArgs { get; set; }
public string CustomBrowserCSS { get; set; }
public string CustomNotificationCSS { get; set; }
public bool IsCustomNotificationPositionSet{
get{
return CustomNotificationPosition.X != -32000 && CustomNotificationPosition.X != 32000;
}
public bool IsCustomNotificationPositionSet => CustomNotificationPosition != ControlExtensions.InvisibleLocation;
public string NotificationSoundPath{
get => string.IsNullOrEmpty(notificationSoundPath) ? string.Empty : notificationSoundPath;
set => notificationSoundPath = value;
}
public bool MuteNotifications{
get{
return muteNotifications;
}
get => muteNotifications;
set{
if (muteNotifications == value)return;
muteNotifications = value;
if (MuteToggled != null){
MuteToggled(this, new EventArgs());
if (muteNotifications != value){
muteNotifications = value;
MuteToggled?.Invoke(this, new EventArgs());
}
}
}
public TrayIcon.Behavior TrayBehavior{
get{
return trayBehavior;
}
public int ZoomLevel{
get => zoomLevel;
set{
if (trayBehavior == value)return;
if (zoomLevel != value){
zoomLevel = value;
ZoomLevelChanged?.Invoke(this, new EventArgs());
}
}
}
trayBehavior = value;
public double ZoomMultiplier => zoomLevel/100.0;
if (TrayBehaviorChanged != null){
TrayBehaviorChanged(this, new EventArgs());
public TrayIcon.Behavior TrayBehavior{
get => trayBehavior;
set{
if (trayBehavior != value){
trayBehavior = value;
TrayBehaviorChanged?.Invoke(this, new EventArgs());
}
}
}
@@ -91,6 +94,9 @@ namespace TweetDck.Configuration{
[field:NonSerialized]
public event EventHandler MuteToggled;
[field:NonSerialized]
public event EventHandler ZoomLevelChanged;
[field:NonSerialized]
public event EventHandler TrayBehaviorChanged;
@@ -99,26 +105,26 @@ namespace TweetDck.Configuration{
private int fileVersion;
private bool muteNotifications;
private int zoomLevel;
private string notificationSoundPath;
private TrayIcon.Behavior trayBehavior;
private UserConfig(string file){
this.file = file;
BrowserWindow = new WindowState();
ZoomLevel = 100;
DisplayNotificationTimer = true;
NotificationDuration = TweetNotification.Duration.Medium;
NotificationNonIntrusiveMode = true;
NotificationPosition = TweetNotification.Position.TopRight;
CustomNotificationPosition = new Point(-32000, -32000);
CustomNotificationPosition = ControlExtensions.InvisibleLocation;
NotificationEdgeDistance = 8;
NotificationDurationValue = 25;
EnableUpdateCheck = true;
ExpandLinksOnHover = true;
SwitchAccountSelectors = true;
EnableTrayHighlight = true;
Plugins = new PluginConfig();
PluginsWindow = new WindowState();
Plugins.DisableOfficialFromConfig("clear-columns");
Plugins.DisableOfficialFromConfig("reply-account");
}
private void UpgradeFile(){
@@ -140,27 +146,36 @@ namespace TweetDck.Configuration{
if (fileVersion == 2){
BrowserWindow = new WindowState();
Plugins = new PluginConfig();
PluginsWindow = new WindowState();
++fileVersion;
}
if (fileVersion == 3){
EnableTrayHighlight = true;
switch(NotificationDuration){
case TweetNotification.Duration.Short: NotificationDurationValue = 15; break;
case TweetNotification.Duration.Medium: NotificationDurationValue = 25; break;
case TweetNotification.Duration.Long: NotificationDurationValue = 35; break;
case TweetNotification.Duration.VeryLong: NotificationDurationValue = 45; break;
}
NotificationDurationValue = 25;
++fileVersion;
}
if (fileVersion == 4){
Plugins.DisableOfficialFromConfig("clear-columns");
Plugins.DisableOfficialFromConfig("reply-account");
++fileVersion;
}
if (fileVersion == 5){
++fileVersion;
}
if (fileVersion == 6){
NotificationNonIntrusiveMode = true;
++fileVersion;
}
if (fileVersion == 7){
ZoomLevel = 100;
++fileVersion;
}
if (fileVersion == 8){
SwitchAccountSelectors = true;
++fileVersion;
}
@@ -188,13 +203,14 @@ namespace TweetDck.Configuration{
return true;
}catch(Exception e){
Program.HandleException("Could not save the configuration file.", e);
Program.Reporter.HandleException("Configuration Error", "Could not save the configuration file.", true, e);
return false;
}
}
public static UserConfig Load(string file){
UserConfig config = null;
Exception firstException = null;
for(int attempt = 0; attempt < 2; attempt++){
try{
@@ -203,36 +219,32 @@ namespace TweetDck.Configuration{
config.file = file;
}
}
if (config != null){
config.UpgradeFile();
}
config?.UpgradeFile();
break;
}catch(FileNotFoundException){
}catch(DirectoryNotFoundException){
break;
}catch(Exception e){
Program.HandleException("Could not open the configuration file.", e);
if (attempt == 0){
firstException = e;
Program.Reporter.Log(e.ToString());
}
else if (firstException != null){
Program.Reporter.HandleException("Configuration Error", "Could not open the backup configuration file. If you continue, you may lose your settings and list of enabled plugins.", true, e);
}
}
}
if (firstException != null && config == null){
Program.Reporter.HandleException("Configuration Error", "Could not open the configuration file.", true, firstException);
}
return config ?? new UserConfig(file);
}
public static string GetBackupFile(string file){
return file+".bak";
}
private class SerializationCompatibilityHandler : SerializationBinder{
public override Type BindToType(string assemblyName, string typeName){
#if DUCK
assemblyName = assemblyName.Replace("TweetDick", "TweetDuck");
#else
assemblyName = assemblyName.Replace("TweetDuck", "TweetDick");
#endif
typeName = typeName.Replace("TweetDick", "TweetDck");
return Type.GetType(string.Format(CultureInfo.CurrentCulture, "{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,19 @@
using System;
using System.Windows.Forms;
using TweetDck.Core.Controls;
namespace TweetDck.Core.Bridge{
sealed class CallbackBridge{
private readonly Control owner;
private readonly Action safeCallback;
public CallbackBridge(Control owner, Action safeCallback){
this.owner = owner;
this.safeCallback = safeCallback;
}
public void Trigger(){
owner.InvokeSafe(safeCallback);
}
}
}

View File

@@ -0,0 +1,45 @@
using System;
using System.Text;
namespace TweetDck.Core.Bridge{
static class PropertyBridge{
[Flags]
public enum Properties{
ExpandLinksOnHover = 1,
MuteNotifications = 2,
HasCustomNotificationSound = 4,
SkipOnLinkClick = 8,
SwitchAccountSelectors = 16,
AllBrowser = ExpandLinksOnHover | SwitchAccountSelectors | MuteNotifications | HasCustomNotificationSound,
AllNotification = ExpandLinksOnHover | SkipOnLinkClick
}
public static string GenerateScript(Properties properties){
StringBuilder build = new StringBuilder();
build.Append("(function(c){");
if (properties.HasFlag(Properties.ExpandLinksOnHover)){
build.Append("c.expandLinksOnHover=").Append(Program.UserConfig.ExpandLinksOnHover ? "true;" : "false;");
}
if (properties.HasFlag(Properties.SwitchAccountSelectors)){
build.Append("c.switchAccountSelectors=").Append(Program.UserConfig.SwitchAccountSelectors ? "true;" : "false;");
}
if (properties.HasFlag(Properties.MuteNotifications)){
build.Append("c.muteNotifications=").Append(Program.UserConfig.MuteNotifications ? "true;" : "false;");
}
if (properties.HasFlag(Properties.HasCustomNotificationSound)){
build.Append("c.hasCustomNotificationSound=").Append(Program.UserConfig.NotificationSoundPath.Length > 0 ? "true;" : "false;");
}
if (properties.HasFlag(Properties.SkipOnLinkClick)){
build.Append("c.skipOnLinkClick=").Append(Program.UserConfig.NotificationSkipOnLinkClick ? "true;" : "false;");
}
build.Append("})(window.$TDX=window.$TDX||{})");
return build.ToString();
}
}
}

View File

@@ -0,0 +1,107 @@
using System.Windows.Forms;
using TweetDck.Core.Controls;
using TweetDck.Core.Notification;
using TweetDck.Core.Utils;
namespace TweetDck.Core.Bridge{
sealed class TweetDeckBridge{
public static string LastRightClickedLink = string.Empty;
public static string LastHighlightedTweet = string.Empty;
public static string LastHighlightedQuotedTweet = string.Empty;
public static void ResetStaticProperties(){
LastRightClickedLink = LastHighlightedTweet = LastHighlightedQuotedTweet = string.Empty;
}
private readonly FormBrowser form;
private readonly FormNotificationMain notification;
public TweetDeckBridge(FormBrowser form, FormNotificationMain notification){
this.form = form;
this.notification = notification;
}
public void LoadFontSizeClass(string fsClass){
form.InvokeAsyncSafe(() => {
TweetNotification.SetFontSizeClass(fsClass);
});
}
public void LoadNotificationHeadContents(string headContents){
form.InvokeAsyncSafe(() => {
TweetNotification.SetHeadTag(headContents);
});
}
public void SetLastRightClickedLink(string link){
form.InvokeAsyncSafe(() => LastRightClickedLink = link);
}
public void SetLastHighlightedTweet(string link, string quotedLink){
form.InvokeAsyncSafe(() => {
LastHighlightedTweet = link;
LastHighlightedQuotedTweet = quotedLink;
});
}
public void OpenContextMenu(){
form.InvokeAsyncSafe(form.OpenContextMenu);
}
public void OnTweetPopup(string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl){
notification.InvokeAsyncSafe(() => {
form.OnTweetNotification();
notification.ShowNotification(new TweetNotification(columnName, tweetHtml, tweetCharacters, tweetUrl, quoteUrl));
});
}
public void OnTweetSound(){
form.InvokeAsyncSafe(() => {
form.OnTweetNotification();
form.PlayNotificationSound();
});
}
public void DisplayTooltip(string text, bool showInNotification){
if (showInNotification){
notification.InvokeAsyncSafe(() => notification.DisplayTooltip(text));
}
else{
form.InvokeAsyncSafe(() => form.DisplayTooltip(text));
}
}
public void LoadNextNotification(){
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
}
public void ScreenshotTweet(string html, int width, int height){
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width, height));
}
public void FixClipboard(){
form.InvokeAsyncSafe(WindowsUtils.ClipboardStripHtmlStyles);
}
public void OpenBrowser(string url){
BrowserUtils.OpenExternalBrowser(url);
}
public void Alert(string type, string contents){
MessageBoxIcon icon;
switch(type){
case "error": icon = MessageBoxIcon.Error; break;
case "warning": icon = MessageBoxIcon.Warning; break;
case "info": icon = MessageBoxIcon.Information; break;
default: icon = MessageBoxIcon.None; break;
}
MessageBox.Show(contents, Program.BrandName+" Browser Message", MessageBoxButtons.OK, icon);
}
public void Log(string data){
System.Diagnostics.Debug.WriteLine(data);
}
}
}

View File

@@ -1,9 +1,13 @@
using System;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using TweetDck.Core.Utils;
namespace TweetDck.Core.Controls{
static class ControlExtensions{
public static readonly Point InvisibleLocation = new Point(-32000, -32000);
public static void InvokeSafe(this Control control, Action func){
if (control.InvokeRequired){
control.Invoke(func);
@@ -13,6 +17,14 @@ namespace TweetDck.Core.Controls{
}
}
public static void InvokeAsyncSafe(this Control control, Action func){
control.BeginInvoke(func);
}
public static bool IsFullyOutsideView(this Form form){
return !Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(form.Bounds));
}
public static void MoveToCenter(this Form targetForm, Form parentForm){
targetForm.Location = new Point(parentForm.Location.X+parentForm.Width/2-targetForm.Width/2, parentForm.Location.Y+parentForm.Height/2-targetForm.Height/2);
}
@@ -34,5 +46,21 @@ namespace TweetDck.Core.Controls{
trackBar.Value = value;
}
}
public static void SetElevated(this Button button){
button.Text = " "+button.Text;
button.FlatStyle = FlatStyle.System;
NativeMethods.SendMessage(button.Handle, NativeMethods.BCM_SETSHIELD, 0, new IntPtr(1));
}
public static void EnableMultilineShortcuts(this TextBox textBox){
textBox.KeyDown += (sender, args) => {
if (args.Control && args.KeyCode == Keys.A){
((TextBox)sender).SelectAll();
args.SuppressKeyPress = true;
args.Handled = true;
}
};
}
}
}

View File

@@ -3,11 +3,7 @@ using System.Windows.Forms;
namespace TweetDck.Core.Controls{
class FlatButton : Button{
protected override bool ShowFocusCues{
get{
return false;
}
}
protected override bool ShowFocusCues => false;
public FlatButton(){
GotFocus += FlatButton_GotFocus;

View File

@@ -1,3 +0,0 @@
namespace TweetDck.Core.Controls{
partial class FlatProgressBar{}
}

View File

@@ -2,7 +2,7 @@
using System.Windows.Forms;
namespace TweetDck.Core.Controls{
sealed partial class FlatProgressBar : ProgressBar{
sealed class FlatProgressBar : ProgressBar{
private readonly SolidBrush brush;
public FlatProgressBar(){

View File

@@ -1,3 +0,0 @@
namespace TweetDck.Core.Controls{
partial class TabButton{}
}

View File

@@ -3,7 +3,7 @@ using System.Drawing;
using System.Windows.Forms;
namespace TweetDck.Core.Controls{
sealed partial class TabButton : FlatButton{
sealed class TabButton : FlatButton{
public Action Callback { get; private set; }
public void SetupButton(int locationX, int sizeWidth, string title, Action callback){

View File

@@ -6,21 +6,11 @@ using System.Windows.Forms;
namespace TweetDck.Core.Controls{
sealed partial class TabPanel : UserControl{
public IEnumerable<TabButton> Buttons{
get{
return panelButtons.Controls.Cast<TabButton>();
}
}
public Panel Content => panelContent;
public IEnumerable<TabButton> Buttons => panelButtons.Controls.Cast<TabButton>();
public TabButton ActiveButton { get; private set; }
// ReSharper disable once ConvertToAutoPropertyWithPrivateSetter
public Panel Content{
get{
return panelContent;
}
}
private int btnWidth;
public TabPanel(){

View File

@@ -24,21 +24,18 @@
/// </summary>
private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
this.trayIcon = new TweetDck.Core.TrayIcon();
this.trayIcon = new TweetDck.Core.TrayIcon(this.components);
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.SuspendLayout();
//
// trayIcon
//
this.trayIcon.Visible = false;
//
// FormBrowser
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = TweetDck.Core.Utils.BrowserUtils.BackgroundColor;
this.ClientSize = new System.Drawing.Size(324, 386);
this.Icon = Properties.Resources.icon;
this.Location = new System.Drawing.Point(-32000, -32000);
this.Location = TweetDck.Core.Controls.ControlExtensions.InvisibleLocation;
this.MinimumSize = new System.Drawing.Size(340, 424);
this.Name = "FormBrowser";
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;

View File

@@ -1,65 +1,97 @@
using System;
using System.Windows.Forms;
using CefSharp;
using CefSharp;
using CefSharp.WinForms;
using TweetDck.Configuration;
using TweetDck.Core.Handling;
using TweetDck.Core.Other;
using TweetDck.Resources;
using TweetDck.Core.Controls;
using System;
using System.Diagnostics;
using System.Drawing;
using TweetDck.Updates;
using System.Linq;
using System.Windows.Forms;
using TweetDck.Configuration;
using TweetDck.Core.Bridge;
using TweetDck.Core.Controls;
using TweetDck.Core.Handling;
using TweetDck.Core.Notification;
using TweetDck.Core.Notification.Screenshot;
using TweetDck.Core.Notification.Sound;
using TweetDck.Core.Other;
using TweetDck.Core.Utils;
using TweetDck.Plugins;
using TweetDck.Plugins.Enums;
using TweetDck.Plugins.Events;
using TweetDck.Resources;
using TweetDck.Updates;
using TweetDck.Updates.Events;
namespace TweetDck.Core{
sealed partial class FormBrowser : Form{
private static UserConfig Config{
get{
return Program.UserConfig;
}
}
private static UserConfig Config => Program.UserConfig;
public string UpdateInstallerPath { get; private set; }
private readonly ChromiumWebBrowser browser;
private readonly PluginManager plugins;
private readonly UpdateHandler updates;
private readonly FormNotificationTweet notification;
private readonly ContextMenu contextMenu;
private FormSettings currentFormSettings;
private FormAbout currentFormAbout;
private FormPlugins currentFormPlugins;
private bool isLoaded;
private bool isBrowserReady;
private FormWindowState prevState;
public FormBrowser(PluginManager pluginManager){
private TweetScreenshotManager notificationScreenshotManager;
private ISoundNotificationPlayer soundNotification;
public FormBrowser(PluginManager pluginManager, UpdaterSettings updaterSettings){
InitializeComponent();
Text = Program.BrandName;
this.plugins = pluginManager;
this.plugins.Reloaded += plugins_Reloaded;
this.plugins.Config.PluginChangedState += plugins_PluginChangedState;
this.plugins.PluginChangedState += plugins_PluginChangedState;
FormNotification notification = CreateNotificationForm(true);
notification.CanMoveWindow = () => false;
notification.Show();
this.contextMenu = ContextMenuBrowser.CreateMenu(this);
this.notification = new FormNotificationTweet(this, plugins){
#if DEBUG
CanMoveWindow = () => (ModifierKeys & Keys.Alt) == Keys.Alt
#else
CanMoveWindow = () => false
#endif
};
this.notification.Show();
this.browser = new ChromiumWebBrowser("https://tweetdeck.twitter.com/"){
MenuHandler = new ContextMenuBrowser(this),
DialogHandler = new DialogHandlerBrowser(this),
JsDialogHandler = new JavaScriptDialogHandler(),
LifeSpanHandler = new LifeSpanHandler()
};
this.browser.LoadingStateChanged += Browser_LoadingStateChanged;
this.browser.FrameLoadEnd += Browser_FrameLoadEnd;
this.browser.RegisterJsObject("$TD", new TweetDeckBridge(this, notification));
this.browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
#if DEBUG
this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
#endif
this.browser.LoadingStateChanged += browser_LoadingStateChanged;
this.browser.FrameLoadStart += browser_FrameLoadStart;
this.browser.FrameLoadEnd += browser_FrameLoadEnd;
this.browser.LoadError += browser_LoadError;
this.browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(this, notification));
this.browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
browser.BrowserSettings.BackgroundColor = (uint)BrowserUtils.BackgroundColor.ToArgb();
browser.Dock = DockStyle.None;
browser.Location = ControlExtensions.InvisibleLocation;
Controls.Add(browser);
Disposed += (sender, args) => browser.Dispose();
Controls.Add(new MenuStrip{ Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update
Disposed += (sender, args) => {
browser.Dispose();
contextMenu.Dispose();
notificationScreenshotManager?.Dispose();
soundNotification?.Dispose();
};
this.trayIcon.ClickRestore += trayIcon_ClickRestore;
this.trayIcon.ClickClose += trayIcon_ClickClose;
@@ -67,54 +99,110 @@ namespace TweetDck.Core{
UpdateTrayIcon();
this.updates = new UpdateHandler(browser, this);
Config.MuteToggled += Config_MuteToggled;
Config.ZoomLevelChanged += Config_ZoomLevelChanged;
this.updates = new UpdateHandler(browser, this, updaterSettings);
this.updates.UpdateAccepted += updates_UpdateAccepted;
this.updates.UpdateDismissed += updates_UpdateDismissed;
RestoreWindow();
}
private bool TryBringToFront<T>() where T : Form{
T form = Application.OpenForms.OfType<T>().FirstOrDefault();
if (form != null){
form.BringToFront();
return true;
}
else return false;
}
private void ShowChildForm(Form form){
form.VisibleChanged += (sender, args) => form.MoveToCenter(this);
form.Show(this);
form.Shown += (sender, args) => form.MoveToCenter(this);
}
private void ForceClose(){
public void ForceClose(){
trayIcon.Visible = false; // checked in FormClosing event
Close();
}
public FormNotification CreateNotificationForm(bool autoHide){
return new FormNotification(this, plugins, autoHide);
}
// window setup
private void SetupWindow(){
private void RestoreWindow(){
Config.BrowserWindow.Restore(this, true);
prevState = WindowState;
isLoaded = true;
}
private void OnBrowserReady(){
if (!isBrowserReady){
browser.Location = Point.Empty;
browser.Dock = DockStyle.Fill;
isBrowserReady = true;
}
}
private void UpdateTrayIcon(){
trayIcon.Visible = Config.TrayBehavior.ShouldDisplayIcon();
}
// active event handlers
private void Browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
if (!e.IsLoading){
Invoke(new Action(SetupWindow));
browser.LoadingStateChanged -= Browser_LoadingStateChanged;
foreach(string word in BrowserUtils.DictionaryWords){
browser.AddWordToDictionary(word);
}
BeginInvoke(new Action(OnBrowserReady));
browser.LoadingStateChanged -= browser_LoadingStateChanged;
}
}
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){
if (e.Frame.IsMain){
if (Config.ZoomLevel != 100){
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Config.ZoomLevel);
}
if (BrowserUtils.IsTwitterWebsite(e.Frame)){
ScriptLoader.ExecuteFile(e.Frame, "twitter.js");
}
}
}
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
if (e.Frame.IsMain && BrowserUtils.IsTweetDeckWebsite(e.Frame)){
e.Frame.ExecuteJavaScriptAsync(BrowserUtils.BackgroundColorFix);
UpdateProperties(PropertyBridge.Properties.AllBrowser);
ScriptLoader.ExecuteFile(e.Frame, "code.js");
ReinjectCustomCSS(Config.CustomBrowserCSS);
if (plugins.HasAnyPlugin(PluginEnvironment.Browser)){
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginBrowserScriptFile);
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginGlobalScriptFile);
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Browser, true);
}
TweetDeckBridge.ResetStaticProperties();
}
}
private void browser_LoadError(object sender, LoadErrorEventArgs e){
if (e.ErrorCode == CefErrorCode.Aborted){
return;
}
if (!e.FailedUrl.StartsWith("http://td/")){
string errorPage = ScriptLoader.LoadResource("pages/error.html", true);
if (errorPage != null){
browser.LoadHtml(errorPage.Replace("{err}", BrowserUtils.GetErrorName(e.ErrorCode)), "http://td/error");
}
}
}
@@ -144,7 +232,7 @@ namespace TweetDck.Core{
private void FormBrowser_ResizeEnd(object sender, EventArgs e){ // also triggers when the window moves
if (!isLoaded)return;
if (Location.X != -32000){
if (Location != ControlExtensions.InvisibleLocation){
Config.BrowserWindow.Save(this);
Config.Save();
}
@@ -159,34 +247,35 @@ namespace TweetDck.Core{
}
}
private void Config_MuteToggled(object sender, EventArgs e){
UpdateProperties(PropertyBridge.Properties.MuteNotifications);
}
private void Config_ZoomLevelChanged(object sender, EventArgs e){
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Config.ZoomLevel);
}
private void Config_TrayBehaviorChanged(object sender, EventArgs e){
if (!isLoaded)return;
UpdateTrayIcon();
}
private void trayIcon_ClickRestore(object sender, EventArgs e){
if (!isLoaded)return;
isLoaded = false;
Show();
SetupWindow();
RestoreWindow();
Activate();
UpdateTrayIcon();
}
private void trayIcon_ClickClose(object sender, EventArgs e){
if (!isLoaded)return;
ForceClose();
}
private void plugins_Reloaded(object sender, PluginLoadEventArgs e){
ReloadBrowser();
private void plugins_Reloaded(object sender, PluginErrorEventArgs e){
browser.GetBrowser().Reload();
}
private void plugins_PluginChangedState(object sender, PluginChangedStateEventArgs e){
browser.ExecuteScriptAsync("window.TDPF_setPluginState", e.Plugin, e.IsEnabled ? 1 : 0); // ExecuteScriptAsync cannot handle boolean values as of yet
browser.ExecuteScriptAsync("window.TDPF_setPluginState", e.Plugin, e.IsEnabled);
}
private void updates_UpdateAccepted(object sender, UpdateAcceptedEventArgs e){
@@ -195,6 +284,7 @@ namespace TweetDck.Core{
FormUpdateDownload downloadForm = new FormUpdateDownload(e.UpdateInfo);
downloadForm.MoveToCenter(this);
downloadForm.ShowDialog();
downloadForm.Dispose();
if (downloadForm.UpdateStatus == FormUpdateDownload.Status.Succeeded){
UpdateInstallerPath = downloadForm.InstallerPath;
@@ -208,13 +298,39 @@ namespace TweetDck.Core{
}
}
private void updates_UpdateDismissed(object sender, UpdateDismissedEventArgs e){
Config.DismissedUpdate = e.VersionTag;
Config.Save();
}
private void soundNotification_PlaybackError(object sender, PlaybackErrorEventArgs e){
e.Ignore = true;
using(FormMessage form = new FormMessage("Notification Sound Error", "Could not play custom notification sound."+Environment.NewLine+e.Message, MessageBoxIcon.Error)){
form.CancelButton = form.AddButton("Ignore");
Button btnOpenSettings = form.AddButton("Open Settings");
btnOpenSettings.Width += 16;
btnOpenSettings.Location = new Point(btnOpenSettings.Location.X-16, btnOpenSettings.Location.Y);
if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnOpenSettings){
OpenSettings(FormSettings.TabIndexNotification);
}
}
}
protected override void WndProc(ref Message m){
if (isLoaded && m.Msg == Program.WindowRestoreMessage){
trayIcon_ClickRestore(trayIcon, new EventArgs());
using(Process process = Process.GetCurrentProcess()){
if (process.Id == m.WParam.ToInt32()){
trayIcon_ClickRestore(trayIcon, new EventArgs());
}
}
return;
}
if (isLoaded && m.Msg == 0x210 && (m.WParam.ToInt32() & 0xFFFF) == 0x020B){ // WM_PARENTNOTIFY, WM_XBUTTONDOWN
if (isBrowserReady && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN){
browser.ExecuteScriptAsync("TDGF_onMouseClickExtra", (m.WParam.ToInt32() >> 16) & 0xFFFF);
return;
}
@@ -222,63 +338,107 @@ namespace TweetDck.Core{
base.WndProc(ref m);
}
// notification helpers
public FormNotificationMain CreateNotificationForm(bool enableContextMenu){
return new FormNotificationMain(this, plugins, enableContextMenu);
}
public void PauseNotification(){
notification.PauseNotification();
}
public void ResumeNotification(){
notification.ResumeNotification();
}
// javascript calls
public void ReinjectCustomCSS(string css){
browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty);
}
public void UpdateProperties(PropertyBridge.Properties properties){
browser.ExecuteScriptAsync(PropertyBridge.GenerateScript(properties));
}
public void ReloadToTweetDeck(){
browser.ExecuteScriptAsync("window.location.href = 'https://tweetdeck.twitter.com'");
}
// callback handlers
public void OpenContextMenu(){
contextMenu.Show(this, PointToClient(Cursor.Position));
}
public void OpenSettings(){
if (currentFormSettings != null){
currentFormSettings.BringToFront();
}
else{
OpenSettings(0);
}
public void OpenSettings(int tabIndex){
if (!TryBringToFront<FormSettings>()){
bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
currentFormSettings = new FormSettings(this, plugins, updates);
currentFormSettings.FormClosed += (sender, args) => {
currentFormSettings = null;
FormSettings form = new FormSettings(this, plugins, updates, tabIndex);
form.FormClosed += (sender, args) => {
if (!prevEnableUpdateCheck && Config.EnableUpdateCheck){
Config.DismissedUpdate = string.Empty;
Config.Save();
updates.DismissUpdate(string.Empty);
updates.Check(false);
}
if (!Config.EnableTrayHighlight){
trayIcon.HasNotifications = false;
}
UpdateProperties(PropertyBridge.Properties.ExpandLinksOnHover | PropertyBridge.Properties.SwitchAccountSelectors | PropertyBridge.Properties.HasCustomNotificationSound);
form.Dispose();
};
ShowChildForm(currentFormSettings);
ShowChildForm(form);
}
}
public void OpenAbout(){
if (currentFormAbout != null){
currentFormAbout.BringToFront();
}
else{
currentFormAbout = new FormAbout();
currentFormAbout.FormClosed += (sender, args) => currentFormAbout = null;
ShowChildForm(currentFormAbout);
if (!TryBringToFront<FormAbout>()){
ShowChildForm(new FormAbout());
}
}
public void OpenPlugins(){
if (currentFormPlugins != null){
currentFormPlugins.BringToFront();
}
else{
currentFormPlugins = new FormPlugins(plugins);
currentFormPlugins.FormClosed += (sender, args) => currentFormPlugins = null;
ShowChildForm(currentFormPlugins);
if (!TryBringToFront<FormPlugins>()){
ShowChildForm(new FormPlugins(plugins));
}
}
public void OnTweetNotification(){
public void OnTweetNotification(){ // may be called multiple times, once for each type of notification
if (Config.EnableTrayHighlight && !ContainsFocus){
trayIcon.HasNotifications = true;
}
}
public void PlayNotificationSound(){
if (Config.NotificationSoundPath.Length == 0){
return;
}
if (soundNotification == null){
soundNotification = SoundNotification.New();
soundNotification.PlaybackError += soundNotification_PlaybackError;
}
soundNotification.Play(Config.NotificationSoundPath);
}
public void OnTweetScreenshotReady(string html, int width, int height){
if (notificationScreenshotManager == null){
notificationScreenshotManager = new TweetScreenshotManager(this);
}
notificationScreenshotManager.Trigger(html, width, height);
}
public void DisplayTooltip(string text){
if (string.IsNullOrEmpty(text)){
toolTip.Hide(this);
@@ -290,16 +450,8 @@ namespace TweetDck.Core{
}
}
public void OnImagePasted(){
browser.ExecuteScriptAsync("TDGF_tryPasteImage", new object[0]);
}
public void OnImagePastedFinish(){
browser.ExecuteScriptAsync("TDGF_tryPasteImageFinish", new object[0]);
}
public void ReloadBrowser(){
browser.ExecuteScriptAsync("window.location.reload()");
public void TriggerTweetScreenshot(){
browser.ExecuteScriptAsync("TDGF_triggerScreenshot()");
}
}
}
}

View File

@@ -1,367 +0,0 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using CefSharp;
using CefSharp.WinForms;
using TweetDck.Configuration;
using TweetDck.Core.Handling;
using TweetDck.Resources;
using TweetDck.Core.Utils;
using TweetDck.Plugins;
namespace TweetDck.Core{
sealed partial class FormNotification : Form{
private const string NotificationScriptFile = "notification.js";
private static readonly string NotificationScriptIdentifier = ScriptLoader.GetRootIdentifier(NotificationScriptFile);
private static readonly string PluginScriptIdentifier = ScriptLoader.GetRootIdentifier(PluginManager.PluginNotificationScriptFile);
public Func<bool> CanMoveWindow = () => true;
private readonly Form owner;
private readonly PluginManager plugins;
private readonly ChromiumWebBrowser browser;
private readonly Queue<TweetNotification> tweetQueue = new Queue<TweetNotification>(4);
private readonly bool autoHide;
private int timeLeft, totalTime;
private readonly NativeMethods.HookProc mouseHookDelegate;
private IntPtr mouseHook;
private bool? prevDisplayTimer;
private int? prevFontSize;
private bool RequiresResize{
get{
return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Program.UserConfig.DisplayNotificationTimer || prevFontSize != TweetNotification.FontSizeLevel;
}
set{
if (value){
prevDisplayTimer = null;
prevFontSize = null;
}
else{
prevDisplayTimer = Program.UserConfig.DisplayNotificationTimer;
prevFontSize = TweetNotification.FontSizeLevel;
}
}
}
private readonly string notificationJS;
private readonly string pluginJS;
protected override bool ShowWithoutActivation{
get{
return true;
}
}
public bool FreezeTimer { get; set; }
public bool ContextMenuOpen { get; set; }
public string CurrentUrl { get; private set; }
public EventHandler Initialized;
private bool isInitialized;
private static int BaseClientWidth{
get{
int level = TweetNotification.FontSizeLevel;
return level == 0 ? 284 : (int)Math.Round(284.0*(1.0+0.05*level));
}
}
private static int BaseClientHeight{
get{
int level = TweetNotification.FontSizeLevel;
return level == 0 ? 118 : (int)Math.Round(118.0*(1.0+0.075*level));
}
}
public FormNotification(FormBrowser owner, PluginManager pluginManager, bool autoHide){
InitializeComponent();
Text = Program.BrandName;
this.owner = owner;
this.plugins = pluginManager;
this.autoHide = autoHide;
owner.FormClosed += (sender, args) => Close();
notificationJS = ScriptLoader.LoadResource(NotificationScriptFile);
pluginJS = ScriptLoader.LoadResource(PluginManager.PluginNotificationScriptFile);
browser = new ChromiumWebBrowser("about:blank"){
MenuHandler = new ContextMenuNotification(this, autoHide),
LifeSpanHandler = new LifeSpanHandler()
};
browser.IsBrowserInitializedChanged += Browser_IsBrowserInitializedChanged;
browser.FrameLoadEnd += Browser_FrameLoadEnd;
browser.RegisterJsObject("$TD", new TweetDeckBridge(owner, this));
browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
panelBrowser.Controls.Add(browser);
if (autoHide){
Program.UserConfig.MuteToggled += Config_MuteToggled;
Disposed += (sender, args) => Program.UserConfig.MuteToggled -= Config_MuteToggled;
}
mouseHookDelegate = MouseHookProc;
Disposed += FormNotification_Disposed;
}
protected override void WndProc(ref Message m){
if (m.Msg == 0x0112 && (m.WParam.ToInt32() & 0xFFF0) == 0xF010 && !CanMoveWindow()){ // WM_SYSCOMMAND, SC_MOVE
return;
}
base.WndProc(ref m);
}
// mouse wheel hook
private void StartMouseHook(){
if (mouseHook == IntPtr.Zero){
mouseHook = NativeMethods.SetWindowsHookEx(NativeMethods.WH_MOUSE_LL, mouseHookDelegate, IntPtr.Zero, 0);
}
}
private void StopMouseHook(){
if (mouseHook != IntPtr.Zero){
NativeMethods.UnhookWindowsHookEx(mouseHook);
mouseHook = IntPtr.Zero;
}
}
private IntPtr MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam){
if (!ContainsFocus && wParam.ToInt32() == NativeMethods.WH_MOUSEWHEEL && browser.Bounds.Contains(PointToClient(Cursor.Position))){
// fuck it, Activate() doesn't work with this
Point prevPos = Cursor.Position;
Cursor.Position = PointToScreen(new Point(0, -1));
NativeMethods.SimulateMouseClick(NativeMethods.MouseButton.Left);
Cursor.Position = prevPos;
}
return NativeMethods.CallNextHookEx(mouseHook, nCode, wParam, lParam);
}
// event handlers
private void timerHideProgress_Tick(object sender, EventArgs e){
if (Bounds.Contains(Cursor.Position) || FreezeTimer || ContextMenuOpen)return;
timeLeft -= timerProgress.Interval;
int value = (int)Math.Round(1025.0*(totalTime-timeLeft)/totalTime);
progressBarTimer.SetValueInstant(Math.Min(1000, Math.Max(0, Program.UserConfig.NotificationTimerCountDown ? 1000-value : value)));
if (timeLeft <= 0){
FinishCurrentTweet();
}
}
private void Config_MuteToggled(object sender, EventArgs e){
if (Program.UserConfig.MuteNotifications){
HideNotification(true);
}
else if (tweetQueue.Count > 0){
LoadNextNotification();
}
}
private void Browser_IsBrowserInitializedChanged(object sender, IsBrowserInitializedChangedEventArgs e){
if (e.IsBrowserInitialized && Initialized != null){
Initialized(this, new EventArgs());
}
}
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
if (!e.Frame.IsMain)return;
if (!isInitialized && !Program.UserConfig.NotificationLegacyLoad){
isInitialized = true;
if (Initialized != null){
Initialized(this, new EventArgs());
}
}
else if (notificationJS != null && browser.Address != "about:blank"){
ScriptLoader.ExecuteScript(e.Frame, notificationJS, NotificationScriptIdentifier);
if (plugins.HasAnyPlugin(PluginEnvironment.Notification)){
ScriptLoader.ExecuteScript(e.Frame, pluginJS, PluginScriptIdentifier);
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginGlobalScriptFile);
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Notification, false);
}
}
}
private void FormNotification_FormClosing(object sender, FormClosingEventArgs e){
if (e.CloseReason == CloseReason.UserClosing){
HideNotification(false);
tweetQueue.Clear();
e.Cancel = true;
}
}
private void FormNotification_Disposed(object sender, EventArgs e){
browser.Dispose();
StopMouseHook();
}
// notification methods
public void ShowNotification(TweetNotification notification){
if (Program.UserConfig.MuteNotifications){
tweetQueue.Enqueue(notification);
}
else{
tweetQueue.Enqueue(notification);
UpdateTitle();
if (!timerProgress.Enabled){
LoadNextNotification();
}
}
}
public void ShowNotificationForSettings(bool reset){
if (reset){
LoadTweet(TweetNotification.ExampleTweet);
}
else{
MoveToVisibleLocation();
}
}
public void HideNotification(bool loadBlank){
if (loadBlank || Program.UserConfig.NotificationLegacyLoad){
browser.LoadHtml("", "about:blank");
}
Location = new Point(-32000, -32000);
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0;
timerProgress.Stop();
StopMouseHook();
}
public void OnNotificationReady(){
UpdateTitle();
MoveToVisibleLocation();
timerProgress.Start();
}
public void FinishCurrentTweet(){
if (tweetQueue.Count > 0){
LoadNextNotification();
}
else if (autoHide){
HideNotification(true);
}
else{
timerProgress.Stop();
}
}
private void LoadNextNotification(){
LoadTweet(tweetQueue.Dequeue());
}
private void LoadTweet(TweetNotification tweet){
CurrentUrl = tweet.Url;
timerProgress.Stop();
totalTime = timeLeft = tweet.GetDisplayDuration(Program.UserConfig.NotificationDurationValue);
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0;
browser.LoadHtml(tweet.GenerateHtml(), "http://tweetdeck.twitter.com/?"+DateTime.Now.Ticks);
if (Program.UserConfig.NotificationLegacyLoad){
OnNotificationReady();
}
}
private void MoveToVisibleLocation(){
UserConfig config = Program.UserConfig;
if (RequiresResize){
RequiresResize = false;
if (config.DisplayNotificationTimer){
ClientSize = new Size(BaseClientWidth, BaseClientHeight+4);
progressBarTimer.Visible = true;
}
else{
ClientSize = new Size(BaseClientWidth, BaseClientHeight);
progressBarTimer.Visible = false;
}
panelBrowser.Height = BaseClientHeight;
}
Screen screen = Screen.FromControl(owner);
if (config.NotificationDisplay > 0 && config.NotificationDisplay <= Screen.AllScreens.Length){
screen = Screen.AllScreens[config.NotificationDisplay-1];
}
bool needsReactivating = Location.X == -32000;
int edgeDist = config.NotificationEdgeDistance;
switch(config.NotificationPosition){
case TweetNotification.Position.TopLeft:
Location = new Point(screen.WorkingArea.X+edgeDist, screen.WorkingArea.Y+edgeDist);
break;
case TweetNotification.Position.TopRight:
Location = new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+edgeDist);
break;
case TweetNotification.Position.BottomLeft:
Location = new Point(screen.WorkingArea.X+edgeDist, screen.WorkingArea.Y+screen.WorkingArea.Height-edgeDist-Height);
break;
case TweetNotification.Position.BottomRight:
Location = new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+screen.WorkingArea.Height-edgeDist-Height);
break;
case TweetNotification.Position.Custom:
if (!config.IsCustomNotificationPositionSet){
config.CustomNotificationPosition = new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+edgeDist);
config.Save();
}
Location = config.CustomNotificationPosition;
break;
}
if (needsReactivating){
NativeMethods.SetFormPos(this, NativeMethods.HWND_TOPMOST, NativeMethods.SWP_NOACTIVATE);
}
StartMouseHook();
}
private void UpdateTitle(){
Text = tweetQueue.Count > 0 ? Program.BrandName+" ("+tweetQueue.Count+" more left)" : Program.BrandName;
}
public void DisplayTooltip(string text){
if (string.IsNullOrEmpty(text)){
toolTip.Hide(this);
}
else{
Point position = PointToClient(Cursor.Position);
position.Offset(20, 5);
toolTip.Show(text, this, position);
}
}
}
}

View File

@@ -0,0 +1,15 @@
using CefSharp;
using System;
namespace TweetDck.Core.Handling{
class BrowserProcessHandler : IBrowserProcessHandler{
void IBrowserProcessHandler.OnContextInitialized(){
using(IRequestContext ctx = Cef.GetGlobalRequestContext()){
ctx.SetPreference("browser.enable_spellchecking", Program.UserConfig.EnableSpellCheck, out string _);
}
}
void IBrowserProcessHandler.OnScheduleMessagePumpWork(long delay){}
void IDisposable.Dispose(){}
}
}

View File

@@ -1,21 +1,49 @@
using CefSharp;
using System;
using System.IO;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using TweetDck.Core.Bridge;
using TweetDck.Core.Controls;
using TweetDck.Core.Utils;
namespace TweetDck.Core.Handling{
abstract class ContextMenuBase : IContextMenuHandler{
private static readonly Regex RegexTwitterAccount = new Regex(@"^https?://twitter\.com/([^/]+)/?$", RegexOptions.Compiled);
private const int MenuOpenLinkUrl = 26500;
private const int MenuCopyLinkUrl = 26501;
private const int MenuOpenImage = 26502;
private const int MenuSaveImage = 26503;
private const int MenuCopyImageUrl = 26504;
private const int MenuCopyUsername = 26502;
private const int MenuOpenImage = 26503;
private const int MenuSaveImage = 26504;
private const int MenuCopyImageUrl = 26505;
#if DEBUG
private const int MenuOpenDevTools = 26599;
protected void AddDebugMenuItems(IMenuModel model){
model.AddItem((CefMenuCommand)MenuOpenDevTools, "Open dev tools");
}
#endif
private readonly Form form;
protected ContextMenuBase(Form form){
this.form = form;
}
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && !parameters.UnfilteredLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal)){
model.AddItem((CefMenuCommand)MenuOpenLinkUrl, "Open link in browser");
model.AddItem((CefMenuCommand)MenuCopyLinkUrl, "Copy link address");
if (RegexTwitterAccount.IsMatch(parameters.UnfilteredLinkUrl)){
model.AddItem((CefMenuCommand)MenuOpenLinkUrl, "Open account in browser");
model.AddItem((CefMenuCommand)MenuCopyLinkUrl, "Copy account address");
model.AddItem((CefMenuCommand)MenuCopyUsername, "Copy account username");
}
else{
model.AddItem((CefMenuCommand)MenuOpenLinkUrl, "Open link in browser");
model.AddItem((CefMenuCommand)MenuCopyLinkUrl, "Copy link address");
}
model.AddSeparator();
}
@@ -34,7 +62,7 @@ namespace TweetDck.Core.Handling{
break;
case MenuCopyLinkUrl:
Clipboard.SetText(string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedLink) ? parameters.UnfilteredLinkUrl : TweetDeckBridge.LastRightClickedLink, TextDataFormat.UnicodeText);
SetClipboardText(string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedLink) ? parameters.UnfilteredLinkUrl : TweetDeckBridge.LastRightClickedLink);
break;
case MenuOpenImage:
@@ -65,8 +93,19 @@ namespace TweetDck.Core.Handling{
break;
case MenuCopyImageUrl:
Clipboard.SetText(parameters.SourceUrl, TextDataFormat.UnicodeText);
SetClipboardText(parameters.SourceUrl);
break;
case MenuCopyUsername:
Match match = RegexTwitterAccount.Match(parameters.UnfilteredLinkUrl);
SetClipboardText(match.Success ? match.Groups[1].Value : parameters.UnfilteredLinkUrl);
break;
#if DEBUG
case MenuOpenDevTools:
browserControl.ShowDevTools();
break;
#endif
}
return false;
@@ -78,6 +117,10 @@ namespace TweetDck.Core.Handling{
return false;
}
protected void SetClipboardText(string text){
form.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText));
}
protected static void RemoveSeparatorIfLast(IMenuModel model){
if (model.Count > 0 && model.GetTypeAt(model.Count-1) == MenuItemType.Separator){
model.RemoveAt(model.Count-1);

View File

@@ -1,5 +1,6 @@
using CefSharp;
using System.Windows.Forms;
using TweetDck.Core.Bridge;
using TweetDck.Core.Controls;
using TweetDck.Core.Utils;
@@ -15,10 +16,20 @@ namespace TweetDck.Core.Handling{
private const int MenuCopyTweetUrl = 26611;
private const int MenuOpenQuotedTweetUrl = 26612;
private const int MenuCopyQuotedTweetUrl = 26613;
private const int MenuScreenshotTweet = 26614;
private const string TitleReloadBrowser = "Reload browser";
private const string TitleMuteNotifications = "Mute notifications";
private const string TitleSettings = "Settings";
private const string TitlePlugins = "Plugins";
private const string TitleAboutProgram = "About "+Program.BrandName;
private readonly FormBrowser form;
public ContextMenuBrowser(FormBrowser form){
private string lastHighlightedTweet;
private string lastHighlightedQuotedTweet;
public ContextMenuBrowser(FormBrowser form) : base(form){
this.form = form;
}
@@ -29,13 +40,26 @@ namespace TweetDck.Core.Handling{
model.Remove(CefMenuCommand.ViewSource);
RemoveSeparatorIfLast(model);
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){
model.AddSeparator();
}
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
if (!string.IsNullOrEmpty(TweetDeckBridge.LastHighlightedTweet) && (parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
lastHighlightedTweet = TweetDeckBridge.LastHighlightedTweet;
lastHighlightedQuotedTweet = TweetDeckBridge.LastHighlightedQuotedTweet;
if (!BrowserUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
lastHighlightedTweet = string.Empty;
lastHighlightedQuotedTweet = string.Empty;
}
if (!string.IsNullOrEmpty(lastHighlightedTweet) && (parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
model.AddItem((CefMenuCommand)MenuOpenTweetUrl, "Open tweet in browser");
model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address");
model.AddItem((CefMenuCommand)MenuScreenshotTweet, "Screenshot tweet to clipboard");
if (!string.IsNullOrEmpty(TweetDeckBridge.LastHighlightedQuotedTweet)){
if (!string.IsNullOrEmpty(lastHighlightedQuotedTweet)){
model.AddSeparator();
model.AddItem((CefMenuCommand)MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
model.AddItem((CefMenuCommand)MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
@@ -49,15 +73,22 @@ namespace TweetDck.Core.Handling{
IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu((CefMenuCommand)MenuGlobal, Program.BrandName);
globalMenu.AddItem(CefMenuCommand.Reload, "Reload browser");
globalMenu.AddCheckItem((CefMenuCommand)MenuMute, "Mute notifications");
globalMenu.AddItem(CefMenuCommand.Reload, TitleReloadBrowser);
globalMenu.AddCheckItem((CefMenuCommand)MenuMute, TitleMuteNotifications);
globalMenu.SetChecked((CefMenuCommand)MenuMute, Program.UserConfig.MuteNotifications);
globalMenu.AddSeparator();
globalMenu.AddItem((CefMenuCommand)MenuSettings, "Settings");
globalMenu.AddItem((CefMenuCommand)MenuPlugins, "Plugins");
globalMenu.AddItem((CefMenuCommand)MenuAbout, "About "+Program.BrandName);
globalMenu.AddItem((CefMenuCommand)MenuSettings, TitleSettings);
globalMenu.AddItem((CefMenuCommand)MenuPlugins, TitlePlugins);
globalMenu.AddItem((CefMenuCommand)MenuAbout, TitleAboutProgram);
#if DEBUG
globalMenu.AddSeparator();
AddDebugMenuItems(globalMenu);
#endif
}
RemoveSeparatorIfLast(model);
}
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
@@ -67,47 +98,69 @@ namespace TweetDck.Core.Handling{
switch((int)commandId){
case (int)CefMenuCommand.Reload:
frame.ExecuteJavaScriptAsync("window.location.href = 'https://tweetdeck.twitter.com'");
form.InvokeAsyncSafe(form.ReloadToTweetDeck);
return true;
case MenuSettings:
form.InvokeSafe(form.OpenSettings);
form.InvokeAsyncSafe(form.OpenSettings);
return true;
case MenuAbout:
form.InvokeSafe(form.OpenAbout);
form.InvokeAsyncSafe(form.OpenAbout);
return true;
case MenuPlugins:
form.InvokeSafe(form.OpenPlugins);
form.InvokeAsyncSafe(form.OpenPlugins);
return true;
case MenuMute:
form.InvokeSafe(() => {
Program.UserConfig.MuteNotifications = !Program.UserConfig.MuteNotifications;
Program.UserConfig.Save();
});
form.InvokeAsyncSafe(ToggleMuteNotifications);
return true;
case MenuOpenTweetUrl:
BrowserUtils.OpenExternalBrowser(TweetDeckBridge.LastHighlightedTweet);
BrowserUtils.OpenExternalBrowser(lastHighlightedTweet);
return true;
case MenuCopyTweetUrl:
Clipboard.SetText(TweetDeckBridge.LastHighlightedTweet, TextDataFormat.UnicodeText);
SetClipboardText(lastHighlightedTweet);
return true;
case MenuScreenshotTweet:
form.InvokeAsyncSafe(form.TriggerTweetScreenshot);
return true;
case MenuOpenQuotedTweetUrl:
BrowserUtils.OpenExternalBrowser(TweetDeckBridge.LastHighlightedQuotedTweet);
BrowserUtils.OpenExternalBrowser(lastHighlightedQuotedTweet);
return true;
case MenuCopyQuotedTweetUrl:
Clipboard.SetText(TweetDeckBridge.LastHighlightedQuotedTweet, TextDataFormat.UnicodeText);
SetClipboardText(lastHighlightedQuotedTweet);
return true;
}
return false;
}
public static ContextMenu CreateMenu(FormBrowser form){
ContextMenu menu = new ContextMenu();
menu.MenuItems.Add(TitleReloadBrowser, (sender, args) => form.ReloadToTweetDeck());
menu.MenuItems.Add(TitleMuteNotifications, (sender, args) => ToggleMuteNotifications());
menu.MenuItems.Add("-");
menu.MenuItems.Add(TitleSettings, (sender, args) => form.OpenSettings());
menu.MenuItems.Add(TitlePlugins, (sender, args) => form.OpenPlugins());
menu.MenuItems.Add(TitleAboutProgram, (sender, args) => form.OpenAbout());
menu.Popup += (sender, args) => {
menu.MenuItems[1].Checked = Program.UserConfig.MuteNotifications;
};
return menu;
}
private static void ToggleMuteNotifications(){
Program.UserConfig.MuteNotifications = !Program.UserConfig.MuteNotifications;
Program.UserConfig.Save();
}
}
}

View File

@@ -1,18 +1,18 @@
using System.Windows.Forms;
using CefSharp;
using CefSharp;
using TweetDck.Core.Controls;
using TweetDck.Core.Notification;
namespace TweetDck.Core.Handling{
class ContextMenuNotification : ContextMenuBase{
private const int MenuSkipTweet = 26600;
private const int MenuFreeze = 26601;
private const int MenuCopyTweetUrl = 26602;
private const int MenuCopyTweetEmbeddedUrl = 26603;
private const int MenuCopyQuotedTweetUrl = 26603;
private readonly FormNotification form;
private readonly FormNotificationBase form;
private readonly bool enableCustomMenu;
public ContextMenuNotification(FormNotification form, bool enableCustomMenu){
public ContextMenuNotification(FormNotificationBase form, bool enableCustomMenu) : base(form){
this.form = form;
this.enableCustomMenu = enableCustomMenu;
}
@@ -20,27 +20,37 @@ namespace TweetDck.Core.Handling{
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
model.Clear();
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){
model.AddItem(CefMenuCommand.Copy, "Copy");
model.AddSeparator();
}
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
if (enableCustomMenu){
model.AddItem((CefMenuCommand)MenuSkipTweet, "Skip tweet");
model.AddCheckItem((CefMenuCommand)MenuFreeze, "Freeze");
model.SetChecked((CefMenuCommand)MenuFreeze, form.FreezeTimer);
model.AddSeparator();
if (!string.IsNullOrEmpty(form.CurrentUrl)){
if (!string.IsNullOrEmpty(form.CurrentTweetUrl)){
model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address");
if (!string.IsNullOrEmpty(TweetDeckBridge.NotificationTweetEmbedded)){
model.AddItem((CefMenuCommand)MenuCopyTweetEmbeddedUrl, "Copy quoted tweet address");
if (!string.IsNullOrEmpty(form.CurrentQuoteUrl)){
model.AddItem((CefMenuCommand)MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
}
model.AddSeparator();
}
}
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
#if DEBUG
AddDebugMenuItems(model);
#endif
RemoveSeparatorIfLast(model);
form.InvokeSafe(() => form.ContextMenuOpen = true);
form.InvokeAsyncSafe(() => form.ContextMenuOpen = true);
}
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
@@ -50,19 +60,19 @@ namespace TweetDck.Core.Handling{
switch((int)commandId){
case MenuSkipTweet:
form.InvokeSafe(form.FinishCurrentTweet);
form.InvokeAsyncSafe(form.FinishCurrentNotification);
return true;
case MenuFreeze:
form.InvokeSafe(() => form.FreezeTimer = !form.FreezeTimer);
form.InvokeAsyncSafe(() => form.FreezeTimer = !form.FreezeTimer);
return true;
case MenuCopyTweetUrl:
Clipboard.SetText(form.CurrentUrl, TextDataFormat.UnicodeText);
SetClipboardText(form.CurrentTweetUrl);
return true;
case MenuCopyTweetEmbeddedUrl:
Clipboard.SetText(TweetDeckBridge.NotificationTweetEmbedded, TextDataFormat.UnicodeText);
case MenuCopyQuotedTweetUrl:
SetClipboardText(form.CurrentQuoteUrl);
return true;
}
@@ -71,7 +81,7 @@ namespace TweetDck.Core.Handling{
public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
base.OnContextMenuDismissed(browserControl, browser, frame);
form.InvokeSafe(() => form.ContextMenuOpen = false);
form.InvokeAsyncSafe(() => form.ContextMenuOpen = false);
}
}
}

View File

@@ -1,27 +0,0 @@
using CefSharp;
using System.Collections.Generic;
using TweetDck.Core.Controls;
namespace TweetDck.Core.Handling{
class DialogHandlerBrowser : IDialogHandler{
private readonly FormBrowser form;
public DialogHandlerBrowser(FormBrowser form){
this.form = form;
}
public bool OnFileDialog(IWebBrowser browserControl, IBrowser browser, CefFileDialogMode mode, string title, string defaultFilePath, List<string> acceptFilters, int selectedAcceptFilter, IFileDialogCallback callback){
if (!string.IsNullOrEmpty(TweetDeckBridge.ClipboardImagePath)){
callback.Continue(selectedAcceptFilter, new List<string>{ TweetDeckBridge.ClipboardImagePath });
form.InvokeSafe(() => {
TweetDeckBridge.ClipboardImagePath = string.Empty;
});
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,60 @@
using CefSharp;
using CefSharp.WinForms;
using System.Drawing;
using System.Windows.Forms;
using TweetDck.Core.Controls;
using TweetDck.Core.Other;
namespace TweetDck.Core.Handling {
class JavaScriptDialogHandler : IJsDialogHandler{
bool IJsDialogHandler.OnJSDialog(IWebBrowser browserControl, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage){
((ChromiumWebBrowser)browserControl).InvokeSafe(() => {
FormMessage form = new FormMessage(Program.BrandName, messageText, MessageBoxIcon.None);
TextBox input = null;
if (dialogType == CefJsDialogType.Alert){
form.AcceptButton = form.AddButton("OK");
}
else if (dialogType == CefJsDialogType.Confirm){
form.CancelButton = form.AddButton("No", DialogResult.No);
form.AcceptButton = form.AddButton("Yes");
}
else if (dialogType == CefJsDialogType.Prompt){
form.CancelButton = form.AddButton("Cancel", DialogResult.Cancel);
form.AcceptButton = form.AddButton("OK");
input = new TextBox{
Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
Location = new Point(27, form.ActionPanelY-46),
Size = new Size(form.ClientSize.Width-54, 20)
};
form.Controls.Add(input);
form.ActiveControl = input;
form.Height += input.Size.Height+input.Margin.Vertical;
}
bool success = form.ShowDialog() == DialogResult.OK;
if (input == null){
callback.Continue(success);
}
else{
callback.Continue(success, input.Text);
input.Dispose();
}
form.Dispose();
});
return true;
}
bool IJsDialogHandler.OnJSBeforeUnload(IWebBrowser browserControl, IBrowser browser, string message, bool isReload, IJsDialogCallback callback){
return false;
}
void IJsDialogHandler.OnResetDialogState(IWebBrowser browserControl, IBrowser browser){}
void IJsDialogHandler.OnDialogClosed(IWebBrowser browserControl, IBrowser browser){}
}
}

View File

@@ -0,0 +1,63 @@
using CefSharp;
using System.Collections.Specialized;
using System.IO;
using System.Text;
namespace TweetDck.Core.Handling{
class ResourceHandlerNotification : IResourceHandler{
private readonly NameValueCollection headers = new NameValueCollection(0);
private MemoryStream dataIn;
public void SetHTML(string html){
dataIn?.Dispose();
dataIn = ResourceHandler.GetMemoryStream(html, Encoding.UTF8);
}
public void Dispose(){
if (dataIn != null){
dataIn.Dispose();
dataIn = null;
}
}
bool IResourceHandler.ProcessRequest(IRequest request, ICallback callback){
callback.Continue();
return true;
}
void IResourceHandler.GetResponseHeaders(IResponse response, out long responseLength, out string redirectUrl){
redirectUrl = null;
response.MimeType = "text/html";
response.StatusCode = 200;
response.StatusText = "OK";
response.ResponseHeaders = headers;
responseLength = dataIn?.Length ?? -1;
}
bool IResourceHandler.ReadResponse(Stream dataOut, out int bytesRead, ICallback callback){
callback.Dispose();
try{
int length = (int)dataIn.Length;
dataIn.CopyTo(dataOut, length);
bytesRead = length;
return true;
}catch{ // catch IOException, possibly NullReferenceException if dataIn is null
bytesRead = 0;
return false;
}
}
bool IResourceHandler.CanGetCookie(Cookie cookie){
return true;
}
bool IResourceHandler.CanSetCookie(Cookie cookie){
return true;
}
void IResourceHandler.Cancel(){}
}
}

View File

@@ -1,175 +0,0 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Forms;
using TweetDck.Core.Utils;
using TweetDck.Core.Controls;
namespace TweetDck.Core.Handling{
class TweetDeckBridge{
public static string LastRightClickedLink = string.Empty;
public static string LastHighlightedTweet = string.Empty;
public static string LastHighlightedQuotedTweet = string.Empty;
public static string NotificationTweetEmbedded = string.Empty;
public static string ClipboardImagePath = string.Empty;
private readonly FormBrowser form;
private readonly FormNotification notification;
public string BrandName{
get{
return Program.BrandName;
}
}
public string VersionTag{
get{
return Program.VersionTag;
}
}
public bool MuteNotifications{
get{
return Program.UserConfig.MuteNotifications;
}
}
public bool ExpandLinksOnHover{
get{
return Program.UserConfig.ExpandLinksOnHover;
}
}
public bool HasCustomBrowserCSS{
get{
return !string.IsNullOrEmpty(Program.UserConfig.CustomBrowserCSS);
}
}
public string CustomBrowserCSS{
get{
return Program.UserConfig.CustomBrowserCSS;
}
}
public TweetDeckBridge(FormBrowser form, FormNotification notification){
this.form = form;
this.notification = notification;
}
public void LoadFontSizeClass(string fsClass){
form.InvokeSafe(() => {
TweetNotification.SetFontSizeClass(fsClass);
});
}
public void LoadNotificationHeadContents(string headContents){
form.InvokeSafe(() => {
TweetNotification.SetHeadTag(headContents);
});
}
public void SetLastRightClickedLink(string link){
form.InvokeSafe(() => LastRightClickedLink = link);
}
public void SetLastHighlightedTweet(string link, string quotedLink){
form.InvokeSafe(() => {
LastHighlightedTweet = link;
LastHighlightedQuotedTweet = quotedLink;
});
}
public void SetNotificationTweetEmbedded(string link){
form.InvokeSafe(() => NotificationTweetEmbedded = link);
}
public void OpenSettingsMenu(){
form.InvokeSafe(form.OpenSettings);
}
public void OpenPluginsMenu(){
form.InvokeSafe(form.OpenPlugins);
}
public void OnTweetPopup(string tweetHtml, string tweetUrl, int tweetCharacters){
notification.InvokeSafe(() => {
form.OnTweetNotification();
notification.ShowNotification(new TweetNotification(tweetHtml, tweetUrl, tweetCharacters));
});
}
public void OnTweetSound(){
form.InvokeSafe(form.OnTweetNotification);
}
public void OnNotificationReady(){
if (!Program.UserConfig.NotificationLegacyLoad){
notification.InvokeSafe(notification.OnNotificationReady);
}
}
public void DisplayTooltip(string text, bool showInNotification){
if (showInNotification){
notification.InvokeSafe(() => notification.DisplayTooltip(text));
}
else{
form.InvokeSafe(() => form.DisplayTooltip(text));
}
}
public void 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));
NativeMethods.SimulateMouseClick(NativeMethods.MouseButton.Left);
Cursor.Position = prevPos;
form.OnImagePastedFinish();
});
}
public void OpenBrowser(string url){
BrowserUtils.OpenExternalBrowser(url);
}
public void Alert(string type, string contents){
MessageBoxIcon icon;
switch(type){
case "error": icon = MessageBoxIcon.Error; break;
case "warning": icon = MessageBoxIcon.Warning; break;
case "info": icon = MessageBoxIcon.Information; break;
default: icon = MessageBoxIcon.None; break;
}
MessageBox.Show(contents, Program.BrandName+" Browser Message", MessageBoxButtons.OK, icon);
}
public void Log(string data){
System.Diagnostics.Debug.WriteLine(data);
}
}
}

View File

@@ -1,116 +0,0 @@
using System;
using System.Text;
namespace TweetDck.Core.Handling{
sealed class TweetNotification{
private static string FontSizeClass { get; set; }
private static string HeadTag { get; set; }
private static string DefaultFontSizeClass{
get{
return "medium";
}
}
private static string DefaultHeadTag{
get{
return @"<meta charset='utf-8'><meta http-equiv='X-UA-Compatible' content='chrome=1'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/font.5ef884f9f9.css'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/app-dark.5631e0dd42.css'>";
}
}
private static string CustomCSS{
get{
return @".scroll-styled-v::-webkit-scrollbar{width:8px}.scroll-styled-v::-webkit-scrollbar-thumb{border-radius:0}a[data-full-url]{word-break:break-all}";
}
}
public static int FontSizeLevel{
get{
switch(FontSizeClass){
case "largest": return 4;
case "large": return 3;
case "medium": return 2;
case "small": return 1;
default: return 0;
}
}
}
public static TweetNotification ExampleTweet{
get{
StringBuilder build = new StringBuilder();
build.Append(@"<article><div class='js-stream-item-content item-box js-show-detail'><div class='js-tweet tweet'>");
build.Append(@"<header class='tweet-header'>");
build.Append(@"<time class='tweet-timestamp js-timestamp pull-right txt-mute'><a target='_blank' rel='url' href='https://twitter.com/chylexmc' class='txt-small'>0s</a></time>");
build.Append(@"<a target='_blank' rel='user' href='https://twitter.com/chylexmc' class='account-link link-complex block'>");
build.Append(@"<div class='obj-left item-img tweet-img'><img width='48' height='48' alt='chylexmc's avatar' src='https://pbs.twimg.com/profile_images/765161905312980992/AhDP9iY-_normal.jpg' class='tweet-avatar avatar pull-right'></div>");
build.Append(@"<div class='nbfc'><span class='account-inline txt-ellipsis'><b class='fullname link-complex-target'>chylex</b> <span class='username txt-mute'>@chylexmc</span></span></div>");
build.Append(@"</a>");
build.Append(@"</header>");
build.Append(@"<div class='tweet-body'><p class='js-tweet-text tweet-text with-linebreaks'>This is an example tweet, which lets you test the location and duration of popup notifications.</p></div>");
#if DEBUG
build.Append(@"<div style='margin-top:64px'>Scrollbar test padding...</div>");
#endif
build.Append(@"</div></div></article>");
return new TweetNotification(build.ToString(), "", 95);
}
}
public static void SetFontSizeClass(string newFSClass){
FontSizeClass = newFSClass;
}
public static void SetHeadTag(string headContents){
HeadTag = headContents;
}
public enum Position{
TopLeft, TopRight, BottomLeft, BottomRight, Custom
}
public enum Duration{
Short, Medium, Long, VeryLong
}
public string Url{
get{
return url;
}
}
private readonly string html;
private readonly string url;
private readonly int characters;
public TweetNotification(string html, string url, int characters){
this.html = html;
this.url = url;
this.characters = characters;
}
public int GetDisplayDuration(int value){
return 2000+Math.Max(1000, value*characters);
}
public string GenerateHtml(){
StringBuilder build = new StringBuilder();
build.Append("<!DOCTYPE html>");
build.Append("<html class='os-windows txt-base-").Append(FontSizeClass ?? DefaultFontSizeClass).Append("'>");
build.Append("<head>").Append(HeadTag ?? DefaultHeadTag).Append("<style type='text/css'>").Append(CustomCSS).Append("</style>");
if (!string.IsNullOrEmpty(Program.UserConfig.CustomNotificationCSS)){
build.Append("<style type='text/css'>").Append(Program.UserConfig.CustomNotificationCSS).Append("</style>");
}
build.Append("</head>");
build.Append("<body class='hearty'><div class='app-columns-container'><div class='column scroll-styled-v' style='width:100%;overflow-y:auto'>");
build.Append(html);
build.Append("</div></div></body>");
build.Append("</html>");
return build.ToString();
}
}
}

View File

@@ -0,0 +1,52 @@
namespace TweetDck.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 = TweetDck.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,208 @@
using CefSharp;
using CefSharp.WinForms;
using System;
using System.Drawing;
using System.Windows.Forms;
using TweetDck.Configuration;
using TweetDck.Core.Controls;
using TweetDck.Core.Handling;
using TweetDck.Core.Utils;
namespace TweetDck.Core.Notification{
partial class FormNotificationBase : Form{
protected Point PrimaryLocation{
get{
UserConfig config = Program.UserConfig;
Screen screen;
if (config.NotificationDisplay > 0 && config.NotificationDisplay <= Screen.AllScreens.Length){
screen = Screen.AllScreens[config.NotificationDisplay-1];
}
else{
screen = Screen.FromControl(owner);
}
int edgeDist = config.NotificationEdgeDistance;
switch(config.NotificationPosition){
case TweetNotification.Position.TopLeft:
return new Point(screen.WorkingArea.X+edgeDist, screen.WorkingArea.Y+edgeDist);
case TweetNotification.Position.TopRight:
return new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+edgeDist);
case TweetNotification.Position.BottomLeft:
return new Point(screen.WorkingArea.X+edgeDist, screen.WorkingArea.Y+screen.WorkingArea.Height-edgeDist-Height);
case TweetNotification.Position.BottomRight:
return new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+screen.WorkingArea.Height-edgeDist-Height);
case TweetNotification.Position.Custom:
if (!config.IsCustomNotificationPositionSet){
config.CustomNotificationPosition = new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+edgeDist);
config.Save();
}
return config.CustomNotificationPosition;
}
return Location;
}
}
public bool IsNotificationVisible => Location != ControlExtensions.InvisibleLocation;
public new Point Location{
get => base.Location;
set => Visible = (base.Location = value) != ControlExtensions.InvisibleLocation;
}
public Func<bool> CanMoveWindow = () => true;
protected override bool ShowWithoutActivation => true;
protected readonly Form owner;
protected readonly ChromiumWebBrowser browser;
private readonly ResourceHandlerNotification resourceHandler = new ResourceHandlerNotification();
private string currentColumn;
private int pauseCounter;
public bool IsPaused => pauseCounter > 0;
public bool FreezeTimer { get; set; }
public bool ContextMenuOpen { get; set; }
public string CurrentTweetUrl { get; private set; }
public string CurrentQuoteUrl { get; private set; }
public event EventHandler Initialized;
public FormNotificationBase(Form owner, bool enableContextMenu){
InitializeComponent();
this.owner = owner;
this.owner.FormClosed += owner_FormClosed;
this.browser = new ChromiumWebBrowser("about:blank"){
MenuHandler = new ContextMenuNotification(this, enableContextMenu),
LifeSpanHandler = new LifeSpanHandler()
};
this.browser.Dock = DockStyle.None;
this.browser.ClientSize = ClientSize;
this.browser.IsBrowserInitializedChanged += Browser_IsBrowserInitializedChanged;
#if DEBUG
this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
#endif
DefaultResourceHandlerFactory handlerFactory = (DefaultResourceHandlerFactory)browser.ResourceHandlerFactory;
handlerFactory.RegisterHandler("https://tweetdeck.twitter.com", this.resourceHandler);
Controls.Add(browser);
Disposed += (sender, args) => {
this.browser.Dispose();
this.owner.FormClosed -= owner_FormClosed;
};
if (WindowsUtils.ShouldAvoidToolWindow){
FormBorderStyle = FormBorderStyle.FixedSingle;
}
// ReSharper disable once VirtualMemberCallInContructor
UpdateTitle();
}
protected override void WndProc(ref Message m){
if (m.Msg == 0x0112 && (m.WParam.ToInt32() & 0xFFF0) == 0xF010 && !CanMoveWindow()){ // WM_SYSCOMMAND, SC_MOVE
return;
}
base.WndProc(ref m);
}
// event handlers
private void owner_FormClosed(object sender, FormClosedEventArgs e){
Close();
}
private void Browser_IsBrowserInitializedChanged(object sender, IsBrowserInitializedChangedEventArgs e){
if (e.IsBrowserInitialized){
Initialized?.Invoke(this, new EventArgs());
}
}
// notification methods
public virtual void HideNotification(bool loadBlank){
if (loadBlank){
browser.Load("about:blank");
}
Location = ControlExtensions.InvisibleLocation;
currentColumn = null;
}
public virtual void FinishCurrentNotification(){}
public virtual void PauseNotification(){
if (pauseCounter++ == 0 && IsNotificationVisible){
Location = ControlExtensions.InvisibleLocation;
}
}
public virtual void ResumeNotification(){
if (pauseCounter > 0){
--pauseCounter;
}
}
protected virtual string GetTweetHTML(TweetNotification tweet){
string bodyClasses = browser.Bounds.Contains(PointToClient(Cursor.Position)) ? "td-hover" : string.Empty;
return tweet.GenerateHtml(bodyClasses);
}
protected virtual void LoadTweet(TweetNotification tweet){
CurrentTweetUrl = tweet.TweetUrl;
CurrentQuoteUrl = tweet.QuoteUrl;
currentColumn = tweet.Column;
resourceHandler.SetHTML(GetTweetHTML(tweet));
browser.Load("https://tweetdeck.twitter.com");
}
protected virtual void SetNotificationSize(int width, int height){
browser.ClientSize = ClientSize = new Size((int)Math.Round(width*Program.UserConfig.ZoomMultiplier), (int)Math.Round(height*Program.UserConfig.ZoomMultiplier));
}
protected virtual void OnNotificationReady(){
MoveToVisibleLocation();
}
protected virtual void UpdateTitle(){
Text = string.IsNullOrEmpty(currentColumn) || !Program.UserConfig.DisplayNotificationColumn ? Program.BrandName : Program.BrandName+" - "+currentColumn;
}
public void MoveToVisibleLocation(){
bool needsReactivating = Location == ControlExtensions.InvisibleLocation;
Location = PrimaryLocation;
if (needsReactivating){
NativeMethods.SetFormPos(this, NativeMethods.HWND_TOPMOST, NativeMethods.SWP_NOACTIVATE);
}
}
public void DisplayTooltip(string text){
if (string.IsNullOrEmpty(text)){
toolTip.Hide(this);
}
else{
Point position = PointToClient(Cursor.Position);
position.Offset(20, 5);
toolTip.Show(text, this, position);
}
}
}
}

View File

@@ -1,7 +1,5 @@
using TweetDck.Core.Controls;
namespace TweetDck.Core {
sealed partial class FormNotification {
namespace TweetDck.Core.Notification {
partial class FormNotificationMain {
/// <summary>
/// Required designer variable.
/// </summary>
@@ -26,22 +24,15 @@ namespace TweetDck.Core {
/// </summary>
private void InitializeComponent() {
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.progressBarTimer = new TweetDck.Core.Controls.FlatProgressBar();
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.SuspendLayout();
//
// panelBrowser
// timerDisplayDelay
//
this.panelBrowser.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
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;
this.timerDisplayDelay.Interval = 17;
this.timerDisplayDelay.Tick += new System.EventHandler(this.timerDisplayDelay_Tick);
//
// timerProgress
//
@@ -68,15 +59,6 @@ namespace TweetDck.Core {
this.BackColor = System.Drawing.SystemColors.Control;
this.ClientSize = new System.Drawing.Size(284, 122);
this.Controls.Add(this.progressBarTimer);
this.Controls.Add(this.panelBrowser);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
this.Location = new System.Drawing.Point(-32000, -32000);
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "FormNotification";
this.ShowIcon = false;
this.ShowInTaskbar = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormNotification_FormClosing);
this.ResumeLayout(false);
@@ -84,9 +66,8 @@ namespace TweetDck.Core {
#endregion
private System.Windows.Forms.Panel panelBrowser;
private System.Windows.Forms.Timer timerDisplayDelay;
protected System.Windows.Forms.Timer timerProgress;
private Controls.FlatProgressBar progressBarTimer;
private System.Windows.Forms.Timer timerProgress;
private System.Windows.Forms.ToolTip toolTip;
}
}

View File

@@ -0,0 +1,282 @@
using CefSharp;
using System;
using System.Drawing;
using System.Windows.Forms;
using TweetDck.Core.Bridge;
using TweetDck.Core.Controls;
using TweetDck.Core.Utils;
using TweetDck.Plugins;
using TweetDck.Plugins.Enums;
using TweetDck.Resources;
namespace TweetDck.Core.Notification{
partial class FormNotificationMain : FormNotificationBase{
private const string NotificationScriptFile = "notification.js";
private static readonly string NotificationScriptIdentifier = ScriptLoader.GetRootIdentifier(NotificationScriptFile);
private static readonly string PluginScriptIdentifier = ScriptLoader.GetRootIdentifier(PluginManager.PluginNotificationScriptFile);
private static readonly string NotificationJS, PluginJS;
static FormNotificationMain(){
NotificationJS = ScriptLoader.LoadResource(NotificationScriptFile);
PluginJS = ScriptLoader.LoadResource(PluginManager.PluginNotificationScriptFile);
}
private static int BaseClientWidth{
get{
int level = TweetNotification.FontSizeLevel;
int width = level == 0 ? 284 : (int)Math.Round(284.0*(1.0+0.05*level));
return (int)Math.Round(width*Program.UserConfig.ZoomMultiplier);
}
}
private static int BaseClientHeight{
get{
int level = TweetNotification.FontSizeLevel;
int height = level == 0 ? 118 : (int)Math.Round(118.0*(1.0+0.075*level));
return (int)Math.Round(height*Program.UserConfig.ZoomMultiplier);
}
}
private readonly PluginManager plugins;
protected int timeLeft, totalTime;
protected bool pausedDuringNotification;
private readonly NativeMethods.HookProc mouseHookDelegate;
private IntPtr mouseHook;
private bool blockXButtonUp;
private bool? prevDisplayTimer;
private int? prevFontSize;
private bool RequiresResize{
get{
return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Program.UserConfig.DisplayNotificationTimer || prevFontSize != TweetNotification.FontSizeLevel;
}
set{
if (value){
prevDisplayTimer = null;
prevFontSize = null;
}
else{
prevDisplayTimer = Program.UserConfig.DisplayNotificationTimer;
prevFontSize = TweetNotification.FontSizeLevel;
}
}
}
public FormNotificationMain(FormBrowser owner, PluginManager pluginManager, bool enableContextMenu) : base(owner, enableContextMenu){
InitializeComponent();
this.plugins = pluginManager;
browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(owner, this));
browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
browser.LoadingStateChanged += Browser_LoadingStateChanged;
browser.FrameLoadEnd += Browser_FrameLoadEnd;
mouseHookDelegate = MouseHookProc;
Disposed += (sender, args) => StopMouseHook(true);
}
// mouse wheel hook
private void StartMouseHook(){
if (mouseHook == IntPtr.Zero){
mouseHook = NativeMethods.SetWindowsHookEx(NativeMethods.WM_MOUSE_LL, mouseHookDelegate, IntPtr.Zero, 0);
}
}
private void StopMouseHook(bool force){
if (mouseHook != IntPtr.Zero && (force || !blockXButtonUp)){
NativeMethods.UnhookWindowsHookEx(mouseHook);
mouseHook = IntPtr.Zero;
blockXButtonUp = false;
}
}
private IntPtr MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam){
if (nCode == 0){
int eventType = wParam.ToInt32();
if (eventType == NativeMethods.WM_MOUSEWHEEL && browser.Bounds.Contains(PointToClient(Cursor.Position)) && !ContainsFocus && !owner.ContainsFocus){
browser.SendMouseWheelEvent(0, 0, 0, NativeMethods.GetMouseHookData(lParam), CefEventFlags.None);
return NativeMethods.HOOK_HANDLED;
}
else if (eventType == NativeMethods.WM_XBUTTONDOWN && DesktopBounds.Contains(Cursor.Position)){
int extraButton = NativeMethods.GetMouseHookData(lParam);
if (extraButton == 2){ // forward button
this.InvokeAsyncSafe(FinishCurrentNotification);
}
else if (extraButton == 1){ // back button
this.InvokeAsyncSafe(Close);
}
blockXButtonUp = true;
return NativeMethods.HOOK_HANDLED;
}
else if (eventType == NativeMethods.WM_XBUTTONUP && blockXButtonUp){
blockXButtonUp = false;
if (!Visible){
StopMouseHook(false);
}
return NativeMethods.HOOK_HANDLED;
}
}
return NativeMethods.CallNextHookEx(mouseHook, nCode, wParam, lParam);
}
// event handlers
private void FormNotification_FormClosing(object sender, FormClosingEventArgs e){
if (e.CloseReason == CloseReason.UserClosing){
HideNotification(true);
e.Cancel = true;
}
}
private void Browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
if (!e.IsLoading && browser.Address != "about:blank"){
this.InvokeSafe(() => {
Visible = true; // ensures repaint before moving the window to a visible location
timerDisplayDelay.Start();
});
}
}
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
if (e.Frame.IsMain && NotificationJS != null && browser.Address != "about:blank"){
e.Frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Properties.AllNotification));
ScriptLoader.ExecuteScript(e.Frame, NotificationJS, NotificationScriptIdentifier);
if (plugins.HasAnyPlugin(PluginEnvironment.Notification)){
ScriptLoader.ExecuteScript(e.Frame, PluginJS, PluginScriptIdentifier);
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginGlobalScriptFile);
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Notification, false);
}
}
}
private void timerDisplayDelay_Tick(object sender, EventArgs e){
OnNotificationReady();
timerDisplayDelay.Stop();
}
private void timerHideProgress_Tick(object sender, EventArgs e){
if (Bounds.Contains(Cursor.Position) || FreezeTimer || ContextMenuOpen)return;
timeLeft -= timerProgress.Interval;
int value = (int)Math.Round(1025.0*(totalTime-timeLeft)/totalTime);
progressBarTimer.SetValueInstant(Math.Min(1000, Math.Max(0, Program.UserConfig.NotificationTimerCountDown ? 1000-value : value)));
if (timeLeft <= 0){
FinishCurrentNotification();
}
}
// notification methods
public virtual void ShowNotification(TweetNotification notification){
LoadTweet(notification);
}
public void ShowNotificationForSettings(bool reset){
if (reset){
LoadTweet(TweetNotification.ExampleTweet);
}
else{
PrepareAndDisplayWindow();
}
UpdateTitle();
}
public override void HideNotification(bool loadBlank){
base.HideNotification(loadBlank);
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0;
timerProgress.Stop();
totalTime = 0;
StopMouseHook(false);
}
public override void FinishCurrentNotification(){
timerProgress.Stop();
}
public override void PauseNotification(){
if (!IsPaused){
pausedDuringNotification = IsNotificationVisible;
timerProgress.Stop();
StopMouseHook(true);
}
base.PauseNotification();
}
public override void ResumeNotification(){
bool wasPaused = IsPaused;
base.ResumeNotification();
if (wasPaused && !IsPaused && pausedDuringNotification){
OnNotificationReady();
}
}
protected override string GetTweetHTML(TweetNotification tweet){
string html = base.GetTweetHTML(tweet);
foreach(InjectedHTML injection in plugins.Bridge.NotificationInjections){
html = injection.Inject(html);
}
return html;
}
protected override void LoadTweet(TweetNotification tweet){
timerProgress.Stop();
totalTime = timeLeft = tweet.GetDisplayDuration(Program.UserConfig.NotificationDurationValue);
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0;
base.LoadTweet(tweet);
}
protected override void SetNotificationSize(int width, int height){
if (Program.UserConfig.DisplayNotificationTimer){
ClientSize = new Size(width, height+4);
progressBarTimer.Visible = true;
}
else{
ClientSize = new Size(width, height);
progressBarTimer.Visible = false;
}
browser.ClientSize = new Size(width, height);
}
private void PrepareAndDisplayWindow(){
if (RequiresResize){
RequiresResize = false;
SetNotificationSize(BaseClientWidth, BaseClientHeight);
}
MoveToVisibleLocation();
StartMouseHook();
}
protected override void OnNotificationReady(){
PrepareAndDisplayWindow();
timerProgress.Start();
}
}
}

View File

@@ -0,0 +1,53 @@
namespace TweetDck.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.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormNotificationTweet_FormClosing);
this.ResumeLayout(true);
}
#endregion
private System.Windows.Forms.Timer timerCursorCheck;
private System.Windows.Forms.Timer timerIdlePauseCheck;
}
}

View File

@@ -0,0 +1,141 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using TweetDck.Plugins;
using System.Windows.Forms;
using TweetDck.Core.Utils;
namespace TweetDck.Core.Notification{
sealed partial class FormNotificationTweet : FormNotificationMain{
private const int NonIntrusiveIdleLimit = 30;
private const int TrimMinimum = 32;
private bool IsCursorOverNotificationArea => new Rectangle(PrimaryLocation, Size).Contains(Cursor.Position);
private readonly Queue<TweetNotification> tweetQueue = new Queue<TweetNotification>(4);
private bool needsTrim;
public FormNotificationTweet(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, true){
InitializeComponent();
Program.UserConfig.MuteToggled += Config_MuteToggled;
Disposed += (sender, args) => Program.UserConfig.MuteToggled -= Config_MuteToggled;
if (Program.UserConfig.MuteNotifications){
PauseNotification();
}
}
private void FormNotificationTweet_FormClosing(object sender, FormClosingEventArgs e){
if (e.CloseReason == CloseReason.UserClosing){
tweetQueue.Clear(); // already canceled
TrimQueue();
}
}
private void TrimQueue(){
if (needsTrim){
tweetQueue.TrimExcess();
needsTrim = false;
}
}
// event handlers
private void Config_MuteToggled(object sender, EventArgs e){
if (Program.UserConfig.MuteNotifications){
PauseNotification();
}
else{
ResumeNotification();
}
}
private void timerCursorCheck_Tick(object sender, EventArgs e){
if (!IsCursorOverNotificationArea){
ResumeNotification();
timerCursorCheck.Stop();
}
}
private void timerIdlePauseCheck_Tick(object sender, EventArgs e){
if (NativeMethods.GetIdleSeconds() < Program.UserConfig.NotificationIdlePauseSeconds){
ResumeNotification();
timerIdlePauseCheck.Stop();
}
}
// notification methods
public override void ShowNotification(TweetNotification notification){
if (IsPaused){
tweetQueue.Enqueue(notification);
}
else{
tweetQueue.Enqueue(notification);
UpdateTitle();
if (totalTime == 0){
LoadNextNotification();
}
}
needsTrim |= tweetQueue.Count >= TrimMinimum;
}
public override void FinishCurrentNotification(){
if (tweetQueue.Count > 0){
LoadNextNotification();
}
else{
HideNotification(true);
TrimQueue();
}
}
public override void ResumeNotification(){
bool wasPaused = IsPaused;
base.ResumeNotification();
if (wasPaused && !IsPaused && !pausedDuringNotification && tweetQueue.Count > 0){
LoadNextNotification();
}
}
private void LoadNextNotification(){
if (!IsNotificationVisible){
if (Program.UserConfig.NotificationNonIntrusiveMode && IsCursorOverNotificationArea && NativeMethods.GetIdleSeconds() < NonIntrusiveIdleLimit){
if (!timerCursorCheck.Enabled){
PauseNotification();
timerCursorCheck.Start();
}
return;
}
else if (Program.UserConfig.NotificationIdlePauseSeconds > 0 && NativeMethods.GetIdleSeconds() >= Program.UserConfig.NotificationIdlePauseSeconds){
if (!timerIdlePauseCheck.Enabled){
PauseNotification();
timerIdlePauseCheck.Start();
}
return;
}
}
LoadTweet(tweetQueue.Dequeue());
}
protected override void UpdateTitle(){
base.UpdateTitle();
if (tweetQueue.Count > 0){
Text = Text+" ("+tweetQueue.Count+" more left)";
}
}
protected override void OnNotificationReady(){
UpdateTitle();
base.OnNotificationReady();
}
}
}

View File

@@ -0,0 +1,49 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
using TweetDck.Core.Bridge;
using TweetDck.Core.Utils;
using TweetDck.Resources;
namespace TweetDck.Core.Notification.Screenshot{
sealed class FormNotificationScreenshotable : FormNotificationBase{
public FormNotificationScreenshotable(Action callback, Form owner) : base(owner, false){
browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new CallbackBridge(this, callback));
browser.FrameLoadEnd += (sender, args) => {
if (args.Frame.IsMain && browser.Address != "about:blank"){
ScriptLoader.ExecuteScript(args.Frame, "window.setTimeout($TD_NotificationScreenshot.trigger, 67)", "gen:screenshot");
}
};
}
protected override string GetTweetHTML(TweetNotification tweet){
return tweet.GenerateHtml(enableCustomCSS: false);
}
public void LoadNotificationForScreenshot(TweetNotification tweet, int width, int height){
LoadTweet(tweet);
SetNotificationSize(width, height);
}
public void TakeScreenshot(){
IntPtr context = NativeMethods.GetDC(this.Handle);
if (context == IntPtr.Zero){
MessageBox.Show("Could not retrieve a graphics context handle for the notification window to take the screenshot.", "Screenshot Failed", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else{
using(Bitmap bmp = new Bitmap(ClientSize.Width, ClientSize.Height, PixelFormat.Format32bppRgb)){
try{
NativeMethods.RenderSourceIntoBitmap(context, bmp);
}finally{
NativeMethods.ReleaseDC(this.Handle, context);
}
Clipboard.SetImage(bmp);
}
}
}
}
}

View File

@@ -0,0 +1,75 @@
// Uncomment to keep screenshot windows visible for debugging
// #define NO_HIDE_SCREENSHOTS
using System;
using System.Windows.Forms;
using TweetDck.Core.Controls;
namespace TweetDck.Core.Notification.Screenshot{
sealed class TweetScreenshotManager : IDisposable{
private readonly Form owner;
private readonly Timer timeout;
private readonly Timer disposer;
private FormNotificationScreenshotable screenshot;
public TweetScreenshotManager(Form owner){
this.owner = owner;
this.timeout = new Timer{ Interval = 8000 };
this.timeout.Tick += timeout_Tick;
this.disposer = new Timer{ Interval = 1 };
this.disposer.Tick += disposer_Tick;
}
private void timeout_Tick(object sender, EventArgs e){
timeout.Stop();
screenshot.Location = ControlExtensions.InvisibleLocation;
disposer.Start();
}
private void disposer_Tick(object sender, EventArgs e){
disposer.Stop();
screenshot.Dispose();
screenshot = null;
}
public void Trigger(string html, int width, int height){
if (screenshot != null){
return;
}
screenshot = new FormNotificationScreenshotable(Callback, owner){
CanMoveWindow = () => false
};
screenshot.LoadNotificationForScreenshot(new TweetNotification(string.Empty, html, 0, string.Empty, string.Empty), width, height);
screenshot.Show();
timeout.Start();
}
private void Callback(){
if (!timeout.Enabled){
return;
}
timeout.Stop();
screenshot.TakeScreenshot();
#if !(DEBUG && NO_HIDE_SCREENSHOTS)
screenshot.Location = ControlExtensions.InvisibleLocation;
disposer.Start();
#else
screenshot.MoveToVisibleLocation();
screenshot.FormClosed += (sender, args) => disposer.Start();
#endif
}
public void Dispose(){
timeout.Dispose();
disposer.Dispose();
screenshot?.Dispose();
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
namespace TweetDck.Core.Notification.Sound{
interface ISoundNotificationPlayer : IDisposable{
string SupportedFormats { get; }
event EventHandler<PlaybackErrorEventArgs> PlaybackError;
void Play(string file);
void Stop();
}
}

View File

@@ -0,0 +1,13 @@
using System;
namespace TweetDck.Core.Notification.Sound{
sealed class PlaybackErrorEventArgs : EventArgs{
public string Message { get; }
public bool Ignore { get; set; }
public PlaybackErrorEventArgs(string message){
this.Message = message;
this.Ignore = false;
}
}
}

View File

@@ -0,0 +1,53 @@
using System;
using System.IO;
using System.Media;
namespace TweetDck.Core.Notification.Sound{
sealed class SoundPlayerImplFallback : ISoundNotificationPlayer{
string ISoundNotificationPlayer.SupportedFormats => "*.wav";
public event EventHandler<PlaybackErrorEventArgs> PlaybackError;
private readonly SoundPlayer player;
private bool ignorePlaybackError;
public SoundPlayerImplFallback(){
player = new SoundPlayer{
LoadTimeout = 5000
};
}
void ISoundNotificationPlayer.Play(string file){
if (player.SoundLocation != file){
player.SoundLocation = file;
ignorePlaybackError = false;
}
try{
player.Play();
}catch(FileNotFoundException e){
OnNotificationSoundError("File not found: "+e.FileName);
}catch(InvalidOperationException){
OnNotificationSoundError("File format was not recognized.");
}catch(TimeoutException){
OnNotificationSoundError("File took too long to load.");
}
}
void ISoundNotificationPlayer.Stop(){
player.Stop();
}
void IDisposable.Dispose(){
player.Dispose();
}
private void OnNotificationSoundError(string message){
if (!ignorePlaybackError && PlaybackError != null){
PlaybackErrorEventArgs args = new PlaybackErrorEventArgs(message);
PlaybackError(this, args);
ignorePlaybackError = args.Ignore;
}
}
}
}

View File

@@ -0,0 +1,113 @@
using System;
using System.Runtime.InteropServices;
using TweetDck.Core.Utils;
using WMPLib;
namespace TweetDck.Core.Notification.Sound{
sealed class SoundPlayerImplWMP : ISoundNotificationPlayer{
string ISoundNotificationPlayer.SupportedFormats => "*.wav;*.mp3;*.mp2;*.m4a;*.mid;*.midi;*.rmi;*.wma;*.aif;*.aifc;*.aiff;*.snd;*.au";
public event EventHandler<PlaybackErrorEventArgs> PlaybackError;
private readonly WindowsMediaPlayer player;
private bool wasTryingToPlay;
private bool ignorePlaybackError;
// changing the player volume also affects the value in the Windows mixer
// however, the initial value is always 50 or some other stupid shit
// so we have to tell the player to set its volume to whatever the mixer is set to
// using the most code required for the least functionality with a sorry excuse for an API
// thanks, Microsoft
public SoundPlayerImplWMP(){
player = new WindowsMediaPlayer();
player.settings.autoStart = false;
player.settings.enableErrorDialogs = false;
player.settings.invokeURLs = false;
player.settings.volume = (int)Math.Floor(100.0*NativeCoreAudio.GetMixerVolumeForCurrentProcess());
player.MediaChange += player_MediaChange;
player.MediaError += player_MediaError;
}
void ISoundNotificationPlayer.Play(string file){
wasTryingToPlay = true;
try{
if (player.URL != file){
player.close();
player.URL = file;
ignorePlaybackError = false;
}
else{
player.controls.stop();
}
player.controls.play();
}catch(Exception e){
OnNotificationSoundError("An error occurred in Windows Media Player: "+e.Message);
}
}
void ISoundNotificationPlayer.Stop(){
try{
player.controls.stop();
}catch{
// ignore
}
}
void IDisposable.Dispose(){
player.close();
Marshal.ReleaseComObject(player);
}
private void player_MediaChange(object item){
IWMPMedia2 media = item as IWMPMedia2;
if (media == null){
OnNotificationSoundError("Unknown error.");
return;
}
// ReSharper disable once CompareOfFloatsByEqualityOperator
else if (media.Error == null && media.duration == 0.0){
OnNotificationSoundError("File does not contain an audio track.");
}
else if (media.Error != null){
OnNotificationSoundError(media.Error);
}
Marshal.ReleaseComObject(media);
}
private void player_MediaError(object pMediaObject){
IWMPMedia2 media = pMediaObject as IWMPMedia2;
if (media == null){
OnNotificationSoundError("Unknown error.");
return;
}
else if (media.Error != null){
OnNotificationSoundError(media.Error);
}
Marshal.ReleaseComObject(media);
}
private void OnNotificationSoundError(IWMPErrorItem error){
OnNotificationSoundError(error.errorCode == -1072885353 ? "Invalid media file." : error.errorDescription);
Marshal.ReleaseComObject(error);
}
private void OnNotificationSoundError(string message){
if (wasTryingToPlay){
wasTryingToPlay = false;
if (!ignorePlaybackError && PlaybackError != null){
PlaybackErrorEventArgs args = new PlaybackErrorEventArgs(message);
PlaybackError(this, args);
ignorePlaybackError = args.Ignore;
}
}
}
}
}

View File

@@ -0,0 +1,28 @@
using System.Runtime.InteropServices;
using TweetDck.Core.Notification.Sound;
namespace TweetDck.Core.Notification{
static class SoundNotification{
private static bool? IsWMPAvailable;
public static ISoundNotificationPlayer New(){
if (IsWMPAvailable.HasValue){
if (IsWMPAvailable.Value){
return new SoundPlayerImplWMP();
}
else{
return new SoundPlayerImplFallback();
}
}
try{
SoundPlayerImplWMP implWMP = new SoundPlayerImplWMP();
IsWMPAvailable = true;
return implWMP;
}catch(COMException){
IsWMPAvailable = false;
return new SoundPlayerImplFallback();
}
}
}
}

View File

@@ -0,0 +1,108 @@
using System;
using System.Text;
using TweetDck.Resources;
namespace TweetDck.Core.Notification{
sealed class TweetNotification{
private static string FontSizeClass { get; set; }
private static string HeadTag { get; set; }
private const string DefaultFontSizeClass = "medium";
private const string DefaultHeadTag = @"<meta charset='utf-8'><meta http-equiv='X-UA-Compatible' content='chrome=1'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/font.5ef884f9f9.css'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/app-dark.5631e0dd42.css'><style type='text/css'>body{background:#222426}</style>";
private const string CustomCSS = @"body:before{content:none}body{overflow-y:auto}.scroll-styled-v::-webkit-scrollbar{width:7px}.scroll-styled-v::-webkit-scrollbar-thumb{border-radius:0}.scroll-styled-v::-webkit-scrollbar-track{border-left:0}#td-skip{opacity:0;cursor:pointer;transition:opacity 0.15s ease}.td-hover #td-skip{opacity:0.75}#td-skip:hover{opacity:1}";
public static int FontSizeLevel{
get{
switch(FontSizeClass){
case "largest": return 4;
case "large": return 3;
case "medium": return 2;
case "small": return 1;
default: return 0;
}
}
}
private static string ExampleTweetHTML;
public static TweetNotification ExampleTweet{
get{
if (ExampleTweetHTML == null){
ExampleTweetHTML = ScriptLoader.LoadResource("pages/example.html", true);
#if DEBUG
ExampleTweetHTML = ExampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:64px'>Scrollbar test padding...</div>");
#endif
}
return new TweetNotification("Home", ExampleTweetHTML, 95, string.Empty, string.Empty, true);
}
}
public static void SetFontSizeClass(string newFSClass){
FontSizeClass = newFSClass;
}
public static void SetHeadTag(string headContents){
HeadTag = headContents;
}
public enum Position{
TopLeft, TopRight, BottomLeft, BottomRight, Custom
}
public string Column => column;
public string TweetUrl => tweetUrl;
public string QuoteUrl => quoteUrl;
private readonly string column;
private readonly string html;
private readonly int characters;
private readonly string tweetUrl;
private readonly string quoteUrl;
private readonly bool isExample;
public TweetNotification(string column, string html, int characters, string tweetUrl, string quoteUrl) : this(column, html, characters, tweetUrl, quoteUrl, false){}
private TweetNotification(string column, string html, int characters, string tweetUrl, string quoteUrl, bool isExample){
this.column = column;
this.html = html;
this.tweetUrl = tweetUrl;
this.quoteUrl = quoteUrl;
this.characters = characters;
this.isExample = isExample;
}
public int GetDisplayDuration(int value){
return 2000+Math.Max(1000, value*characters);
}
public string GenerateHtml(string bodyClasses = null, bool enableCustomCSS = true){
StringBuilder build = new StringBuilder();
build.Append("<!DOCTYPE html>");
build.Append("<html class='os-windows txt-base-").Append(FontSizeClass ?? DefaultFontSizeClass).Append("'>");
build.Append("<head>").Append(HeadTag ?? DefaultHeadTag);
if (enableCustomCSS){
build.Append("<style type='text/css'>").Append(CustomCSS).Append("</style>");
if (!string.IsNullOrEmpty(Program.UserConfig.CustomNotificationCSS)){
build.Append("<style type='text/css'>").Append(Program.UserConfig.CustomNotificationCSS).Append("</style>");
}
}
build.Append("</head>");
build.Append("<body class='scroll-styled-v");
if (!string.IsNullOrEmpty(bodyClasses)){
build.Append(' ').Append(bodyClasses);
}
build.Append('\'').Append(isExample ? " td-example-notification" : "").Append("><div class='column' style='width:100%;height:auto;overflow:initial;'>");
build.Append(html);
build.Append("</div></body>");
build.Append("</html>");
return build.ToString();
}
}
}

View File

@@ -1,5 +1,3 @@
using TweetDck.Core.Controls;
namespace TweetDck.Core.Other {
sealed partial class FormAbout {
/// <summary>
@@ -28,7 +26,7 @@ namespace TweetDck.Core.Other {
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FormAbout));
this.pictureLogo = new System.Windows.Forms.PictureBox();
this.labelDescription = new System.Windows.Forms.Label();
this.labelSourceCode = new System.Windows.Forms.LinkLabel();
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();
@@ -57,21 +55,22 @@ namespace TweetDck.Core.Other {
this.labelDescription.Location = new System.Drawing.Point(114, 12);
this.labelDescription.Name = "labelDescription";
this.labelDescription.Size = new System.Drawing.Size(232, 109);
this.labelDescription.TabIndex = 1;
this.labelDescription.TabIndex = 0;
//
// labelSourceCode
// labelTips
//
this.labelSourceCode.Dock = System.Windows.Forms.DockStyle.Fill;
this.labelSourceCode.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelSourceCode.LinkArea = new System.Windows.Forms.LinkArea(0, 0);
this.labelSourceCode.Location = new System.Drawing.Point(117, 0);
this.labelSourceCode.Margin = new System.Windows.Forms.Padding(0);
this.labelSourceCode.Name = "labelSourceCode";
this.labelSourceCode.Size = new System.Drawing.Size(99, 16);
this.labelSourceCode.TabIndex = 3;
this.labelSourceCode.Text = "Source Code";
this.labelSourceCode.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.labelSourceCode.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.OnLinkClicked);
this.labelTips.Dock = System.Windows.Forms.DockStyle.Fill;
this.labelTips.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelTips.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, 16);
this.labelTips.TabIndex = 1;
this.labelTips.TabStop = true;
this.labelTips.Text = "Tips && Tricks";
this.labelTips.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.labelTips.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.OnLinkClicked);
//
// labelWebsite
//
@@ -83,7 +82,8 @@ namespace TweetDck.Core.Other {
this.labelWebsite.Margin = new System.Windows.Forms.Padding(0);
this.labelWebsite.Name = "labelWebsite";
this.labelWebsite.Size = new System.Drawing.Size(117, 16);
this.labelWebsite.TabIndex = 2;
this.labelWebsite.TabIndex = 0;
this.labelWebsite.TabStop = true;
this.labelWebsite.Text = "Official Website";
this.labelWebsite.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
this.labelWebsite.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.OnLinkClicked);
@@ -98,13 +98,13 @@ namespace TweetDck.Core.Other {
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.labelSourceCode, 1, 0);
this.tablePanelLinks.Controls.Add(this.labelTips, 1, 0);
this.tablePanelLinks.Location = new System.Drawing.Point(12, 124);
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, 16);
this.tablePanelLinks.TabIndex = 4;
this.tablePanelLinks.TabIndex = 1;
//
// labelIssues
//
@@ -115,7 +115,8 @@ namespace TweetDck.Core.Other {
this.labelIssues.Margin = new System.Windows.Forms.Padding(0);
this.labelIssues.Name = "labelIssues";
this.labelIssues.Size = new System.Drawing.Size(118, 16);
this.labelIssues.TabIndex = 4;
this.labelIssues.TabIndex = 2;
this.labelIssues.TabStop = true;
this.labelIssues.Text = "Report an Issue";
this.labelIssues.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
this.labelIssues.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.OnLinkClicked);
@@ -146,7 +147,7 @@ namespace TweetDck.Core.Other {
private System.Windows.Forms.PictureBox pictureLogo;
private System.Windows.Forms.Label labelDescription;
private System.Windows.Forms.LinkLabel labelSourceCode;
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

@@ -3,7 +3,7 @@ using TweetDck.Core.Utils;
namespace TweetDck.Core.Other{
sealed partial class FormAbout : Form{
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(){
@@ -14,12 +14,12 @@ namespace TweetDck.Core.Other{
labelDescription.Text = Program.BrandName+" was created by chylex as a replacement to the discontinued official TweetDeck client for Windows.\n\nThe program is available for free under the open source MIT license.";
labelWebsite.Links.Add(new LinkLabel.Link(0, labelWebsite.Text.Length, Program.Website));
labelSourceCode.Links.Add(new LinkLabel.Link(0, labelSourceCode.Text.Length, GitHubLink));
labelTips.Links.Add(new LinkLabel.Link(0, labelTips.Text.Length, TipsLink));
labelIssues.Links.Add(new LinkLabel.Link(0, labelIssues.Text.Length, IssuesLink));
}
private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e){
BrowserUtils.OpenExternalBrowser(e.Link.LinkData as string);
BrowserUtils.OpenExternalBrowserUnsafe(e.Link.LinkData as string);
}
}
}

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

@@ -0,0 +1,80 @@
namespace TweetDck.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;
}
}

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

@@ -0,0 +1,138 @@
using System;
using System.Drawing;
using System.Windows.Forms;
namespace TweetDck.Core.Other{
sealed partial class FormMessage : Form{
public Button ClickedButton { get; private set; }
public int ActionPanelY => panelActions.Location.Y;
private int ClientWidth{
get => ClientSize.Width;
set => ClientSize = new Size(value, ClientSize.Height);
}
private readonly Icon icon;
private readonly bool isReady;
private int realFormWidth, minFormWidth;
private int buttonCount;
private int prevLabelWidth, prevLabelHeight;
private bool wasLabelMultiline;
public FormMessage(string caption, string text, MessageBoxIcon messageIcon){
InitializeComponent();
this.prevLabelWidth = labelMessage.Width;
this.prevLabelHeight = labelMessage.Height;
this.minFormWidth = 40;
switch(messageIcon){
case MessageBoxIcon.Information:
icon = SystemIcons.Information;
break;
case MessageBoxIcon.Warning:
icon = SystemIcons.Warning;
break;
case MessageBoxIcon.Error:
icon = SystemIcons.Error;
break;
case MessageBoxIcon.Question:
icon = SystemIcons.Question;
break;
default:
icon = null;
labelMessage.Location = new Point(labelMessage.Location.X-38, labelMessage.Location.Y);
break;
}
this.isReady = true;
this.Text = caption;
this.labelMessage.Text = text;
}
private void FormMessage_SizeChanged(object sender, EventArgs e){
RecalculateButtonLocation();
}
public Button AddButton(string title, DialogResult result = DialogResult.OK){
Button button = new Button{
Anchor = AnchorStyles.Bottom,
Font = SystemFonts.MessageBoxFont,
Location = new Point(0, 12),
Size = new Size(88, 26),
TabIndex = buttonCount,
Text = title,
UseVisualStyleBackColor = true
};
button.Click += (sender, args) => {
ClickedButton = (Button)sender;
DialogResult = result;
Close();
};
panelActions.Controls.Add(button);
++buttonCount;
minFormWidth += 96;
ClientWidth = Math.Max(realFormWidth, minFormWidth);
RecalculateButtonLocation();
return button;
}
public void AddActionControl(Control control){
panelActions.Controls.Add(control);
minFormWidth += control.Width+control.Margin.Horizontal;
ClientWidth = Math.Max(realFormWidth, minFormWidth);
}
private void RecalculateButtonLocation(){
for(int index = 0; index < buttonCount; index++){
Control control = panelActions.Controls[index];
control.Location = new Point(ClientWidth-97-index*96, control.Location.Y);
}
}
private void labelMessage_SizeChanged(object sender, EventArgs e){
if (!isReady){
return;
}
bool isMultiline = labelMessage.Height > labelMessage.MinimumSize.Height;
if (isMultiline && !wasLabelMultiline){
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y-8);
prevLabelHeight += 8;
}
else if (!isMultiline && wasLabelMultiline){
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y+8);
prevLabelHeight -= 8;
}
realFormWidth = ClientWidth-(icon == null ? 50 : 0)+labelMessage.Width-prevLabelWidth;
ClientWidth = Math.Max(realFormWidth, minFormWidth);
Height += labelMessage.Height-prevLabelHeight;
prevLabelWidth = labelMessage.Width;
prevLabelHeight = labelMessage.Height;
wasLabelMultiline = isMultiline;
}
protected override void OnPaint(PaintEventArgs e){
if (icon != null){
e.Graphics.DrawIcon(icon, 25, 26);
}
base.OnPaint(e);
}
}
}

View File

@@ -76,7 +76,7 @@
this.tabPanelPlugins.Location = new System.Drawing.Point(12, 12);
this.tabPanelPlugins.Name = "tabPanelPlugins";
this.tabPanelPlugins.Size = new System.Drawing.Size(680, 421);
this.tabPanelPlugins.TabIndex = 4;
this.tabPanelPlugins.TabIndex = 0;
//
// FormPlugins
//

View File

@@ -6,6 +6,7 @@ using System.Windows.Forms;
using TweetDck.Core.Controls;
using TweetDck.Plugins;
using TweetDck.Plugins.Controls;
using TweetDck.Plugins.Enums;
using TweetDck.Plugins.Events;
namespace TweetDck.Core.Other{
@@ -35,17 +36,19 @@ namespace TweetDck.Core.Other{
this.tabBtnOfficial = tabPanelPlugins.AddButton("", () => SelectGroup(PluginGroup.Official));
this.tabBtnCustom = tabPanelPlugins.AddButton("", () => SelectGroup(PluginGroup.Custom));
this.tabPanelPlugins.SelectTab(tabBtnOfficial);
this.pluginManager_Reloaded(pluginManager, null);
Shown += (sender, args) => {
Program.UserConfig.PluginsWindow.Restore(this, false);
this.tabPanelPlugins.SelectTab(tabBtnOfficial);
};
FormClosed += (sender, args) => {
Program.UserConfig.PluginsWindow.Save(this);
Program.UserConfig.Save();
};
Disposed += (sender, args) => this.pluginManager.Reloaded -= pluginManager_Reloaded;
}
private void SelectGroup(PluginGroup group){
@@ -75,11 +78,11 @@ namespace TweetDck.Core.Other{
}
}
flowLayoutPlugins_Resize(flowLayoutPlugins, new EventArgs());
flowLayoutPlugins.ResumeLayout(true);
flowLayoutPlugins_Resize(flowLayoutPlugins, new EventArgs());
}
private void pluginManager_Reloaded(object sender, PluginLoadEventArgs e){
private void pluginManager_Reloaded(object sender, PluginErrorEventArgs e){
tabBtnOfficial.Text = "Official: "+pluginManager.CountPluginByGroup(PluginGroup.Official);
tabBtnCustom.Text = "Custom: "+pluginManager.CountPluginByGroup(PluginGroup.Custom);
}

View File

@@ -32,7 +32,7 @@
//
this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnClose.AutoSize = true;
this.btnClose.Location = new System.Drawing.Point(443, 331);
this.btnClose.Location = new System.Drawing.Point(443, 349);
this.btnClose.Name = "btnClose";
this.btnClose.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnClose.Size = new System.Drawing.Size(49, 23);
@@ -43,8 +43,9 @@
//
// labelTip
//
this.labelTip.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.labelTip.AutoSize = true;
this.labelTip.Location = new System.Drawing.Point(12, 333);
this.labelTip.Location = new System.Drawing.Point(12, 351);
this.labelTip.Name = "labelTip";
this.labelTip.Size = new System.Drawing.Size(310, 13);
this.labelTip.TabIndex = 5;
@@ -57,19 +58,19 @@
| System.Windows.Forms.AnchorStyles.Right)));
this.tabPanel.Location = new System.Drawing.Point(12, 12);
this.tabPanel.Name = "tabPanel";
this.tabPanel.Size = new System.Drawing.Size(480, 313);
this.tabPanel.Size = new System.Drawing.Size(480, 331);
this.tabPanel.TabIndex = 3;
//
// FormSettings
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(504, 366);
this.ClientSize = new System.Drawing.Size(504, 384);
this.Controls.Add(this.labelTip);
this.Controls.Add(this.btnClose);
this.Controls.Add(this.tabPanel);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.Icon = Properties.Resources.icon;
this.Icon = global::TweetDck.Properties.Resources.icon;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "FormSettings";

View File

@@ -8,44 +8,51 @@ using TweetDck.Updates;
namespace TweetDck.Core.Other{
sealed partial class FormSettings : Form{
public const int TabIndexNotification = 1;
private readonly FormBrowser browser;
private readonly Dictionary<Type, BaseTabSettings> tabs = new Dictionary<Type, BaseTabSettings>(4);
public FormSettings(FormBrowser browserForm, PluginManager plugins, UpdateHandler updates){
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, int startTabIndex = 0){
InitializeComponent();
Text = Program.BrandName+" Settings";
this.tabPanel.SetupTabPanel(100);
this.tabPanel.AddButton("General", SelectTab<TabSettingsGeneral>);
this.tabPanel.AddButton("Notifications", () => SelectTab(() => new TabSettingsNotifications(browserForm.CreateNotificationForm(false))));
this.tabPanel.AddButton("Updates", () => SelectTab(() => new TabSettingsUpdates(updates)));
this.tabPanel.AddButton("Advanced", () => SelectTab(() => new TabSettingsAdvanced(browserForm.ReloadBrowser, plugins)));
this.tabPanel.SelectTab(tabPanel.Buttons.First());
}
this.browser = browser;
this.browser.PauseNotification();
private void SelectTab<T>() where T : BaseTabSettings, new(){
SelectTab(() => new T());
this.tabPanel.SetupTabPanel(100);
this.tabPanel.AddButton("General", () => SelectTab(() => new TabSettingsGeneral(updates)));
this.tabPanel.AddButton("Notifications", () => SelectTab(() => new TabSettingsNotifications(browser.CreateNotificationForm(false))));
this.tabPanel.AddButton("Sounds", () => SelectTab(() => new TabSettingsSounds()));
this.tabPanel.AddButton("Advanced", () => SelectTab(() => new TabSettingsAdvanced(browser.ReinjectCustomCSS, plugins)));
this.tabPanel.SelectTab(tabPanel.Buttons.ElementAt(startTabIndex));
}
private void SelectTab<T>(Func<T> constructor) where T : BaseTabSettings{
BaseTabSettings control;
if (tabs.TryGetValue(typeof(T), out control)){
if (tabs.TryGetValue(typeof(T), out BaseTabSettings control)){
tabPanel.ReplaceContent(control);
}
else{
control = tabs[typeof(T)] = constructor();
control.Ready = true;
control.OnReady();
tabPanel.ReplaceContent(control);
}
}
private void FormSettings_FormClosing(object sender, FormClosingEventArgs e){
foreach(BaseTabSettings control in tabs.Values){
control.OnClosing();
}
Program.UserConfig.Save();
foreach(BaseTabSettings control in tabs.Values){
control.Dispose();
}
browser.ResumeNotification();
}
private void btnClose_Click(object sender, EventArgs e){

View File

@@ -1,3 +0,0 @@
namespace TweetDck.Core.Other.Settings{
partial class BaseTabSettings{}
}

View File

@@ -2,17 +2,20 @@
using TweetDck.Configuration;
namespace TweetDck.Core.Other.Settings{
partial class BaseTabSettings : UserControl{
protected static UserConfig Config{
get{
return Program.UserConfig;
}
}
public bool Ready { get; set; }
class BaseTabSettings : UserControl{
protected static UserConfig Config => Program.UserConfig;
public BaseTabSettings(){
Padding = new Padding(6);
}
public virtual void OnReady(){}
public virtual void OnClosing(){}
protected static void PromptRestart(){
if (MessageBox.Show("The application must restart for the setting to take place. Do you want to restart now?", Program.BrandName+" Settings", MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.Yes){
Program.Restart();
}
}
}
}

View File

@@ -23,6 +23,7 @@
/// 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();
@@ -31,6 +32,8 @@
this.labelNotification = new System.Windows.Forms.Label();
this.textBoxNotificationCSS = new System.Windows.Forms.TextBox();
this.labelWarning = new System.Windows.Forms.Label();
this.btnOpenWiki = new System.Windows.Forms.Button();
this.timerTestBrowser = new System.Windows.Forms.Timer(this.components);
((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit();
this.splitContainer.Panel1.SuspendLayout();
this.splitContainer.Panel2.SuspendLayout();
@@ -48,18 +51,19 @@
this.textBoxBrowserCSS.Multiline = true;
this.textBoxBrowserCSS.Name = "textBoxBrowserCSS";
this.textBoxBrowserCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.textBoxBrowserCSS.Size = new System.Drawing.Size(226, 193);
this.textBoxBrowserCSS.TabIndex = 0;
this.textBoxBrowserCSS.Size = new System.Drawing.Size(373, 253);
this.textBoxBrowserCSS.TabIndex = 1;
this.textBoxBrowserCSS.WordWrap = false;
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.Location = new System.Drawing.Point(354, 227);
this.btnCancel.Location = new System.Drawing.Point(654, 287);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnCancel.Size = new System.Drawing.Size(56, 23);
this.btnCancel.TabIndex = 1;
this.btnCancel.TabIndex = 2;
this.btnCancel.Text = "Cancel";
this.btnCancel.UseVisualStyleBackColor = true;
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
@@ -67,11 +71,11 @@
// btnApply
//
this.btnApply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnApply.Location = new System.Drawing.Point(416, 227);
this.btnApply.Location = new System.Drawing.Point(716, 287);
this.btnApply.Name = "btnApply";
this.btnApply.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnApply.Size = new System.Drawing.Size(56, 23);
this.btnApply.TabIndex = 2;
this.btnApply.TabIndex = 1;
this.btnApply.Text = "Apply";
this.btnApply.UseVisualStyleBackColor = true;
this.btnApply.Click += new System.EventHandler(this.btnApply_Click);
@@ -95,10 +99,10 @@
this.splitContainer.Panel2.Controls.Add(this.labelNotification);
this.splitContainer.Panel2.Controls.Add(this.textBoxNotificationCSS);
this.splitContainer.Panel2MinSize = 64;
this.splitContainer.Size = new System.Drawing.Size(460, 209);
this.splitContainer.SplitterDistance = 226;
this.splitContainer.Size = new System.Drawing.Size(760, 269);
this.splitContainer.SplitterDistance = 373;
this.splitContainer.SplitterWidth = 5;
this.splitContainer.TabIndex = 5;
this.splitContainer.TabIndex = 0;
//
// labelBrowser
//
@@ -107,7 +111,7 @@
this.labelBrowser.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.labelBrowser.Name = "labelBrowser";
this.labelBrowser.Size = new System.Drawing.Size(45, 13);
this.labelBrowser.TabIndex = 1;
this.labelBrowser.TabIndex = 0;
this.labelBrowser.Text = "Browser";
//
// labelNotification
@@ -117,7 +121,7 @@
this.labelNotification.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.labelNotification.Name = "labelNotification";
this.labelNotification.Size = new System.Drawing.Size(60, 13);
this.labelNotification.TabIndex = 2;
this.labelNotification.TabIndex = 0;
this.labelNotification.Text = "Notification";
//
// textBoxNotificationCSS
@@ -131,7 +135,7 @@
this.textBoxNotificationCSS.Multiline = true;
this.textBoxNotificationCSS.Name = "textBoxNotificationCSS";
this.textBoxNotificationCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.textBoxNotificationCSS.Size = new System.Drawing.Size(226, 193);
this.textBoxNotificationCSS.Size = new System.Drawing.Size(372, 253);
this.textBoxNotificationCSS.TabIndex = 1;
this.textBoxNotificationCSS.WordWrap = false;
//
@@ -139,22 +143,41 @@
//
this.labelWarning.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.labelWarning.AutoSize = true;
this.labelWarning.Location = new System.Drawing.Point(9, 232);
this.labelWarning.Location = new System.Drawing.Point(91, 292);
this.labelWarning.Name = "labelWarning";
this.labelWarning.Size = new System.Drawing.Size(341, 13);
this.labelWarning.TabIndex = 6;
this.labelWarning.TabIndex = 3;
this.labelWarning.Text = "The code is not validated, please make sure there are no syntax errors.";
//
// btnOpenWiki
//
this.btnOpenWiki.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.btnOpenWiki.AutoSize = true;
this.btnOpenWiki.Location = new System.Drawing.Point(12, 287);
this.btnOpenWiki.Name = "btnOpenWiki";
this.btnOpenWiki.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnOpenWiki.Size = new System.Drawing.Size(73, 23);
this.btnOpenWiki.TabIndex = 4;
this.btnOpenWiki.Text = "Open Wiki";
this.btnOpenWiki.UseVisualStyleBackColor = true;
this.btnOpenWiki.Click += new System.EventHandler(this.btnOpenWiki_Click);
//
// timerTestBrowser
//
this.timerTestBrowser.Interval = 500;
this.timerTestBrowser.Tick += new System.EventHandler(this.timerTestBrowser_Tick);
//
// DialogSettingsCSS
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(484, 262);
this.ClientSize = new System.Drawing.Size(784, 322);
this.Controls.Add(this.btnOpenWiki);
this.Controls.Add(this.labelWarning);
this.Controls.Add(this.splitContainer);
this.Controls.Add(this.btnApply);
this.Controls.Add(this.btnCancel);
this.MinimumSize = new System.Drawing.Size(500, 160);
this.MinimumSize = new System.Drawing.Size(600, 160);
this.Name = "DialogSettingsCSS";
this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
@@ -179,5 +202,7 @@
private System.Windows.Forms.Label labelBrowser;
private System.Windows.Forms.Label labelNotification;
private System.Windows.Forms.Label labelWarning;
private System.Windows.Forms.Button btnOpenWiki;
private System.Windows.Forms.Timer timerTestBrowser;
}
}

View File

@@ -1,29 +1,43 @@
using System;
using System.Windows.Forms;
using TweetDck.Core.Controls;
using TweetDck.Core.Utils;
namespace TweetDck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsCSS : Form{
public string BrowserCSS{
get{
return textBoxBrowserCSS.Text;
}
}
public string NotificationCSS{
get{
return textBoxNotificationCSS.Text;
}
}
public string BrowserCSS => textBoxBrowserCSS.Text;
public string NotificationCSS => textBoxNotificationCSS.Text;
public DialogSettingsCSS(){
private readonly Action<string> reinjectBrowserCSS;
public DialogSettingsCSS(Action<string> reinjectBrowserCSS){
InitializeComponent();
Text = Program.BrandName+" Settings - CSS";
this.reinjectBrowserCSS = reinjectBrowserCSS;
textBoxBrowserCSS.EnableMultilineShortcuts();
textBoxBrowserCSS.Text = Program.UserConfig.CustomBrowserCSS ?? "";
textBoxNotificationCSS.EnableMultilineShortcuts();
textBoxNotificationCSS.Text = Program.UserConfig.CustomNotificationCSS ?? "";
}
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 btnOpenWiki_Click(object sender, EventArgs e){
BrowserUtils.OpenExternalBrowserUnsafe("https://github.com/chylex/TweetDuck/wiki");
}
private void btnApply_Click(object sender, EventArgs e){
DialogResult = DialogResult.OK;
Close();

View File

@@ -40,7 +40,7 @@
this.textBoxArgs.Multiline = true;
this.textBoxArgs.Name = "textBoxArgs";
this.textBoxArgs.Size = new System.Drawing.Size(460, 193);
this.textBoxArgs.TabIndex = 0;
this.textBoxArgs.TabIndex = 1;
//
// btnCancel
//
@@ -49,7 +49,7 @@
this.btnCancel.Name = "btnCancel";
this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnCancel.Size = new System.Drawing.Size(56, 23);
this.btnCancel.TabIndex = 1;
this.btnCancel.TabIndex = 3;
this.btnCancel.Text = "Cancel";
this.btnCancel.UseVisualStyleBackColor = true;
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
@@ -74,7 +74,7 @@
this.btnHelp.Name = "btnHelp";
this.btnHelp.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnHelp.Size = new System.Drawing.Size(124, 23);
this.btnHelp.TabIndex = 3;
this.btnHelp.TabIndex = 4;
this.btnHelp.Text = "List of Chromium Args";
this.btnHelp.UseVisualStyleBackColor = true;
this.btnHelp.Click += new System.EventHandler(this.btnHelp_Click);
@@ -86,7 +86,7 @@
this.labelWarning.Margin = new System.Windows.Forms.Padding(3, 0, 3, 3);
this.labelWarning.Name = "labelWarning";
this.labelWarning.Size = new System.Drawing.Size(423, 13);
this.labelWarning.TabIndex = 4;
this.labelWarning.TabIndex = 0;
this.labelWarning.Text = "Warning: Some arguments may cause the program to stop working, edit at your own r" +
"isk.";
//

View File

@@ -1,27 +1,24 @@
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using TweetDck.Core.Controls;
using TweetDck.Core.Utils;
namespace TweetDck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsCefArgs : Form{
public string CefArgs{
get{
return textBoxArgs.Text;
}
}
public string CefArgs => textBoxArgs.Text;
public DialogSettingsCefArgs(){
InitializeComponent();
Text = Program.BrandName+" Settings - CEF Arguments";
textBoxArgs.EnableMultilineShortcuts();
textBoxArgs.Text = Program.UserConfig.CustomCefArgs ?? "";
textBoxArgs.Select(textBoxArgs.Text.Length, 0);
}
private void btnHelp_Click(object sender, EventArgs e){
BrowserUtils.OpenExternalBrowser("http://peter.sh/experiments/chromium-command-line-switches/");
BrowserUtils.OpenExternalBrowserUnsafe("http://peter.sh/experiments/chromium-command-line-switches/");
}
private void btnApply_Click(object sender, EventArgs e){
@@ -33,7 +30,7 @@ namespace TweetDck.Core.Other.Settings.Dialogs{
return;
}
int count = CommandLineArgsParser.AddToDictionary(CefArgs, new Dictionary<string, string>());
int count = CommandLineArgsParser.ReadCefArguments(CefArgs).Count;
string prompt = count == 0 && !string.IsNullOrWhiteSpace(prevArgs) ? "All arguments will be removed from the settings. Continue?" : count+(count == 1 ? " argument" : " arguments")+" will be added to the settings. Continue?";
if (MessageBox.Show(prompt, "Confirm CEF Arguments", MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.OK){

View File

@@ -0,0 +1,130 @@
namespace TweetDck.Core.Other.Settings.Dialogs {
partial class DialogSettingsExport {
/// <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.btnApply = new System.Windows.Forms.Button();
this.cbConfig = 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.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.Location = new System.Drawing.Point(176, 97);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnCancel.Size = new System.Drawing.Size(56, 23);
this.btnCancel.TabIndex = 4;
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.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.btnApply.Location = new System.Drawing.Point(144, 97);
this.btnApply.Name = "btnApply";
this.btnApply.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnApply.Size = new System.Drawing.Size(26, 23);
this.btnApply.TabIndex = 3;
this.btnApply.Text = " ";
this.btnApply.UseVisualStyleBackColor = true;
this.btnApply.Click += new System.EventHandler(this.btnApply_Click);
//
// cbConfig
//
this.cbConfig.AutoSize = true;
this.cbConfig.Location = new System.Drawing.Point(13, 13);
this.cbConfig.Name = "cbConfig";
this.cbConfig.Size = new System.Drawing.Size(106, 17);
this.cbConfig.TabIndex = 0;
this.cbConfig.Text = "Program Settings";
this.toolTip.SetToolTip(this.cbConfig, "Interface, notification, and update settings.\r\nIncludes a list of disabled plugin" +
"s.");
this.cbConfig.UseVisualStyleBackColor = true;
this.cbConfig.CheckedChanged += new System.EventHandler(this.cbConfig_CheckedChanged);
//
// cbSession
//
this.cbSession.AutoSize = true;
this.cbSession.Location = new System.Drawing.Point(13, 37);
this.cbSession.Name = "cbSession";
this.cbSession.Size = new System.Drawing.Size(92, 17);
this.cbSession.TabIndex = 1;
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.cbSession_CheckedChanged);
//
// cbPluginData
//
this.cbPluginData.AutoSize = true;
this.cbPluginData.Location = new System.Drawing.Point(13, 61);
this.cbPluginData.Name = "cbPluginData";
this.cbPluginData.Size = new System.Drawing.Size(81, 17);
this.cbPluginData.TabIndex = 2;
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.cbPluginData_CheckedChanged);
//
// DialogSettingsExport
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(244, 132);
this.Controls.Add(this.cbPluginData);
this.Controls.Add(this.cbSession);
this.Controls.Add(this.cbConfig);
this.Controls.Add(this.btnApply);
this.Controls.Add(this.btnCancel);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.MinimumSize = new System.Drawing.Size(200, 170);
this.Name = "DialogSettingsExport";
this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button btnCancel;
private System.Windows.Forms.Button btnApply;
private System.Windows.Forms.CheckBox cbConfig;
private System.Windows.Forms.CheckBox cbSession;
private System.Windows.Forms.CheckBox cbPluginData;
private System.Windows.Forms.ToolTip toolTip;
}
}

View File

@@ -0,0 +1,80 @@
using System;
using System.Windows.Forms;
using TweetDck.Core.Other.Settings.Export;
namespace TweetDck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsExport : Form{
public static DialogSettingsExport Import(ExportFileFlags flags){
return new DialogSettingsExport(flags);
}
public static DialogSettingsExport Export(){
return new DialogSettingsExport(ExportFileFlags.None);
}
public ExportFileFlags Flags{
get => selectedFlags;
set{
// this will call events and SetFlag, which also updates the UI
cbConfig.Checked = value.HasFlag(ExportFileFlags.Config);
cbSession.Checked = value.HasFlag(ExportFileFlags.Session);
cbPluginData.Checked = value.HasFlag(ExportFileFlags.PluginData);
}
}
private readonly bool isExporting;
private ExportFileFlags selectedFlags = ExportFileFlags.None;
private DialogSettingsExport(ExportFileFlags importFlags){
InitializeComponent();
this.isExporting = importFlags == ExportFileFlags.None;
if (isExporting){
Text = "Export Profile";
btnApply.Text = "Export Profile";
Flags = ExportFileFlags.All & ~ExportFileFlags.Session;
}
else{
Text = "Import Profile";
Flags = importFlags;
cbConfig.Enabled = cbConfig.Checked;
cbSession.Enabled = cbSession.Checked;
cbPluginData.Enabled = cbPluginData.Checked;
}
}
private void SetFlag(ExportFileFlags flag, bool enable){
selectedFlags = enable ? selectedFlags | flag : selectedFlags & ~flag;
btnApply.Enabled = selectedFlags != ExportFileFlags.None;
if (!isExporting){
btnApply.Text = selectedFlags.HasFlag(ExportFileFlags.Session) ? "Import && Restart" : "Import Profile";
}
}
private void cbConfig_CheckedChanged(object sender, EventArgs e){
SetFlag(ExportFileFlags.Config, cbConfig.Checked);
}
private void cbSession_CheckedChanged(object sender, EventArgs e){
SetFlag(ExportFileFlags.Session, cbSession.Checked);
}
private void cbPluginData_CheckedChanged(object sender, EventArgs e){
SetFlag(ExportFileFlags.PluginData, cbPluginData.Checked);
}
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,161 @@
namespace TweetDck.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.cbDebugUpdates = new System.Windows.Forms.CheckBox();
this.labelLocale = new System.Windows.Forms.Label();
this.comboLocale = new System.Windows.Forms.ComboBox();
this.labelDataFolder = new System.Windows.Forms.Label();
this.tbDataFolder = new System.Windows.Forms.TextBox();
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.Location = new System.Drawing.Point(160, 171);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnCancel.Size = new System.Drawing.Size(56, 23);
this.btnCancel.TabIndex = 7;
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.Location = new System.Drawing.Point(97, 171);
this.btnRestart.Name = "btnRestart";
this.btnRestart.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnRestart.Size = new System.Drawing.Size(57, 23);
this.btnRestart.TabIndex = 6;
this.btnRestart.Text = "Restart";
this.btnRestart.UseVisualStyleBackColor = true;
this.btnRestart.Click += new System.EventHandler(this.btnRestart_Click);
//
// cbLogging
//
this.cbLogging.AutoSize = true;
this.cbLogging.Location = new System.Drawing.Point(12, 12);
this.cbLogging.Name = "cbLogging";
this.cbLogging.Size = new System.Drawing.Size(64, 17);
this.cbLogging.TabIndex = 0;
this.cbLogging.Text = "Logging";
this.toolTip.SetToolTip(this.cbLogging, "Logging JavaScript output into a\r\ndebug.txt file in the data folder.");
this.cbLogging.UseVisualStyleBackColor = true;
//
// cbDebugUpdates
//
this.cbDebugUpdates.AutoSize = true;
this.cbDebugUpdates.Location = new System.Drawing.Point(12, 35);
this.cbDebugUpdates.Name = "cbDebugUpdates";
this.cbDebugUpdates.Size = new System.Drawing.Size(127, 17);
this.cbDebugUpdates.TabIndex = 1;
this.cbDebugUpdates.Text = "Pre-Release Updates";
this.toolTip.SetToolTip(this.cbDebugUpdates, "Allows updating to pre-releases.");
this.cbDebugUpdates.UseVisualStyleBackColor = true;
//
// labelLocale
//
this.labelLocale.AutoSize = true;
this.labelLocale.Location = new System.Drawing.Point(12, 67);
this.labelLocale.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelLocale.Name = "labelLocale";
this.labelLocale.Size = new System.Drawing.Size(39, 13);
this.labelLocale.TabIndex = 2;
this.labelLocale.Text = "Locale";
//
// comboLocale
//
this.comboLocale.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.comboLocale.FormattingEnabled = true;
this.comboLocale.Location = new System.Drawing.Point(15, 83);
this.comboLocale.Name = "comboLocale";
this.comboLocale.Size = new System.Drawing.Size(201, 21);
this.comboLocale.TabIndex = 3;
//
// labelDataFolder
//
this.labelDataFolder.AutoSize = true;
this.labelDataFolder.Location = new System.Drawing.Point(12, 119);
this.labelDataFolder.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelDataFolder.Name = "labelDataFolder";
this.labelDataFolder.Size = new System.Drawing.Size(62, 13);
this.labelDataFolder.TabIndex = 4;
this.labelDataFolder.Text = "Data Folder";
//
// tbDataFolder
//
this.tbDataFolder.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.tbDataFolder.Location = new System.Drawing.Point(15, 135);
this.tbDataFolder.Name = "tbDataFolder";
this.tbDataFolder.Size = new System.Drawing.Size(201, 20);
this.tbDataFolder.TabIndex = 5;
//
// DialogSettingsRestart
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(228, 206);
this.Controls.Add(this.tbDataFolder);
this.Controls.Add(this.labelDataFolder);
this.Controls.Add(this.comboLocale);
this.Controls.Add(this.labelLocale);
this.Controls.Add(this.cbDebugUpdates);
this.Controls.Add(this.cbLogging);
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.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.CheckBox cbDebugUpdates;
private System.Windows.Forms.Label labelLocale;
private System.Windows.Forms.ComboBox comboLocale;
private System.Windows.Forms.Label labelDataFolder;
private System.Windows.Forms.TextBox tbDataFolder;
}
}

View File

@@ -0,0 +1,62 @@
using System;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using TweetDck.Configuration;
using TweetDck.Core.Utils;
namespace TweetDck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsRestart : Form{
private const string DefaultLocale = "en-US";
public CommandLineArgs Args { get; private set; }
public DialogSettingsRestart(CommandLineArgs currentArgs){
InitializeComponent();
try{
object[] locales = Directory.EnumerateFiles(Path.Combine(Program.ProgramPath, "locales"), "*.pak", SearchOption.TopDirectoryOnly).Select(Path.GetFileNameWithoutExtension).ToArray<object>();
comboLocale.Items.AddRange(locales);
}catch{
comboLocale.Items.Add(DefaultLocale);
}
cbLogging.Checked = currentArgs.HasFlag(Arguments.ArgLogging);
cbDebugUpdates.Checked = currentArgs.HasFlag(Arguments.ArgDebugUpdates);
comboLocale.SelectedItem = currentArgs.GetValue(Arguments.ArgLocale, DefaultLocale);
tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder, string.Empty);
Text = Program.BrandName+" Arguments";
}
private void btnRestart_Click(object sender, EventArgs e){
Args = new CommandLineArgs();
if (cbLogging.Checked){
Args.AddFlag(Arguments.ArgLogging);
}
if (cbDebugUpdates.Checked){
Args.AddFlag(Arguments.ArgDebugUpdates);
}
string locale = comboLocale.SelectedItem as string;
if (!string.IsNullOrWhiteSpace(locale) && locale != DefaultLocale){
Args.SetValue(Arguments.ArgLocale, locale);
}
if (!string.IsNullOrWhiteSpace(tbDataFolder.Text)){
Args.SetValue(Arguments.ArgDataFolder, tbDataFolder.Text);
}
DialogResult = DialogResult.OK;
Close();
}
private void btnCancel_Click(object sender, EventArgs e){
DialogResult = DialogResult.Cancel;
Close();
}
}
}

View File

@@ -4,7 +4,7 @@ using System.Text;
namespace TweetDck.Core.Other.Settings.Export{
class CombinedFileStream : IDisposable{
public const char KeySeparator = '/';
public const char KeySeparator = '|';
private readonly Stream stream;
@@ -12,6 +12,10 @@ namespace TweetDck.Core.Other.Settings.Export{
this.stream = stream;
}
public void WriteFile(string[] identifier, string path){
WriteFile(string.Join(KeySeparator.ToString(), identifier), path);
}
public void WriteFile(string identifier, string path){
byte[] name = Encoding.UTF8.GetBytes(identifier);
@@ -59,6 +63,26 @@ namespace TweetDck.Core.Other.Settings.Export{
return new Entry(Encoding.UTF8.GetString(name), contents);
}
public string SkipFile(){
int nameLength = stream.ReadByte();
if (nameLength == -1){
return null;
}
byte[] name = new byte[nameLength];
stream.Read(name, 0, nameLength);
byte[] contentLength = new byte[4];
stream.Read(contentLength, 0, 4);
stream.Position += BitConverter.ToInt32(contentLength, 0);
string keyName = Encoding.UTF8.GetString(name);
int separatorIndex = keyName.IndexOf(KeySeparator);
return separatorIndex == -1 ? keyName : keyName.Substring(0, separatorIndex);
}
public void Flush(){
stream.Flush();
}
@@ -68,7 +92,7 @@ namespace TweetDck.Core.Other.Settings.Export{
}
public class Entry{
public string Identifier { get; private set; }
public string Identifier { get; }
public string KeyName{
get{
@@ -77,6 +101,13 @@ namespace TweetDck.Core.Other.Settings.Export{
}
}
public string[] KeyValue{
get{
int index = Identifier.IndexOf(KeySeparator);
return index == -1 ? new string[0] : Identifier.Substring(index+1).Split(KeySeparator);
}
}
private readonly byte[] contents;
public Entry(string identifier, byte[] contents){
@@ -90,8 +121,11 @@ namespace TweetDck.Core.Other.Settings.Export{
public void WriteToFile(string path, bool createDirectory){
if (createDirectory){
// ReSharper disable once AssignNullToNotNullAttribute
Directory.CreateDirectory(Path.GetDirectoryName(path));
string dir = Path.GetDirectoryName(path);
if (!string.IsNullOrEmpty(dir)){
Directory.CreateDirectory(dir);
}
}
File.WriteAllBytes(path, contents);

View File

@@ -0,0 +1,12 @@
using System;
namespace TweetDck.Core.Other.Settings.Export{
[Flags]
enum ExportFileFlags{
None = 0,
Config = 1,
Session = 2,
PluginData = 4,
All = Config|Session|PluginData
}
}

View File

@@ -1,15 +1,16 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using TweetDck.Configuration;
using TweetDck.Plugins;
using TweetDck.Plugins.Enums;
namespace TweetDck.Core.Other.Settings.Export{
sealed class ExportManager{
public static readonly string CookiesPath = Path.Combine(Program.StoragePath, "Cookies");
public static readonly string TempCookiesPath = Path.Combine(Program.StoragePath, "CookiesTmp");
private static readonly string CookiesPath = Path.Combine(Program.StoragePath, "Cookies");
private static readonly string TempCookiesPath = Path.Combine(Program.StoragePath, "CookiesTmp");
public bool IsRestarting { get; private set; }
public Exception LastException { get; private set; }
@@ -22,41 +23,28 @@ namespace TweetDck.Core.Other.Settings.Export{
this.plugins = plugins;
}
public bool Export(bool includeSession){
public bool Export(ExportFileFlags flags){
try{
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
stream.WriteFile("config", Program.ConfigFilePath);
if (flags.HasFlag(ExportFileFlags.Config)){
stream.WriteFile("config", Program.ConfigFilePath);
}
foreach(PathInfo path in EnumerateFilesRelative(plugins.PathOfficialPlugins)){
string[] split = path.Relative.Split(CombinedFileStream.KeySeparator);
if (flags.HasFlag(ExportFileFlags.PluginData)){
stream.WriteFile("plugin.config", Program.PluginConfigFilePath);
if (split.Length < 3){
continue;
}
else if (split.Length == 3){
if (split[2].Equals(".meta", StringComparison.OrdinalIgnoreCase) ||
split[2].Equals("browser.js", StringComparison.OrdinalIgnoreCase) ||
split[2].Equals("notification.js", StringComparison.OrdinalIgnoreCase)){
continue;
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){
MessageBox.Show("Could not include a plugin file in the export. "+e.Message, "Export Profile", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
}
try{
stream.WriteFile("plugin.off"+path.Relative, path.Full);
}catch(ArgumentOutOfRangeException e){
MessageBox.Show("Could not include a file in the export. "+e.Message, "Export Profile", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
foreach(PathInfo path in EnumerateFilesRelative(plugins.PathCustomPlugins)){
try{
stream.WriteFile("plugin.usr"+path.Relative, path.Full);
}catch(ArgumentOutOfRangeException e){
MessageBox.Show("Could not include a file in the export. "+e.Message, "Export Profile", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
if (includeSession){
if (flags.HasFlag(ExportFileFlags.Session)){
stream.WriteFile("cookies", CookiesPath);
}
@@ -70,9 +58,41 @@ namespace TweetDck.Core.Other.Settings.Export{
}
}
public bool Import(){
public ExportFileFlags GetImportFlags(){
ExportFileFlags flags = ExportFileFlags.None;
try{
bool updatedPlugins = false;
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":
flags |= ExportFileFlags.Config;
break;
case "plugin.config":
case "plugin.data":
flags |= ExportFileFlags.PluginData;
break;
case "cookies":
flags |= ExportFileFlags.Session;
break;
}
}
}
}catch(Exception e){
LastException = e;
flags = ExportFileFlags.None;
}
return flags;
}
public bool Import(ExportFileFlags flags){
try{
HashSet<string> missingPlugins = new HashSet<string>();
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))){
CombinedFileStream.Entry entry;
@@ -80,33 +100,36 @@ namespace TweetDck.Core.Other.Settings.Export{
while((entry = stream.ReadFile()) != null){
switch(entry.KeyName){
case "config":
entry.WriteToFile(Program.ConfigFilePath);
Program.ReloadConfig();
break;
case "plugin.off":
string root = Path.Combine(plugins.PathOfficialPlugins, entry.Identifier.Split(CombinedFileStream.KeySeparator)[1]);
if (Directory.Exists(root)){
entry.WriteToFile(Path.Combine(plugins.PathOfficialPlugins, entry.Identifier.Substring(entry.KeyName.Length+1)), true);
updatedPlugins = true;
if (flags.HasFlag(ExportFileFlags.Config)){
entry.WriteToFile(Program.ConfigFilePath);
}
break;
case "plugin.usr":
entry.WriteToFile(Path.Combine(plugins.PathCustomPlugins, entry.Identifier.Substring(entry.KeyName.Length+1)), true);
updatedPlugins = true;
case "plugin.config":
if (flags.HasFlag(ExportFileFlags.PluginData)){
entry.WriteToFile(Program.PluginConfigFilePath);
}
break;
case "plugin.data":
if (flags.HasFlag(ExportFileFlags.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 (MessageBox.Show("Do you want to import the login session? This will restart "+Program.BrandName+".", "Importing "+Program.BrandName+" Settings", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
if (flags.HasFlag(ExportFileFlags.Session)){
entry.WriteToFile(Path.Combine(Program.StoragePath, TempCookiesPath));
// okay to and restart, 'cookies' is always the last entry
IsRestarting = true;
Process.Start(Application.ExecutablePath, "-restart -importcookies");
Application.Exit();
}
break;
@@ -114,8 +137,15 @@ namespace TweetDck.Core.Other.Settings.Export{
}
}
if (updatedPlugins){
plugins.Reload();
if (missingPlugins.Count > 0){
MessageBox.Show("Detected missing plugins when importing plugin data:"+Environment.NewLine+string.Join(Environment.NewLine, missingPlugins), "Importing "+Program.BrandName+" Profile", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
if (IsRestarting){
Program.Restart(new string[]{ Arguments.ArgImportCookies });
}
else{
Program.ReloadConfig();
}
return true;
@@ -125,11 +155,25 @@ namespace TweetDck.Core.Other.Settings.Export{
}
}
public static void ImportCookies(){
if (File.Exists(TempCookiesPath)){
try{
if (File.Exists(CookiesPath)){
File.Delete(CookiesPath);
}
File.Move(TempCookiesPath, CookiesPath);
}catch(Exception e){
Program.Reporter.HandleException("Profile Import Error", "Could not import the cookie file to restore login session.", true, e);
}
}
}
private static IEnumerable<PathInfo> EnumerateFilesRelative(string root){
return Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo{
return Directory.Exists(root) ? Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo{
Full = fullPath,
Relative = fullPath.Substring(root.Length).Replace(Path.DirectorySeparatorChar, CombinedFileStream.KeySeparator) // includes leading separator character
});
Relative = fullPath.Substring(root.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) // strip leading separator character
}) : Enumerable.Empty<PathInfo>();
}
private class PathInfo{

View File

@@ -29,13 +29,19 @@
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.btnReset = new System.Windows.Forms.Button();
this.btnImport = new System.Windows.Forms.Button();
this.btnExport = new System.Windows.Forms.Button();
this.groupPerformance = new System.Windows.Forms.GroupBox();
this.groupConfiguration = new System.Windows.Forms.GroupBox();
this.groupApp = new System.Windows.Forms.GroupBox();
this.groupPerformance.SuspendLayout();
this.groupConfiguration.SuspendLayout();
this.groupApp.SuspendLayout();
this.SuspendLayout();
//
// btnClearCache
@@ -43,88 +49,122 @@
this.btnClearCache.Location = new System.Drawing.Point(6, 44);
this.btnClearCache.Name = "btnClearCache";
this.btnClearCache.Size = new System.Drawing.Size(171, 23);
this.btnClearCache.TabIndex = 14;
this.btnClearCache.TabIndex = 1;
this.btnClearCache.Text = "Clear Cache (calculating)";
this.toolTip.SetToolTip(this.btnClearCache, "Clearing cache will free up space taken by downloaded images and other resources." +
"");
this.btnClearCache.UseVisualStyleBackColor = true;
this.btnClearCache.Click += new System.EventHandler(this.btnClearCache_Click);
//
// checkHardwareAcceleration
//
this.checkHardwareAcceleration.AutoSize = true;
this.checkHardwareAcceleration.Location = new System.Drawing.Point(6, 21);
this.checkHardwareAcceleration.Margin = new System.Windows.Forms.Padding(3, 5, 3, 3);
this.checkHardwareAcceleration.Location = new System.Drawing.Point(9, 21);
this.checkHardwareAcceleration.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3);
this.checkHardwareAcceleration.Name = "checkHardwareAcceleration";
this.checkHardwareAcceleration.Size = new System.Drawing.Size(134, 17);
this.checkHardwareAcceleration.TabIndex = 12;
this.checkHardwareAcceleration.TabIndex = 0;
this.checkHardwareAcceleration.Text = "Hardware Acceleration";
this.toolTip.SetToolTip(this.checkHardwareAcceleration, "Uses your graphics card to improve performance.\r\nDisable if you experience issues" +
" with rendering.");
this.checkHardwareAcceleration.UseVisualStyleBackColor = true;
this.checkHardwareAcceleration.CheckedChanged += new System.EventHandler(this.checkHardwareAcceleration_CheckedChanged);
//
// btnEditCefArgs
//
this.btnEditCefArgs.Location = new System.Drawing.Point(6, 19);
this.btnEditCefArgs.Name = "btnEditCefArgs";
this.btnEditCefArgs.Size = new System.Drawing.Size(171, 23);
this.btnEditCefArgs.TabIndex = 15;
this.btnEditCefArgs.TabIndex = 0;
this.btnEditCefArgs.Text = "Edit CEF Arguments";
this.toolTip.SetToolTip(this.btnEditCefArgs, "Set custom command line arguments for Chromium Embedded Framework.");
this.btnEditCefArgs.UseVisualStyleBackColor = true;
this.btnEditCefArgs.Click += new System.EventHandler(this.btnEditCefArgs_Click);
//
// btnEditCSS
//
this.btnEditCSS.Location = new System.Drawing.Point(6, 48);
this.btnEditCSS.Name = "btnEditCSS";
this.btnEditCSS.Size = new System.Drawing.Size(171, 23);
this.btnEditCSS.TabIndex = 16;
this.btnEditCSS.TabIndex = 1;
this.btnEditCSS.Text = "Edit CSS";
this.toolTip.SetToolTip(this.btnEditCSS, "Set custom CSS for browser and notification windows.");
this.btnEditCSS.UseVisualStyleBackColor = true;
this.btnEditCSS.Click += new System.EventHandler(this.btnEditCSS_Click);
//
// btnRestartArgs
//
this.btnRestartArgs.Location = new System.Drawing.Point(6, 106);
this.btnRestartArgs.Name = "btnRestartArgs";
this.btnRestartArgs.Size = new System.Drawing.Size(171, 23);
this.btnRestartArgs.TabIndex = 3;
this.btnRestartArgs.Text = "Restart with Arguments";
this.toolTip.SetToolTip(this.btnRestartArgs, "Restarts the program with customizable\r\ncommand line arguments.");
this.btnRestartArgs.UseVisualStyleBackColor = true;
//
// btnRestart
//
this.btnRestart.Location = new System.Drawing.Point(6, 77);
this.btnRestart.Name = "btnRestart";
this.btnRestart.Size = new System.Drawing.Size(171, 23);
this.btnRestart.TabIndex = 2;
this.btnRestart.Text = "Restart the Program";
this.toolTip.SetToolTip(this.btnRestart, "Restarts the program using the same command\r\nline arguments that were used at lau" +
"nch.");
this.btnRestart.UseVisualStyleBackColor = true;
//
// btnOpenAppFolder
//
this.btnOpenAppFolder.Location = new System.Drawing.Point(6, 19);
this.btnOpenAppFolder.Name = "btnOpenAppFolder";
this.btnOpenAppFolder.Size = new System.Drawing.Size(171, 23);
this.btnOpenAppFolder.TabIndex = 0;
this.btnOpenAppFolder.Text = "Open Program Folder";
this.toolTip.SetToolTip(this.btnOpenAppFolder, "Opens the folder where the app is located.");
this.btnOpenAppFolder.UseVisualStyleBackColor = true;
//
// btnOpenDataFolder
//
this.btnOpenDataFolder.Location = new System.Drawing.Point(6, 48);
this.btnOpenDataFolder.Name = "btnOpenDataFolder";
this.btnOpenDataFolder.Size = new System.Drawing.Size(171, 23);
this.btnOpenDataFolder.TabIndex = 1;
this.btnOpenDataFolder.Text = "Open Data Folder";
this.toolTip.SetToolTip(this.btnOpenDataFolder, "Opens the folder where your profile data is located.");
this.btnOpenDataFolder.UseVisualStyleBackColor = true;
//
// btnReset
//
this.btnReset.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.btnReset.AutoSize = true;
this.btnReset.Location = new System.Drawing.Point(190, 250);
this.btnReset.Location = new System.Drawing.Point(190, 268);
this.btnReset.Name = "btnReset";
this.btnReset.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnReset.Size = new System.Drawing.Size(102, 23);
this.btnReset.TabIndex = 17;
this.btnReset.TabIndex = 5;
this.btnReset.Text = "Restore Defaults";
this.btnReset.UseVisualStyleBackColor = true;
this.btnReset.Click += new System.EventHandler(this.btnReset_Click);
//
// btnImport
//
this.btnImport.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.btnImport.AutoSize = true;
this.btnImport.Location = new System.Drawing.Point(100, 250);
this.btnImport.Location = new System.Drawing.Point(100, 268);
this.btnImport.Name = "btnImport";
this.btnImport.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnImport.Size = new System.Drawing.Size(84, 23);
this.btnImport.TabIndex = 16;
this.btnImport.TabIndex = 4;
this.btnImport.Text = "Import Profile";
this.btnImport.UseVisualStyleBackColor = true;
this.btnImport.Click += new System.EventHandler(this.btnImport_Click);
//
// btnExport
//
this.btnExport.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.btnExport.AutoSize = true;
this.btnExport.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.btnExport.Location = new System.Drawing.Point(9, 250);
this.btnExport.Location = new System.Drawing.Point(9, 268);
this.btnExport.Name = "btnExport";
this.btnExport.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnExport.Size = new System.Drawing.Size(85, 23);
this.btnExport.TabIndex = 15;
this.btnExport.TabIndex = 3;
this.btnExport.Text = "Export Profile";
this.btnExport.UseVisualStyleBackColor = true;
this.btnExport.Click += new System.EventHandler(this.btnExport_Click);
//
// groupPerformance
//
@@ -133,7 +173,7 @@
this.groupPerformance.Location = new System.Drawing.Point(9, 9);
this.groupPerformance.Name = "groupPerformance";
this.groupPerformance.Size = new System.Drawing.Size(183, 74);
this.groupPerformance.TabIndex = 18;
this.groupPerformance.TabIndex = 0;
this.groupPerformance.TabStop = false;
this.groupPerformance.Text = "Performance";
//
@@ -144,24 +184,39 @@
this.groupConfiguration.Location = new System.Drawing.Point(9, 89);
this.groupConfiguration.Name = "groupConfiguration";
this.groupConfiguration.Size = new System.Drawing.Size(183, 77);
this.groupConfiguration.TabIndex = 19;
this.groupConfiguration.TabIndex = 1;
this.groupConfiguration.TabStop = false;
this.groupConfiguration.Text = "Configuration";
//
// groupApp
//
this.groupApp.Controls.Add(this.btnOpenDataFolder);
this.groupApp.Controls.Add(this.btnOpenAppFolder);
this.groupApp.Controls.Add(this.btnRestartArgs);
this.groupApp.Controls.Add(this.btnRestart);
this.groupApp.Location = new System.Drawing.Point(198, 9);
this.groupApp.Name = "groupApp";
this.groupApp.Size = new System.Drawing.Size(183, 135);
this.groupApp.TabIndex = 2;
this.groupApp.TabStop = false;
this.groupApp.Text = "App";
//
// TabSettingsAdvanced
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.groupApp);
this.Controls.Add(this.groupConfiguration);
this.Controls.Add(this.groupPerformance);
this.Controls.Add(this.btnReset);
this.Controls.Add(this.btnImport);
this.Controls.Add(this.btnExport);
this.Name = "TabSettingsAdvanced";
this.Size = new System.Drawing.Size(478, 282);
this.Size = new System.Drawing.Size(478, 300);
this.groupPerformance.ResumeLayout(false);
this.groupPerformance.PerformLayout();
this.groupConfiguration.ResumeLayout(false);
this.groupApp.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
@@ -179,5 +234,10 @@
private System.Windows.Forms.GroupBox groupConfiguration;
private System.Windows.Forms.Button btnEditCefArgs;
private System.Windows.Forms.Button btnEditCSS;
private System.Windows.Forms.GroupBox groupApp;
private System.Windows.Forms.Button btnRestartArgs;
private System.Windows.Forms.Button btnRestart;
private System.Windows.Forms.Button btnOpenAppFolder;
private System.Windows.Forms.Button btnOpenDataFolder;
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.Windows.Forms;
using TweetDck.Configuration;
using TweetDck.Core.Controls;
using TweetDck.Core.Other.Settings.Dialogs;
using TweetDck.Core.Other.Settings.Export;
@@ -9,13 +10,13 @@ using TweetDck.Plugins;
namespace TweetDck.Core.Other.Settings{
partial class TabSettingsAdvanced : BaseTabSettings{
private readonly Action browserReloadAction;
private readonly Action<string> reinjectBrowserCSS;
private readonly PluginManager plugins;
public TabSettingsAdvanced(Action browserReloadAction, PluginManager plugins){
public TabSettingsAdvanced(Action<string> reinjectBrowserCSS, PluginManager plugins){
InitializeComponent();
this.browserReloadAction = browserReloadAction;
this.reinjectBrowserCSS = reinjectBrowserCSS;
this.plugins = plugins;
checkHardwareAcceleration.Checked = HardwareAcceleration.IsEnabled;
@@ -30,9 +31,24 @@ namespace TweetDck.Core.Other.Settings{
}));
}
private void btnClearCache_Click(object sender, EventArgs e){
if (!Ready)return;
public override void OnReady(){
btnClearCache.Click += btnClearCache_Click;
checkHardwareAcceleration.CheckedChanged += checkHardwareAcceleration_CheckedChanged;
btnEditCefArgs.Click += btnEditCefArgs_Click;
btnEditCSS.Click += btnEditCSS_Click;
btnExport.Click += btnExport_Click;
btnImport.Click += btnImport_Click;
btnReset.Click += btnReset_Click;
btnOpenAppFolder.Click += btnOpenAppFolder_Click;
btnOpenDataFolder.Click += btnOpenDataFolder_Click;
btnRestart.Click += btnRestart_Click;
btnRestartArgs.Click += btnRestartArgs_Click;
}
private void btnClearCache_Click(object sender, EventArgs e){
btnClearCache.Enabled = false;
BrowserCache.SetClearOnExit();
@@ -40,8 +56,6 @@ namespace TweetDck.Core.Other.Settings{
}
private void checkHardwareAcceleration_CheckedChanged(object sender, EventArgs e){
if (!Ready)return;
bool succeeded = false;
if (checkHardwareAcceleration.Checked){
@@ -71,30 +85,36 @@ namespace TweetDck.Core.Other.Settings{
if (form.ShowDialog(ParentForm) == DialogResult.OK){
Config.CustomCefArgs = form.CefArgs;
form.Dispose();
PromptRestart();
}
else{
form.Dispose();
}
}
private void btnEditCSS_Click(object sender, EventArgs e){
DialogSettingsCSS form = new DialogSettingsCSS();
if (form.ShowDialog(ParentForm) == DialogResult.OK){
bool hasChangedBrowser = form.BrowserCSS != Config.CustomBrowserCSS;
Config.CustomBrowserCSS = form.BrowserCSS;
Config.CustomNotificationCSS = form.NotificationCSS;
if (hasChangedBrowser && MessageBox.Show("The browser CSS has changed, do you want to reload it?", "Browser CSS Changed", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
browserReloadAction();
using(DialogSettingsCSS form = new DialogSettingsCSS(reinjectBrowserCSS)){
if (form.ShowDialog(ParentForm) == DialogResult.OK){
Config.CustomBrowserCSS = form.BrowserCSS;
Config.CustomNotificationCSS = form.NotificationCSS;
}
reinjectBrowserCSS(Config.CustomBrowserCSS); // reinject on cancel too, because the CSS is updated while typing
}
}
private void btnExport_Click(object sender, EventArgs e){
DialogResult resultSaveCredentials = MessageBox.Show("Do you want to include your login session? This will not save your password into the file, but it will allow anyone with the file to login into TweetDeck as you.", "Export "+Program.BrandName+" Settings", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button3);
if (resultSaveCredentials == DialogResult.Cancel)return;
ExportFileFlags flags;
using(DialogSettingsExport dialog = DialogSettingsExport.Export()){
if (dialog.ShowDialog() != DialogResult.OK){
return;
}
flags = dialog.Flags;
}
bool saveCredentials = resultSaveCredentials == DialogResult.Yes;
string file;
using(SaveFileDialog dialog = new SaveFileDialog{
@@ -106,17 +126,19 @@ namespace TweetDck.Core.Other.Settings{
Title = "Export "+Program.BrandName+" Settings",
Filter = Program.BrandName+" Settings (*.tdsettings)|*.tdsettings"
}){
file = dialog.ShowDialog() == DialogResult.OK ? dialog.FileName : null;
if (dialog.ShowDialog() != DialogResult.OK){
return;
}
file = dialog.FileName;
}
if (file != null){
Program.UserConfig.Save();
Program.UserConfig.Save();
ExportManager manager = new ExportManager(file, plugins);
if (!manager.Export(saveCredentials)){
Program.HandleException("An exception happened while exporting "+Program.BrandName+" settings.", manager.LastException);
}
ExportManager manager = new ExportManager(file, plugins);
if (!manager.Export(flags)){
Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting "+Program.BrandName+" settings.", true, manager.LastException);
}
}
@@ -129,34 +151,58 @@ namespace TweetDck.Core.Other.Settings{
Title = "Import "+Program.BrandName+" Settings",
Filter = Program.BrandName+" Settings (*.tdsettings)|*.tdsettings"
}){
file = dialog.ShowDialog() == DialogResult.OK ? dialog.FileName : null;
if (dialog.ShowDialog() != DialogResult.OK){
return;
}
file = dialog.FileName;
}
if (file != null){
ExportManager manager = new ExportManager(file, plugins);
ExportManager manager = new ExportManager(file, plugins);
ExportFileFlags flags;
if (manager.Import()){
if (!manager.IsRestarting){
((FormSettings)ParentForm).ReloadUI();
}
using(DialogSettingsExport dialog = DialogSettingsExport.Import(manager.GetImportFlags())){
if (dialog.ShowDialog() != DialogResult.OK){
return;
}
else{
Program.HandleException("An exception happened while importing "+Program.BrandName+" settings.", manager.LastException);
flags = dialog.Flags;
}
if (manager.Import(flags)){
if (!manager.IsRestarting){
((FormSettings)ParentForm).ReloadUI();
}
}
else{
Program.Reporter.HandleException("Profile Import Error", "An exception happened while importing "+Program.BrandName+" settings.", true, manager.LastException);
}
}
private void btnReset_Click(object sender, EventArgs e){
if (MessageBox.Show("This will reset all of your settings, including disabled plugins. Do you want to proceed?", "Reset "+Program.BrandName+" Settings", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
if (MessageBox.Show("This will reset all of your program settings. Plugins will not be affected. Do you want to proceed?", "Reset "+Program.BrandName+" Settings", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
Program.ResetConfig();
((FormSettings)ParentForm).ReloadUI();
}
}
private static void PromptRestart(){
if (MessageBox.Show("The application must restart for the setting to take place. Do you want to restart now?", Program.BrandName+" Settings", MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.Yes){
Process.Start(Application.ExecutablePath, "-restart");
Application.Exit();
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);
}
}
}
}

View File

@@ -28,26 +28,36 @@
this.comboBoxTrayType = new System.Windows.Forms.ComboBox();
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.checkTrayHighlight = new System.Windows.Forms.CheckBox();
this.checkSpellCheck = new System.Windows.Forms.CheckBox();
this.checkUpdateNotifications = new System.Windows.Forms.CheckBox();
this.btnCheckUpdates = new System.Windows.Forms.Button();
this.labelZoomValue = new System.Windows.Forms.Label();
this.checkSwitchAccountSelectors = new System.Windows.Forms.CheckBox();
this.groupTray = new System.Windows.Forms.GroupBox();
this.labelTrayIcon = new System.Windows.Forms.Label();
this.groupInterface = new System.Windows.Forms.GroupBox();
this.trackBarZoom = new System.Windows.Forms.TrackBar();
this.labelZoom = new System.Windows.Forms.Label();
this.groupUpdates = new System.Windows.Forms.GroupBox();
this.zoomUpdateTimer = new System.Windows.Forms.Timer(this.components);
this.groupTray.SuspendLayout();
this.groupInterface.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).BeginInit();
this.groupUpdates.SuspendLayout();
this.SuspendLayout();
//
// checkExpandLinks
//
this.checkExpandLinks.AutoSize = true;
this.checkExpandLinks.Location = new System.Drawing.Point(9, 21);
this.checkExpandLinks.Margin = new System.Windows.Forms.Padding(3, 5, 3, 3);
this.checkExpandLinks.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3);
this.checkExpandLinks.Name = "checkExpandLinks";
this.checkExpandLinks.Size = new System.Drawing.Size(166, 17);
this.checkExpandLinks.TabIndex = 14;
this.checkExpandLinks.TabIndex = 0;
this.checkExpandLinks.Text = "Expand Links When Hovered";
this.toolTip.SetToolTip(this.checkExpandLinks, "Expands links inside the tweets. If disabled,\r\nthe full links show up in a toolti" +
"p instead.");
this.checkExpandLinks.UseVisualStyleBackColor = true;
this.checkExpandLinks.CheckedChanged += new System.EventHandler(this.checkExpandLinks_CheckedChanged);
//
// comboBoxTrayType
//
@@ -56,33 +66,92 @@
this.comboBoxTrayType.Location = new System.Drawing.Point(6, 19);
this.comboBoxTrayType.Name = "comboBoxTrayType";
this.comboBoxTrayType.Size = new System.Drawing.Size(171, 21);
this.comboBoxTrayType.TabIndex = 13;
this.comboBoxTrayType.TabIndex = 0;
this.toolTip.SetToolTip(this.comboBoxTrayType, "Changes behavior of the Tray icon.\r\nRight-click the icon for an action menu.");
this.comboBoxTrayType.SelectedIndexChanged += new System.EventHandler(this.comboBoxTrayType_SelectedIndexChanged);
//
// checkTrayHighlight
//
this.checkTrayHighlight.AutoSize = true;
this.checkTrayHighlight.Location = new System.Drawing.Point(9, 70);
this.checkTrayHighlight.Margin = new System.Windows.Forms.Padding(3, 5, 3, 3);
this.checkTrayHighlight.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3);
this.checkTrayHighlight.Name = "checkTrayHighlight";
this.checkTrayHighlight.Size = new System.Drawing.Size(103, 17);
this.checkTrayHighlight.TabIndex = 15;
this.checkTrayHighlight.TabIndex = 2;
this.checkTrayHighlight.Text = "Enable Highlight";
this.toolTip.SetToolTip(this.checkTrayHighlight, "Highlights the tray icon if there are new tweets.\r\nOnly works for columns with po" +
"pup or audio notifications.\r\nThe icon resets when the main window is restored.");
this.checkTrayHighlight.UseVisualStyleBackColor = true;
this.checkTrayHighlight.CheckedChanged += new System.EventHandler(this.checkTrayHighlight_CheckedChanged);
//
// checkSpellCheck
//
this.checkSpellCheck.AutoSize = true;
this.checkSpellCheck.Location = new System.Drawing.Point(9, 67);
this.checkSpellCheck.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkSpellCheck.Name = "checkSpellCheck";
this.checkSpellCheck.Size = new System.Drawing.Size(119, 17);
this.checkSpellCheck.TabIndex = 2;
this.checkSpellCheck.Text = "Enable Spell Check";
this.toolTip.SetToolTip(this.checkSpellCheck, "Underlines words that are spelled incorrectly.");
this.checkSpellCheck.UseVisualStyleBackColor = true;
//
// checkUpdateNotifications
//
this.checkUpdateNotifications.AutoSize = true;
this.checkUpdateNotifications.Location = new System.Drawing.Point(9, 21);
this.checkUpdateNotifications.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3);
this.checkUpdateNotifications.Name = "checkUpdateNotifications";
this.checkUpdateNotifications.Size = new System.Drawing.Size(165, 17);
this.checkUpdateNotifications.TabIndex = 0;
this.checkUpdateNotifications.Text = "Check Updates Automatically";
this.toolTip.SetToolTip(this.checkUpdateNotifications, "Checks for updates every hour.\r\nIf an update is dismissed, it will not appear aga" +
"in.");
this.checkUpdateNotifications.UseVisualStyleBackColor = true;
//
// btnCheckUpdates
//
this.btnCheckUpdates.Location = new System.Drawing.Point(6, 44);
this.btnCheckUpdates.Name = "btnCheckUpdates";
this.btnCheckUpdates.Size = new System.Drawing.Size(171, 23);
this.btnCheckUpdates.TabIndex = 1;
this.btnCheckUpdates.Text = "Check Updates Now";
this.toolTip.SetToolTip(this.btnCheckUpdates, "Forces an update check, even for updates that had been dismissed.");
this.btnCheckUpdates.UseVisualStyleBackColor = true;
//
// labelZoomValue
//
this.labelZoomValue.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.labelZoomValue.BackColor = System.Drawing.Color.Transparent;
this.labelZoomValue.Location = new System.Drawing.Point(139, 116);
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 = 5;
this.labelZoomValue.Text = "100%";
this.labelZoomValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
this.toolTip.SetToolTip(this.labelZoomValue, "Changes the zoom level.\r\nAlso affects notifications and screenshots.");
//
// checkSwitchAccountSelectors
//
this.checkSwitchAccountSelectors.AutoSize = true;
this.checkSwitchAccountSelectors.Location = new System.Drawing.Point(9, 44);
this.checkSwitchAccountSelectors.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkSwitchAccountSelectors.Name = "checkSwitchAccountSelectors";
this.checkSwitchAccountSelectors.Size = new System.Drawing.Size(172, 17);
this.checkSwitchAccountSelectors.TabIndex = 1;
this.checkSwitchAccountSelectors.Text = "Shift Selects Multiple Accounts";
this.toolTip.SetToolTip(this.checkSwitchAccountSelectors, "When (re)tweeting, click to select a single account or hold Shift to\r\nselect mult" +
"iple accounts, instead of TweetDeck\'s default behavior.");
this.checkSwitchAccountSelectors.UseVisualStyleBackColor = true;
//
// groupTray
//
this.groupTray.Controls.Add(this.checkTrayHighlight);
this.groupTray.Controls.Add(this.labelTrayIcon);
this.groupTray.Controls.Add(this.comboBoxTrayType);
this.groupTray.Location = new System.Drawing.Point(9, 63);
this.groupTray.Location = new System.Drawing.Point(9, 168);
this.groupTray.Name = "groupTray";
this.groupTray.Size = new System.Drawing.Size(183, 93);
this.groupTray.TabIndex = 15;
this.groupTray.TabIndex = 1;
this.groupTray.TabStop = false;
this.groupTray.Text = "System Tray";
//
@@ -93,31 +162,84 @@
this.labelTrayIcon.Margin = new System.Windows.Forms.Padding(3, 9, 3, 0);
this.labelTrayIcon.Name = "labelTrayIcon";
this.labelTrayIcon.Size = new System.Drawing.Size(52, 13);
this.labelTrayIcon.TabIndex = 14;
this.labelTrayIcon.TabIndex = 1;
this.labelTrayIcon.Text = "Tray Icon";
//
// groupInterface
//
this.groupInterface.Controls.Add(this.checkSwitchAccountSelectors);
this.groupInterface.Controls.Add(this.labelZoomValue);
this.groupInterface.Controls.Add(this.trackBarZoom);
this.groupInterface.Controls.Add(this.labelZoom);
this.groupInterface.Controls.Add(this.checkSpellCheck);
this.groupInterface.Controls.Add(this.checkExpandLinks);
this.groupInterface.Location = new System.Drawing.Point(9, 9);
this.groupInterface.Name = "groupInterface";
this.groupInterface.Size = new System.Drawing.Size(183, 48);
this.groupInterface.TabIndex = 16;
this.groupInterface.Size = new System.Drawing.Size(183, 153);
this.groupInterface.TabIndex = 0;
this.groupInterface.TabStop = false;
this.groupInterface.Text = "User Interface";
//
// trackBarZoom
//
this.trackBarZoom.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.trackBarZoom.AutoSize = false;
this.trackBarZoom.LargeChange = 25;
this.trackBarZoom.Location = new System.Drawing.Point(6, 115);
this.trackBarZoom.Maximum = 200;
this.trackBarZoom.Minimum = 50;
this.trackBarZoom.Name = "trackBarZoom";
this.trackBarZoom.Size = new System.Drawing.Size(141, 30);
this.trackBarZoom.SmallChange = 5;
this.trackBarZoom.TabIndex = 4;
this.trackBarZoom.TickFrequency = 25;
this.trackBarZoom.Value = 100;
//
// labelZoom
//
this.labelZoom.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.labelZoom.AutoSize = true;
this.labelZoom.Location = new System.Drawing.Point(5, 99);
this.labelZoom.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelZoom.Name = "labelZoom";
this.labelZoom.Size = new System.Drawing.Size(34, 13);
this.labelZoom.TabIndex = 3;
this.labelZoom.Text = "Zoom";
//
// groupUpdates
//
this.groupUpdates.Controls.Add(this.checkUpdateNotifications);
this.groupUpdates.Controls.Add(this.btnCheckUpdates);
this.groupUpdates.Location = new System.Drawing.Point(198, 9);
this.groupUpdates.Name = "groupUpdates";
this.groupUpdates.Size = new System.Drawing.Size(183, 75);
this.groupUpdates.TabIndex = 2;
this.groupUpdates.TabStop = false;
this.groupUpdates.Text = "Updates";
//
// zoomUpdateTimer
//
this.zoomUpdateTimer.Interval = 250;
this.zoomUpdateTimer.Tick += new System.EventHandler(this.zoomUpdateTimer_Tick);
//
// TabSettingsGeneral
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.groupUpdates);
this.Controls.Add(this.groupInterface);
this.Controls.Add(this.groupTray);
this.Name = "TabSettingsGeneral";
this.Size = new System.Drawing.Size(478, 282);
this.Size = new System.Drawing.Size(478, 300);
this.groupTray.ResumeLayout(false);
this.groupTray.PerformLayout();
this.groupInterface.ResumeLayout(false);
this.groupInterface.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).EndInit();
this.groupUpdates.ResumeLayout(false);
this.groupUpdates.PerformLayout();
this.ResumeLayout(false);
}
@@ -131,5 +253,14 @@
private System.Windows.Forms.GroupBox groupInterface;
private System.Windows.Forms.Label labelTrayIcon;
private System.Windows.Forms.CheckBox checkTrayHighlight;
private System.Windows.Forms.CheckBox checkSpellCheck;
private System.Windows.Forms.GroupBox groupUpdates;
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.CheckBox checkSwitchAccountSelectors;
}
}

View File

@@ -1,9 +1,20 @@
using System;
using System.Windows.Forms;
using TweetDck.Core.Controls;
using TweetDck.Updates;
using TweetDck.Updates.Events;
namespace TweetDck.Core.Other.Settings{
partial class TabSettingsGeneral : BaseTabSettings{
public TabSettingsGeneral(){
private readonly UpdateHandler updates;
private int updateCheckEventId = -1;
public TabSettingsGeneral(UpdateHandler updates){
InitializeComponent();
this.updates = updates;
this.updates.CheckFinished += updates_CheckFinished;
Disposed += (sender, args) => this.updates.CheckFinished -= updates_CheckFinished;
comboBoxTrayType.Items.Add("Disabled");
comboBoxTrayType.Items.Add("Display Icon Only");
@@ -11,27 +22,97 @@ namespace TweetDck.Core.Other.Settings{
comboBoxTrayType.Items.Add("Close to Tray");
comboBoxTrayType.Items.Add("Combined");
comboBoxTrayType.SelectedIndex = Math.Min(Math.Max((int)Config.TrayBehavior, 0), comboBoxTrayType.Items.Count-1);
toolTip.SetToolTip(trackBarZoom, toolTip.GetToolTip(labelZoomValue));
trackBarZoom.SetValueSafe(Config.ZoomLevel);
labelZoomValue.Text = trackBarZoom.Value+"%";
checkExpandLinks.Checked = Program.UserConfig.ExpandLinksOnHover;
checkTrayHighlight.Checked = Program.UserConfig.EnableTrayHighlight;
checkExpandLinks.Checked = Config.ExpandLinksOnHover;
checkSwitchAccountSelectors.Checked = Config.SwitchAccountSelectors;
checkSpellCheck.Checked = Config.EnableSpellCheck;
checkTrayHighlight.Checked = Config.EnableTrayHighlight;
checkUpdateNotifications.Checked = Config.EnableUpdateCheck;
}
public override void OnReady(){
checkExpandLinks.CheckedChanged += checkExpandLinks_CheckedChanged;
checkSwitchAccountSelectors.CheckedChanged += checkSwitchAccountSelectors_CheckedChanged;
checkSpellCheck.CheckedChanged += checkSpellCheck_CheckedChanged;
trackBarZoom.ValueChanged += trackBarZoom_ValueChanged;
comboBoxTrayType.SelectedIndexChanged += comboBoxTrayType_SelectedIndexChanged;
checkTrayHighlight.CheckedChanged += checkTrayHighlight_CheckedChanged;
checkUpdateNotifications.CheckedChanged += checkUpdateNotifications_CheckedChanged;
btnCheckUpdates.Click += btnCheckUpdates_Click;
}
public override void OnClosing(){
Config.ZoomLevel = trackBarZoom.Value;
}
private void checkExpandLinks_CheckedChanged(object sender, EventArgs e){
if (!Ready)return;
Config.ExpandLinksOnHover = checkExpandLinks.Checked;
}
private void comboBoxTrayType_SelectedIndexChanged(object sender, EventArgs e){
if (!Ready)return;
private void checkSwitchAccountSelectors_CheckedChanged(object sender, EventArgs e){
Config.SwitchAccountSelectors = checkSwitchAccountSelectors.Checked;
}
private void checkSpellCheck_CheckedChanged(object sender, EventArgs e){
Config.EnableSpellCheck = checkSpellCheck.Checked;
PromptRestart();
}
private void trackBarZoom_ValueChanged(object sender, EventArgs e){
if (trackBarZoom.Value % trackBarZoom.SmallChange != 0){
trackBarZoom.Value = trackBarZoom.SmallChange*(int)Math.Floor(((double)trackBarZoom.Value/trackBarZoom.SmallChange)+0.5);
}
else{
zoomUpdateTimer.Stop();
zoomUpdateTimer.Start();
labelZoomValue.Text = trackBarZoom.Value+"%";
}
}
private void comboBoxTrayType_SelectedIndexChanged(object sender, EventArgs e){
Config.TrayBehavior = (TrayIcon.Behavior)comboBoxTrayType.SelectedIndex;
}
private void checkTrayHighlight_CheckedChanged(object sender, EventArgs e){
if (!Ready)return;
Config.EnableTrayHighlight = checkTrayHighlight.Checked;
}
private void checkUpdateNotifications_CheckedChanged(object sender, EventArgs e){
Config.EnableUpdateCheck = checkUpdateNotifications.Checked;
}
private void btnCheckUpdates_Click(object sender, EventArgs e){
updateCheckEventId = updates.Check(true);
if (updateCheckEventId == -1){
MessageBox.Show("Sorry, your system is no longer supported.", "Unsupported System", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
else{
btnCheckUpdates.Enabled = false;
updates.DismissUpdate(string.Empty);
}
}
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){
if (e.EventId == updateCheckEventId){
btnCheckUpdates.Enabled = true;
if (!e.UpdateAvailable){
MessageBox.Show("Your version of "+Program.BrandName+" is up to date.", "No Updates Available", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
private void zoomUpdateTimer_Tick(object sender, EventArgs e){
Config.ZoomLevel = trackBarZoom.Value;
zoomUpdateTimer.Stop();
}
}
}

View File

@@ -43,8 +43,12 @@
this.labelDurationValue = new System.Windows.Forms.Label();
this.trackBarDuration = new System.Windows.Forms.TrackBar();
this.groupUserInterface = new System.Windows.Forms.GroupBox();
this.checkSkipOnLinkClick = new System.Windows.Forms.CheckBox();
this.checkColumnName = new System.Windows.Forms.CheckBox();
this.labelIdlePause = new System.Windows.Forms.Label();
this.comboBoxIdlePause = new System.Windows.Forms.ComboBox();
this.checkNonIntrusive = new System.Windows.Forms.CheckBox();
this.checkTimerCountDown = new System.Windows.Forms.CheckBox();
this.checkLegacyLoad = new System.Windows.Forms.CheckBox();
this.checkNotificationTimer = new System.Windows.Forms.CheckBox();
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.groupNotificationLocation.SuspendLayout();
@@ -69,30 +73,30 @@
this.groupNotificationLocation.Controls.Add(this.trackBarEdgeDistance);
this.groupNotificationLocation.Location = new System.Drawing.Point(198, 9);
this.groupNotificationLocation.Name = "groupNotificationLocation";
this.groupNotificationLocation.Size = new System.Drawing.Size(183, 264);
this.groupNotificationLocation.TabIndex = 1;
this.groupNotificationLocation.Size = new System.Drawing.Size(183, 282);
this.groupNotificationLocation.TabIndex = 2;
this.groupNotificationLocation.TabStop = false;
this.groupNotificationLocation.Text = "Location";
//
// labelEdgeDistanceValue
//
this.labelEdgeDistanceValue.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.labelEdgeDistanceValue.Location = new System.Drawing.Point(143, 214);
this.labelEdgeDistanceValue.Location = new System.Drawing.Point(143, 217);
this.labelEdgeDistanceValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.labelEdgeDistanceValue.Name = "labelEdgeDistanceValue";
this.labelEdgeDistanceValue.Size = new System.Drawing.Size(34, 13);
this.labelEdgeDistanceValue.TabIndex = 11;
this.labelEdgeDistanceValue.TabIndex = 9;
this.labelEdgeDistanceValue.Text = "0 px";
this.labelEdgeDistanceValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
//
// labelDisplay
//
this.labelDisplay.AutoSize = true;
this.labelDisplay.Location = new System.Drawing.Point(3, 148);
this.labelDisplay.Location = new System.Drawing.Point(5, 144);
this.labelDisplay.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelDisplay.Name = "labelDisplay";
this.labelDisplay.Size = new System.Drawing.Size(41, 13);
this.labelDisplay.TabIndex = 8;
this.labelDisplay.TabIndex = 5;
this.labelDisplay.Text = "Display";
//
// comboBoxDisplay
@@ -101,26 +105,26 @@
| System.Windows.Forms.AnchorStyles.Right)));
this.comboBoxDisplay.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxDisplay.FormattingEnabled = true;
this.comboBoxDisplay.Location = new System.Drawing.Point(6, 164);
this.comboBoxDisplay.Location = new System.Drawing.Point(6, 160);
this.comboBoxDisplay.Name = "comboBoxDisplay";
this.comboBoxDisplay.Size = new System.Drawing.Size(171, 21);
this.comboBoxDisplay.TabIndex = 7;
this.comboBoxDisplay.SelectedValueChanged += new System.EventHandler(this.comboBoxDisplay_SelectedValueChanged);
this.comboBoxDisplay.TabIndex = 6;
//
// labelEdgeDistance
//
this.labelEdgeDistance.AutoSize = true;
this.labelEdgeDistance.Location = new System.Drawing.Point(3, 197);
this.labelEdgeDistance.Margin = new System.Windows.Forms.Padding(3, 9, 3, 0);
this.labelEdgeDistance.Location = new System.Drawing.Point(5, 196);
this.labelEdgeDistance.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelEdgeDistance.Name = "labelEdgeDistance";
this.labelEdgeDistance.Size = new System.Drawing.Size(103, 13);
this.labelEdgeDistance.TabIndex = 6;
this.labelEdgeDistance.TabIndex = 7;
this.labelEdgeDistance.Text = "Distance From Edge";
//
// radioLocCustom
//
this.radioLocCustom.AutoSize = true;
this.radioLocCustom.Location = new System.Drawing.Point(7, 116);
this.radioLocCustom.Location = new System.Drawing.Point(8, 112);
this.radioLocCustom.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.radioLocCustom.Name = "radioLocCustom";
this.radioLocCustom.Size = new System.Drawing.Size(60, 17);
this.radioLocCustom.TabIndex = 4;
@@ -128,81 +132,80 @@
this.radioLocCustom.Text = "Custom";
this.toolTip.SetToolTip(this.radioLocCustom, "Drag the notification window to the desired location.");
this.radioLocCustom.UseVisualStyleBackColor = true;
this.radioLocCustom.CheckedChanged += new System.EventHandler(this.radioLoc_CheckedChanged);
//
// radioLocBR
//
this.radioLocBR.AutoSize = true;
this.radioLocBR.Location = new System.Drawing.Point(7, 92);
this.radioLocBR.Location = new System.Drawing.Point(8, 89);
this.radioLocBR.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
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);
//
// radioLocBL
//
this.radioLocBL.AutoSize = true;
this.radioLocBL.Location = new System.Drawing.Point(7, 68);
this.radioLocBL.Location = new System.Drawing.Point(8, 66);
this.radioLocBL.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
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);
//
// radioLocTR
//
this.radioLocTR.AutoSize = true;
this.radioLocTR.Location = new System.Drawing.Point(7, 44);
this.radioLocTR.Location = new System.Drawing.Point(8, 43);
this.radioLocTR.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
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);
//
// radioLocTL
//
this.radioLocTL.AutoSize = true;
this.radioLocTL.Location = new System.Drawing.Point(7, 20);
this.radioLocTL.Location = new System.Drawing.Point(8, 20);
this.radioLocTL.Margin = new System.Windows.Forms.Padding(5, 4, 3, 3);
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);
//
// 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.AutoSize = false;
this.trackBarEdgeDistance.LargeChange = 8;
this.trackBarEdgeDistance.Location = new System.Drawing.Point(6, 213);
this.trackBarEdgeDistance.Location = new System.Drawing.Point(8, 212);
this.trackBarEdgeDistance.Maximum = 40;
this.trackBarEdgeDistance.Minimum = 8;
this.trackBarEdgeDistance.Name = "trackBarEdgeDistance";
this.trackBarEdgeDistance.Size = new System.Drawing.Size(141, 45);
this.trackBarEdgeDistance.Size = new System.Drawing.Size(141, 30);
this.trackBarEdgeDistance.SmallChange = 2;
this.trackBarEdgeDistance.TabIndex = 5;
this.trackBarEdgeDistance.TabIndex = 8;
this.trackBarEdgeDistance.TickFrequency = 4;
this.trackBarEdgeDistance.Value = 8;
this.trackBarEdgeDistance.ValueChanged += new System.EventHandler(this.trackBarEdgeDistance_ValueChanged);
//
// groupNotificationDuration
//
this.groupNotificationDuration.Controls.Add(this.tableLayoutDurationButtons);
this.groupNotificationDuration.Controls.Add(this.labelDurationValue);
this.groupNotificationDuration.Controls.Add(this.trackBarDuration);
this.groupNotificationDuration.Location = new System.Drawing.Point(9, 106);
this.groupNotificationDuration.Location = new System.Drawing.Point(9, 202);
this.groupNotificationDuration.Name = "groupNotificationDuration";
this.groupNotificationDuration.Size = new System.Drawing.Size(183, 89);
this.groupNotificationDuration.TabIndex = 9;
this.groupNotificationDuration.TabIndex = 1;
this.groupNotificationDuration.TabStop = false;
this.groupNotificationDuration.Text = "Duration";
//
@@ -222,7 +225,7 @@
this.tableLayoutDurationButtons.RowCount = 1;
this.tableLayoutDurationButtons.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutDurationButtons.Size = new System.Drawing.Size(171, 27);
this.tableLayoutDurationButtons.TabIndex = 5;
this.tableLayoutDurationButtons.TabIndex = 2;
//
// btnDurationMedium
//
@@ -235,10 +238,9 @@
this.btnDurationMedium.Margin = new System.Windows.Forms.Padding(1);
this.btnDurationMedium.Name = "btnDurationMedium";
this.btnDurationMedium.Size = new System.Drawing.Size(59, 25);
this.btnDurationMedium.TabIndex = 2;
this.btnDurationMedium.TabIndex = 1;
this.btnDurationMedium.Text = "Medium";
this.btnDurationMedium.UseVisualStyleBackColor = true;
this.btnDurationMedium.Click += new System.EventHandler(this.btnDurationMedium_Click);
//
// btnDurationLong
//
@@ -251,10 +253,9 @@
this.btnDurationLong.Margin = new System.Windows.Forms.Padding(1);
this.btnDurationLong.Name = "btnDurationLong";
this.btnDurationLong.Size = new System.Drawing.Size(54, 25);
this.btnDurationLong.TabIndex = 1;
this.btnDurationLong.TabIndex = 2;
this.btnDurationLong.Text = "Long";
this.btnDurationLong.UseVisualStyleBackColor = true;
this.btnDurationLong.Click += new System.EventHandler(this.btnDurationLong_Click);
//
// btnDurationShort
//
@@ -270,7 +271,6 @@
this.btnDurationShort.TabIndex = 0;
this.btnDurationShort.Text = "Short";
this.btnDurationShort.UseVisualStyleBackColor = true;
this.btnDurationShort.Click += new System.EventHandler(this.btnDurationShort_Click);
//
// labelDurationValue
//
@@ -280,7 +280,7 @@
this.labelDurationValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.labelDurationValue.Name = "labelDurationValue";
this.labelDurationValue.Size = new System.Drawing.Size(48, 13);
this.labelDurationValue.TabIndex = 13;
this.labelDurationValue.TabIndex = 1;
this.labelDurationValue.Text = "0 ms/c";
this.labelDurationValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
this.toolTip.SetToolTip(this.labelDurationValue, "Milliseconds per character.");
@@ -289,65 +289,115 @@
//
this.trackBarDuration.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.trackBarDuration.AutoSize = false;
this.trackBarDuration.Location = new System.Drawing.Point(6, 19);
this.trackBarDuration.Maximum = 60;
this.trackBarDuration.Minimum = 10;
this.trackBarDuration.Name = "trackBarDuration";
this.trackBarDuration.Size = new System.Drawing.Size(128, 45);
this.trackBarDuration.TabIndex = 12;
this.trackBarDuration.Size = new System.Drawing.Size(128, 30);
this.trackBarDuration.TabIndex = 0;
this.trackBarDuration.TickFrequency = 5;
this.trackBarDuration.Value = 25;
this.trackBarDuration.ValueChanged += new System.EventHandler(this.trackBarDuration_ValueChanged);
//
// groupUserInterface
//
this.groupUserInterface.Controls.Add(this.checkSkipOnLinkClick);
this.groupUserInterface.Controls.Add(this.checkColumnName);
this.groupUserInterface.Controls.Add(this.labelIdlePause);
this.groupUserInterface.Controls.Add(this.comboBoxIdlePause);
this.groupUserInterface.Controls.Add(this.checkNonIntrusive);
this.groupUserInterface.Controls.Add(this.checkTimerCountDown);
this.groupUserInterface.Controls.Add(this.checkLegacyLoad);
this.groupUserInterface.Controls.Add(this.checkNotificationTimer);
this.groupUserInterface.Location = new System.Drawing.Point(9, 9);
this.groupUserInterface.Name = "groupUserInterface";
this.groupUserInterface.Size = new System.Drawing.Size(183, 91);
this.groupUserInterface.TabIndex = 10;
this.groupUserInterface.Size = new System.Drawing.Size(183, 187);
this.groupUserInterface.TabIndex = 0;
this.groupUserInterface.TabStop = false;
this.groupUserInterface.Text = "General";
//
// checkSkipOnLinkClick
//
this.checkSkipOnLinkClick.AutoSize = true;
this.checkSkipOnLinkClick.Location = new System.Drawing.Point(9, 90);
this.checkSkipOnLinkClick.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkSkipOnLinkClick.Name = "checkSkipOnLinkClick";
this.checkSkipOnLinkClick.Size = new System.Drawing.Size(113, 17);
this.checkSkipOnLinkClick.TabIndex = 3;
this.checkSkipOnLinkClick.Text = "Skip On Link Click";
this.toolTip.SetToolTip(this.checkSkipOnLinkClick, "Skips current notification when a link\r\ninside the notification is clicked.");
this.checkSkipOnLinkClick.UseVisualStyleBackColor = true;
//
// checkColumnName
//
this.checkColumnName.AutoSize = true;
this.checkColumnName.Location = new System.Drawing.Point(9, 21);
this.checkColumnName.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3);
this.checkColumnName.Name = "checkColumnName";
this.checkColumnName.Size = new System.Drawing.Size(129, 17);
this.checkColumnName.TabIndex = 0;
this.checkColumnName.Text = "Display Column Name";
this.toolTip.SetToolTip(this.checkColumnName, "Shows column name each notification originated\r\nfrom in the notification window t" +
"itle.");
this.checkColumnName.UseVisualStyleBackColor = true;
//
// labelIdlePause
//
this.labelIdlePause.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.labelIdlePause.AutoSize = true;
this.labelIdlePause.Location = new System.Drawing.Point(5, 141);
this.labelIdlePause.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelIdlePause.Name = "labelIdlePause";
this.labelIdlePause.Size = new System.Drawing.Size(89, 13);
this.labelIdlePause.TabIndex = 3;
this.labelIdlePause.Text = "Pause When Idle";
//
// comboBoxIdlePause
//
this.comboBoxIdlePause.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.comboBoxIdlePause.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxIdlePause.FormattingEnabled = true;
this.comboBoxIdlePause.Location = new System.Drawing.Point(6, 157);
this.comboBoxIdlePause.Name = "comboBoxIdlePause";
this.comboBoxIdlePause.Size = new System.Drawing.Size(171, 21);
this.comboBoxIdlePause.TabIndex = 4;
this.toolTip.SetToolTip(this.comboBoxIdlePause, "Pauses new notifications after going idle for a set amount of time.");
//
// checkNonIntrusive
//
this.checkNonIntrusive.AutoSize = true;
this.checkNonIntrusive.Location = new System.Drawing.Point(9, 113);
this.checkNonIntrusive.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkNonIntrusive.Name = "checkNonIntrusive";
this.checkNonIntrusive.Size = new System.Drawing.Size(128, 17);
this.checkNonIntrusive.TabIndex = 4;
this.checkNonIntrusive.Text = "Non-Intrusive Popups";
this.toolTip.SetToolTip(this.checkNonIntrusive, "When not idle and the cursor is within the notification window area,\r\nit will be " +
"delayed until the cursor moves away to prevent accidental clicks.");
this.checkNonIntrusive.UseVisualStyleBackColor = true;
//
// checkTimerCountDown
//
this.checkTimerCountDown.AutoSize = true;
this.checkTimerCountDown.Location = new System.Drawing.Point(6, 44);
this.checkTimerCountDown.Location = new System.Drawing.Point(9, 67);
this.checkTimerCountDown.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkTimerCountDown.Name = "checkTimerCountDown";
this.checkTimerCountDown.Size = new System.Drawing.Size(119, 17);
this.checkTimerCountDown.TabIndex = 6;
this.checkTimerCountDown.TabIndex = 2;
this.checkTimerCountDown.Text = "Timer Counts Down";
this.toolTip.SetToolTip(this.checkTimerCountDown, "The notification timer counts down instead of up.");
this.checkTimerCountDown.UseVisualStyleBackColor = true;
this.checkTimerCountDown.CheckedChanged += new System.EventHandler(this.checkTimerCountDown_CheckedChanged);
//
// checkLegacyLoad
//
this.checkLegacyLoad.AutoSize = true;
this.checkLegacyLoad.Location = new System.Drawing.Point(6, 67);
this.checkLegacyLoad.Name = "checkLegacyLoad";
this.checkLegacyLoad.Size = new System.Drawing.Size(139, 17);
this.checkLegacyLoad.TabIndex = 5;
this.checkLegacyLoad.Text = "Legacy Loading System";
this.toolTip.SetToolTip(this.checkLegacyLoad, "Try enabling if notifications do not display.\r\nMight cause delays and visual arti" +
"facts.");
this.checkLegacyLoad.UseVisualStyleBackColor = true;
this.checkLegacyLoad.CheckedChanged += new System.EventHandler(this.checkLegacyLoad_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.Location = new System.Drawing.Point(9, 44);
this.checkNotificationTimer.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkNotificationTimer.Name = "checkNotificationTimer";
this.checkNotificationTimer.Size = new System.Drawing.Size(145, 17);
this.checkNotificationTimer.TabIndex = 4;
this.checkNotificationTimer.TabIndex = 1;
this.checkNotificationTimer.Text = "Display Notification Timer";
this.toolTip.SetToolTip(this.checkNotificationTimer, "Shows how much time is left before the current notification disappears.");
this.checkNotificationTimer.UseVisualStyleBackColor = true;
this.checkNotificationTimer.CheckedChanged += new System.EventHandler(this.checkNotificationTimer_CheckedChanged);
//
// TabSettingsNotifications
//
@@ -357,13 +407,12 @@
this.Controls.Add(this.groupNotificationDuration);
this.Controls.Add(this.groupNotificationLocation);
this.Name = "TabSettingsNotifications";
this.Size = new System.Drawing.Size(478, 282);
this.Size = new System.Drawing.Size(478, 300);
this.ParentChanged += new System.EventHandler(this.TabSettingsNotifications_ParentChanged);
this.groupNotificationLocation.ResumeLayout(false);
this.groupNotificationLocation.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.trackBarEdgeDistance)).EndInit();
this.groupNotificationDuration.ResumeLayout(false);
this.groupNotificationDuration.PerformLayout();
this.tableLayoutDurationButtons.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.trackBarDuration)).EndInit();
this.groupUserInterface.ResumeLayout(false);
@@ -386,16 +435,20 @@
private System.Windows.Forms.RadioButton radioLocTL;
private System.Windows.Forms.GroupBox groupNotificationDuration;
private System.Windows.Forms.GroupBox groupUserInterface;
private System.Windows.Forms.CheckBox checkNotificationTimer;
private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.Label labelEdgeDistanceValue;
private System.Windows.Forms.CheckBox checkLegacyLoad;
private System.Windows.Forms.CheckBox checkTimerCountDown;
private System.Windows.Forms.Label labelDurationValue;
private System.Windows.Forms.TrackBar trackBarDuration;
private System.Windows.Forms.TableLayoutPanel tableLayoutDurationButtons;
private TweetDck.Core.Controls.FlatButton btnDurationMedium;
private TweetDck.Core.Controls.FlatButton btnDurationLong;
private TweetDck.Core.Controls.FlatButton btnDurationShort;
private System.Windows.Forms.CheckBox checkNonIntrusive;
private System.Windows.Forms.Label labelIdlePause;
private System.Windows.Forms.ComboBox comboBoxIdlePause;
private System.Windows.Forms.CheckBox checkColumnName;
private System.Windows.Forms.CheckBox checkSkipOnLinkClick;
private System.Windows.Forms.CheckBox checkTimerCountDown;
private System.Windows.Forms.CheckBox checkNotificationTimer;
}
}

View File

@@ -1,30 +1,33 @@
using System;
using System.Globalization;
using System.Windows.Forms;
using TweetDck.Core.Handling;
using TweetDck.Core.Controls;
using TweetDck.Core.Notification;
namespace TweetDck.Core.Other.Settings{
partial class TabSettingsNotifications : BaseTabSettings{
private readonly FormNotification notification;
private static readonly int[] IdlePauseSeconds = { 0, 30, 60, 120, 300 };
public TabSettingsNotifications(FormNotification notification){
private readonly FormNotificationMain notification;
public TabSettingsNotifications(FormNotificationMain notification){
InitializeComponent();
this.notification = notification;
this.notification.CanMoveWindow = () => radioLocCustom.Checked;
this.notification.Move += (sender, args) => {
if (radioLocCustom.Checked){
if (radioLocCustom.Checked && this.notification.Location != ControlExtensions.InvisibleLocation){
Config.CustomNotificationPosition = this.notification.Location;
}
};
this.notification.Initialized += (sender, args) => {
this.InvokeSafe(() => this.notification.ShowNotificationForSettings(true));
this.InvokeAsyncSafe(() => this.notification.ShowNotificationForSettings(true));
};
this.notification.Show(this);
this.notification.Activated += notification_Activated;
this.notification.Show();
switch(Config.NotificationPosition){
case TweetNotification.Position.TopLeft: radioLocTL.Checked = true; break;
@@ -34,9 +37,19 @@ namespace TweetDck.Core.Other.Settings{
case TweetNotification.Position.Custom: radioLocCustom.Checked = true; break;
}
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = !radioLocCustom.Checked;
toolTip.SetToolTip(trackBarDuration, toolTip.GetToolTip(labelDurationValue));
trackBarDuration.SetValueSafe(Config.NotificationDurationValue);
labelDurationValue.Text = Config.NotificationDurationValue+" ms/c";
comboBoxIdlePause.Items.Add("Disabled");
comboBoxIdlePause.Items.Add("30 seconds");
comboBoxIdlePause.Items.Add("1 minute");
comboBoxIdlePause.Items.Add("2 minutes");
comboBoxIdlePause.Items.Add("5 minutes");
comboBoxIdlePause.SelectedIndex = Math.Max(0, Array.FindIndex(IdlePauseSeconds, val => val == Config.NotificationIdlePauseSeconds));
comboBoxDisplay.Items.Add("(Same As "+Program.BrandName+")");
foreach(Screen screen in Screen.AllScreens){
@@ -44,11 +57,13 @@ namespace TweetDck.Core.Other.Settings{
}
comboBoxDisplay.SelectedIndex = Math.Min(comboBoxDisplay.Items.Count-1, Config.NotificationDisplay);
checkColumnName.Checked = Config.DisplayNotificationColumn;
checkNotificationTimer.Checked = Config.DisplayNotificationTimer;
checkTimerCountDown.Enabled = checkNotificationTimer.Checked;
checkTimerCountDown.Checked = Config.NotificationTimerCountDown;
checkLegacyLoad.Checked = Config.NotificationLegacyLoad;
checkSkipOnLinkClick.Checked = Config.NotificationSkipOnLinkClick;
checkNonIntrusive.Checked = Config.NotificationNonIntrusiveMode;
trackBarEdgeDistance.SetValueSafe(Config.NotificationEdgeDistance);
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value.ToString(CultureInfo.InvariantCulture)+" px";
@@ -56,6 +71,30 @@ namespace TweetDck.Core.Other.Settings{
Disposed += (sender, args) => this.notification.Dispose();
}
public override void OnReady(){
radioLocTL.CheckedChanged += radioLoc_CheckedChanged;
radioLocTR.CheckedChanged += radioLoc_CheckedChanged;
radioLocBL.CheckedChanged += radioLoc_CheckedChanged;
radioLocBR.CheckedChanged += radioLoc_CheckedChanged;
radioLocCustom.Click += radioLocCustom_Click;
trackBarDuration.ValueChanged += trackBarDuration_ValueChanged;
btnDurationShort.Click += btnDurationShort_Click;
btnDurationMedium.Click += btnDurationMedium_Click;
btnDurationLong.Click += btnDurationLong_Click;
checkColumnName.CheckedChanged += checkColumnName_CheckedChanged;
checkNotificationTimer.CheckedChanged += checkNotificationTimer_CheckedChanged;
checkTimerCountDown.CheckedChanged += checkTimerCountDown_CheckedChanged;
checkSkipOnLinkClick.CheckedChanged += checkSkipOnLinkClick_CheckedChanged;
checkNonIntrusive.CheckedChanged += checkNonIntrusive_CheckedChanged;
comboBoxIdlePause.SelectedValueChanged += comboBoxIdlePause_SelectedValueChanged;
comboBoxDisplay.SelectedValueChanged += comboBoxDisplay_SelectedValueChanged;
trackBarEdgeDistance.ValueChanged += trackBarEdgeDistance_ValueChanged;
}
private void TabSettingsNotifications_ParentChanged(object sender, EventArgs e){
if (Parent == null){
notification.HideNotification(false);
@@ -65,28 +104,43 @@ namespace TweetDck.Core.Other.Settings{
}
}
private void radioLoc_CheckedChanged(object sender, EventArgs e){
if (!Ready)return;
private void notification_Activated(object sender, EventArgs e){
notification.Hide();
notification.Activated -= notification_Activated;
}
private void radioLoc_CheckedChanged(object sender, EventArgs e){
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;
}
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = !radioLocCustom.Checked;
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = true;
notification.ShowNotificationForSettings(false);
}
private void radioLocCustom_Click(object sender, EventArgs e){
if (!Config.IsCustomNotificationPositionSet){
Config.CustomNotificationPosition = notification.Location;
}
Config.NotificationPosition = TweetNotification.Position.Custom;
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = false;
notification.ShowNotificationForSettings(false);
if (notification.IsFullyOutsideView() && MessageBox.Show("The notification seems to be outside of view, would you like to reset its position?", "Notification is outside view", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
Config.NotificationPosition = TweetNotification.Position.TopRight;
notification.MoveToVisibleLocation();
Config.CustomNotificationPosition = notification.Location;
Config.NotificationPosition = TweetNotification.Position.Custom;
notification.MoveToVisibleLocation();
}
}
private void trackBarDuration_ValueChanged(object sender, EventArgs e){
if (!Ready)return;
Config.NotificationDurationValue = trackBarDuration.Value;
labelDurationValue.Text = Config.NotificationDurationValue+" ms/c";
@@ -94,54 +148,51 @@ namespace TweetDck.Core.Other.Settings{
}
private void btnDurationShort_Click(object sender, EventArgs e){
if (!Ready)return;
trackBarDuration.Value = 15;
}
private void btnDurationMedium_Click(object sender, EventArgs e){
if (!Ready)return;
trackBarDuration.Value = 25;
}
private void btnDurationLong_Click(object sender, EventArgs e){
if (!Ready)return;
trackBarDuration.Value = 35;
}
private void checkNotificationTimer_CheckedChanged(object sender, EventArgs e){
if (!Ready)return;
private void checkColumnName_CheckedChanged(object sender, EventArgs e){
Config.DisplayNotificationColumn = checkColumnName.Checked;
notification.ShowNotificationForSettings(false);
}
private void checkNotificationTimer_CheckedChanged(object sender, EventArgs e){
Config.DisplayNotificationTimer = checkNotificationTimer.Checked;
checkTimerCountDown.Enabled = checkNotificationTimer.Checked;
notification.ShowNotificationForSettings(true);
}
private void checkTimerCountDown_CheckedChanged(object sender, EventArgs e){
if (!Ready)return;
Config.NotificationTimerCountDown = checkTimerCountDown.Checked;
notification.ShowNotificationForSettings(true);
}
private void checkLegacyLoad_CheckedChanged(object sender, EventArgs e){
if (!Ready)return;
private void checkSkipOnLinkClick_CheckedChanged(object sender, EventArgs e){
Config.NotificationSkipOnLinkClick = checkSkipOnLinkClick.Checked;
}
Config.NotificationLegacyLoad = checkLegacyLoad.Checked;
private void checkNonIntrusive_CheckedChanged(object sender, EventArgs e){
Config.NotificationNonIntrusiveMode = checkNonIntrusive.Checked;
}
private void comboBoxIdlePause_SelectedValueChanged(object sender, EventArgs e){
Config.NotificationIdlePauseSeconds = IdlePauseSeconds[comboBoxIdlePause.SelectedIndex];
}
private void comboBoxDisplay_SelectedValueChanged(object sender, EventArgs e){
if (!Ready)return;
Config.NotificationDisplay = comboBoxDisplay.SelectedIndex;
notification.ShowNotificationForSettings(false);
}
private void trackBarEdgeDistance_ValueChanged(object sender, EventArgs e){
if (!Ready)return;
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value.ToString(CultureInfo.InvariantCulture)+" px";
Config.NotificationEdgeDistance = trackBarEdgeDistance.Value;
notification.ShowNotificationForSettings(false);

View File

@@ -0,0 +1,111 @@
namespace TweetDck.Core.Other.Settings {
partial class TabSettingsSounds {
/// <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.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.groupCustomSound = new System.Windows.Forms.GroupBox();
this.btnPlaySound = new System.Windows.Forms.Button();
this.btnResetSound = new System.Windows.Forms.Button();
this.btnBrowseSound = new System.Windows.Forms.Button();
this.tbCustomSound = new System.Windows.Forms.TextBox();
this.groupCustomSound.SuspendLayout();
this.SuspendLayout();
//
// groupCustomSound
//
this.groupCustomSound.Controls.Add(this.btnPlaySound);
this.groupCustomSound.Controls.Add(this.btnResetSound);
this.groupCustomSound.Controls.Add(this.btnBrowseSound);
this.groupCustomSound.Controls.Add(this.tbCustomSound);
this.groupCustomSound.Location = new System.Drawing.Point(9, 9);
this.groupCustomSound.Name = "groupCustomSound";
this.groupCustomSound.Size = new System.Drawing.Size(372, 75);
this.groupCustomSound.TabIndex = 0;
this.groupCustomSound.TabStop = false;
this.groupCustomSound.Text = "Custom Sound Notification";
//
// btnPlaySound
//
this.btnPlaySound.AutoSize = true;
this.btnPlaySound.Location = new System.Drawing.Point(250, 45);
this.btnPlaySound.Name = "btnPlaySound";
this.btnPlaySound.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnPlaySound.Size = new System.Drawing.Size(43, 23);
this.btnPlaySound.TabIndex = 2;
this.btnPlaySound.Text = "Play";
this.btnPlaySound.UseVisualStyleBackColor = true;
//
// btnResetSound
//
this.btnResetSound.AutoSize = true;
this.btnResetSound.Location = new System.Drawing.Point(6, 45);
this.btnResetSound.Name = "btnResetSound";
this.btnResetSound.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnResetSound.Size = new System.Drawing.Size(51, 23);
this.btnResetSound.TabIndex = 3;
this.btnResetSound.Text = "Reset";
this.btnResetSound.UseVisualStyleBackColor = true;
//
// btnBrowseSound
//
this.btnBrowseSound.AutoSize = true;
this.btnBrowseSound.Location = new System.Drawing.Point(299, 45);
this.btnBrowseSound.Name = "btnBrowseSound";
this.btnBrowseSound.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnBrowseSound.Size = new System.Drawing.Size(67, 23);
this.btnBrowseSound.TabIndex = 1;
this.btnBrowseSound.Text = "Browse...";
this.btnBrowseSound.UseVisualStyleBackColor = true;
//
// tbCustomSound
//
this.tbCustomSound.Location = new System.Drawing.Point(6, 19);
this.tbCustomSound.Name = "tbCustomSound";
this.tbCustomSound.Size = new System.Drawing.Size(360, 20);
this.tbCustomSound.TabIndex = 0;
//
// TabSettingsSounds
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.groupCustomSound);
this.Name = "TabSettingsSounds";
this.Size = new System.Drawing.Size(478, 300);
this.groupCustomSound.ResumeLayout(false);
this.groupCustomSound.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.GroupBox groupCustomSound;
private System.Windows.Forms.Button btnResetSound;
private System.Windows.Forms.Button btnBrowseSound;
private System.Windows.Forms.TextBox tbCustomSound;
private System.Windows.Forms.Button btnPlaySound;
}
}

View File

@@ -0,0 +1,67 @@
using System;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using TweetDck.Core.Notification;
using TweetDck.Core.Notification.Sound;
namespace TweetDck.Core.Other.Settings{
partial class TabSettingsSounds : BaseTabSettings{
private readonly ISoundNotificationPlayer soundNotification;
public TabSettingsSounds(){
InitializeComponent();
soundNotification = SoundNotification.New();
soundNotification.PlaybackError += sound_PlaybackError;
tbCustomSound.Text = Config.NotificationSoundPath;
tbCustomSound_TextChanged(tbCustomSound, new EventArgs());
Disposed += (sender, args) => soundNotification.Dispose();
}
public override void OnReady(){
tbCustomSound.TextChanged += tbCustomSound_TextChanged;
btnPlaySound.Click += btnPlaySound_Click;
btnBrowseSound.Click += btnBrowseSound_Click;
btnResetSound.Click += btnResetSound_Click;
}
public override void OnClosing(){
Config.NotificationSoundPath = tbCustomSound.Text;
}
private void tbCustomSound_TextChanged(object sender, EventArgs e){
bool isEmpty = string.IsNullOrEmpty(tbCustomSound.Text);
tbCustomSound.ForeColor = isEmpty || File.Exists(tbCustomSound.Text) ? SystemColors.WindowText : Color.Maroon;
btnPlaySound.Enabled = !isEmpty;
btnResetSound.Enabled = !isEmpty;
}
private void btnPlaySound_Click(object sender, EventArgs e){
soundNotification.Play(tbCustomSound.Text);
}
private void sound_PlaybackError(object sender, PlaybackErrorEventArgs e){
MessageBox.Show("Could not play custom notification sound."+Environment.NewLine+e.Message, "Notification Sound Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
private void btnBrowseSound_Click(object sender, EventArgs e){
using(OpenFileDialog dialog = new OpenFileDialog{
AutoUpgradeEnabled = true,
DereferenceLinks = true,
Title = "Custom Notification Sound",
Filter = "Sound file ("+soundNotification.SupportedFormats+")|"+soundNotification.SupportedFormats+"|All files (*.*)|*.*"
}){
if (dialog.ShowDialog() == DialogResult.OK){
tbCustomSound.Text = dialog.FileName;
}
}
}
private void btnResetSound_Click(object sender, EventArgs e){
tbCustomSound.Text = string.Empty;
}
}
}

View File

@@ -1,90 +0,0 @@
namespace TweetDck.Core.Other.Settings {
partial class TabSettingsUpdates {
/// <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.btnCheckUpdates = new System.Windows.Forms.Button();
this.checkUpdateNotifications = new System.Windows.Forms.CheckBox();
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.groupGeneral = new System.Windows.Forms.GroupBox();
this.groupGeneral.SuspendLayout();
this.SuspendLayout();
//
// btnCheckUpdates
//
this.btnCheckUpdates.Location = new System.Drawing.Point(6, 44);
this.btnCheckUpdates.Name = "btnCheckUpdates";
this.btnCheckUpdates.Size = new System.Drawing.Size(171, 23);
this.btnCheckUpdates.TabIndex = 15;
this.btnCheckUpdates.Text = "Check Updates Now";
this.toolTip.SetToolTip(this.btnCheckUpdates, "Forces an update check, even for updates that had been dismissed.");
this.btnCheckUpdates.UseVisualStyleBackColor = true;
this.btnCheckUpdates.Click += new System.EventHandler(this.btnCheckUpdates_Click);
//
// checkUpdateNotifications
//
this.checkUpdateNotifications.AutoSize = true;
this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 21);
this.checkUpdateNotifications.Margin = new System.Windows.Forms.Padding(3, 5, 3, 3);
this.checkUpdateNotifications.Name = "checkUpdateNotifications";
this.checkUpdateNotifications.Size = new System.Drawing.Size(165, 17);
this.checkUpdateNotifications.TabIndex = 14;
this.checkUpdateNotifications.Text = "Check Updates Automatically";
this.toolTip.SetToolTip(this.checkUpdateNotifications, "Checks for updates every hour.\r\nIf an update is dismissed, it will not appear aga" +
"in.");
this.checkUpdateNotifications.UseVisualStyleBackColor = true;
this.checkUpdateNotifications.CheckedChanged += new System.EventHandler(this.checkUpdateNotifications_CheckedChanged);
//
// groupGeneral
//
this.groupGeneral.Controls.Add(this.checkUpdateNotifications);
this.groupGeneral.Controls.Add(this.btnCheckUpdates);
this.groupGeneral.Location = new System.Drawing.Point(9, 9);
this.groupGeneral.Name = "groupGeneral";
this.groupGeneral.Size = new System.Drawing.Size(183, 75);
this.groupGeneral.TabIndex = 16;
this.groupGeneral.TabStop = false;
this.groupGeneral.Text = "General";
//
// TabSettingsUpdates
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.groupGeneral);
this.Name = "TabSettingsUpdates";
this.Size = new System.Drawing.Size(478, 282);
this.groupGeneral.ResumeLayout(false);
this.groupGeneral.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Button btnCheckUpdates;
private System.Windows.Forms.CheckBox checkUpdateNotifications;
private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.GroupBox groupGeneral;
}
}

View File

@@ -1,48 +0,0 @@
using System;
using System.Windows.Forms;
using TweetDck.Updates;
namespace TweetDck.Core.Other.Settings{
partial class TabSettingsUpdates : BaseTabSettings{
private readonly UpdateHandler updates;
private int updateCheckEventId = -1;
public TabSettingsUpdates(UpdateHandler updates){
InitializeComponent();
this.updates = updates;
this.updates.CheckFinished += updates_CheckFinished;
Disposed += (sender, args) => this.updates.CheckFinished -= updates_CheckFinished;
checkUpdateNotifications.Checked = Config.EnableUpdateCheck;
}
private void checkUpdateNotifications_CheckedChanged(object sender, EventArgs e){
if (!Ready)return;
Config.EnableUpdateCheck = checkUpdateNotifications.Checked;
}
private void btnCheckUpdates_Click(object sender, EventArgs e){
if (!Ready)return;
Config.DismissedUpdate = string.Empty;
Config.Save();
updateCheckEventId = updates.Check(true);
btnCheckUpdates.Enabled = false;
}
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){
if (e.EventId == updateCheckEventId){
btnCheckUpdates.Enabled = true;
if (!e.UpdateAvailable){
MessageBox.Show("Your version of "+Program.BrandName+" is up to date.", "No Updates Available", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
}
}

View File

@@ -25,65 +25,17 @@
private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
this.notifyIcon = new System.Windows.Forms.NotifyIcon(this.components);
this.contextMenu = new System.Windows.Forms.ContextMenuStrip(this.components);
this.restoreToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.muteNotificationsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.closeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.contextMenu.SuspendLayout();
//
// notifyIcon
//
this.notifyIcon.ContextMenuStrip = this.contextMenu;
this.notifyIcon.Icon = global::TweetDck.Properties.Resources.icon_tray;
this.notifyIcon.MouseClick += new System.Windows.Forms.MouseEventHandler(this.trayIcon_MouseClick);
//
// contextMenu
//
this.contextMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.restoreToolStripMenuItem,
this.muteNotificationsToolStripMenuItem,
this.closeToolStripMenuItem});
this.contextMenu.Name = "contextMenuTray";
this.contextMenu.ShowCheckMargin = true;
this.contextMenu.ShowImageMargin = false;
this.contextMenu.ShowItemToolTips = false;
this.contextMenu.Size = new System.Drawing.Size(174, 92);
this.contextMenu.Opening += new System.ComponentModel.CancelEventHandler(this.contextMenuTray_Opening);
this.contextMenu.Opened += new System.EventHandler(this.contextMenuTray_Opened);
//
// restoreToolStripMenuItem
//
this.restoreToolStripMenuItem.Name = "restoreToolStripMenuItem";
this.restoreToolStripMenuItem.Size = new System.Drawing.Size(173, 22);
this.restoreToolStripMenuItem.Text = "Restore";
this.restoreToolStripMenuItem.Click += new System.EventHandler(this.restoreToolStripMenuItem_Click);
//
// muteNotificationsToolStripMenuItem
//
this.muteNotificationsToolStripMenuItem.CheckOnClick = true;
this.muteNotificationsToolStripMenuItem.Name = "muteNotificationsToolStripMenuItem";
this.muteNotificationsToolStripMenuItem.Size = new System.Drawing.Size(173, 22);
this.muteNotificationsToolStripMenuItem.Text = "Mute Notifications";
this.muteNotificationsToolStripMenuItem.CheckedChanged += new System.EventHandler(this.muteNotificationsToolStripMenuItem_CheckedChanged);
//
// closeToolStripMenuItem
//
this.closeToolStripMenuItem.Name = "closeToolStripMenuItem";
this.closeToolStripMenuItem.Size = new System.Drawing.Size(173, 22);
this.closeToolStripMenuItem.Text = "Close";
this.closeToolStripMenuItem.Click += new System.EventHandler(this.closeToolStripMenuItem_Click);
//
// TrayIcon
//
this.contextMenu.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.NotifyIcon notifyIcon;
private System.Windows.Forms.ContextMenuStrip contextMenu;
private System.Windows.Forms.ToolStripMenuItem restoreToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem closeToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem muteNotificationsToolStripMenuItem;
}
}

View File

@@ -12,12 +12,15 @@ namespace TweetDck.Core{
public event EventHandler ClickClose;
public bool Visible{
get{
return notifyIcon.Visible;
}
get => notifyIcon.Visible;
set{
if (value){
notifyIcon.Icon = Properties.Resources.icon_tray;
}
notifyIcon.Visible = value;
hasNotifications = false;
}
}
@@ -27,52 +30,56 @@ namespace TweetDck.Core{
}
set{
if (hasNotifications != value){
if (hasNotifications != value && Visible){
notifyIcon.Icon = value ? Properties.Resources.icon_tray_new : Properties.Resources.icon_tray;
hasNotifications = value;
}
}
}
private readonly ContextMenu contextMenu;
private bool hasNotifications;
public TrayIcon(){
InitializeComponent();
notifyIcon.Text = Program.BrandName;
this.contextMenu = new ContextMenu();
this.contextMenu.MenuItems.Add("Restore", menuItemRestore_Click);
this.contextMenu.MenuItems.Add("Mute notifications", menuItemMuteNotifications_Click);
this.contextMenu.MenuItems.Add("Close", menuItemClose_Click);
this.contextMenu.Popup += contextMenu_Popup;
this.notifyIcon.ContextMenu = contextMenu;
this.notifyIcon.Text = Program.BrandName;
}
public TrayIcon(IContainer container) : this(){
container.Add(this);
}
// event handlers
private void trayIcon_MouseClick(object sender, MouseEventArgs e){
if (e.Button == MouseButtons.Left){
restoreToolStripMenuItem_Click(sender, e);
menuItemRestore_Click(sender, e);
}
}
private void contextMenuTray_Opening(object sender, CancelEventArgs e){
muteNotificationsToolStripMenuItem.CheckedChanged -= muteNotificationsToolStripMenuItem_CheckedChanged;
muteNotificationsToolStripMenuItem.Checked = Program.UserConfig.MuteNotifications;
private void contextMenu_Popup(object sender, EventArgs e){
contextMenu.MenuItems[1].Checked = Program.UserConfig.MuteNotifications;
}
private void contextMenuTray_Opened(object sender, EventArgs e){
muteNotificationsToolStripMenuItem.CheckedChanged += muteNotificationsToolStripMenuItem_CheckedChanged;
private void menuItemRestore_Click(object sender, EventArgs e){
ClickRestore?.Invoke(this, e);
}
private void restoreToolStripMenuItem_Click(object sender, EventArgs e){
if (ClickRestore != null){
ClickRestore(this, e);
}
}
private void muteNotificationsToolStripMenuItem_CheckedChanged(object sender, EventArgs e){
Program.UserConfig.MuteNotifications = muteNotificationsToolStripMenuItem.Checked;
private void menuItemMuteNotifications_Click(object sender, EventArgs e){
Program.UserConfig.MuteNotifications = !contextMenu.MenuItems[1].Checked;
Program.UserConfig.Save();
}
private void closeToolStripMenuItem_Click(object sender, EventArgs e){
if (ClickClose != null){
ClickClose(this, e);
}
private void menuItemClose_Click(object sender, EventArgs e){
ClickClose?.Invoke(this, e);
}
}

View File

@@ -8,14 +8,11 @@ namespace TweetDck.Core.Utils{
static class BrowserCache{
private static bool ClearOnExit { get; set; }
private static readonly string IndexFile = Path.Combine(Program.StoragePath, "index");
private static readonly string CacheFolder = Path.Combine(Program.StoragePath, "Cache");
private static IEnumerable<string> CacheFiles{
get{
return Directory.EnumerateFiles(Program.StoragePath).Where(path => {
string file = Path.GetFileName(path);
return file != null && (file.StartsWith("data_", StringComparison.Ordinal) || file.StartsWith("f_", StringComparison.Ordinal));
}).Concat(new[]{ IndexFile });
return Directory.EnumerateFiles(CacheFolder);
}
}

View File

@@ -1,8 +1,11 @@
using System;
using CefSharp;
using System;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;
using System.Windows.Forms;
namespace TweetDck.Core.Utils{
@@ -20,14 +23,37 @@ namespace TweetDck.Core.Utils{
}
}
public static string HeaderUserAgent{
get{
return Program.BrandName+" "+Application.ProductVersion;
public static string HeaderUserAgent => Program.BrandName+" "+Application.ProductVersion;
public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153);
public const string BackgroundColorFix = "let e=document.createElement('style');document.head.appendChild(e);e.innerHTML='body::before{background:#1c6399!important}'";
public static readonly string[] DictionaryWords = {
"tweetdeck", "TweetDeck", "tweetduck", "TweetDuck", "TD"
};
public static bool IsValidUrl(string url){
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){
string scheme = uri.Scheme;
return scheme == Uri.UriSchemeHttp || scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeFtp || scheme == Uri.UriSchemeMailto;
}
return false;
}
public static void OpenExternalBrowser(string url){
if (string.IsNullOrWhiteSpace(url))return;
if (IsValidUrl(url)){
OpenExternalBrowserUnsafe(url);
}
else{
MessageBox.Show("A potentially malicious URL was blocked from opening:"+Environment.NewLine+url, "Blocked URL", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
public static void OpenExternalBrowser(string url){ // TODO implement mailto
Process.Start(url);
public static void OpenExternalBrowserUnsafe(string url){
using(Process.Start(url)){}
}
public static string GetFileNameFromUrl(string url){
@@ -35,6 +61,14 @@ namespace TweetDck.Core.Utils{
return string.IsNullOrEmpty(file) ? null : file;
}
public static string ConvertPascalCaseToScreamingSnakeCase(string str){
return Regex.Replace(str, @"(\p{Ll})(\P{Ll})|(\P{Ll})(\P{Ll}\p{Ll})", "$1$3_$2$4").ToUpperInvariant();
}
public static string GetErrorName(CefErrorCode code){
return ConvertPascalCaseToScreamingSnakeCase(Enum.GetName(typeof(CefErrorCode), code) ?? string.Empty);
}
public static void DownloadFileAsync(string url, string target, Action<Exception> onFailure){
WebClient client = new WebClient{ Proxy = null };
client.Headers[HttpRequestHeader.UserAgent] = HeaderUserAgent;
@@ -47,5 +81,23 @@ namespace TweetDck.Core.Utils{
client.DownloadFileAsync(new Uri(url), target);
}
public static void SetZoomLevel(IBrowser browser, int percentage){
browser.GetHost().SetZoomLevel(Math.Log(percentage/100.0, 1.2));
}
public static bool IsTweetDeckWebsite(IFrame frame){
return frame.Url.Contains("//tweetdeck.twitter.com/");
}
public static bool IsTwitterWebsite(IFrame frame){
return frame.Url.Contains("//twitter.com/");
}
#if DEBUG
public static void HandleConsoleMessage(object sender, ConsoleMessageEventArgs e){
Debug.WriteLine("[Console] {0} ({1}:{2})", e.Message, e.Source, e.Line);
}
#endif
}
}

View File

@@ -0,0 +1,106 @@
using System.Collections.Generic;
using System.Text;
namespace TweetDck.Core.Utils{
class CommandLineArgs{
public static CommandLineArgs FromStringArray(char entryChar, string[] array){
CommandLineArgs args = new CommandLineArgs();
ReadStringArray(entryChar, array, args);
return args;
}
public static void ReadStringArray(char entryChar, string[] array, CommandLineArgs targetArgs){
for(int index = 0; index < array.Length; index++){
string entry = array[index];
if (entry.Length > 0 && entry[0] == entryChar){
if (index < array.Length-1){
string potentialValue = array[index+1];
if (potentialValue.Length > 0 && potentialValue[0] == entryChar){
targetArgs.AddFlag(entry);
}
else{
targetArgs.SetValue(entry, potentialValue);
++index;
}
}
else{
targetArgs.AddFlag(entry);
}
}
}
}
private readonly HashSet<string> flags = new HashSet<string>();
private readonly Dictionary<string, string> values = new Dictionary<string, string>();
public int Count => flags.Count+values.Count;
public void AddFlag(string flag){
flags.Add(flag.ToLowerInvariant());
}
public bool HasFlag(string flag){
return flags.Contains(flag.ToLowerInvariant());
}
public void RemoveFlag(string flag){
flags.Remove(flag.ToLowerInvariant());
}
public void SetValue(string key, string value){
values[key.ToLowerInvariant()] = value;
}
public bool HasValue(string key){
return values.ContainsKey(key.ToLowerInvariant());
}
public string GetValue(string key, string defaultValue){
return values.TryGetValue(key.ToLowerInvariant(), out string val) ? val : defaultValue;
}
public void RemoveValue(string key){
values.Remove(key.ToLowerInvariant());
}
public CommandLineArgs Clone(){
CommandLineArgs copy = new CommandLineArgs();
foreach(string flag in flags){
copy.AddFlag(flag);
}
foreach(KeyValuePair<string, string> kvp in values){
copy.SetValue(kvp.Key, kvp.Value);
}
return copy;
}
public void ToDictionary(IDictionary<string, string> target){
foreach(string flag in flags){
target[flag] = "1";
}
foreach(KeyValuePair<string, string> kvp in values){
target[kvp.Key] = kvp.Value;
}
}
public override string ToString(){
StringBuilder build = new StringBuilder();
foreach(string flag in flags){
build.Append(flag).Append(' ');
}
foreach(KeyValuePair<string, string> kvp in values){
build.Append(kvp.Key).Append(" \"").Append(kvp.Value).Append("\" ");
}
return build.Length == 0 ? string.Empty : build.Remove(build.Length-1, 1).ToString();
}
}
}

View File

@@ -1,24 +1,19 @@
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text.RegularExpressions;
namespace TweetDck.Core.Utils{
static class CommandLineArgsParser{
private static Regex splitRegex;
private static Regex SplitRegex{
get{
return splitRegex ?? (splitRegex = new Regex(@"([^=\s]+(?:=(?:""[^""]*?""|[^ ]*))?)", RegexOptions.Compiled));
}
}
private static Regex SplitRegex => splitRegex ?? (splitRegex = new Regex(@"([^=\s]+(?:=(?:[^ ]*""[^""]*?""[^ ]*|[^ ]*))?)", RegexOptions.Compiled));
public static int AddToDictionary(string args, IDictionary<string, string> dictionary){
if (string.IsNullOrWhiteSpace(args)){
return 0;
public static CommandLineArgs ReadCefArguments(string argumentString){
CommandLineArgs args = new CommandLineArgs();
if (string.IsNullOrWhiteSpace(argumentString)){
return args;
}
int count = 0;
foreach(Match match in SplitRegex.Matches(args)){
foreach(Match match in SplitRegex.Matches(argumentString)){
string matchValue = match.Value;
int indexEquals = matchValue.IndexOf('=');
@@ -34,12 +29,11 @@ namespace TweetDck.Core.Utils{
}
if (key.Length != 0){
dictionary[key] = value;
++count;
args.SetValue(key, value);
}
}
return count;
return args;
}
}
}

View File

@@ -9,18 +9,9 @@ namespace TweetDck.Core.Utils{
private static readonly string DisabledLibEGL = LibEGL+".bak";
private static readonly string DisabledLibGLES = LibGLES+".bak";
public static bool IsEnabled{
get{
return File.Exists(LibEGL) && File.Exists(LibGLES);
}
}
public static bool IsEnabled => File.Exists(LibEGL) && File.Exists(LibGLES);
public static bool CanEnable => File.Exists(DisabledLibEGL) && File.Exists(DisabledLibGLES);
public static bool CanEnable{
get{
return File.Exists(DisabledLibEGL) && File.Exists(DisabledLibGLES);
}
}
public static bool Enable(){
if (IsEnabled)return false;

View File

@@ -0,0 +1,37 @@
using System;
namespace TweetDck.Core.Utils{
class InjectedHTML{
public enum Position{
Before, After
}
private readonly Position position;
private readonly string search;
private readonly string html;
public InjectedHTML(Position position, string search, string html){
this.position = position;
this.search = search;
this.html = html;
}
public string Inject(string targetHTML){
int index = targetHTML.IndexOf(search, StringComparison.Ordinal);
if (index == -1){
return targetHTML;
}
int cutIndex;
switch(position){
case Position.Before: cutIndex = index; break;
case Position.After: cutIndex = index+search.Length; break;
default: return targetHTML;
}
return targetHTML.Insert(cutIndex, html);
}
}
}

View File

@@ -0,0 +1,129 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
namespace TweetDck.Core.Utils{
static class NativeCoreAudio{
private const int EDATAFLOW_RENDER = 0;
private const int EROLE_MULTIMEDIA = 1;
[ComImport]
[Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
private class MMDeviceEnumerator{}
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IMMDeviceEnumerator{
int Unimpl_EnumAudioEndpoints();
IMMDevice GetDefaultAudioEndpoint(int dataFlow, int role);
}
[Guid("D666063F-1587-4E43-81F1-B948E807363F")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IMMDevice{
[return:MarshalAs(UnmanagedType.IUnknown)]
object Activate(ref Guid id, int clsCtx, IntPtr activationParams);
}
[Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IAudioSessionManager2{
int Unimpl_FindWillToLive();
int Unimpl_HelloDarknessMyOldFriend();
IAudioSessionEnumerator GetSessionEnumerator();
}
[Guid("E2F5BB11-0570-40CA-ACDD-3AA01277DEE8")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IAudioSessionEnumerator{
int GetCount();
IAudioSessionControl GetSession(int sessionIndex);
}
[Guid("F4B1A599-7266-4319-A8CA-E70ACB11E8CD")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IAudioSessionControl{}
[Guid("BFB7FF88-7239-4FC9-8FA2-07C950BE9C6D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IAudioSessionControl2{
int Unimpl_GetState();
int Unimpl_GetDisplayName();
int Unimpl_SetDisplayName();
int Unimpl_GetIconPath();
int Unimpl_SetIconPath();
int Unimpl_GetGroupingParam();
int Unimpl_SetGroupingParam();
int Unimpl_RegisterAudioSessionNotification();
int Unimpl_UnregisterAudioSessionNotification();
[return:MarshalAs(UnmanagedType.LPWStr)]
string GetSessionIdentifier();
}
[Guid("87CE5498-68D6-44E5-9215-6DA47EF883D8")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface ISimpleAudioVolume{
void SetMasterVolume(float level, ref Guid eventContext);
float GetMasterVolume();
}
[SuppressMessage("ReSharper", "SuspiciousTypeConversion.Global")]
private static ISimpleAudioVolume GetVolumeObject(string name){
IMMDeviceEnumerator devices = (IMMDeviceEnumerator)new MMDeviceEnumerator();
IMMDevice device = devices.GetDefaultAudioEndpoint(EDATAFLOW_RENDER, EROLE_MULTIMEDIA);
Guid sessionManagerGUID = typeof(IAudioSessionManager2).GUID;
IAudioSessionManager2 manager = (IAudioSessionManager2)device.Activate(ref sessionManagerGUID, 0, IntPtr.Zero);
IAudioSessionEnumerator sessions = manager.GetSessionEnumerator();
ISimpleAudioVolume volumeObj = null;
for(int index = sessions.GetCount()-1; index >= 0; index--){
IAudioSessionControl2 ctl = sessions.GetSession(index) as IAudioSessionControl2;
if (ctl != null){
string identifier = ctl.GetSessionIdentifier();
if (identifier != null && identifier.Contains(name)){
volumeObj = ctl as ISimpleAudioVolume;
break;
}
Marshal.ReleaseComObject(ctl);
}
}
Marshal.ReleaseComObject(devices);
Marshal.ReleaseComObject(device);
Marshal.ReleaseComObject(manager);
Marshal.ReleaseComObject(sessions);
return volumeObj;
}
public static double GetMixerVolume(string appPath){
ISimpleAudioVolume obj = GetVolumeObject(appPath);
float level = 1F;
if (obj != null){
level = obj.GetMasterVolume();
Marshal.ReleaseComObject(obj);
}
return Math.Round(level, 2);
}
public static double GetMixerVolumeForCurrentProcess(){
string path;
using(Process process = Process.GetCurrentProcess()){
path = process.MainModule.FileName;
path = path.Substring(Path.GetPathRoot(path).Length);
}
return GetMixerVolume(path);
}
}
}

View File

@@ -1,35 +1,68 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace TweetDck.Core.Utils{
[SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Local")]
static class NativeMethods{
public static readonly IntPtr HWND_BROADCAST = new IntPtr(0xFFFF);
public static readonly IntPtr HOOK_HANDLED = new IntPtr(-1);
public const int HWND_TOPMOST = -1;
public const uint SWP_NOACTIVATE = 0x0010;
public const int MOUSEEVENTF_LEFTDOWN = 0x02;
public const int MOUSEEVENTF_LEFTUP = 0x04;
public const int MOUSEEVENTF_RIGHTDOWN = 0x08;
public const int MOUSEEVENTF_RIGHTUP = 0x10;
public const int SB_HORZ = 0;
public const int BCM_SETSHIELD = 0x160C;
public const int WH_MOUSE_LL = 14;
public const int WH_MOUSEWHEEL = 0x020A;
public const int WM_MOUSE_LL = 14;
public const int WM_MOUSEWHEEL = 0x020A;
public const int WM_XBUTTONDOWN = 0x020B;
public const int WM_XBUTTONUP = 0x020C;
public const int WM_PARENTNOTIFY = 0x0210;
[StructLayout(LayoutKind.Sequential)]
private struct LASTINPUTINFO{
public static readonly uint Size = (uint)Marshal.SizeOf(typeof(LASTINPUTINFO));
public enum MouseButton{
Left, Right
public uint cbSize;
public uint dwTime;
}
[StructLayout(LayoutKind.Sequential)]
private struct POINT{
public int X;
public int Y;
}
[StructLayout(LayoutKind.Sequential)]
private struct MSLLHOOKSTRUCT{
public POINT pt;
public int mouseData;
public int flags;
public int time;
public UIntPtr dwExtraInfo;
}
public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
public static extern bool SetWindowPos(int hWnd, int hWndOrder, int x, int y, int width, int height, uint flags);
private static extern bool SetWindowPos(int hWnd, int hWndOrder, int x, int y, int width, int height, uint flags);
[DllImport("user32.dll")]
public static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);
private static extern bool GetLastInputInfo(ref LASTINPUTINFO info);
[DllImport("user32.dll")]
public static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("gdi32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool BitBlt(IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, uint dwRop);
[DllImport("user32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, int wParam, IntPtr lParam);
@@ -54,25 +87,38 @@ namespace TweetDck.Core.Utils{
SetWindowPos(form.Handle.ToInt32(), hWndOrder, form.Left, form.Top, form.Width, form.Height, flags);
}
public static void SimulateMouseClick(MouseButton button){
int flagHold, flagRelease;
public static int GetMouseHookData(IntPtr ptr){
return Marshal.PtrToStructure<MSLLHOOKSTRUCT>(ptr).mouseData >> 16;
}
switch(button){
case MouseButton.Left:
flagHold = SystemInformation.MouseButtonsSwapped ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_LEFTDOWN;
flagRelease = SystemInformation.MouseButtonsSwapped ? MOUSEEVENTF_RIGHTUP : MOUSEEVENTF_LEFTUP;
break;
public static int GetIdleSeconds(){
LASTINPUTINFO info = new LASTINPUTINFO();
info.cbSize = LASTINPUTINFO.Size;
case MouseButton.Right:
flagHold = SystemInformation.MouseButtonsSwapped ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_RIGHTDOWN;
flagRelease = SystemInformation.MouseButtonsSwapped ? MOUSEEVENTF_LEFTUP : MOUSEEVENTF_RIGHTUP;
break;
default: return;
if (!GetLastInputInfo(ref info)){
return 0;
}
mouse_event(flagHold, Cursor.Position.X, Cursor.Position.Y, 0, 0);
mouse_event(flagRelease, Cursor.Position.X, Cursor.Position.Y, 0, 0);
uint ticks;
unchecked{
ticks = (uint)Environment.TickCount;
}
int seconds = (int)Math.Floor(TimeSpan.FromMilliseconds(ticks-info.dwTime).TotalSeconds);
return Math.Max(0, seconds); // ignore rollover after several weeks of uptime
}
public static void RenderSourceIntoBitmap(IntPtr source, Bitmap target){
using(Graphics graphics = Graphics.FromImage(target)){
IntPtr graphicsHandle = graphics.GetHdc();
try{
BitBlt(graphicsHandle, 0, 0, target.Width, target.Height, source, 0, 0, 0x00CC0020);
}finally{
graphics.ReleaseHdc(graphicsHandle);
}
}
}
}
}

View File

@@ -0,0 +1,102 @@
using System.Collections.Generic;
using System.Linq;
namespace TweetDck.Core.Utils{
class TwoKeyDictionary<K1, K2, V>{
private readonly Dictionary<K1, Dictionary<K2, V>> dict;
private readonly int innerCapacity;
public TwoKeyDictionary() : this(16, 16){}
public TwoKeyDictionary(int outerCapacity, int innerCapacity){
this.dict = new Dictionary<K1, Dictionary<K2, V>>(outerCapacity);
this.innerCapacity = innerCapacity;
}
// Properties
public V this[K1 outerKey, K2 innerKey]{
get{ // throws on missing key
return dict[outerKey][innerKey];
}
set{
if (!dict.TryGetValue(outerKey, out Dictionary<K2, V> innerDict)){
dict.Add(outerKey, innerDict = new Dictionary<K2, V>(innerCapacity));
}
innerDict[innerKey] = value;
}
}
public IEnumerable<V> InnerValues{
get{
foreach(Dictionary<K2, V> innerDict in dict.Values){
foreach(V value in innerDict.Values){
yield return value;
}
}
}
}
// Members
public void Add(K1 outerKey, K2 innerKey, V value){ // throws on duplicate
if (!dict.TryGetValue(outerKey, out Dictionary<K2, V> innerDict)){
dict.Add(outerKey, innerDict = new Dictionary<K2, V>(innerCapacity));
}
innerDict.Add(innerKey, value);
}
public void Clear(){
dict.Clear();
}
public void Clear(K1 outerKey){ // throws on missing key, but keeps the key unlike Remove(K1)
dict[outerKey].Clear();
}
public bool Contains(K1 outerKey){
return dict.ContainsKey(outerKey);
}
public bool Contains(K1 outerKey, K2 innerKey){
Dictionary<K2, V> innerDict;
return dict.TryGetValue(outerKey, out innerDict) && innerDict.ContainsKey(innerKey);
}
public int Count(){
return dict.Values.Sum(d => d.Count);
}
public int Count(K1 outerKey){ // throws on missing key
return dict[outerKey].Count;
}
public bool Remove(K1 outerKey){
return dict.Remove(outerKey);
}
public bool Remove(K1 outerKey, K2 innerKey){
if (dict.TryGetValue(outerKey, out Dictionary<K2, V> innerDict) && innerDict.Remove(innerKey)){
if (innerDict.Count == 0) {
dict.Remove(outerKey);
}
return true;
}
else return false;
}
public bool TryGetValue(K1 outerKey, K2 innerKey, out V value){
if (dict.TryGetValue(outerKey, out Dictionary<K2, V> innerDict)){
return innerDict.TryGetValue(innerKey, out value);
}
else{
value = default(V);
return false;
}
}
}
}

View File

@@ -1,7 +1,7 @@
using System;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using TweetDck.Core.Controls;
namespace TweetDck.Core.Utils{
[Serializable]
@@ -20,7 +20,7 @@ namespace TweetDck.Core.Utils{
form.WindowState = isMaximized ? FormWindowState.Maximized : FormWindowState.Normal;
}
if ((rect == Rectangle.Empty && firstTimeFullscreen) || !Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(form.Bounds))){
if ((rect == Rectangle.Empty && firstTimeFullscreen) || form.IsFullyOutsideView()){
form.DesktopBounds = Screen.PrimaryScreen.WorkingArea;
form.WindowState = FormWindowState.Maximized;
Save(form);

View File

@@ -0,0 +1,99 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Forms;
namespace TweetDck.Core.Utils{
static class WindowsUtils{
public static bool ShouldAvoidToolWindow { get; }
static WindowsUtils(){
Version ver = Environment.OSVersion.Version;
ShouldAvoidToolWindow = ver.Major == 6 && ver.Minor == 2; // windows 8/10
}
public static bool CheckFolderWritePermission(string path){
string testFile = Path.Combine(path, ".test");
try{
Directory.CreateDirectory(path);
using(File.Create(testFile)){}
File.Delete(testFile);
return true;
}catch{
return false;
}
}
public static Process StartProcess(string file, string arguments, bool runElevated){
ProcessStartInfo processInfo = new ProcessStartInfo{
FileName = file,
Arguments = arguments
};
if (runElevated){
processInfo.Verb = "runas";
}
return Process.Start(processInfo);
}
public static bool TrySleepUntil(Func<bool> test, int timeoutMillis, int timeStepMillis){
for(int waited = 0; waited < timeoutMillis; waited += timeStepMillis){
if (test()){
return true;
}
Thread.Sleep(timeStepMillis);
}
return false;
}
public static void ClipboardStripHtmlStyles(){
if (!Clipboard.ContainsText(TextDataFormat.Html)){
return;
}
string originalText = Clipboard.GetText(TextDataFormat.UnicodeText);
string originalHtml = Clipboard.GetText(TextDataFormat.Html);
string updatedHtml = ClipboardRegexes.RegexStripHtmlStyles.Replace(originalHtml, string.Empty);
int removed = originalHtml.Length-updatedHtml.Length;
updatedHtml = ClipboardRegexes.RegexOffsetClipboardHtml.Replace(updatedHtml, match => (int.Parse(match.Value)-removed).ToString().PadLeft(match.Value.Length, '0'));
DataObject obj = new DataObject();
obj.SetText(originalText, TextDataFormat.UnicodeText);
obj.SetText(updatedHtml, TextDataFormat.Html);
SetClipboardData(obj);
}
public static void SetClipboard(string text, TextDataFormat format){
if (string.IsNullOrEmpty(text)){
return;
}
DataObject obj = new DataObject();
obj.SetText(text, format);
SetClipboardData(obj);
}
private static void SetClipboardData(DataObject obj){
try{
Clipboard.SetDataObject(obj);
}catch(ExternalException e){
Program.Reporter.HandleException("Clipboard Error", Program.BrandName+" could not access the clipboard as it is currently used by another process.", true, e);
}
}
private static class ClipboardRegexes{ // delays construction of regular expressions until needed
public static readonly Regex RegexStripHtmlStyles = new Regex(@"\s?(?:style|class)="".*?""");
public static readonly Regex RegexOffsetClipboardHtml = new Regex(@"(?<=EndHTML:|EndFragment:)(\d+)");
}
}
}

View File

@@ -1,77 +0,0 @@
using TweetDck.Core.Controls;
namespace TweetDck.Migration {
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 System.Windows.Forms.Label();
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(15, 52);
this.progressBarUseless.MarqueeAnimationSpeed = 10;
this.progressBarUseless.Name = "progressBarUseless";
this.progressBarUseless.Size = new System.Drawing.Size(477, 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.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelDescription.Location = new System.Drawing.Point(12, 12);
this.labelDescription.Name = "labelDescription";
this.labelDescription.Size = new System.Drawing.Size(480, 37);
this.labelDescription.TabIndex = 1;
this.labelDescription.Text = "Please, watch this informationless progress bar showcase while some magic happens" +
" in the background...";
//
// FormBackgroundWork
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(504, 87);
this.Controls.Add(this.labelDescription);
this.Controls.Add(this.progressBarUseless);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.Name = "FormBackgroundWork";
this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "TweetDeck Migration";
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.ProgressBar progressBarUseless;
private System.Windows.Forms.Label labelDescription;
}
}

View File

@@ -1,15 +0,0 @@
using System;
using System.Windows.Forms;
namespace TweetDck.Migration{
partial class FormBackgroundWork : Form{
public FormBackgroundWork(){
InitializeComponent();
}
public void ShowWorkDialog(Action onBegin){
Shown += (sender, args) => onBegin();
ShowDialog();
}
}
}

View File

@@ -1,148 +0,0 @@
using TweetDck.Core.Controls;
namespace TweetDck.Migration {
partial class FormMigrationQuestion {
/// <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.btnIgnore = new System.Windows.Forms.Button();
this.panelButtons = new System.Windows.Forms.FlowLayoutPanel();
this.btnAskLater = new System.Windows.Forms.Button();
this.btnMigrate = new System.Windows.Forms.Button();
this.btnMigrateUninstall = new System.Windows.Forms.Button();
this.labelQuestion = new System.Windows.Forms.Label();
this.panelButtons.SuspendLayout();
this.SuspendLayout();
//
// btnIgnore
//
this.btnIgnore.AutoSize = true;
this.btnIgnore.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.btnIgnore.Location = new System.Drawing.Point(391, 0);
this.btnIgnore.Margin = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnIgnore.Name = "btnIgnore";
this.btnIgnore.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnIgnore.Size = new System.Drawing.Size(53, 23);
this.btnIgnore.TabIndex = 1;
this.btnIgnore.Text = "Ignore";
this.btnIgnore.UseVisualStyleBackColor = true;
this.btnIgnore.Click += new System.EventHandler(this.btnIgnore_Click);
//
// panelButtons
//
this.panelButtons.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelButtons.Controls.Add(this.btnAskLater);
this.panelButtons.Controls.Add(this.btnIgnore);
this.panelButtons.Controls.Add(this.btnMigrate);
this.panelButtons.Controls.Add(this.btnMigrateUninstall);
this.panelButtons.FlowDirection = System.Windows.Forms.FlowDirection.RightToLeft;
this.panelButtons.Location = new System.Drawing.Point(12, 67);
this.panelButtons.Name = "panelButtons";
this.panelButtons.Size = new System.Drawing.Size(518, 23);
this.panelButtons.TabIndex = 0;
//
// btnAskLater
//
this.btnAskLater.AutoSize = true;
this.btnAskLater.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.btnAskLater.Location = new System.Drawing.Point(450, 0);
this.btnAskLater.Margin = new System.Windows.Forms.Padding(3, 0, 0, 0);
this.btnAskLater.Name = "btnAskLater";
this.btnAskLater.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnAskLater.Size = new System.Drawing.Size(68, 23);
this.btnAskLater.TabIndex = 0;
this.btnAskLater.Text = "Ask Later";
this.btnAskLater.UseVisualStyleBackColor = true;
this.btnAskLater.Click += new System.EventHandler(this.btnAskLater_Click);
//
// btnMigrate
//
this.btnMigrate.AutoSize = true;
this.btnMigrate.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.btnMigrate.Location = new System.Drawing.Point(327, 0);
this.btnMigrate.Margin = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnMigrate.Name = "btnMigrate";
this.btnMigrate.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnMigrate.Size = new System.Drawing.Size(58, 23);
this.btnMigrate.TabIndex = 3;
this.btnMigrate.Text = "Migrate";
this.btnMigrate.UseVisualStyleBackColor = true;
this.btnMigrate.Click += new System.EventHandler(this.btnMigrate_Click);
//
// btnMigrateUninstall
//
this.btnMigrateUninstall.AutoSize = true;
this.btnMigrateUninstall.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.btnMigrateUninstall.Location = new System.Drawing.Point(223, 0);
this.btnMigrateUninstall.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.btnMigrateUninstall.Name = "btnMigrateUninstall";
this.btnMigrateUninstall.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnMigrateUninstall.Size = new System.Drawing.Size(98, 23);
this.btnMigrateUninstall.TabIndex = 4;
this.btnMigrateUninstall.Text = "Migrate && Purge";
this.btnMigrateUninstall.UseVisualStyleBackColor = true;
this.btnMigrateUninstall.Click += new System.EventHandler(this.btnMigrateUninstall_Click);
//
// labelQuestion
//
this.labelQuestion.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.labelQuestion.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelQuestion.Location = new System.Drawing.Point(49, 9);
this.labelQuestion.Margin = new System.Windows.Forms.Padding(40, 3, 3, 3);
this.labelQuestion.Name = "labelQuestion";
this.labelQuestion.Size = new System.Drawing.Size(481, 52);
this.labelQuestion.TabIndex = 2;
//
// FormMigrationQuestion
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(542, 102);
this.Controls.Add(this.labelQuestion);
this.Controls.Add(this.panelButtons);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "FormMigrationQuestion";
this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "TweetDeck Migration";
this.panelButtons.ResumeLayout(false);
this.panelButtons.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Button btnIgnore;
private System.Windows.Forms.FlowLayoutPanel panelButtons;
private System.Windows.Forms.Button btnMigrate;
private System.Windows.Forms.Label labelQuestion;
private System.Windows.Forms.Button btnAskLater;
private System.Windows.Forms.Button btnMigrateUninstall;
}
}

View File

@@ -1,42 +0,0 @@
using System;
using System.Drawing;
using System.Windows.Forms;
namespace TweetDck.Migration{
partial class FormMigrationQuestion : Form{
public MigrationDecision Decision { get; private set; }
public FormMigrationQuestion(){
InitializeComponent();
labelQuestion.Text = "Hey there, I found some TweetDeck data! Do you want to »Migrate« it and delete the old data folder, »Ignore« the request forever, or try "+Program.BrandName+" out first?\r\nYou may also »Migrate && Purge« which uninstalls TweetDeck too!";
}
protected override void OnPaint(PaintEventArgs e){
e.Graphics.DrawIcon(SystemIcons.Question, 10, 10);
base.OnPaint(e);
}
private void btnMigrateUninstall_Click(object sender, EventArgs e){
Close(MigrationDecision.MigratePurge);
}
private void btnMigrate_Click(object sender, EventArgs e){
Close(MigrationDecision.Migrate);
}
private void btnIgnore_Click(object sender, EventArgs e){
Close(MigrationDecision.Ignore);
}
private void btnAskLater_Click(object sender, EventArgs e){
Close(MigrationDecision.AskLater);
}
private void Close(MigrationDecision decision){
Decision = decision;
DialogResult = DialogResult.OK;
Close();
}
}
}

View File

@@ -1,43 +0,0 @@
using System;
using System.Linq;
using Microsoft.Win32;
namespace TweetDck.Migration.Helpers{
static class ProgramRegistrySearch{
public static string FindByDisplayName(string displayName){
Predicate<RegistryKey> predicate = key => displayName.Equals(key.GetValue("DisplayName") as string, StringComparison.OrdinalIgnoreCase);
string guid;
return FindMatchingSubKey(Registry.LocalMachine, @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall", predicate, out guid) ||
FindMatchingSubKey(Registry.LocalMachine, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", predicate, out guid) ||
FindMatchingSubKey(Registry.CurrentUser, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", predicate, out guid)
? guid : null;
}
private static bool FindMatchingSubKey(RegistryKey keyHandle, string path, Predicate<RegistryKey> predicate, out string guid){
string outputId = null;
try{
RegistryKey parentKey = keyHandle.OpenSubKey(path, false);
if (parentKey == null)throw new InvalidOperationException();
foreach(RegistryKey subKey in parentKey.GetSubKeyNames().Select(subName => parentKey.OpenSubKey(subName, false)).Where(subKey => subKey != null)){
if (predicate(subKey)){
outputId = subKey.Name.Substring(subKey.Name.LastIndexOf('\\')+1);
subKey.Close();
break;
}
subKey.Close();
}
parentKey.Close();
}catch(Exception){
guid = null;
return false;
}
return (guid = outputId) != null;
}
}
}

View File

@@ -1,23 +0,0 @@
namespace TweetDck.Migration{
enum MigrationDecision{
/// <summary>
/// Copies the important files and then deletes the TweetDeck folder.
/// </summary>
Migrate,
/// <summary>
/// Does exactly what <see cref="Migrate"/> does, but also silently uninstalls TweetDeck.
/// </summary>
MigratePurge,
/// <summary>
/// Does not copy any files and does not ask the user about data migration again.
/// </summary>
Ignore,
/// <summary>
/// Does not copy any files but asks the user again when the program is re-ran.
/// </summary>
AskLater
}
}

View File

@@ -1,188 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.Win32;
using TweetDck.Migration.Helpers;
namespace TweetDck.Migration{
static class MigrationManager{
private static readonly string TweetDeckPathParent = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "twitter");
private static readonly string TweetDeckPath = Path.Combine(TweetDeckPathParent, "TweetDeck");
private static readonly string TweetDickStorage = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "TweetDick");
public static void Run(){
#if DUCK
if (!Program.IsPortable && Directory.Exists(TweetDickStorage) && !Directory.Exists(Program.StoragePath)){
if (MessageBox.Show("Welcome to TweetDuck! Would you like to move your old TweetDick configuration and login data?", "TweetDick Migration", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes){
try{
Directory.Move(TweetDickStorage, Program.StoragePath);
MessageBox.Show("All done! You can now uninstall TweetDick.", "TweetDick Migration", MessageBoxButtons.OK, MessageBoxIcon.Information);
}catch(Exception ex){
Program.HandleException("An unexpected exception has occurred during the migration process.", ex);
}
}
return;
}
#endif
if (!Program.UserConfig.IgnoreMigration && Directory.Exists(TweetDeckPath)){
MigrationDecision decision;
using(FormMigrationQuestion formQuestion = new FormMigrationQuestion()){
decision = formQuestion.ShowDialog() == DialogResult.OK ? formQuestion.Decision : MigrationDecision.AskLater;
}
switch(decision){
case MigrationDecision.MigratePurge:
case MigrationDecision.Migrate:
FormBackgroundWork formWait = new FormBackgroundWork();
formWait.ShowWorkDialog(() => {
if (!BeginMigration(decision, ex => formWait.Invoke(new Action(() => {
formWait.Close();
if (ex != null){
Program.HandleException("An unexpected exception has occurred during the migration process.", ex);
return;
}
Program.UserConfig.IgnoreMigration = true;
Program.UserConfig.Save();
})))){
formWait.Close();
}
});
break;
case MigrationDecision.Ignore:
Program.UserConfig.IgnoreMigration = true;
Program.UserConfig.Save();
break;
}
}
else if (!Program.UserConfig.IgnoreUninstallCheck){
string guid = ProgramRegistrySearch.FindByDisplayName("TweetDeck");
if (guid != null && MessageBox.Show("TweetDeck is still installed on your computer, do you want to uninstall it?", "Uninstall TweetDeck", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
RunUninstaller(guid, 0);
CleanupTweetDeck();
}
Program.UserConfig.IgnoreUninstallCheck = true;
Program.UserConfig.Save();
}
}
private static bool BeginMigration(MigrationDecision decision, Action<Exception> onFinished){
if (decision != MigrationDecision.MigratePurge && decision != MigrationDecision.Migrate){
return false;
}
Task task = new Task(() => {
Directory.CreateDirectory(Program.StoragePath);
Directory.CreateDirectory(Path.Combine(Program.StoragePath, "localStorage"));
Directory.CreateDirectory(Path.Combine(Program.StoragePath, "Local Storage"));
CopyFile("Cookies");
CopyFile("Cookies-journal");
CopyFile("localStorage"+Path.DirectorySeparatorChar+"qrc__0.localstorage");
CopyFile("Local Storage"+Path.DirectorySeparatorChar+"https_tweetdeck.twitter.com_0.localstorage");
CopyFile("Local Storage"+Path.DirectorySeparatorChar+"https_tweetdeck.twitter.com_0.localstorage-journal");
if (decision == MigrationDecision.Migrate || decision == MigrationDecision.MigratePurge){
// kill process if running
Process runningProcess = null;
try{
runningProcess = Process.GetProcessesByName("TweetDeck").FirstOrDefault(process => process.MainWindowHandle != IntPtr.Zero);
}catch(Exception){
// process not found
}
if (runningProcess != null){
runningProcess.CloseMainWindow();
for(int wait = 0; wait < 100 && !runningProcess.HasExited; wait++){ // 10 seconds
runningProcess.Refresh();
Thread.Sleep(100);
}
runningProcess.Close();
}
// delete folders
for(int wait = 0; wait < 50; wait++){
try{
Directory.Delete(TweetDeckPath, true);
break;
}catch(Exception){
// browser subprocess not ended yet, wait
Thread.Sleep(300);
}
}
try{
Directory.Delete(TweetDeckPathParent, false);
}catch(IOException){
// most likely not empty, ignore
}
}
if (decision == MigrationDecision.MigratePurge){
// uninstall in the background
string guid = ProgramRegistrySearch.FindByDisplayName("TweetDeck");
if (guid != null){
RunUninstaller(guid, 5000);
}
// registry cleanup
CleanupTweetDeck();
// migration finished like a boss
}
});
task.ContinueWith(originalTask => onFinished(originalTask.Exception), TaskContinuationOptions.ExecuteSynchronously);
task.Start();
return true;
}
private static void CopyFile(string relativePath){
try{
File.Copy(Path.Combine(TweetDeckPath, relativePath), Path.Combine(Program.StoragePath, relativePath), true);
}catch(FileNotFoundException){
}catch(DirectoryNotFoundException){
}
}
private static void RunUninstaller(string guid, int timeout){
Process uninstaller = Process.Start("msiexec.exe", "/x "+guid+" /quiet /qn");
if (uninstaller != null){
if (timeout > 0){
uninstaller.WaitForExit(timeout); // it appears that the process is restarted or something that triggers this, but it shouldn't be a problem
}
uninstaller.Close();
}
}
private static void CleanupTweetDeck(){
try{
Registry.CurrentUser.DeleteSubKeyTree(@"Software\Twitter\TweetDeck", true);
Registry.CurrentUser.DeleteSubKey(@"Software\Twitter"); // only if empty
}catch(Exception){
// not found or too bad
}
}
}
}

View File

@@ -42,7 +42,7 @@
this.btnToggleState.Location = new System.Drawing.Point(459, 80);
this.btnToggleState.Name = "btnToggleState";
this.btnToggleState.Size = new System.Drawing.Size(65, 23);
this.btnToggleState.TabIndex = 0;
this.btnToggleState.TabIndex = 5;
this.btnToggleState.Text = "Disable";
this.btnToggleState.UseVisualStyleBackColor = true;
this.btnToggleState.Click += new System.EventHandler(this.btnToggleState_Click);
@@ -54,8 +54,9 @@
this.labelName.Location = new System.Drawing.Point(7, 7);
this.labelName.Name = "labelName";
this.labelName.Size = new System.Drawing.Size(61, 24);
this.labelName.TabIndex = 1;
this.labelName.TabIndex = 0;
this.labelName.Text = "Name";
this.labelName.UseMnemonic = false;
//
// panelDescription
//
@@ -79,8 +80,9 @@
this.labelDescription.Margin = new System.Windows.Forms.Padding(0);
this.labelDescription.Name = "labelDescription";
this.labelDescription.Size = new System.Drawing.Size(13, 39);
this.labelDescription.TabIndex = 3;
this.labelDescription.TabIndex = 0;
this.labelDescription.Text = "a\r\nb\r\nc";
this.labelDescription.UseMnemonic = false;
//
// labelAuthor
//
@@ -89,8 +91,9 @@
this.labelAuthor.Margin = new System.Windows.Forms.Padding(3, 0, 32, 0);
this.labelAuthor.Name = "labelAuthor";
this.labelAuthor.Size = new System.Drawing.Size(38, 13);
this.labelAuthor.TabIndex = 3;
this.labelAuthor.TabIndex = 0;
this.labelAuthor.Text = "Author";
this.labelAuthor.UseMnemonic = false;
//
// flowLayoutInfo
//
@@ -101,7 +104,7 @@
this.flowLayoutInfo.Location = new System.Drawing.Point(11, 85);
this.flowLayoutInfo.Name = "flowLayoutInfo";
this.flowLayoutInfo.Size = new System.Drawing.Size(368, 18);
this.flowLayoutInfo.TabIndex = 4;
this.flowLayoutInfo.TabIndex = 3;
this.flowLayoutInfo.WrapContents = false;
//
// labelWebsite
@@ -113,8 +116,9 @@
this.labelWebsite.Location = new System.Drawing.Point(76, 0);
this.labelWebsite.Name = "labelWebsite";
this.labelWebsite.Size = new System.Drawing.Size(46, 13);
this.labelWebsite.TabIndex = 5;
this.labelWebsite.TabIndex = 1;
this.labelWebsite.Text = "Website";
this.labelWebsite.UseMnemonic = false;
this.labelWebsite.Click += new System.EventHandler(this.labelWebsite_Click);
//
// labelVersion
@@ -125,9 +129,10 @@
this.labelVersion.Margin = new System.Windows.Forms.Padding(3, 9, 3, 0);
this.labelVersion.Name = "labelVersion";
this.labelVersion.Size = new System.Drawing.Size(513, 13);
this.labelVersion.TabIndex = 5;
this.labelVersion.TabIndex = 1;
this.labelVersion.Text = "Version";
this.labelVersion.TextAlign = System.Drawing.ContentAlignment.TopRight;
this.labelVersion.UseMnemonic = false;
//
// btnOpenConfig
//
@@ -135,7 +140,7 @@
this.btnOpenConfig.Location = new System.Drawing.Point(385, 80);
this.btnOpenConfig.Name = "btnOpenConfig";
this.btnOpenConfig.Size = new System.Drawing.Size(68, 23);
this.btnOpenConfig.TabIndex = 6;
this.btnOpenConfig.TabIndex = 4;
this.btnOpenConfig.Text = "Configure";
this.btnOpenConfig.UseVisualStyleBackColor = true;
this.btnOpenConfig.Click += new System.EventHandler(this.btnOpenConfig_Click);

View File

@@ -50,7 +50,7 @@ namespace TweetDck.Plugins.Controls{
}
private void btnOpenConfig_Click(object sender, EventArgs e){
using(Process.Start("explorer.exe", "/select,\""+plugin.ConfigPath+"\"")){}
using(Process.Start("explorer.exe", "/select,\""+plugin.ConfigPath.Replace('/', '\\')+"\"")){}
}
private void btnToggleState_Click(object sender, EventArgs e){
@@ -61,7 +61,7 @@ namespace TweetDck.Plugins.Controls{
}
private void UpdatePluginState(){
bool isEnabled = plugin.CanRun && pluginManager.Config.IsEnabled(plugin);
bool isEnabled = pluginManager.Config.IsEnabled(plugin) && plugin.CanRun;
Color textColor = isEnabled ? Color.Black : Color.FromArgb(90, 90, 90);
labelVersion.ForeColor = textColor;

View File

@@ -1,3 +0,0 @@
namespace TweetDck.Plugins.Controls{
partial class PluginListFlowLayout{}
}

View File

@@ -2,7 +2,7 @@
using TweetDck.Core.Utils;
namespace TweetDck.Plugins.Controls{
sealed partial class PluginListFlowLayout : FlowLayoutPanel{
sealed class PluginListFlowLayout : FlowLayoutPanel{
public PluginListFlowLayout(){
FlowDirection = FlowDirection.TopDown;
WrapContents = false;

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
namespace TweetDck.Plugins{
namespace TweetDck.Plugins.Enums{
[Flags]
enum PluginEnvironment{
None = 0,

View File

@@ -0,0 +1,5 @@
namespace TweetDck.Plugins.Enums{
enum PluginFolder{
Root, Data
}
}

View File

@@ -1,4 +1,4 @@
namespace TweetDck.Plugins{
namespace TweetDck.Plugins.Enums{
enum PluginGroup{
Official, Custom
}

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