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

Compare commits

...

490 Commits

Author SHA1 Message Date
e50480aa35 Fix edit-design plugin modal labels changing margins with different themes 2018-01-16 22:55:21 +01:00
6943c7813f Fix hovering scrollbars not changing their color with edit-design plugin enabled 2018-01-16 22:54:53 +01:00
7c9b4382ca Fix Follow dialog closing when clicking any but the first Follow button 2018-01-16 19:20:14 +01:00
3187f97592 Rewrite 'Keep Like/Follow dialogs open' code after TD removed the old way 2018-01-15 21:25:43 +01:00
b71a367052 Merge pull request #196 from chylex/delet_audio_lib
Remove audio library
2018-01-14 11:13:31 +01:00
2d4bbf2a6f Fix sound notification extension detection and add warning to mp3 files 2018-01-14 00:08:43 +01:00
6e59dfddcc Remove audio library 2018-01-13 23:38:30 +01:00
bd92fc6ee0 Use <audio> for custom sound notifications & allow volume control for default one
Closes #195
2018-01-13 22:59:34 +01:00
2f61de7025 Add GetHandlerFactory extension method to BrowserUtils 2018-01-13 22:37:24 +01:00
8fcec7ec7c Merge remote-tracking branch 'refs/remotes/origin/master' into delet_audio_lib 2018-01-13 19:50:13 +01:00
33d9ba3871 Refactor UserConfig event invocations into a generic method 2018-01-13 19:49:16 +01:00
4f8c778ba0 Ignore errors in automatic cache clearing
Closes #194
2018-01-13 15:35:20 +01:00
804c739038 Fix broken element dragging (timeline tweets and maybe more) 2018-01-13 15:27:07 +01:00
a0445fbb12 Release 1.12.1.1 2018-01-12 12:48:14 +01:00
7ab5d7b796 Release 1.12.1 2018-01-12 12:40:09 +01:00
7f83a7773b Fix 'Show this thread' not working >_> 2018-01-12 12:39:46 +01:00
fc9e8a808f Fix emoji inline tweak not working in DMs 2018-01-11 21:48:00 +01:00
5ab8976bc7 Fix broken links in guide because Chromium is an idiot 2018-01-11 21:06:31 +01:00
e2a28f2811 Allow typing unambiguous :emoji: in inline replies 2018-01-11 20:15:56 +01:00
137a20ed0e Fix notification tooltip not disappearing when skipping/closing without moving mouse 2018-01-11 00:06:18 +01:00
f956f696f4 Limit some $TD functions to browser/notification, change displayTooltip params 2018-01-10 23:53:37 +01:00
bb7cbde38f Make 'Show this thread' in notification open tweet detail
Closes #191
2018-01-10 22:47:50 +01:00
8c452d3fa2 Fix clipboard html styles when copying text from notifications 2018-01-10 22:47:06 +01:00
f65c33c432 Add newly added option to analytics report 2018-01-10 22:17:30 +01:00
da2758ccb1 Add option to keep Like/Follow dialogs open
Closes #193
2018-01-10 14:22:47 +01:00
de10112b7f Remove non-english locale files from the installation 2018-01-03 16:23:56 +01:00
301d4fb171 Replace app locale option with spell check language & use correct lang list 2018-01-03 16:04:11 +01:00
f0a79add14 Fix broken example notification after closing it and then changing options 2018-01-03 14:52:47 +01:00
d33bc9fe25 Release 1.12 2018-01-01 01:48:48 +01:00
a2a5dfd435 Reset the official plugins folder in the update installer 2018-01-01 01:27:58 +01:00
f3d7c8d4c3 Set language header to match app locale 2017-12-31 14:21:21 +01:00
67f60dd787 Merge pull request #190 from chylex/locale_magic
Add options for app locale (remove argument) & target translation language
2017-12-31 14:03:15 +01:00
62310ce4a4 Add an option to set target language for tweet translations 2017-12-31 13:59:19 +01:00
3a27089364 Add an option to set app locale in a new Options tab & remove locale argument 2017-12-31 13:29:38 +01:00
a05460f562 Make BrowserCache.CacheFolder a property 2017-12-31 12:14:45 +01:00
390872c305 Merge pull request #189 from chylex/settings_ui_plz 2017-12-31 10:26:23 +01:00
594d12df79 Reorganize all Options tabs into FlowLayoutPanels 2017-12-31 10:20:42 +01:00
c42c12c72b Move Options tooltips outside designer files & reorder options in code 2017-12-31 08:10:24 +01:00
c37f4fe365 Merge branch 'master' of https://github.com/chylex/TweetDuck 2017-12-31 07:02:20 +01:00
8d37d68770 Fix dragging links not stripping t.co shortener
Closes #183
2017-12-31 06:59:17 +01:00
1b3d1fb36a Tweak readme wording 2017-12-31 05:36:32 +01:00
2f352ef9bb Update CefSharp to 63-pre01 and update documentation 2017-12-31 05:34:36 +01:00
527f3cab4c Fix reinstantiating AnalyticsManager when restoring from tray & test stuff 2017-12-31 05:01:23 +01:00
f67bf27db2 Add an option to automatically clear cache after exceeding a set size
Closes #182
2017-12-30 02:06:36 +01:00
a8bb3ba349 Improve performance and safety of cache size checking & clearing 2017-12-28 04:52:53 +01:00
321ab12213 WTF is AnyCPU doing in the project files 2017-12-28 00:50:51 +01:00
4cab18e557 Merge pull request #187 from chylex/cefsharp63
Update CefSharp to 63 (early build) & delete GC reload
2017-12-28 00:39:07 +01:00
c15ea97a36 Update CEF again 2017-12-28 00:32:26 +01:00
a0cc4109df Delete GC reload in CEF 63 (#186) 2017-12-28 00:03:24 +01:00
f66ff1000a Replace AbstractRequestHandler with CefSharp's implementation 2017-12-21 22:45:29 +01:00
51a9bb6d3c Update CefSharp to 63 pre-release 2017-12-21 20:25:36 +01:00
07017bd29b Fix cut off usernames in Messages column 2017-12-21 20:15:02 +01:00
45b6f49a08 Fix middle-click and ctrl-click handling in CEF 62 2017-12-07 20:07:15 +01:00
103ad72788 Update CefSharp to 62 (early build) 2017-12-07 19:50:43 +01:00
543259f29f Release 1.11.2 2017-11-21 07:07:24 +01:00
98799734c5 Add a box shadow to main menu in the guide 2017-11-21 06:59:54 +01:00
96f491a666 Redo the introduction dialog (add main menu image & follow link, rewrite text) 2017-11-21 06:11:49 +01:00
29e541dbef Fix comment formatting in notification.css 2017-11-20 18:01:17 +01:00
1343b9c113 Fix username alignment (follow notifications & all types in desktop notifications) 2017-11-20 18:01:02 +01:00
94920fd459 Ensure only one guide window is open and fix webkit element outline 2017-11-20 17:44:28 +01:00
b2f3b245b7 Open TweetDuck guide links directly in the app 2017-11-20 17:25:25 +01:00
15bc6c1d73 Fix idiot chromium being unable to figure out window size while loading guide 2017-11-20 17:21:46 +01:00
2c175b8d3a Fix incorrect default config value for notification scroll speed 2017-11-18 15:31:36 +01:00
a48c17a769 Update analytics (fix system edition, add dev tools and other feature collection) 2017-11-18 07:15:18 +01:00
03465c4ab0 Remove dismissed update config entry after accepting an update 2017-11-18 02:53:25 +01:00
b4e936c530 Minor refactoring of notification classes & remove no longer needed CSS 2017-11-14 19:06:05 +01:00
fb1482370a Fix issues from TweetDeck updates (long usernames, badge in detail view, notification media previews) 2017-11-14 18:42:34 +01:00
e831bc2bea Fix broken compose text size in edit-design plugin after a TweetDeck update 2017-11-11 17:32:20 +01:00
c74c168c96 Release 1.11.1 2017-11-10 14:59:48 +01:00
40b53fa40c Fix broken update notification CSS after a TweetDeck update 2017-11-10 14:52:32 +01:00
3481cc0349 Fix broken CSS in browser and plugins after a TweetDeck update 2017-11-10 14:44:47 +01:00
09abd889e9 Add ID to <html> for selector priority 2017-11-10 14:14:50 +01:00
330bbfbb31 Fix broken CSS in notifications after a TweetDeck update 2017-11-10 13:35:29 +01:00
6b7b690476 Remove Log method from the bridge object 2017-11-09 21:43:22 +01:00
cea72801a7 Fix video player process sometimes taking too long to close 2017-11-09 19:29:01 +01:00
04369e22a7 Add option to disable animated avatars (general GIF toggle)
Note: this breaks when disable-extensions is used, so it was changed to
disable-pdf-extension instead
2017-11-09 18:18:22 +01:00
f1b16eab9a Add a global function (including one for plugins) to reload columns 2017-11-09 18:15:58 +01:00
13646d9c90 Increase quality of media previews in desktop notifications 2017-11-09 17:08:39 +01:00
17d762ce91 Make spell check option not require a restart 2017-11-09 16:51:40 +01:00
edb40adaa1 Release 1.11 2017-11-09 14:40:09 +01:00
bc0809994a Fix import & restart overwriting some imported settings such as zoom, add a TODO for system config import/export 2017-11-08 12:23:58 +01:00
a3e3d517b0 Fix high DPI for dialogs displayed before the main window 2017-11-08 11:39:32 +01:00
d8b63a54ca Update the guide 2017-11-08 10:56:05 +01:00
b81e7583eb Implement feature usage analytics 2017-11-08 08:15:45 +01:00
51f9ba3642 Add option to always show character count to edit-design plugin 2017-11-08 06:40:36 +01:00
296626f7c7 Implement a notification about previously enabled anonymous data sending 2017-11-07 21:37:41 +01:00
5b2daf9746 Refactor method order and return types in config file classes 2017-11-07 18:36:52 +01:00
9a6b615174 Cleanup FileSerializer Write/Read calls & change exception for empty files 2017-11-07 18:10:38 +01:00
18f8d5b269 Tweak key format in analytics request 2017-11-04 14:07:24 +01:00
2867a875c9 "Fix" an uncommon video player crash when closing short videos
Closes #177
2017-11-04 14:03:28 +01:00
ee2f5ae8cb Disable default TweetDeck update notification 2017-11-04 03:30:50 +01:00
bd5c301fb9 Refactor old icon style declaration in edit-design and add !important 2017-11-02 16:45:26 +01:00
6df68629f7 Implement system config export/import/reset (without UI) 2017-11-02 13:34:51 +01:00
be08fd4445 Remove UserConfig.ZoomMultiplier and old migration code 2017-11-02 13:20:19 +01:00
6f1afb94fb Add a global function to reprioritize events and refactor stuff 2017-11-02 11:02:00 +01:00
7401b8a52d Fix search input not being unfocused after opening search externally 2017-11-02 10:10:04 +01:00
c83b62ebaa Make searching while holding Ctrl or middle-clicking search icon open search externally 2017-11-02 09:46:33 +01:00
108cf8923e Implement analytics (#176)
* Implement analytics report generation w/ user config and basic system info

* Add HW and plugin info to analytics report generation

* Add a way of displaying the full analytics report

* Fix issues in analytics report and include design theme

* Ensure tab config is saved when switching tabs

* Fix compilation error in TabSettingsFeedback and safeguard nulls

* Add locale to analytics report

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

* Add analytics state file and implement sending reports every week

* Send an analytics report after each update
2017-11-02 03:08:43 +01:00
4e26fd9d56 Random refactoring 2017-11-01 04:02:44 +01:00
8c9168a4bf Temporarily move notification with Alt and restore by middle-clicking title bar
Closes #175
2017-10-31 23:40:56 +01:00
97da0b79e4 Fix compile error after removing TimerBarHeight constant 2017-10-31 23:34:00 +01:00
d7e5f6876b Increase notification timer height with higher DPI 2017-10-31 23:10:08 +01:00
1b92b112e2 Refactor FormNotificationBase.GetBorderStyle 2017-10-31 12:50:34 +01:00
ca55119531 Rewrite CanMoveWindow and CanResizeWindow in notification classes 2017-10-31 12:36:46 +01:00
d9da14b5dc Make the example notification its own class 2017-10-31 12:01:53 +01:00
512b5666ac Fix 'Escape' key not clearing notification tweet queue & refactor HideNotification 2017-10-31 11:31:01 +01:00
64977964e8 Bump project versions 2017-10-31 10:53:42 +01:00
2bc13e0de6 Remove subprocess dependency on communication lib & remove Comms class 2017-10-31 10:51:33 +01:00
b90c5f17cf Fix misaligned Plugins form controls when reloading plugins after scrolling down 2017-10-31 07:18:38 +01:00
7d8d0bd43b Refactor awful plugin loading and management code 2017-10-31 07:13:17 +01:00
Alexander
54c1137927 Fix warning about possible null reference in audio playback error event (#174)
* Fix V3083 warnings from PVS-Studio Static Analyzer
2017-10-29 19:32:18 +01:00
e6655219ee Add a context menu item to apply ROT13 to text selection in inputs 2017-10-29 04:39:28 +01:00
5896f8e35a Allow only px values in custom items in edit-design plugin 2017-10-25 22:12:28 +02:00
934cba7251 Fix link expansion & tooltips not working with long domains (ellipsis in front)
Closes #172
2017-10-25 19:23:02 +02:00
9cc1a11bef Fix a rare issue where GIFs open in browser due to a missing source URL 2017-10-23 00:25:23 +02:00
c1bc956d6d Move TrayIcon to a different namespace 2017-10-21 15:46:52 +02:00
351b174b86 Refactor TweetDeck browser into a separate class 2017-10-21 15:35:46 +02:00
0b4aaf80dc Remove unnecessary usings and code 2017-10-21 14:53:50 +02:00
c10c185817 Release 1.10.3 2017-10-19 00:43:06 +02:00
327ef1cbee Restart TweetDuck if user declines UAC when updating and improve error handling 2017-10-19 00:30:37 +02:00
15eb823c7f Replace OpenExternalBrowserUnsafe with the new OpenAssociatedProgram 2017-10-19 00:24:40 +02:00
54613e5242 Handle errors when opening images in associated viewer 2017-10-19 00:21:53 +02:00
df1352cbe3 Update README.md 2017-10-18 23:50:23 +02:00
0559afd972 Make middle-click tweet actions work in detail view 2017-10-18 16:50:34 +02:00
afffca020e Add context menu item to view images in photo viewer 2017-10-18 13:50:20 +02:00
d663cc3f64 Add username to default video download filename & tweak playback error message 2017-10-17 19:39:35 +02:00
110d41e393 Add function to trigger video playback for plugins 2017-10-17 13:16:19 +02:00
1a8823f592 Fix clipboard html stripping crashing with no text data
Closes #171
2017-10-17 11:22:38 +02:00
6374a852b0 Merge branch 'master' of https://github.com/chylex/TweetDuck 2017-10-16 16:37:24 +02:00
a10c7dd7c3 Fix README heading links 2017-10-16 16:36:30 +02:00
547c7ea417 Update README heading links (still bork on GitHub) 2017-10-16 16:33:41 +02:00
760607995a Update README and post build log message 2017-10-16 16:30:55 +02:00
4704197c09 Add option for larger quote font size to edit-design & update modal layout 2017-10-16 15:38:19 +02:00
093ac1ac40 Make middle-click instant quote work with reply-account plugin 2017-10-16 09:32:52 +02:00
9ed8b0d904 Fix used account in updated middle-click actions 2017-10-15 23:48:56 +02:00
7346ce370d Add middle-click actions (reply popout, RT quote, fav modal) & fix popout in temp columns 2017-10-15 22:59:56 +02:00
adefdadc19 Fix border-radius when quoting a tweet 2017-10-15 22:29:44 +02:00
703bce2d00 Fix PostBuild.ps1 errors not causing failed build & refactor 2017-10-13 13:09:50 +02:00
97928ecd84 Check emoji-ordering.txt for carriage return on build 2017-10-13 12:54:15 +02:00
be9ea7f64a Fuck everything about gitattributes 2017-10-13 12:39:03 +02:00
ec2aaa8789 Add exact emoji name match detection to emoji keyboard 2017-10-13 12:20:54 +02:00
ab14b72526 Force LF in emoji-ordering.txt and other txt/js files 2017-10-13 12:16:39 +02:00
d8e304f3c1 Release 1.10.2 2017-10-12 13:01:47 +02:00
ea53ce361f Bump edit-design plugin version 2017-10-12 10:58:12 +02:00
2fce80b347 Fix custom font size in edit-design & time font in example notification 2017-10-11 20:42:42 +02:00
373c0b1cc3 Fix separator before 'Open dev tools' in example notification context menu 2017-10-11 20:34:50 +02:00
e5e1b7e608 Fix TweetDeck being unable to detect OS name 2017-10-11 20:33:39 +02:00
7e9221c9e0 Merge branch 'master' of https://github.com/chylex/TweetDuck 2017-10-11 20:24:09 +02:00
6b849f854e Rewrite font size & theme handling, add <html> attributes to notifications 2017-10-11 20:22:32 +02:00
831f6bc744 Add support section to readme 2017-10-10 12:25:52 +02:00
d282a7a537 Fix border-radius in media upload previews 2017-10-08 05:43:54 +02:00
fb2f1e3031 Release 1.10.1 2017-10-08 05:27:38 +02:00
00a0da3df3 Fix detail view screenshots & tweak screenshot timings for iframes separately 2017-10-08 01:42:53 +02:00
8c447b1ffb Maybe fix dragged tweet links not being recognized from certain drag sources 2017-09-29 18:04:44 +02:00
a4841175e8 Fix more border-radius, "Ready to Tweet" alignment, and docked reply close icon position 2017-09-29 16:52:39 +02:00
9b139132a1 Disable reply middle-click in temporary columns & fix random reloads from middle-clicks 2017-09-29 14:58:58 +02:00
4a404ecabc Fix weird button styles in inline replies in modal dialogs 2017-09-29 12:48:00 +02:00
aee758b559 Update reply-account docs & fix error with temporary columns and advanced selector 2017-09-29 12:47:13 +02:00
be060d0386 Add new info about emoji usage to the guide 2017-09-28 17:29:34 +02:00
0195378c10 Release 1.10 2017-09-28 17:09:46 +02:00
bc804c6a53 Allow linking to items in the guide 2017-09-28 13:33:54 +02:00
76b15f1971 Fix wrong emoji names & issues with emoji keyboard on while using :emoji_name: 2017-09-28 01:00:03 +02:00
c4d43c9d5b Fix bugs in :emoji_name: update (character count & duplicate keyboard modal) 2017-09-27 23:56:12 +02:00
e8d3e530de Update guide with a screenshot of the main menu 2017-09-27 17:08:51 +02:00
e145adec58 Bump emoji keyboard plugin version 2017-09-27 14:55:08 +02:00
e2dad3e477 Automatically convert :emoji_name: in emoji keyboard plugin
Closes #160
2017-09-27 13:25:05 +02:00
27bdbde171 Optimize images and scripts in the guide 2017-09-25 21:39:04 +02:00
e9ec27169c Improve guide for small width screens and screen readers 2017-09-25 21:12:47 +02:00
2e24cb634c Add favicon and meta tags to the guide 2017-09-25 19:47:00 +02:00
beb9046055 Add a button to send feedback / bug report to Feedback tab in Options 2017-09-25 17:23:16 +02:00
e57301952c Rewrite introduction text and update styles 2017-09-25 15:59:19 +02:00
7411279e48 Update guide URL 2017-09-25 14:22:19 +02:00
16acfa85b5 Bump project and plugin versions 2017-09-25 14:21:51 +02:00
41ef37f3f0 Fix GIFs in detail view not having the pointer cursor 2017-09-24 23:41:39 +02:00
00d8538726 Fix middle-clicking GIFs not opening the tweet externally
Closes #169
2017-09-24 23:35:31 +02:00
6eeb3f9895 Fix error when hovering a quoted tweet with a video 2017-09-24 23:22:20 +02:00
d19dca6ea5 Add a (currently unimplemented) anonymous data collection option 2017-09-24 15:31:44 +02:00
2008ccdaa4 Add an introduction modal & guide 2017-09-23 23:33:06 +02:00
ba2e62de3a Remove a warning in the reply-account plugin's default config 2017-09-23 10:17:55 +02:00
2b62eb254d Fix quoted tweet and media border radius in notifications & screenshots 2017-09-21 13:39:51 +02:00
31f72b7957 Move browser CSS to a file & optimize CSS injection 2017-09-21 13:19:21 +02:00
fdc4616875 Minify CSS files after build 2017-09-21 10:50:13 +02:00
b7de261d25 Move notification CSS to a file, refactor FontSizeLevel & TweetDeckBridge props 2017-09-21 10:34:57 +02:00
ae78a5a026 Fix screenshots of reply threads 2017-09-21 09:50:48 +02:00
fd2cf5d4d7 Make memory cleanup detect recent activity and fix modals 2017-09-21 09:06:55 +02:00
9f0997be1a Refactor some JS 2017-09-21 08:51:41 +02:00
dbade7f854 Fix crash when clicking video overlay after a playback error 2017-09-17 19:06:12 +02:00
3cdc1e190a Fix configuration.default.js files being minified on build 2017-09-14 19:40:41 +02:00
36bede7211 Tweak build instructions (wording and formatting) 2017-09-12 23:06:40 +02:00
46689bb700 Rewrite build and setup instructions 2017-09-12 22:51:59 +02:00
13e1a6543c Release 1.9.2 2017-09-12 20:29:40 +02:00
820ce9e845 Fix uploading files with uppercase extensions 2017-09-11 18:27:39 +02:00
f17806f4e8 Allow dragging tweet links over columns to open them in detail view
Closes #167
2017-09-10 23:03:03 +02:00
3f5ffc9e10 Fix t.co bypass when middle-clicking links in tweets 2017-09-09 19:16:08 +02:00
aeb0842ab4 Move system tray options to a separate tab 2017-09-08 11:36:34 +02:00
38837ae84c Add option to open search columns before the first column 2017-09-07 15:22:14 +02:00
a4eb6935af Fix 'Theme color tweaks' changing color of some scrollbars it shouldn't change 2017-09-06 12:57:43 +02:00
52f1f4c4eb Release 1.9.1 2017-09-05 22:55:32 +02:00
6c1782a038 Fix some twitter links (/signup, /tos, /privacy) having context menu for accounts 2017-09-05 22:43:07 +02:00
8b8f5f5473 Fix login page links opening in the app instead of external browser 2017-09-05 22:32:11 +02:00
61d3ed891a Update t.co bypass to work for media and bio urls 2017-09-05 21:47:22 +02:00
b1abf87320 Revert TweetDeck scrollbar color & fix notification scrollbar with 'Theme color tweaks' on 2017-09-05 14:55:29 +02:00
9aedfc2799 Fix scrollbar in Options not disappearing when switching tabs while animating 2017-09-04 17:52:01 +02:00
ad6240a067 Refactor delegating multiple events at once in code.js 2017-09-04 03:02:30 +02:00
9539eb076a Fix heart icon size and animation 2017-09-03 01:45:59 +02:00
c808e7bd83 Fix calling OpenExternalBrowser from non-UI threads, causing crashes or errors 2017-09-02 21:49:45 +02:00
13ea388f5e Fix upload dialog to include 'All Supported Formats' instead of separating them 2017-09-02 20:51:58 +02:00
c46dc0f1a3 Fix 'Open link in browser' not bypassing t.co 2017-09-02 20:30:54 +02:00
2ae311007d Make https scheme check first because https rocks 2017-09-02 13:54:54 +02:00
9344e02bff Add a privacy warning when opening a t.co link in case the bypass fails 2017-09-02 13:47:43 +02:00
40ad836fc3 Bypass t.co on click & fix right-clicking t.co links in notifications 2017-09-02 13:07:40 +02:00
e8604a261d Replace 'new EventArgs()' with 'EventArgs.Empty' 2017-09-01 14:34:23 +02:00
2a41d21a29 Add unit tests for UserConfig 2017-08-31 21:38:26 +02:00
4c62aa067b Update unit test generated file names 2017-08-31 19:45:56 +02:00
49db3074c6 Rewrite IO handling in unit tests 2017-08-31 19:33:24 +02:00
f5e3b34f30 Tweak border radius on inputs in column settings and custom timelines 2017-08-31 16:03:38 +02:00
f0affa4aec Implement 'Save all images as...' for images in quoted tweets 2017-08-31 15:34:05 +02:00
4f5075ac54 Fix 'Save image as...' usernames in quoted tweets & more refactoring 2017-08-31 15:26:03 +02:00
20f0445b10 Replace FormNotificationBase.ChirpId with CanViewDetail that checks ColumnId too 2017-08-31 14:52:38 +02:00
c77c974455 Rename more parameters and fields in TweetDeckBridge 2017-08-31 14:40:10 +02:00
44397b2d45 Fix parameter name in TweetDeckBridge 2017-08-31 14:07:46 +02:00
943d4d4d72 Release 1.9 2017-08-30 21:40:11 +02:00
6468c03465 Fix 'Restore Defaults' not resetting plugin status and import/reset not closing Plugins form 2017-08-30 21:34:12 +02:00
8141a5a5c5 Fix TrackBar labels being above focus cues 2017-08-30 21:14:58 +02:00
26a1779310 Fix 'Restart with Arguments' including disabled data folder message in shortcut 2017-08-30 21:00:59 +02:00
45d18ffafe Set volume slider SmallChange to 1 and increase width of video player volume slider 2017-08-30 21:00:11 +02:00
5f1c30609c Fix typo in error message in FileSerializer 2017-08-30 20:34:02 +02:00
7266d705d3 Fix video player UI for small videos & increase FormBrowser min size 2017-08-30 19:15:45 +02:00
ee6bb782d6 Tweak the download icon in video player 2017-08-30 16:59:01 +02:00
8ae6e2c886 Bump project and plugin versions 2017-08-30 16:51:53 +02:00
dd3a0d3890 Tweak emoji warning message 2017-08-30 14:57:36 +02:00
8d8e2da57e Make Enter key in emoji search insert the first available emoji 2017-08-30 14:53:43 +02:00
e60d204302 Refocus tweet input after closing emoji keyboard via icon & remove unused code 2017-08-30 14:50:39 +02:00
3d642d8ad2 Tweak emoji search to only select query on click and refocus it after clicking emoji 2017-08-30 14:48:43 +02:00
8db6e8a090 Remove unused custom emoji keyboard code 2017-08-30 14:09:32 +02:00
8153fcde85 Minor refactoring 2017-08-30 13:35:47 +02:00
96469cfca5 Rewrite config reload & fix some options breaking after import or reset 2017-08-30 12:53:10 +02:00
7601645c12 Fix some config options not being committed before opening Manage Options 2017-08-30 12:41:54 +02:00
c28615d548 Add options to reset session and plugin data when restoring defaults 2017-08-29 14:28:33 +02:00
b515add94e Rewrite browser/plugin reload handling when importing a profile 2017-08-29 14:26:42 +02:00
9fd5e9443d Make 'Manage Options' dialog close options after a successful operation 2017-08-29 14:22:20 +02:00
b2ddb1fab2 Disable 'Tray Highlight' option when the icon is disabled 2017-08-29 00:02:41 +02:00
fdac42947c Only activate parent form in video player if the player window itself is active 2017-08-28 23:41:46 +02:00
eeaf6949c5 Delay 'View detail' if the website is reloading 2017-08-28 22:46:06 +02:00
d7ad62d476 Make TweetNotification use persistent column ID 2017-08-28 22:38:11 +02:00
cd87a329fc Add a network error notification if the device goes offline
Closes #145
2017-08-28 22:14:07 +02:00
8c0d306823 Rewrite sound notification hook to be hopefully more reliable 2017-08-28 20:05:49 +02:00
d5c3ea0862 'View detail' errors now ask user if they want to open the tweet in a browser 2017-08-28 19:55:10 +02:00
83c962a7a4 Add support for icons in alert/confirm/prompt JS functions 2017-08-28 19:40:32 +02:00
40ef9a42dd Fix unsealed classes 2017-08-28 18:46:14 +02:00
868af5ac6a Goodbye, sweet rant 2017-08-28 18:19:32 +02:00
625227d0ce Rewrite audio library & add notification volume option for WMP impl 2017-08-28 18:16:13 +02:00
064627961e Fix zoom option label overlapping the slider 2017-08-28 17:16:04 +02:00
de0321cb2d Tweak video player label rendering & add label to volume slider 2017-08-28 15:31:27 +02:00
0d71a33b28 Add close/download/fullscreen buttons to video player 2017-08-28 13:31:19 +02:00
6d779f17b3 Fix video player tooltip going outside Form bounds 2017-08-28 10:37:18 +02:00
05510d7bc1 Add tooltip to seek bar in video player 2017-08-28 10:36:53 +02:00
8e162fe031 Add a custom tooltip to be used for video player controls 2017-08-27 23:13:39 +02:00
7ea7366a43 Change default CultureInfo in video player 2017-08-27 23:12:52 +02:00
445e6fcec0 Make Escape key in video player exit fullscreen or close the player 2017-08-27 21:03:57 +02:00
42f4d97d5d Rewrite key handling in video player 2017-08-27 20:46:10 +02:00
6357708533 Finish implementing 'View detail' context menu option in notifications
Closes #152
2017-08-27 20:11:56 +02:00
59c9801437 Address code analysis and remove unused code 2017-08-27 18:48:54 +02:00
d691bef1fb Add video context menu items and update video service check 2017-08-27 18:23:50 +02:00
442d74d0cb Refactor context menu handling and make adding new types of context easier 2017-08-27 18:18:30 +02:00
588bb9a093 Refactor FormNotificationBase to store TweetNotification instead of copying data 2017-08-27 13:40:49 +02:00
380e580d65 Fix cut off badge icon in notifications in notifications 2017-08-27 13:35:02 +02:00
4e306661f8 Fix cut off badge icon in notifications 2017-08-24 14:45:20 +02:00
9f3f33da93 More power! 2017-08-23 07:28:08 +02:00
69cd96a37c Add 'View detail' context menu item in notifications (currently loaded tweets only) 2017-08-22 11:59:34 +02:00
1293a2a533 Harness the incredible power of return-if statements 2017-08-22 10:10:46 +02:00
d24b7bbcb9 Implement return-if transpiler for JS files 2017-08-22 09:51:27 +02:00
b55b47b689 Refactor postbuild js/html processing script 2017-08-22 09:48:03 +02:00
c4c032b4d5 Bump TweetDuck.Video project version 2017-08-22 08:16:28 +02:00
970cd21964 Move TweetDuck.Video project folder 2017-08-22 08:13:49 +02:00
8ca9d242b2 Fix tab order in restart dialog 2017-08-22 07:30:17 +02:00
6f0518edcc Disable text input in locale drop-down in restart dialog 2017-08-22 07:22:09 +02:00
e2d15dd7e3 Add a shortcut target field to restart dialog 2017-08-22 07:20:40 +02:00
5c310e8647 Disable data folder in restart dialog for portable installs, and fix up tooltips 2017-08-22 06:24:02 +02:00
01dca0bc66 Fix sensitive media preference being ignored in notification previews 2017-08-22 04:59:55 +02:00
8b54fbdb2f Remove GC reload & threshold option migration code 2017-08-22 03:53:59 +02:00
663d0a633e Remove redundant Config.Save() call in TabSettingsGeneral 2017-08-22 03:44:13 +02:00
ccd5edb0e4 Remove legacy config file upgrade code 2017-08-22 03:23:53 +02:00
c6190db918 Rewrite update event args and update dismissal handling 2017-08-22 03:22:44 +02:00
3d4cec3b22 Remove update code that handles unsupported system check 2017-08-22 02:45:51 +02:00
5ed970b5a0 Remove resx file on FormUpdateDownload 2017-08-21 19:18:12 +02:00
c22934336b Remove Program.VersionFull and refactor plugin version checks 2017-08-21 18:47:26 +02:00
a3a52e0a1c Release 1.8.7 2017-08-21 14:32:15 +02:00
68dca6e3d9 Fix spacebar not toggling video pause when the main app was focused 2017-08-21 14:14:38 +02:00
017f883e0b Disable custom emoji input, fix selection handling and support twemoji font if installed 2017-08-21 13:37:21 +02:00
77b5c95f75 Add basic js minification (trim whitespace and remove single line comments) 2017-08-21 09:41:15 +02:00
9d052c8339 Update close button fix to only affect New Tweet drawer 2017-08-21 02:17:48 +02:00
d67623a657 Tweak follow notification padding in the browser 2017-08-21 01:52:19 +02:00
c740b3dd46 What the fuck are you doing Twitter 2017-08-21 01:35:53 +02:00
2ef5f7f96f Fix border radius on media previews in tweet detail 2017-08-16 18:27:44 +02:00
404568d795 Fix pre-build powershell command causing build error 2017-08-16 18:23:38 +02:00
b5a6337a0c Update custom CSS to work better with recent TweetDeck changes 2017-08-14 17:15:18 +02:00
82170c3fbd Fix sensitive media in notification previews and tweak follow notification padding 2017-08-14 16:12:34 +02:00
e6d6275fcc Work on emoji keyboard contenteditable fixes (selection, focus, editor migration) 2017-08-14 15:37:55 +02:00
97c865a127 Make emoji editor only show after adding emoji, fix minor UI issues 2017-08-14 04:22:13 +02:00
1ff21f0ee0 Make emoji keyboard replace tweet input with one that displays emoji
Closes #146
2017-08-14 00:47:08 +02:00
2a3dca4467 Rewrite video player to use duplex pipe for process communication 2017-08-13 17:52:46 +02:00
d4ecfcceec Tweak DuplexPipe to set key instead of data when separator is missing 2017-08-13 17:31:58 +02:00
ec5d503e4d Make DuplexPipe data serialized as key/value pairs 2017-08-13 17:23:23 +02:00
346391ca2d Remove unused 'using' statements for the billionth time 2017-08-13 16:55:08 +02:00
9074cdf340 Add a hover effect to video player seek bar 2017-08-13 16:46:33 +02:00
2fcf3604a8 Move video player form controls to a different namespace 2017-08-13 16:14:46 +02:00
34e5185fa1 Fuck localized .NET exceptions 2017-08-13 15:53:39 +02:00
e09e0e69ca Fuck browser process when building the project 2017-08-13 15:50:43 +02:00
963c98e588 Move interprocess comms to a separate project & implement duplex pipe 2017-08-13 15:20:04 +02:00
92acb823a4 Implement a duplex anonymous pipe in TweetLib.Communication 2017-08-13 15:14:17 +02:00
b967b1288f Move process communication to a separate project 2017-08-13 13:54:34 +02:00
1db271ce90 Fix spacebar triggering fullscreen in video player 2017-08-13 00:23:08 +02:00
58c64025e3 Fix level 2 lists and links in update changelog modal 2017-08-12 23:52:38 +02:00
643a7a87aa Release 1.8.6 2017-08-12 23:39:41 +02:00
5e9ed5d713 Improve video player startup and ensure it's always closed with the main app 2017-08-12 23:36:14 +02:00
78e492c764 Tweak 'stay open' pin position and tooltip 2017-08-12 20:33:52 +02:00
59c2a3642b Bump version of subprocess exe (should have been done a long time ago) 2017-08-12 19:10:38 +02:00
40ca923745 Cleanup FormPlayer code and set sync timer interval to 15 instead of 10 2017-08-12 19:08:17 +02:00
03af6cecaa Replace 'Stay open' checkbox with a pin icon
Closes #154
2017-08-12 17:49:27 +02:00
3992e447f4 Change tooltip border radius to be almost square 2017-08-12 17:47:55 +02:00
14a9edeb73 Fix various focus issues with video player and fix double-clicking control panel 2017-08-12 15:12:54 +02:00
92f1e9f7ec Make video player progress bar seek on mouse down instead of up 2017-08-12 14:31:46 +02:00
19c294c53e Terminate video player when pressing back mouse button over it 2017-08-12 13:43:53 +02:00
fe88ea5c05 Fix ctrl key not opening gifs externally 2017-08-12 03:37:24 +02:00
c9d551213a Remove license screen from installers 2017-08-12 03:15:51 +02:00
1e86a33ceb Hide video player overlay when video process exits gracelessly 2017-08-12 03:12:50 +02:00
551dd229f5 Make back mouse button hide video player and overlay 2017-08-12 03:04:24 +02:00
5ecf3c4147 Fix video player going past the end of a video when paused near the end 2017-08-12 02:26:52 +02:00
91bb2f4df0 Fix video player control panel not disappearing & improve error handling 2017-08-12 01:02:09 +02:00
ae3a0ae83d Fix crash when trying to update with 'Edit CSS' or 'Edit CEF Arguments' open 2017-08-12 00:05:56 +02:00
63ce7523de Fix oversight from previous commit 2017-08-12 00:01:13 +02:00
9e3b92bfc1 Move PluginManager initialization and move Form manipulation to FormManager 2017-08-11 23:57:44 +02:00
bc1767fb84 Change namespace of BrowserProcesses, MemoryUsageTracker, VideoPlayer 2017-08-11 23:50:16 +02:00
f917096cc7 Refactor plugin execution code 2017-08-11 23:32:47 +02:00
308926a2ae Add video player volume sync with user config 2017-08-11 20:58:37 +02:00
76f2b1a454 Make video player volume slider constant width 2017-08-11 20:20:07 +02:00
d899e4b38b Refactor video player control outside designer for dev convenience 2017-08-11 20:14:45 +02:00
e1422e35cc Add seeking + current time and duration to video player 2017-08-11 16:49:23 +02:00
2c00c6bb81 Expand the video player control panel and add progress bar 2017-08-11 16:21:31 +02:00
7e56ba6408 Make custom video player triggerable in tweet detail 2017-08-11 15:52:20 +02:00
8ceb70e67d Fix back button and context menu handling with a video playing 2017-08-11 15:22:45 +02:00
37d5efef1d Add an icon to TweetDuck.Video.exe 2017-08-11 15:06:38 +02:00
924065c26e Change video play icon color and handle playback errors 2017-08-11 13:59:05 +02:00
58cc7ea10d Add WIP video player for MP4s 2017-08-11 13:27:15 +02:00
f93e275ddf Add a volume slider to video player 2017-08-11 13:22:12 +02:00
06d2a5f715 Make video player pause/unpause when pressing space 2017-08-11 13:20:50 +02:00
3a7455eafe Fix video player cursor & pause/unpause on click 2017-08-11 12:33:34 +02:00
8b676fe6ce Implement video player in TweetDeck 2017-08-11 11:56:19 +02:00
54d12686af Tweak video player UI handling 2017-08-11 11:32:20 +02:00
f231256402 Improve player UI handling (cursor, position, setting owner handle) 2017-08-11 10:31:23 +02:00
410ead66f8 Add video player args and adjust location and size to owner window 2017-08-11 09:36:29 +02:00
c833a810af Add TweetDuck.Video project for video playback 2017-08-11 08:22:12 +02:00
50f1336b1d Tweak headings in update changelog renderer 2017-08-10 16:33:45 +02:00
60ed0b8cde Release 1.8.5.1 2017-08-10 16:25:55 +02:00
cc55a81c1b Remove emoji-instructions.txt during an update 2017-08-10 16:25:49 +02:00
f832e04e9e Remove unnecessary resx files and cleanup csproj 2017-08-10 15:09:15 +02:00
fc760b9a0c Fix another case in duplicate DM notifications 2017-08-10 13:46:13 +02:00
9addff0521 Exclude emoji-instructions.txt from build 2017-08-10 13:45:32 +02:00
dcaa3aab19 Work around duplicate DM notifications and rewrite recent tweet check 2017-08-10 00:51:38 +02:00
628785c68c Move _postbuild.bat to an MSBuild target directly in the project file 2017-08-10 00:20:58 +02:00
a5aa396fda Fix image quality setting not working in columns with large previews 2017-08-09 18:48:36 +02:00
f53a9f05e3 Fix image download filename for avatars and add more unit tests 2017-08-07 14:48:20 +02:00
7749b14156 Increment emoji keyboard plugin version 2017-08-06 20:32:13 +02:00
c15f339718 Fix emoji keyboard not disappearing after pressing ctrl+enter to tweet 2017-08-06 20:31:44 +02:00
775f590bfa Release 1.8.5 2017-08-06 15:58:28 +02:00
76408ea56f Increment verison of edit-design and emoji-keyboard plugins 2017-08-06 15:58:23 +02:00
a391d8ee83 Fix image pasting allowing more than 1 image in DMs 2017-08-05 21:52:38 +02:00
48c38f6e1d Include tweet author and quality in image download filename 2017-08-05 21:32:07 +02:00
37c5fba162 Change text color of sound notification file option for invalid paths 2017-08-05 19:50:30 +02:00
23e99b1d44 Update GC memory threshold defaults, also GC reload is enabled by default 2017-08-05 19:42:10 +02:00
8432240a47 Update HW acceleration & GC reload tooltips to note they won't be exported 2017-08-05 19:37:14 +02:00
a4bab743d6 Remove notification warning in GC reload option tooltip 2017-08-05 19:34:20 +02:00
60766789ab Move GC reload options to SystemConfig 2017-08-05 19:27:20 +02:00
ca014f881c Rewrite unknown property handling in FileSerializer 2017-08-05 19:23:42 +02:00
886eabe26c Show notifications that were missed during a browser reload 2017-08-05 18:43:57 +02:00
65b7167b5f Rewrite browser reload to save column notification state in session data 2017-08-05 18:36:17 +02:00
abbdde851e Make quoted tweets and RT account selectors square, fix RT account selector heading 2017-08-05 18:30:42 +02:00
54ac54aba6 Add session data that persists across browser reloads 2017-08-05 18:08:22 +02:00
184340f400 Increase delay for clearing recent notifications to prevent duplicates 2017-08-05 17:06:02 +02:00
93dd6813e8 Fix old icon alignment in 'Add column' dialog 2017-08-05 14:44:49 +02:00
b689b08711 Make follow notification button less visible when not hovered 2017-08-05 12:56:18 +02:00
1479a097d6 Fix alignment of old icons on buttons after TweetDeck update 2017-08-05 02:39:23 +02:00
b2be530f6b Remove legacy config file upgrade code 2017-08-01 19:29:01 +02:00
e4967ea46d Add paragraphs and level 1-2 headings to update notification markdown renderer 2017-08-01 17:33:47 +02:00
3f28f18fb4 Release 1.8.4.1 2017-08-01 17:11:34 +02:00
1b90e0f65e Slightly increase default notification height 2017-08-01 17:05:31 +02:00
756ed649e6 Change default avatar shape to square, rename 'Default' to 'Legacy' 2017-08-01 17:03:29 +02:00
fbc423e2a7 Fix like/retweet notifications having invisible space with notification media previews disabled 2017-08-01 16:59:25 +02:00
f04cdb6a13 Fix PropertyBridge not updating properly 2017-08-01 16:58:46 +02:00
63b58b1cfe Release 1.8.4 2017-08-01 15:07:03 +02:00
77e656d8e4 Tweak JS prompt dialog layout on high DPI 2017-08-01 15:06:09 +02:00
a673957bd0 Tweak JS prompt dialog layout 2017-08-01 14:54:21 +02:00
c99a0c9974 Add Layout & Design plugin button to the TweetDeck settings modal 2017-08-01 13:45:44 +02:00
0fb06d0ff2 Remove reply revert option from edit-design plugin 2017-08-01 12:28:52 +02:00
c51eebfe22 Add new unit tests for TwitterUtils and CombinedFileStream 2017-07-31 22:27:02 +02:00
a51b34b48f Move CommandLineArgsParser code to CommandLineArgs 2017-07-31 22:26:48 +02:00
1b239bada1 Delay screenshots again due to iframes 2017-07-31 21:17:31 +02:00
50ab1a6ac3 Improve login/logout page design 2017-07-31 20:29:07 +02:00
f181f1fadc Refactor PropertyBridge 2017-07-31 19:58:23 +02:00
c686349922 Refactor Program (tweak properties, move locking code) 2017-07-31 18:04:04 +02:00
5f44a1f4ad Fix semicolons in code.js 2017-07-31 14:58:42 +02:00
a968938832 Move square scrollbars from edit-design plugin to code.js 2017-07-31 14:55:31 +02:00
8d67f3dfdc Move code.js notification setup and fix dropdown border radius 2017-07-31 14:42:26 +02:00
973ae8cb5d Move twitter account regex to TwitterUtils 2017-07-31 14:31:32 +02:00
a4747b0d7b Add JS dialog handler to notifications 2017-07-31 14:25:00 +02:00
f07640cc84 Reorganize CEF handlers 2017-07-31 14:24:42 +02:00
c235c55b19 Add option to show media previews in notification 2017-07-31 14:12:24 +02:00
485ef684be Prevent notification keyboard controls from triggering in dev tools 2017-07-31 13:36:44 +02:00
7caca22e57 Remove 'TweetDuck' from JS dialog captions 2017-07-31 01:42:22 +02:00
f1d9e32bf5 Add keyboard controls to notifications
Closes #153
2017-07-31 01:23:57 +02:00
23d5fa3107 Tweak emoji keyboard border radius and character count width 2017-07-30 23:58:35 +02:00
4e7d8aba1c Improve FormMessage to match MessageBox closer and look better on high DPI 2017-07-30 23:50:24 +02:00
98ba871a71 Fix back mouse button ignoring columns inside User modals
Closes #155
2017-07-30 21:38:38 +02:00
3ff23c0264 Remove unnecessary TweetDeck logo CSS rule 2017-07-30 21:29:02 +02:00
e21f89477b Fix ISerializedObject not being removed from unit tests and csproj file 2017-07-30 21:28:26 +02:00
f177f514f5 Fix column type icons jumping when opening column settings 2017-07-30 21:19:03 +02:00
af30f3b348 Square-ify many elements of TweetDeck (buttons, inputs, dialogs, menus, previews) 2017-07-30 21:15:39 +02:00
82df618429 Fix code.js after refactoring CSS insertion 2017-07-30 21:13:45 +02:00
bb3538e270 Refocus tweet textarea after selecting a different account
Closes #156
2017-07-30 20:36:17 +02:00
71925e1126 Refactor parts of code.js (make code shorter, use 'let') 2017-07-30 20:19:59 +02:00
93c1cbd231 Update SystemConfig to use FileSerializer and migrate old files 2017-07-30 19:54:28 +02:00
894b890fe5 Tweak serialization code and remove ISerializedObject 2017-07-30 19:28:03 +02:00
8e9e8f7fad Fix magic number and add a comment 2017-07-30 19:02:30 +02:00
2a0461a76f Add safeguards for accessing TweetDeckBridge.LastHighlightedTweetImages 2017-07-21 12:43:10 +02:00
85f923a6fc Add StringUtils.EmptyArray and use it instead of new string[0] 2017-07-21 12:37:30 +02:00
b35e4d4d01 Add "Save all images as..." context menu option for tweets with multiple images 2017-07-21 12:14:15 +02:00
cb24a859f4 Fix file type description in Save image dialog 2017-07-21 11:16:47 +02:00
b1ef00746f Hide open/copy link context menu items for media previews 2017-07-21 11:07:40 +02:00
aebe82e3a7 Add context menu for image previews that use background-image 2017-07-21 10:46:28 +02:00
7c87856b4d Show waiting cursor while taking a tweet screenshot 2017-07-20 16:29:39 +02:00
d1b1dd539f Add an option to use :orig image links in context menu 2017-07-17 05:39:59 +02:00
55eea88ace Add twitter image link & download methods to TwitterUtils 2017-07-17 05:10:06 +02:00
a70f64e1f6 Move some stuff from BrowserUtils to a new TwitterUtils class 2017-07-17 02:09:20 +02:00
fa0cb120a7 Add a 'Close' button to the modal dialog in the template plugin
Closes #143
2017-07-13 05:57:12 +02:00
e3080d07dc Ensure plugin config exists after first run, fixes profile export crash
Closes #147
2017-07-13 05:21:22 +02:00
34726c533e Release 1.8.3 2017-07-09 20:17:33 +02:00
4a0d72d2cc Fix FormMessage icon position on high DPI 2017-07-09 17:24:01 +02:00
fe3fc5c9f7 Add WindowsUtils.CreateDirectoryForFile and use it 2017-07-09 14:12:27 +02:00
441228e2b0 Stop using BrandName in msg dialogs, update msg titles, fix mistakes from prev commits 2017-07-09 04:21:33 +02:00
7538aee4f2 Replace all MessageBox.Show calls with FormMessage 2017-07-09 03:50:04 +02:00
acf809268e Add many helper methods to FormMessage 2017-07-09 03:45:35 +02:00
4ebc0c10b6 Forgot something! 2017-07-09 02:55:48 +02:00
a453888ca2 Tweak new lines in FormMessage, add ControlType enum for FormMessage buttons 2017-07-09 02:40:37 +02:00
530b44762b Make \n the only new line character in FormMessage 2017-07-09 01:52:44 +02:00
f85587fb0b Bump emoji keyboard plugin version 2017-07-09 00:36:22 +02:00
edb8799b1a Update emoji keyboard w/ emoji 9.0, instructions, and code tweaks 2017-07-09 00:30:03 +02:00
e47aeb37f0 Designer, why 2017-07-08 20:19:22 +02:00
776e9968dc Fix tab order in Advanced tab in Options 2017-07-08 19:25:20 +02:00
1898bf4731 Add a tooltip to browser GC reload checkbox 2017-07-08 19:21:36 +02:00
78df020737 Add a modal with release info to update notifications
Closes #139
2017-07-08 18:00:00 +02:00
b93f9a4b9a Fix compose textarea not being focused after pasting an image in a reply 2017-07-08 03:17:20 +02:00
748b230ef5 Fix missing BrowserProcesses in project file after merge 2017-07-08 02:55:45 +02:00
deb8dde9e1 Merge pull request #141 from chylex/memory
Merge browser process identification & GC reload with memory threshold
2017-07-08 02:50:03 +02:00
dbb2f10754 Update from master 2017-07-08 02:49:21 +02:00
0ded03ab92 Fix more analysis violations (exceptions, native method pointers, form disposal) 2017-07-08 00:21:41 +02:00
2198e84f3b Fix subprocess NativeMethods to use pointers instead of value types 2017-07-07 23:58:45 +02:00
14d44528b0 Fuck CultureInfo some more and fix analysis violations (dispose pattern, lang features) 2017-07-07 23:53:04 +02:00
eb8159ca0f Add a tooltip to text box in the Sounds tab in Options 2017-07-07 23:49:57 +02:00
9811f40a53 Go fuck yourself CurrentCulture and stop messing with string interpolation 2017-07-07 22:56:36 +02:00
8de7e13aa3 Reorganize and refactor UserConfig and PluginConfig 2017-07-07 19:22:33 +02:00
c63e6a1e49 More refactoring (seal classes, fix names and comments) 2017-07-07 16:15:10 +02:00
5a21d2cb10 Add StringUtils with unit tests and use it 2017-07-07 15:52:13 +02:00
424c0e596c Add legacy config detection and replace UserConfig serialization with FileSerializer 2017-07-07 02:56:02 +02:00
d431b63c27 Add SingleTypeConverter and update names in FileSerializer 2017-07-07 01:47:14 +02:00
38c2781cd3 Add an enum test to FileSerializer unit test 2017-07-07 00:53:19 +02:00
796fb348a3 Add classes for serializing objects to/from text files 2017-07-07 00:48:00 +02:00
71b306d5fd Fix unit test project file after refactoring 2017-07-06 21:26:43 +02:00
4c610ea32d Move TweetDeck URL into a constant 2017-07-06 20:58:40 +02:00
4bff006743 Refactor (move files into different namespaces) 2017-07-06 20:58:06 +02:00
1645079bc0 Allow plugins to modify screenshot css and include a 'td-screenshot' body class 2017-07-06 03:47:59 +02:00
9afb58e4a7 Remove unused 'using' statement 2017-07-06 03:30:15 +02:00
2820fc8acf Fix some modals not closing when pressing the back button 2017-07-04 22:01:33 +02:00
4d77a498f6 Add a WIP memory tracker that runs GC reload, and fix config 2017-07-04 22:00:03 +02:00
d77de3bb12 Remove debug code 2017-06-30 23:53:36 +02:00
29e7ad6ce6 Add a way to track browser process IDs 2017-06-30 23:46:52 +02:00
1712b5120e Merge remote-tracking branch 'refs/remotes/origin/master' into memory 2017-06-30 20:47:22 +02:00
06c0153cf5 Fix tray restoration from another process if the original process is hung 2017-06-30 20:44:39 +02:00
44f7ecda6d Merge remote-tracking branch 'refs/remotes/origin/master' into memory 2017-06-30 20:17:21 +02:00
fb94bf1b80 Add WindowsUtils.IsChildProcess to check process parent 2017-06-30 20:14:49 +02:00
4818652582 Add current PID into WindowsUtils.CurrentProcessID and use it 2017-06-30 17:07:37 +02:00
c69b9784fc Add option to enable GC reload with a custom memory threshold (currently unused) 2017-06-30 16:47:31 +02:00
0ac244a3ea Merge remote-tracking branch 'refs/remotes/origin/master' into memory 2017-06-30 00:00:33 +02:00
19a445fdab Add a NumericUpDown control with a text suffix 2017-06-30 00:00:20 +02:00
c90a18a2c0 Merge remote-tracking branch 'refs/remotes/origin/master' into memory 2017-06-29 23:47:00 +02:00
502310c413 Prevent TrackBar from stealing focus when scrolling 2017-06-29 23:34:00 +02:00
6f9424d4ec Force GC cleanup when clicking 'Reload browser' 2017-06-29 18:21:09 +02:00
bb379fe667 Expose gc() in JS 2017-06-29 04:01:50 +02:00
0fd86bf214 Move CEF argument setup to BrowserUtils 2017-06-29 03:52:55 +02:00
29b75d4391 Release 1.8.2 2017-06-29 02:25:07 +02:00
214 changed files with 10438 additions and 4363 deletions

5
.gitattributes vendored Normal file
View File

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

View File

@@ -1,17 +1,17 @@
using System; using System;
using TweetDuck.Core.Utils; using TweetDuck.Data;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration{
static class Arguments{ static class Arguments{
// public args // public args
public const string ArgDataFolder = "-datafolder"; public const string ArgDataFolder = "-datafolder";
public const string ArgLocale = "-locale";
public const string ArgLogging = "-log"; public const string ArgLogging = "-log";
public const string ArgDebugUpdates = "-debugupdates"; public const string ArgDebugUpdates = "-debugupdates";
// internal args // internal args
public const string ArgRestart = "-restart"; public const string ArgRestart = "-restart";
public const string ArgImportCookies = "-importcookies"; public const string ArgImportCookies = "-importcookies";
public const string ArgDeleteCookies = "-deletecookies";
public const string ArgUpdated = "-updated"; public const string ArgUpdated = "-updated";
// class data and methods // class data and methods
@@ -29,6 +29,7 @@ namespace TweetDuck.Configuration{
CommandLineArgs args = Current.Clone(); CommandLineArgs args = Current.Clone();
args.RemoveFlag(ArgRestart); args.RemoveFlag(ArgRestart);
args.RemoveFlag(ArgImportCookies); args.RemoveFlag(ArgImportCookies);
args.RemoveFlag(ArgDeleteCookies);
args.RemoveFlag(ArgUpdated); args.RemoveFlag(ArgUpdated);
return args; return args;
} }

View File

@@ -1,29 +1,27 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Threading;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration{
sealed class LockManager{ sealed class LockManager{
private const int RetryDelay = 250;
public enum Result{ public enum Result{
Success, HasProcess, Fail Success, HasProcess, Fail
} }
public Process LockingProcess { get; private set; }
private readonly string file; private readonly string file;
private FileStream lockStream; private FileStream lockStream;
private Process lockingProcess;
public LockManager(string file){ public LockManager(string file){
this.file = file; this.file = file;
} }
private void CreateLockFileStream(){ // Lock file
lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
WriteIntToStream(lockStream, GetCurrentProcessId());
lockStream.Flush(true);
}
private bool ReleaseLockFileStream(){ private bool ReleaseLockFileStream(){
if (lockStream != null){ if (lockStream != null){
@@ -37,8 +35,10 @@ namespace TweetDuck.Configuration{
} }
private Result TryCreateLockFile(){ private Result TryCreateLockFile(){
if (lockStream != null){ void CreateLockFileStream(){
throw new InvalidOperationException("Lock file already exists."); lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
lockStream.Write(BitConverter.GetBytes(WindowsUtils.CurrentProcessID), 0, sizeof(int));
lockStream.Flush(true);
} }
try{ try{
@@ -60,6 +60,8 @@ namespace TweetDuck.Configuration{
} }
} }
// Lock management
public Result Lock(){ public Result Lock(){
if (lockStream != null){ if (lockStream != null){
return Result.Success; return Result.Success;
@@ -72,7 +74,9 @@ namespace TweetDuck.Configuration{
int pid; int pid;
using(FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){ using(FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){
pid = ReadIntFromStream(fileStream); byte[] bytes = new byte[sizeof(int)];
fileStream.Read(bytes, 0, bytes.Length);
pid = BitConverter.ToInt32(bytes, 0);
} }
try{ try{
@@ -80,7 +84,7 @@ namespace TweetDuck.Configuration{
using(Process currentProcess = Process.GetCurrentProcess()){ using(Process currentProcess = Process.GetCurrentProcess()){
if (foundProcess.MainModule.FileVersionInfo.InternalName == currentProcess.MainModule.FileVersionInfo.InternalName){ if (foundProcess.MainModule.FileVersionInfo.InternalName == currentProcess.MainModule.FileVersionInfo.InternalName){
LockingProcess = foundProcess; lockingProcess = foundProcess;
} }
else{ else{
foundProcess.Close(); foundProcess.Close();
@@ -91,7 +95,7 @@ namespace TweetDuck.Configuration{
// Process.MainModule can throw exceptions in some cases // Process.MainModule can throw exceptions in some cases
} }
return LockingProcess == null ? Result.Fail : Result.HasProcess; return lockingProcess == null ? Result.Fail : Result.HasProcess;
}catch{ }catch{
return Result.Fail; return Result.Fail;
} }
@@ -100,77 +104,80 @@ namespace TweetDuck.Configuration{
return initialResult; return initialResult;
} }
public bool Unlock(){ public Result LockWait(int timeout){
bool result = true; for(int elapsed = 0; elapsed < timeout; elapsed += RetryDelay){
Result result = Lock();
if (result == Result.HasProcess){
Thread.Sleep(RetryDelay);
}
else{
return result;
}
}
return Lock();
}
public bool Unlock(){
if (ReleaseLockFileStream()){ if (ReleaseLockFileStream()){
try{ try{
File.Delete(file); File.Delete(file);
}catch(Exception e){ }catch(Exception e){
Program.Reporter.Log(e.ToString()); Program.Reporter.Log(e.ToString());
result = false; return false;
} }
} }
return result; return true;
}
// Locking process
public bool RestoreLockingProcess(int failTimeout){
if (lockingProcess != null && lockingProcess.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
NativeMethods.BroadcastMessage(Program.WindowRestoreMessage, (uint)lockingProcess.Id, 0);
if (WindowsUtils.TrySleepUntil(() => CheckLockingProcessExited() || (lockingProcess.MainWindowHandle != IntPtr.Zero && lockingProcess.Responding), failTimeout, RetryDelay)){
return true;
}
}
return false;
} }
public bool CloseLockingProcess(int closeTimeout, int killTimeout){ public bool CloseLockingProcess(int closeTimeout, int killTimeout){
if (LockingProcess != null){ if (lockingProcess != null){
try{ try{
if (LockingProcess.CloseMainWindow()){ if (lockingProcess.CloseMainWindow()){
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, closeTimeout, 250); WindowsUtils.TrySleepUntil(CheckLockingProcessExited, closeTimeout, RetryDelay);
} }
if (!LockingProcess.HasExited){ if (!lockingProcess.HasExited){
LockingProcess.Kill(); lockingProcess.Kill();
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, killTimeout, 250); WindowsUtils.TrySleepUntil(CheckLockingProcessExited, killTimeout, RetryDelay);
} }
if (LockingProcess.HasExited){ if (lockingProcess.HasExited){
LockingProcess.Dispose(); lockingProcess.Dispose();
LockingProcess = null; lockingProcess = null;
return true; return true;
} }
}catch(Exception ex){ }catch(Exception ex) when (ex is InvalidOperationException || ex is Win32Exception){
if (ex is InvalidOperationException || ex is Win32Exception){ if (lockingProcess != null){
if (LockingProcess != null){ bool hasExited = CheckLockingProcessExited();
LockingProcess.Refresh(); lockingProcess.Dispose();
bool hasExited = LockingProcess.HasExited;
LockingProcess.Dispose();
return hasExited; return hasExited;
} }
} }
else throw;
}
} }
return false; return false;
} }
private bool CheckLockingProcessExited(){ private bool CheckLockingProcessExited(){
LockingProcess.Refresh(); lockingProcess.Refresh();
return LockingProcess.HasExited; 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,49 +1,43 @@
using System; using System;
using System.IO; using System.IO;
using TweetDuck.Data.Serialization;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration{
sealed class SystemConfig{ sealed class SystemConfig{
private static readonly FileSerializer<SystemConfig> Serializer = new FileSerializer<SystemConfig>{
HandleUnknownProperties = FileSerializer<SystemConfig>.IgnoreProperties("EnableBrowserGCReload", "BrowserMemoryThreshold")
};
public static readonly bool IsHardwareAccelerationSupported = File.Exists(Path.Combine(Program.ProgramPath, "libEGL.dll")) && public static readonly bool IsHardwareAccelerationSupported = File.Exists(Path.Combine(Program.ProgramPath, "libEGL.dll")) &&
File.Exists(Path.Combine(Program.ProgramPath, "libGLESv2.dll")); File.Exists(Path.Combine(Program.ProgramPath, "libGLESv2.dll"));
// CONFIGURATION DATA
private bool _hardwareAcceleration = true;
public bool ClearCacheAutomatically { get; set; } = true;
public int ClearCacheThreshold { get; set; } = 250;
// SPECIAL PROPERTIES
public bool HardwareAcceleration{ public bool HardwareAcceleration{
get => hardwareAcceleration && IsHardwareAccelerationSupported; get => _hardwareAcceleration && IsHardwareAccelerationSupported;
set => hardwareAcceleration = value; set => _hardwareAcceleration = value;
} }
// END OF CONFIG
private readonly string file; private readonly string file;
private bool hardwareAcceleration;
private SystemConfig(string file){ private SystemConfig(string file){
this.file = file; this.file = file;
HardwareAcceleration = true;
} }
private void WriteToStream(Stream stream){ public void Save(){
stream.WriteByte((byte)(HardwareAcceleration ? 1 : 0));
}
private void ReadFromStream(Stream stream){
HardwareAcceleration = stream.ReadByte() > 0;
}
public bool Save(){
try{ try{
string directory = Path.GetDirectoryName(file); Serializer.Write(file, this);
if (directory == null)return false;
Directory.CreateDirectory(directory);
using(Stream stream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None)){
WriteToStream(stream);
}
return true;
}catch(Exception e){ }catch(Exception e){
Program.Reporter.HandleException("Configuration Error", "Could not save the system configuration file.", true, e); Program.Reporter.HandleException("Configuration Error", "Could not save the system configuration file.", true, e);
return false;
} }
} }
@@ -51,11 +45,7 @@ namespace TweetDuck.Configuration{
SystemConfig config = new SystemConfig(file); SystemConfig config = new SystemConfig(file);
try{ try{
using(Stream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)){ Serializer.ReadIfExists(file, config);
config.ReadFromStream(stream);
}
}catch(FileNotFoundException){
}catch(DirectoryNotFoundException){
}catch(Exception e){ }catch(Exception e){
Program.Reporter.HandleException("Configuration Error", "Could not open the system configuration file. If you continue, you will lose system specific configuration such as Hardware Acceleration.", true, e); Program.Reporter.HandleException("Configuration Error", "Could not open the system configuration file. If you continue, you will lose system specific configuration such as Hardware Acceleration.", true, e);
} }

View File

@@ -1,249 +1,196 @@
using System; using System;
using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using TweetDuck.Core;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification; using TweetDuck.Core.Notification;
using TweetDuck.Core.Other;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Data.Serialization;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration{
[Serializable]
sealed class UserConfig{ sealed class UserConfig{
private static readonly IFormatter Formatter = new BinaryFormatter{ Binder = new LegacyBinder() }; private static readonly FileSerializer<UserConfig> Serializer = new FileSerializer<UserConfig>{
HandleUnknownProperties = FileSerializer<UserConfig>.IgnoreProperties("AppLocale")
};
private class LegacyBinder : SerializationBinder{ static UserConfig(){
public override Type BindToType(string assemblyName, string typeName){ Serializer.RegisterTypeConverter(typeof(WindowState), WindowState.Converter);
return Type.GetType(string.Format("{0}, {1}", typeName.Replace("TweetDck", "TweetDuck"), assemblyName.Replace("TweetDck", "TweetDuck")));
Serializer.RegisterTypeConverter(typeof(Point), new SingleTypeConverter<Point>{
ConvertToString = value => $"{value.X} {value.Y}",
ConvertToObject = value => {
int[] elements = StringUtils.ParseInts(value, ' ');
return new Point(elements[0], elements[1]);
} }
});
Serializer.RegisterTypeConverter(typeof(Size), new SingleTypeConverter<Size>{
ConvertToString = value => $"{value.Width} {value.Height}",
ConvertToObject = value => {
int[] elements = StringUtils.ParseInts(value, ' ');
return new Size(elements[0], elements[1]);
}
});
} }
private const int CurrentFileVersion = 11; // CONFIGURATION DATA
// START OF CONFIGURATION public bool FirstRun { get; set; } = true;
public bool AllowDataCollection { get; set; } = false;
public bool ShowDataCollectionNotification { get; set; } = true;
public WindowState BrowserWindow { get; set; } public WindowState BrowserWindow { get; set; } = new WindowState();
public WindowState PluginsWindow { get; set; } public WindowState PluginsWindow { get; set; } = new WindowState();
public bool DisplayNotificationColumn { get; set; } public bool ExpandLinksOnHover { get; set; } = true;
public bool DisplayNotificationTimer { get; set; } public bool SwitchAccountSelectors { get; set; } = true;
public bool NotificationTimerCountDown { get; set; } public bool OpenSearchInFirstColumn { get; set; } = true;
public bool NotificationSkipOnLinkClick { get; set; } public bool KeepLikeFollowDialogsOpen { get; set; } = true;
public bool NotificationNonIntrusiveMode { get; set; } public bool BestImageQuality { get; set; } = true;
public bool EnableAnimatedImages { get; set; } = true;
public int VideoPlayerVolume { get; set; } = 50;
private int _zoomLevel = 100;
private bool _muteNotifications;
public int NotificationIdlePauseSeconds { get; set; } public bool EnableSpellCheck { get; set; } = false;
public int NotificationDurationValue { get; set; } public string SpellCheckLanguage { get; set; } = "en-US";
public int NotificationScrollSpeed { get; set; } public string TranslationTarget { get; set; } = "en";
public TweetNotification.Position NotificationPosition { get; set; } private TrayIcon.Behavior _trayBehavior = TrayIcon.Behavior.Disabled;
public Point CustomNotificationPosition { get; set; } public bool EnableTrayHighlight { get; set; } = true;
public int NotificationEdgeDistance { get; set; }
public int NotificationDisplay { get; set; }
public TweetNotification.Size NotificationSize { get; set; } public bool EnableUpdateCheck { get; set; } = true;
public Size CustomNotificationSize { get; set; } public string DismissedUpdate { get; set; } = null;
public bool EnableSpellCheck { get; set; } public bool DisplayNotificationColumn { get; set; } = false;
public bool ExpandLinksOnHover { get; set; } public bool NotificationMediaPreviews { get; set; } = true;
public bool SwitchAccountSelectors { get; set; } public bool NotificationSkipOnLinkClick { get; set; } = false;
public bool EnableTrayHighlight { get; set; } public bool NotificationNonIntrusiveMode { get; set; } = true;
public int NotificationIdlePauseSeconds { get; set; } = 0;
public bool EnableUpdateCheck { get; set; } public bool DisplayNotificationTimer { get; set; } = true;
public string DismissedUpdate { get; set; } public bool NotificationTimerCountDown { get; set; } = false;
public int NotificationDurationValue { get; set; } = 25;
public string CustomCefArgs { get; set; } public TweetNotification.Position NotificationPosition { get; set; } = TweetNotification.Position.TopRight;
public string CustomBrowserCSS { get; set; } public Point CustomNotificationPosition { get; set; } = ControlExtensions.InvisibleLocation;
public string CustomNotificationCSS { get; set; } public int NotificationDisplay { get; set; } = 0;
public int NotificationEdgeDistance { get; set; } = 8;
public TweetNotification.Size NotificationSize { get; set; } = TweetNotification.Size.Auto;
public Size CustomNotificationSize { get; set; } = Size.Empty;
public int NotificationScrollSpeed { get; set; } = 100;
private string _notificationSoundPath;
private int _notificationSoundVolume = 100;
public string CustomCefArgs { get; set; } = null;
public string CustomBrowserCSS { get; set; } = null;
public string CustomNotificationCSS { get; set; } = null;
// SPECIAL PROPERTIES
public bool IsCustomNotificationPositionSet => CustomNotificationPosition != ControlExtensions.InvisibleLocation; public bool IsCustomNotificationPositionSet => CustomNotificationPosition != ControlExtensions.InvisibleLocation;
public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty; public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty;
public bool IsCustomSoundNotificationSet => NotificationSoundPath != string.Empty;
public TwitterUtils.ImageQuality TwitterImageQuality => BestImageQuality ? TwitterUtils.ImageQuality.Orig : TwitterUtils.ImageQuality.Default;
public string NotificationSoundPath{ public string NotificationSoundPath{
get => string.IsNullOrEmpty(notificationSoundPath) ? string.Empty : notificationSoundPath; get => _notificationSoundPath ?? string.Empty;
set => notificationSoundPath = value; set => UpdatePropertyWithEvent(ref _notificationSoundPath, value, SoundNotificationChanged);
}
public int NotificationSoundVolume{
get => _notificationSoundVolume;
set => UpdatePropertyWithEvent(ref _notificationSoundVolume, value, SoundNotificationChanged);
} }
public bool MuteNotifications{ public bool MuteNotifications{
get => muteNotifications; get => _muteNotifications;
set => UpdatePropertyWithEvent(ref _muteNotifications, value, MuteToggled);
set{
if (muteNotifications != value){
muteNotifications = value;
MuteToggled?.Invoke(this, new EventArgs());
}
}
} }
public int ZoomLevel{ public int ZoomLevel{
get => zoomLevel; get => _zoomLevel;
set => UpdatePropertyWithEvent(ref _zoomLevel, value, ZoomLevelChanged);
set{
if (zoomLevel != value){
zoomLevel = value;
ZoomLevelChanged?.Invoke(this, new EventArgs());
} }
}
}
public double ZoomMultiplier => zoomLevel/100.0;
public TrayIcon.Behavior TrayBehavior{ public TrayIcon.Behavior TrayBehavior{
get => trayBehavior; get => _trayBehavior;
set => UpdatePropertyWithEvent(ref _trayBehavior, value, TrayBehaviorChanged);
set{
if (trayBehavior != value){
trayBehavior = value;
TrayBehaviorChanged?.Invoke(this, new EventArgs());
}
}
} }
// END OF CONFIGURATION // EVENTS
[field:NonSerialized]
public event EventHandler MuteToggled; public event EventHandler MuteToggled;
[field:NonSerialized]
public event EventHandler ZoomLevelChanged; public event EventHandler ZoomLevelChanged;
[field:NonSerialized]
public event EventHandler TrayBehaviorChanged; public event EventHandler TrayBehaviorChanged;
public event EventHandler SoundNotificationChanged;
[NonSerialized] // END OF CONFIG
private string file;
private int fileVersion; private readonly string file;
private bool muteNotifications;
private int zoomLevel;
private string notificationSoundPath;
private TrayIcon.Behavior trayBehavior;
private UserConfig(string file){ private UserConfig(string file){
this.file = file; this.file = file;
BrowserWindow = new WindowState();
ZoomLevel = 100;
DisplayNotificationTimer = true;
NotificationNonIntrusiveMode = true;
NotificationPosition = TweetNotification.Position.TopRight;
CustomNotificationPosition = ControlExtensions.InvisibleLocation;
NotificationSize = TweetNotification.Size.Auto;
NotificationEdgeDistance = 8;
NotificationDurationValue = 25;
NotificationScrollSpeed = 100;
EnableUpdateCheck = true;
ExpandLinksOnHover = true;
SwitchAccountSelectors = true;
EnableTrayHighlight = true;
PluginsWindow = new WindowState();
} }
private void UpgradeFile(){ private void UpdatePropertyWithEvent<T>(ref T field, T value, EventHandler eventHandler){
if (fileVersion == CurrentFileVersion){ if (!EqualityComparer<T>.Default.Equals(field, value)){
return; field = value;
eventHandler?.Invoke(this, EventArgs.Empty);
}
} }
// if outdated, cycle through all versions public void Save(){
if (fileVersion == 0){
DisplayNotificationTimer = true;
EnableUpdateCheck = true;
++fileVersion;
}
if (fileVersion == 1){
ExpandLinksOnHover = true;
++fileVersion;
}
if (fileVersion == 2){
BrowserWindow = new WindowState();
PluginsWindow = new WindowState();
++fileVersion;
}
if (fileVersion == 3){
EnableTrayHighlight = true;
NotificationDurationValue = 25;
++fileVersion;
}
if (fileVersion == 4){
++fileVersion;
}
if (fileVersion == 5){
++fileVersion;
}
if (fileVersion == 6){
NotificationNonIntrusiveMode = true;
++fileVersion;
}
if (fileVersion == 7){
ZoomLevel = 100;
++fileVersion;
}
if (fileVersion == 8){
SwitchAccountSelectors = true;
++fileVersion;
}
if (fileVersion == 9){
NotificationScrollSpeed = 100;
++fileVersion;
}
if (fileVersion == 10){
NotificationSize = TweetNotification.Size.Auto;
++fileVersion;
}
// update the version
fileVersion = CurrentFileVersion;
Save();
}
public bool Save(){
try{ try{
string directory = Path.GetDirectoryName(file);
if (directory == null)return false;
Directory.CreateDirectory(directory);
if (File.Exists(file)){ if (File.Exists(file)){
string backupFile = GetBackupFile(file); string backupFile = GetBackupFile(file);
File.Delete(backupFile); File.Delete(backupFile);
File.Move(file, backupFile); File.Move(file, backupFile);
} }
using(Stream stream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None)){ Serializer.Write(file, this);
Formatter.Serialize(stream, this);
}
return true;
}catch(Exception e){ }catch(Exception e){
Program.Reporter.HandleException("Configuration Error", "Could not save the configuration file.", true, e); Program.Reporter.HandleException("Configuration Error", "Could not save the configuration file.", true, e);
return false; }
}
public void Reload(){
try{
LoadInternal(false);
}catch(FileNotFoundException){
try{
Serializer.Write(file, new UserConfig(file));
LoadInternal(false);
}catch(Exception e){
Program.Reporter.HandleException("Configuration Error", "Could not regenerate configuration file.", true, e);
}
}catch(Exception e){
Program.Reporter.HandleException("Configuration Error", "Could not reload configuration file.", true, e);
}
}
private void LoadInternal(bool backup){
Serializer.Read(backup ? GetBackupFile(file) : file, this);
if (NotificationScrollSpeed == 10){ // incorrect initial value
NotificationScrollSpeed = 100;
Save();
} }
} }
public static UserConfig Load(string file){ public static UserConfig Load(string file){
UserConfig config = null;
Exception firstException = null; Exception firstException = null;
for(int attempt = 0; attempt < 2; attempt++){ for(int attempt = 0; attempt < 2; attempt++){
try{ try{
using(Stream stream = new FileStream(attempt == 0 ? file : GetBackupFile(file), FileMode.Open, FileAccess.Read, FileShare.Read)){ UserConfig config = new UserConfig(file);
if ((config = Formatter.Deserialize(stream) as UserConfig) != null){ config.LoadInternal(attempt > 0);
config.file = file; return config;
}
}
config?.UpgradeFile();
break;
}catch(FileNotFoundException){ }catch(FileNotFoundException){
}catch(DirectoryNotFoundException){ }catch(DirectoryNotFoundException){
break; break;
@@ -252,17 +199,22 @@ namespace TweetDuck.Configuration{
firstException = e; firstException = e;
Program.Reporter.Log(e.ToString()); Program.Reporter.Log(e.ToString());
} }
else if (firstException is FormatException){
Program.Reporter.HandleException("Configuration Error", "The configuration file is outdated or corrupted. If you continue, your program options will be reset.", true, e);
return new UserConfig(file);
}
else if (firstException != null){ else if (firstException != null){
Program.Reporter.HandleException("Configuration Error", "Could not open the backup configuration file. If you continue, your program options will be reset.", true, e); Program.Reporter.HandleException("Configuration Error", "Could not open the backup configuration file. If you continue, your program options will be reset.", true, e);
return new UserConfig(file);
} }
} }
} }
if (firstException != null && config == null){ if (firstException != null){
Program.Reporter.HandleException("Configuration Error", "Could not open the configuration file.", true, firstException); Program.Reporter.HandleException("Configuration Error", "Could not open the configuration file.", true, firstException);
} }
return config ?? new UserConfig(file); return new UserConfig(file);
} }
public static string GetBackupFile(string file){ public static string GetBackupFile(string file){

View File

@@ -1,45 +1,33 @@
using System; using System.Text;
using System.Text;
namespace TweetDuck.Core.Bridge{ namespace TweetDuck.Core.Bridge{
static class PropertyBridge{ static class PropertyBridge{
[Flags] public enum Environment{
public enum Properties{ Browser, Notification
ExpandLinksOnHover = 1,
MuteNotifications = 2,
HasCustomNotificationSound = 4,
SkipOnLinkClick = 8,
SwitchAccountSelectors = 16,
AllBrowser = ExpandLinksOnHover | SwitchAccountSelectors | MuteNotifications | HasCustomNotificationSound,
AllNotification = ExpandLinksOnHover | SkipOnLinkClick
} }
public static string GenerateScript(Properties properties){ public static string GenerateScript(Environment environment){
StringBuilder build = new StringBuilder(); string Bool(bool value) => value ? "true;" : "false;";
build.Append("(function(c){"); string Str(string value) => '"'+value+"\";";
if (properties.HasFlag(Properties.ExpandLinksOnHover)){ StringBuilder build = new StringBuilder().Append("(function(x){");
build.Append("c.expandLinksOnHover=").Append(Program.UserConfig.ExpandLinksOnHover ? "true;" : "false;");
build.Append("x.expandLinksOnHover=").Append(Bool(Program.UserConfig.ExpandLinksOnHover));
if (environment == Environment.Browser){
build.Append("x.switchAccountSelectors=").Append(Bool(Program.UserConfig.SwitchAccountSelectors));
build.Append("x.openSearchInFirstColumn=").Append(Bool(Program.UserConfig.OpenSearchInFirstColumn));
build.Append("x.keepLikeFollowDialogsOpen=").Append(Bool(Program.UserConfig.KeepLikeFollowDialogsOpen));
build.Append("x.muteNotifications=").Append(Bool(Program.UserConfig.MuteNotifications));
build.Append("x.notificationMediaPreviews=").Append(Bool(Program.UserConfig.NotificationMediaPreviews));
build.Append("x.translationTarget=").Append(Str(Program.UserConfig.TranslationTarget));
} }
if (properties.HasFlag(Properties.SwitchAccountSelectors)){ if (environment == Environment.Notification){
build.Append("c.switchAccountSelectors=").Append(Program.UserConfig.SwitchAccountSelectors ? "true;" : "false;"); build.Append("x.skipOnLinkClick=").Append(Bool(Program.UserConfig.NotificationSkipOnLinkClick));
} }
if (properties.HasFlag(Properties.MuteNotifications)){ return build.Append("})(window.$TDX=window.$TDX||{})").ToString();
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

@@ -1,94 +1,153 @@
using System.Windows.Forms; using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using CefSharp;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling;
using TweetDuck.Core.Notification; using TweetDuck.Core.Notification;
using TweetDuck.Core.Other;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Resources;
namespace TweetDuck.Core.Bridge{ namespace TweetDuck.Core.Bridge{
sealed class TweetDeckBridge{ class TweetDeckBridge{
public static string LastRightClickedLink = string.Empty; public static string FontSize { get; private set; }
public static string LastHighlightedTweet = string.Empty; public static string NotificationHeadLayout { get; private set; }
public static string LastHighlightedQuotedTweet = string.Empty;
public static string LastHighlightedTweetUrl = string.Empty;
public static string LastHighlightedQuoteUrl = string.Empty;
private static string LastHighlightedTweetAuthors = string.Empty;
private static string LastHighlightedTweetImages = string.Empty;
public static string[] LastHighlightedTweetAuthorsArray => LastHighlightedTweetAuthors.Split(';');
public static string[] LastHighlightedTweetImagesArray => LastHighlightedTweetImages.Split(';');
private static readonly Dictionary<string, string> SessionData = new Dictionary<string, string>(2);
public static void ResetStaticProperties(){ public static void ResetStaticProperties(){
LastRightClickedLink = LastHighlightedTweet = LastHighlightedQuotedTweet = string.Empty; FontSize = NotificationHeadLayout = null;
LastHighlightedTweetUrl = LastHighlightedQuoteUrl = LastHighlightedTweetAuthors = LastHighlightedTweetImages = string.Empty;
}
public static void RestoreSessionData(IFrame frame){
if (SessionData.Count > 0){
StringBuilder build = new StringBuilder().Append("window.TD_SESSION={");
foreach(KeyValuePair<string, string> kvp in SessionData){
build.Append(kvp.Key).Append(":'").Append(kvp.Value.Replace("'", "\\'")).Append("',");
}
ScriptLoader.ExecuteScript(frame, build.Append("}").ToString(), "gen:session");
SessionData.Clear();
}
} }
private readonly FormBrowser form; private readonly FormBrowser form;
private readonly FormNotificationMain notification; private readonly FormNotificationMain notification;
public TweetDeckBridge(FormBrowser form, FormNotificationMain notification){ protected TweetDeckBridge(FormBrowser form, FormNotificationMain notification){
this.form = form; this.form = form;
this.notification = notification; this.notification = notification;
} }
public void LoadFontSizeClass(string fsClass){ // Browser only
form.InvokeAsyncSafe(() => {
TweetNotification.SetFontSizeClass(fsClass);
});
}
public void LoadNotificationHeadContents(string headContents){ public sealed class Browser : TweetDeckBridge{
form.InvokeAsyncSafe(() => { public Browser(FormBrowser form, FormNotificationMain notification) : base(form, notification){}
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(){ public void OpenContextMenu(){
form.InvokeAsyncSafe(form.OpenContextMenu); form.InvokeAsyncSafe(form.OpenContextMenu);
} }
public void OnTweetPopup(string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl){ public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){
notification.InvokeAsyncSafe(() => {
form.OnTweetNotification();
notification.ShowNotification(new TweetNotification(columnName, tweetHtml, tweetCharacters, tweetUrl, quoteUrl));
});
}
public void OnTweetSound(){
form.InvokeAsyncSafe(() => { form.InvokeAsyncSafe(() => {
form.OnTweetNotification(); form.OnIntroductionClosed(showGuide, allowDataCollection);
form.PlayNotificationSound();
}); });
} }
public void DisplayTooltip(string text, bool showInNotification){ public void LoadNotificationLayout(string fontSize, string headLayout){
if (showInNotification){ form.InvokeAsyncSafe(() => {
notification.InvokeAsyncSafe(() => notification.DisplayTooltip(text)); FontSize = fontSize;
NotificationHeadLayout = headLayout;
});
} }
else{
public void SetLastHighlightedTweet(string tweetUrl, string quoteUrl, string authors, string imageList){
form.InvokeAsyncSafe(() => {
LastHighlightedTweetUrl = tweetUrl;
LastHighlightedQuoteUrl = quoteUrl;
LastHighlightedTweetAuthors = authors;
LastHighlightedTweetImages = imageList;
});
}
public void DisplayTooltip(string text){
form.InvokeAsyncSafe(() => form.DisplayTooltip(text)); form.InvokeAsyncSafe(() => form.DisplayTooltip(text));
} }
public void SetSessionData(string key, string value){
form.InvokeSafe(() => { // do not use InvokeAsyncSafe, return only after invocation
SessionData.Add(key, value);
});
}
}
// Notification only
public sealed class Notification : TweetDeckBridge{
public Notification(FormBrowser form, FormNotificationMain notification) : base(form, notification){}
public void DisplayTooltip(string text){
notification.InvokeAsyncSafe(() => notification.DisplayTooltip(text));
} }
public void LoadNextNotification(){ public void LoadNextNotification(){
notification.InvokeAsyncSafe(notification.FinishCurrentNotification); notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
} }
public void ShowTweetDetail(){
notification.InvokeAsyncSafe(notification.ShowTweetDetail);
}
}
// Global
public void SetLastRightClickInfo(string type, string link){
form.InvokeAsyncSafe(() => ContextMenuBase.SetContextInfo(type, link));
}
public void OnTweetPopup(string columnId, string chirpId, string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl){
notification.InvokeAsyncSafe(() => {
form.OnTweetNotification();
notification.ShowNotification(new TweetNotification(columnId, chirpId, columnName, tweetHtml, tweetCharacters, tweetUrl, quoteUrl));
});
}
public void OnTweetSound(){
form.InvokeAsyncSafe(() => {
form.OnTweetNotification();
form.OnTweetSound();
});
}
public void ScreenshotTweet(string html, int width, int height){ public void ScreenshotTweet(string html, int width, int height){
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width, height)); form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width, height));
} }
public void PlayVideo(string url, string username){
form.InvokeAsyncSafe(() => form.PlayVideo(url, username));
}
public void FixClipboard(){ public void FixClipboard(){
form.InvokeAsyncSafe(WindowsUtils.ClipboardStripHtmlStyles); form.InvokeAsyncSafe(WindowsUtils.ClipboardStripHtmlStyles);
} }
public int GetIdleSeconds(){ public void OpenBrowser(string url){
return NativeMethods.GetIdleSeconds(); form.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url));
} }
public void OpenBrowser(string url){ public int GetIdleSeconds(){
BrowserUtils.OpenExternalBrowser(url); return NativeMethods.GetIdleSeconds();
} }
public void Alert(string type, string contents){ public void Alert(string type, string contents){
@@ -101,18 +160,14 @@ namespace TweetDuck.Core.Bridge{
default: icon = MessageBoxIcon.None; break; default: icon = MessageBoxIcon.None; break;
} }
MessageBox.Show(contents, Program.BrandName+" Browser Message", MessageBoxButtons.OK, icon); FormMessage.Show("TweetDuck Browser Message", contents, icon, FormMessage.OK);
} }
public void CrashDebug(string message){ public void CrashDebug(string message){
#if DEBUG #if DEBUG
Log(message); System.Diagnostics.Debug.WriteLine(message);
System.Diagnostics.Debugger.Break(); System.Diagnostics.Debugger.Break();
#endif #endif
} }
public void Log(string data){
System.Diagnostics.Debug.WriteLine(data);
}
} }
} }

View File

@@ -1,8 +1,7 @@
using System; using System;
using System.Drawing; using System.Drawing;
using System.Linq; using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Controls{ namespace TweetDuck.Core.Controls{
static class ControlExtensions{ static class ControlExtensions{
@@ -47,6 +46,12 @@ namespace TweetDuck.Core.Controls{
} }
} }
public static void SetValueSafe(this NumericUpDown numUpDown, int value){
if (value >= numUpDown.Minimum && value <= numUpDown.Maximum){
numUpDown.Value = value;
}
}
public static void SetValueSafe(this TrackBar trackBar, int value){ public static void SetValueSafe(this TrackBar trackBar, int value){
if (value >= trackBar.Minimum && value <= trackBar.Maximum){ if (value >= trackBar.Minimum && value <= trackBar.Maximum){
trackBar.Value = value; trackBar.Value = value;
@@ -61,12 +66,6 @@ namespace TweetDuck.Core.Controls{
else return true; else return true;
} }
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){ public static void EnableMultilineShortcuts(this TextBox textBox){
textBox.KeyDown += (sender, args) => { textBox.KeyDown += (sender, args) => {
if (args.Control && args.KeyCode == Keys.A){ if (args.Control && args.KeyCode == Keys.A){

View File

@@ -2,7 +2,7 @@
using System.Windows.Forms; using System.Windows.Forms;
namespace TweetDuck.Core.Controls{ namespace TweetDuck.Core.Controls{
class FlatButton : Button{ sealed class FlatButton : Button{
protected override bool ShowFocusCues => false; protected override bool ShowFocusCues => false;
public FlatButton(){ public FlatButton(){

View File

@@ -1,4 +1,5 @@
using System.Drawing; using System;
using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
namespace TweetDuck.Core.Controls{ namespace TweetDuck.Core.Controls{
@@ -13,7 +14,7 @@ namespace TweetDuck.Core.Controls{
} }
public void SetValueInstant(int value){ public void SetValueInstant(int value){
ControlExtensions.SetValueInstant(this, value); ControlExtensions.SetValueInstant(this, Math.Max(Minimum, Math.Min(Maximum, value)));
} }
protected override void OnPaint(PaintEventArgs e){ protected override void OnPaint(PaintEventArgs e){

View File

@@ -0,0 +1,18 @@
using System.ComponentModel;
using System.Windows.Forms;
namespace TweetDuck.Core.Controls{
sealed class NumericUpDownEx : NumericUpDown{
public string TextSuffix { get; set ; }
protected override void UpdateEditText(){
base.UpdateEditText();
if (LicenseManager.UsageMode != LicenseUsageMode.Designtime){
ChangingText = true;
Text += TextSuffix;
ChangingText = false;
}
}
}
}

View File

@@ -24,7 +24,7 @@
/// </summary> /// </summary>
private void InitializeComponent() { private void InitializeComponent() {
this.components = new System.ComponentModel.Container(); this.components = new System.ComponentModel.Container();
this.trayIcon = new TweetDuck.Core.TrayIcon(this.components); this.trayIcon = new TweetDuck.Core.Other.TrayIcon(this.components);
this.toolTip = new System.Windows.Forms.ToolTip(this.components); this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.timerResize = new System.Windows.Forms.Timer(this.components); this.timerResize = new System.Windows.Forms.Timer(this.components);
this.SuspendLayout(); this.SuspendLayout();
@@ -38,11 +38,11 @@
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = TweetDuck.Core.Utils.BrowserUtils.BackgroundColor; this.BackColor = TweetDuck.Core.Utils.TwitterUtils.BackgroundColor;
this.ClientSize = new System.Drawing.Size(324, 386); this.ClientSize = new System.Drawing.Size(400, 386);
this.Icon = Properties.Resources.icon; this.Icon = Properties.Resources.icon;
this.Location = TweetDuck.Core.Controls.ControlExtensions.InvisibleLocation; this.Location = TweetDuck.Core.Controls.ControlExtensions.InvisibleLocation;
this.MinimumSize = new System.Drawing.Size(340, 424); this.MinimumSize = new System.Drawing.Size(416, 424);
this.Name = "FormBrowser"; this.Name = "FormBrowser";
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
this.Activated += new System.EventHandler(this.FormBrowser_Activated); this.Activated += new System.EventHandler(this.FormBrowser_Activated);
@@ -57,7 +57,7 @@
#endregion #endregion
private TrayIcon trayIcon; private TweetDuck.Core.Other.TrayIcon trayIcon;
private System.Windows.Forms.ToolTip toolTip; private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.Timer timerResize; private System.Windows.Forms.Timer timerResize;
} }

View File

@@ -1,9 +1,5 @@
using CefSharp; using System;
using CefSharp.WinForms;
using System;
using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetDuck.Core.Bridge; using TweetDuck.Core.Bridge;
@@ -12,115 +8,101 @@ using TweetDuck.Core.Handling;
using TweetDuck.Core.Notification; using TweetDuck.Core.Notification;
using TweetDuck.Core.Notification.Screenshot; using TweetDuck.Core.Notification.Screenshot;
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
using TweetDuck.Core.Other.Analytics;
using TweetDuck.Core.Other.Management;
using TweetDuck.Core.Other.Settings; using TweetDuck.Core.Other.Settings;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Plugins; using TweetDuck.Plugins;
using TweetDuck.Plugins.Enums;
using TweetDuck.Plugins.Events; using TweetDuck.Plugins.Events;
using TweetDuck.Resources;
using TweetDuck.Updates; using TweetDuck.Updates;
using TweetDuck.Updates.Events;
using TweetLib.Audio.Utils;
namespace TweetDuck.Core{ namespace TweetDuck.Core{
sealed partial class FormBrowser : Form{ sealed partial class FormBrowser : Form{
private static UserConfig Config => Program.UserConfig; private static UserConfig Config => Program.UserConfig;
public bool IsWaiting{
set{
if (value){
browser.Enabled = false;
Cursor = Cursors.WaitCursor;
}
else{
browser.Enabled = true;
Cursor = Cursors.Default;
if (Focused){ // re-focus browser only if the window or a child is activated
browser.Focus();
}
}
}
}
public string UpdateInstallerPath { get; private set; } public string UpdateInstallerPath { get; private set; }
private readonly ChromiumWebBrowser browser; private readonly TweetDeckBrowser browser;
private readonly PluginManager plugins; private readonly PluginManager plugins;
private readonly UpdateHandler updates; private readonly UpdateHandler updates;
private readonly FormNotificationTweet notification; private readonly FormNotificationTweet notification;
private readonly ContextMenu contextMenu; private readonly ContextMenu contextMenu;
private bool isLoaded; private bool isLoaded;
private bool isBrowserReady;
private FormWindowState prevState; private FormWindowState prevState;
private TweetScreenshotManager notificationScreenshotManager; private TweetScreenshotManager notificationScreenshotManager;
private SoundNotification soundNotification; private VideoPlayer videoPlayer;
private AnalyticsManager analytics;
public FormBrowser(PluginManager pluginManager, UpdaterSettings updaterSettings){ public FormBrowser(UpdaterSettings updaterSettings){
InitializeComponent(); InitializeComponent();
Text = Program.BrandName; Text = Program.BrandName;
this.plugins = pluginManager; this.plugins = new PluginManager(Program.PluginPath, Program.PluginConfigFilePath);
this.plugins.Reloaded += plugins_Reloaded; this.plugins.Reloaded += plugins_Reloaded;
this.plugins.PluginChangedState += plugins_PluginChangedState; this.plugins.Executed += plugins_Executed;
this.plugins.Reload();
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 = new FormNotificationTweet(this, plugins);
this.notification.Show(); this.notification.Show();
this.browser = new ChromiumWebBrowser("https://tweetdeck.twitter.com/"){ this.browser = new TweetDeckBrowser(this, plugins, new TweetDeckBridge.Browser(this, notification));
MenuHandler = new ContextMenuBrowser(this), this.browser.PageLoaded += browser_PageLoaded;
JsDialogHandler = new JavaScriptDialogHandler(),
LifeSpanHandler = new LifeSpanHandler(),
RequestHandler = new RequestHandlerBrowser()
};
#if DEBUG this.contextMenu = ContextMenuBrowser.CreateMenu(this);
this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
#endif
this.browser.LoadingStateChanged += browser_LoadingStateChanged;
this.browser.FrameLoadStart += browser_FrameLoadStart;
this.browser.FrameLoadEnd += browser_FrameLoadEnd;
this.browser.LoadError += browser_LoadError;
this.browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(this, notification));
this.browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
browser.BrowserSettings.BackgroundColor = (uint)BrowserUtils.BackgroundColor.ToArgb();
browser.Dock = DockStyle.None;
browser.Location = ControlExtensions.InvisibleLocation;
Controls.Add(browser);
Controls.Add(new MenuStrip{ Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update Controls.Add(new MenuStrip{ Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update
Disposed += (sender, args) => { Disposed += (sender, args) => {
Config.MuteToggled -= Config_MuteToggled;
Config.TrayBehaviorChanged -= Config_TrayBehaviorChanged;
browser.Dispose(); browser.Dispose();
contextMenu.Dispose(); contextMenu.Dispose();
notificationScreenshotManager?.Dispose(); notificationScreenshotManager?.Dispose();
soundNotification?.Dispose(); videoPlayer?.Dispose();
analytics?.Dispose();
}; };
Config.MuteToggled += Config_MuteToggled;
this.trayIcon.ClickRestore += trayIcon_ClickRestore; this.trayIcon.ClickRestore += trayIcon_ClickRestore;
this.trayIcon.ClickClose += trayIcon_ClickClose; this.trayIcon.ClickClose += trayIcon_ClickClose;
Config.TrayBehaviorChanged += Config_TrayBehaviorChanged; Config.TrayBehaviorChanged += Config_TrayBehaviorChanged;
UpdateTrayIcon(); UpdateTrayIcon();
Config.MuteToggled += Config_MuteToggled; this.updates = browser.CreateUpdateHandler(updaterSettings);
Config.ZoomLevelChanged += Config_ZoomLevelChanged;
this.updates = new UpdateHandler(browser, updaterSettings);
this.updates.UpdateAccepted += updates_UpdateAccepted; this.updates.UpdateAccepted += updates_UpdateAccepted;
this.updates.UpdateDismissed += updates_UpdateDismissed; this.updates.UpdateDismissed += updates_UpdateDismissed;
if (Config.AllowDataCollection){
analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
}
RestoreWindow(); 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){ private void ShowChildForm(Form form){
form.VisibleChanged += (sender, args) => form.MoveToCenter(this); form.VisibleChanged += (sender, args) => form.MoveToCenter(this);
form.Show(this); form.Show(this);
@@ -139,74 +121,11 @@ namespace TweetDuck.Core{
isLoaded = true; isLoaded = true;
} }
private void OnBrowserReady(){
if (!isBrowserReady){
browser.Location = Point.Empty;
browser.Dock = DockStyle.Fill;
isBrowserReady = true;
}
}
private void UpdateTrayIcon(){ private void UpdateTrayIcon(){
trayIcon.Visible = Config.TrayBehavior.ShouldDisplayIcon(); trayIcon.Visible = Config.TrayBehavior.ShouldDisplayIcon();
} }
// active event handlers // event handlers
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
if (!e.IsLoading){
foreach(string word in BrowserUtils.DictionaryWords){
browser.AddWordToDictionary(word);
}
BeginInvoke(new Action(OnBrowserReady));
browser.LoadingStateChanged -= browser_LoadingStateChanged;
}
}
private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){
if (e.Frame.IsMain){
if (Config.ZoomLevel != 100){
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Config.ZoomLevel);
}
if (BrowserUtils.IsTwitterWebsite(e.Frame)){
ScriptLoader.ExecuteFile(e.Frame, "twitter.js");
}
}
}
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
if (e.Frame.IsMain && BrowserUtils.IsTweetDeckWebsite(e.Frame)){
e.Frame.ExecuteJavaScriptAsync(BrowserUtils.BackgroundColorFix);
UpdateProperties(PropertyBridge.Properties.AllBrowser);
ScriptLoader.ExecuteFile(e.Frame, "code.js");
ReinjectCustomCSS(Config.CustomBrowserCSS);
if (plugins.HasAnyPlugin(PluginEnvironment.Browser)){
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginBrowserScriptFile);
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginGlobalScriptFile);
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Browser, true);
}
TweetDeckBridge.ResetStaticProperties();
}
}
private void browser_LoadError(object sender, LoadErrorEventArgs e){
if (e.ErrorCode == CefErrorCode.Aborted){
return;
}
if (!e.FailedUrl.StartsWith("http://td/")){
string errorPage = ScriptLoader.LoadResource("pages/error.html", true);
if (errorPage != null){
browser.LoadHtml(errorPage.Replace("{err}", BrowserUtils.GetErrorName(e.ErrorCode)), "http://td/error");
}
}
}
private void timerResize_Tick(object sender, EventArgs e){ private void timerResize_Tick(object sender, EventArgs e){
FormBrowser_ResizeEnd(this, e); // also stops timer FormBrowser_ResizeEnd(this, e); // also stops timer
@@ -216,6 +135,10 @@ namespace TweetDuck.Core{
if (!isLoaded)return; if (!isLoaded)return;
trayIcon.HasNotifications = false; trayIcon.HasNotifications = false;
if (!browser.Enabled){ // when taking a screenshot, the window is unfocused and
browser.Enabled = true; // the browser is disabled; if the user clicks back into
} // the window, enable the browser again
} }
private void FormBrowser_LocationChanged(object sender, EventArgs e){ private void FormBrowser_LocationChanged(object sender, EventArgs e){
@@ -273,11 +196,7 @@ namespace TweetDuck.Core{
} }
private void Config_MuteToggled(object sender, EventArgs e){ private void Config_MuteToggled(object sender, EventArgs e){
UpdateProperties(PropertyBridge.Properties.MuteNotifications); TriggerAnalyticsEvent(AnalyticsFile.Event.MuteNotification);
}
private void Config_ZoomLevelChanged(object sender, EventArgs e){
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Config.ZoomLevel);
} }
private void Config_TrayBehaviorChanged(object sender, EventArgs e){ private void Config_TrayBehaviorChanged(object sender, EventArgs e){
@@ -295,20 +214,61 @@ namespace TweetDuck.Core{
ForceClose(); ForceClose();
} }
private void plugins_Reloaded(object sender, PluginErrorEventArgs e){ private void browser_PageLoaded(object sender, EventArgs e){
browser.GetBrowser().Reload(); if (Config.ShowDataCollectionNotification){
}
private void plugins_PluginChangedState(object sender, PluginChangedStateEventArgs e){
browser.ExecuteScriptAsync("window.TDPF_setPluginState", e.Plugin, e.IsEnabled);
}
private void updates_UpdateAccepted(object sender, UpdateAcceptedEventArgs e){
this.InvokeAsyncSafe(() => { this.InvokeAsyncSafe(() => {
foreach(Form form in Application.OpenForms.Cast<Form>().Reverse()){ if (!Config.FirstRun && Config.AllowDataCollection){
if (form is FormSettings || form is FormPlugins || form is FormAbout){ FormMessage form = new FormMessage("Anonymous Data Update", "Hi! You can now review your anonymous data report, and opt-out if you've changed your mind. Collected data will be used to focus development on most commonly used features. If you want to opt-out but still support the project, any feedback and donations are appreciated.", MessageBoxIcon.Information);
form.AddButton("OK", ControlType.Accept | ControlType.Focused);
Button btnReviewSettings = new Button{
Anchor = AnchorStyles.Bottom | AnchorStyles.Left,
Font = SystemFonts.MessageBoxFont,
Location = new Point(9, 12),
Margin = new Padding(0, 0, 48, 0),
Size = new Size(160, 26),
Text = "Review Feedback Options",
UseVisualStyleBackColor = true
};
btnReviewSettings.Click += (sender2, args2) => {
form.Close(); form.Close();
OpenSettings(typeof(TabSettingsFeedback));
};
form.AddActionControl(btnReviewSettings);
ShowChildForm(form);
} }
Config.ShowDataCollectionNotification = false;
Config.Save();
});
}
}
private void plugins_Reloaded(object sender, PluginErrorEventArgs e){
if (e.HasErrors){
FormMessage.Error("Error Loading Plugins", "The following plugins will not be available until the issues are resolved:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK);
}
if (isLoaded){
ReloadToTweetDeck();
}
}
private static void plugins_Executed(object sender, PluginErrorEventArgs e){
if (e.HasErrors){
FormMessage.Error("Error Executing Plugins", "Failed to execute the following plugins:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK);
}
}
private void updates_UpdateAccepted(object sender, UpdateEventArgs e){
this.InvokeAsyncSafe(() => {
FormManager.CloseAllDialogs();
if (!string.IsNullOrEmpty(Config.DismissedUpdate)){
Config.DismissedUpdate = null;
Config.Save();
} }
updates.BeginUpdateDownload(this, e.UpdateInfo, update => { updates.BeginUpdateDownload(this, e.UpdateInfo, update => {
@@ -321,53 +281,38 @@ namespace TweetDuck.Core{
}); });
} }
private void updates_UpdateDismissed(object sender, UpdateDismissedEventArgs e){ private void updates_UpdateDismissed(object sender, UpdateEventArgs e){
this.InvokeAsyncSafe(() => { this.InvokeAsyncSafe(() => {
Config.DismissedUpdate = e.VersionTag; Config.DismissedUpdate = e.UpdateInfo.VersionTag;
Config.Save(); Config.Save();
}); });
} }
private void soundNotification_PlaybackError(object sender, PlaybackErrorEventArgs e){
e.Ignore = true;
using(FormMessage form = new FormMessage("Notification Sound Error", "Could not play custom notification sound."+Environment.NewLine+e.Message, MessageBoxIcon.Error)){
form.CancelButton = form.AddButton("Ignore");
Button btnOpenSettings = form.AddButton("View Options");
btnOpenSettings.Width += 16;
btnOpenSettings.Location = new Point(btnOpenSettings.Location.X-16, btnOpenSettings.Location.Y);
if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnOpenSettings){
OpenSettings(typeof(TabSettingsSounds));
}
}
}
protected override void WndProc(ref Message m){ protected override void WndProc(ref Message m){
if (isLoaded && m.Msg == Program.WindowRestoreMessage){ if (isLoaded && m.Msg == Program.WindowRestoreMessage){
using(Process process = Process.GetCurrentProcess()){ if (WindowsUtils.CurrentProcessID == m.WParam.ToInt32()){
if (process.Id == m.WParam.ToInt32()){ trayIcon_ClickRestore(trayIcon, EventArgs.Empty);
trayIcon_ClickRestore(trayIcon, new EventArgs());
}
} }
return; return;
} }
if (isBrowserReady && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN){ if (browser.Ready && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN){
browser.ExecuteScriptAsync("TDGF_onMouseClickExtra", (m.WParam.ToInt32() >> 16) & 0xFFFF); if (videoPlayer != null && videoPlayer.Running){
videoPlayer.Close();
}
else{
browser.OnMouseClickExtra(m.WParam);
TriggerAnalyticsEvent(AnalyticsFile.Event.BrowserExtraMouseButton);
}
return; return;
} }
base.WndProc(ref m); base.WndProc(ref m);
} }
// notification helpers // bridge methods
public FormNotificationMain CreateNotificationForm(bool enableContextMenu){
return new FormNotificationMain(this, plugins, enableContextMenu);
}
public void PauseNotification(){ public void PauseNotification(){
notification.PauseNotification(); notification.PauseNotification();
@@ -377,22 +322,53 @@ namespace TweetDuck.Core{
notification.ResumeNotification(); notification.ResumeNotification();
} }
// javascript calls
public void ReinjectCustomCSS(string css){ public void ReinjectCustomCSS(string css){
browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty); browser.ReinjectCustomCSS(css);
}
public void UpdateProperties(PropertyBridge.Properties properties){
browser.ExecuteScriptAsync(PropertyBridge.GenerateScript(properties));
} }
public void ReloadToTweetDeck(){ public void ReloadToTweetDeck(){
browser.ExecuteScriptAsync("window.location.href = 'https://tweetdeck.twitter.com'"); browser.ReloadToTweetDeck();
}
public void TriggerTweetScreenshot(){
browser.TriggerTweetScreenshot();
}
public void ReloadColumns(){
browser.ReloadColumns();
}
public void PlaySoundNotification(){
browser.PlaySoundNotification();
}
public void ApplyROT13(){
browser.ApplyROT13();
}
public void TriggerAnalyticsEvent(AnalyticsFile.Event e){
analytics?.TriggerEvent(e);
} }
// callback handlers // callback handlers
public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){
if (Config.FirstRun){
Config.FirstRun = false;
Config.AllowDataCollection = allowDataCollection;
Config.ShowDataCollectionNotification = false;
Config.Save();
if (allowDataCollection && analytics == null){
analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
}
}
if (showGuide){
FormGuide.Show();
}
}
public void OpenContextMenu(){ public void OpenContextMenu(){
contextMenu.Show(this, PointToClient(Cursor.Position)); contextMenu.Show(this, PointToClient(Cursor.Position));
} }
@@ -402,39 +378,62 @@ namespace TweetDuck.Core{
} }
public void OpenSettings(Type startTab){ public void OpenSettings(Type startTab){
if (!TryBringToFront<FormSettings>()){ if (!FormManager.TryBringToFront<FormSettings>()){
bool prevEnableUpdateCheck = Config.EnableUpdateCheck; bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
FormSettings form = new FormSettings(this, plugins, updates, startTab); FormSettings form = new FormSettings(this, plugins, updates, analytics, startTab);
form.FormClosed += (sender, args) => { form.FormClosed += (sender, args) => {
if (!prevEnableUpdateCheck && Config.EnableUpdateCheck){ if (!prevEnableUpdateCheck && Config.EnableUpdateCheck){
updates.DismissUpdate(string.Empty); Config.DismissedUpdate = null;
updates.Check(false); Config.Save();
updates.Check(true);
} }
if (!Config.EnableTrayHighlight){ if (!Config.EnableTrayHighlight){
trayIcon.HasNotifications = false; trayIcon.HasNotifications = false;
} }
UpdateProperties(PropertyBridge.Properties.ExpandLinksOnHover | PropertyBridge.Properties.SwitchAccountSelectors | PropertyBridge.Properties.HasCustomNotificationSound); if (Config.AllowDataCollection){
if (analytics == null){
analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
}
}
else if (analytics != null){
analytics.Dispose();
analytics = null;
}
BrowserCache.RefreshTimer();
if (form.ShouldReloadBrowser){
FormManager.TryFind<FormPlugins>()?.Close();
plugins.Reload(); // also reloads the browser
}
else{
browser.UpdateProperties();
}
notification.RequiresResize = true; notification.RequiresResize = true;
form.Dispose(); form.Dispose();
}; };
TriggerAnalyticsEvent(AnalyticsFile.Event.OpenOptions);
ShowChildForm(form); ShowChildForm(form);
} }
} }
public void OpenAbout(){ public void OpenAbout(){
if (!TryBringToFront<FormAbout>()){ if (!FormManager.TryBringToFront<FormAbout>()){
TriggerAnalyticsEvent(AnalyticsFile.Event.OpenAbout);
ShowChildForm(new FormAbout()); ShowChildForm(new FormAbout());
} }
} }
public void OpenPlugins(){ public void OpenPlugins(){
if (!TryBringToFront<FormPlugins>()){ if (!FormManager.TryBringToFront<FormPlugins>()){
TriggerAnalyticsEvent(AnalyticsFile.Event.OpenPlugins);
ShowChildForm(new FormPlugins(plugins)); ShowChildForm(new FormPlugins(plugins));
} }
} }
@@ -445,25 +444,57 @@ namespace TweetDuck.Core{
} }
} }
public void PlayNotificationSound(){ public void OnTweetSound(){
if (Config.NotificationSoundPath.Length == 0){ TriggerAnalyticsEvent(AnalyticsFile.Event.SoundNotification);
}
public void PlayVideo(string url, string username){
if (string.IsNullOrEmpty(url)){
videoPlayer?.Close();
return; return;
} }
if (soundNotification == null){ if (videoPlayer == null){
soundNotification = new SoundNotification(); videoPlayer = new VideoPlayer(this);
soundNotification.PlaybackError += soundNotification_PlaybackError;
videoPlayer.ProcessExited += (sender, args) => {
browser.HideVideoOverlay(true);
};
} }
soundNotification.Play(Config.NotificationSoundPath); videoPlayer.Launch(url, username);
TriggerAnalyticsEvent(AnalyticsFile.Event.VideoPlay);
}
public bool ProcessBrowserKey(Keys key){
if (videoPlayer != null && videoPlayer.Running){
videoPlayer.SendKeyEvent(key);
return true;
}
return false;
}
public void ShowTweetDetail(string columnId, string chirpId, string fallbackUrl){
Activate();
if (!browser.IsTweetDeckWebsite){
FormMessage.Error("View Tweet Detail", "TweetDeck is not currently loaded.", FormMessage.OK);
return;
}
notification.FinishCurrentNotification();
browser.ShowTweetDetail(columnId, chirpId, fallbackUrl);
TriggerAnalyticsEvent(AnalyticsFile.Event.TweetDetail);
} }
public void OnTweetScreenshotReady(string html, int width, int height){ public void OnTweetScreenshotReady(string html, int width, int height){
if (notificationScreenshotManager == null){ if (notificationScreenshotManager == null){
notificationScreenshotManager = new TweetScreenshotManager(this); notificationScreenshotManager = new TweetScreenshotManager(this, plugins);
} }
notificationScreenshotManager.Trigger(html, width, height); notificationScreenshotManager.Trigger(html, width, height);
TriggerAnalyticsEvent(AnalyticsFile.Event.TweetScreenshot);
} }
public void DisplayTooltip(string text){ public void DisplayTooltip(string text){
@@ -476,9 +507,5 @@ namespace TweetDuck.Core{
toolTip.Show(text, this, position); toolTip.Show(text, this, position);
} }
} }
public void TriggerTweetScreenshot(){
browser.ExecuteScriptAsync("TDGF_triggerScreenshot()");
}
} }
} }

29
Core/FormManager.cs Normal file
View File

@@ -0,0 +1,29 @@
using System.Linq;
using System.Windows.Forms;
using TweetDuck.Core.Other;
namespace TweetDuck.Core{
static class FormManager{
public static T TryFind<T>() where T : Form{
return Application.OpenForms.OfType<T>().FirstOrDefault();
}
public static bool TryBringToFront<T>() where T : Form{
T form = TryFind<T>();
if (form != null){
form.BringToFront();
return true;
}
else return false;
}
public static void CloseAllDialogs(){
foreach(Form form in Application.OpenForms.Cast<Form>().Reverse()){
if (form is FormSettings || form is FormPlugins || form is FormAbout || form is FormGuide){
form.Close();
}
}
}
}
}

View File

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

View File

@@ -1,98 +1,160 @@
using CefSharp; using System;
using System;
using System.IO; using System.IO;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp;
using TweetDuck.Core.Bridge; using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using System.Collections.Generic;
using System.Linq;
using TweetDuck.Core.Other;
namespace TweetDuck.Core.Handling{ namespace TweetDuck.Core.Handling{
abstract class ContextMenuBase : IContextMenuHandler{ abstract class ContextMenuBase : IContextMenuHandler{
private static readonly Lazy<Regex> RegexTwitterAccount = new Lazy<Regex>(() => new Regex(@"^https?://twitter\.com/([^/]+)/?$", RegexOptions.Compiled), false); public static readonly bool HasDevTools = File.Exists(Path.Combine(Program.ProgramPath, "devtools_resources.pak"));
protected static readonly bool HasDevTools = File.Exists(Path.Combine(Program.ProgramPath, "devtools_resources.pak"));
private const int MenuOpenLinkUrl = 26500; private static TwitterUtils.ImageQuality ImageQuality => Program.UserConfig.TwitterImageQuality;
private const int MenuCopyLinkUrl = 26501;
private const int MenuCopyUsername = 26502;
private const int MenuOpenImage = 26503;
private const int MenuSaveImage = 26504;
private const int MenuCopyImageUrl = 26505;
private const int MenuOpenDevTools = 26599;
private readonly Form form; private static KeyValuePair<string, string> ContextInfo;
private static bool IsLink => ContextInfo.Key == "link";
private static bool IsImage => ContextInfo.Key == "image";
private static bool IsVideo => ContextInfo.Key == "video";
protected ContextMenuBase(Form form){ public static void SetContextInfo(string type, string link){
this.form = form; ContextInfo = new KeyValuePair<string, string>(string.IsNullOrEmpty(link) ? null : type, link);
} }
private static string GetMediaLink(IContextMenuParams parameters){
return IsImage || IsVideo ? ContextInfo.Value : parameters.SourceUrl;
}
private const CefMenuCommand MenuOpenLinkUrl = (CefMenuCommand)26500;
private const CefMenuCommand MenuCopyLinkUrl = (CefMenuCommand)26501;
private const CefMenuCommand MenuCopyUsername = (CefMenuCommand)26502;
private const CefMenuCommand MenuViewImage = (CefMenuCommand)26503;
private const CefMenuCommand MenuOpenMediaUrl = (CefMenuCommand)26504;
private const CefMenuCommand MenuCopyMediaUrl = (CefMenuCommand)26505;
private const CefMenuCommand MenuSaveMedia = (CefMenuCommand)26506;
private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand)26507;
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand)26599;
private string[] lastHighlightedTweetAuthors;
private string[] lastHighlightedTweetImageList;
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){ public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && !parameters.UnfilteredLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal)){ if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
if (RegexTwitterAccount.Value.IsMatch(parameters.UnfilteredLinkUrl)){ lastHighlightedTweetAuthors = StringUtils.EmptyArray;
model.AddItem((CefMenuCommand)MenuOpenLinkUrl, "Open account in browser"); lastHighlightedTweetImageList = StringUtils.EmptyArray;
model.AddItem((CefMenuCommand)MenuCopyLinkUrl, "Copy account address"); ContextInfo = default(KeyValuePair<string, string>);
model.AddItem((CefMenuCommand)MenuCopyUsername, "Copy account username");
} }
else{ else{
model.AddItem((CefMenuCommand)MenuOpenLinkUrl, "Open link in browser"); lastHighlightedTweetAuthors = TweetDeckBridge.LastHighlightedTweetAuthorsArray;
model.AddItem((CefMenuCommand)MenuCopyLinkUrl, "Copy link address"); lastHighlightedTweetImageList = TweetDeckBridge.LastHighlightedTweetImagesArray;
}
bool hasTweetImage = IsImage;
bool hasTweetVideo = IsVideo;
string TextOpen(string name) => "Open "+name+" in browser";
string TextCopy(string name) => "Copy "+name+" address";
string TextSave(string name) => "Save "+name+" as...";
if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && !parameters.UnfilteredLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal) && !hasTweetImage && !hasTweetVideo){
if (TwitterUtils.RegexAccount.IsMatch(parameters.UnfilteredLinkUrl)){
model.AddItem(MenuOpenLinkUrl, TextOpen("account"));
model.AddItem(MenuCopyLinkUrl, TextCopy("account"));
model.AddItem(MenuCopyUsername, "Copy account username");
}
else{
model.AddItem(MenuOpenLinkUrl, TextOpen("link"));
model.AddItem(MenuCopyLinkUrl, TextCopy("link"));
} }
model.AddSeparator(); model.AddSeparator();
} }
if (parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents){ if (hasTweetVideo){
model.AddItem((CefMenuCommand)MenuOpenImage, "Open image in browser"); model.AddItem(MenuOpenMediaUrl, TextOpen("video"));
model.AddItem((CefMenuCommand)MenuSaveImage, "Save image as..."); model.AddItem(MenuCopyMediaUrl, TextCopy("video"));
model.AddItem((CefMenuCommand)MenuCopyImageUrl, "Copy image address"); model.AddItem(MenuSaveMedia, TextSave("video"));
model.AddSeparator();
}
else if ((parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) || hasTweetImage){
model.AddItem(MenuViewImage, "View image in photo viewer");
model.AddItem(MenuOpenMediaUrl, TextOpen("image"));
model.AddItem(MenuCopyMediaUrl, TextCopy("image"));
model.AddItem(MenuSaveMedia, TextSave("image"));
if (lastHighlightedTweetImageList.Length > 1){
model.AddItem(MenuSaveTweetImages, TextSave("all images"));
}
model.AddSeparator(); model.AddSeparator();
} }
} }
public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){ public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
switch((int)commandId){ switch(commandId){
case MenuOpenLinkUrl: case MenuOpenLinkUrl:
BrowserUtils.OpenExternalBrowser(parameters.LinkUrl); OpenBrowser(browserControl.AsControl(), IsLink ? ContextInfo.Value : parameters.LinkUrl);
break; break;
case MenuCopyLinkUrl: case MenuCopyLinkUrl:
SetClipboardText(string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedLink) ? parameters.UnfilteredLinkUrl : TweetDeckBridge.LastRightClickedLink); SetClipboardText(browserControl.AsControl(), IsLink ? ContextInfo.Value : parameters.UnfilteredLinkUrl);
break; break;
case MenuOpenImage: case MenuCopyUsername:
BrowserUtils.OpenExternalBrowser(parameters.SourceUrl); Match match = TwitterUtils.RegexAccount.Match(parameters.UnfilteredLinkUrl);
SetClipboardText(browserControl.AsControl(), match.Success ? match.Groups[1].Value : parameters.UnfilteredLinkUrl);
break; break;
case MenuSaveImage: case MenuOpenMediaUrl:
string fileName = GetImageFileName(parameters.SourceUrl); OpenBrowser(browserControl.AsControl(), TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality));
string extension = Path.GetExtension(fileName); break;
string saveTarget;
using(SaveFileDialog dialog = new SaveFileDialog{ case MenuCopyMediaUrl:
AutoUpgradeEnabled = true, SetClipboardText(browserControl.AsControl(), TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality));
OverwritePrompt = true, break;
Title = "Save image",
FileName = fileName, case MenuViewImage:
Filter = "Image ("+(string.IsNullOrEmpty(extension) ? "unknown" : extension)+")|*.*" string url = GetMediaLink(parameters);
}){ string file = Path.Combine(BrowserCache.CacheFolder, TwitterUtils.GetImageFileName(url));
saveTarget = dialog.ShowDialog() == DialogResult.OK ? dialog.FileName : null;
void ViewFile(){
string ext = Path.GetExtension(file);
if (TwitterUtils.ValidImageExtensions.Contains(ext)){
WindowsUtils.OpenAssociatedProgram(file);
}
else{
FormMessage.Error("Image Download", "Invalid file extension "+ext, FormMessage.OK);
}
} }
if (saveTarget != null){ if (File.Exists(file)){
BrowserUtils.DownloadFileAsync(parameters.SourceUrl, saveTarget, null, ex => { ViewFile();
MessageBox.Show("An error occurred while downloading the image: "+ex.Message, Program.BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error); }
else{
BrowserUtils.DownloadFileAsync(TwitterUtils.GetMediaLink(url, ImageQuality), file, ViewFile, ex => {
FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK);
}); });
} }
break; break;
case MenuCopyImageUrl: case MenuSaveMedia:
SetClipboardText(parameters.SourceUrl); if (IsVideo){
TwitterUtils.DownloadVideo(GetMediaLink(parameters), lastHighlightedTweetAuthors.LastOrDefault());
}
else{
TwitterUtils.DownloadImage(GetMediaLink(parameters), lastHighlightedTweetAuthors.LastOrDefault(), ImageQuality);
}
break; break;
case MenuCopyUsername: case MenuSaveTweetImages:
Match match = RegexTwitterAccount.Value.Match(parameters.UnfilteredLinkUrl); TwitterUtils.DownloadImages(lastHighlightedTweetImageList, lastHighlightedTweetAuthors.LastOrDefault(), ImageQuality);
SetClipboardText(match.Success ? match.Groups[1].Value : parameters.UnfilteredLinkUrl);
break; break;
case MenuOpenDevTools: case MenuOpenDevTools:
@@ -103,18 +165,24 @@ namespace TweetDuck.Core.Handling{
return false; return false;
} }
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){} public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
ContextInfo = default(KeyValuePair<string, string>);
}
public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){ public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){
return false; return false;
} }
protected void SetClipboardText(string text){ protected void OpenBrowser(Control control, string url){
form.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText)); control.InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(url));
} }
protected void AddDebugMenuItems(IMenuModel model){ protected void SetClipboardText(Control control, string text){
model.AddItem((CefMenuCommand)MenuOpenDevTools, "Open dev tools"); control.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText));
}
protected static void AddDebugMenuItems(IMenuModel model){
model.AddItem(MenuOpenDevTools, "Open dev tools");
} }
protected static void RemoveSeparatorIfLast(IMenuModel model){ protected static void RemoveSeparatorIfLast(IMenuModel model){
@@ -128,21 +196,5 @@ namespace TweetDuck.Core.Handling{
model.AddSeparator(); model.AddSeparator();
} }
} }
private static string GetImageFileName(string url){
// twimg adds a colon after file extension
int dot = url.LastIndexOf('.');
if (dot != -1){
int colon = url.IndexOf(':', dot);
if (colon != -1){
url = url.Substring(0, colon);
}
}
// return file name
return BrowserUtils.GetFileNameFromUrl(url) ?? "unknown";
}
} }
} }

View File

@@ -2,21 +2,23 @@
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Bridge; using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Other.Analytics;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Handling{ namespace TweetDuck.Core.Handling{
class ContextMenuBrowser : ContextMenuBase{ sealed class ContextMenuBrowser : ContextMenuBase{
private const int MenuGlobal = 26600; private const CefMenuCommand MenuGlobal = (CefMenuCommand)26600;
private const int MenuMute = 26601; private const CefMenuCommand MenuMute = (CefMenuCommand)26601;
private const int MenuSettings = 26602; private const CefMenuCommand MenuSettings = (CefMenuCommand)26602;
private const int MenuPlugins = 26003; private const CefMenuCommand MenuPlugins = (CefMenuCommand)26003;
private const int MenuAbout = 26604; private const CefMenuCommand MenuAbout = (CefMenuCommand)26604;
private const int MenuOpenTweetUrl = 26610; private const CefMenuCommand MenuOpenTweetUrl = (CefMenuCommand)26610;
private const int MenuCopyTweetUrl = 26611; private const CefMenuCommand MenuCopyTweetUrl = (CefMenuCommand)26611;
private const int MenuOpenQuotedTweetUrl = 26612; private const CefMenuCommand MenuOpenQuotedTweetUrl = (CefMenuCommand)26612;
private const int MenuCopyQuotedTweetUrl = 26613; private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand)26613;
private const int MenuScreenshotTweet = 26614; private const CefMenuCommand MenuScreenshotTweet = (CefMenuCommand)26614;
private const CefMenuCommand MenuInputApplyROT13 = (CefMenuCommand)26615;
private const string TitleReloadBrowser = "Reload browser"; private const string TitleReloadBrowser = "Reload browser";
private const string TitleMuteNotifications = "Mute notifications"; private const string TitleMuteNotifications = "Mute notifications";
@@ -26,10 +28,10 @@ namespace TweetDuck.Core.Handling{
private readonly FormBrowser form; private readonly FormBrowser form;
private string lastHighlightedTweet; private string lastHighlightedTweetUrl;
private string lastHighlightedQuotedTweet; private string lastHighlightedQuoteUrl;
public ContextMenuBrowser(FormBrowser form) : base(form){ public ContextMenuBrowser(FormBrowser form){
this.form = form; this.form = form;
} }
@@ -41,28 +43,33 @@ namespace TweetDuck.Core.Handling{
RemoveSeparatorIfLast(model); RemoveSeparatorIfLast(model);
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){ if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){
if (parameters.TypeFlags.HasFlag(ContextMenuType.Editable)){
model.AddSeparator();
model.AddItem(MenuInputApplyROT13, "Apply ROT13");
}
model.AddSeparator(); model.AddSeparator();
} }
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model); base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
lastHighlightedTweet = TweetDeckBridge.LastHighlightedTweet; lastHighlightedTweetUrl = TweetDeckBridge.LastHighlightedTweetUrl;
lastHighlightedQuotedTweet = TweetDeckBridge.LastHighlightedQuotedTweet; lastHighlightedQuoteUrl = TweetDeckBridge.LastHighlightedQuoteUrl;
if (!BrowserUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){ if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
lastHighlightedTweet = string.Empty; lastHighlightedTweetUrl = string.Empty;
lastHighlightedQuotedTweet = string.Empty; lastHighlightedQuoteUrl = string.Empty;
} }
if (!string.IsNullOrEmpty(lastHighlightedTweet) && (parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){ if (!string.IsNullOrEmpty(lastHighlightedTweetUrl) && (parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
model.AddItem((CefMenuCommand)MenuOpenTweetUrl, "Open tweet in browser"); model.AddItem(MenuOpenTweetUrl, "Open tweet in browser");
model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address"); model.AddItem(MenuCopyTweetUrl, "Copy tweet address");
model.AddItem((CefMenuCommand)MenuScreenshotTweet, "Screenshot tweet to clipboard"); model.AddItem(MenuScreenshotTweet, "Screenshot tweet to clipboard");
if (!string.IsNullOrEmpty(lastHighlightedQuotedTweet)){ if (!string.IsNullOrEmpty(lastHighlightedQuoteUrl)){
model.AddSeparator(); model.AddSeparator();
model.AddItem((CefMenuCommand)MenuOpenQuotedTweetUrl, "Open quoted tweet in browser"); model.AddItem(MenuOpenQuotedTweetUrl, "Open quoted tweet in browser");
model.AddItem((CefMenuCommand)MenuCopyQuotedTweetUrl, "Copy quoted tweet address"); model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
} }
model.AddSeparator(); model.AddSeparator();
@@ -71,16 +78,16 @@ namespace TweetDuck.Core.Handling{
if ((parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){ if ((parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
AddSeparator(model); AddSeparator(model);
IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu((CefMenuCommand)MenuGlobal, Program.BrandName); IMenuModel globalMenu = model.Count == 0 ? model : model.AddSubMenu(MenuGlobal, Program.BrandName);
globalMenu.AddItem(CefMenuCommand.Reload, TitleReloadBrowser); globalMenu.AddItem(CefMenuCommand.Reload, TitleReloadBrowser);
globalMenu.AddCheckItem((CefMenuCommand)MenuMute, TitleMuteNotifications); globalMenu.AddCheckItem(MenuMute, TitleMuteNotifications);
globalMenu.SetChecked((CefMenuCommand)MenuMute, Program.UserConfig.MuteNotifications); globalMenu.SetChecked(MenuMute, Program.UserConfig.MuteNotifications);
globalMenu.AddSeparator(); globalMenu.AddSeparator();
globalMenu.AddItem((CefMenuCommand)MenuSettings, TitleSettings); globalMenu.AddItem(MenuSettings, TitleSettings);
globalMenu.AddItem((CefMenuCommand)MenuPlugins, TitlePlugins); globalMenu.AddItem(MenuPlugins, TitlePlugins);
globalMenu.AddItem((CefMenuCommand)MenuAbout, TitleAboutProgram); globalMenu.AddItem(MenuAbout, TitleAboutProgram);
if (HasDevTools){ if (HasDevTools){
globalMenu.AddSeparator(); globalMenu.AddSeparator();
@@ -89,6 +96,8 @@ namespace TweetDuck.Core.Handling{
} }
RemoveSeparatorIfLast(model); RemoveSeparatorIfLast(model);
form.InvokeAsyncSafe(() => form.TriggerAnalyticsEvent(AnalyticsFile.Event.BrowserContextMenu));
} }
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){ public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
@@ -96,8 +105,8 @@ namespace TweetDuck.Core.Handling{
return true; return true;
} }
switch((int)commandId){ switch(commandId){
case (int)CefMenuCommand.Reload: case CefMenuCommand.Reload:
form.InvokeAsyncSafe(form.ReloadToTweetDeck); form.InvokeAsyncSafe(form.ReloadToTweetDeck);
return true; return true;
@@ -118,11 +127,11 @@ namespace TweetDuck.Core.Handling{
return true; return true;
case MenuOpenTweetUrl: case MenuOpenTweetUrl:
BrowserUtils.OpenExternalBrowser(lastHighlightedTweet); OpenBrowser(form, lastHighlightedTweetUrl);
return true; return true;
case MenuCopyTweetUrl: case MenuCopyTweetUrl:
SetClipboardText(lastHighlightedTweet); SetClipboardText(form, lastHighlightedTweetUrl);
return true; return true;
case MenuScreenshotTweet: case MenuScreenshotTweet:
@@ -130,11 +139,15 @@ namespace TweetDuck.Core.Handling{
return true; return true;
case MenuOpenQuotedTweetUrl: case MenuOpenQuotedTweetUrl:
BrowserUtils.OpenExternalBrowser(lastHighlightedQuotedTweet); OpenBrowser(form, lastHighlightedQuoteUrl);
return true; return true;
case MenuCopyQuotedTweetUrl: case MenuCopyQuotedTweetUrl:
SetClipboardText(lastHighlightedQuotedTweet); SetClipboardText(form, lastHighlightedQuoteUrl);
return true;
case MenuInputApplyROT13:
form.InvokeAsyncSafe(form.ApplyROT13);
return true; return true;
} }
@@ -153,6 +166,7 @@ namespace TweetDuck.Core.Handling{
menu.Popup += (sender, args) => { menu.Popup += (sender, args) => {
menu.MenuItems[1].Checked = Program.UserConfig.MuteNotifications; menu.MenuItems[1].Checked = Program.UserConfig.MuteNotifications;
form.TriggerAnalyticsEvent(AnalyticsFile.Event.BrowserContextMenu);
}; };
return menu; return menu;

View File

@@ -0,0 +1,15 @@
using CefSharp;
namespace TweetDuck.Core.Handling{
sealed class ContextMenuGuide : ContextMenuBase{
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
model.Clear();
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
if (HasDevTools){
AddSeparator(model);
AddDebugMenuItems(model);
}
}
}
}

View File

@@ -1,18 +1,20 @@
using CefSharp; using CefSharp;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification; using TweetDuck.Core.Notification;
using TweetDuck.Core.Other.Analytics;
namespace TweetDuck.Core.Handling{ namespace TweetDuck.Core.Handling{
class ContextMenuNotification : ContextMenuBase{ sealed class ContextMenuNotification : ContextMenuBase{
private const int MenuSkipTweet = 26600; private const CefMenuCommand MenuViewDetail = (CefMenuCommand)26600;
private const int MenuFreeze = 26601; private const CefMenuCommand MenuSkipTweet = (CefMenuCommand)26601;
private const int MenuCopyTweetUrl = 26602; private const CefMenuCommand MenuFreeze = (CefMenuCommand)26602;
private const int MenuCopyQuotedTweetUrl = 26603; private const CefMenuCommand MenuCopyTweetUrl = (CefMenuCommand)26603;
private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand)26604;
private readonly FormNotificationBase form; private readonly FormNotificationBase form;
private readonly bool enableCustomMenu; private readonly bool enableCustomMenu;
public ContextMenuNotification(FormNotificationBase form, bool enableCustomMenu) : base(form){ public ContextMenuNotification(FormNotificationBase form, bool enableCustomMenu){
this.form = form; this.form = form;
this.enableCustomMenu = enableCustomMenu; this.enableCustomMenu = enableCustomMenu;
} }
@@ -28,29 +30,35 @@ namespace TweetDuck.Core.Handling{
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model); base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
if (enableCustomMenu){ if (enableCustomMenu){
model.AddItem((CefMenuCommand)MenuSkipTweet, "Skip tweet"); if (form.CanViewDetail){
model.AddCheckItem((CefMenuCommand)MenuFreeze, "Freeze"); model.AddItem(MenuViewDetail, "View detail");
model.SetChecked((CefMenuCommand)MenuFreeze, form.FreezeTimer);
model.AddSeparator();
if (!string.IsNullOrEmpty(form.CurrentTweetUrl)){
model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address");
if (!string.IsNullOrEmpty(form.CurrentQuoteUrl)){
model.AddItem((CefMenuCommand)MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
} }
model.AddItem(MenuSkipTweet, "Skip tweet");
model.AddCheckItem(MenuFreeze, "Freeze");
model.SetChecked(MenuFreeze, form.FreezeTimer);
if (!string.IsNullOrEmpty(form.CurrentTweetUrl)){
model.AddSeparator(); model.AddSeparator();
model.AddItem(MenuCopyTweetUrl, "Copy tweet address");
if (!string.IsNullOrEmpty(form.CurrentQuoteUrl)){
model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
}
} }
} }
if (HasDevTools){ if (HasDevTools){
AddSeparator(model);
AddDebugMenuItems(model); AddDebugMenuItems(model);
} }
RemoveSeparatorIfLast(model); RemoveSeparatorIfLast(model);
form.InvokeAsyncSafe(() => form.ContextMenuOpen = true); form.InvokeAsyncSafe(() => {
form.ContextMenuOpen = true;
form.TriggerAnalyticsEvent(AnalyticsFile.Event.NotificationContextMenu);
});
} }
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){ public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
@@ -58,7 +66,7 @@ namespace TweetDuck.Core.Handling{
return true; return true;
} }
switch((int)commandId){ switch(commandId){
case MenuSkipTweet: case MenuSkipTweet:
form.InvokeAsyncSafe(form.FinishCurrentNotification); form.InvokeAsyncSafe(form.FinishCurrentNotification);
return true; return true;
@@ -67,12 +75,16 @@ namespace TweetDuck.Core.Handling{
form.InvokeAsyncSafe(() => form.FreezeTimer = !form.FreezeTimer); form.InvokeAsyncSafe(() => form.FreezeTimer = !form.FreezeTimer);
return true; return true;
case MenuViewDetail:
form.InvokeSafe(form.ShowTweetDetail);
return true;
case MenuCopyTweetUrl: case MenuCopyTweetUrl:
SetClipboardText(form.CurrentTweetUrl); SetClipboardText(form, form.CurrentTweetUrl);
return true; return true;
case MenuCopyQuotedTweetUrl: case MenuCopyQuotedTweetUrl:
SetClipboardText(form.CurrentQuoteUrl); SetClipboardText(form, form.CurrentQuoteUrl);
return true; return true;
} }

View File

@@ -0,0 +1,26 @@
using System.Collections.Generic;
using CefSharp;
namespace TweetDuck.Core.Handling{
sealed class DragHandlerBrowser : IDragHandler{
public bool OnDragEnter(IWebBrowser browserControl, IBrowser browser, IDragData dragData, DragOperationsMask mask){
void TriggerDragStart(string type, string data = null){
browserControl.ExecuteScriptAsync("window.TDGF_onGlobalDragStart", type, data);
}
if (dragData.IsLink){
TriggerDragStart("link", dragData.LinkUrl);
}
else if (dragData.IsFragment){
TriggerDragStart("text", dragData.FragmentText.Trim());
}
else{
TriggerDragStart("unknown");
}
return false;
}
public void OnDraggableRegionsChanged(IWebBrowser browserControl, IBrowser browser, IList<DraggableRegion> regions){}
}
}

View File

@@ -0,0 +1,26 @@
using System;
using System.Threading.Tasks;
using CefSharp;
namespace TweetDuck.Core.Handling.General{
sealed class BrowserProcessHandler : IBrowserProcessHandler{
public static Task UpdatePrefs(){
return Cef.UIThreadTaskFactory.StartNew(UpdatePrefsInternal);
}
private static void UpdatePrefsInternal(){
using(IRequestContext ctx = Cef.GetGlobalRequestContext()){
ctx.SetPreference("browser.enable_spellchecking", Program.UserConfig.EnableSpellCheck, out string _);
ctx.SetPreference("spellcheck.dictionary", Program.UserConfig.SpellCheckLanguage, out string _);
ctx.SetPreference("settings.a11y.animation_policy", Program.UserConfig.EnableAnimatedImages ? "allowed" : "none", out string _);
}
}
void IBrowserProcessHandler.OnContextInitialized(){
UpdatePrefsInternal();
}
void IBrowserProcessHandler.OnScheduleMessagePumpWork(long delay){}
void IDisposable.Dispose(){}
}
}

View File

@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using CefSharp;
namespace TweetDuck.Core.Handling.General{
sealed class FileDialogHandler : IDialogHandler{
public bool OnFileDialog(IWebBrowser browserControl, IBrowser browser, CefFileDialogMode mode, string title, string defaultFilePath, List<string> acceptFilters, int selectedAcceptFilter, IFileDialogCallback callback){
CefFileDialogMode dialogType = mode & CefFileDialogMode.TypeMask;
if (dialogType == CefFileDialogMode.Open || dialogType == CefFileDialogMode.OpenMultiple){
string allFilters = string.Join(";", acceptFilters.Select(filter => "*"+filter));
using(OpenFileDialog dialog = new OpenFileDialog{
AutoUpgradeEnabled = true,
DereferenceLinks = true,
Multiselect = dialogType == CefFileDialogMode.OpenMultiple,
Title = "Open Files",
Filter = $"All Supported Formats ({allFilters})|{allFilters}|All Files (*.*)|*.*"
}){
if (dialog.ShowDialog() == DialogResult.OK){
string ext = Path.GetExtension(dialog.FileName);
callback.Continue(acceptFilters.FindIndex(filter => filter.Equals(ext, StringComparison.OrdinalIgnoreCase)), dialog.FileNames.ToList());
}
else{
callback.Cancel();
}
callback.Dispose();
}
return true;
}
else{
callback.Dispose();
return false;
}
}
}
}

View File

@@ -0,0 +1,90 @@
using System.Drawing;
using System.Windows.Forms;
using CefSharp;
using CefSharp.WinForms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Other;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Handling.General{
sealed class JavaScriptDialogHandler : IJsDialogHandler{
private static FormMessage CreateMessageForm(string caption, string text){
MessageBoxIcon icon = MessageBoxIcon.None;
int pipe = text.IndexOf('|');
if (pipe != -1){
switch(text.Substring(0, pipe)){
case "error": icon = MessageBoxIcon.Error; break;
case "warning": icon = MessageBoxIcon.Warning; break;
case "info": icon = MessageBoxIcon.Information; break;
case "question": icon = MessageBoxIcon.Question; break;
default: return new FormMessage(caption, text, icon);
}
text = text.Substring(pipe+1);
}
return new FormMessage(caption, text, icon);
}
bool IJsDialogHandler.OnJSDialog(IWebBrowser browserControl, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage){
((ChromiumWebBrowser)browserControl).InvokeSafe(() => {
FormMessage form;
TextBox input = null;
if (dialogType == CefJsDialogType.Alert){
form = CreateMessageForm("Browser Message", messageText);
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
}
else if (dialogType == CefJsDialogType.Confirm){
form = CreateMessageForm("Browser Confirmation", messageText);
form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel);
form.AddButton(FormMessage.Yes, ControlType.Focused);
}
else if (dialogType == CefJsDialogType.Prompt){
form = CreateMessageForm("Browser Prompt", messageText);
form.AddButton(FormMessage.Cancel, DialogResult.Cancel, ControlType.Cancel);
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
float dpiScale = form.GetDPIScale();
int inputPad = form.HasIcon ? 43 : 0;
input = new TextBox{
Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
Location = new Point(BrowserUtils.Scale(22+inputPad, dpiScale), form.ActionPanelY-BrowserUtils.Scale(46, dpiScale)),
Size = new Size(form.ClientSize.Width-BrowserUtils.Scale(44+inputPad, dpiScale), 20)
};
form.Controls.Add(input);
form.ActiveControl = input;
form.Height += input.Size.Height+input.Margin.Vertical;
}
else{
callback.Continue(false);
return;
}
bool success = form.ShowDialog() == DialogResult.OK;
if (input == null){
callback.Continue(success);
}
else{
callback.Continue(success, input.Text);
input.Dispose();
}
form.Dispose();
});
return true;
}
bool IJsDialogHandler.OnJSBeforeUnload(IWebBrowser browserControl, IBrowser browser, string message, bool isReload, IJsDialogCallback callback){
return false;
}
void IJsDialogHandler.OnResetDialogState(IWebBrowser browserControl, IBrowser browser){}
void IJsDialogHandler.OnDialogClosed(IWebBrowser browserControl, IBrowser browser){}
}
}

View File

@@ -1,17 +1,16 @@
using CefSharp; using CefSharp;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Handling{ namespace TweetDuck.Core.Handling.General{
class LifeSpanHandler : ILifeSpanHandler{ sealed class LifeSpanHandler : ILifeSpanHandler{
public bool OnBeforePopup(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser){ public static bool HandleLinkClick(IWebBrowser browserControl, WindowOpenDisposition targetDisposition, string targetUrl){
newBrowser = null;
switch(targetDisposition){ switch(targetDisposition){
case WindowOpenDisposition.NewBackgroundTab: case WindowOpenDisposition.NewBackgroundTab:
case WindowOpenDisposition.NewForegroundTab: case WindowOpenDisposition.NewForegroundTab:
case WindowOpenDisposition.NewPopup: case WindowOpenDisposition.NewPopup:
case WindowOpenDisposition.NewWindow: case WindowOpenDisposition.NewWindow:
BrowserUtils.OpenExternalBrowser(targetUrl); browserControl.AsControl().InvokeAsyncSafe(() => BrowserUtils.OpenExternalBrowser(targetUrl));
return true; return true;
default: default:
@@ -19,6 +18,11 @@ namespace TweetDuck.Core.Handling{
} }
} }
public bool OnBeforePopup(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser){
newBrowser = null;
return HandleLinkClick(browserControl, targetDisposition, targetUrl);
}
public void OnAfterCreated(IWebBrowser browserControl, IBrowser browser){} public void OnAfterCreated(IWebBrowser browserControl, IBrowser browser){}
public bool DoClose(IWebBrowser browserControl, IBrowser browser){ public bool DoClose(IWebBrowser browserControl, IBrowser browser){

View File

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

View File

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

View File

@@ -0,0 +1,46 @@
using CefSharp;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification;
using TweetDuck.Core.Other.Analytics;
namespace TweetDuck.Core.Handling {
sealed class KeyboardHandlerNotification : IKeyboardHandler{
private readonly FormNotificationBase notification;
public KeyboardHandlerNotification(FormNotificationBase notification){
this.notification = notification;
}
private void TriggerKeyboardShortcutAnalytics(){
notification.InvokeAsyncSafe(() => notification.TriggerAnalyticsEvent(AnalyticsFile.Event.NotificationKeyboardShortcut));
}
bool IKeyboardHandler.OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut){
if (type == KeyType.RawKeyDown && !browser.FocusedFrame.Url.StartsWith("chrome-devtools://")){
switch((Keys)windowsKeyCode){
case Keys.Enter:
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
TriggerKeyboardShortcutAnalytics();
return true;
case Keys.Escape:
notification.InvokeAsyncSafe(notification.HideNotification);
TriggerKeyboardShortcutAnalytics();
return true;
case Keys.Space:
notification.InvokeAsyncSafe(() => notification.FreezeTimer = !notification.FreezeTimer);
TriggerKeyboardShortcutAnalytics();
return true;
}
}
return false;
}
bool IKeyboardHandler.OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey){
return false;
}
}
}

View File

@@ -1,68 +0,0 @@
using System.Security.Cryptography.X509Certificates;
using CefSharp;
namespace TweetDuck.Core.Handling{
abstract class RequestHandler : IRequestHandler{
// Browser
public virtual void OnRenderViewReady(IWebBrowser browserControl, IBrowser browser){}
public virtual void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){}
public virtual bool OnBeforeBrowse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, bool isRedirect){
return false;
}
public virtual bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture){
return false;
}
public virtual bool OnProtocolExecution(IWebBrowser browserControl, IBrowser browser, string url){
return false;
}
// Resources
public virtual CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){
return CefReturnValue.Continue;
}
public virtual void OnResourceRedirect(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response, ref string newUrl){}
public virtual bool OnResourceResponse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){
return false;
}
public virtual IResponseFilter GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){
return null;
}
public virtual void OnResourceLoadComplete(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response, UrlRequestStatus status, long receivedContentLength){}
// JavaScript & Plugins
public virtual bool OnQuotaRequest(IWebBrowser browserControl, IBrowser browser, string originUrl, long newSize, IRequestCallback callback){
callback.Dispose();
return false;
}
public virtual void OnPluginCrashed(IWebBrowser browserControl, IBrowser browser, string pluginPath){}
// Auth
public virtual bool GetAuthCredentials(IWebBrowser browserControl, IBrowser browser, IFrame frame, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback){
callback.Dispose();
return false;
}
public virtual bool OnSelectClientCertificate(IWebBrowser browserControl, IBrowser browser, bool isProxy, string host, int port, X509Certificate2Collection certificates, ISelectClientCertificateCallback callback){
callback.Dispose();
return false;
}
public virtual bool OnCertificateError(IWebBrowser browserControl, IBrowser browser, CefErrorCode errorCode, string requestUrl, ISslInfo sslInfo, IRequestCallback callback){
callback.Dispose();
return false;
}
}
}

View File

@@ -0,0 +1,11 @@
using CefSharp;
using CefSharp.Handler;
using TweetDuck.Core.Handling.General;
namespace TweetDuck.Core.Handling{
class RequestHandlerBase : DefaultRequestHandler{
public override bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture){
return LifeSpanHandler.HandleLinkClick(browserControl, targetDisposition, targetUrl);
}
}
}

View File

@@ -1,7 +1,7 @@
using CefSharp; using CefSharp;
namespace TweetDuck.Core.Handling{ namespace TweetDuck.Core.Handling{
class RequestHandlerBrowser : RequestHandler{ sealed class RequestHandlerBrowser : RequestHandlerBase{
public override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){ public override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){
browser.Reload(); browser.Reload();
} }

View File

@@ -4,7 +4,7 @@ using System.IO;
using System.Text; using System.Text;
namespace TweetDuck.Core.Handling{ namespace TweetDuck.Core.Handling{
class ResourceHandlerNotification : IResourceHandler{ sealed class ResourceHandlerNotification : IResourceHandler{
private readonly NameValueCollection headers = new NameValueCollection(0); private readonly NameValueCollection headers = new NameValueCollection(0);
private MemoryStream dataIn; private MemoryStream dataIn;

View File

@@ -0,0 +1,51 @@
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Plugins;
using TweetDuck.Resources;
namespace TweetDuck.Core.Notification.Example{
sealed class FormNotificationExample : FormNotificationMain{
public override bool RequiresResize => true;
protected override bool CanDragWindow => Program.UserConfig.NotificationPosition == TweetNotification.Position.Custom;
protected override FormBorderStyle NotificationBorderStyle{
get{
if (Program.UserConfig.NotificationSize == TweetNotification.Size.Custom){
switch(base.NotificationBorderStyle){
case FormBorderStyle.FixedSingle: return FormBorderStyle.Sizable;
case FormBorderStyle.FixedToolWindow: return FormBorderStyle.SizableToolWindow;
}
}
return base.NotificationBorderStyle;
}
}
private readonly TweetNotification exampleNotification;
public FormNotificationExample(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, false){
string exampleTweetHTML = ScriptLoader.LoadResource("pages/example.html", true);
#if DEBUG
exampleTweetHTML = exampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>");
#endif
exampleNotification = TweetNotification.Example(exampleTweetHTML, 95);
}
public override void HideNotification(){
Location = ControlExtensions.InvisibleLocation;
}
public void ShowExampleNotification(bool reset){
if (reset){
LoadTweet(exampleNotification);
}
else{
PrepareAndDisplayWindow();
}
UpdateTitle();
}
}
}

View File

@@ -4,13 +4,28 @@ using System;
using System.Drawing; using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling; using TweetDuck.Core.Handling;
using TweetDuck.Core.Handling.General;
using TweetDuck.Core.Other.Analytics;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Notification{ namespace TweetDuck.Core.Notification{
partial class FormNotificationBase : Form{ partial class FormNotificationBase : Form{
protected Point PrimaryLocation{ protected static int FontSizeLevel{
get{
switch(TweetDeckBridge.FontSize){
case "largest": return 4;
case "large": return 3;
case "small": return 1;
case "smallest": return 0;
default: return 2;
}
}
}
protected virtual Point PrimaryLocation{
get{ get{
UserConfig config = Program.UserConfig; UserConfig config = Program.UserConfig;
Screen screen; Screen screen;
@@ -51,45 +66,57 @@ namespace TweetDuck.Core.Notification{
} }
public bool IsNotificationVisible => Location != ControlExtensions.InvisibleLocation; public bool IsNotificationVisible => Location != ControlExtensions.InvisibleLocation;
protected virtual bool CanDragWindow => true;
public new Point Location{ public new Point Location{
get => base.Location; get{
return base.Location;
}
set{ set{
Visible = (base.Location = value) != ControlExtensions.InvisibleLocation; Visible = (base.Location = value) != ControlExtensions.InvisibleLocation;
FormBorderStyle = GetBorderStyle(CanResizeWindow); FormBorderStyle = NotificationBorderStyle;
} }
} }
public bool CanResizeWindow{ protected virtual FormBorderStyle NotificationBorderStyle{
get => FormBorderStyle == FormBorderStyle.Sizable || FormBorderStyle == FormBorderStyle.SizableToolWindow; get{
set => FormBorderStyle = GetBorderStyle(value); if (WindowsUtils.ShouldAvoidToolWindow && Visible){ // Visible = workaround for alt+tab
return FormBorderStyle.FixedSingle;
}
else{
return FormBorderStyle.FixedToolWindow;
}
}
} }
public Func<bool> CanMoveWindow { get; set; } = () => true;
protected override bool ShowWithoutActivation => true; protected override bool ShowWithoutActivation => true;
protected double SizeScale => dpiScale*Program.UserConfig.ZoomMultiplier; protected float DpiScale { get; }
protected double SizeScale => DpiScale*Program.UserConfig.ZoomLevel/100.0;
protected readonly Form owner; protected readonly FormBrowser owner;
protected readonly ChromiumWebBrowser browser; protected readonly ChromiumWebBrowser browser;
private readonly ResourceHandlerNotification resourceHandler = new ResourceHandlerNotification(); private readonly ResourceHandlerNotification resourceHandler = new ResourceHandlerNotification();
private readonly float dpiScale;
private string currentColumn; private TweetNotification currentNotification;
private int pauseCounter; private int pauseCounter;
public string CurrentTweetUrl => currentNotification?.TweetUrl;
public string CurrentQuoteUrl => currentNotification?.QuoteUrl;
public bool CanViewDetail => currentNotification != null && !string.IsNullOrEmpty(currentNotification.ColumnId) && !string.IsNullOrEmpty(currentNotification.ChirpId);
public bool IsPaused => pauseCounter > 0; public bool IsPaused => pauseCounter > 0;
protected bool IsCursorOverBrowser => browser.Bounds.Contains(PointToClient(Cursor.Position));
public bool FreezeTimer { get; set; } public bool FreezeTimer { get; set; }
public bool ContextMenuOpen { get; set; } public bool ContextMenuOpen { get; set; }
public string CurrentTweetUrl { get; private set; }
public string CurrentQuoteUrl { get; private set; }
public event EventHandler Initialized; public event EventHandler Initialized;
public FormNotificationBase(Form owner, bool enableContextMenu){ protected FormNotificationBase(FormBrowser owner, bool enableContextMenu){
InitializeComponent(); InitializeComponent();
this.owner = owner; this.owner = owner;
@@ -97,21 +124,22 @@ namespace TweetDuck.Core.Notification{
this.browser = new ChromiumWebBrowser("about:blank"){ this.browser = new ChromiumWebBrowser("about:blank"){
MenuHandler = new ContextMenuNotification(this, enableContextMenu), MenuHandler = new ContextMenuNotification(this, enableContextMenu),
LifeSpanHandler = new LifeSpanHandler() JsDialogHandler = new JavaScriptDialogHandler(),
LifeSpanHandler = new LifeSpanHandler(),
RequestHandler = new RequestHandlerBase()
}; };
this.browser.Dock = DockStyle.None; this.browser.Dock = DockStyle.None;
this.browser.ClientSize = ClientSize; this.browser.ClientSize = ClientSize;
this.browser.IsBrowserInitializedChanged += Browser_IsBrowserInitializedChanged; this.browser.IsBrowserInitializedChanged += browser_IsBrowserInitializedChanged;
#if DEBUG #if DEBUG
this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage; this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
#endif #endif
this.dpiScale = this.GetDPIScale(); DpiScale = this.GetDPIScale();
DefaultResourceHandlerFactory handlerFactory = (DefaultResourceHandlerFactory)browser.ResourceHandlerFactory; browser.GetHandlerFactory().RegisterHandler(TwitterUtils.TweetDeckURL, this.resourceHandler);
handlerFactory.RegisterHandler("https://tweetdeck.twitter.com", this.resourceHandler);
Controls.Add(browser); Controls.Add(browser);
@@ -125,34 +153,37 @@ namespace TweetDuck.Core.Notification{
} }
protected override void WndProc(ref Message m){ protected override void WndProc(ref Message m){
if (m.Msg == 0x0112 && (m.WParam.ToInt32() & 0xFFF0) == 0xF010 && !CanMoveWindow()){ // WM_SYSCOMMAND, SC_MOVE if (m.Msg == 0x0112 && (m.WParam.ToInt32() & 0xFFF0) == 0xF010 && !CanDragWindow){ // WM_SYSCOMMAND, SC_MOVE
return; return;
} }
base.WndProc(ref m); base.WndProc(ref m);
} }
public void TriggerAnalyticsEvent(AnalyticsFile.Event e){
owner.TriggerAnalyticsEvent(e);
}
// event handlers // event handlers
private void owner_FormClosed(object sender, FormClosedEventArgs e){ private void owner_FormClosed(object sender, FormClosedEventArgs e){
Close(); Close();
} }
private void Browser_IsBrowserInitializedChanged(object sender, IsBrowserInitializedChangedEventArgs e){ private void browser_IsBrowserInitializedChanged(object sender, IsBrowserInitializedChangedEventArgs e){
if (e.IsBrowserInitialized){ if (e.IsBrowserInitialized){
Initialized?.Invoke(this, new EventArgs()); Initialized?.Invoke(this, EventArgs.Empty);
} }
} }
// notification methods // notification methods
public virtual void HideNotification(bool loadBlank){ public virtual void HideNotification(){
if (loadBlank){
browser.Load("about:blank"); browser.Load("about:blank");
} DisplayTooltip(null);
Location = ControlExtensions.InvisibleLocation; Location = ControlExtensions.InvisibleLocation;
currentColumn = null; currentNotification = null;
} }
public virtual void FinishCurrentNotification(){} public virtual void FinishCurrentNotification(){}
@@ -170,29 +201,28 @@ namespace TweetDuck.Core.Notification{
} }
protected virtual string GetTweetHTML(TweetNotification tweet){ protected virtual string GetTweetHTML(TweetNotification tweet){
string bodyClasses = browser.Bounds.Contains(PointToClient(Cursor.Position)) ? "td-hover" : string.Empty; return tweet.GenerateHtml(IsCursorOverBrowser ? "td-hover" : string.Empty);
return tweet.GenerateHtml(bodyClasses);
} }
protected virtual void LoadTweet(TweetNotification tweet){ protected virtual void LoadTweet(TweetNotification tweet){
CurrentTweetUrl = tweet.TweetUrl; currentNotification = tweet;
CurrentQuoteUrl = tweet.QuoteUrl;
currentColumn = tweet.Column;
resourceHandler.SetHTML(GetTweetHTML(tweet)); resourceHandler.SetHTML(GetTweetHTML(tweet));
browser.Load("https://tweetdeck.twitter.com");
browser.Load(TwitterUtils.TweetDeckURL);
DisplayTooltip(null);
} }
protected virtual void SetNotificationSize(int width, int height){ protected virtual void SetNotificationSize(int width, int height){
browser.ClientSize = ClientSize = new Size(BrowserUtils.Scale(width, SizeScale), BrowserUtils.Scale(height, SizeScale)); browser.ClientSize = ClientSize = new Size(BrowserUtils.Scale(width, SizeScale), BrowserUtils.Scale(height, SizeScale));
} }
protected virtual void OnNotificationReady(){ protected virtual void UpdateTitle(){
MoveToVisibleLocation(); string title = currentNotification?.ColumnTitle;
Text = string.IsNullOrEmpty(title) || !Program.UserConfig.DisplayNotificationColumn ? Program.BrandName : Program.BrandName+" - "+title;
} }
protected virtual void UpdateTitle(){ public void ShowTweetDetail(){
Text = string.IsNullOrEmpty(currentColumn) || !Program.UserConfig.DisplayNotificationColumn ? Program.BrandName : Program.BrandName+" - "+currentColumn; owner.ShowTweetDetail(currentNotification.ColumnId, currentNotification.ChirpId, currentNotification.TweetUrl);
} }
public void MoveToVisibleLocation(){ public void MoveToVisibleLocation(){
@@ -214,14 +244,5 @@ namespace TweetDuck.Core.Notification{
toolTip.Show(text, this, position); toolTip.Show(text, this, position);
} }
} }
private FormBorderStyle GetBorderStyle(bool sizable){
if (WindowsUtils.ShouldAvoidToolWindow && Visible){ // Visible = workaround for alt+tab
return sizable ? FormBorderStyle.Sizable : FormBorderStyle.FixedSingle;
}
else{
return sizable ? FormBorderStyle.SizableToolWindow : FormBorderStyle.FixedToolWindow;
}
}
} }
} }

View File

@@ -49,7 +49,7 @@
this.progressBarTimer.Margin = new System.Windows.Forms.Padding(0); this.progressBarTimer.Margin = new System.Windows.Forms.Padding(0);
this.progressBarTimer.Maximum = 1000; this.progressBarTimer.Maximum = 1000;
this.progressBarTimer.Name = "progressBarTimer"; this.progressBarTimer.Name = "progressBarTimer";
this.progressBarTimer.Size = new System.Drawing.Size(284, TimerBarHeight); this.progressBarTimer.Size = new System.Drawing.Size(284, 4);
this.progressBarTimer.TabIndex = 1; this.progressBarTimer.TabIndex = 1;
// //
// FormNotification // FormNotification

View File

@@ -4,27 +4,23 @@ using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Bridge; using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling;
using TweetDuck.Core.Other.Analytics;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Plugins; using TweetDuck.Plugins;
using TweetDuck.Plugins.Enums; using TweetDuck.Plugins.Enums;
using TweetDuck.Resources; using TweetDuck.Resources;
namespace TweetDuck.Core.Notification{ namespace TweetDuck.Core.Notification{
partial class FormNotificationMain : FormNotificationBase{ abstract partial class FormNotificationMain : FormNotificationBase{
private const string NotificationScriptFile = "notification.js"; private const string NotificationScriptFile = "notification.js";
private const int TimerBarHeight = 4;
private static readonly string NotificationScriptIdentifier = ScriptLoader.GetRootIdentifier(NotificationScriptFile); private static readonly string NotificationScriptIdentifier = ScriptLoader.GetRootIdentifier(NotificationScriptFile);
private static readonly string PluginScriptIdentifier = ScriptLoader.GetRootIdentifier(PluginManager.PluginNotificationScriptFile); private static readonly string NotificationJS = ScriptLoader.LoadResource(NotificationScriptFile);
private static readonly string NotificationJS, PluginJS;
static FormNotificationMain(){
NotificationJS = ScriptLoader.LoadResource(NotificationScriptFile);
PluginJS = ScriptLoader.LoadResource(PluginManager.PluginNotificationScriptFile);
}
private readonly PluginManager plugins; private readonly PluginManager plugins;
private readonly int timerBarHeight;
protected int timeLeft, totalTime; protected int timeLeft, totalTime;
protected bool pausedDuringNotification; protected bool pausedDuringNotification;
@@ -36,9 +32,9 @@ namespace TweetDuck.Core.Notification{
private bool? prevDisplayTimer; private bool? prevDisplayTimer;
private int? prevFontSize; private int? prevFontSize;
public bool RequiresResize{ public virtual bool RequiresResize{
get{ get{
return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Program.UserConfig.DisplayNotificationTimer || prevFontSize != TweetNotification.FontSizeLevel || CanResizeWindow; return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Program.UserConfig.DisplayNotificationTimer || prevFontSize != FontSizeLevel;
} }
set{ set{
@@ -48,7 +44,7 @@ namespace TweetDuck.Core.Notification{
} }
else{ else{
prevDisplayTimer = Program.UserConfig.DisplayNotificationTimer; prevDisplayTimer = Program.UserConfig.DisplayNotificationTimer;
prevFontSize = TweetNotification.FontSizeLevel; prevFontSize = FontSizeLevel;
} }
} }
} }
@@ -57,7 +53,7 @@ namespace TweetDuck.Core.Notification{
get{ get{
switch(Program.UserConfig.NotificationSize){ switch(Program.UserConfig.NotificationSize){
default: default:
return BrowserUtils.Scale(284, SizeScale*(1.0+0.05*TweetNotification.FontSizeLevel)); return BrowserUtils.Scale(284, SizeScale*(1.0+0.05*FontSizeLevel));
case TweetNotification.Size.Custom: case TweetNotification.Size.Custom:
return Program.UserConfig.CustomNotificationSize.Width; return Program.UserConfig.CustomNotificationSize.Width;
@@ -69,7 +65,7 @@ namespace TweetDuck.Core.Notification{
get{ get{
switch(Program.UserConfig.NotificationSize){ switch(Program.UserConfig.NotificationSize){
default: default:
return BrowserUtils.Scale(118, SizeScale*(1.0+0.075*TweetNotification.FontSizeLevel)); return BrowserUtils.Scale(122, SizeScale*(1.0+0.075*FontSizeLevel));
case TweetNotification.Size.Custom: case TweetNotification.Size.Custom:
return Program.UserConfig.CustomNotificationSize.Height; return Program.UserConfig.CustomNotificationSize.Height;
@@ -77,16 +73,17 @@ namespace TweetDuck.Core.Notification{
} }
} }
public Size BrowserSize{ public Size BrowserSize => Program.UserConfig.DisplayNotificationTimer ? new Size(ClientSize.Width, ClientSize.Height-timerBarHeight) : ClientSize;
get => Program.UserConfig.DisplayNotificationTimer ? new Size(ClientSize.Width, ClientSize.Height-TimerBarHeight) : ClientSize;
}
public FormNotificationMain(FormBrowser owner, PluginManager pluginManager, bool enableContextMenu) : base(owner, enableContextMenu){ protected FormNotificationMain(FormBrowser owner, PluginManager pluginManager, bool enableContextMenu) : base(owner, enableContextMenu){
InitializeComponent(); InitializeComponent();
this.plugins = pluginManager; this.plugins = pluginManager;
this.timerBarHeight = BrowserUtils.Scale(4, DpiScale);
browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(owner, this)); browser.KeyboardHandler = new KeyboardHandlerNotification(this);
browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge.Notification(owner, this));
browser.RegisterAsyncJsObject("$TDP", plugins.Bridge); browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
browser.LoadingStateChanged += Browser_LoadingStateChanged; browser.LoadingStateChanged += Browser_LoadingStateChanged;
@@ -116,8 +113,8 @@ namespace TweetDuck.Core.Notification{
if (nCode == 0){ if (nCode == 0){
int eventType = wParam.ToInt32(); int eventType = wParam.ToInt32();
if (eventType == NativeMethods.WM_MOUSEWHEEL && browser.Bounds.Contains(PointToClient(Cursor.Position))){ if (eventType == NativeMethods.WM_MOUSEWHEEL && IsCursorOverBrowser){
browser.SendMouseWheelEvent(0, 0, 0, BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Program.UserConfig.NotificationScrollSpeed/100.0), CefEventFlags.None); browser.SendMouseWheelEvent(0, 0, 0, BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Program.UserConfig.NotificationScrollSpeed*0.01), CefEventFlags.None);
return NativeMethods.HOOK_HANDLED; return NativeMethods.HOOK_HANDLED;
} }
else if (eventType == NativeMethods.WM_XBUTTONDOWN && DesktopBounds.Contains(Cursor.Position)){ else if (eventType == NativeMethods.WM_XBUTTONDOWN && DesktopBounds.Contains(Cursor.Position)){
@@ -131,6 +128,7 @@ namespace TweetDuck.Core.Notification{
} }
blockXButtonUp = true; blockXButtonUp = true;
this.InvokeAsyncSafe(() => TriggerAnalyticsEvent(AnalyticsFile.Event.NotificationExtraMouseButton));
return NativeMethods.HOOK_HANDLED; return NativeMethods.HOOK_HANDLED;
} }
else if (eventType == NativeMethods.WM_XBUTTONUP && blockXButtonUp){ else if (eventType == NativeMethods.WM_XBUTTONUP && blockXButtonUp){
@@ -151,7 +149,7 @@ namespace TweetDuck.Core.Notification{
private void FormNotification_FormClosing(object sender, FormClosingEventArgs e){ private void FormNotification_FormClosing(object sender, FormClosingEventArgs e){
if (e.CloseReason == CloseReason.UserClosing){ if (e.CloseReason == CloseReason.UserClosing){
HideNotification(true); HideNotification();
e.Cancel = true; e.Cancel = true;
} }
} }
@@ -167,14 +165,9 @@ namespace TweetDuck.Core.Notification{
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){ private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
if (e.Frame.IsMain && NotificationJS != null && browser.Address != "about:blank"){ if (e.Frame.IsMain && NotificationJS != null && browser.Address != "about:blank"){
e.Frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Properties.AllNotification)); e.Frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Notification));
ScriptLoader.ExecuteScript(e.Frame, NotificationJS, NotificationScriptIdentifier); ScriptLoader.ExecuteScript(e.Frame, NotificationJS, NotificationScriptIdentifier);
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Notification);
if (plugins.HasAnyPlugin(PluginEnvironment.Notification)){
ScriptLoader.ExecuteScript(e.Frame, PluginJS, PluginScriptIdentifier);
ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginGlobalScriptFile);
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Notification, false);
}
} }
} }
@@ -184,12 +177,14 @@ namespace TweetDuck.Core.Notification{
} }
private void timerHideProgress_Tick(object sender, EventArgs e){ private void timerHideProgress_Tick(object sender, EventArgs e){
if (Bounds.Contains(Cursor.Position) || FreezeTimer || ContextMenuOpen)return; if (Bounds.Contains(Cursor.Position) || FreezeTimer || ContextMenuOpen){
return;
}
timeLeft -= timerProgress.Interval; timeLeft -= timerProgress.Interval;
int value = BrowserUtils.Scale(1025, (totalTime-timeLeft)/(double)totalTime); int value = BrowserUtils.Scale(progressBarTimer.Maximum+25, (totalTime-timeLeft)/(double)totalTime);
progressBarTimer.SetValueInstant(Math.Min(1000, Math.Max(0, Program.UserConfig.NotificationTimerCountDown ? 1000-value : value))); progressBarTimer.SetValueInstant(Program.UserConfig.NotificationTimerCountDown ? progressBarTimer.Maximum-value : value);
if (timeLeft <= 0){ if (timeLeft <= 0){
FinishCurrentNotification(); FinishCurrentNotification();
@@ -202,21 +197,10 @@ namespace TweetDuck.Core.Notification{
LoadTweet(notification); LoadTweet(notification);
} }
public void ShowNotificationForSettings(bool reset){ public override void HideNotification(){
if (reset){ base.HideNotification();
LoadTweet(TweetNotification.ExampleTweet);
}
else{
PrepareAndDisplayWindow();
}
UpdateTitle(); progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
}
public override void HideNotification(bool loadBlank){
base.HideNotification(loadBlank);
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0;
timerProgress.Stop(); timerProgress.Stop();
totalTime = 0; totalTime = 0;
@@ -259,14 +243,14 @@ namespace TweetDuck.Core.Notification{
protected override void LoadTweet(TweetNotification tweet){ protected override void LoadTweet(TweetNotification tweet){
timerProgress.Stop(); timerProgress.Stop();
totalTime = timeLeft = tweet.GetDisplayDuration(Program.UserConfig.NotificationDurationValue); totalTime = timeLeft = tweet.GetDisplayDuration(Program.UserConfig.NotificationDurationValue);
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0; progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
base.LoadTweet(tweet); base.LoadTweet(tweet);
} }
protected override void SetNotificationSize(int width, int height){ protected override void SetNotificationSize(int width, int height){
if (Program.UserConfig.DisplayNotificationTimer){ if (Program.UserConfig.DisplayNotificationTimer){
ClientSize = new Size(width, height+TimerBarHeight); ClientSize = new Size(width, height+timerBarHeight);
progressBarTimer.Visible = true; progressBarTimer.Visible = true;
} }
else{ else{
@@ -277,7 +261,7 @@ namespace TweetDuck.Core.Notification{
browser.ClientSize = new Size(width, height); browser.ClientSize = new Size(width, height);
} }
private void PrepareAndDisplayWindow(){ protected void PrepareAndDisplayWindow(){
if (RequiresResize){ if (RequiresResize){
RequiresResize = false; RequiresResize = false;
SetNotificationSize(BaseClientWidth, BaseClientHeight); SetNotificationSize(BaseClientWidth, BaseClientHeight);
@@ -287,7 +271,7 @@ namespace TweetDuck.Core.Notification{
StartMouseHook(); StartMouseHook();
} }
protected override void OnNotificationReady(){ protected virtual void OnNotificationReady(){
PrepareAndDisplayWindow(); PrepareAndDisplayWindow();
timerProgress.Start(); timerProgress.Start();
} }

View File

@@ -40,7 +40,6 @@ namespace TweetDuck.Core.Notification {
// //
// FormNotificationTweet // FormNotificationTweet
// //
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormNotificationTweet_FormClosing);
this.ResumeLayout(true); this.ResumeLayout(true);
} }

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using TweetDuck.Plugins; using TweetDuck.Plugins;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Other.Analytics;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Notification{ namespace TweetDuck.Core.Notification{
@@ -10,10 +11,24 @@ namespace TweetDuck.Core.Notification{
private const int NonIntrusiveIdleLimit = 30; private const int NonIntrusiveIdleLimit = 30;
private const int TrimMinimum = 32; private const int TrimMinimum = 32;
protected override Point PrimaryLocation => hasTemporarilyMoved && IsNotificationVisible ? Location : base.PrimaryLocation;
private bool IsCursorOverNotificationArea => new Rectangle(PrimaryLocation, Size).Contains(Cursor.Position); private bool IsCursorOverNotificationArea => new Rectangle(PrimaryLocation, Size).Contains(Cursor.Position);
protected override bool CanDragWindow{
get{
if (ModifierKeys.HasFlag(Keys.Alt)){
hasTemporarilyMoved = true;
return true;
}
else{
return false;
}
}
}
private readonly Queue<TweetNotification> tweetQueue = new Queue<TweetNotification>(4); private readonly Queue<TweetNotification> tweetQueue = new Queue<TweetNotification>(4);
private bool needsTrim; private bool needsTrim;
private bool hasTemporarilyMoved;
public FormNotificationTweet(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, true){ public FormNotificationTweet(FormBrowser owner, PluginManager pluginManager) : base(owner, pluginManager, true){
InitializeComponent(); InitializeComponent();
@@ -26,18 +41,18 @@ namespace TweetDuck.Core.Notification{
} }
} }
private void FormNotificationTweet_FormClosing(object sender, FormClosingEventArgs e){ protected override void WndProc(ref Message m){
if (e.CloseReason == CloseReason.UserClosing){ if (m.Msg == 0x00A7){ // WM_NCMBUTTONDOWN
tweetQueue.Clear(); // already canceled int hitTest = m.WParam.ToInt32();
TrimQueue();
if (hitTest == 2 || hitTest == 20){ // HTCAPTION, HTCLOSE
hasTemporarilyMoved = false;
MoveToVisibleLocation();
return;
} }
} }
private void TrimQueue(){ base.WndProc(ref m);
if (needsTrim){
tweetQueue.TrimExcess();
needsTrim = false;
}
} }
// event handlers // event handlers
@@ -68,11 +83,9 @@ namespace TweetDuck.Core.Notification{
// notification methods // notification methods
public override void ShowNotification(TweetNotification notification){ public override void ShowNotification(TweetNotification notification){
if (IsPaused){
tweetQueue.Enqueue(notification);
}
else{
tweetQueue.Enqueue(notification); tweetQueue.Enqueue(notification);
if (!IsPaused){
UpdateTitle(); UpdateTitle();
if (totalTime == 0){ if (totalTime == 0){
@@ -81,6 +94,19 @@ namespace TweetDuck.Core.Notification{
} }
needsTrim |= tweetQueue.Count >= TrimMinimum; needsTrim |= tweetQueue.Count >= TrimMinimum;
TriggerAnalyticsEvent(AnalyticsFile.Event.DesktopNotification);
}
public override void HideNotification(){
base.HideNotification();
tweetQueue.Clear();
if (needsTrim){
tweetQueue.TrimExcess();
needsTrim = false;
}
hasTemporarilyMoved = false;
} }
public override void FinishCurrentNotification(){ public override void FinishCurrentNotification(){
@@ -88,8 +114,7 @@ namespace TweetDuck.Core.Notification{
LoadNextNotification(); LoadNextNotification();
} }
else{ else{
HideNotification(true); HideNotification();
TrimQueue();
} }
} }

View File

@@ -2,24 +2,42 @@
using System.Drawing; using System.Drawing;
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp;
using TweetDuck.Core.Bridge; using TweetDuck.Core.Bridge;
using TweetDuck.Core.Other;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Plugins;
using TweetDuck.Resources; using TweetDuck.Resources;
namespace TweetDuck.Core.Notification.Screenshot{ namespace TweetDuck.Core.Notification.Screenshot{
sealed class FormNotificationScreenshotable : FormNotificationBase{ sealed class FormNotificationScreenshotable : FormNotificationBase{
public FormNotificationScreenshotable(Action callback, Form owner) : base(owner, false){ protected override bool CanDragWindow => false;
private readonly PluginManager plugins;
public FormNotificationScreenshotable(Action callback, FormBrowser owner, PluginManager pluginManager) : base(owner, false){
this.plugins = pluginManager;
browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new CallbackBridge(this, callback)); browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new CallbackBridge(this, callback));
browser.FrameLoadEnd += (sender, args) => { browser.LoadingStateChanged += (sender, args) => {
if (args.Frame.IsMain && browser.Address != "about:blank"){ if (!args.IsLoading){
ScriptLoader.ExecuteScript(args.Frame, "window.setTimeout($TD_NotificationScreenshot.trigger, 67)", "gen:screenshot"); using(IFrame frame = args.Browser.MainFrame){
ScriptLoader.ExecuteScript(frame, "window.setTimeout($TD_NotificationScreenshot.trigger, document.getElementsByTagName('iframe').length ? 267 : 67)", "gen:screenshot");
}
} }
}; };
} }
protected override string GetTweetHTML(TweetNotification tweet){ protected override string GetTweetHTML(TweetNotification tweet){
return tweet.GenerateHtml(enableCustomCSS: false); string html = tweet.GenerateHtml("td-screenshot", false);
foreach(InjectedHTML injection in plugins.Bridge.NotificationInjections){
html = injection.Inject(html);
}
return html;
} }
public void LoadNotificationForScreenshot(TweetNotification tweet, int width, int height){ public void LoadNotificationForScreenshot(TweetNotification tweet, int width, int height){
@@ -31,7 +49,7 @@ namespace TweetDuck.Core.Notification.Screenshot{
IntPtr context = NativeMethods.GetDC(this.Handle); IntPtr context = NativeMethods.GetDC(this.Handle);
if (context == IntPtr.Zero){ 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); FormMessage.Error("Screenshot Failed", "Could not retrieve a graphics context handle for the notification window to take the screenshot.", FormMessage.OK);
} }
else{ else{
using(Bitmap bmp = new Bitmap(ClientSize.Width, ClientSize.Height, PixelFormat.Format32bppRgb)){ using(Bitmap bmp = new Bitmap(ClientSize.Width, ClientSize.Height, PixelFormat.Format32bppRgb)){

View File

@@ -4,17 +4,20 @@
using System; using System;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Plugins;
namespace TweetDuck.Core.Notification.Screenshot{ namespace TweetDuck.Core.Notification.Screenshot{
sealed class TweetScreenshotManager : IDisposable{ sealed class TweetScreenshotManager : IDisposable{
private readonly Form owner; private readonly FormBrowser owner;
private readonly PluginManager plugins;
private readonly Timer timeout; private readonly Timer timeout;
private readonly Timer disposer; private readonly Timer disposer;
private FormNotificationScreenshotable screenshot; private FormNotificationScreenshotable screenshot;
public TweetScreenshotManager(Form owner){ public TweetScreenshotManager(FormBrowser owner, PluginManager pluginManager){
this.owner = owner; this.owner = owner;
this.plugins = pluginManager;
this.timeout = new Timer{ Interval = 8000 }; this.timeout = new Timer{ Interval = 8000 };
this.timeout.Tick += timeout_Tick; this.timeout.Tick += timeout_Tick;
@@ -25,8 +28,7 @@ namespace TweetDuck.Core.Notification.Screenshot{
private void timeout_Tick(object sender, EventArgs e){ private void timeout_Tick(object sender, EventArgs e){
timeout.Stop(); timeout.Stop();
screenshot.Location = ControlExtensions.InvisibleLocation; OnFinished();
disposer.Start();
} }
private void disposer_Tick(object sender, EventArgs e){ private void disposer_Tick(object sender, EventArgs e){
@@ -40,13 +42,14 @@ namespace TweetDuck.Core.Notification.Screenshot{
return; return;
} }
screenshot = new FormNotificationScreenshotable(Callback, owner){ screenshot = new FormNotificationScreenshotable(Callback, owner, plugins);
CanMoveWindow = () => false screenshot.LoadNotificationForScreenshot(new TweetNotification(string.Empty, string.Empty, string.Empty, html, 0, string.Empty, string.Empty), width, height);
};
screenshot.LoadNotificationForScreenshot(new TweetNotification(string.Empty, html, 0, string.Empty, string.Empty), width, height);
screenshot.Show(); screenshot.Show();
timeout.Start(); timeout.Start();
#if !(DEBUG && NO_HIDE_SCREENSHOTS)
owner.IsWaiting = true;
#endif
} }
private void Callback(){ private void Callback(){
@@ -58,14 +61,19 @@ namespace TweetDuck.Core.Notification.Screenshot{
screenshot.TakeScreenshot(); screenshot.TakeScreenshot();
#if !(DEBUG && NO_HIDE_SCREENSHOTS) #if !(DEBUG && NO_HIDE_SCREENSHOTS)
screenshot.Location = ControlExtensions.InvisibleLocation; OnFinished();
disposer.Start();
#else #else
screenshot.MoveToVisibleLocation(); screenshot.MoveToVisibleLocation();
screenshot.FormClosed += (sender, args) => disposer.Start(); screenshot.FormClosed += (sender, args) => disposer.Start();
#endif #endif
} }
private void OnFinished(){
screenshot.Location = ControlExtensions.InvisibleLocation;
owner.IsWaiting = false;
disposer.Start();
}
public void Dispose(){ public void Dispose(){
timeout.Dispose(); timeout.Dispose();
disposer.Dispose(); disposer.Dispose();

View File

@@ -1,29 +1,53 @@
using System; using System.Drawing;
using TweetLib.Audio; using System.IO;
using TweetLib.Audio.Utils; using System.Windows.Forms;
using CefSharp;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Other;
using TweetDuck.Core.Other.Settings;
namespace TweetDuck.Core.Notification{ namespace TweetDuck.Core.Notification{
sealed class SoundNotification : IDisposable{ static class SoundNotification{
public string SupportedFormats => player.SupportedFormats; public const string SupportedFormats = "*.wav;*.ogg;*.flac;*.opus;*.weba;*.webm"; // TODO add mp3 when supported
public event EventHandler<PlaybackErrorEventArgs> PlaybackError;
private readonly AudioPlayer player; public static IResourceHandler CreateFileHandler(string path){
string mimeType;
public SoundNotification(){ switch(Path.GetExtension(path)){
this.player = AudioPlayer.New(); case ".weba":
this.player.PlaybackError += Player_PlaybackError; case ".webm": mimeType = "audio/webm"; break;
case ".wav": mimeType = "audio/wav"; break;
case ".ogg": mimeType = "audio/ogg"; break;
case ".flac": mimeType = "audio/flac"; break;
case ".opus": mimeType = "audio/ogg; codecs=opus"; break;
case ".mp3": TryShowError("MP3 sound notifications are temporarily unsupported, please convert the file to another format, such as .ogg, .wav, or .flac."); return null;
default: mimeType = null; break;
} }
public void Play(string file){ try{
player.Play(file); return ResourceHandler.FromFilePath(path, mimeType);
}catch{
TryShowError("Could not find custom notification sound file:\n"+path);
return null;
}
} }
private void Player_PlaybackError(object sender, PlaybackErrorEventArgs e){ private static void TryShowError(string message){
PlaybackError?.Invoke(this, e); FormBrowser browser = FormManager.TryFind<FormBrowser>();
}
public void Dispose(){ browser?.InvokeAsyncSafe(() => {
player.Dispose(); using(FormMessage form = new FormMessage("Sound Notification Error", message, MessageBoxIcon.Error)){
form.AddButton(FormMessage.Ignore, ControlType.Cancel | ControlType.Focused);
Button btnViewOptions = form.AddButton("View Options");
btnViewOptions.Width += 16;
btnViewOptions.Location = new Point(btnViewOptions.Location.X-16, btnViewOptions.Location.Y);
if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnViewOptions){
browser.OpenSettings(typeof(TabSettingsSounds));
}
}
});
} }
} }
} }

View File

@@ -1,50 +1,15 @@
using System; using System;
using System.Text; using System.Text;
using TweetDuck.Core.Bridge;
using TweetDuck.Resources; using TweetDuck.Resources;
namespace TweetDuck.Core.Notification{ namespace TweetDuck.Core.Notification{
sealed class TweetNotification{ sealed class TweetNotification{
private static string FontSizeClass { get; set; } private const string DefaultHeadLayout = @"<html id='tduck' class='os-windows txt-size--14' data-td-font='medium' data-td-theme='dark'><head><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 static string HeadTag { get; set; } private static readonly string CustomCSS = ScriptLoader.LoadResource("styles/notification.css");
private const string DefaultFontSizeClass = "medium"; public static TweetNotification Example(string html, int characters){
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>"; return new TweetNotification(string.Empty, string.Empty, "Home", html, characters, string.Empty, string.Empty, true);
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:256px'>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{ public enum Position{
@@ -55,7 +20,10 @@ namespace TweetDuck.Core.Notification{
Auto, Custom Auto, Custom
} }
public string Column { get; } public string ColumnId { get; }
public string ChirpId { get; }
public string ColumnTitle { get; }
public string TweetUrl { get; } public string TweetUrl { get; }
public string QuoteUrl { get; } public string QuoteUrl { get; }
@@ -63,10 +31,13 @@ namespace TweetDuck.Core.Notification{
private readonly int characters; private readonly int characters;
private readonly bool isExample; private readonly bool isExample;
public TweetNotification(string column, string html, int characters, string tweetUrl, string quoteUrl) : this(column, html, characters, tweetUrl, quoteUrl, false){} public TweetNotification(string columnId, string chirpId, string title, string html, int characters, string tweetUrl, string quoteUrl) : this(columnId, chirpId, title, html, characters, tweetUrl, quoteUrl, false){}
private TweetNotification(string column, string html, int characters, string tweetUrl, string quoteUrl, bool isExample){ private TweetNotification(string columnId, string chirpId, string title, string html, int characters, string tweetUrl, string quoteUrl, bool isExample){
this.Column = column; this.ColumnId = columnId;
this.ChirpId = chirpId;
this.ColumnTitle = title;
this.TweetUrl = tweetUrl; this.TweetUrl = tweetUrl;
this.QuoteUrl = quoteUrl; this.QuoteUrl = quoteUrl;
@@ -82,8 +53,7 @@ namespace TweetDuck.Core.Notification{
public string GenerateHtml(string bodyClasses = null, bool enableCustomCSS = true){ public string GenerateHtml(string bodyClasses = null, bool enableCustomCSS = true){
StringBuilder build = new StringBuilder(); StringBuilder build = new StringBuilder();
build.Append("<!DOCTYPE html>"); build.Append("<!DOCTYPE html>");
build.Append("<html class='os-windows txt-base-").Append(FontSizeClass ?? DefaultFontSizeClass).Append("'>"); build.Append(TweetDeckBridge.NotificationHeadLayout ?? DefaultHeadLayout);
build.Append("<head>").Append(HeadTag ?? DefaultHeadTag);
if (enableCustomCSS){ if (enableCustomCSS){
build.Append("<style type='text/css'>").Append(CustomCSS).Append("</style>"); build.Append("<style type='text/css'>").Append(CustomCSS).Append("</style>");
@@ -100,7 +70,7 @@ namespace TweetDuck.Core.Notification{
build.Append(' ').Append(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('\'').Append(isExample ? " td-example-notification" : "").Append("><div class='column' style='width:100%!important;height:auto!important;overflow:initial!important;'>");
build.Append(html); build.Append(html);
build.Append("</div></body>"); build.Append("</div></body>");
build.Append("</html>"); build.Append("</html>");

View File

@@ -0,0 +1,103 @@
using System;
using TweetDuck.Data.Serialization;
namespace TweetDuck.Core.Other.Analytics{
sealed class AnalyticsFile{
private static readonly FileSerializer<AnalyticsFile> Serializer = new FileSerializer<AnalyticsFile>{
HandleUnknownProperties = FileSerializer<AnalyticsFile>.IgnoreProperties("CountGCReloads")
};
static AnalyticsFile(){
Serializer.RegisterTypeConverter(typeof(DateTime), new SingleTypeConverter<DateTime>{
ConvertToString = value => value.ToBinary().ToString(),
ConvertToObject = value => DateTime.FromBinary(long.Parse(value))
});
}
public enum Event{
OpenOptions, OpenPlugins, OpenAbout, OpenGuide,
DesktopNotification, SoundNotification, MuteNotification,
BrowserContextMenu, BrowserExtraMouseButton,
NotificationContextMenu, NotificationExtraMouseButton, NotificationKeyboardShortcut,
TweetScreenshot, TweetDetail, VideoPlay
}
// STATE PROPERTIES
public DateTime LastDataCollection { get; set; } = DateTime.MinValue;
public string LastCollectionVersion { get; set; } = null;
public string LastCollectionMessage { get; set; } = null;
// USAGE DATA
public int CountOpenOptions { get; private set; } = 0;
public int CountOpenPlugins { get; private set; } = 0;
public int CountOpenAbout { get; private set; } = 0;
public int CountOpenGuide { get; private set; } = 0;
public int CountDesktopNotifications { get; private set; } = 0;
public int CountSoundNotifications { get; private set; } = 0;
public int CountMuteNotifications { get; private set; } = 0;
public int CountBrowserContextMenus { get; private set; } = 0;
public int CountBrowserExtraMouseButtons { get; private set; } = 0;
public int CountNotificationContextMenus { get; private set; } = 0;
public int CountNotificationExtraMouseButtons { get; private set; } = 0;
public int CountNotificationKeyboardShortcuts { get; private set; } = 0;
public int CountTweetScreenshots { get; private set; } = 0;
public int CountTweetDetails { get; private set; } = 0;
public int CountVideoPlays { get; private set; } = 0;
// END OF DATA
private readonly string file;
private AnalyticsFile(string file){
this.file = file;
}
public void TriggerEvent(Event e){
switch(e){
case Event.OpenOptions: ++CountOpenOptions; break;
case Event.OpenPlugins: ++CountOpenPlugins; break;
case Event.OpenAbout: ++CountOpenAbout; break;
case Event.OpenGuide: ++CountOpenGuide; break;
case Event.DesktopNotification: ++CountDesktopNotifications; break;
case Event.SoundNotification: ++CountSoundNotifications; break;
case Event.MuteNotification: ++CountMuteNotifications; break;
case Event.BrowserContextMenu: ++CountBrowserContextMenus; break;
case Event.BrowserExtraMouseButton: ++CountBrowserExtraMouseButtons; break;
case Event.NotificationContextMenu: ++CountNotificationContextMenus; break;
case Event.NotificationExtraMouseButton: ++CountNotificationExtraMouseButtons; break;
case Event.NotificationKeyboardShortcut: ++CountNotificationKeyboardShortcuts; break;
case Event.TweetScreenshot: ++CountTweetScreenshots; break;
case Event.TweetDetail: ++CountTweetDetails; break;
case Event.VideoPlay: ++CountVideoPlays; break;
}
}
public void Save(){
try{
Serializer.Write(file, this);
}catch(Exception e){
Program.Reporter.HandleException("Analytics File Error", "Could not save the analytics file.", true, e);
}
}
public static AnalyticsFile Load(string file){
AnalyticsFile config = new AnalyticsFile(file);
try{
Serializer.ReadIfExists(file, config);
}catch(Exception e){
Program.Reporter.HandleException("Analytics File Error", "Could not open the analytics file.", true, e);
}
return config;
}
}
}

View File

@@ -0,0 +1,132 @@
using System;
using System.Net;
using System.Threading.Tasks;
using System.Timers;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils;
using TweetDuck.Plugins;
namespace TweetDuck.Core.Other.Analytics{
sealed class AnalyticsManager : IDisposable{
private static readonly TimeSpan CollectionInterval = TimeSpan.FromDays(7);
private static readonly Uri CollectionUrl = new Uri("https://tweetduck.chylex.com/breadcrumb/report");
public AnalyticsFile File { get; }
private readonly FormBrowser browser;
private readonly PluginManager plugins;
private readonly Timer currentTimer, saveTimer;
public AnalyticsManager(FormBrowser browser, PluginManager plugins, string file){
this.browser = browser;
this.plugins = plugins;
this.File = AnalyticsFile.Load(file);
this.currentTimer = new Timer{ SynchronizingObject = browser };
this.currentTimer.Elapsed += currentTimer_Elapsed;
this.saveTimer = new Timer{ SynchronizingObject = browser, Interval = 60000 };
this.saveTimer.Elapsed += saveTimer_Elapsed;
if (this.File.LastCollectionVersion != Program.VersionTag){
ScheduleReportIn(TimeSpan.FromHours(12), string.Empty);
}
else{
RestartTimer();
}
}
public void Dispose(){
if (saveTimer.Enabled){
File.Save();
}
currentTimer.Dispose();
saveTimer.Dispose();
}
public void TriggerEvent(AnalyticsFile.Event e){
File.TriggerEvent(e);
saveTimer.Enabled = true;
}
private void saveTimer_Elapsed(object sender, ElapsedEventArgs e){
saveTimer.Stop();
File.Save();
}
private void ScheduleReportIn(TimeSpan delay, string message = null){
SetLastDataCollectionTime(DateTime.Now.Subtract(CollectionInterval).Add(delay), message);
}
private void SetLastDataCollectionTime(DateTime dt, string message = null){
File.LastDataCollection = new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0, dt.Kind);
File.LastCollectionVersion = Program.VersionTag;
File.LastCollectionMessage = message ?? dt.ToString("g", Program.Culture);
File.Save();
RestartTimer();
}
private void RestartTimer(){
TimeSpan diff = DateTime.Now.Subtract(File.LastDataCollection);
int minutesTillNext = (int)(CollectionInterval.TotalMinutes-Math.Floor(diff.TotalMinutes));
currentTimer.Interval = Math.Max(minutesTillNext, 1)*60000;
currentTimer.Start();
}
private void currentTimer_Elapsed(object sender, ElapsedEventArgs e){
currentTimer.Stop();
TimeSpan diff = DateTime.Now.Subtract(File.LastDataCollection);
if (Math.Floor(diff.TotalMinutes) >= CollectionInterval.TotalMinutes){
SendReport();
}
else{
RestartTimer();
}
}
private void SendReport(){
AnalyticsReportGenerator.ExternalInfo info = AnalyticsReportGenerator.ExternalInfo.From(browser);
Task.Factory.StartNew(() => {
AnalyticsReport report = AnalyticsReportGenerator.Create(File, info, plugins);
#if DEBUG
System.Diagnostics.Debugger.Break();
#endif
BrowserUtils.CreateWebClient().UploadValues(CollectionUrl, "POST", report.ToNameValueCollection());
}).ContinueWith(task => browser.InvokeAsyncSafe(() => {
if (task.Status == TaskStatus.RanToCompletion){
SetLastDataCollectionTime(DateTime.Now);
}
else if (task.Exception != null){
string message = null;
if (task.Exception.InnerException is WebException e){
switch(e.Status){
case WebExceptionStatus.ConnectFailure:
message = "Connection Error";
break;
case WebExceptionStatus.NameResolutionFailure:
message = "DNS Error";
break;
case WebExceptionStatus.ProtocolError:
HttpWebResponse response = e.Response as HttpWebResponse;
message = "HTTP Error "+(response != null ? $"{(int)response.StatusCode} ({response.StatusDescription})" : "(unknown code)");
break;
}
}
ScheduleReportIn(TimeSpan.FromHours(4), message ?? "Error: "+(task.Exception.InnerException?.Message ?? task.Exception.Message));
}
}));
}
}
}

View File

@@ -0,0 +1,57 @@
using System.Collections;
using System.Collections.Specialized;
using System.Text;
namespace TweetDuck.Core.Other.Analytics{
sealed class AnalyticsReport : IEnumerable{
private OrderedDictionary data = new OrderedDictionary(32);
private int separators;
public void Add(int ignored){ // adding separators to pretty print
data.Add((++separators).ToString(), null);
}
public void Add(string key, string value){
data.Add(key, value);
}
public AnalyticsReport FinalizeReport(){
if (!data.IsReadOnly){
data = data.AsReadOnly();
}
return this;
}
public IEnumerator GetEnumerator(){
return data.GetEnumerator();
}
public NameValueCollection ToNameValueCollection(){
NameValueCollection collection = new NameValueCollection();
foreach(DictionaryEntry entry in data){
if (entry.Value != null){
collection.Add(((string)entry.Key).ToLower().Replace(' ', '_'), (string)entry.Value);
}
}
return collection;
}
public override string ToString(){
StringBuilder build = new StringBuilder();
foreach(DictionaryEntry entry in data){
if (entry.Value == null){
build.AppendLine();
}
else{
build.AppendLine(entry.Key+": "+entry.Value);
}
}
return build.ToString();
}
}
}

View File

@@ -0,0 +1,304 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using Microsoft.Win32;
using TweetDuck.Configuration;
using System.Linq;
using System.Management;
using System.Text.RegularExpressions;
using TweetDuck.Core.Handling;
using TweetDuck.Core.Notification;
using TweetDuck.Core.Utils;
using TweetDuck.Plugins;
namespace TweetDuck.Core.Other.Analytics{
static class AnalyticsReportGenerator{
public static AnalyticsReport Create(AnalyticsFile file, ExternalInfo info, PluginManager plugins){
Dictionary<string, string> editLayoutDesign = EditLayoutDesignPluginData;
return new AnalyticsReport{
{ "App Version" , Program.VersionTag },
{ "App Type" , Program.IsPortable ? "portable" : "installed" },
{ "App Dev Tools" , Bool(ContextMenuBase.HasDevTools) },
0,
{ "System Name" , SystemName },
{ "System Edition" , SystemEdition },
{ "System Environment" , Environment.Is64BitOperatingSystem ? "64-bit" : "32-bit" },
{ "System Build" , SystemBuild },
{ "System Locale" , Program.Culture.Name.ToLower() },
0,
{ "RAM" , Exact(RamSize) },
{ "GPU" , GpuVendor },
0,
{ "Screen Count" , Exact(Screen.AllScreens.Length) },
{ "Screen Resolution" , info.Resolution ?? "(unknown)" },
{ "Screen DPI" , info.DPI != null ? Exact(info.DPI.Value) : "(unknown)" },
0,
{ "Hardware Acceleration" , Bool(SysConfig.HardwareAcceleration) },
0,
{ "Expand Links" , Bool(UserConfig.ExpandLinksOnHover) },
{ "Switch Account Selectors" , Bool(UserConfig.SwitchAccountSelectors) },
{ "Search In First Column" , Bool(UserConfig.OpenSearchInFirstColumn) },
{ "Keep Like Follow Dialogs Open" , Bool(UserConfig.KeepLikeFollowDialogsOpen) },
{ "Best Image Quality" , Bool(UserConfig.BestImageQuality) },
{ "Spell Check" , Bool(UserConfig.EnableSpellCheck) },
{ "Zoom" , Exact(UserConfig.ZoomLevel) },
0,
{ "Updates" , Bool(UserConfig.EnableUpdateCheck) },
{ "Update Dismissed" , Bool(!string.IsNullOrEmpty(UserConfig.DismissedUpdate)) },
0,
{ "Tray" , TrayMode },
{ "Tray Highlight" , Bool(UserConfig.EnableTrayHighlight) },
0,
{ "Notification Position" , NotificationPosition },
{ "Notification Size" , NotificationSize },
{ "Notification Timer" , NotificationTimer },
{ "Notification Timer Speed" , RoundUp(UserConfig.NotificationDurationValue, 5) },
{ "Notification Scroll Speed" , Exact(UserConfig.NotificationScrollSpeed) },
{ "Notification Column Title" , Bool(UserConfig.DisplayNotificationColumn) },
{ "Notification Media Previews" , Bool(UserConfig.NotificationMediaPreviews) },
{ "Notification Link Skip" , Bool(UserConfig.NotificationSkipOnLinkClick) },
{ "Notification Non-Intrusive" , Bool(UserConfig.NotificationNonIntrusiveMode) },
{ "Notification Idle Pause" , Exact(UserConfig.NotificationIdlePauseSeconds) },
{ "Custom Sound Notification" , string.IsNullOrEmpty(UserConfig.NotificationSoundPath) ? "off" : Path.GetExtension(UserConfig.NotificationSoundPath) },
{ "Custom Sound Notification Volume" , RoundUp(UserConfig.NotificationSoundVolume, 5) },
0,
{ "Program Arguments" , List(ProgramArguments) },
{ "Custom CEF Arguments" , RoundUp((UserConfig.CustomCefArgs ?? string.Empty).Length, 10) },
{ "Custom Browser CSS" , RoundUp((UserConfig.CustomBrowserCSS ?? string.Empty).Length, 50) },
{ "Custom Notification CSS" , RoundUp((UserConfig.CustomNotificationCSS ?? string.Empty).Length, 50) },
0,
{ "Plugins All" , List(plugins.Plugins.Select(plugin => plugin.Identifier)) },
{ "Plugins Enabled" , List(plugins.Plugins.Where(plugin => plugins.Config.IsEnabled(plugin)).Select(plugin => plugin.Identifier)) },
0,
{ "Theme" , Dict(editLayoutDesign, "_theme", "light/def") },
{ "Column Width" , Dict(editLayoutDesign, "columnWidth", "310px/def") },
{ "Font Size" , Dict(editLayoutDesign, "fontSize", "12px/def") },
{ "Large Quote Font" , Dict(editLayoutDesign, "increaseQuoteTextSize", "false/def") },
{ "Small Compose Font" , Dict(editLayoutDesign, "smallComposeTextSize", "false/def") },
{ "Avatar Radius" , Dict(editLayoutDesign, "avatarRadius", "2/def") },
{ "Hide Tweet Actions" , Dict(editLayoutDesign, "hideTweetActions", "true/def") },
{ "Move Tweet Actions" , Dict(editLayoutDesign, "moveTweetActionsToRight", "true/def") },
{ "Theme Color Tweaks" , Dict(editLayoutDesign, "themeColorTweaks", "true/def") },
{ "Revert Icons" , Dict(editLayoutDesign, "revertIcons", "true/def") },
{ "Optimize Animations" , Dict(editLayoutDesign, "optimizeAnimations", "true/def") },
{ "Reply Account Mode" , ReplyAccountConfigFromPlugin },
{ "Template Count" , Exact(TemplateCountFromPlugin) },
0,
{ "Opened Options" , LogRound(file.CountOpenOptions, 4) },
{ "Opened Plugins" , LogRound(file.CountOpenPlugins, 4) },
{ "Opened About" , LogRound(file.CountOpenAbout, 4) },
{ "Opened Guide" , LogRound(file.CountOpenGuide, 4) },
{ "Desktop Notifications" , LogRound(file.CountDesktopNotifications, 5) },
{ "Sound Notifications" , LogRound(file.CountSoundNotifications, 5) },
{ "Mute Notifications" , LogRound(file.CountMuteNotifications, 2) },
{ "Browser Context Menus" , LogRound(file.CountBrowserContextMenus, 2) },
{ "Browser Extra Mouse Buttons" , LogRound(file.CountBrowserExtraMouseButtons, 2) },
{ "Notification Context Menus" , LogRound(file.CountNotificationContextMenus, 2) },
{ "Notification Extra Mouse Buttons" , LogRound(file.CountNotificationExtraMouseButtons, 2) },
{ "Notification Keyboard Shortcuts" , LogRound(file.CountNotificationKeyboardShortcuts, 2) },
{ "Tweet Screenshots" , LogRound(file.CountTweetScreenshots, 2) },
{ "Tweet Details" , LogRound(file.CountTweetDetails, 2) },
{ "Video Plays" , LogRound(file.CountVideoPlays, 4) }
}.FinalizeReport();
}
private static UserConfig UserConfig => Program.UserConfig;
private static SystemConfig SysConfig => Program.SystemConfig;
private static string Bool(bool value) => value ? "on" : "off";
private static string Exact(int value) => value.ToString();
private static string RoundUp(int value, int multiple) => (multiple*(int)Math.Ceiling((double)value/multiple)).ToString();
private static string LogRound(int value, int logBase) => (value <= 0 ? 0 : (int)Math.Pow(logBase, Math.Floor(Math.Log(value, logBase)))).ToString();
private static string Dict(Dictionary<string, string> dict, string key, string def = "(unknown)") => dict.TryGetValue(key, out string value) ? value : def;
private static string List(IEnumerable<string> list) => string.Join("|", list.DefaultIfEmpty("(none)"));
private static string SystemName { get; }
private static string SystemEdition { get; }
private static string SystemBuild { get; }
private static int RamSize { get; }
private static string GpuVendor { get; }
private static string[] ProgramArguments { get; }
static AnalyticsReportGenerator(){
string osName, osEdition, osBuild;
try{
using(RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", false)){
// ReSharper disable once PossibleNullReferenceException
osName = key.GetValue("ProductName") as string;
osBuild = key.GetValue("CurrentBuild") as string;
osEdition = null;
if (osName != null){
Match match = Regex.Match(osName, @"^(.*?\d+(?:\.\d+)?) (.*)$");
if (match.Success){
osName = match.Groups[1].Value;
osEdition = match.Groups[2].Value;
}
}
}
}catch{
osName = osEdition = osBuild = null;
}
SystemName = osName ?? "Windows (unknown)";
SystemEdition = osEdition ?? "(unknown)";
SystemBuild = osBuild ?? "(unknown)";
try{
using(ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Capacity FROM Win32_PhysicalMemory")){
foreach(ManagementBaseObject obj in searcher.Get()){
RamSize += (int)((ulong)obj["Capacity"]/(1024L*1024L));
}
}
}catch{
RamSize = 0;
}
string gpu = null;
try{
using(ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Caption FROM Win32_VideoController")){
foreach(ManagementBaseObject obj in searcher.Get()){
string vendor = obj["Caption"] as string;
if (!string.IsNullOrEmpty(vendor)){
gpu = vendor;
}
}
}
}catch{
// rip
}
GpuVendor = gpu ?? "(unknown)";
Dictionary<string, string> args = new Dictionary<string, string>();
Arguments.GetCurrentClean().ToDictionary(args);
ProgramArguments = args.Keys.Select(key => key.TrimStart('-')).ToArray();
}
private static string TrayMode{
get{
switch(UserConfig.TrayBehavior){
case TrayIcon.Behavior.DisplayOnly: return "icon";
case TrayIcon.Behavior.MinimizeToTray: return "minimize";
case TrayIcon.Behavior.CloseToTray: return "close";
case TrayIcon.Behavior.Combined: return "combined";
default: return "off";
}
}
}
private static string NotificationPosition{
get{
switch(UserConfig.NotificationPosition){
case TweetNotification.Position.TopLeft: return "top left";
case TweetNotification.Position.TopRight: return "top right";
case TweetNotification.Position.BottomLeft: return "bottom left";
case TweetNotification.Position.BottomRight: return "bottom right";
default: return "custom";
}
}
}
private static string NotificationSize{
get{
switch(UserConfig.NotificationSize){
case TweetNotification.Size.Auto: return "auto";
default: return RoundUp(UserConfig.CustomNotificationSize.Width, 20)+"x"+RoundUp(UserConfig.CustomNotificationSize.Height, 20);
}
}
}
private static string NotificationTimer{
get{
if (!UserConfig.DisplayNotificationTimer){
return "off";
}
else{
return UserConfig.NotificationTimerCountDown ? "count down" : "count up";
}
}
}
private static Dictionary<string, string> EditLayoutDesignPluginData{
get{
Dictionary<string, string> dict = new Dictionary<string, string>();
try{
string data = File.ReadAllText(Path.Combine(Program.PluginDataPath, "official", "edit-design", "config.json"));
foreach(Match match in Regex.Matches(data, "\"(\\w+?)\":(.*?)[,}]")){
dict[match.Groups[1].Value] = match.Groups[2].Value.Trim('"');
}
}catch{
// rip
}
return dict;
}
}
private static int TemplateCountFromPlugin{
get{
try{
string data = File.ReadAllText(Path.Combine(Program.PluginDataPath, "official", "templates", "config.json"));
return Math.Min(StringUtils.CountOccurrences(data, "{\"name\":"), StringUtils.CountOccurrences(data, ",\"contents\":"));
}catch{
return 0;
}
}
}
private static string ReplyAccountConfigFromPlugin{
get{
try{
string data = File.ReadAllText(Path.Combine(Program.PluginDataPath, "official", "reply-account", "configuration.js")).Replace(" ", "");
Match matchType = Regex.Match(data, "defaultAccount:\"([#@])(.*?)\"(?:,|$)");
Match matchAdvanced = Regex.Match(data, "useAdvancedSelector:(.*?)(?:,|$)", RegexOptions.Multiline);
if (!matchType.Success){
return data.Contains("defaultAccount:\"\"") ? "(legacy)" : "(unknown)";
}
string accType = matchType.Groups[1].Value == "#" ? matchType.Groups[2].Value : "account";
return matchAdvanced.Success && !matchAdvanced.Value.Contains("false") ? "advanced/"+accType : accType;
}catch{
return "(unknown)";
}
}
}
public class ExternalInfo{
public static ExternalInfo From(Form form){
if (form == null){
return new ExternalInfo();
}
else{
Screen screen = Screen.FromControl(form); // works on multi-monitor setups even in tray
int dpi;
using(Graphics graphics = form.CreateGraphics()){
dpi = (int)graphics.DpiY;
}
return new ExternalInfo{
Resolution = screen.Bounds.Width+"x"+screen.Bounds.Height,
DPI = dpi
};
}
}
public string Resolution { get; private set; }
public int? DPI { get; private set; }
private ExternalInfo(){}
}
}
}

View File

@@ -131,11 +131,14 @@ namespace TweetDuck.Core.Other {
this.Controls.Add(this.labelDescription); this.Controls.Add(this.labelDescription);
this.Controls.Add(this.pictureLogo); this.Controls.Add(this.pictureLogo);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.HelpButton = true;
this.MaximizeBox = false; this.MaximizeBox = false;
this.MinimizeBox = false; this.MinimizeBox = false;
this.Name = "FormAbout"; this.Name = "FormAbout";
this.ShowIcon = false; this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.HelpButtonClicked += new System.ComponentModel.CancelEventHandler(this.FormAbout_HelpButtonClicked);
this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.FormAbout_HelpRequested);
((System.ComponentModel.ISupportInitialize)(this.pictureLogo)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.pictureLogo)).EndInit();
this.tablePanelLinks.ResumeLayout(false); this.tablePanelLinks.ResumeLayout(false);
this.tablePanelLinks.PerformLayout(); this.tablePanelLinks.PerformLayout();

View File

@@ -1,4 +1,5 @@
using System.Windows.Forms; using System.ComponentModel;
using System.Windows.Forms;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Other{ namespace TweetDuck.Core.Other{
@@ -11,7 +12,7 @@ namespace TweetDuck.Core.Other{
Text = "About "+Program.BrandName+" "+Program.VersionTag; Text = "About "+Program.BrandName+" "+Program.VersionTag;
labelDescription.Text = Program.BrandName+" was created by chylex as a replacement to the discontinued official TweetDeck client for Windows.\n\nThe program is available for free under the open source MIT license."; labelDescription.Text = "TweetDuck was created by chylex as a replacement to the discontinued official TweetDeck client for Windows.\n\nThe program is available for free under the open source MIT license.";
labelWebsite.Links.Add(new LinkLabel.Link(0, labelWebsite.Text.Length, Program.Website)); labelWebsite.Links.Add(new LinkLabel.Link(0, labelWebsite.Text.Length, Program.Website));
labelTips.Links.Add(new LinkLabel.Link(0, labelTips.Text.Length, TipsLink)); labelTips.Links.Add(new LinkLabel.Link(0, labelTips.Text.Length, TipsLink));
@@ -19,7 +20,21 @@ namespace TweetDuck.Core.Other{
} }
private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e){ private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e){
BrowserUtils.OpenExternalBrowserUnsafe(e.Link.LinkData as string); BrowserUtils.OpenExternalBrowser(e.Link.LinkData as string);
}
private void FormAbout_HelpRequested(object sender, HelpEventArgs hlpevent){
ShowGuide();
}
private void FormAbout_HelpButtonClicked(object sender, CancelEventArgs e){
e.Cancel = true;
ShowGuide();
}
private void ShowGuide(){
FormGuide.Show();
Close();
} }
} }
} }

44
Core/Other/FormGuide.Designer.cs generated Normal file
View File

@@ -0,0 +1,44 @@
namespace TweetDuck.Core.Other {
partial class FormGuide {
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.SuspendLayout();
//
// FormGuide
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(34)))), ((int)(((byte)(34)))), ((int)(((byte)(34)))));
this.ClientSize = new System.Drawing.Size(424, 282);
this.Icon = global::TweetDuck.Properties.Resources.icon;
this.MinimumSize = new System.Drawing.Size(440, 320);
this.Name = "FormGuide";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.ResumeLayout(false);
}
#endregion
}
}

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

@@ -0,0 +1,124 @@
using System;
using System.Drawing;
using System.Windows.Forms;
using CefSharp;
using CefSharp.WinForms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling;
using TweetDuck.Core.Handling.General;
using TweetDuck.Core.Other.Analytics;
using TweetDuck.Core.Utils;
using System.Text.RegularExpressions;
using TweetDuck.Resources;
namespace TweetDuck.Core.Other{
sealed partial class FormGuide : Form{
private const string GuideUrl = "https://tweetduck.chylex.com/guide/v2/";
private const string GuidePathRegex = @"^guide(?:/v\d+)?(?:/(#.*))?";
public static bool CheckGuideUrl(string url, out string hash){
if (!url.Contains("//tweetduck.chylex.com/guide")){
hash = null;
return false;
}
string path = url.Substring(url.IndexOf("/guide")+1);
Match match = Regex.Match(path, GuidePathRegex);
if (match.Success){
hash = match.Groups[1].Value;
return true;
}
else{
hash = null;
return false;
}
}
public static void Show(string hash = null){
string url = GuideUrl+(hash ?? string.Empty);
FormGuide guide = FormManager.TryFind<FormGuide>();
if (guide == null){
FormBrowser owner = FormManager.TryFind<FormBrowser>();
if (owner != null){
owner.TriggerAnalyticsEvent(AnalyticsFile.Event.OpenGuide);
new FormGuide(url, owner).Show(owner);
}
}
else{
guide.Reload(url);
guide.Activate();
}
}
private readonly ChromiumWebBrowser browser;
private FormGuide(string url, Form owner){
InitializeComponent();
Text = Program.BrandName+" Guide";
if (owner != null){
Size = new Size(owner.Size.Width*3/4, owner.Size.Height*3/4);
VisibleChanged += (sender, args) => this.MoveToCenter(owner);
}
this.browser = new ChromiumWebBrowser(url){
MenuHandler = new ContextMenuGuide(),
JsDialogHandler = new JavaScriptDialogHandler(),
LifeSpanHandler = new LifeSpanHandler(),
RequestHandler = new RequestHandlerBrowser()
};
browser.LoadingStateChanged += browser_LoadingStateChanged;
browser.FrameLoadStart += browser_FrameLoadStart;
browser.FrameLoadEnd += browser_FrameLoadEnd;
browser.BrowserSettings.BackgroundColor = (uint)BackColor.ToArgb();
browser.Dock = DockStyle.None;
browser.Location = ControlExtensions.InvisibleLocation;
Controls.Add(browser);
Disposed += (sender, args) => {
Program.UserConfig.ZoomLevelChanged -= Config_ZoomLevelChanged;
browser.Dispose();
};
Program.UserConfig.ZoomLevelChanged += Config_ZoomLevelChanged;
}
private void Reload(string url){
browser.LoadingStateChanged += browser_LoadingStateChanged;
browser.Dock = DockStyle.None;
browser.Location = ControlExtensions.InvisibleLocation;
browser.Load("about:blank");
browser.Load(url);
}
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
if (!e.IsLoading && browser.Address != "about:blank"){
this.InvokeAsyncSafe(() => {
browser.Location = Point.Empty;
browser.Dock = DockStyle.Fill;
});
browser.LoadingStateChanged -= browser_LoadingStateChanged;
}
}
private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Program.UserConfig.ZoomLevel);
}
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
// idiot chromium
ScriptLoader.ExecuteScript(e.Frame, "Array.prototype.forEach.call(document.getElementsByTagName('A'), ele => ele.addEventListener('click', e => { e.preventDefault(); window.open(ele.getAttribute('href')); }))", "gen:links");
}
private void Config_ZoomLevelChanged(object sender, EventArgs e){
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Program.UserConfig.ZoomLevel);
}
}
}

View File

@@ -5,9 +5,62 @@ using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Other{ namespace TweetDuck.Core.Other{
[Flags]
enum ControlType{
None = 0,
Accept = 1, // triggered by pressing enter when a non-button is focused
Cancel = 2, // triggered by closing the dialog without pressing a button
Focused = 4 // active control after the dialog is showed
}
sealed partial class FormMessage : Form{ sealed partial class FormMessage : Form{
public const string OK = "OK";
public const string Yes = "Yes";
public const string No = "No";
public const string Cancel = "Cancel";
public const string Retry = "Retry";
public const string Ignore = "Ignore";
public const string Exit = "Exit";
public static bool Information(string caption, string text, string buttonAccept, string buttonCancel = null){
return Show(caption, text, MessageBoxIcon.Information, buttonAccept, buttonCancel);
}
public static bool Warning(string caption, string text, string buttonAccept, string buttonCancel = null){
return Show(caption, text, MessageBoxIcon.Warning, buttonAccept, buttonCancel);
}
public static bool Error(string caption, string text, string buttonAccept, string buttonCancel = null){
return Show(caption, text, MessageBoxIcon.Error, buttonAccept, buttonCancel);
}
public static bool Question(string caption, string text, string buttonAccept, string buttonCancel = null){
return Show(caption, text, MessageBoxIcon.Question, buttonAccept, buttonCancel);
}
public static bool Show(string caption, string text, MessageBoxIcon icon, string button){
return Show(caption, text, icon, button, null);
}
public static bool Show(string caption, string text, MessageBoxIcon icon, string buttonAccept, string buttonCancel){
using(FormMessage message = new FormMessage(caption, text, icon)){
if (buttonCancel == null){
message.AddButton(buttonAccept, DialogResult.OK, ControlType.Cancel | ControlType.Focused);
}
else{
message.AddButton(buttonCancel, DialogResult.Cancel, ControlType.Cancel);
message.AddButton(buttonAccept, DialogResult.OK, ControlType.Accept | ControlType.Focused);
}
return message.ShowDialog() == DialogResult.OK;
}
}
// Instance
public Button ClickedButton { get; private set; } public Button ClickedButton { get; private set; }
public bool HasIcon => icon != null;
public int ActionPanelY => panelActions.Location.Y; public int ActionPanelY => panelActions.Location.Y;
private int ClientWidth{ private int ClientWidth{
@@ -35,7 +88,7 @@ namespace TweetDuck.Core.Other{
this.prevLabelWidth = labelMessage.Width; this.prevLabelWidth = labelMessage.Width;
this.prevLabelHeight = labelMessage.Height; this.prevLabelHeight = labelMessage.Height;
this.minFormWidth = BrowserUtils.Scale(40, dpiScale); this.minFormWidth = BrowserUtils.Scale(42, dpiScale);
switch(messageIcon){ switch(messageIcon){
case MessageBoxIcon.Information: case MessageBoxIcon.Information:
@@ -56,21 +109,25 @@ namespace TweetDuck.Core.Other{
default: default:
icon = null; icon = null;
labelMessage.Location = new Point(labelMessage.Location.X-38, labelMessage.Location.Y); labelMessage.Location = new Point(BrowserUtils.Scale(19, dpiScale), labelMessage.Location.Y); // 19 instead of 9 due to larger height
break; break;
} }
this.isReady = true; this.isReady = true;
this.Text = caption; this.Text = caption;
this.labelMessage.Text = text; this.labelMessage.Text = text.Replace("\r", "").Replace("\n", Environment.NewLine);
} }
private void FormMessage_SizeChanged(object sender, EventArgs e){ private void FormMessage_SizeChanged(object sender, EventArgs e){
RecalculateButtonLocation(); RecalculateButtonLocation();
} }
public Button AddButton(string title, DialogResult result = DialogResult.OK){ public Button AddButton(string title, ControlType type){
return AddButton(title, DialogResult.OK, type);
}
public Button AddButton(string title, DialogResult result = DialogResult.OK, ControlType type = ControlType.None){
Button button = new Button{ Button button = new Button{
Anchor = AnchorStyles.Bottom, Anchor = AnchorStyles.Bottom,
Font = SystemFonts.MessageBoxFont, Font = SystemFonts.MessageBoxFont,
@@ -94,6 +151,18 @@ namespace TweetDuck.Core.Other{
ClientWidth = Math.Max(realFormWidth, minFormWidth); ClientWidth = Math.Max(realFormWidth, minFormWidth);
RecalculateButtonLocation(); RecalculateButtonLocation();
if (type.HasFlag(ControlType.Accept)){
AcceptButton = button;
}
if (type.HasFlag(ControlType.Cancel)){
CancelButton = button;
}
if (type.HasFlag(ControlType.Focused)){
ActiveControl = button;
}
return button; return button;
} }
@@ -108,7 +177,7 @@ namespace TweetDuck.Core.Other{
private void RecalculateButtonLocation(){ private void RecalculateButtonLocation(){
int dist = ButtonDistance; int dist = ButtonDistance;
int start = ClientWidth-dist-BrowserUtils.Scale(1, dpiScale); int start = ClientWidth-dist;
for(int index = 0; index < buttonCount; index++){ for(int index = 0; index < buttonCount; index++){
Control control = panelActions.Controls[index]; Control control = panelActions.Controls[index];
@@ -133,7 +202,7 @@ namespace TweetDuck.Core.Other{
prevLabelHeight -= labelOffset; prevLabelHeight -= labelOffset;
} }
realFormWidth = ClientWidth-(icon == null ? 50 : 0)+labelMessage.Width-prevLabelWidth; realFormWidth = ClientWidth-(icon == null ? BrowserUtils.Scale(50, dpiScale) : 0)+labelMessage.Width-prevLabelWidth;
ClientWidth = Math.Max(realFormWidth, minFormWidth); ClientWidth = Math.Max(realFormWidth, minFormWidth);
Height += labelMessage.Height-prevLabelHeight; Height += labelMessage.Height-prevLabelHeight;
@@ -144,7 +213,7 @@ namespace TweetDuck.Core.Other{
protected override void OnPaint(PaintEventArgs e){ protected override void OnPaint(PaintEventArgs e){
if (icon != null){ if (icon != null){
e.Graphics.DrawIcon(icon, 25, 26); e.Graphics.DrawIcon(icon, BrowserUtils.Scale(25, dpiScale), 1+BrowserUtils.Scale(25, dpiScale));
} }
base.OnPaint(e); base.OnPaint(e);

View File

@@ -35,8 +35,8 @@ namespace TweetDuck.Core.Other{
} }
private void ReloadPluginList(){ private void ReloadPluginList(){
flowLayoutPlugins.SuspendLayout();
flowLayoutPlugins.Controls.Clear(); flowLayoutPlugins.Controls.Clear();
flowLayoutPlugins.SuspendLayout();
foreach(Plugin plugin in pluginManager.Plugins.OrderBy(GetPluginOrderIndex).ThenBy(plugin => plugin.Name)){ foreach(Plugin plugin in pluginManager.Plugins.OrderBy(GetPluginOrderIndex).ThenBy(plugin => plugin.Name)){
flowLayoutPlugins.Controls.Add(new PluginControl(pluginManager, plugin)); flowLayoutPlugins.Controls.Add(new PluginControl(pluginManager, plugin));
@@ -80,7 +80,7 @@ namespace TweetDuck.Core.Other{
} }
private void btnReload_Click(object sender, EventArgs e){ private void btnReload_Click(object sender, EventArgs e){
if (MessageBox.Show("This will also reload the browser window. Do you want to proceed?", "Reloading Plugins", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes){ if (FormMessage.Warning("Reloading Plugins", "This will also reload the browser window. Do you want to proceed?", FormMessage.Yes, FormMessage.No)){
pluginManager.Reload(); pluginManager.Reload();
ReloadPluginList(); ReloadPluginList();
} }

View File

@@ -3,6 +3,9 @@ using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling.General;
using TweetDuck.Core.Notification.Example;
using TweetDuck.Core.Other.Analytics;
using TweetDuck.Core.Other.Settings; using TweetDuck.Core.Other.Settings;
using TweetDuck.Core.Other.Settings.Dialogs; using TweetDuck.Core.Other.Settings.Dialogs;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
@@ -19,7 +22,9 @@ namespace TweetDuck.Core.Other{
private readonly Dictionary<Type, SettingsTab> tabs = new Dictionary<Type, SettingsTab>(4); private readonly Dictionary<Type, SettingsTab> tabs = new Dictionary<Type, SettingsTab>(4);
private SettingsTab currentTab; private SettingsTab currentTab;
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, Type startTab){ public bool ShouldReloadBrowser { get; private set; }
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, AnalyticsManager analytics, Type startTab){
InitializeComponent(); InitializeComponent();
Text = Program.BrandName+" Options"; Text = Program.BrandName+" Options";
@@ -31,18 +36,22 @@ namespace TweetDuck.Core.Other{
this.buttonHeight = BrowserUtils.Scale(39, this.GetDPIScale()) | 1; this.buttonHeight = BrowserUtils.Scale(39, this.GetDPIScale()) | 1;
AddButton("General", () => new TabSettingsGeneral(updates)); AddButton("General", () => new TabSettingsGeneral(this.browser, updates));
AddButton("Notifications", () => new TabSettingsNotifications(browser.CreateNotificationForm(false))); AddButton("Locales", () => new TabSettingsLocales());
AddButton("Sounds", () => new TabSettingsSounds()); AddButton("System Tray", () => new TabSettingsTray());
AddButton("Advanced", () => new TabSettingsAdvanced(browser.ReinjectCustomCSS)); AddButton("Notifications", () => new TabSettingsNotifications(new FormNotificationExample(this.browser, this.plugins)));
AddButton("Sounds", () => new TabSettingsSounds(this.browser.PlaySoundNotification));
AddButton("Feedback", () => new TabSettingsFeedback(analytics, AnalyticsReportGenerator.ExternalInfo.From(this.browser), this.plugins));
AddButton("Advanced", () => new TabSettingsAdvanced(this.browser.ReinjectCustomCSS));
SelectTab(tabs[startTab ?? typeof(TabSettingsGeneral)]); SelectTab(tabs[startTab ?? typeof(TabSettingsGeneral)]);
} }
private void FormSettings_FormClosing(object sender, FormClosingEventArgs e){ private void FormSettings_FormClosing(object sender, FormClosingEventArgs e){
currentTab.Control.OnClosing();
foreach(SettingsTab tab in tabs.Values){ foreach(SettingsTab tab in tabs.Values){
if (tab.IsInitialized){ if (tab.IsInitialized){
tab.Control.OnClosing();
tab.Control.Dispose(); tab.Control.Dispose();
} }
} }
@@ -52,13 +61,20 @@ namespace TweetDuck.Core.Other{
} }
private void btnManageOptions_Click(object sender, EventArgs e){ private void btnManageOptions_Click(object sender, EventArgs e){
using(DialogSettingsManage dialog = new DialogSettingsManage(plugins)){ currentTab.Control.OnClosing();
if (dialog.ShowDialog() == DialogResult.OK && dialog.ShouldReloadUI){
foreach(SettingsTab tab in tabs.Values){
tab.Control = null;
}
SelectTab(currentTab); using(DialogSettingsManage dialog = new DialogSettingsManage(plugins)){
FormClosing -= FormSettings_FormClosing;
if (dialog.ShowDialog() == DialogResult.OK){
browser.ResumeNotification();
BrowserProcessHandler.UpdatePrefs();
ShouldReloadBrowser = dialog.ShouldReloadBrowser;
Close();
}
else{
FormClosing += FormSettings_FormClosing;
} }
} }
} }
@@ -103,23 +119,34 @@ namespace TweetDuck.Core.Other{
private void SelectTab(SettingsTab tab){ private void SelectTab(SettingsTab tab){
if (currentTab != null){ if (currentTab != null){
currentTab.Button.BackColor = SystemColors.Control; currentTab.Button.BackColor = SystemColors.Control;
currentTab.Control.OnClosing();
} }
tab.Button.BackColor = tab.Button.FlatAppearance.MouseDownBackColor; tab.Button.BackColor = tab.Button.FlatAppearance.MouseDownBackColor;
if (!tab.IsInitialized){ if (!tab.IsInitialized){
foreach(Control control in tab.Control.InteractiveControls){ foreach(Control control in tab.Control.InteractiveControls){
if (control is ComboBox){
control.MouseLeave += control_MouseLeave; control.MouseLeave += control_MouseLeave;
} }
else if (control is TrackBar){
control.MouseWheel += control_MouseWheel;
}
}
tab.Control.OnReady(); tab.Control.OnReady();
} }
panelContents.VerticalScroll.Enabled = false; // required to stop animation that would otherwise break everything
panelContents.PerformLayout();
panelContents.SuspendLayout(); panelContents.SuspendLayout();
panelContents.VerticalScroll.Value = 0; // https://gfycat.com/GrotesqueTastyAstarte panelContents.VerticalScroll.Value = 0; // https://gfycat.com/GrotesqueTastyAstarte
panelContents.Controls.Clear(); panelContents.Controls.Clear();
panelContents.Controls.Add(tab.Control); panelContents.Controls.Add(tab.Control);
panelContents.ResumeLayout(true); panelContents.ResumeLayout(true);
panelContents.VerticalScroll.Enabled = true;
panelContents.Focus(); panelContents.Focus();
currentTab = tab; currentTab = tab;
@@ -129,14 +156,15 @@ namespace TweetDuck.Core.Other{
panelContents.Focus(); panelContents.Focus();
} }
private class SettingsTab{ private void control_MouseWheel(object sender, MouseEventArgs e){
public Button Button { get; } ((HandledMouseEventArgs)e).Handled = true;
panelContents.Focus();
public BaseTabSettings Control{
get => control ?? (control = constructor());
set => control = value;
} }
private sealed class SettingsTab{
public Button Button { get; }
public BaseTabSettings Control => control ?? (control = constructor());
public bool IsInitialized => control != null; public bool IsInitialized => control != null;
private readonly Func<BaseTabSettings> constructor; private readonly Func<BaseTabSettings> constructor;

View File

@@ -1,120 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,181 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils;
using TweetLib.Communication;
namespace TweetDuck.Core.Other.Management{
sealed class VideoPlayer : IDisposable{
public bool Running{
get{
if (currentProcess == null){
return false;
}
currentProcess.Refresh();
return !currentProcess.HasExited;
}
}
public event EventHandler ProcessExited;
private readonly Form owner;
private string lastUrl;
private string lastUsername;
private Process currentProcess;
private DuplexPipe.Server currentPipe;
private bool isClosing;
public VideoPlayer(Form owner){
this.owner = owner;
this.owner.FormClosing += owner_FormClosing;
}
public void Launch(string url, string username){
if (Running){
Destroy();
isClosing = false;
}
lastUrl = url;
lastUsername = username;
try{
currentPipe = DuplexPipe.CreateServer();
currentPipe.DataIn += currentPipe_DataIn;
if ((currentProcess = Process.Start(new ProcessStartInfo{
FileName = Path.Combine(Program.ProgramPath, "TweetDuck.Video.exe"),
Arguments = $"{owner.Handle} {Program.UserConfig.VideoPlayerVolume} \"{url}\" \"{currentPipe.GenerateToken()}\"",
UseShellExecute = false,
RedirectStandardOutput = true
})) != null){
currentProcess.EnableRaisingEvents = true;
currentProcess.Exited += process_Exited;
#if DEBUG
currentProcess.BeginOutputReadLine();
currentProcess.OutputDataReceived += (sender, args) => Debug.WriteLine("VideoPlayer: "+args.Data);
#endif
}
currentPipe.DisposeToken();
}catch(Exception e){
Program.Reporter.HandleException("Video Playback Error", "Error launching video player.", true, e);
}
}
public void SendKeyEvent(Keys key){
currentPipe?.Write("key", ((int)key).ToString());
}
private void currentPipe_DataIn(object sender, DuplexPipe.PipeReadEventArgs e){
owner.InvokeSafe(() => {
switch(e.Key){
case "vol":
if (int.TryParse(e.Data, out int volume) && volume != Program.UserConfig.VideoPlayerVolume){
Program.UserConfig.VideoPlayerVolume = volume;
Program.UserConfig.Save();
}
break;
case "download":
TwitterUtils.DownloadVideo(lastUrl, lastUsername);
break;
case "rip":
currentPipe.Dispose();
currentPipe = null;
currentProcess.Dispose();
currentProcess = null;
isClosing = false;
TriggerProcessExitEventUnsafe();
break;
}
});
}
public void Close(){
if (currentProcess != null){
if (isClosing){
Destroy();
isClosing = false;
}
else{
isClosing = true;
currentProcess.Exited -= process_Exited;
currentPipe.Write("die");
}
}
}
public void Dispose(){
ProcessExited = null;
isClosing = true;
Destroy();
}
private void Destroy(){
if (currentProcess != null){
try{
currentProcess.Kill();
}catch{
// kill me instead then
}
currentProcess.Dispose();
currentProcess = null;
currentPipe.Dispose();
currentPipe = null;
TriggerProcessExitEventUnsafe();
}
}
private void owner_FormClosing(object sender, FormClosingEventArgs e){
if (currentProcess != null){
currentProcess.Exited -= process_Exited;
}
}
private void process_Exited(object sender, EventArgs e){
int exitCode = currentProcess.ExitCode;
currentProcess.Dispose();
currentProcess = null;
currentPipe.Dispose();
currentPipe = null;
switch(exitCode){
case 3: // CODE_LAUNCH_FAIL
if (FormMessage.Error("Video Playback Error", "Error launching video player, this may be caused by missing Windows Media Player. Do you want to open the video in your browser?", FormMessage.Yes, FormMessage.No)){
BrowserUtils.OpenExternalBrowser(lastUrl);
}
break;
case 4: // CODE_MEDIA_ERROR
if (FormMessage.Error("Video Playback Error", "The video could not be loaded, most likely due to unknown format. Do you want to open the video in your browser?", FormMessage.Yes, FormMessage.No)){
BrowserUtils.OpenExternalBrowser(lastUrl);
}
break;
}
owner.InvokeAsyncSafe(TriggerProcessExitEventUnsafe);
}
private void TriggerProcessExitEventUnsafe(){
ProcessExited?.Invoke(this, EventArgs.Empty);
}
}
}

View File

@@ -17,7 +17,7 @@ namespace TweetDuck.Core.Other.Settings{
} }
} }
public BaseTabSettings(){ protected BaseTabSettings(){
Padding = new Padding(6); Padding = new Padding(6);
} }
@@ -25,7 +25,7 @@ namespace TweetDuck.Core.Other.Settings{
public virtual void OnClosing(){} public virtual void OnClosing(){}
protected static void PromptRestart(){ protected static void PromptRestart(){
if (MessageBox.Show("The application must restart for the option to take place. Do you want to restart now?", Program.BrandName+" Options", MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.Yes){ if (FormMessage.Information("TweetDuck Options", "The application must restart for the option to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)){
Program.Restart(); Program.Restart();
} }
} }

View File

@@ -0,0 +1,90 @@
namespace TweetDuck.Core.Other.Settings.Dialogs {
partial class DialogSettingsAnalytics {
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.textBoxReport = new System.Windows.Forms.TextBox();
this.btnClose = new System.Windows.Forms.Button();
this.labelInfo = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// textBoxReport
//
this.textBoxReport.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.textBoxReport.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.textBoxReport.Location = new System.Drawing.Point(12, 41);
this.textBoxReport.Multiline = true;
this.textBoxReport.Name = "textBoxReport";
this.textBoxReport.ReadOnly = true;
this.textBoxReport.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.textBoxReport.Size = new System.Drawing.Size(460, 480);
this.textBoxReport.TabIndex = 1;
//
// btnClose
//
this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnClose.Location = new System.Drawing.Point(416, 527);
this.btnClose.Name = "btnClose";
this.btnClose.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnClose.Size = new System.Drawing.Size(56, 23);
this.btnClose.TabIndex = 2;
this.btnClose.Text = "Close";
this.btnClose.UseVisualStyleBackColor = true;
this.btnClose.Click += new System.EventHandler(this.btnClose_Click);
//
// labelInfo
//
this.labelInfo.Location = new System.Drawing.Point(12, 9);
this.labelInfo.Margin = new System.Windows.Forms.Padding(3, 0, 3, 3);
this.labelInfo.Name = "labelInfo";
this.labelInfo.Size = new System.Drawing.Size(460, 26);
this.labelInfo.TabIndex = 0;
this.labelInfo.Text = "When enabled, this data will be sent over a secure network roughly once every wee" +
"k.\r\nSome numbers in the report were made imprecise on purpose.";
//
// DialogSettingsAnalytics
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(484, 562);
this.Controls.Add(this.labelInfo);
this.Controls.Add(this.btnClose);
this.Controls.Add(this.textBoxReport);
this.MinimumSize = new System.Drawing.Size(450, 340);
this.Name = "DialogSettingsAnalytics";
this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.TextBox textBoxReport;
private System.Windows.Forms.Button btnClose;
private System.Windows.Forms.Label labelInfo;
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Other.Analytics;
namespace TweetDuck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsAnalytics : Form{
public string CefArgs => textBoxReport.Text;
public DialogSettingsAnalytics(AnalyticsReport report){
InitializeComponent();
Text = Program.BrandName+" Options - Analytics Report";
textBoxReport.EnableMultilineShortcuts();
textBoxReport.Text = report.ToString().TrimEnd();
textBoxReport.Select(0, 0);
}
private void btnClose_Click(object sender, EventArgs e){
Close();
}
}
}

View File

@@ -35,7 +35,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
} }
private void btnOpenWiki_Click(object sender, EventArgs e){ private void btnOpenWiki_Click(object sender, EventArgs e){
BrowserUtils.OpenExternalBrowserUnsafe("https://github.com/chylex/TweetDuck/wiki"); BrowserUtils.OpenExternalBrowser("https://github.com/chylex/TweetDuck/wiki");
} }
private void btnApply_Click(object sender, EventArgs e){ private void btnApply_Click(object sender, EventArgs e){

View File

@@ -2,6 +2,7 @@
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Data;
namespace TweetDuck.Core.Other.Settings.Dialogs{ namespace TweetDuck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsCefArgs : Form{ sealed partial class DialogSettingsCefArgs : Form{
@@ -18,7 +19,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
} }
private void btnHelp_Click(object sender, EventArgs e){ private void btnHelp_Click(object sender, EventArgs e){
BrowserUtils.OpenExternalBrowserUnsafe("http://peter.sh/experiments/chromium-command-line-switches/"); BrowserUtils.OpenExternalBrowser("http://peter.sh/experiments/chromium-command-line-switches/");
} }
private void btnApply_Click(object sender, EventArgs e){ private void btnApply_Click(object sender, EventArgs e){
@@ -30,10 +31,10 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
return; return;
} }
int count = CommandLineArgsParser.ReadCefArguments(CefArgs).Count; int count = CommandLineArgs.ReadCefArguments(CefArgs).Count;
string prompt = count == 0 && !string.IsNullOrWhiteSpace(prevArgs) ? "All current arguments will be removed. Continue?" : count+(count == 1 ? " argument was" : " arguments were")+" detected. Continue?"; string prompt = count == 0 && !string.IsNullOrWhiteSpace(prevArgs) ? "All current arguments will be removed. Continue?" : count+(count == 1 ? " argument was" : " arguments were")+" detected. Continue?";
if (MessageBox.Show(prompt, "Confirm CEF Arguments", MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.OK){ if (FormMessage.Question("Confirm CEF Arguments", prompt, FormMessage.OK, FormMessage.Cancel)){
DialogResult = DialogResult.OK; DialogResult = DialogResult.OK;
Close(); Close();
} }

View File

@@ -1,12 +1,14 @@
using System; using System;
using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Configuration;
using TweetDuck.Core.Other.Settings.Export; using TweetDuck.Core.Other.Settings.Export;
using TweetDuck.Plugins; using TweetDuck.Plugins;
namespace TweetDuck.Core.Other.Settings.Dialogs{ namespace TweetDuck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsManage : Form{ sealed partial class DialogSettingsManage : Form{
private enum State{ private enum State{
Deciding, Import, Export Deciding, Reset, Import, Export
} }
public ExportFileFlags Flags{ public ExportFileFlags Flags{
@@ -14,13 +16,13 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
set{ set{
// this will call events and SetFlag, which also updates the UI // this will call events and SetFlag, which also updates the UI
cbConfig.Checked = value.HasFlag(ExportFileFlags.Config); cbConfig.Checked = value.HasFlag(ExportFileFlags.UserConfig);
cbSession.Checked = value.HasFlag(ExportFileFlags.Session); cbSession.Checked = value.HasFlag(ExportFileFlags.Session);
cbPluginData.Checked = value.HasFlag(ExportFileFlags.PluginData); cbPluginData.Checked = value.HasFlag(ExportFileFlags.PluginData);
} }
} }
public bool ShouldReloadUI { get; private set; } public bool ShouldReloadBrowser { get; private set; }
private readonly PluginManager plugins; private readonly PluginManager plugins;
private State currentState; private State currentState;
@@ -40,7 +42,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
} }
private void cbConfig_CheckedChanged(object sender, EventArgs e){ private void cbConfig_CheckedChanged(object sender, EventArgs e){
SetFlag(ExportFileFlags.Config, cbConfig.Checked); SetFlag(ExportFileFlags.UserConfig, cbConfig.Checked);
} }
private void cbSession_CheckedChanged(object sender, EventArgs e){ private void cbSession_CheckedChanged(object sender, EventArgs e){
@@ -58,15 +60,10 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
case State.Deciding: case State.Deciding:
// Reset // Reset
if (radioReset.Checked){ if (radioReset.Checked){
if (MessageBox.Show("This will reset all of your program options. Plugins will not be affected. Do you want to proceed?", "Reset "+Program.BrandName+" Options", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes){ currentState = State.Reset;
Program.ResetConfig();
ShouldReloadUI = true; Text = "Restore Defaults";
DialogResult = DialogResult.OK; Flags = ExportFileFlags.UserConfig;
Close();
}
return;
} }
// Import // Import
@@ -74,8 +71,8 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
using(OpenFileDialog dialog = new OpenFileDialog{ using(OpenFileDialog dialog = new OpenFileDialog{
AutoUpgradeEnabled = true, AutoUpgradeEnabled = true,
DereferenceLinks = true, DereferenceLinks = true,
Title = "Import "+Program.BrandName+" Profile", Title = "Import TweetDuck Profile",
Filter = Program.BrandName+" Profile (*.tdsettings)|*.tdsettings" Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings"
}){ }){
if (dialog.ShowDialog() != DialogResult.OK){ if (dialog.ShowDialog() != DialogResult.OK){
return; return;
@@ -109,14 +106,63 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
panelExport.Visible = true; panelExport.Visible = true;
break; break;
case State.Reset:
if (FormMessage.Warning("Reset TweetDuck Options", "This will reset the selected items. Are you sure you want to proceed?", FormMessage.Yes, FormMessage.No)){
if (Flags.HasFlag(ExportFileFlags.UserConfig)){
Program.ResetConfig();
}
if (Flags.HasFlag(ExportFileFlags.SystemConfig)){
try{
File.Delete(Program.SystemConfigFilePath);
}catch(Exception ex){
Program.Reporter.HandleException("System Config Reset Error", "Could not delete system config.", true, ex);
}
}
if (Flags.HasFlag(ExportFileFlags.PluginData)){
try{
File.Delete(Program.PluginConfigFilePath);
Directory.Delete(Program.PluginDataPath, true);
}catch(Exception ex){
Program.Reporter.HandleException("Plugin Data Reset Error", "Could not delete plugin data.", true, ex);
}
}
if (Flags.HasFlag(ExportFileFlags.Session)){
Program.Restart(Arguments.ArgDeleteCookies);
}
else if (Flags.HasFlag(ExportFileFlags.SystemConfig)){
Program.Restart();
}
else{
ShouldReloadBrowser = true;
}
DialogResult = DialogResult.OK;
Close();
}
break;
case State.Import: case State.Import:
if (importManager.Import(Flags)){ if (importManager.Import(Flags)){
if (!importManager.IsRestarting){ Program.UserConfig.Reload();
ShouldReloadUI = true;
if (importManager.IsRestarting){
if (Flags.HasFlag(ExportFileFlags.Session)){
Program.Restart(Arguments.ArgImportCookies);
}
else if (Flags.HasFlag(ExportFileFlags.SystemConfig)){
Program.Restart();
} }
} }
else{ else{
Program.Reporter.HandleException("Profile Import Error", "An exception happened while importing "+Program.BrandName+" profile.", true, importManager.LastException); ShouldReloadBrowser = true;
}
}
else{
Program.Reporter.HandleException("Profile Import Error", "An exception happened while importing TweetDuck profile.", true, importManager.LastException);
} }
DialogResult = DialogResult.OK; DialogResult = DialogResult.OK;
@@ -129,9 +175,9 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
AutoUpgradeEnabled = true, AutoUpgradeEnabled = true,
OverwritePrompt = true, OverwritePrompt = true,
DefaultExt = "tdsettings", DefaultExt = "tdsettings",
FileName = Program.BrandName+".tdsettings", FileName = "TweetDuck.tdsettings",
Title = "Export "+Program.BrandName+" Profile", Title = "Export TweetDuck Profile",
Filter = Program.BrandName+" Profile (*.tdsettings)|*.tdsettings" Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings"
}){ }){
if (dialog.ShowDialog() != DialogResult.OK){ if (dialog.ShowDialog() != DialogResult.OK){
return; return;
@@ -141,10 +187,12 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
} }
Program.UserConfig.Save(); Program.UserConfig.Save();
Program.SystemConfig.Save();
ExportManager manager = new ExportManager(file, plugins); ExportManager manager = new ExportManager(file, plugins);
if (!manager.Export(Flags)){ if (!manager.Export(Flags)){
Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting "+Program.BrandName+" profile.", true, manager.LastException); Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting TweetDuck profile.", true, manager.LastException);
} }
DialogResult = DialogResult.OK; DialogResult = DialogResult.OK;
@@ -165,6 +213,9 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
if (currentState == State.Import){ if (currentState == State.Import){
btnContinue.Text = selectedFlags.HasFlag(ExportFileFlags.Session) ? "Import && Restart" : "Import Profile"; btnContinue.Text = selectedFlags.HasFlag(ExportFileFlags.Session) ? "Import && Restart" : "Import Profile";
} }
else if (currentState == State.Reset){
btnContinue.Text = selectedFlags.HasFlag(ExportFileFlags.Session) ? "Restore && Restart" : "Restore Defaults";
}
} }
} }
} }

View File

@@ -29,20 +29,20 @@
this.cbLogging = new System.Windows.Forms.CheckBox(); this.cbLogging = new System.Windows.Forms.CheckBox();
this.toolTip = new System.Windows.Forms.ToolTip(this.components); this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.cbDebugUpdates = new System.Windows.Forms.CheckBox(); 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.tbDataFolder = new System.Windows.Forms.TextBox();
this.tbShortcutTarget = new System.Windows.Forms.TextBox();
this.labelDataFolder = new System.Windows.Forms.Label();
this.labelShortcutTarget = new System.Windows.Forms.Label();
this.SuspendLayout(); this.SuspendLayout();
// //
// btnCancel // btnCancel
// //
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnCancel.Location = new System.Drawing.Point(160, 171); this.btnCancel.Location = new System.Drawing.Point(215, 163);
this.btnCancel.Name = "btnCancel"; this.btnCancel.Name = "btnCancel";
this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0); this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnCancel.Size = new System.Drawing.Size(56, 23); this.btnCancel.Size = new System.Drawing.Size(56, 23);
this.btnCancel.TabIndex = 7; this.btnCancel.TabIndex = 9;
this.btnCancel.Text = "Cancel"; this.btnCancel.Text = "Cancel";
this.btnCancel.UseVisualStyleBackColor = true; this.btnCancel.UseVisualStyleBackColor = true;
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
@@ -50,11 +50,11 @@
// btnRestart // btnRestart
// //
this.btnRestart.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.btnRestart.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnRestart.Location = new System.Drawing.Point(97, 171); this.btnRestart.Location = new System.Drawing.Point(152, 163);
this.btnRestart.Name = "btnRestart"; this.btnRestart.Name = "btnRestart";
this.btnRestart.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0); this.btnRestart.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnRestart.Size = new System.Drawing.Size(57, 23); this.btnRestart.Size = new System.Drawing.Size(57, 23);
this.btnRestart.TabIndex = 6; this.btnRestart.TabIndex = 8;
this.btnRestart.Text = "Restart"; this.btnRestart.Text = "Restart";
this.btnRestart.UseVisualStyleBackColor = true; this.btnRestart.UseVisualStyleBackColor = true;
this.btnRestart.Click += new System.EventHandler(this.btnRestart_Click); this.btnRestart.Click += new System.EventHandler(this.btnRestart_Click);
@@ -67,7 +67,7 @@
this.cbLogging.Size = new System.Drawing.Size(64, 17); this.cbLogging.Size = new System.Drawing.Size(64, 17);
this.cbLogging.TabIndex = 0; this.cbLogging.TabIndex = 0;
this.cbLogging.Text = "Logging"; this.cbLogging.Text = "Logging";
this.toolTip.SetToolTip(this.cbLogging, "Logging JavaScript output into a\r\ndebug.txt file in the data folder."); this.toolTip.SetToolTip(this.cbLogging, "Logging JavaScript output into TD_Console.txt file in the data folder.");
this.cbLogging.UseVisualStyleBackColor = true; this.cbLogging.UseVisualStyleBackColor = true;
// //
// cbDebugUpdates // cbDebugUpdates
@@ -81,54 +81,58 @@
this.toolTip.SetToolTip(this.cbDebugUpdates, "Allows updating to pre-releases."); this.toolTip.SetToolTip(this.cbDebugUpdates, "Allows updating to pre-releases.");
this.cbDebugUpdates.UseVisualStyleBackColor = true; this.cbDebugUpdates.UseVisualStyleBackColor = true;
// //
// labelLocale // tbDataFolder
// //
this.labelLocale.AutoSize = true; this.tbDataFolder.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
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))); | System.Windows.Forms.AnchorStyles.Right)));
this.comboLocale.FormattingEnabled = true; this.tbDataFolder.Location = new System.Drawing.Point(15, 83);
this.comboLocale.Location = new System.Drawing.Point(15, 83); this.tbDataFolder.Name = "tbDataFolder";
this.comboLocale.Name = "comboLocale"; this.tbDataFolder.Size = new System.Drawing.Size(257, 20);
this.comboLocale.Size = new System.Drawing.Size(201, 21); this.tbDataFolder.TabIndex = 5;
this.comboLocale.TabIndex = 3; this.toolTip.SetToolTip(this.tbDataFolder, "Path to the data folder. Must be either an absolute path,\r\nor a simple folder nam" +
"e that will be created in LocalAppData.");
//
// tbShortcutTarget
//
this.tbShortcutTarget.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.tbShortcutTarget.Cursor = System.Windows.Forms.Cursors.Hand;
this.tbShortcutTarget.Location = new System.Drawing.Point(15, 134);
this.tbShortcutTarget.Name = "tbShortcutTarget";
this.tbShortcutTarget.ReadOnly = true;
this.tbShortcutTarget.Size = new System.Drawing.Size(257, 20);
this.tbShortcutTarget.TabIndex = 7;
this.tbShortcutTarget.Click += new System.EventHandler(this.tbShortcutTarget_Click);
// //
// labelDataFolder // labelDataFolder
// //
this.labelDataFolder.AutoSize = true; this.labelDataFolder.AutoSize = true;
this.labelDataFolder.Location = new System.Drawing.Point(12, 119); this.labelDataFolder.Location = new System.Drawing.Point(12, 67);
this.labelDataFolder.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0); this.labelDataFolder.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelDataFolder.Name = "labelDataFolder"; this.labelDataFolder.Name = "labelDataFolder";
this.labelDataFolder.Size = new System.Drawing.Size(62, 13); this.labelDataFolder.Size = new System.Drawing.Size(62, 13);
this.labelDataFolder.TabIndex = 4; this.labelDataFolder.TabIndex = 4;
this.labelDataFolder.Text = "Data Folder"; this.labelDataFolder.Text = "Data Folder";
// //
// tbDataFolder // labelShortcutTarget
// //
this.tbDataFolder.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) this.labelShortcutTarget.AutoSize = true;
| System.Windows.Forms.AnchorStyles.Right))); this.labelShortcutTarget.Location = new System.Drawing.Point(12, 118);
this.tbDataFolder.Location = new System.Drawing.Point(15, 135); this.labelShortcutTarget.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.tbDataFolder.Name = "tbDataFolder"; this.labelShortcutTarget.Name = "labelShortcutTarget";
this.tbDataFolder.Size = new System.Drawing.Size(201, 20); this.labelShortcutTarget.Size = new System.Drawing.Size(155, 13);
this.tbDataFolder.TabIndex = 5; this.labelShortcutTarget.TabIndex = 6;
this.labelShortcutTarget.Text = "Shortcut Target (click to select)";
// //
// DialogSettingsRestart // DialogSettingsRestart
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(228, 206); this.ClientSize = new System.Drawing.Size(284, 198);
this.Controls.Add(this.tbShortcutTarget);
this.Controls.Add(this.labelShortcutTarget);
this.Controls.Add(this.tbDataFolder); this.Controls.Add(this.tbDataFolder);
this.Controls.Add(this.labelDataFolder); this.Controls.Add(this.labelDataFolder);
this.Controls.Add(this.comboLocale);
this.Controls.Add(this.labelLocale);
this.Controls.Add(this.cbDebugUpdates); this.Controls.Add(this.cbDebugUpdates);
this.Controls.Add(this.cbLogging); this.Controls.Add(this.cbLogging);
this.Controls.Add(this.btnRestart); this.Controls.Add(this.btnRestart);
@@ -151,9 +155,9 @@
private System.Windows.Forms.CheckBox cbLogging; private System.Windows.Forms.CheckBox cbLogging;
private System.Windows.Forms.ToolTip toolTip; private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.CheckBox cbDebugUpdates; 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.Label labelDataFolder;
private System.Windows.Forms.TextBox tbDataFolder; private System.Windows.Forms.TextBox tbDataFolder;
private System.Windows.Forms.TextBox tbShortcutTarget;
private System.Windows.Forms.Label labelShortcutTarget;
} }
} }

View File

@@ -1,35 +1,36 @@
using System; using System;
using System.IO;
using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetDuck.Core.Utils; using TweetDuck.Data;
namespace TweetDuck.Core.Other.Settings.Dialogs{ namespace TweetDuck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsRestart : Form{ sealed partial class DialogSettingsRestart : Form{
private const string DefaultLocale = "en-US";
public CommandLineArgs Args { get; private set; } public CommandLineArgs Args { get; private set; }
public DialogSettingsRestart(CommandLineArgs currentArgs){ public DialogSettingsRestart(CommandLineArgs currentArgs){
InitializeComponent(); 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); cbLogging.Checked = currentArgs.HasFlag(Arguments.ArgLogging);
cbDebugUpdates.Checked = currentArgs.HasFlag(Arguments.ArgDebugUpdates); cbDebugUpdates.Checked = currentArgs.HasFlag(Arguments.ArgDebugUpdates);
comboLocale.SelectedItem = currentArgs.GetValue(Arguments.ArgLocale, DefaultLocale);
cbLogging.CheckedChanged += control_Change;
cbDebugUpdates.CheckedChanged += control_Change;
if (Program.IsPortable){
tbDataFolder.Text = "Not available in portable version";
tbDataFolder.Enabled = false;
}
else{
tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder, string.Empty); tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder, string.Empty);
tbDataFolder.TextChanged += control_Change;
}
control_Change(this, EventArgs.Empty);
Text = Program.BrandName+" Arguments"; Text = Program.BrandName+" Arguments";
} }
private void btnRestart_Click(object sender, EventArgs e){ private void control_Change(object sender, EventArgs e){
Args = new CommandLineArgs(); Args = new CommandLineArgs();
if (cbLogging.Checked){ if (cbLogging.Checked){
@@ -40,16 +41,21 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
Args.AddFlag(Arguments.ArgDebugUpdates); Args.AddFlag(Arguments.ArgDebugUpdates);
} }
string locale = comboLocale.SelectedItem as string; if (!string.IsNullOrWhiteSpace(tbDataFolder.Text) && tbDataFolder.Enabled){
if (!string.IsNullOrWhiteSpace(locale) && locale != DefaultLocale){
Args.SetValue(Arguments.ArgLocale, locale);
}
if (!string.IsNullOrWhiteSpace(tbDataFolder.Text)){
Args.SetValue(Arguments.ArgDataFolder, tbDataFolder.Text); Args.SetValue(Arguments.ArgDataFolder, tbDataFolder.Text);
} }
tbShortcutTarget.Text = $@"""{Application.ExecutablePath}""{(Args.Count > 0 ? " " : "")}{Args}";
tbShortcutTarget.Select(tbShortcutTarget.Text.Length, 0);
}
private void tbShortcutTarget_Click(object sender, EventArgs e){
if (tbShortcutTarget.SelectionLength == 0){
tbShortcutTarget.SelectAll();
}
}
private void btnRestart_Click(object sender, EventArgs e){
DialogResult = DialogResult.OK; DialogResult = DialogResult.OK;
Close(); Close();
} }

View File

@@ -4,9 +4,10 @@ namespace TweetDuck.Core.Other.Settings.Export{
[Flags] [Flags]
enum ExportFileFlags{ enum ExportFileFlags{
None = 0, None = 0,
Config = 1, UserConfig = 1,
Session = 2, SystemConfig = 2, // TODO implement later
PluginData = 4, Session = 4,
All = Config|Session|PluginData PluginData = 8,
All = UserConfig|SystemConfig|Session|PluginData
} }
} }

View File

@@ -2,8 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Windows.Forms; using TweetDuck.Data;
using TweetDuck.Configuration;
using TweetDuck.Plugins; using TweetDuck.Plugins;
using TweetDuck.Plugins.Enums; using TweetDuck.Plugins.Enums;
@@ -26,10 +25,14 @@ namespace TweetDuck.Core.Other.Settings.Export{
public bool Export(ExportFileFlags flags){ public bool Export(ExportFileFlags flags){
try{ try{
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){ using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
if (flags.HasFlag(ExportFileFlags.Config)){ if (flags.HasFlag(ExportFileFlags.UserConfig)){
stream.WriteFile("config", Program.UserConfigFilePath); stream.WriteFile("config", Program.UserConfigFilePath);
} }
if (flags.HasFlag(ExportFileFlags.SystemConfig)){
stream.WriteFile("system", Program.SystemConfigFilePath);
}
if (flags.HasFlag(ExportFileFlags.PluginData)){ if (flags.HasFlag(ExportFileFlags.PluginData)){
stream.WriteFile("plugin.config", Program.PluginConfigFilePath); stream.WriteFile("plugin.config", Program.PluginConfigFilePath);
@@ -38,7 +41,7 @@ namespace TweetDuck.Core.Other.Settings.Export{
try{ try{
stream.WriteFile(new string[]{ "plugin.data", plugin.Identifier, path.Relative }, path.Full); stream.WriteFile(new string[]{ "plugin.data", plugin.Identifier, path.Relative }, path.Full);
}catch(ArgumentOutOfRangeException e){ }catch(ArgumentOutOfRangeException e){
MessageBox.Show("Could not include a plugin file in the export. "+e.Message, "Export Profile", MessageBoxButtons.OK, MessageBoxIcon.Warning); FormMessage.Warning("Export Profile", "Could not include a plugin file in the export. "+e.Message, FormMessage.OK);
} }
} }
} }
@@ -68,7 +71,11 @@ namespace TweetDuck.Core.Other.Settings.Export{
while((key = stream.SkipFile()) != null){ while((key = stream.SkipFile()) != null){
switch(key){ switch(key){
case "config": case "config":
flags |= ExportFileFlags.Config; flags |= ExportFileFlags.UserConfig;
break;
case "system":
flags |= ExportFileFlags.SystemConfig;
break; break;
case "plugin.config": case "plugin.config":
@@ -100,12 +107,20 @@ namespace TweetDuck.Core.Other.Settings.Export{
while((entry = stream.ReadFile()) != null){ while((entry = stream.ReadFile()) != null){
switch(entry.KeyName){ switch(entry.KeyName){
case "config": case "config":
if (flags.HasFlag(ExportFileFlags.Config)){ if (flags.HasFlag(ExportFileFlags.UserConfig)){
entry.WriteToFile(Program.UserConfigFilePath); entry.WriteToFile(Program.UserConfigFilePath);
} }
break; break;
case "system":
if (flags.HasFlag(ExportFileFlags.SystemConfig)){
entry.WriteToFile(Program.SystemConfigFilePath);
IsRestarting = true;
}
break;
case "plugin.config": case "plugin.config":
if (flags.HasFlag(ExportFileFlags.PluginData)){ if (flags.HasFlag(ExportFileFlags.PluginData)){
entry.WriteToFile(Program.PluginConfigFilePath); entry.WriteToFile(Program.PluginConfigFilePath);
@@ -138,14 +153,7 @@ namespace TweetDuck.Core.Other.Settings.Export{
} }
if (missingPlugins.Count > 0){ 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); FormMessage.Information("Importing TweetDuck Profile", "Detected missing plugins when importing plugin data:\n"+string.Join("\n", missingPlugins), FormMessage.OK);
}
if (IsRestarting){
Program.Restart(Arguments.ArgImportCookies);
}
else{
Program.ReloadConfig();
} }
return true; return true;
@@ -169,6 +177,16 @@ namespace TweetDuck.Core.Other.Settings.Export{
} }
} }
public static void DeleteCookies(){
try{
if (File.Exists(CookiesPath)){
File.Delete(CookiesPath);
}
}catch(Exception e){
Program.Reporter.HandleException("Session Reset Error", "Could not remove the cookie file to reset the login session.", true, e);
}
}
private static IEnumerable<PathInfo> EnumerateFilesRelative(string root){ private static IEnumerable<PathInfo> EnumerateFilesRelative(string root){
return Directory.Exists(root) ? Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo{ return Directory.Exists(root) ? Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo{
Full = fullPath, Full = fullPath,
@@ -176,7 +194,7 @@ namespace TweetDuck.Core.Other.Settings.Export{
}) : Enumerable.Empty<PathInfo>(); }) : Enumerable.Empty<PathInfo>();
} }
private class PathInfo{ private sealed class PathInfo{
public string Full { get; set; } public string Full { get; set; }
public string Relative { get; set; } public string Relative { get; set; }
} }

View File

@@ -33,40 +33,42 @@
this.btnRestart = new System.Windows.Forms.Button(); this.btnRestart = new System.Windows.Forms.Button();
this.btnOpenAppFolder = new System.Windows.Forms.Button(); this.btnOpenAppFolder = new System.Windows.Forms.Button();
this.btnOpenDataFolder = new System.Windows.Forms.Button(); this.btnOpenDataFolder = new System.Windows.Forms.Button();
this.numClearCacheThreshold = new TweetDuck.Core.Controls.NumericUpDownEx();
this.checkClearCacheAuto = new System.Windows.Forms.CheckBox();
this.labelApp = new System.Windows.Forms.Label(); this.labelApp = new System.Windows.Forms.Label();
this.panelApp = new System.Windows.Forms.Panel(); this.panelAppButtons = new System.Windows.Forms.Panel();
this.labelPerformance = new System.Windows.Forms.Label(); this.labelPerformance = new System.Windows.Forms.Label();
this.panelPerformance = new System.Windows.Forms.Panel(); this.panelClearCacheAuto = new System.Windows.Forms.Panel();
this.labelCache = new System.Windows.Forms.Label();
this.panelConfiguration = new System.Windows.Forms.Panel(); this.panelConfiguration = new System.Windows.Forms.Panel();
this.labelConfiguration = new System.Windows.Forms.Label(); this.labelConfiguration = new System.Windows.Forms.Label();
this.panelApp.SuspendLayout(); this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
this.panelPerformance.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.numClearCacheThreshold)).BeginInit();
this.panelAppButtons.SuspendLayout();
this.panelClearCacheAuto.SuspendLayout();
this.panelConfiguration.SuspendLayout(); this.panelConfiguration.SuspendLayout();
this.flowPanel.SuspendLayout();
this.SuspendLayout(); this.SuspendLayout();
// //
// btnClearCache // btnClearCache
// //
this.btnClearCache.Location = new System.Drawing.Point(5, 28); this.btnClearCache.Location = new System.Drawing.Point(5, 172);
this.btnClearCache.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3); this.btnClearCache.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.btnClearCache.Name = "btnClearCache"; this.btnClearCache.Name = "btnClearCache";
this.btnClearCache.Size = new System.Drawing.Size(144, 23); this.btnClearCache.Size = new System.Drawing.Size(144, 23);
this.btnClearCache.TabIndex = 1; this.btnClearCache.TabIndex = 1;
this.btnClearCache.Text = "Clear Cache (calculating)"; 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.UseVisualStyleBackColor = true;
// //
// checkHardwareAcceleration // checkHardwareAcceleration
// //
this.checkHardwareAcceleration.AutoSize = true; this.checkHardwareAcceleration.AutoSize = true;
this.checkHardwareAcceleration.Location = new System.Drawing.Point(6, 5); this.checkHardwareAcceleration.Location = new System.Drawing.Point(6, 124);
this.checkHardwareAcceleration.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3); this.checkHardwareAcceleration.Margin = new System.Windows.Forms.Padding(6, 6, 3, 3);
this.checkHardwareAcceleration.Name = "checkHardwareAcceleration"; this.checkHardwareAcceleration.Name = "checkHardwareAcceleration";
this.checkHardwareAcceleration.Size = new System.Drawing.Size(134, 17); this.checkHardwareAcceleration.Size = new System.Drawing.Size(134, 17);
this.checkHardwareAcceleration.TabIndex = 0; this.checkHardwareAcceleration.TabIndex = 0;
this.checkHardwareAcceleration.Text = "Hardware Acceleration"; 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.UseVisualStyleBackColor = true;
// //
// btnEditCefArgs // btnEditCefArgs
@@ -77,7 +79,6 @@
this.btnEditCefArgs.Size = new System.Drawing.Size(144, 23); this.btnEditCefArgs.Size = new System.Drawing.Size(144, 23);
this.btnEditCefArgs.TabIndex = 0; this.btnEditCefArgs.TabIndex = 0;
this.btnEditCefArgs.Text = "Edit CEF Arguments"; this.btnEditCefArgs.Text = "Edit CEF Arguments";
this.toolTip.SetToolTip(this.btnEditCefArgs, "Set custom command line arguments for Chromium Embedded Framework.");
this.btnEditCefArgs.UseVisualStyleBackColor = true; this.btnEditCefArgs.UseVisualStyleBackColor = true;
// //
// btnEditCSS // btnEditCSS
@@ -87,7 +88,6 @@
this.btnEditCSS.Size = new System.Drawing.Size(144, 23); this.btnEditCSS.Size = new System.Drawing.Size(144, 23);
this.btnEditCSS.TabIndex = 1; this.btnEditCSS.TabIndex = 1;
this.btnEditCSS.Text = "Edit CSS"; this.btnEditCSS.Text = "Edit CSS";
this.toolTip.SetToolTip(this.btnEditCSS, "Set custom CSS for browser and notification windows.");
this.btnEditCSS.UseVisualStyleBackColor = true; this.btnEditCSS.UseVisualStyleBackColor = true;
// //
// btnRestartArgs // btnRestartArgs
@@ -97,7 +97,6 @@
this.btnRestartArgs.Size = new System.Drawing.Size(144, 23); this.btnRestartArgs.Size = new System.Drawing.Size(144, 23);
this.btnRestartArgs.TabIndex = 3; this.btnRestartArgs.TabIndex = 3;
this.btnRestartArgs.Text = "Restart with Arguments"; this.btnRestartArgs.Text = "Restart with Arguments";
this.toolTip.SetToolTip(this.btnRestartArgs, "Restarts the program with customizable\r\ncommand line arguments.");
this.btnRestartArgs.UseVisualStyleBackColor = true; this.btnRestartArgs.UseVisualStyleBackColor = true;
// //
// btnRestart // btnRestart
@@ -107,8 +106,6 @@
this.btnRestart.Size = new System.Drawing.Size(144, 23); this.btnRestart.Size = new System.Drawing.Size(144, 23);
this.btnRestart.TabIndex = 2; this.btnRestart.TabIndex = 2;
this.btnRestart.Text = "Restart the Program"; this.btnRestart.Text = "Restart the Program";
this.toolTip.SetToolTip(this.btnRestart, "Restarts the program using the same command\r\nline arguments that were used at lau" +
"nch.");
this.btnRestart.UseVisualStyleBackColor = true; this.btnRestart.UseVisualStyleBackColor = true;
// //
// btnOpenAppFolder // btnOpenAppFolder
@@ -119,7 +116,6 @@
this.btnOpenAppFolder.Size = new System.Drawing.Size(144, 23); this.btnOpenAppFolder.Size = new System.Drawing.Size(144, 23);
this.btnOpenAppFolder.TabIndex = 0; this.btnOpenAppFolder.TabIndex = 0;
this.btnOpenAppFolder.Text = "Open Program Folder"; this.btnOpenAppFolder.Text = "Open Program Folder";
this.toolTip.SetToolTip(this.btnOpenAppFolder, "Opens the folder where the app is located.");
this.btnOpenAppFolder.UseVisualStyleBackColor = true; this.btnOpenAppFolder.UseVisualStyleBackColor = true;
// //
// btnOpenDataFolder // btnOpenDataFolder
@@ -130,62 +126,94 @@
this.btnOpenDataFolder.Size = new System.Drawing.Size(144, 23); this.btnOpenDataFolder.Size = new System.Drawing.Size(144, 23);
this.btnOpenDataFolder.TabIndex = 1; this.btnOpenDataFolder.TabIndex = 1;
this.btnOpenDataFolder.Text = "Open Data Folder"; this.btnOpenDataFolder.Text = "Open Data Folder";
this.toolTip.SetToolTip(this.btnOpenDataFolder, "Opens the folder where your profile data is located.");
this.btnOpenDataFolder.UseVisualStyleBackColor = true; this.btnOpenDataFolder.UseVisualStyleBackColor = true;
// //
// numClearCacheThreshold
//
this.numClearCacheThreshold.Increment = 50;
this.numClearCacheThreshold.Location = new System.Drawing.Point(227, 4);
this.numClearCacheThreshold.Maximum = 1000;
this.numClearCacheThreshold.Minimum = 100;
this.numClearCacheThreshold.Name = "numClearCacheThreshold";
this.numClearCacheThreshold.Size = new System.Drawing.Size(72, 20);
this.numClearCacheThreshold.TabIndex = 4;
this.numClearCacheThreshold.TextSuffix = " MB";
this.numClearCacheThreshold.Value = 250;
//
// checkClearCacheAuto
//
this.checkClearCacheAuto.AutoSize = true;
this.checkClearCacheAuto.Location = new System.Drawing.Point(6, 6);
this.checkClearCacheAuto.Margin = new System.Windows.Forms.Padding(6, 6, 3, 3);
this.checkClearCacheAuto.Name = "checkClearCacheAuto";
this.checkClearCacheAuto.Size = new System.Drawing.Size(215, 17);
this.checkClearCacheAuto.TabIndex = 3;
this.checkClearCacheAuto.Text = "Clear Cache Automatically When Above";
this.checkClearCacheAuto.UseVisualStyleBackColor = true;
//
// labelApp // labelApp
// //
this.labelApp.AutoSize = true; this.labelApp.AutoSize = true;
this.labelApp.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.labelApp.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelApp.Location = new System.Drawing.Point(6, 8); this.labelApp.Location = new System.Drawing.Point(0, 0);
this.labelApp.Margin = new System.Windows.Forms.Padding(0, 2, 0, 0); this.labelApp.Margin = new System.Windows.Forms.Padding(0);
this.labelApp.Name = "labelApp"; this.labelApp.Name = "labelApp";
this.labelApp.Size = new System.Drawing.Size(38, 20); this.labelApp.Size = new System.Drawing.Size(38, 20);
this.labelApp.TabIndex = 0; this.labelApp.TabIndex = 0;
this.labelApp.Text = "App"; this.labelApp.Text = "App";
// //
// panelApp // panelAppButtons
// //
this.panelApp.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) this.panelAppButtons.Anchor = System.Windows.Forms.AnchorStyles.Top;
| System.Windows.Forms.AnchorStyles.Right))); this.panelAppButtons.Controls.Add(this.btnOpenDataFolder);
this.panelApp.Controls.Add(this.btnOpenDataFolder); this.panelAppButtons.Controls.Add(this.btnOpenAppFolder);
this.panelApp.Controls.Add(this.btnOpenAppFolder); this.panelAppButtons.Controls.Add(this.btnRestart);
this.panelApp.Controls.Add(this.btnRestart); this.panelAppButtons.Controls.Add(this.btnRestartArgs);
this.panelApp.Controls.Add(this.btnRestartArgs); this.panelAppButtons.Location = new System.Drawing.Point(0, 20);
this.panelApp.Location = new System.Drawing.Point(9, 31); this.panelAppButtons.Margin = new System.Windows.Forms.Padding(0);
this.panelApp.Name = "panelApp"; this.panelAppButtons.Name = "panelAppButtons";
this.panelApp.Size = new System.Drawing.Size(322, 59); this.panelAppButtons.Size = new System.Drawing.Size(322, 58);
this.panelApp.TabIndex = 1; this.panelAppButtons.TabIndex = 1;
// //
// labelPerformance // labelPerformance
// //
this.labelPerformance.AutoSize = true; this.labelPerformance.AutoSize = true;
this.labelPerformance.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.labelPerformance.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelPerformance.Location = new System.Drawing.Point(6, 114); this.labelPerformance.Location = new System.Drawing.Point(0, 98);
this.labelPerformance.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0); this.labelPerformance.Margin = new System.Windows.Forms.Padding(0, 20, 0, 0);
this.labelPerformance.Name = "labelPerformance"; this.labelPerformance.Name = "labelPerformance";
this.labelPerformance.Size = new System.Drawing.Size(100, 20); this.labelPerformance.Size = new System.Drawing.Size(100, 20);
this.labelPerformance.TabIndex = 2; this.labelPerformance.TabIndex = 2;
this.labelPerformance.Text = "Performance"; this.labelPerformance.Text = "Performance";
// //
// panelPerformance // panelClearCacheAuto
// //
this.panelPerformance.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) this.panelClearCacheAuto.Anchor = System.Windows.Forms.AnchorStyles.Top;
| System.Windows.Forms.AnchorStyles.Right))); this.panelClearCacheAuto.Controls.Add(this.checkClearCacheAuto);
this.panelPerformance.Controls.Add(this.checkHardwareAcceleration); this.panelClearCacheAuto.Controls.Add(this.numClearCacheThreshold);
this.panelPerformance.Controls.Add(this.btnClearCache); this.panelClearCacheAuto.Location = new System.Drawing.Point(0, 198);
this.panelPerformance.Location = new System.Drawing.Point(9, 137); this.panelClearCacheAuto.Margin = new System.Windows.Forms.Padding(0);
this.panelPerformance.Name = "panelPerformance"; this.panelClearCacheAuto.Name = "panelClearCacheAuto";
this.panelPerformance.Size = new System.Drawing.Size(322, 54); this.panelClearCacheAuto.Size = new System.Drawing.Size(322, 26);
this.panelPerformance.TabIndex = 3; this.panelClearCacheAuto.TabIndex = 3;
//
// labelCache
//
this.labelCache.AutoSize = true;
this.labelCache.Location = new System.Drawing.Point(3, 156);
this.labelCache.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelCache.Name = "labelCache";
this.labelCache.Size = new System.Drawing.Size(38, 13);
this.labelCache.TabIndex = 2;
this.labelCache.Text = "Cache";
// //
// panelConfiguration // panelConfiguration
// //
this.panelConfiguration.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) this.panelConfiguration.Anchor = System.Windows.Forms.AnchorStyles.Top;
| System.Windows.Forms.AnchorStyles.Right)));
this.panelConfiguration.Controls.Add(this.btnEditCSS); this.panelConfiguration.Controls.Add(this.btnEditCSS);
this.panelConfiguration.Controls.Add(this.btnEditCefArgs); this.panelConfiguration.Controls.Add(this.btnEditCefArgs);
this.panelConfiguration.Location = new System.Drawing.Point(9, 238); this.panelConfiguration.Location = new System.Drawing.Point(0, 264);
this.panelConfiguration.Margin = new System.Windows.Forms.Padding(0);
this.panelConfiguration.Name = "panelConfiguration"; this.panelConfiguration.Name = "panelConfiguration";
this.panelConfiguration.Size = new System.Drawing.Size(322, 29); this.panelConfiguration.Size = new System.Drawing.Size(322, 29);
this.panelConfiguration.TabIndex = 5; this.panelConfiguration.TabIndex = 5;
@@ -194,31 +222,49 @@
// //
this.labelConfiguration.AutoSize = true; this.labelConfiguration.AutoSize = true;
this.labelConfiguration.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.labelConfiguration.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelConfiguration.Location = new System.Drawing.Point(6, 215); this.labelConfiguration.Location = new System.Drawing.Point(0, 244);
this.labelConfiguration.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0); this.labelConfiguration.Margin = new System.Windows.Forms.Padding(0, 20, 0, 0);
this.labelConfiguration.Name = "labelConfiguration"; this.labelConfiguration.Name = "labelConfiguration";
this.labelConfiguration.Size = new System.Drawing.Size(104, 20); this.labelConfiguration.Size = new System.Drawing.Size(104, 20);
this.labelConfiguration.TabIndex = 4; this.labelConfiguration.TabIndex = 4;
this.labelConfiguration.Text = "Configuration"; this.labelConfiguration.Text = "Configuration";
// //
// flowPanel
//
this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.flowPanel.Controls.Add(this.labelApp);
this.flowPanel.Controls.Add(this.panelAppButtons);
this.flowPanel.Controls.Add(this.labelPerformance);
this.flowPanel.Controls.Add(this.checkHardwareAcceleration);
this.flowPanel.Controls.Add(this.labelCache);
this.flowPanel.Controls.Add(this.btnClearCache);
this.flowPanel.Controls.Add(this.panelClearCacheAuto);
this.flowPanel.Controls.Add(this.labelConfiguration);
this.flowPanel.Controls.Add(this.panelConfiguration);
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanel.Location = new System.Drawing.Point(9, 9);
this.flowPanel.Name = "flowPanel";
this.flowPanel.Size = new System.Drawing.Size(322, 295);
this.flowPanel.TabIndex = 6;
this.flowPanel.WrapContents = false;
//
// TabSettingsAdvanced // TabSettingsAdvanced
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.labelConfiguration); this.Controls.Add(this.flowPanel);
this.Controls.Add(this.panelConfiguration);
this.Controls.Add(this.panelPerformance);
this.Controls.Add(this.labelPerformance);
this.Controls.Add(this.panelApp);
this.Controls.Add(this.labelApp);
this.Name = "TabSettingsAdvanced"; this.Name = "TabSettingsAdvanced";
this.Size = new System.Drawing.Size(340, 277); this.Size = new System.Drawing.Size(340, 313);
this.panelApp.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.numClearCacheThreshold)).EndInit();
this.panelPerformance.ResumeLayout(false); this.panelAppButtons.ResumeLayout(false);
this.panelPerformance.PerformLayout(); this.panelClearCacheAuto.ResumeLayout(false);
this.panelClearCacheAuto.PerformLayout();
this.panelConfiguration.ResumeLayout(false); this.panelConfiguration.ResumeLayout(false);
this.flowPanel.ResumeLayout(false);
this.flowPanel.PerformLayout();
this.ResumeLayout(false); this.ResumeLayout(false);
this.PerformLayout();
} }
@@ -234,10 +280,14 @@
private System.Windows.Forms.Button btnOpenAppFolder; private System.Windows.Forms.Button btnOpenAppFolder;
private System.Windows.Forms.Button btnOpenDataFolder; private System.Windows.Forms.Button btnOpenDataFolder;
private System.Windows.Forms.Label labelApp; private System.Windows.Forms.Label labelApp;
private System.Windows.Forms.Panel panelApp; private System.Windows.Forms.Panel panelAppButtons;
private System.Windows.Forms.Label labelPerformance; private System.Windows.Forms.Label labelPerformance;
private System.Windows.Forms.Panel panelPerformance; private System.Windows.Forms.Panel panelClearCacheAuto;
private System.Windows.Forms.Panel panelConfiguration; private System.Windows.Forms.Panel panelConfiguration;
private System.Windows.Forms.Label labelConfiguration; private System.Windows.Forms.Label labelConfiguration;
private System.Windows.Forms.Label labelCache;
private Controls.NumericUpDownEx numClearCacheThreshold;
private System.Windows.Forms.CheckBox checkClearCacheAuto;
private System.Windows.Forms.FlowLayoutPanel flowPanel;
} }
} }

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
@@ -7,7 +8,9 @@ using TweetDuck.Core.Other.Settings.Dialogs;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Other.Settings{ namespace TweetDuck.Core.Other.Settings{
partial class TabSettingsAdvanced : BaseTabSettings{ sealed partial class TabSettingsAdvanced : BaseTabSettings{
private static SystemConfig SysConfig => Program.SystemConfig;
private readonly Action<string> reinjectBrowserCSS; private readonly Action<string> reinjectBrowserCSS;
public TabSettingsAdvanced(Action<string> reinjectBrowserCSS){ public TabSettingsAdvanced(Action<string> reinjectBrowserCSS){
@@ -15,48 +18,71 @@ namespace TweetDuck.Core.Other.Settings{
this.reinjectBrowserCSS = reinjectBrowserCSS; this.reinjectBrowserCSS = reinjectBrowserCSS;
toolTip.SetToolTip(btnOpenAppFolder, "Opens the folder where the app is located.");
toolTip.SetToolTip(btnOpenDataFolder, "Opens the folder where your profile data is located.");
toolTip.SetToolTip(btnRestart, "Restarts the program using the same command\r\nline arguments that were used at launch.");
toolTip.SetToolTip(btnRestartArgs, "Restarts the program with customizable\r\ncommand line arguments.");
toolTip.SetToolTip(checkHardwareAcceleration, "Uses graphics card to improve performance. Disable if you experience\r\nvisual glitches. This option will not be exported in a profile.");
toolTip.SetToolTip(btnClearCache, "Clearing cache will free up space taken by downloaded images and other resources.");
toolTip.SetToolTip(checkClearCacheAuto, "Automatically clears cache when its size exceeds the set threshold. Note that cache can only be cleared when closing TweetDuck.");
toolTip.SetToolTip(btnEditCefArgs, "Set custom command line arguments for Chromium Embedded Framework.");
toolTip.SetToolTip(btnEditCSS, "Set custom CSS for browser and notification windows.");
if (SystemConfig.IsHardwareAccelerationSupported){ if (SystemConfig.IsHardwareAccelerationSupported){
checkHardwareAcceleration.Checked = Program.SystemConfig.HardwareAcceleration; checkHardwareAcceleration.Checked = SysConfig.HardwareAcceleration;
} }
else{ else{
checkHardwareAcceleration.Enabled = false; checkHardwareAcceleration.Enabled = false;
checkHardwareAcceleration.Checked = false; checkHardwareAcceleration.Checked = false;
} }
BrowserCache.CalculateCacheSize(bytes => this.InvokeSafe(() => { checkClearCacheAuto.Checked = SysConfig.ClearCacheAutomatically;
if (bytes == -1L){ numClearCacheThreshold.Enabled = checkClearCacheAuto.Checked;
btnClearCache.Text = "Clear Cache (unknown size)"; numClearCacheThreshold.SetValueSafe(SysConfig.ClearCacheThreshold);
}
else{ BrowserCache.GetCacheSize(task => {
btnClearCache.Text = "Clear Cache ("+(int)Math.Ceiling(bytes/(1024.0*1024.0))+" MB)"; string text = task.Status == TaskStatus.RanToCompletion ? (int)Math.Ceiling(task.Result/(1024.0*1024.0))+" MB" : "unknown";
} this.InvokeSafe(() => btnClearCache.Text = $"Clear Cache ({text})");
})); });
} }
public override void OnReady(){ public override void OnReady(){
btnClearCache.Click += btnClearCache_Click;
checkHardwareAcceleration.CheckedChanged += checkHardwareAcceleration_CheckedChanged;
btnEditCefArgs.Click += btnEditCefArgs_Click;
btnEditCSS.Click += btnEditCSS_Click;
btnOpenAppFolder.Click += btnOpenAppFolder_Click; btnOpenAppFolder.Click += btnOpenAppFolder_Click;
btnOpenDataFolder.Click += btnOpenDataFolder_Click; btnOpenDataFolder.Click += btnOpenDataFolder_Click;
btnRestart.Click += btnRestart_Click; btnRestart.Click += btnRestart_Click;
btnRestartArgs.Click += btnRestartArgs_Click; btnRestartArgs.Click += btnRestartArgs_Click;
checkHardwareAcceleration.CheckedChanged += checkHardwareAcceleration_CheckedChanged;
btnClearCache.Click += btnClearCache_Click;
checkClearCacheAuto.CheckedChanged += checkClearCacheAuto_CheckedChanged;
btnEditCefArgs.Click += btnEditCefArgs_Click;
btnEditCSS.Click += btnEditCSS_Click;
}
public override void OnClosing(){
SysConfig.ClearCacheAutomatically = checkClearCacheAuto.Checked;
SysConfig.ClearCacheThreshold = (int)numClearCacheThreshold.Value;
SysConfig.Save();
} }
private void btnClearCache_Click(object sender, EventArgs e){ private void btnClearCache_Click(object sender, EventArgs e){
btnClearCache.Enabled = false; btnClearCache.Enabled = false;
BrowserCache.SetClearOnExit(); BrowserCache.SetClearOnExit();
FormMessage.Information("Clear Cache", "Cache will be automatically cleared when TweetDuck exits.", FormMessage.OK);
}
MessageBox.Show("Cache will be automatically cleared when "+Program.BrandName+" exits.", "Clear Cache", MessageBoxButtons.OK, MessageBoxIcon.Information); private void checkClearCacheAuto_CheckedChanged(object sender, EventArgs e){
numClearCacheThreshold.Enabled = checkClearCacheAuto.Checked;
} }
private void checkHardwareAcceleration_CheckedChanged(object sender, EventArgs e){ private void checkHardwareAcceleration_CheckedChanged(object sender, EventArgs e){
Program.SystemConfig.HardwareAcceleration = checkHardwareAcceleration.Checked; SysConfig.HardwareAcceleration = checkHardwareAcceleration.Checked;
Program.SystemConfig.Save(); PromptRestart(); // calls OnClosing
PromptRestart();
} }
private void btnEditCefArgs_Click(object sender, EventArgs e){ private void btnEditCefArgs_Click(object sender, EventArgs e){
@@ -67,7 +93,7 @@ namespace TweetDuck.Core.Other.Settings{
}; };
form.FormClosed += (sender2, args2) => { form.FormClosed += (sender2, args2) => {
NativeMethods.SetFormDisabled(ParentForm, false); RestoreParentForm();
if (form.DialogResult == DialogResult.OK){ if (form.DialogResult == DialogResult.OK){
Config.CustomCefArgs = form.CefArgs; Config.CustomCefArgs = form.CefArgs;
@@ -89,7 +115,7 @@ namespace TweetDuck.Core.Other.Settings{
}; };
form.FormClosed += (sender2, args2) => { form.FormClosed += (sender2, args2) => {
NativeMethods.SetFormDisabled(ParentForm, false); RestoreParentForm();
if (form.DialogResult == DialogResult.OK){ if (form.DialogResult == DialogResult.OK){
Config.CustomBrowserCSS = form.BrowserCSS; Config.CustomBrowserCSS = form.BrowserCSS;
@@ -123,5 +149,11 @@ namespace TweetDuck.Core.Other.Settings{
} }
} }
} }
private void RestoreParentForm(){
if (ParentForm != null){ // when the parent is closed first, ParentForm is null in FormClosed event
NativeMethods.SetFormDisabled(ParentForm, false);
}
}
} }
} }

View File

@@ -0,0 +1,175 @@
namespace TweetDuck.Core.Other.Settings {
partial class TabSettingsFeedback {
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
this.panelDataCollection = new System.Windows.Forms.Panel();
this.labelDataCollectionLink = new System.Windows.Forms.LinkLabel();
this.checkDataCollection = new System.Windows.Forms.CheckBox();
this.labelDataCollectionMessage = new System.Windows.Forms.Label();
this.btnViewReport = new System.Windows.Forms.Button();
this.btnSendFeedback = new System.Windows.Forms.Button();
this.labelDataCollection = new System.Windows.Forms.Label();
this.labelFeedback = new System.Windows.Forms.Label();
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
this.panelDataCollection.SuspendLayout();
this.flowPanel.SuspendLayout();
this.SuspendLayout();
//
// panelDataCollection
//
this.panelDataCollection.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.panelDataCollection.Controls.Add(this.labelDataCollectionLink);
this.panelDataCollection.Controls.Add(this.checkDataCollection);
this.panelDataCollection.Location = new System.Drawing.Point(0, 74);
this.panelDataCollection.Margin = new System.Windows.Forms.Padding(0);
this.panelDataCollection.Name = "panelDataCollection";
this.panelDataCollection.Size = new System.Drawing.Size(322, 26);
this.panelDataCollection.TabIndex = 1;
//
// labelDataCollectionLink
//
this.labelDataCollectionLink.AutoSize = true;
this.labelDataCollectionLink.LinkArea = new System.Windows.Forms.LinkArea(1, 10);
this.labelDataCollectionLink.LinkBehavior = System.Windows.Forms.LinkBehavior.HoverUnderline;
this.labelDataCollectionLink.Location = new System.Drawing.Point(141, 6);
this.labelDataCollectionLink.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.labelDataCollectionLink.Name = "labelDataCollectionLink";
this.labelDataCollectionLink.Size = new System.Drawing.Size(66, 17);
this.labelDataCollectionLink.TabIndex = 3;
this.labelDataCollectionLink.TabStop = true;
this.labelDataCollectionLink.Text = "(learn more)";
this.labelDataCollectionLink.UseCompatibleTextRendering = true;
//
// checkDataCollection
//
this.checkDataCollection.AutoSize = true;
this.checkDataCollection.Location = new System.Drawing.Point(6, 6);
this.checkDataCollection.Margin = new System.Windows.Forms.Padding(6, 6, 0, 3);
this.checkDataCollection.Name = "checkDataCollection";
this.checkDataCollection.Size = new System.Drawing.Size(135, 17);
this.checkDataCollection.TabIndex = 2;
this.checkDataCollection.Text = "Send Anonymous Data";
this.checkDataCollection.UseVisualStyleBackColor = true;
//
// labelDataCollectionMessage
//
this.labelDataCollectionMessage.Location = new System.Drawing.Point(6, 135);
this.labelDataCollectionMessage.Margin = new System.Windows.Forms.Padding(6);
this.labelDataCollectionMessage.Name = "labelDataCollectionMessage";
this.labelDataCollectionMessage.Size = new System.Drawing.Size(310, 67);
this.labelDataCollectionMessage.TabIndex = 5;
//
// btnViewReport
//
this.btnViewReport.AutoSize = true;
this.btnViewReport.Location = new System.Drawing.Point(5, 103);
this.btnViewReport.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.btnViewReport.Name = "btnViewReport";
this.btnViewReport.Size = new System.Drawing.Size(144, 23);
this.btnViewReport.TabIndex = 4;
this.btnViewReport.Text = "View My Analytics Report";
this.btnViewReport.UseVisualStyleBackColor = true;
//
// btnSendFeedback
//
this.btnSendFeedback.AutoSize = true;
this.btnSendFeedback.Location = new System.Drawing.Point(5, 23);
this.btnSendFeedback.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.btnSendFeedback.Name = "btnSendFeedback";
this.btnSendFeedback.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnSendFeedback.Size = new System.Drawing.Size(164, 23);
this.btnSendFeedback.TabIndex = 0;
this.btnSendFeedback.Text = "Send Feedback / Bug Report";
this.btnSendFeedback.UseVisualStyleBackColor = true;
//
// labelDataCollection
//
this.labelDataCollection.AutoSize = true;
this.labelDataCollection.Location = new System.Drawing.Point(3, 61);
this.labelDataCollection.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelDataCollection.Name = "labelDataCollection";
this.labelDataCollection.Size = new System.Drawing.Size(79, 13);
this.labelDataCollection.TabIndex = 1;
this.labelDataCollection.Text = "Data Collection";
//
// labelFeedback
//
this.labelFeedback.AutoSize = true;
this.labelFeedback.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelFeedback.Location = new System.Drawing.Point(0, 0);
this.labelFeedback.Margin = new System.Windows.Forms.Padding(0);
this.labelFeedback.Name = "labelFeedback";
this.labelFeedback.Size = new System.Drawing.Size(80, 20);
this.labelFeedback.TabIndex = 0;
this.labelFeedback.Text = "Feedback";
//
// flowPanel
//
this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.flowPanel.Controls.Add(this.labelFeedback);
this.flowPanel.Controls.Add(this.btnSendFeedback);
this.flowPanel.Controls.Add(this.labelDataCollection);
this.flowPanel.Controls.Add(this.panelDataCollection);
this.flowPanel.Controls.Add(this.btnViewReport);
this.flowPanel.Controls.Add(this.labelDataCollectionMessage);
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanel.Location = new System.Drawing.Point(9, 9);
this.flowPanel.Name = "flowPanel";
this.flowPanel.Size = new System.Drawing.Size(322, 209);
this.flowPanel.TabIndex = 2;
this.flowPanel.WrapContents = false;
//
// TabSettingsFeedback
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.flowPanel);
this.Name = "TabSettingsFeedback";
this.Size = new System.Drawing.Size(340, 227);
this.panelDataCollection.ResumeLayout(false);
this.panelDataCollection.PerformLayout();
this.flowPanel.ResumeLayout(false);
this.flowPanel.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Panel panelDataCollection;
private System.Windows.Forms.CheckBox checkDataCollection;
private System.Windows.Forms.Label labelDataCollection;
private System.Windows.Forms.Label labelFeedback;
private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.LinkLabel labelDataCollectionLink;
private System.Windows.Forms.Button btnSendFeedback;
private System.Windows.Forms.Button btnViewReport;
private System.Windows.Forms.Label labelDataCollectionMessage;
private System.Windows.Forms.FlowLayoutPanel flowPanel;
}
}

View File

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

View File

@@ -25,161 +25,126 @@
private void InitializeComponent() { private void InitializeComponent() {
this.components = new System.ComponentModel.Container(); this.components = new System.ComponentModel.Container();
this.checkExpandLinks = new System.Windows.Forms.CheckBox(); this.checkExpandLinks = new System.Windows.Forms.CheckBox();
this.comboBoxTrayType = new System.Windows.Forms.ComboBox();
this.toolTip = new System.Windows.Forms.ToolTip(this.components); 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.checkUpdateNotifications = new System.Windows.Forms.CheckBox();
this.btnCheckUpdates = new System.Windows.Forms.Button(); this.btnCheckUpdates = new System.Windows.Forms.Button();
this.labelZoomValue = new System.Windows.Forms.Label(); this.labelZoomValue = new System.Windows.Forms.Label();
this.checkSwitchAccountSelectors = new System.Windows.Forms.CheckBox(); this.checkSwitchAccountSelectors = new System.Windows.Forms.CheckBox();
this.labelTrayIcon = new System.Windows.Forms.Label(); this.checkBestImageQuality = new System.Windows.Forms.CheckBox();
this.checkOpenSearchInFirstColumn = new System.Windows.Forms.CheckBox();
this.trackBarZoom = new System.Windows.Forms.TrackBar(); this.trackBarZoom = new System.Windows.Forms.TrackBar();
this.labelZoom = new System.Windows.Forms.Label(); this.labelZoom = new System.Windows.Forms.Label();
this.zoomUpdateTimer = new System.Windows.Forms.Timer(this.components); this.zoomUpdateTimer = new System.Windows.Forms.Timer(this.components);
this.labelUI = new System.Windows.Forms.Label(); this.labelUI = new System.Windows.Forms.Label();
this.panelUI = new System.Windows.Forms.Panel(); this.panelZoom = new System.Windows.Forms.Panel();
this.labelTray = new System.Windows.Forms.Label(); this.checkAnimatedAvatars = new System.Windows.Forms.CheckBox();
this.panelUpdates = new System.Windows.Forms.Panel();
this.panelTray = new System.Windows.Forms.Panel();
this.labelUpdates = new System.Windows.Forms.Label(); this.labelUpdates = new System.Windows.Forms.Label();
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
this.checkKeepLikeFollowDialogsOpen = new System.Windows.Forms.CheckBox();
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).BeginInit();
this.panelUI.SuspendLayout(); this.panelZoom.SuspendLayout();
this.panelUpdates.SuspendLayout(); this.flowPanel.SuspendLayout();
this.panelTray.SuspendLayout();
this.SuspendLayout(); this.SuspendLayout();
// //
// checkExpandLinks // checkExpandLinks
// //
this.checkExpandLinks.AutoSize = true; this.checkExpandLinks.AutoSize = true;
this.checkExpandLinks.Location = new System.Drawing.Point(6, 5); this.checkExpandLinks.Location = new System.Drawing.Point(6, 26);
this.checkExpandLinks.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3); this.checkExpandLinks.Margin = new System.Windows.Forms.Padding(6, 6, 3, 3);
this.checkExpandLinks.Name = "checkExpandLinks"; this.checkExpandLinks.Name = "checkExpandLinks";
this.checkExpandLinks.Size = new System.Drawing.Size(166, 17); this.checkExpandLinks.Size = new System.Drawing.Size(166, 17);
this.checkExpandLinks.TabIndex = 0; this.checkExpandLinks.TabIndex = 0;
this.checkExpandLinks.Text = "Expand Links When Hovered"; 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.UseVisualStyleBackColor = true;
// //
// comboBoxTrayType
//
this.comboBoxTrayType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxTrayType.FormattingEnabled = true;
this.comboBoxTrayType.Location = new System.Drawing.Point(5, 5);
this.comboBoxTrayType.Margin = new System.Windows.Forms.Padding(5, 5, 3, 3);
this.comboBoxTrayType.Name = "comboBoxTrayType";
this.comboBoxTrayType.Size = new System.Drawing.Size(144, 21);
this.comboBoxTrayType.TabIndex = 0;
this.toolTip.SetToolTip(this.comboBoxTrayType, "Changes behavior of the Tray icon.\r\nRight-click the icon for an action menu.");
//
// checkTrayHighlight
//
this.checkTrayHighlight.AutoSize = true;
this.checkTrayHighlight.Location = new System.Drawing.Point(6, 56);
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 = 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;
//
// checkSpellCheck
//
this.checkSpellCheck.AutoSize = true;
this.checkSpellCheck.Location = new System.Drawing.Point(6, 51);
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 // checkUpdateNotifications
// //
this.checkUpdateNotifications.AutoSize = true; this.checkUpdateNotifications.AutoSize = true;
this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 5); this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 268);
this.checkUpdateNotifications.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3); this.checkUpdateNotifications.Margin = new System.Windows.Forms.Padding(6, 6, 3, 3);
this.checkUpdateNotifications.Name = "checkUpdateNotifications"; this.checkUpdateNotifications.Name = "checkUpdateNotifications";
this.checkUpdateNotifications.Size = new System.Drawing.Size(165, 17); this.checkUpdateNotifications.Size = new System.Drawing.Size(165, 17);
this.checkUpdateNotifications.TabIndex = 0; this.checkUpdateNotifications.TabIndex = 0;
this.checkUpdateNotifications.Text = "Check Updates Automatically"; 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.UseVisualStyleBackColor = true;
// //
// btnCheckUpdates // btnCheckUpdates
// //
this.btnCheckUpdates.Location = new System.Drawing.Point(5, 28); this.btnCheckUpdates.Location = new System.Drawing.Point(5, 291);
this.btnCheckUpdates.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3); this.btnCheckUpdates.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.btnCheckUpdates.Name = "btnCheckUpdates"; this.btnCheckUpdates.Name = "btnCheckUpdates";
this.btnCheckUpdates.Size = new System.Drawing.Size(144, 23); this.btnCheckUpdates.Size = new System.Drawing.Size(144, 23);
this.btnCheckUpdates.TabIndex = 1; this.btnCheckUpdates.TabIndex = 1;
this.btnCheckUpdates.Text = "Check Updates Now"; 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.UseVisualStyleBackColor = true;
// //
// labelZoomValue // labelZoomValue
// //
this.labelZoomValue.BackColor = System.Drawing.Color.Transparent; this.labelZoomValue.BackColor = System.Drawing.Color.Transparent;
this.labelZoomValue.Location = new System.Drawing.Point(141, 100); this.labelZoomValue.Location = new System.Drawing.Point(147, 4);
this.labelZoomValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0); this.labelZoomValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.labelZoomValue.Name = "labelZoomValue"; this.labelZoomValue.Name = "labelZoomValue";
this.labelZoomValue.Size = new System.Drawing.Size(38, 13); this.labelZoomValue.Size = new System.Drawing.Size(38, 13);
this.labelZoomValue.TabIndex = 5; this.labelZoomValue.TabIndex = 8;
this.labelZoomValue.Text = "100%"; this.labelZoomValue.Text = "100%";
this.labelZoomValue.TextAlign = System.Drawing.ContentAlignment.TopRight; this.labelZoomValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
this.toolTip.SetToolTip(this.labelZoomValue, "Changes the zoom level.\r\nAlso affects notifications and screenshots.");
// //
// checkSwitchAccountSelectors // checkSwitchAccountSelectors
// //
this.checkSwitchAccountSelectors.AutoSize = true; this.checkSwitchAccountSelectors.AutoSize = true;
this.checkSwitchAccountSelectors.Location = new System.Drawing.Point(6, 28); this.checkSwitchAccountSelectors.Location = new System.Drawing.Point(6, 49);
this.checkSwitchAccountSelectors.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3); this.checkSwitchAccountSelectors.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkSwitchAccountSelectors.Name = "checkSwitchAccountSelectors"; this.checkSwitchAccountSelectors.Name = "checkSwitchAccountSelectors";
this.checkSwitchAccountSelectors.Size = new System.Drawing.Size(172, 17); this.checkSwitchAccountSelectors.Size = new System.Drawing.Size(172, 17);
this.checkSwitchAccountSelectors.TabIndex = 1; this.checkSwitchAccountSelectors.TabIndex = 1;
this.checkSwitchAccountSelectors.Text = "Shift Selects Multiple Accounts"; 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; this.checkSwitchAccountSelectors.UseVisualStyleBackColor = true;
// //
// labelTrayIcon // checkBestImageQuality
// //
this.labelTrayIcon.AutoSize = true; this.checkBestImageQuality.AutoSize = true;
this.labelTrayIcon.Location = new System.Drawing.Point(3, 38); this.checkBestImageQuality.Location = new System.Drawing.Point(6, 118);
this.labelTrayIcon.Margin = new System.Windows.Forms.Padding(3, 9, 3, 0); this.checkBestImageQuality.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.labelTrayIcon.Name = "labelTrayIcon"; this.checkBestImageQuality.Name = "checkBestImageQuality";
this.labelTrayIcon.Size = new System.Drawing.Size(52, 13); this.checkBestImageQuality.Size = new System.Drawing.Size(114, 17);
this.labelTrayIcon.TabIndex = 1; this.checkBestImageQuality.TabIndex = 3;
this.labelTrayIcon.Text = "Tray Icon"; this.checkBestImageQuality.Text = "Best Image Quality";
this.checkBestImageQuality.UseVisualStyleBackColor = true;
//
// checkOpenSearchInFirstColumn
//
this.checkOpenSearchInFirstColumn.AutoSize = true;
this.checkOpenSearchInFirstColumn.Location = new System.Drawing.Point(6, 72);
this.checkOpenSearchInFirstColumn.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkOpenSearchInFirstColumn.Name = "checkOpenSearchInFirstColumn";
this.checkOpenSearchInFirstColumn.Size = new System.Drawing.Size(219, 17);
this.checkOpenSearchInFirstColumn.TabIndex = 2;
this.checkOpenSearchInFirstColumn.Text = "Add Search Columns Before First Column";
this.checkOpenSearchInFirstColumn.UseVisualStyleBackColor = true;
// //
// trackBarZoom // trackBarZoom
// //
this.trackBarZoom.AutoSize = false; this.trackBarZoom.AutoSize = false;
this.trackBarZoom.BackColor = System.Drawing.SystemColors.Control; this.trackBarZoom.BackColor = System.Drawing.SystemColors.Control;
this.trackBarZoom.LargeChange = 25; this.trackBarZoom.LargeChange = 25;
this.trackBarZoom.Location = new System.Drawing.Point(3, 99); this.trackBarZoom.Location = new System.Drawing.Point(3, 3);
this.trackBarZoom.Maximum = 200; this.trackBarZoom.Maximum = 200;
this.trackBarZoom.Minimum = 50; this.trackBarZoom.Minimum = 50;
this.trackBarZoom.Name = "trackBarZoom"; this.trackBarZoom.Name = "trackBarZoom";
this.trackBarZoom.Size = new System.Drawing.Size(148, 30); this.trackBarZoom.Size = new System.Drawing.Size(148, 30);
this.trackBarZoom.SmallChange = 5; this.trackBarZoom.SmallChange = 5;
this.trackBarZoom.TabIndex = 4; this.trackBarZoom.TabIndex = 7;
this.trackBarZoom.TickFrequency = 25; this.trackBarZoom.TickFrequency = 25;
this.trackBarZoom.Value = 100; this.trackBarZoom.Value = 100;
// //
// labelZoom // labelZoom
// //
this.labelZoom.AutoSize = true; this.labelZoom.AutoSize = true;
this.labelZoom.Location = new System.Drawing.Point(3, 83); this.labelZoom.Location = new System.Drawing.Point(3, 173);
this.labelZoom.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0); this.labelZoom.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelZoom.Name = "labelZoom"; this.labelZoom.Name = "labelZoom";
this.labelZoom.Size = new System.Drawing.Size(34, 13); this.labelZoom.Size = new System.Drawing.Size(34, 13);
this.labelZoom.TabIndex = 3; this.labelZoom.TabIndex = 6;
this.labelZoom.Text = "Zoom"; this.labelZoom.Text = "Zoom";
// //
// zoomUpdateTimer // zoomUpdateTimer
@@ -191,105 +156,100 @@
// //
this.labelUI.AutoSize = true; this.labelUI.AutoSize = true;
this.labelUI.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.labelUI.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelUI.Location = new System.Drawing.Point(6, 8); this.labelUI.Location = new System.Drawing.Point(0, 0);
this.labelUI.Margin = new System.Windows.Forms.Padding(0, 2, 0, 0); this.labelUI.Margin = new System.Windows.Forms.Padding(0);
this.labelUI.Name = "labelUI"; this.labelUI.Name = "labelUI";
this.labelUI.Size = new System.Drawing.Size(111, 20); this.labelUI.Size = new System.Drawing.Size(111, 20);
this.labelUI.TabIndex = 0; this.labelUI.TabIndex = 0;
this.labelUI.Text = "User Interface"; this.labelUI.Text = "User Interface";
// //
// panelUI // panelZoom
// //
this.panelUI.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) this.panelZoom.Anchor = System.Windows.Forms.AnchorStyles.Top;
| System.Windows.Forms.AnchorStyles.Right))); this.panelZoom.Controls.Add(this.trackBarZoom);
this.panelUI.Controls.Add(this.checkExpandLinks); this.panelZoom.Controls.Add(this.labelZoomValue);
this.panelUI.Controls.Add(this.checkSwitchAccountSelectors); this.panelZoom.Location = new System.Drawing.Point(0, 186);
this.panelUI.Controls.Add(this.checkSpellCheck); this.panelZoom.Margin = new System.Windows.Forms.Padding(0);
this.panelUI.Controls.Add(this.labelZoom); this.panelZoom.Name = "panelZoom";
this.panelUI.Controls.Add(this.labelZoomValue); this.panelZoom.Size = new System.Drawing.Size(322, 36);
this.panelUI.Controls.Add(this.trackBarZoom); this.panelZoom.TabIndex = 1;
this.panelUI.Location = new System.Drawing.Point(9, 31);
this.panelUI.Name = "panelUI";
this.panelUI.Size = new System.Drawing.Size(322, 134);
this.panelUI.TabIndex = 1;
// //
// labelTray // checkAnimatedAvatars
// //
this.labelTray.AutoSize = true; this.checkAnimatedAvatars.AutoSize = true;
this.labelTray.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.checkAnimatedAvatars.Location = new System.Drawing.Point(6, 141);
this.labelTray.Location = new System.Drawing.Point(5, 189); this.checkAnimatedAvatars.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.labelTray.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0); this.checkAnimatedAvatars.Name = "checkAnimatedAvatars";
this.labelTray.Name = "labelTray"; this.checkAnimatedAvatars.Size = new System.Drawing.Size(145, 17);
this.labelTray.Size = new System.Drawing.Size(96, 20); this.checkAnimatedAvatars.TabIndex = 4;
this.labelTray.TabIndex = 2; this.checkAnimatedAvatars.Text = "Enable Animated Avatars";
this.labelTray.Text = "System Tray"; this.checkAnimatedAvatars.UseVisualStyleBackColor = true;
//
// panelUpdates
//
this.panelUpdates.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelUpdates.Controls.Add(this.checkUpdateNotifications);
this.panelUpdates.Controls.Add(this.btnCheckUpdates);
this.panelUpdates.Location = new System.Drawing.Point(9, 335);
this.panelUpdates.Name = "panelUpdates";
this.panelUpdates.Size = new System.Drawing.Size(322, 55);
this.panelUpdates.TabIndex = 5;
//
// panelTray
//
this.panelTray.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelTray.Controls.Add(this.checkTrayHighlight);
this.panelTray.Controls.Add(this.comboBoxTrayType);
this.panelTray.Controls.Add(this.labelTrayIcon);
this.panelTray.Location = new System.Drawing.Point(9, 212);
this.panelTray.Name = "panelTray";
this.panelTray.Size = new System.Drawing.Size(322, 76);
this.panelTray.TabIndex = 3;
// //
// labelUpdates // labelUpdates
// //
this.labelUpdates.AutoSize = true; this.labelUpdates.AutoSize = true;
this.labelUpdates.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.labelUpdates.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelUpdates.Location = new System.Drawing.Point(6, 312); this.labelUpdates.Location = new System.Drawing.Point(0, 242);
this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0); this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 20, 0, 0);
this.labelUpdates.Name = "labelUpdates"; this.labelUpdates.Name = "labelUpdates";
this.labelUpdates.Size = new System.Drawing.Size(70, 20); this.labelUpdates.Size = new System.Drawing.Size(70, 20);
this.labelUpdates.TabIndex = 4; this.labelUpdates.TabIndex = 2;
this.labelUpdates.Text = "Updates"; this.labelUpdates.Text = "Updates";
// //
// flowPanel
//
this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.flowPanel.Controls.Add(this.labelUI);
this.flowPanel.Controls.Add(this.checkExpandLinks);
this.flowPanel.Controls.Add(this.checkSwitchAccountSelectors);
this.flowPanel.Controls.Add(this.checkOpenSearchInFirstColumn);
this.flowPanel.Controls.Add(this.checkKeepLikeFollowDialogsOpen);
this.flowPanel.Controls.Add(this.checkBestImageQuality);
this.flowPanel.Controls.Add(this.checkAnimatedAvatars);
this.flowPanel.Controls.Add(this.labelZoom);
this.flowPanel.Controls.Add(this.panelZoom);
this.flowPanel.Controls.Add(this.labelUpdates);
this.flowPanel.Controls.Add(this.checkUpdateNotifications);
this.flowPanel.Controls.Add(this.btnCheckUpdates);
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanel.Location = new System.Drawing.Point(9, 9);
this.flowPanel.Name = "flowPanel";
this.flowPanel.Size = new System.Drawing.Size(322, 319);
this.flowPanel.TabIndex = 4;
this.flowPanel.WrapContents = false;
//
// checkKeepLikeFollowDialogsOpen
//
this.checkKeepLikeFollowDialogsOpen.AutoSize = true;
this.checkKeepLikeFollowDialogsOpen.Location = new System.Drawing.Point(6, 95);
this.checkKeepLikeFollowDialogsOpen.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkKeepLikeFollowDialogsOpen.Name = "checkKeepLikeFollowDialogsOpen";
this.checkKeepLikeFollowDialogsOpen.Size = new System.Drawing.Size(176, 17);
this.checkKeepLikeFollowDialogsOpen.TabIndex = 7;
this.checkKeepLikeFollowDialogsOpen.Text = "Keep Like/Follow Dialogs Open";
this.checkKeepLikeFollowDialogsOpen.UseVisualStyleBackColor = true;
//
// TabSettingsGeneral // TabSettingsGeneral
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.labelUpdates); this.Controls.Add(this.flowPanel);
this.Controls.Add(this.panelTray);
this.Controls.Add(this.panelUpdates);
this.Controls.Add(this.labelTray);
this.Controls.Add(this.panelUI);
this.Controls.Add(this.labelUI);
this.Name = "TabSettingsGeneral"; this.Name = "TabSettingsGeneral";
this.Size = new System.Drawing.Size(340, 400); this.Size = new System.Drawing.Size(340, 337);
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).EndInit();
this.panelUI.ResumeLayout(false); this.panelZoom.ResumeLayout(false);
this.panelUI.PerformLayout(); this.flowPanel.ResumeLayout(false);
this.panelUpdates.ResumeLayout(false); this.flowPanel.PerformLayout();
this.panelUpdates.PerformLayout();
this.panelTray.ResumeLayout(false);
this.panelTray.PerformLayout();
this.ResumeLayout(false); this.ResumeLayout(false);
this.PerformLayout();
} }
#endregion #endregion
private System.Windows.Forms.CheckBox checkExpandLinks; private System.Windows.Forms.CheckBox checkExpandLinks;
private System.Windows.Forms.ComboBox comboBoxTrayType;
private System.Windows.Forms.ToolTip toolTip; private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.Label labelTrayIcon;
private System.Windows.Forms.CheckBox checkTrayHighlight;
private System.Windows.Forms.CheckBox checkSpellCheck;
private System.Windows.Forms.CheckBox checkUpdateNotifications; private System.Windows.Forms.CheckBox checkUpdateNotifications;
private System.Windows.Forms.Button btnCheckUpdates; private System.Windows.Forms.Button btnCheckUpdates;
private System.Windows.Forms.Label labelZoom; private System.Windows.Forms.Label labelZoom;
@@ -298,10 +258,12 @@
private System.Windows.Forms.Timer zoomUpdateTimer; private System.Windows.Forms.Timer zoomUpdateTimer;
private System.Windows.Forms.CheckBox checkSwitchAccountSelectors; private System.Windows.Forms.CheckBox checkSwitchAccountSelectors;
private System.Windows.Forms.Label labelUI; private System.Windows.Forms.Label labelUI;
private System.Windows.Forms.Panel panelUI; private System.Windows.Forms.Panel panelZoom;
private System.Windows.Forms.Label labelTray;
private System.Windows.Forms.Panel panelUpdates;
private System.Windows.Forms.Panel panelTray;
private System.Windows.Forms.Label labelUpdates; private System.Windows.Forms.Label labelUpdates;
private System.Windows.Forms.CheckBox checkBestImageQuality;
private System.Windows.Forms.CheckBox checkOpenSearchInFirstColumn;
private System.Windows.Forms.CheckBox checkAnimatedAvatars;
private System.Windows.Forms.FlowLayoutPanel flowPanel;
private System.Windows.Forms.CheckBox checkKeepLikeFollowDialogsOpen;
} }
} }

View File

@@ -1,36 +1,45 @@
using System; using System;
using System.Windows.Forms;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling.General;
using TweetDuck.Updates; using TweetDuck.Updates;
using TweetDuck.Updates.Events;
namespace TweetDuck.Core.Other.Settings{ namespace TweetDuck.Core.Other.Settings{
partial class TabSettingsGeneral : BaseTabSettings{ sealed partial class TabSettingsGeneral : BaseTabSettings{
private readonly FormBrowser browser;
private readonly UpdateHandler updates; private readonly UpdateHandler updates;
private int updateCheckEventId = -1; private int updateCheckEventId = -1;
public TabSettingsGeneral(UpdateHandler updates){ public TabSettingsGeneral(FormBrowser browser, UpdateHandler updates){
InitializeComponent(); InitializeComponent();
this.browser = browser;
this.updates = updates; this.updates = updates;
this.updates.CheckFinished += updates_CheckFinished; this.updates.CheckFinished += updates_CheckFinished;
Disposed += (sender, args) => this.updates.CheckFinished -= updates_CheckFinished; Disposed += (sender, args) => this.updates.CheckFinished -= updates_CheckFinished;
comboBoxTrayType.Items.Add("Disabled"); toolTip.SetToolTip(checkExpandLinks, "Expands links inside the tweets. If disabled,\r\nthe full links show up in a tooltip instead.");
comboBoxTrayType.Items.Add("Display Icon Only"); toolTip.SetToolTip(checkSwitchAccountSelectors, "When (re)tweeting, click to select a single account or hold Shift to\r\nselect multiple accounts, instead of TweetDeck\'s default behavior.");
comboBoxTrayType.Items.Add("Minimize to Tray"); toolTip.SetToolTip(checkOpenSearchInFirstColumn, "By default, TweetDeck adds Search columns at the end.\r\nThis option makes them appear before the first column instead.");
comboBoxTrayType.Items.Add("Close to Tray"); toolTip.SetToolTip(checkKeepLikeFollowDialogsOpen, "Allows liking and following from multiple accounts at once,\r\ninstead of automatically closing the dialog after taking an action.");
comboBoxTrayType.Items.Add("Combined"); toolTip.SetToolTip(checkBestImageQuality, "When right-clicking a tweet image, the context menu options\r\nwill use links to the original image size (:orig in the URL).");
comboBoxTrayType.SelectedIndex = Math.Min(Math.Max((int)Config.TrayBehavior, 0), comboBoxTrayType.Items.Count-1); toolTip.SetToolTip(checkAnimatedAvatars, "Some old Twitter avatars could be uploaded as animated GIFs.");
toolTip.SetToolTip(labelZoomValue, "Changes the zoom level.\r\nAlso affects notifications and screenshots.");
toolTip.SetToolTip(trackBarZoom, toolTip.GetToolTip(labelZoomValue)); toolTip.SetToolTip(trackBarZoom, toolTip.GetToolTip(labelZoomValue));
toolTip.SetToolTip(checkUpdateNotifications, "Checks for updates every hour.\r\nIf an update is dismissed, it will not appear again.");
toolTip.SetToolTip(btnCheckUpdates, "Forces an update check, even for updates that had been dismissed.");
trackBarZoom.SetValueSafe(Config.ZoomLevel); trackBarZoom.SetValueSafe(Config.ZoomLevel);
labelZoomValue.Text = trackBarZoom.Value+"%"; labelZoomValue.Text = trackBarZoom.Value+"%";
checkExpandLinks.Checked = Config.ExpandLinksOnHover; checkExpandLinks.Checked = Config.ExpandLinksOnHover;
checkSwitchAccountSelectors.Checked = Config.SwitchAccountSelectors; checkSwitchAccountSelectors.Checked = Config.SwitchAccountSelectors;
checkSpellCheck.Checked = Config.EnableSpellCheck; checkOpenSearchInFirstColumn.Checked = Config.OpenSearchInFirstColumn;
checkTrayHighlight.Checked = Config.EnableTrayHighlight; checkKeepLikeFollowDialogsOpen.Checked = Config.KeepLikeFollowDialogsOpen;
checkBestImageQuality.Checked = Config.BestImageQuality;
checkAnimatedAvatars.Checked = Config.EnableAnimatedImages;
checkUpdateNotifications.Checked = Config.EnableUpdateCheck; checkUpdateNotifications.Checked = Config.EnableUpdateCheck;
} }
@@ -38,12 +47,12 @@ namespace TweetDuck.Core.Other.Settings{
public override void OnReady(){ public override void OnReady(){
checkExpandLinks.CheckedChanged += checkExpandLinks_CheckedChanged; checkExpandLinks.CheckedChanged += checkExpandLinks_CheckedChanged;
checkSwitchAccountSelectors.CheckedChanged += checkSwitchAccountSelectors_CheckedChanged; checkSwitchAccountSelectors.CheckedChanged += checkSwitchAccountSelectors_CheckedChanged;
checkSpellCheck.CheckedChanged += checkSpellCheck_CheckedChanged; checkOpenSearchInFirstColumn.CheckedChanged += checkOpenSearchInFirstColumn_CheckedChanged;
checkKeepLikeFollowDialogsOpen.CheckedChanged += checkKeepLikeFollowDialogsOpen_CheckedChanged;
checkBestImageQuality.CheckedChanged += checkBestImageQuality_CheckedChanged;
checkAnimatedAvatars.CheckedChanged += checkAnimatedAvatars_CheckedChanged;
trackBarZoom.ValueChanged += trackBarZoom_ValueChanged; trackBarZoom.ValueChanged += trackBarZoom_ValueChanged;
comboBoxTrayType.SelectedIndexChanged += comboBoxTrayType_SelectedIndexChanged;
checkTrayHighlight.CheckedChanged += checkTrayHighlight_CheckedChanged;
checkUpdateNotifications.CheckedChanged += checkUpdateNotifications_CheckedChanged; checkUpdateNotifications.CheckedChanged += checkUpdateNotifications_CheckedChanged;
btnCheckUpdates.Click += btnCheckUpdates_Click; btnCheckUpdates.Click += btnCheckUpdates_Click;
} }
@@ -60,9 +69,21 @@ namespace TweetDuck.Core.Other.Settings{
Config.SwitchAccountSelectors = checkSwitchAccountSelectors.Checked; Config.SwitchAccountSelectors = checkSwitchAccountSelectors.Checked;
} }
private void checkSpellCheck_CheckedChanged(object sender, EventArgs e){ private void checkOpenSearchInFirstColumn_CheckedChanged(object sender, EventArgs e){
Config.EnableSpellCheck = checkSpellCheck.Checked; Config.OpenSearchInFirstColumn = checkOpenSearchInFirstColumn.Checked;
PromptRestart(); }
private void checkKeepLikeFollowDialogsOpen_CheckedChanged(object sender, EventArgs e){
Config.KeepLikeFollowDialogsOpen = checkKeepLikeFollowDialogsOpen.Checked;
}
private void checkBestImageQuality_CheckedChanged(object sender, EventArgs e){
Config.BestImageQuality = checkBestImageQuality.Checked;
}
private void checkAnimatedAvatars_CheckedChanged(object sender, EventArgs e){
Config.EnableAnimatedImages = checkAnimatedAvatars.Checked;
BrowserProcessHandler.UpdatePrefs().ContinueWith(task => browser.ReloadColumns());
} }
private void trackBarZoom_ValueChanged(object sender, EventArgs e){ private void trackBarZoom_ValueChanged(object sender, EventArgs e){
@@ -73,37 +94,24 @@ namespace TweetDuck.Core.Other.Settings{
} }
} }
private void comboBoxTrayType_SelectedIndexChanged(object sender, EventArgs e){
Config.TrayBehavior = (TrayIcon.Behavior)comboBoxTrayType.SelectedIndex;
}
private void checkTrayHighlight_CheckedChanged(object sender, EventArgs e){
Config.EnableTrayHighlight = checkTrayHighlight.Checked;
}
private void checkUpdateNotifications_CheckedChanged(object sender, EventArgs e){ private void checkUpdateNotifications_CheckedChanged(object sender, EventArgs e){
Config.EnableUpdateCheck = checkUpdateNotifications.Checked; Config.EnableUpdateCheck = checkUpdateNotifications.Checked;
} }
private void btnCheckUpdates_Click(object sender, EventArgs e){ private void btnCheckUpdates_Click(object sender, EventArgs e){
updateCheckEventId = updates.Check(true); Config.DismissedUpdate = null;
if (updateCheckEventId == -1){
MessageBox.Show("Sorry, your system is no longer supported.", "Unsupported System", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
else{
btnCheckUpdates.Enabled = false; btnCheckUpdates.Enabled = false;
updates.DismissUpdate(string.Empty); updateCheckEventId = updates.Check(true);
}
} }
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){ private void updates_CheckFinished(object sender, UpdateEventArgs e){
this.InvokeAsyncSafe(() => { this.InvokeAsyncSafe(() => {
if (e.EventId == updateCheckEventId){ if (e.EventId == updateCheckEventId){
btnCheckUpdates.Enabled = true; btnCheckUpdates.Enabled = true;
if (!e.UpdateAvailable){ if (!e.IsUpdateAvailable){
MessageBox.Show("Your version of "+Program.BrandName+" is up to date.", "No Updates Available", MessageBoxButtons.OK, MessageBoxIcon.Information); FormMessage.Information("No Updates Available", "Your version of TweetDuck is up to date.", FormMessage.OK);
} }
} }
}); });

View File

@@ -0,0 +1,155 @@
namespace TweetDuck.Core.Other.Settings {
partial class TabSettingsLocales {
/// <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.checkSpellCheck = new System.Windows.Forms.CheckBox();
this.labelLocales = new System.Windows.Forms.Label();
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
this.labelSpellCheckLanguage = new System.Windows.Forms.Label();
this.comboBoxSpellCheckLanguage = new System.Windows.Forms.ComboBox();
this.labelTranslations = new System.Windows.Forms.Label();
this.labelTranslationTarget = new System.Windows.Forms.Label();
this.comboBoxTranslationTarget = new System.Windows.Forms.ComboBox();
this.flowPanel.SuspendLayout();
this.SuspendLayout();
//
// checkSpellCheck
//
this.checkSpellCheck.AutoSize = true;
this.checkSpellCheck.Location = new System.Drawing.Point(6, 26);
this.checkSpellCheck.Margin = new System.Windows.Forms.Padding(6, 6, 3, 3);
this.checkSpellCheck.Name = "checkSpellCheck";
this.checkSpellCheck.Size = new System.Drawing.Size(119, 17);
this.checkSpellCheck.TabIndex = 5;
this.checkSpellCheck.Text = "Enable Spell Check";
this.checkSpellCheck.UseVisualStyleBackColor = true;
//
// labelLocales
//
this.labelLocales.AutoSize = true;
this.labelLocales.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelLocales.Location = new System.Drawing.Point(0, 0);
this.labelLocales.Margin = new System.Windows.Forms.Padding(0);
this.labelLocales.Name = "labelLocales";
this.labelLocales.Size = new System.Drawing.Size(64, 20);
this.labelLocales.TabIndex = 0;
this.labelLocales.Text = "Locales";
//
// flowPanel
//
this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.flowPanel.Controls.Add(this.labelLocales);
this.flowPanel.Controls.Add(this.checkSpellCheck);
this.flowPanel.Controls.Add(this.labelSpellCheckLanguage);
this.flowPanel.Controls.Add(this.comboBoxSpellCheckLanguage);
this.flowPanel.Controls.Add(this.labelTranslations);
this.flowPanel.Controls.Add(this.labelTranslationTarget);
this.flowPanel.Controls.Add(this.comboBoxTranslationTarget);
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanel.Location = new System.Drawing.Point(9, 9);
this.flowPanel.Name = "flowPanel";
this.flowPanel.Size = new System.Drawing.Size(322, 193);
this.flowPanel.TabIndex = 4;
this.flowPanel.WrapContents = false;
//
// labelSpellCheckLanguage
//
this.labelSpellCheckLanguage.AutoSize = true;
this.labelSpellCheckLanguage.Location = new System.Drawing.Point(3, 58);
this.labelSpellCheckLanguage.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelSpellCheckLanguage.Name = "labelSpellCheckLanguage";
this.labelSpellCheckLanguage.Size = new System.Drawing.Size(115, 13);
this.labelSpellCheckLanguage.TabIndex = 11;
this.labelSpellCheckLanguage.Text = "Spell Check Language";
//
// comboBoxSpellCheckLanguage
//
this.comboBoxSpellCheckLanguage.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxSpellCheckLanguage.FormattingEnabled = true;
this.comboBoxSpellCheckLanguage.Location = new System.Drawing.Point(5, 74);
this.comboBoxSpellCheckLanguage.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.comboBoxSpellCheckLanguage.Name = "comboBoxSpellCheckLanguage";
this.comboBoxSpellCheckLanguage.Size = new System.Drawing.Size(311, 21);
this.comboBoxSpellCheckLanguage.TabIndex = 9;
//
// labelTranslations
//
this.labelTranslations.AutoSize = true;
this.labelTranslations.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelTranslations.Location = new System.Drawing.Point(0, 118);
this.labelTranslations.Margin = new System.Windows.Forms.Padding(0, 20, 0, 0);
this.labelTranslations.Name = "labelTranslations";
this.labelTranslations.Size = new System.Drawing.Size(116, 20);
this.labelTranslations.TabIndex = 10;
this.labelTranslations.Text = "Bing Translator";
//
// labelTranslationTarget
//
this.labelTranslationTarget.AutoSize = true;
this.labelTranslationTarget.Location = new System.Drawing.Point(3, 150);
this.labelTranslationTarget.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelTranslationTarget.Name = "labelTranslationTarget";
this.labelTranslationTarget.Size = new System.Drawing.Size(89, 13);
this.labelTranslationTarget.TabIndex = 8;
this.labelTranslationTarget.Text = "Target Language";
//
// comboBoxTranslationTarget
//
this.comboBoxTranslationTarget.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxTranslationTarget.FormattingEnabled = true;
this.comboBoxTranslationTarget.Location = new System.Drawing.Point(5, 166);
this.comboBoxTranslationTarget.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.comboBoxTranslationTarget.Name = "comboBoxTranslationTarget";
this.comboBoxTranslationTarget.Size = new System.Drawing.Size(311, 21);
this.comboBoxTranslationTarget.TabIndex = 7;
//
// TabSettingsLocales
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.flowPanel);
this.Name = "TabSettingsLocales";
this.Size = new System.Drawing.Size(340, 211);
this.flowPanel.ResumeLayout(false);
this.flowPanel.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.CheckBox checkSpellCheck;
private System.Windows.Forms.Label labelLocales;
private System.Windows.Forms.FlowLayoutPanel flowPanel;
private System.Windows.Forms.ComboBox comboBoxTranslationTarget;
private System.Windows.Forms.Label labelTranslationTarget;
private System.Windows.Forms.ComboBox comboBoxSpellCheckLanguage;
private System.Windows.Forms.Label labelTranslations;
private System.Windows.Forms.Label labelSpellCheckLanguage;
}
}

View File

@@ -0,0 +1,53 @@
using System;
using TweetDuck.Core.Handling.General;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Other.Settings{
sealed partial class TabSettingsLocales : BaseTabSettings{
public TabSettingsLocales(){
InitializeComponent();
toolTip.SetToolTip(checkSpellCheck, "Underlines words that are spelled incorrectly.");
toolTip.SetToolTip(comboBoxSpellCheckLanguage, "Language used for spell check.");
toolTip.SetToolTip(comboBoxTranslationTarget, "Language tweets are translated into.");
checkSpellCheck.Checked = Config.EnableSpellCheck;
try{
foreach(LocaleUtils.Item item in LocaleUtils.SpellCheckLanguages){
comboBoxSpellCheckLanguage.Items.Add(item);
}
}catch{
comboBoxSpellCheckLanguage.Items.Add(new LocaleUtils.Item("en-US"));
}
comboBoxSpellCheckLanguage.SelectedItem = new LocaleUtils.Item(Config.SpellCheckLanguage);
foreach(LocaleUtils.Item item in LocaleUtils.TweetDeckTranslationLocales){
comboBoxTranslationTarget.Items.Add(item);
}
comboBoxTranslationTarget.SelectedItem = new LocaleUtils.Item(Config.TranslationTarget);
}
public override void OnReady(){
checkSpellCheck.CheckedChanged += checkSpellCheck_CheckedChanged;
comboBoxSpellCheckLanguage.SelectedValueChanged += comboBoxSpellCheckLanguage_SelectedValueChanged;
comboBoxTranslationTarget.SelectedValueChanged += comboBoxTranslationTarget_SelectedValueChanged;
}
private void checkSpellCheck_CheckedChanged(object sender, EventArgs e){
Config.EnableSpellCheck = checkSpellCheck.Checked;
BrowserProcessHandler.UpdatePrefs();
}
private void comboBoxSpellCheckLanguage_SelectedValueChanged(object sender, EventArgs e){
Config.SpellCheckLanguage = (comboBoxSpellCheckLanguage.SelectedItem as LocaleUtils.Item)?.Code;
PromptRestart();
}
private void comboBoxTranslationTarget_SelectedValueChanged(object sender, EventArgs e){
Config.TranslationTarget = (comboBoxTranslationTarget.SelectedItem as LocaleUtils.Item)?.Code;
}
}
}

View File

@@ -51,7 +51,8 @@
this.radioSizeAuto = new System.Windows.Forms.RadioButton(); this.radioSizeAuto = new System.Windows.Forms.RadioButton();
this.radioSizeCustom = new System.Windows.Forms.RadioButton(); this.radioSizeCustom = new System.Windows.Forms.RadioButton();
this.labelGeneral = new System.Windows.Forms.Label(); this.labelGeneral = new System.Windows.Forms.Label();
this.panelGeneral = new System.Windows.Forms.Panel(); this.panelEdgeDistance = new System.Windows.Forms.Panel();
this.checkMediaPreviews = new System.Windows.Forms.CheckBox();
this.labelScrollSpeedValue = new System.Windows.Forms.Label(); this.labelScrollSpeedValue = new System.Windows.Forms.Label();
this.trackBarScrollSpeed = new System.Windows.Forms.TrackBar(); this.trackBarScrollSpeed = new System.Windows.Forms.TrackBar();
this.labelScrollSpeed = new System.Windows.Forms.Label(); this.labelScrollSpeed = new System.Windows.Forms.Label();
@@ -61,24 +62,28 @@
this.labelDuration = new System.Windows.Forms.Label(); this.labelDuration = new System.Windows.Forms.Label();
this.labelTimer = new System.Windows.Forms.Label(); this.labelTimer = new System.Windows.Forms.Label();
this.labelSize = new System.Windows.Forms.Label(); this.labelSize = new System.Windows.Forms.Label();
this.panelMiscellaneous = new System.Windows.Forms.Panel(); this.panelSize = new System.Windows.Forms.Panel();
this.durationUpdateTimer = new System.Windows.Forms.Timer(this.components); this.durationUpdateTimer = new System.Windows.Forms.Timer(this.components);
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
this.panelScrollSpeed = new System.Windows.Forms.Panel();
((System.ComponentModel.ISupportInitialize)(this.trackBarEdgeDistance)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.trackBarEdgeDistance)).BeginInit();
this.tableLayoutDurationButtons.SuspendLayout(); this.tableLayoutDurationButtons.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.trackBarDuration)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.trackBarDuration)).BeginInit();
this.panelGeneral.SuspendLayout(); this.panelEdgeDistance.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.trackBarScrollSpeed)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.trackBarScrollSpeed)).BeginInit();
this.panelLocation.SuspendLayout(); this.panelLocation.SuspendLayout();
this.panelTimer.SuspendLayout(); this.panelTimer.SuspendLayout();
this.panelMiscellaneous.SuspendLayout(); this.panelSize.SuspendLayout();
this.flowPanel.SuspendLayout();
this.panelScrollSpeed.SuspendLayout();
this.SuspendLayout(); this.SuspendLayout();
// //
// labelEdgeDistanceValue // labelEdgeDistanceValue
// //
this.labelEdgeDistanceValue.Location = new System.Drawing.Point(147, 129); this.labelEdgeDistanceValue.Location = new System.Drawing.Point(145, 4);
this.labelEdgeDistanceValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0); this.labelEdgeDistanceValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.labelEdgeDistanceValue.Name = "labelEdgeDistanceValue"; this.labelEdgeDistanceValue.Name = "labelEdgeDistanceValue";
this.labelEdgeDistanceValue.Size = new System.Drawing.Size(34, 13); this.labelEdgeDistanceValue.Size = new System.Drawing.Size(40, 13);
this.labelEdgeDistanceValue.TabIndex = 9; this.labelEdgeDistanceValue.TabIndex = 9;
this.labelEdgeDistanceValue.Text = "0 px"; this.labelEdgeDistanceValue.Text = "0 px";
this.labelEdgeDistanceValue.TextAlign = System.Drawing.ContentAlignment.TopRight; this.labelEdgeDistanceValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
@@ -86,7 +91,7 @@
// labelDisplay // labelDisplay
// //
this.labelDisplay.AutoSize = true; this.labelDisplay.AutoSize = true;
this.labelDisplay.Location = new System.Drawing.Point(3, 60); this.labelDisplay.Location = new System.Drawing.Point(3, 451);
this.labelDisplay.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0); this.labelDisplay.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelDisplay.Name = "labelDisplay"; this.labelDisplay.Name = "labelDisplay";
this.labelDisplay.Size = new System.Drawing.Size(41, 13); this.labelDisplay.Size = new System.Drawing.Size(41, 13);
@@ -95,11 +100,9 @@
// //
// comboBoxDisplay // comboBoxDisplay
// //
this.comboBoxDisplay.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.comboBoxDisplay.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.comboBoxDisplay.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxDisplay.FormattingEnabled = true; this.comboBoxDisplay.FormattingEnabled = true;
this.comboBoxDisplay.Location = new System.Drawing.Point(5, 76); this.comboBoxDisplay.Location = new System.Drawing.Point(5, 467);
this.comboBoxDisplay.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3); this.comboBoxDisplay.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.comboBoxDisplay.Name = "comboBoxDisplay"; this.comboBoxDisplay.Name = "comboBoxDisplay";
this.comboBoxDisplay.Size = new System.Drawing.Size(144, 21); this.comboBoxDisplay.Size = new System.Drawing.Size(144, 21);
@@ -108,7 +111,7 @@
// labelEdgeDistance // labelEdgeDistance
// //
this.labelEdgeDistance.AutoSize = true; this.labelEdgeDistance.AutoSize = true;
this.labelEdgeDistance.Location = new System.Drawing.Point(3, 112); this.labelEdgeDistance.Location = new System.Drawing.Point(3, 503);
this.labelEdgeDistance.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0); this.labelEdgeDistance.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelEdgeDistance.Name = "labelEdgeDistance"; this.labelEdgeDistance.Name = "labelEdgeDistance";
this.labelEdgeDistance.Size = new System.Drawing.Size(103, 13); this.labelEdgeDistance.Size = new System.Drawing.Size(103, 13);
@@ -124,7 +127,6 @@
this.radioLocCustom.TabIndex = 4; this.radioLocCustom.TabIndex = 4;
this.radioLocCustom.TabStop = true; this.radioLocCustom.TabStop = true;
this.radioLocCustom.Text = "Custom"; this.radioLocCustom.Text = "Custom";
this.toolTip.SetToolTip(this.radioLocCustom, "Drag the example notification window to the desired location.");
this.radioLocCustom.UseVisualStyleBackColor = true; this.radioLocCustom.UseVisualStyleBackColor = true;
// //
// radioLocBR // radioLocBR
@@ -175,7 +177,7 @@
// //
this.trackBarEdgeDistance.AutoSize = false; this.trackBarEdgeDistance.AutoSize = false;
this.trackBarEdgeDistance.LargeChange = 8; this.trackBarEdgeDistance.LargeChange = 8;
this.trackBarEdgeDistance.Location = new System.Drawing.Point(5, 128); this.trackBarEdgeDistance.Location = new System.Drawing.Point(3, 3);
this.trackBarEdgeDistance.Maximum = 40; this.trackBarEdgeDistance.Maximum = 40;
this.trackBarEdgeDistance.Minimum = 8; this.trackBarEdgeDistance.Minimum = 8;
this.trackBarEdgeDistance.Name = "trackBarEdgeDistance"; this.trackBarEdgeDistance.Name = "trackBarEdgeDistance";
@@ -194,7 +196,7 @@
this.tableLayoutDurationButtons.Controls.Add(this.btnDurationMedium, 0, 0); this.tableLayoutDurationButtons.Controls.Add(this.btnDurationMedium, 0, 0);
this.tableLayoutDurationButtons.Controls.Add(this.btnDurationLong, 1, 0); this.tableLayoutDurationButtons.Controls.Add(this.btnDurationLong, 1, 0);
this.tableLayoutDurationButtons.Controls.Add(this.btnDurationShort, 0, 0); this.tableLayoutDurationButtons.Controls.Add(this.btnDurationShort, 0, 0);
this.tableLayoutDurationButtons.Location = new System.Drawing.Point(3, 113); this.tableLayoutDurationButtons.Location = new System.Drawing.Point(3, 320);
this.tableLayoutDurationButtons.Name = "tableLayoutDurationButtons"; this.tableLayoutDurationButtons.Name = "tableLayoutDurationButtons";
this.tableLayoutDurationButtons.RowCount = 1; this.tableLayoutDurationButtons.RowCount = 1;
this.tableLayoutDurationButtons.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); this.tableLayoutDurationButtons.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
@@ -249,19 +251,18 @@
// labelDurationValue // labelDurationValue
// //
this.labelDurationValue.BackColor = System.Drawing.Color.Transparent; this.labelDurationValue.BackColor = System.Drawing.Color.Transparent;
this.labelDurationValue.Location = new System.Drawing.Point(147, 77); this.labelDurationValue.Location = new System.Drawing.Point(147, 4);
this.labelDurationValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0); this.labelDurationValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.labelDurationValue.Name = "labelDurationValue"; this.labelDurationValue.Name = "labelDurationValue";
this.labelDurationValue.Size = new System.Drawing.Size(48, 13); this.labelDurationValue.Size = new System.Drawing.Size(52, 13);
this.labelDurationValue.TabIndex = 4; this.labelDurationValue.TabIndex = 4;
this.labelDurationValue.Text = "0 ms/c"; this.labelDurationValue.Text = "0 ms/c";
this.labelDurationValue.TextAlign = System.Drawing.ContentAlignment.TopRight; this.labelDurationValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
this.toolTip.SetToolTip(this.labelDurationValue, "Milliseconds per character.");
// //
// trackBarDuration // trackBarDuration
// //
this.trackBarDuration.AutoSize = false; this.trackBarDuration.AutoSize = false;
this.trackBarDuration.Location = new System.Drawing.Point(3, 76); this.trackBarDuration.Location = new System.Drawing.Point(3, 3);
this.trackBarDuration.Maximum = 60; this.trackBarDuration.Maximum = 60;
this.trackBarDuration.Minimum = 10; this.trackBarDuration.Minimum = 10;
this.trackBarDuration.Name = "trackBarDuration"; this.trackBarDuration.Name = "trackBarDuration";
@@ -273,79 +274,72 @@
// checkSkipOnLinkClick // checkSkipOnLinkClick
// //
this.checkSkipOnLinkClick.AutoSize = true; this.checkSkipOnLinkClick.AutoSize = true;
this.checkSkipOnLinkClick.Location = new System.Drawing.Point(6, 28); this.checkSkipOnLinkClick.Location = new System.Drawing.Point(6, 72);
this.checkSkipOnLinkClick.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3); this.checkSkipOnLinkClick.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkSkipOnLinkClick.Name = "checkSkipOnLinkClick"; this.checkSkipOnLinkClick.Name = "checkSkipOnLinkClick";
this.checkSkipOnLinkClick.Size = new System.Drawing.Size(113, 17); this.checkSkipOnLinkClick.Size = new System.Drawing.Size(113, 17);
this.checkSkipOnLinkClick.TabIndex = 1; this.checkSkipOnLinkClick.TabIndex = 2;
this.checkSkipOnLinkClick.Text = "Skip On Link Click"; 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; this.checkSkipOnLinkClick.UseVisualStyleBackColor = true;
// //
// checkColumnName // checkColumnName
// //
this.checkColumnName.AutoSize = true; this.checkColumnName.AutoSize = true;
this.checkColumnName.Location = new System.Drawing.Point(6, 5); this.checkColumnName.Location = new System.Drawing.Point(6, 26);
this.checkColumnName.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3); this.checkColumnName.Margin = new System.Windows.Forms.Padding(6, 6, 3, 3);
this.checkColumnName.Name = "checkColumnName"; this.checkColumnName.Name = "checkColumnName";
this.checkColumnName.Size = new System.Drawing.Size(129, 17); this.checkColumnName.Size = new System.Drawing.Size(129, 17);
this.checkColumnName.TabIndex = 0; this.checkColumnName.TabIndex = 0;
this.checkColumnName.Text = "Display Column Name"; 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; this.checkColumnName.UseVisualStyleBackColor = true;
// //
// labelIdlePause // labelIdlePause
// //
this.labelIdlePause.AutoSize = true; this.labelIdlePause.AutoSize = true;
this.labelIdlePause.Location = new System.Drawing.Point(3, 83); this.labelIdlePause.Location = new System.Drawing.Point(3, 127);
this.labelIdlePause.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0); this.labelIdlePause.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelIdlePause.Name = "labelIdlePause"; this.labelIdlePause.Name = "labelIdlePause";
this.labelIdlePause.Size = new System.Drawing.Size(89, 13); this.labelIdlePause.Size = new System.Drawing.Size(89, 13);
this.labelIdlePause.TabIndex = 3; this.labelIdlePause.TabIndex = 4;
this.labelIdlePause.Text = "Pause When Idle"; this.labelIdlePause.Text = "Pause When Idle";
// //
// comboBoxIdlePause // comboBoxIdlePause
// //
this.comboBoxIdlePause.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.comboBoxIdlePause.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxIdlePause.FormattingEnabled = true; this.comboBoxIdlePause.FormattingEnabled = true;
this.comboBoxIdlePause.Location = new System.Drawing.Point(5, 99); this.comboBoxIdlePause.Location = new System.Drawing.Point(5, 143);
this.comboBoxIdlePause.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3); this.comboBoxIdlePause.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.comboBoxIdlePause.Name = "comboBoxIdlePause"; this.comboBoxIdlePause.Name = "comboBoxIdlePause";
this.comboBoxIdlePause.Size = new System.Drawing.Size(144, 21); this.comboBoxIdlePause.Size = new System.Drawing.Size(144, 21);
this.comboBoxIdlePause.TabIndex = 4; this.comboBoxIdlePause.TabIndex = 5;
this.toolTip.SetToolTip(this.comboBoxIdlePause, "Pauses new notifications after going idle for a set amount of time.");
// //
// checkNonIntrusive // checkNonIntrusive
// //
this.checkNonIntrusive.AutoSize = true; this.checkNonIntrusive.AutoSize = true;
this.checkNonIntrusive.Location = new System.Drawing.Point(6, 51); this.checkNonIntrusive.Location = new System.Drawing.Point(6, 95);
this.checkNonIntrusive.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3); this.checkNonIntrusive.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkNonIntrusive.Name = "checkNonIntrusive"; this.checkNonIntrusive.Name = "checkNonIntrusive";
this.checkNonIntrusive.Size = new System.Drawing.Size(128, 17); this.checkNonIntrusive.Size = new System.Drawing.Size(128, 17);
this.checkNonIntrusive.TabIndex = 2; this.checkNonIntrusive.TabIndex = 3;
this.checkNonIntrusive.Text = "Non-Intrusive Popups"; 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; this.checkNonIntrusive.UseVisualStyleBackColor = true;
// //
// checkTimerCountDown // checkTimerCountDown
// //
this.checkTimerCountDown.AutoSize = true; this.checkTimerCountDown.AutoSize = true;
this.checkTimerCountDown.Location = new System.Drawing.Point(6, 28); this.checkTimerCountDown.Location = new System.Drawing.Point(6, 236);
this.checkTimerCountDown.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3); this.checkTimerCountDown.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkTimerCountDown.Name = "checkTimerCountDown"; this.checkTimerCountDown.Name = "checkTimerCountDown";
this.checkTimerCountDown.Size = new System.Drawing.Size(119, 17); this.checkTimerCountDown.Size = new System.Drawing.Size(119, 17);
this.checkTimerCountDown.TabIndex = 1; this.checkTimerCountDown.TabIndex = 1;
this.checkTimerCountDown.Text = "Timer Counts Down"; 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.UseVisualStyleBackColor = true;
// //
// checkNotificationTimer // checkNotificationTimer
// //
this.checkNotificationTimer.AutoSize = true; this.checkNotificationTimer.AutoSize = true;
this.checkNotificationTimer.Location = new System.Drawing.Point(6, 5); this.checkNotificationTimer.Location = new System.Drawing.Point(6, 213);
this.checkNotificationTimer.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3); this.checkNotificationTimer.Margin = new System.Windows.Forms.Padding(6, 6, 3, 3);
this.checkNotificationTimer.Name = "checkNotificationTimer"; this.checkNotificationTimer.Name = "checkNotificationTimer";
this.checkNotificationTimer.Size = new System.Drawing.Size(145, 17); this.checkNotificationTimer.Size = new System.Drawing.Size(145, 17);
this.checkNotificationTimer.TabIndex = 0; this.checkNotificationTimer.TabIndex = 0;
@@ -354,59 +348,65 @@
// //
// radioSizeAuto // radioSizeAuto
// //
this.radioSizeAuto.Location = new System.Drawing.Point(6, 4); this.radioSizeAuto.Location = new System.Drawing.Point(5, 4);
this.radioSizeAuto.Margin = new System.Windows.Forms.Padding(5, 4, 3, 3); this.radioSizeAuto.Margin = new System.Windows.Forms.Padding(5, 4, 3, 3);
this.radioSizeAuto.Name = "radioSizeAuto"; this.radioSizeAuto.Name = "radioSizeAuto";
this.radioSizeAuto.Size = new System.Drawing.Size(92, 17); this.radioSizeAuto.Size = new System.Drawing.Size(92, 17);
this.radioSizeAuto.TabIndex = 0; this.radioSizeAuto.TabIndex = 0;
this.radioSizeAuto.TabStop = true; this.radioSizeAuto.TabStop = true;
this.radioSizeAuto.Text = "Auto"; this.radioSizeAuto.Text = "Auto";
this.toolTip.SetToolTip(this.radioSizeAuto, "Notification size is based on the font size and browser zoom level.");
this.radioSizeAuto.UseVisualStyleBackColor = true; this.radioSizeAuto.UseVisualStyleBackColor = true;
// //
// radioSizeCustom // radioSizeCustom
// //
this.radioSizeCustom.Location = new System.Drawing.Point(106, 4); this.radioSizeCustom.Location = new System.Drawing.Point(105, 4);
this.radioSizeCustom.Margin = new System.Windows.Forms.Padding(5, 4, 3, 3); this.radioSizeCustom.Margin = new System.Windows.Forms.Padding(5, 4, 3, 3);
this.radioSizeCustom.Name = "radioSizeCustom"; this.radioSizeCustom.Name = "radioSizeCustom";
this.radioSizeCustom.Size = new System.Drawing.Size(92, 17); this.radioSizeCustom.Size = new System.Drawing.Size(92, 17);
this.radioSizeCustom.TabIndex = 1; this.radioSizeCustom.TabIndex = 1;
this.radioSizeCustom.TabStop = true; this.radioSizeCustom.TabStop = true;
this.radioSizeCustom.Text = "Custom"; this.radioSizeCustom.Text = "Custom";
this.toolTip.SetToolTip(this.radioSizeCustom, "Resize the example notification window to the desired size.");
this.radioSizeCustom.UseVisualStyleBackColor = true; this.radioSizeCustom.UseVisualStyleBackColor = true;
// //
// labelGeneral // labelGeneral
// //
this.labelGeneral.AutoSize = true; this.labelGeneral.AutoSize = true;
this.labelGeneral.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.labelGeneral.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelGeneral.Location = new System.Drawing.Point(6, 8); this.labelGeneral.Location = new System.Drawing.Point(0, 0);
this.labelGeneral.Margin = new System.Windows.Forms.Padding(0, 2, 0, 0); this.labelGeneral.Margin = new System.Windows.Forms.Padding(0);
this.labelGeneral.Name = "labelGeneral"; this.labelGeneral.Name = "labelGeneral";
this.labelGeneral.Size = new System.Drawing.Size(66, 20); this.labelGeneral.Size = new System.Drawing.Size(66, 20);
this.labelGeneral.TabIndex = 0; this.labelGeneral.TabIndex = 0;
this.labelGeneral.Text = "General"; this.labelGeneral.Text = "General";
// //
// panelGeneral // panelEdgeDistance
// //
this.panelGeneral.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) this.panelEdgeDistance.Anchor = System.Windows.Forms.AnchorStyles.Top;
| System.Windows.Forms.AnchorStyles.Right))); this.panelEdgeDistance.Controls.Add(this.trackBarEdgeDistance);
this.panelGeneral.Controls.Add(this.checkColumnName); this.panelEdgeDistance.Controls.Add(this.labelEdgeDistanceValue);
this.panelGeneral.Controls.Add(this.checkSkipOnLinkClick); this.panelEdgeDistance.Location = new System.Drawing.Point(0, 516);
this.panelGeneral.Controls.Add(this.checkNonIntrusive); this.panelEdgeDistance.Margin = new System.Windows.Forms.Padding(0);
this.panelGeneral.Controls.Add(this.labelIdlePause); this.panelEdgeDistance.Name = "panelEdgeDistance";
this.panelGeneral.Controls.Add(this.comboBoxIdlePause); this.panelEdgeDistance.Size = new System.Drawing.Size(322, 36);
this.panelGeneral.Location = new System.Drawing.Point(9, 31); this.panelEdgeDistance.TabIndex = 1;
this.panelGeneral.Name = "panelGeneral"; //
this.panelGeneral.Size = new System.Drawing.Size(322, 126); // checkMediaPreviews
this.panelGeneral.TabIndex = 1; //
this.checkMediaPreviews.AutoSize = true;
this.checkMediaPreviews.Location = new System.Drawing.Point(6, 49);
this.checkMediaPreviews.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkMediaPreviews.Name = "checkMediaPreviews";
this.checkMediaPreviews.Size = new System.Drawing.Size(131, 17);
this.checkMediaPreviews.TabIndex = 1;
this.checkMediaPreviews.Text = "Show Media Previews";
this.checkMediaPreviews.UseVisualStyleBackColor = true;
// //
// labelScrollSpeedValue // labelScrollSpeedValue
// //
this.labelScrollSpeedValue.Location = new System.Drawing.Point(147, 53); this.labelScrollSpeedValue.Location = new System.Drawing.Point(145, 4);
this.labelScrollSpeedValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0); this.labelScrollSpeedValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.labelScrollSpeedValue.Name = "labelScrollSpeedValue"; this.labelScrollSpeedValue.Name = "labelScrollSpeedValue";
this.labelScrollSpeedValue.Size = new System.Drawing.Size(34, 13); this.labelScrollSpeedValue.Size = new System.Drawing.Size(38, 13);
this.labelScrollSpeedValue.TabIndex = 4; this.labelScrollSpeedValue.TabIndex = 4;
this.labelScrollSpeedValue.Text = "100%"; this.labelScrollSpeedValue.Text = "100%";
this.labelScrollSpeedValue.TextAlign = System.Drawing.ContentAlignment.TopRight; this.labelScrollSpeedValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
@@ -415,7 +415,7 @@
// //
this.trackBarScrollSpeed.AutoSize = false; this.trackBarScrollSpeed.AutoSize = false;
this.trackBarScrollSpeed.LargeChange = 25; this.trackBarScrollSpeed.LargeChange = 25;
this.trackBarScrollSpeed.Location = new System.Drawing.Point(5, 52); this.trackBarScrollSpeed.Location = new System.Drawing.Point(3, 3);
this.trackBarScrollSpeed.Maximum = 200; this.trackBarScrollSpeed.Maximum = 200;
this.trackBarScrollSpeed.Minimum = 25; this.trackBarScrollSpeed.Minimum = 25;
this.trackBarScrollSpeed.Name = "trackBarScrollSpeed"; this.trackBarScrollSpeed.Name = "trackBarScrollSpeed";
@@ -428,7 +428,7 @@
// labelScrollSpeed // labelScrollSpeed
// //
this.labelScrollSpeed.AutoSize = true; this.labelScrollSpeed.AutoSize = true;
this.labelScrollSpeed.Location = new System.Drawing.Point(3, 36); this.labelScrollSpeed.Location = new System.Drawing.Point(3, 629);
this.labelScrollSpeed.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0); this.labelScrollSpeed.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelScrollSpeed.Name = "labelScrollSpeed"; this.labelScrollSpeed.Name = "labelScrollSpeed";
this.labelScrollSpeed.Size = new System.Drawing.Size(67, 13); this.labelScrollSpeed.Size = new System.Drawing.Size(67, 13);
@@ -439,8 +439,8 @@
// //
this.labelLocation.AutoSize = true; this.labelLocation.AutoSize = true;
this.labelLocation.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.labelLocation.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelLocation.Location = new System.Drawing.Point(6, 372); this.labelLocation.Location = new System.Drawing.Point(0, 370);
this.labelLocation.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0); this.labelLocation.Margin = new System.Windows.Forms.Padding(0, 20, 0, 0);
this.labelLocation.Name = "labelLocation"; this.labelLocation.Name = "labelLocation";
this.labelLocation.Size = new System.Drawing.Size(70, 20); this.labelLocation.Size = new System.Drawing.Size(70, 20);
this.labelLocation.TabIndex = 4; this.labelLocation.TabIndex = 4;
@@ -448,42 +448,33 @@
// //
// panelLocation // panelLocation
// //
this.panelLocation.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) this.panelLocation.Anchor = System.Windows.Forms.AnchorStyles.Top;
| System.Windows.Forms.AnchorStyles.Right)));
this.panelLocation.Controls.Add(this.labelEdgeDistanceValue);
this.panelLocation.Controls.Add(this.radioLocTL); this.panelLocation.Controls.Add(this.radioLocTL);
this.panelLocation.Controls.Add(this.labelDisplay);
this.panelLocation.Controls.Add(this.trackBarEdgeDistance);
this.panelLocation.Controls.Add(this.comboBoxDisplay);
this.panelLocation.Controls.Add(this.radioLocTR); this.panelLocation.Controls.Add(this.radioLocTR);
this.panelLocation.Controls.Add(this.labelEdgeDistance);
this.panelLocation.Controls.Add(this.radioLocBL); this.panelLocation.Controls.Add(this.radioLocBL);
this.panelLocation.Controls.Add(this.radioLocCustom); this.panelLocation.Controls.Add(this.radioLocCustom);
this.panelLocation.Controls.Add(this.radioLocBR); this.panelLocation.Controls.Add(this.radioLocBR);
this.panelLocation.Location = new System.Drawing.Point(9, 395); this.panelLocation.Location = new System.Drawing.Point(0, 390);
this.panelLocation.Margin = new System.Windows.Forms.Padding(0);
this.panelLocation.Name = "panelLocation"; this.panelLocation.Name = "panelLocation";
this.panelLocation.Size = new System.Drawing.Size(322, 165); this.panelLocation.Size = new System.Drawing.Size(322, 49);
this.panelLocation.TabIndex = 5; this.panelLocation.TabIndex = 5;
// //
// panelTimer // panelTimer
// //
this.panelTimer.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) this.panelTimer.Anchor = System.Windows.Forms.AnchorStyles.Top;
| System.Windows.Forms.AnchorStyles.Right)));
this.panelTimer.Controls.Add(this.labelDuration);
this.panelTimer.Controls.Add(this.checkNotificationTimer);
this.panelTimer.Controls.Add(this.tableLayoutDurationButtons);
this.panelTimer.Controls.Add(this.checkTimerCountDown);
this.panelTimer.Controls.Add(this.labelDurationValue); this.panelTimer.Controls.Add(this.labelDurationValue);
this.panelTimer.Controls.Add(this.trackBarDuration); this.panelTimer.Controls.Add(this.trackBarDuration);
this.panelTimer.Location = new System.Drawing.Point(9, 204); this.panelTimer.Location = new System.Drawing.Point(0, 281);
this.panelTimer.Margin = new System.Windows.Forms.Padding(0);
this.panelTimer.Name = "panelTimer"; this.panelTimer.Name = "panelTimer";
this.panelTimer.Size = new System.Drawing.Size(322, 144); this.panelTimer.Size = new System.Drawing.Size(322, 36);
this.panelTimer.TabIndex = 3; this.panelTimer.TabIndex = 3;
// //
// labelDuration // labelDuration
// //
this.labelDuration.AutoSize = true; this.labelDuration.AutoSize = true;
this.labelDuration.Location = new System.Drawing.Point(3, 60); this.labelDuration.Location = new System.Drawing.Point(3, 268);
this.labelDuration.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0); this.labelDuration.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelDuration.Name = "labelDuration"; this.labelDuration.Name = "labelDuration";
this.labelDuration.Size = new System.Drawing.Size(47, 13); this.labelDuration.Size = new System.Drawing.Size(47, 13);
@@ -494,8 +485,8 @@
// //
this.labelTimer.AutoSize = true; this.labelTimer.AutoSize = true;
this.labelTimer.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.labelTimer.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelTimer.Location = new System.Drawing.Point(6, 181); this.labelTimer.Location = new System.Drawing.Point(0, 187);
this.labelTimer.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0); this.labelTimer.Margin = new System.Windows.Forms.Padding(0, 20, 0, 0);
this.labelTimer.Name = "labelTimer"; this.labelTimer.Name = "labelTimer";
this.labelTimer.Size = new System.Drawing.Size(48, 20); this.labelTimer.Size = new System.Drawing.Size(48, 20);
this.labelTimer.TabIndex = 2; this.labelTimer.TabIndex = 2;
@@ -505,61 +496,96 @@
// //
this.labelSize.AutoSize = true; this.labelSize.AutoSize = true;
this.labelSize.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.labelSize.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelSize.Location = new System.Drawing.Point(6, 584); this.labelSize.Location = new System.Drawing.Point(0, 572);
this.labelSize.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0); this.labelSize.Margin = new System.Windows.Forms.Padding(0, 20, 0, 0);
this.labelSize.Name = "labelSize"; this.labelSize.Name = "labelSize";
this.labelSize.Size = new System.Drawing.Size(40, 20); this.labelSize.Size = new System.Drawing.Size(40, 20);
this.labelSize.TabIndex = 6; this.labelSize.TabIndex = 6;
this.labelSize.Text = "Size"; this.labelSize.Text = "Size";
// //
// panelMiscellaneous // panelSize
// //
this.panelMiscellaneous.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) this.panelSize.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right))); | System.Windows.Forms.AnchorStyles.Right)));
this.panelMiscellaneous.Controls.Add(this.radioSizeCustom); this.panelSize.Controls.Add(this.radioSizeCustom);
this.panelMiscellaneous.Controls.Add(this.radioSizeAuto); this.panelSize.Controls.Add(this.radioSizeAuto);
this.panelMiscellaneous.Controls.Add(this.labelScrollSpeedValue); this.panelSize.Location = new System.Drawing.Point(0, 592);
this.panelMiscellaneous.Controls.Add(this.trackBarScrollSpeed); this.panelSize.Margin = new System.Windows.Forms.Padding(0);
this.panelMiscellaneous.Controls.Add(this.labelScrollSpeed); this.panelSize.Name = "panelSize";
this.panelMiscellaneous.Location = new System.Drawing.Point(9, 607); this.panelSize.Size = new System.Drawing.Size(322, 25);
this.panelMiscellaneous.Name = "panelMiscellaneous"; this.panelSize.TabIndex = 7;
this.panelMiscellaneous.Size = new System.Drawing.Size(322, 92);
this.panelMiscellaneous.TabIndex = 7;
// //
// durationUpdateTimer // durationUpdateTimer
// //
this.durationUpdateTimer.Interval = 200; this.durationUpdateTimer.Interval = 200;
this.durationUpdateTimer.Tick += new System.EventHandler(this.durationUpdateTimer_Tick); this.durationUpdateTimer.Tick += new System.EventHandler(this.durationUpdateTimer_Tick);
// //
// flowPanel
//
this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.flowPanel.Controls.Add(this.labelGeneral);
this.flowPanel.Controls.Add(this.checkColumnName);
this.flowPanel.Controls.Add(this.checkMediaPreviews);
this.flowPanel.Controls.Add(this.checkSkipOnLinkClick);
this.flowPanel.Controls.Add(this.checkNonIntrusive);
this.flowPanel.Controls.Add(this.labelIdlePause);
this.flowPanel.Controls.Add(this.comboBoxIdlePause);
this.flowPanel.Controls.Add(this.labelTimer);
this.flowPanel.Controls.Add(this.checkNotificationTimer);
this.flowPanel.Controls.Add(this.checkTimerCountDown);
this.flowPanel.Controls.Add(this.labelDuration);
this.flowPanel.Controls.Add(this.panelTimer);
this.flowPanel.Controls.Add(this.tableLayoutDurationButtons);
this.flowPanel.Controls.Add(this.labelLocation);
this.flowPanel.Controls.Add(this.panelLocation);
this.flowPanel.Controls.Add(this.labelDisplay);
this.flowPanel.Controls.Add(this.comboBoxDisplay);
this.flowPanel.Controls.Add(this.labelEdgeDistance);
this.flowPanel.Controls.Add(this.panelEdgeDistance);
this.flowPanel.Controls.Add(this.labelSize);
this.flowPanel.Controls.Add(this.panelSize);
this.flowPanel.Controls.Add(this.labelScrollSpeed);
this.flowPanel.Controls.Add(this.panelScrollSpeed);
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanel.Location = new System.Drawing.Point(9, 9);
this.flowPanel.Name = "flowPanel";
this.flowPanel.Size = new System.Drawing.Size(322, 678);
this.flowPanel.TabIndex = 8;
this.flowPanel.WrapContents = false;
//
// panelScrollSpeed
//
this.panelScrollSpeed.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.panelScrollSpeed.Controls.Add(this.trackBarScrollSpeed);
this.panelScrollSpeed.Controls.Add(this.labelScrollSpeedValue);
this.panelScrollSpeed.Location = new System.Drawing.Point(0, 642);
this.panelScrollSpeed.Margin = new System.Windows.Forms.Padding(0);
this.panelScrollSpeed.Name = "panelScrollSpeed";
this.panelScrollSpeed.Size = new System.Drawing.Size(322, 36);
this.panelScrollSpeed.TabIndex = 9;
//
// TabSettingsNotifications // TabSettingsNotifications
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.panelMiscellaneous); this.Controls.Add(this.flowPanel);
this.Controls.Add(this.labelSize);
this.Controls.Add(this.labelTimer);
this.Controls.Add(this.panelLocation);
this.Controls.Add(this.labelLocation);
this.Controls.Add(this.panelGeneral);
this.Controls.Add(this.labelGeneral);
this.Controls.Add(this.panelTimer);
this.Name = "TabSettingsNotifications"; this.Name = "TabSettingsNotifications";
this.Size = new System.Drawing.Size(340, 708); this.Size = new System.Drawing.Size(340, 697);
this.ParentChanged += new System.EventHandler(this.TabSettingsNotifications_ParentChanged); this.ParentChanged += new System.EventHandler(this.TabSettingsNotifications_ParentChanged);
((System.ComponentModel.ISupportInitialize)(this.trackBarEdgeDistance)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.trackBarEdgeDistance)).EndInit();
this.tableLayoutDurationButtons.ResumeLayout(false); this.tableLayoutDurationButtons.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.trackBarDuration)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.trackBarDuration)).EndInit();
this.panelGeneral.ResumeLayout(false); this.panelEdgeDistance.ResumeLayout(false);
this.panelGeneral.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.trackBarScrollSpeed)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.trackBarScrollSpeed)).EndInit();
this.panelLocation.ResumeLayout(false); this.panelLocation.ResumeLayout(false);
this.panelLocation.PerformLayout();
this.panelTimer.ResumeLayout(false); this.panelTimer.ResumeLayout(false);
this.panelTimer.PerformLayout(); this.panelSize.ResumeLayout(false);
this.panelMiscellaneous.ResumeLayout(false); this.flowPanel.ResumeLayout(false);
this.panelMiscellaneous.PerformLayout(); this.flowPanel.PerformLayout();
this.panelScrollSpeed.ResumeLayout(false);
this.ResumeLayout(false); this.ResumeLayout(false);
this.PerformLayout();
} }
@@ -589,7 +615,7 @@
private System.Windows.Forms.CheckBox checkTimerCountDown; private System.Windows.Forms.CheckBox checkTimerCountDown;
private System.Windows.Forms.CheckBox checkNotificationTimer; private System.Windows.Forms.CheckBox checkNotificationTimer;
private System.Windows.Forms.Label labelGeneral; private System.Windows.Forms.Label labelGeneral;
private System.Windows.Forms.Panel panelGeneral; private System.Windows.Forms.Panel panelEdgeDistance;
private System.Windows.Forms.Label labelLocation; private System.Windows.Forms.Label labelLocation;
private System.Windows.Forms.Panel panelLocation; private System.Windows.Forms.Panel panelLocation;
private System.Windows.Forms.Panel panelTimer; private System.Windows.Forms.Panel panelTimer;
@@ -598,10 +624,13 @@
private System.Windows.Forms.TrackBar trackBarScrollSpeed; private System.Windows.Forms.TrackBar trackBarScrollSpeed;
private System.Windows.Forms.Label labelScrollSpeed; private System.Windows.Forms.Label labelScrollSpeed;
private System.Windows.Forms.Label labelSize; private System.Windows.Forms.Label labelSize;
private System.Windows.Forms.Panel panelMiscellaneous; private System.Windows.Forms.Panel panelSize;
private System.Windows.Forms.Label labelDuration; private System.Windows.Forms.Label labelDuration;
private System.Windows.Forms.Timer durationUpdateTimer; private System.Windows.Forms.Timer durationUpdateTimer;
private System.Windows.Forms.RadioButton radioSizeCustom; private System.Windows.Forms.RadioButton radioSizeCustom;
private System.Windows.Forms.RadioButton radioSizeAuto; private System.Windows.Forms.RadioButton radioSizeAuto;
private System.Windows.Forms.CheckBox checkMediaPreviews;
private System.Windows.Forms.FlowLayoutPanel flowPanel;
private System.Windows.Forms.Panel panelScrollSpeed;
} }
} }

View File

@@ -1,23 +1,23 @@
using System; using System;
using System.Globalization;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification; using TweetDuck.Core.Notification;
using TweetDuck.Core.Notification.Example;
namespace TweetDuck.Core.Other.Settings{ namespace TweetDuck.Core.Other.Settings{
partial class TabSettingsNotifications : BaseTabSettings{ sealed partial class TabSettingsNotifications : BaseTabSettings{
private static readonly int[] IdlePauseSeconds = { 0, 30, 60, 120, 300 }; private static readonly int[] IdlePauseSeconds = { 0, 30, 60, 120, 300 };
private readonly FormNotificationMain notification; private readonly FormNotificationExample notification;
public TabSettingsNotifications(FormNotificationMain notification){ public TabSettingsNotifications(FormNotificationExample notification){
InitializeComponent(); InitializeComponent();
this.notification = notification; this.notification = notification;
this.notification.Initialized += (sender, args) => { this.notification.Initialized += (sender, args) => {
this.InvokeAsyncSafe(() => { this.InvokeAsyncSafe(() => {
this.notification.ShowNotificationForSettings(true); this.notification.ShowExampleNotification(true);
this.notification.Move += notification_Move; this.notification.Move += notification_Move;
this.notification.ResizeEnd += notification_ResizeEnd; this.notification.ResizeEnd += notification_ResizeEnd;
}); });
@@ -26,6 +26,41 @@ namespace TweetDuck.Core.Other.Settings{
this.notification.Activated += notification_Activated; this.notification.Activated += notification_Activated;
this.notification.Show(); this.notification.Show();
toolTip.SetToolTip(checkColumnName, "Shows column name each notification originated\r\nfrom in the notification window title.");
toolTip.SetToolTip(checkMediaPreviews, "Shows image and video thumbnails in the notification window.");
toolTip.SetToolTip(checkSkipOnLinkClick, "Skips current notification when a link\r\ninside the notification is clicked.");
toolTip.SetToolTip(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.");
toolTip.SetToolTip(comboBoxIdlePause, "Pauses new notifications after going idle for a set amount of time.");
toolTip.SetToolTip(checkTimerCountDown, "The notification timer counts down instead of up.");
toolTip.SetToolTip(labelDurationValue, "Milliseconds per character.");
toolTip.SetToolTip(trackBarDuration, toolTip.GetToolTip(labelDurationValue));
toolTip.SetToolTip(radioLocCustom, "Drag the example notification window to the desired location.");
toolTip.SetToolTip(radioSizeAuto, "Notification size is based on the font size and browser zoom level.");
toolTip.SetToolTip(radioSizeCustom, "Resize the example notification window to the desired size.");
checkColumnName.Checked = Config.DisplayNotificationColumn;
checkMediaPreviews.Checked = Config.NotificationMediaPreviews;
checkSkipOnLinkClick.Checked = Config.NotificationSkipOnLinkClick;
checkNonIntrusive.Checked = Config.NotificationNonIntrusiveMode;
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));
checkNotificationTimer.Checked = Config.DisplayNotificationTimer;
checkTimerCountDown.Enabled = checkNotificationTimer.Checked;
checkTimerCountDown.Checked = Config.NotificationTimerCountDown;
trackBarDuration.SetValueSafe(Config.NotificationDurationValue);
labelDurationValue.Text = Config.NotificationDurationValue+" ms/c";
switch(Config.NotificationPosition){ switch(Config.NotificationPosition){
case TweetNotification.Position.TopLeft: radioLocTL.Checked = true; break; case TweetNotification.Position.TopLeft: radioLocTL.Checked = true; break;
case TweetNotification.Position.TopRight: radioLocTR.Checked = true; break; case TweetNotification.Position.TopRight: radioLocTR.Checked = true; break;
@@ -35,24 +70,7 @@ namespace TweetDuck.Core.Other.Settings{
} }
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = !radioLocCustom.Checked; comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = !radioLocCustom.Checked;
comboBoxDisplay.Items.Add("(Same as TweetDuck)");
switch(Config.NotificationSize){
case TweetNotification.Size.Auto: radioSizeAuto.Checked = true; break;
case TweetNotification.Size.Custom: radioSizeCustom.Checked = true; break;
}
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){ foreach(Screen screen in Screen.AllScreens){
comboBoxDisplay.Items.Add(screen.DeviceName.TrimStart('\\', '.')+" ("+screen.Bounds.Width+"x"+screen.Bounds.Height+")"); comboBoxDisplay.Items.Add(screen.DeviceName.TrimStart('\\', '.')+" ("+screen.Bounds.Width+"x"+screen.Bounds.Height+")");
@@ -60,59 +78,57 @@ namespace TweetDuck.Core.Other.Settings{
comboBoxDisplay.SelectedIndex = Math.Min(comboBoxDisplay.Items.Count-1, Config.NotificationDisplay); comboBoxDisplay.SelectedIndex = Math.Min(comboBoxDisplay.Items.Count-1, Config.NotificationDisplay);
checkColumnName.Checked = Config.DisplayNotificationColumn; trackBarEdgeDistance.SetValueSafe(Config.NotificationEdgeDistance);
checkNotificationTimer.Checked = Config.DisplayNotificationTimer; labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value+" px";
checkTimerCountDown.Enabled = checkNotificationTimer.Checked;
checkTimerCountDown.Checked = Config.NotificationTimerCountDown; switch(Config.NotificationSize){
checkSkipOnLinkClick.Checked = Config.NotificationSkipOnLinkClick; case TweetNotification.Size.Auto: radioSizeAuto.Checked = true; break;
checkNonIntrusive.Checked = Config.NotificationNonIntrusiveMode; case TweetNotification.Size.Custom: radioSizeCustom.Checked = true; break;
}
trackBarScrollSpeed.SetValueSafe(Config.NotificationScrollSpeed); trackBarScrollSpeed.SetValueSafe(Config.NotificationScrollSpeed);
labelScrollSpeedValue.Text = trackBarScrollSpeed.Value.ToString(CultureInfo.InvariantCulture)+"%"; labelScrollSpeedValue.Text = trackBarScrollSpeed.Value+"%";
trackBarEdgeDistance.SetValueSafe(Config.NotificationEdgeDistance);
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value.ToString(CultureInfo.InvariantCulture)+" px";
this.notification.CanMoveWindow = () => radioLocCustom.Checked;
this.notification.CanResizeWindow = radioSizeCustom.Checked;
Disposed += (sender, args) => this.notification.Dispose(); Disposed += (sender, args) => this.notification.Dispose();
} }
public override void OnReady(){ public override void OnReady(){
radioLocTL.CheckedChanged += radioLoc_CheckedChanged; checkColumnName.CheckedChanged += checkColumnName_CheckedChanged;
radioLocTR.CheckedChanged += radioLoc_CheckedChanged; checkMediaPreviews.CheckedChanged += checkMediaPreviews_CheckedChanged;
radioLocBL.CheckedChanged += radioLoc_CheckedChanged; checkSkipOnLinkClick.CheckedChanged += checkSkipOnLinkClick_CheckedChanged;
radioLocBR.CheckedChanged += radioLoc_CheckedChanged; checkNonIntrusive.CheckedChanged += checkNonIntrusive_CheckedChanged;
radioLocCustom.Click += radioLocCustom_Click;
radioSizeAuto.CheckedChanged += radioSize_CheckedChanged; comboBoxIdlePause.SelectedValueChanged += comboBoxIdlePause_SelectedValueChanged;
radioSizeCustom.Click += radioSizeCustom_Click;
checkNotificationTimer.CheckedChanged += checkNotificationTimer_CheckedChanged;
checkTimerCountDown.CheckedChanged += checkTimerCountDown_CheckedChanged;
trackBarDuration.ValueChanged += trackBarDuration_ValueChanged; trackBarDuration.ValueChanged += trackBarDuration_ValueChanged;
btnDurationShort.Click += btnDurationShort_Click; btnDurationShort.Click += btnDurationShort_Click;
btnDurationMedium.Click += btnDurationMedium_Click; btnDurationMedium.Click += btnDurationMedium_Click;
btnDurationLong.Click += btnDurationLong_Click; btnDurationLong.Click += btnDurationLong_Click;
checkColumnName.CheckedChanged += checkColumnName_CheckedChanged; radioLocTL.CheckedChanged += radioLoc_CheckedChanged;
checkNotificationTimer.CheckedChanged += checkNotificationTimer_CheckedChanged; radioLocTR.CheckedChanged += radioLoc_CheckedChanged;
checkTimerCountDown.CheckedChanged += checkTimerCountDown_CheckedChanged; radioLocBL.CheckedChanged += radioLoc_CheckedChanged;
checkSkipOnLinkClick.CheckedChanged += checkSkipOnLinkClick_CheckedChanged; radioLocBR.CheckedChanged += radioLoc_CheckedChanged;
checkNonIntrusive.CheckedChanged += checkNonIntrusive_CheckedChanged; radioLocCustom.Click += radioLocCustom_Click;
comboBoxIdlePause.SelectedValueChanged += comboBoxIdlePause_SelectedValueChanged;
trackBarScrollSpeed.ValueChanged += trackBarScrollSpeed_ValueChanged;
comboBoxDisplay.SelectedValueChanged += comboBoxDisplay_SelectedValueChanged; comboBoxDisplay.SelectedValueChanged += comboBoxDisplay_SelectedValueChanged;
trackBarEdgeDistance.ValueChanged += trackBarEdgeDistance_ValueChanged; trackBarEdgeDistance.ValueChanged += trackBarEdgeDistance_ValueChanged;
radioSizeAuto.CheckedChanged += radioSize_CheckedChanged;
radioSizeCustom.Click += radioSizeCustom_Click;
trackBarScrollSpeed.ValueChanged += trackBarScrollSpeed_ValueChanged;
} }
private void TabSettingsNotifications_ParentChanged(object sender, EventArgs e){ private void TabSettingsNotifications_ParentChanged(object sender, EventArgs e){
if (Parent == null){ if (Parent == null){
notification.HideNotification(false); notification.HideNotification();
} }
else{ else{
notification.ShowNotificationForSettings(true); notification.ShowExampleNotification(true);
} }
} }
@@ -130,7 +146,7 @@ namespace TweetDuck.Core.Other.Settings{
private void notification_ResizeEnd(object sender, EventArgs e){ private void notification_ResizeEnd(object sender, EventArgs e){
if (radioSizeCustom.Checked){ if (radioSizeCustom.Checked){
Config.CustomNotificationSize = notification.BrowserSize; Config.CustomNotificationSize = notification.BrowserSize;
notification.ShowNotificationForSettings(false); notification.ShowExampleNotification(false);
} }
} }
@@ -141,7 +157,7 @@ namespace TweetDuck.Core.Other.Settings{
else if (radioLocBR.Checked)Config.NotificationPosition = TweetNotification.Position.BottomRight; else if (radioLocBR.Checked)Config.NotificationPosition = TweetNotification.Position.BottomRight;
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = true; comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = true;
notification.ShowNotificationForSettings(false); notification.ShowExampleNotification(false);
} }
private void radioLocCustom_Click(object sender, EventArgs e){ private void radioLocCustom_Click(object sender, EventArgs e){
@@ -152,9 +168,9 @@ namespace TweetDuck.Core.Other.Settings{
Config.NotificationPosition = TweetNotification.Position.Custom; Config.NotificationPosition = TweetNotification.Position.Custom;
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = false; comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = false;
notification.ShowNotificationForSettings(false); notification.ShowExampleNotification(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){ if (notification.IsFullyOutsideView() && FormMessage.Question("Notification is outside view", "The notification seems to be outside of view, would you like to reset its position?", FormMessage.Yes, FormMessage.No)){
Config.NotificationPosition = TweetNotification.Position.TopRight; Config.NotificationPosition = TweetNotification.Position.TopRight;
notification.MoveToVisibleLocation(); notification.MoveToVisibleLocation();
@@ -166,10 +182,11 @@ namespace TweetDuck.Core.Other.Settings{
} }
private void radioSize_CheckedChanged(object sender, EventArgs e){ private void radioSize_CheckedChanged(object sender, EventArgs e){
if (radioSizeAuto.Checked)Config.NotificationSize = TweetNotification.Size.Auto; if (radioSizeAuto.Checked){
Config.NotificationSize = TweetNotification.Size.Auto;
}
notification.ShowNotificationForSettings(false); notification.ShowExampleNotification(false);
notification.CanResizeWindow = false; // must be after ShowNotificationForSettings
} }
private void radioSizeCustom_Click(object sender, EventArgs e){ private void radioSizeCustom_Click(object sender, EventArgs e){
@@ -178,9 +195,7 @@ namespace TweetDuck.Core.Other.Settings{
} }
Config.NotificationSize = TweetNotification.Size.Custom; Config.NotificationSize = TweetNotification.Size.Custom;
notification.ShowExampleNotification(false);
notification.CanResizeWindow = true;
notification.ShowNotificationForSettings(false);
} }
private void trackBarDuration_ValueChanged(object sender, EventArgs e){ private void trackBarDuration_ValueChanged(object sender, EventArgs e){
@@ -205,18 +220,22 @@ namespace TweetDuck.Core.Other.Settings{
private void checkColumnName_CheckedChanged(object sender, EventArgs e){ private void checkColumnName_CheckedChanged(object sender, EventArgs e){
Config.DisplayNotificationColumn = checkColumnName.Checked; Config.DisplayNotificationColumn = checkColumnName.Checked;
notification.ShowNotificationForSettings(false); notification.ShowExampleNotification(false);
} }
private void checkNotificationTimer_CheckedChanged(object sender, EventArgs e){ private void checkNotificationTimer_CheckedChanged(object sender, EventArgs e){
Config.DisplayNotificationTimer = checkNotificationTimer.Checked; Config.DisplayNotificationTimer = checkNotificationTimer.Checked;
checkTimerCountDown.Enabled = checkNotificationTimer.Checked; checkTimerCountDown.Enabled = checkNotificationTimer.Checked;
notification.ShowNotificationForSettings(true); notification.ShowExampleNotification(true);
} }
private void checkTimerCountDown_CheckedChanged(object sender, EventArgs e){ private void checkTimerCountDown_CheckedChanged(object sender, EventArgs e){
Config.NotificationTimerCountDown = checkTimerCountDown.Checked; Config.NotificationTimerCountDown = checkTimerCountDown.Checked;
notification.ShowNotificationForSettings(true); notification.ShowExampleNotification(true);
}
private void checkMediaPreviews_CheckedChanged(object sender, EventArgs e){
Config.NotificationMediaPreviews = checkMediaPreviews.Checked;
} }
private void checkSkipOnLinkClick_CheckedChanged(object sender, EventArgs e){ private void checkSkipOnLinkClick_CheckedChanged(object sender, EventArgs e){
@@ -233,24 +252,24 @@ namespace TweetDuck.Core.Other.Settings{
private void trackBarScrollSpeed_ValueChanged(object sender, EventArgs e){ private void trackBarScrollSpeed_ValueChanged(object sender, EventArgs e){
if (trackBarScrollSpeed.AlignValueToTick()){ if (trackBarScrollSpeed.AlignValueToTick()){
labelScrollSpeedValue.Text = trackBarScrollSpeed.Value.ToString(CultureInfo.InvariantCulture)+"%"; labelScrollSpeedValue.Text = trackBarScrollSpeed.Value+"%";
Config.NotificationScrollSpeed = trackBarScrollSpeed.Value; Config.NotificationScrollSpeed = trackBarScrollSpeed.Value;
} }
} }
private void comboBoxDisplay_SelectedValueChanged(object sender, EventArgs e){ private void comboBoxDisplay_SelectedValueChanged(object sender, EventArgs e){
Config.NotificationDisplay = comboBoxDisplay.SelectedIndex; Config.NotificationDisplay = comboBoxDisplay.SelectedIndex;
notification.ShowNotificationForSettings(false); notification.ShowExampleNotification(false);
} }
private void trackBarEdgeDistance_ValueChanged(object sender, EventArgs e){ private void trackBarEdgeDistance_ValueChanged(object sender, EventArgs e){
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value.ToString(CultureInfo.InvariantCulture)+" px"; labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value+" px";
Config.NotificationEdgeDistance = trackBarEdgeDistance.Value; Config.NotificationEdgeDistance = trackBarEdgeDistance.Value;
notification.ShowNotificationForSettings(false); notification.ShowExampleNotification(false);
} }
private void durationUpdateTimer_Tick(object sender, EventArgs e){ private void durationUpdateTimer_Tick(object sender, EventArgs e){
notification.ShowNotificationForSettings(true); notification.ShowExampleNotification(true);
durationUpdateTimer.Stop(); durationUpdateTimer.Stop();
} }
} }

View File

@@ -25,15 +25,44 @@
private void InitializeComponent() { private void InitializeComponent() {
this.components = new System.ComponentModel.Container(); this.components = new System.ComponentModel.Container();
this.toolTip = new System.Windows.Forms.ToolTip(this.components); this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.tbCustomSound = new System.Windows.Forms.TextBox();
this.labelVolumeValue = new System.Windows.Forms.Label();
this.btnPlaySound = new System.Windows.Forms.Button(); this.btnPlaySound = new System.Windows.Forms.Button();
this.btnResetSound = new System.Windows.Forms.Button(); this.btnResetSound = new System.Windows.Forms.Button();
this.btnBrowseSound = new System.Windows.Forms.Button(); this.btnBrowseSound = new System.Windows.Forms.Button();
this.tbCustomSound = new System.Windows.Forms.TextBox();
this.labelSoundNotification = new System.Windows.Forms.Label(); this.labelSoundNotification = new System.Windows.Forms.Label();
this.panelSoundNotification = new System.Windows.Forms.Panel(); this.panelSoundNotification = new System.Windows.Forms.Panel();
this.labelVolume = new System.Windows.Forms.Label();
this.trackBarVolume = new System.Windows.Forms.TrackBar();
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
this.panelVolume = new System.Windows.Forms.Panel();
this.volumeUpdateTimer = new System.Windows.Forms.Timer(this.components);
this.panelSoundNotification.SuspendLayout(); this.panelSoundNotification.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.trackBarVolume)).BeginInit();
this.flowPanel.SuspendLayout();
this.panelVolume.SuspendLayout();
this.SuspendLayout(); this.SuspendLayout();
// //
// tbCustomSound
//
this.tbCustomSound.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.tbCustomSound.Location = new System.Drawing.Point(3, 3);
this.tbCustomSound.Name = "tbCustomSound";
this.tbCustomSound.Size = new System.Drawing.Size(316, 20);
this.tbCustomSound.TabIndex = 0;
//
// labelVolumeValue
//
this.labelVolumeValue.BackColor = System.Drawing.Color.Transparent;
this.labelVolumeValue.Location = new System.Drawing.Point(147, 4);
this.labelVolumeValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.labelVolumeValue.Name = "labelVolumeValue";
this.labelVolumeValue.Size = new System.Drawing.Size(38, 13);
this.labelVolumeValue.TabIndex = 6;
this.labelVolumeValue.Text = "100%";
this.labelVolumeValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
//
// btnPlaySound // btnPlaySound
// //
this.btnPlaySound.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.btnPlaySound.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
@@ -69,21 +98,12 @@
this.btnBrowseSound.Text = "Browse..."; this.btnBrowseSound.Text = "Browse...";
this.btnBrowseSound.UseVisualStyleBackColor = true; this.btnBrowseSound.UseVisualStyleBackColor = true;
// //
// tbCustomSound
//
this.tbCustomSound.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.tbCustomSound.Location = new System.Drawing.Point(3, 3);
this.tbCustomSound.Name = "tbCustomSound";
this.tbCustomSound.Size = new System.Drawing.Size(316, 20);
this.tbCustomSound.TabIndex = 0;
//
// labelSoundNotification // labelSoundNotification
// //
this.labelSoundNotification.AutoSize = true; this.labelSoundNotification.AutoSize = true;
this.labelSoundNotification.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); this.labelSoundNotification.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelSoundNotification.Location = new System.Drawing.Point(6, 8); this.labelSoundNotification.Location = new System.Drawing.Point(0, 0);
this.labelSoundNotification.Margin = new System.Windows.Forms.Padding(0, 2, 0, 0); this.labelSoundNotification.Margin = new System.Windows.Forms.Padding(0);
this.labelSoundNotification.Name = "labelSoundNotification"; this.labelSoundNotification.Name = "labelSoundNotification";
this.labelSoundNotification.Size = new System.Drawing.Size(198, 20); this.labelSoundNotification.Size = new System.Drawing.Size(198, 20);
this.labelSoundNotification.TabIndex = 1; this.labelSoundNotification.TabIndex = 1;
@@ -91,29 +111,85 @@
// //
// panelSoundNotification // panelSoundNotification
// //
this.panelSoundNotification.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) this.panelSoundNotification.Anchor = System.Windows.Forms.AnchorStyles.Top;
| System.Windows.Forms.AnchorStyles.Right)));
this.panelSoundNotification.Controls.Add(this.btnPlaySound); this.panelSoundNotification.Controls.Add(this.btnPlaySound);
this.panelSoundNotification.Controls.Add(this.tbCustomSound); this.panelSoundNotification.Controls.Add(this.tbCustomSound);
this.panelSoundNotification.Controls.Add(this.btnResetSound); this.panelSoundNotification.Controls.Add(this.btnResetSound);
this.panelSoundNotification.Controls.Add(this.btnBrowseSound); this.panelSoundNotification.Controls.Add(this.btnBrowseSound);
this.panelSoundNotification.Location = new System.Drawing.Point(9, 31); this.panelSoundNotification.Location = new System.Drawing.Point(0, 20);
this.panelSoundNotification.Margin = new System.Windows.Forms.Padding(0);
this.panelSoundNotification.Name = "panelSoundNotification"; this.panelSoundNotification.Name = "panelSoundNotification";
this.panelSoundNotification.Size = new System.Drawing.Size(322, 56); this.panelSoundNotification.Size = new System.Drawing.Size(322, 55);
this.panelSoundNotification.TabIndex = 2; this.panelSoundNotification.TabIndex = 2;
// //
// labelVolume
//
this.labelVolume.AutoSize = true;
this.labelVolume.Location = new System.Drawing.Point(3, 87);
this.labelVolume.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelVolume.Name = "labelVolume";
this.labelVolume.Size = new System.Drawing.Size(42, 13);
this.labelVolume.TabIndex = 4;
this.labelVolume.Text = "Volume";
//
// trackBarVolume
//
this.trackBarVolume.AutoSize = false;
this.trackBarVolume.BackColor = System.Drawing.SystemColors.Control;
this.trackBarVolume.Location = new System.Drawing.Point(3, 3);
this.trackBarVolume.Maximum = 100;
this.trackBarVolume.Name = "trackBarVolume";
this.trackBarVolume.Size = new System.Drawing.Size(148, 30);
this.trackBarVolume.TabIndex = 5;
this.trackBarVolume.TickFrequency = 10;
this.trackBarVolume.Value = 100;
this.trackBarVolume.ValueChanged += new System.EventHandler(this.trackBarVolume_ValueChanged);
//
// flowPanel
//
this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.flowPanel.Controls.Add(this.labelSoundNotification);
this.flowPanel.Controls.Add(this.panelSoundNotification);
this.flowPanel.Controls.Add(this.labelVolume);
this.flowPanel.Controls.Add(this.panelVolume);
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanel.Location = new System.Drawing.Point(9, 9);
this.flowPanel.Name = "flowPanel";
this.flowPanel.Size = new System.Drawing.Size(322, 136);
this.flowPanel.TabIndex = 3;
this.flowPanel.WrapContents = false;
//
// panelVolume
//
this.panelVolume.Controls.Add(this.trackBarVolume);
this.panelVolume.Controls.Add(this.labelVolumeValue);
this.panelVolume.Location = new System.Drawing.Point(0, 100);
this.panelVolume.Margin = new System.Windows.Forms.Padding(0);
this.panelVolume.Name = "panelVolume";
this.panelVolume.Size = new System.Drawing.Size(322, 36);
this.panelVolume.TabIndex = 2;
//
// volumeUpdateTimer
//
this.volumeUpdateTimer.Interval = 250;
this.volumeUpdateTimer.Tick += new System.EventHandler(this.volumeUpdateTimer_Tick);
//
// TabSettingsSounds // TabSettingsSounds
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.panelSoundNotification); this.Controls.Add(this.flowPanel);
this.Controls.Add(this.labelSoundNotification);
this.Name = "TabSettingsSounds"; this.Name = "TabSettingsSounds";
this.Size = new System.Drawing.Size(340, 97); this.Size = new System.Drawing.Size(340, 154);
this.panelSoundNotification.ResumeLayout(false); this.panelSoundNotification.ResumeLayout(false);
this.panelSoundNotification.PerformLayout(); this.panelSoundNotification.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.trackBarVolume)).EndInit();
this.flowPanel.ResumeLayout(false);
this.flowPanel.PerformLayout();
this.panelVolume.ResumeLayout(false);
this.ResumeLayout(false); this.ResumeLayout(false);
this.PerformLayout();
} }
@@ -126,5 +202,11 @@
private System.Windows.Forms.Button btnPlaySound; private System.Windows.Forms.Button btnPlaySound;
private System.Windows.Forms.Label labelSoundNotification; private System.Windows.Forms.Label labelSoundNotification;
private System.Windows.Forms.Panel panelSoundNotification; private System.Windows.Forms.Panel panelSoundNotification;
private System.Windows.Forms.Label labelVolume;
private System.Windows.Forms.Label labelVolumeValue;
private System.Windows.Forms.TrackBar trackBarVolume;
private System.Windows.Forms.FlowLayoutPanel flowPanel;
private System.Windows.Forms.Panel panelVolume;
private System.Windows.Forms.Timer volumeUpdateTimer;
} }
} }

View File

@@ -2,23 +2,25 @@
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification; using TweetDuck.Core.Notification;
using TweetLib.Audio.Utils;
namespace TweetDuck.Core.Other.Settings{ namespace TweetDuck.Core.Other.Settings{
partial class TabSettingsSounds : BaseTabSettings{ sealed partial class TabSettingsSounds : BaseTabSettings{
private readonly SoundNotification soundNotification; private readonly Action playSoundNotification;
public TabSettingsSounds(){ public TabSettingsSounds(Action playSoundNotification){
InitializeComponent(); InitializeComponent();
soundNotification = new SoundNotification(); this.playSoundNotification = playSoundNotification;
soundNotification.PlaybackError += sound_PlaybackError;
toolTip.SetToolTip(tbCustomSound, "When empty, the default TweetDeck sound notification is used.");
trackBarVolume.SetValueSafe(Config.NotificationSoundVolume);
labelVolumeValue.Text = trackBarVolume.Value+"%";
tbCustomSound.Text = Config.NotificationSoundPath; tbCustomSound.Text = Config.NotificationSoundPath;
tbCustomSound_TextChanged(tbCustomSound, new EventArgs()); tbCustomSound_TextChanged(tbCustomSound, EventArgs.Empty);
Disposed += (sender, args) => soundNotification.Dispose();
} }
public override void OnReady(){ public override void OnReady(){
@@ -30,21 +32,29 @@ namespace TweetDuck.Core.Other.Settings{
public override void OnClosing(){ public override void OnClosing(){
Config.NotificationSoundPath = tbCustomSound.Text; Config.NotificationSoundPath = tbCustomSound.Text;
Config.NotificationSoundVolume = trackBarVolume.Value;
}
private bool RefreshCanPlay(){
bool isEmpty = string.IsNullOrEmpty(tbCustomSound.Text);
bool canPlay = isEmpty || File.Exists(tbCustomSound.Text);
tbCustomSound.ForeColor = canPlay ? SystemColors.WindowText : Color.Red;
btnPlaySound.Enabled = canPlay;
btnResetSound.Enabled = !isEmpty;
return canPlay;
} }
private void tbCustomSound_TextChanged(object sender, EventArgs e){ private void tbCustomSound_TextChanged(object sender, EventArgs e){
bool isEmpty = string.IsNullOrEmpty(tbCustomSound.Text); RefreshCanPlay();
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){ private void btnPlaySound_Click(object sender, EventArgs e){
soundNotification.Play(tbCustomSound.Text); if (RefreshCanPlay()){
Config.NotificationSoundPath = tbCustomSound.Text;
Config.NotificationSoundVolume = trackBarVolume.Value;
playSoundNotification();
} }
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){ private void btnBrowseSound_Click(object sender, EventArgs e){
@@ -52,7 +62,7 @@ namespace TweetDuck.Core.Other.Settings{
AutoUpgradeEnabled = true, AutoUpgradeEnabled = true,
DereferenceLinks = true, DereferenceLinks = true,
Title = "Custom Notification Sound", Title = "Custom Notification Sound",
Filter = "Sound file ("+soundNotification.SupportedFormats+")|"+soundNotification.SupportedFormats+"|All files (*.*)|*.*" Filter = $"Sound file ({SoundNotification.SupportedFormats})|{SoundNotification.SupportedFormats}|All files (*.*)|*.*"
}){ }){
if (dialog.ShowDialog() == DialogResult.OK){ if (dialog.ShowDialog() == DialogResult.OK){
tbCustomSound.Text = dialog.FileName; tbCustomSound.Text = dialog.FileName;
@@ -63,5 +73,16 @@ namespace TweetDuck.Core.Other.Settings{
private void btnResetSound_Click(object sender, EventArgs e){ private void btnResetSound_Click(object sender, EventArgs e){
tbCustomSound.Text = string.Empty; tbCustomSound.Text = string.Empty;
} }
private void trackBarVolume_ValueChanged(object sender, EventArgs e){
volumeUpdateTimer.Stop();
volumeUpdateTimer.Start();
labelVolumeValue.Text = trackBarVolume.Value+"%";
}
private void volumeUpdateTimer_Tick(object sender, EventArgs e){
Config.NotificationSoundVolume = trackBarVolume.Value;
volumeUpdateTimer.Stop();
}
} }
} }

View File

@@ -0,0 +1,115 @@
namespace TweetDuck.Core.Other.Settings {
partial class TabSettingsTray {
/// <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.checkTrayHighlight = new System.Windows.Forms.CheckBox();
this.comboBoxTrayType = new System.Windows.Forms.ComboBox();
this.labelTrayIcon = new System.Windows.Forms.Label();
this.labelTray = new System.Windows.Forms.Label();
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
this.flowPanel.SuspendLayout();
this.SuspendLayout();
//
// checkTrayHighlight
//
this.checkTrayHighlight.AutoSize = true;
this.checkTrayHighlight.Location = new System.Drawing.Point(6, 77);
this.checkTrayHighlight.Margin = new System.Windows.Forms.Padding(6, 6, 3, 3);
this.checkTrayHighlight.Name = "checkTrayHighlight";
this.checkTrayHighlight.Size = new System.Drawing.Size(103, 17);
this.checkTrayHighlight.TabIndex = 2;
this.checkTrayHighlight.Text = "Enable Highlight";
this.checkTrayHighlight.UseVisualStyleBackColor = true;
//
// comboBoxTrayType
//
this.comboBoxTrayType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxTrayType.FormattingEnabled = true;
this.comboBoxTrayType.Location = new System.Drawing.Point(5, 25);
this.comboBoxTrayType.Margin = new System.Windows.Forms.Padding(5, 5, 3, 3);
this.comboBoxTrayType.Name = "comboBoxTrayType";
this.comboBoxTrayType.Size = new System.Drawing.Size(144, 21);
this.comboBoxTrayType.TabIndex = 0;
//
// labelTrayIcon
//
this.labelTrayIcon.AutoSize = true;
this.labelTrayIcon.Location = new System.Drawing.Point(3, 58);
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 = 1;
this.labelTrayIcon.Text = "Tray Icon";
//
// labelTray
//
this.labelTray.AutoSize = true;
this.labelTray.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelTray.Location = new System.Drawing.Point(0, 0);
this.labelTray.Margin = new System.Windows.Forms.Padding(0);
this.labelTray.Name = "labelTray";
this.labelTray.Size = new System.Drawing.Size(96, 20);
this.labelTray.TabIndex = 0;
this.labelTray.Text = "System Tray";
//
// flowPanel
//
this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.flowPanel.Controls.Add(this.labelTray);
this.flowPanel.Controls.Add(this.comboBoxTrayType);
this.flowPanel.Controls.Add(this.labelTrayIcon);
this.flowPanel.Controls.Add(this.checkTrayHighlight);
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanel.Location = new System.Drawing.Point(9, 9);
this.flowPanel.Name = "flowPanel";
this.flowPanel.Size = new System.Drawing.Size(322, 97);
this.flowPanel.TabIndex = 2;
this.flowPanel.WrapContents = false;
//
// TabSettingsTray
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.flowPanel);
this.Name = "TabSettingsTray";
this.Size = new System.Drawing.Size(340, 115);
this.flowPanel.ResumeLayout(false);
this.flowPanel.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.CheckBox checkTrayHighlight;
private System.Windows.Forms.ComboBox comboBoxTrayType;
private System.Windows.Forms.Label labelTrayIcon;
private System.Windows.Forms.Label labelTray;
private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.FlowLayoutPanel flowPanel;
}
}

View File

@@ -0,0 +1,36 @@
using System;
namespace TweetDuck.Core.Other.Settings{
sealed partial class TabSettingsTray : BaseTabSettings{
public TabSettingsTray(){
InitializeComponent();
toolTip.SetToolTip(comboBoxTrayType, "Changes behavior of the Tray icon.\r\nRight-click the icon for an action menu.");
toolTip.SetToolTip(checkTrayHighlight, "Highlights the tray icon if there are new tweets.\r\nOnly works for columns with popup or audio notifications.\r\nThe icon resets when the main window is restored.");
comboBoxTrayType.Items.Add("Disabled");
comboBoxTrayType.Items.Add("Display Icon Only");
comboBoxTrayType.Items.Add("Minimize to Tray");
comboBoxTrayType.Items.Add("Close to Tray");
comboBoxTrayType.Items.Add("Combined");
comboBoxTrayType.SelectedIndex = Math.Min(Math.Max((int)Config.TrayBehavior, 0), comboBoxTrayType.Items.Count-1);
checkTrayHighlight.Enabled = Config.TrayBehavior.ShouldDisplayIcon();
checkTrayHighlight.Checked = Config.EnableTrayHighlight;
}
public override void OnReady(){
comboBoxTrayType.SelectedIndexChanged += comboBoxTrayType_SelectedIndexChanged;
checkTrayHighlight.CheckedChanged += checkTrayHighlight_CheckedChanged;
}
private void comboBoxTrayType_SelectedIndexChanged(object sender, EventArgs e){
Config.TrayBehavior = (TrayIcon.Behavior)comboBoxTrayType.SelectedIndex;
checkTrayHighlight.Enabled = Config.TrayBehavior.ShouldDisplayIcon();
}
private void checkTrayHighlight_CheckedChanged(object sender, EventArgs e){
Config.EnableTrayHighlight = checkTrayHighlight.Checked;
}
}
}

View File

@@ -1,4 +1,4 @@
namespace TweetDuck.Core { namespace TweetDuck.Core.Other {
partial class TrayIcon { partial class TrayIcon {
/// <summary> /// <summary>
/// Required designer variable. /// Required designer variable.

View File

@@ -2,8 +2,8 @@
using System.ComponentModel; using System.ComponentModel;
using System.Windows.Forms; using System.Windows.Forms;
namespace TweetDuck.Core{ namespace TweetDuck.Core.Other{
partial class TrayIcon : Component{ sealed partial class TrayIcon : Component{
public enum Behavior{ // keep order public enum Behavior{ // keep order
Disabled, DisplayOnly, MinimizeToTray, CloseToTray, Combined Disabled, DisplayOnly, MinimizeToTray, CloseToTray, Combined
} }
@@ -12,7 +12,9 @@ namespace TweetDuck.Core{
public event EventHandler ClickClose; public event EventHandler ClickClose;
public bool Visible{ public bool Visible{
get => notifyIcon.Visible; get{
return notifyIcon.Visible;
}
set{ set{
if (value){ if (value){

253
Core/TweetDeckBrowser.cs Normal file
View File

@@ -0,0 +1,253 @@
using System;
using System.Drawing;
using System.Windows.Forms;
using CefSharp;
using CefSharp.WinForms;
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling;
using TweetDuck.Core.Handling.General;
using TweetDuck.Core.Notification;
using TweetDuck.Core.Utils;
using TweetDuck.Plugins;
using TweetDuck.Plugins.Enums;
using TweetDuck.Plugins.Events;
using TweetDuck.Resources;
using TweetDuck.Updates;
namespace TweetDuck.Core{
sealed class TweetDeckBrowser : IDisposable{
public bool Ready { get; private set; }
public bool Enabled{
get => browser.Enabled;
set => browser.Enabled = value;
}
public bool IsTweetDeckWebsite{
get{
using(IFrame frame = browser.GetBrowser().MainFrame){
return TwitterUtils.IsTweetDeckWebsite(frame);
}
}
}
public event EventHandler PageLoaded;
private readonly ChromiumWebBrowser browser;
private readonly PluginManager plugins;
private string prevSoundNotificationPath = null;
public TweetDeckBrowser(FormBrowser owner, PluginManager plugins, TweetDeckBridge bridge){
this.browser = new ChromiumWebBrowser(TwitterUtils.TweetDeckURL){
DialogHandler = new FileDialogHandler(),
DragHandler = new DragHandlerBrowser(),
MenuHandler = new ContextMenuBrowser(owner),
JsDialogHandler = new JavaScriptDialogHandler(),
KeyboardHandler = new KeyboardHandlerBrowser(owner),
LifeSpanHandler = new LifeSpanHandler(),
RequestHandler = new RequestHandlerBrowser()
};
#if DEBUG
this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
#endif
this.browser.LoadingStateChanged += browser_LoadingStateChanged;
this.browser.FrameLoadStart += browser_FrameLoadStart;
this.browser.FrameLoadEnd += browser_FrameLoadEnd;
this.browser.LoadError += browser_LoadError;
this.browser.RegisterAsyncJsObject("$TD", bridge);
this.browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
this.browser.BrowserSettings.BackgroundColor = (uint)TwitterUtils.BackgroundColor.ToArgb();
this.browser.Dock = DockStyle.None;
this.browser.Location = ControlExtensions.InvisibleLocation;
owner.Controls.Add(browser);
this.plugins = plugins;
this.plugins.PluginChangedState += plugins_PluginChangedState;
Program.UserConfig.MuteToggled += UserConfig_MuteToggled;
Program.UserConfig.ZoomLevelChanged += UserConfig_ZoomLevelChanged;
Program.UserConfig.SoundNotificationChanged += UserConfig_SoundNotificationInfoChanged;
}
// setup and management
private void OnBrowserReady(){
if (!Ready){
browser.Location = Point.Empty;
browser.Dock = DockStyle.Fill;
Ready = true;
}
}
public void Focus(){
browser.Focus();
}
public void Dispose(){
plugins.PluginChangedState -= plugins_PluginChangedState;
Program.UserConfig.MuteToggled -= UserConfig_MuteToggled;
Program.UserConfig.ZoomLevelChanged -= UserConfig_ZoomLevelChanged;
Program.UserConfig.SoundNotificationChanged -= UserConfig_SoundNotificationInfoChanged;
browser.Dispose();
}
// event handlers
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
if (!e.IsLoading){
foreach(string word in TwitterUtils.DictionaryWords){
browser.AddWordToDictionary(word);
}
browser.BeginInvoke(new Action(OnBrowserReady));
browser.LoadingStateChanged -= browser_LoadingStateChanged;
}
}
private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){
if (e.Frame.IsMain){
if (Program.UserConfig.ZoomLevel != 100){
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Program.UserConfig.ZoomLevel);
}
if (TwitterUtils.IsTwitterWebsite(e.Frame)){
ScriptLoader.ExecuteFile(e.Frame, "twitter.js");
}
}
}
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
if (e.Frame.IsMain && TwitterUtils.IsTweetDeckWebsite(e.Frame)){
e.Frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorFix);
UpdateProperties();
TweetDeckBridge.RestoreSessionData(e.Frame);
ScriptLoader.ExecuteFile(e.Frame, "code.js");
InjectBrowserCSS();
ReinjectCustomCSS(Program.UserConfig.CustomBrowserCSS);
UserConfig_SoundNotificationInfoChanged(null, EventArgs.Empty);
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Browser);
TweetDeckBridge.ResetStaticProperties();
if (Program.UserConfig.FirstRun){
ScriptLoader.ExecuteFile(e.Frame, "introduction.js");
}
PageLoaded?.Invoke(this, EventArgs.Empty);
}
}
private void browser_LoadError(object sender, LoadErrorEventArgs e){
if (e.ErrorCode == CefErrorCode.Aborted){
return;
}
if (!e.FailedUrl.StartsWith("http://td/", StringComparison.Ordinal)){
string errorPage = ScriptLoader.LoadResource("pages/error.html", true);
if (errorPage != null){
browser.LoadHtml(errorPage.Replace("{err}", BrowserUtils.GetErrorName(e.ErrorCode)), "http://td/error");
}
}
}
private void plugins_PluginChangedState(object sender, PluginChangedStateEventArgs e){
browser.ExecuteScriptAsync("TDPF_setPluginState", e.Plugin, e.IsEnabled);
}
private void UserConfig_MuteToggled(object sender, EventArgs e){
UpdateProperties();
}
private void UserConfig_ZoomLevelChanged(object sender, EventArgs e){
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Program.UserConfig.ZoomLevel);
}
private void UserConfig_SoundNotificationInfoChanged(object sender, EventArgs e){
const string soundUrl = "https://ton.twimg.com/tduck/updatesnd";
bool hasCustomSound = Program.UserConfig.IsCustomSoundNotificationSet;
if (prevSoundNotificationPath != Program.UserConfig.NotificationSoundPath){
DefaultResourceHandlerFactory handlerFactory = browser.GetHandlerFactory();
IResourceHandler resourceHandler = hasCustomSound ? SoundNotification.CreateFileHandler(Program.UserConfig.NotificationSoundPath) : null;
if (resourceHandler != null){
handlerFactory.RegisterHandler(soundUrl, resourceHandler);
}
else{
handlerFactory.UnregisterHandler(soundUrl);
}
prevSoundNotificationPath = Program.UserConfig.NotificationSoundPath;
}
browser.ExecuteScriptAsync("TDGF_setSoundNotificationData", hasCustomSound, Program.UserConfig.NotificationSoundVolume);
}
// external handling
public UpdateHandler CreateUpdateHandler(UpdaterSettings settings){
return new UpdateHandler(browser, settings);
}
public void HideVideoOverlay(bool focus){
if (focus){
browser.GetBrowser().GetHost().SendFocusEvent(true);
}
browser.ExecuteScriptAsync("$('#td-video-player-overlay').remove()");
}
// javascript calls
public void ReloadToTweetDeck(){
browser.ExecuteScriptAsync($"if(window.TDGF_reload)window.TDGF_reload();else window.location.href='{TwitterUtils.TweetDeckURL}'");
}
public void UpdateProperties(){
browser.ExecuteScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Environment.Browser));
}
public void InjectBrowserCSS(){
browser.ExecuteScriptAsync("TDGF_injectBrowserCSS", ScriptLoader.LoadResource("styles/browser.css").TrimEnd());
}
public void ReinjectCustomCSS(string css){
browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty);
}
public void OnMouseClickExtra(IntPtr param){
browser.ExecuteScriptAsync("TDGF_onMouseClickExtra", (param.ToInt32() >> 16) & 0xFFFF);
}
public void ShowTweetDetail(string columnId, string chirpId, string fallbackUrl){
browser.ExecuteScriptAsync("TDGF_showTweetDetail", columnId, chirpId, fallbackUrl);
}
public void TriggerTweetScreenshot(){
browser.ExecuteScriptAsync("TDGF_triggerScreenshot()");
}
public void ReloadColumns(){
browser.ExecuteScriptAsync("TDGF_reloadColumns()");
}
public void PlaySoundNotification(){
browser.ExecuteScriptAsync("TDGF_playSoundNotification()");
}
public void ApplyROT13(){
browser.ExecuteScriptAsync("TDGF_applyROT13()");
}
}
}

View File

@@ -1,45 +1,68 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Linq; using System.Linq;
using System.Threading;
namespace TweetDuck.Core.Utils{ namespace TweetDuck.Core.Utils{
static class BrowserCache{ static class BrowserCache{
private static bool ClearOnExit { get; set; } public static string CacheFolder => Path.Combine(Program.StoragePath, "Cache");
private static readonly string CacheFolder = Path.Combine(Program.StoragePath, "Cache"); private static bool ClearOnExit;
private static Timer AutoClearTimer;
private static IEnumerable<string> CacheFiles{ private static long CalculateCacheSize(){
get{ return new DirectoryInfo(CacheFolder).EnumerateFiles().Select(file => {
return Directory.EnumerateFiles(CacheFolder);
}
}
public static void CalculateCacheSize(Action<long> callbackBytes){
Task<long> task = new Task<long>(() => {
return CacheFiles.Select(file => {
try{ try{
return new FileInfo(file).Length; return file.Length;
}catch{ }catch{
return 0L; return 0L;
} }
}).Sum(); }).Sum();
}); }
task.ContinueWith(originalTask => callbackBytes(originalTask.Exception == null ? originalTask.Result : -1L), TaskContinuationOptions.ExecuteSynchronously); public static void GetCacheSize(Action<Task<long>> callbackBytes){
Task<long> task = new Task<long>(CalculateCacheSize);
task.ContinueWith(callbackBytes);
task.Start(); task.Start();
} }
public static void RefreshTimer(){
bool shouldRun = Program.SystemConfig.ClearCacheAutomatically && !ClearOnExit;
if (!shouldRun && AutoClearTimer != null){
AutoClearTimer.Dispose();
AutoClearTimer = null;
}
else if (shouldRun && AutoClearTimer == null){
AutoClearTimer = new Timer(state => {
if (AutoClearTimer != null){
try{
if (CalculateCacheSize() >= Program.SystemConfig.ClearCacheThreshold*1024L*1024L){
SetClearOnExit();
}
}catch(Exception){
// TODO should probably log errors and report them at some point
}
}
}, null, TimeSpan.FromSeconds(30), TimeSpan.FromHours(4));
}
}
public static void SetClearOnExit(){ public static void SetClearOnExit(){
ClearOnExit = true; ClearOnExit = true;
RefreshTimer();
} }
public static void Exit(){ public static void Exit(){
if (AutoClearTimer != null){
AutoClearTimer.Dispose();
AutoClearTimer = null;
}
if (ClearOnExit){ if (ClearOnExit){
foreach(string file in CacheFiles){
try{ try{
File.Delete(file); Directory.Delete(CacheFolder, true);
}catch{ }catch{
// welp, too bad // welp, too bad
} }
@@ -47,4 +70,3 @@ namespace TweetDuck.Core.Utils{
} }
} }
} }
}

View File

@@ -1,59 +1,87 @@
using CefSharp; using CefSharp;
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Text.RegularExpressions;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp.WinForms;
using TweetDuck.Core.Other;
namespace TweetDuck.Core.Utils{ namespace TweetDuck.Core.Utils{
static class BrowserUtils{ static class BrowserUtils{
public static string HeaderAcceptLanguage{ public static string HeaderAcceptLanguage => "en-us,en";
get{
string culture = CultureInfo.CurrentCulture.Name;
if (culture == "en"){
return "en-us,en";
}
else{
return culture.ToLowerInvariant()+",en;q=0.9";
}
}
}
public static string HeaderUserAgent => Program.BrandName+" "+Application.ProductVersion; public static string HeaderUserAgent => Program.BrandName+" "+Application.ProductVersion;
public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153); public static void SetupCefArgs(IDictionary<string, string> args){
public const string BackgroundColorFix = "let e=document.createElement('style');document.head.appendChild(e);e.innerHTML='body::before{background:#1c6399!important}'"; if (!Program.SystemConfig.HardwareAcceleration){
args["disable-gpu"] = "1";
public static readonly string[] DictionaryWords = { args["disable-gpu-vsync"] = "1";
"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; args["disable-pdf-extension"] = "1";
args["disable-plugins-discovery"] = "1";
args["enable-system-flash"] = "0";
if (args.TryGetValue("js-flags", out string jsFlags)){
args["js-flags"] = "--expose-gc "+jsFlags;
}
else{
args["js-flags"] = "--expose-gc";
}
}
public static ChromiumWebBrowser AsControl(this IWebBrowser browserControl){
return (ChromiumWebBrowser)browserControl;
}
public static DefaultResourceHandlerFactory GetHandlerFactory(this ChromiumWebBrowser browser){
return (DefaultResourceHandlerFactory)browser.ResourceHandlerFactory;
}
private const string TwitterTrackingUrl = "t.co";
public enum UrlCheckResult{
Invalid, Tracking, Fine
}
public static UrlCheckResult CheckUrl(string url){
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){
string scheme = uri.Scheme;
if (scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeHttp || scheme == Uri.UriSchemeFtp || scheme == Uri.UriSchemeMailto){
return uri.Host == TwitterTrackingUrl ? UrlCheckResult.Tracking : UrlCheckResult.Fine;
}
}
return UrlCheckResult.Invalid;
} }
public static void OpenExternalBrowser(string url){ public static void OpenExternalBrowser(string url){
if (string.IsNullOrWhiteSpace(url))return; if (string.IsNullOrWhiteSpace(url))return;
if (IsValidUrl(url)){ switch(CheckUrl(url)){
OpenExternalBrowserUnsafe(url); case UrlCheckResult.Fine:
if (FormGuide.CheckGuideUrl(url, out string hash)){
FormGuide.Show(hash);
} }
else{ else{
MessageBox.Show("A potentially malicious URL was blocked from opening:"+Environment.NewLine+url, "Blocked URL", MessageBoxButtons.OK, MessageBoxIcon.Warning); WindowsUtils.OpenAssociatedProgram(url);
}
} }
public static void OpenExternalBrowserUnsafe(string url){ break;
using(Process.Start(url)){}
case UrlCheckResult.Tracking:
if (FormMessage.Warning("Blocked URL", "TweetDuck has blocked a tracking url due to privacy concerns. Do you want to visit it anyway?\n"+url, FormMessage.Yes, FormMessage.No)){
WindowsUtils.OpenAssociatedProgram(url);
}
break;
case UrlCheckResult.Invalid:
FormMessage.Warning("Blocked URL", "A potentially malicious URL was blocked from opening:\n"+url, FormMessage.OK);
break;
}
} }
public static string GetFileNameFromUrl(string url){ public static string GetFileNameFromUrl(string url){
@@ -61,17 +89,18 @@ namespace TweetDuck.Core.Utils{
return string.IsNullOrEmpty(file) ? null : file; return string.IsNullOrEmpty(file) ? null : file;
} }
public static string ConvertPascalCaseToScreamingSnakeCase(string str){ public static string GetErrorName(CefErrorCode code){
return Regex.Replace(str, @"(\p{Ll})(\P{Ll})|(\P{Ll})(\P{Ll}\p{Ll})", "$1$3_$2$4").ToUpperInvariant(); return StringUtils.ConvertPascalCaseToScreamingSnakeCase(Enum.GetName(typeof(CefErrorCode), code) ?? string.Empty);
} }
public static string GetErrorName(CefErrorCode code){ public static WebClient CreateWebClient(){
return ConvertPascalCaseToScreamingSnakeCase(Enum.GetName(typeof(CefErrorCode), code) ?? string.Empty); WebClient client = new WebClient{ Proxy = null };
client.Headers[HttpRequestHeader.UserAgent] = HeaderUserAgent;
return client;
} }
public static WebClient DownloadFileAsync(string url, string target, Action onSuccess, Action<Exception> onFailure){ public static WebClient DownloadFileAsync(string url, string target, Action onSuccess, Action<Exception> onFailure){
WebClient client = new WebClient{ Proxy = null }; WebClient client = CreateWebClient();
client.Headers[HttpRequestHeader.UserAgent] = HeaderUserAgent;
client.DownloadFileCompleted += (sender, args) => { client.DownloadFileCompleted += (sender, args) => {
if (args.Cancelled){ if (args.Cancelled){
@@ -101,14 +130,6 @@ namespace TweetDuck.Core.Utils{
browser.GetHost().SetZoomLevel(Math.Log(percentage/100.0, 1.2)); 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 #if DEBUG
public static void HandleConsoleMessage(object sender, ConsoleMessageEventArgs e){ public static void HandleConsoleMessage(object sender, ConsoleMessageEventArgs e){
Debug.WriteLine("[Console] {0} ({1}:{2})", e.Message, e.Source, e.Line); Debug.WriteLine("[Console] {0} ({1}:{2})", e.Message, e.Source, e.Line);

View File

@@ -1,38 +0,0 @@
using System;
using System.Text.RegularExpressions;
namespace TweetDuck.Core.Utils{
static class CommandLineArgsParser{
private static readonly Lazy<Regex> SplitRegex = new Lazy<Regex>(() => new Regex(@"([^=\s]+(?:=(?:\S*""[^""]*?""\S*|\S*))?)", RegexOptions.Compiled), false);
public static CommandLineArgs ReadCefArguments(string argumentString){
CommandLineArgs args = new CommandLineArgs();
if (string.IsNullOrWhiteSpace(argumentString)){
return args;
}
foreach(Match match in SplitRegex.Value.Matches(argumentString)){
string matchValue = match.Value;
int indexEquals = matchValue.IndexOf('=');
string key, value;
if (indexEquals == -1){
key = matchValue.TrimStart('-');
value = "1";
}
else{
key = matchValue.Substring(0, indexEquals).TrimStart('-');
value = matchValue.Substring(indexEquals+1).Trim('"');
}
if (key.Length != 0){
args.SetValue(key, value);
}
}
return args;
}
}
}

58
Core/Utils/LocaleUtils.cs Normal file
View File

@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace TweetDuck.Core.Utils{
static class LocaleUtils{
// https://cs.chromium.org/chromium/src/third_party/hunspell_dictionaries/
public static IEnumerable<Item> SpellCheckLanguages { get; } = new List<string>{
"af-ZA", "bg-BG", "ca-ES", "cs-CZ", "da-DK", "de-DE",
"el-GR", "en-AU", "en-CA", "en-GB", "en-US", "es-ES",
"et-EE", "fa-IR", "fo-FO", "fr-FR", "he-IL", "hi-IN",
"hr-HR", "hu-HU", "id-ID", "it-IT", "ko" , "lt-LT",
"lv-LV", "nb-NO", "nl-NL", "pl-PL", "pt-BR", "pt-PT",
"ro-RO", "ru-RU", "sk-SK", "sl-SI", "sq" , "sr",
"sv-SE", "ta-IN", "tg-TG", "tr" , "uk-UA", "vi-VN"
}.Select(code => {
string lang = StringUtils.ExtractBefore(code, '-', 2);
return lang == "en" || lang == "pt" ? new Item(code) : new Item(code, lang);
}).OrderBy(code => code).ToList();
// TD.languages.getSupportedTranslationDestinationLanguages() except for "ht", "in", "iw" which are missing/duplicates
public static IEnumerable<Item> TweetDeckTranslationLocales { get; } = new List<string>{
"bg", "ca", "zh-cn", "zh-tw", "cs", "da", "nl",
"en", "et", "fi", "fr", "de", "el", "he", "hi",
"hu", "id", "it", "ja", "ko", "lv", "lt", "no",
"pl", "pt", "ro", "ru", "sk", "sl", "es", "sv",
"th", "tr", "uk", "vi", "ar", "fa"
}.Select(code => new Item(code)).OrderBy(code => code).ToList();
public sealed class Item : IComparable<Item>{
public string Code { get; }
public CultureInfo Info { get; }
public Item(string code, string alt = null){
this.Code = code;
this.Info = CultureInfo.GetCultureInfo(alt ?? code);
}
public override bool Equals(object obj){
return obj is Item other && Code.Equals(other.Code, StringComparison.OrdinalIgnoreCase);
}
public override int GetHashCode(){
return Code.GetHashCode();
}
public override string ToString(){
string capitalizedName = Info.TextInfo.ToTitleCase(Info.NativeName);
return Info.DisplayName == Info.NativeName ? capitalizedName : $"{capitalizedName}, {Info.DisplayName}";
}
public int CompareTo(Item other){
return string.Compare(Info.NativeName, other.Info.NativeName, false, CultureInfo.InvariantCulture);
}
}
}
}

View File

@@ -17,7 +17,6 @@ namespace TweetDuck.Core.Utils{
public const int GWL_STYLE = -16; public const int GWL_STYLE = -16;
public const int SB_HORZ = 0; public const int SB_HORZ = 0;
public const int BCM_SETSHIELD = 0x160C;
public const int WM_MOUSE_LL = 14; public const int WM_MOUSE_LL = 14;
public const int WM_MOUSEWHEEL = 0x020A; public const int WM_MOUSEWHEEL = 0x020A;
@@ -53,6 +52,12 @@ namespace TweetDuck.Core.Utils{
[DllImport("user32.dll", EntryPoint = "SetWindowPos")] [DllImport("user32.dll", EntryPoint = "SetWindowPos")]
private 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 bool PostMessage(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern uint RegisterWindowMessage(string messageName);
[DllImport("user32.dll")] [DllImport("user32.dll")]
private static extern bool GetLastInputInfo(ref LASTINPUTINFO info); private static extern bool GetLastInputInfo(ref LASTINPUTINFO info);
@@ -66,12 +71,6 @@ namespace TweetDuck.Core.Utils{
[return: MarshalAs(UnmanagedType.Bool)] [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); 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);
[DllImport("user32.dll")]
public static extern uint RegisterWindowMessage(string messageName);
[DllImport("user32.dll")] [DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ShowScrollBar(IntPtr hWnd, int wBar, bool bShow); public static extern bool ShowScrollBar(IntPtr hWnd, int wBar, bool bShow);
@@ -104,13 +103,18 @@ namespace TweetDuck.Core.Utils{
} }
} }
public static void BroadcastMessage(uint msg, uint wParam, int lParam){
PostMessage(HWND_BROADCAST, msg, new UIntPtr(wParam), new IntPtr(lParam));
}
public static int GetMouseHookData(IntPtr ptr){ public static int GetMouseHookData(IntPtr ptr){
return Marshal.PtrToStructure<MSLLHOOKSTRUCT>(ptr).mouseData >> 16; return Marshal.PtrToStructure<MSLLHOOKSTRUCT>(ptr).mouseData >> 16;
} }
public static int GetIdleSeconds(){ public static int GetIdleSeconds(){
LASTINPUTINFO info = new LASTINPUTINFO(); LASTINPUTINFO info = new LASTINPUTINFO{
info.cbSize = LASTINPUTINFO.Size; cbSize = LASTINPUTINFO.Size
};
if (!GetLastInputInfo(ref info)){ if (!GetLastInputInfo(ref info)){
return 0; return 0;

33
Core/Utils/StringUtils.cs Normal file
View File

@@ -0,0 +1,33 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;
namespace TweetDuck.Core.Utils{
static class StringUtils{
public static readonly string[] EmptyArray = new string[0];
public static string ExtractBefore(string str, char search, int startIndex = 0){
int index = str.IndexOf(search, startIndex);
return index == -1 ? str : str.Substring(0, index);
}
public static int[] ParseInts(string str, char separator){
return str.Split(new char[]{ separator }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray();
}
public static string ConvertPascalCaseToScreamingSnakeCase(string str){
return Regex.Replace(str, @"(\p{Ll})(\P{Ll})|(\P{Ll})(\P{Ll}\p{Ll})", "$1$3_$2$4").ToUpper();
}
public static int CountOccurrences(string source, string substring){
int count = 0, index = 0;
while((index = source.IndexOf(substring, index)) != -1){
index += substring.Length;
++count;
}
return count;
}
}
}

133
Core/Utils/TwitterUtils.cs Normal file
View File

@@ -0,0 +1,133 @@
using System;
using CefSharp;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using TweetDuck.Core.Other;
namespace TweetDuck.Core.Utils{
static class TwitterUtils{
public const string TweetDeckURL = "https://tweetdeck.twitter.com";
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}'";
private static readonly Lazy<Regex> RegexAccountLazy = new Lazy<Regex>(() => new Regex(@"^https?://twitter\.com/(?!signup$|tos$|privacy$)([^/]+)/?$", RegexOptions.Compiled), false);
public static Regex RegexAccount => RegexAccountLazy.Value;
public static readonly string[] DictionaryWords = {
"tweetdeck", "TweetDeck", "tweetduck", "TweetDuck", "TD"
};
public static readonly string[] ValidImageExtensions = {
".jpg", ".jpeg", ".png", ".gif"
};
public enum ImageQuality{
Default, Orig
}
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/");
}
private static string ExtractMediaBaseLink(string url){
int dot = url.LastIndexOf('/');
return dot == -1 ? url : StringUtils.ExtractBefore(url, ':', dot);
}
public static string GetMediaLink(string url, ImageQuality quality){
if (quality == ImageQuality.Orig){
string result = ExtractMediaBaseLink(url);
if (result != url || url.Contains("//pbs.twimg.com/media/")){
result += ":orig";
}
return result;
}
else{
return url;
}
}
public static string GetImageFileName(string url){
return BrowserUtils.GetFileNameFromUrl(ExtractMediaBaseLink(url));
}
public static void DownloadImage(string url, string username, ImageQuality quality){
DownloadImages(new string[]{ url }, username, quality);
}
public static void DownloadImages(string[] urls, string username, ImageQuality quality){
if (urls.Length == 0){
return;
}
string firstImageLink = GetMediaLink(urls[0], quality);
int qualityIndex = firstImageLink.IndexOf(':', firstImageLink.LastIndexOf('/'));
string file = GetImageFileName(firstImageLink);
string ext = Path.GetExtension(file); // includes dot
string[] fileNameParts = qualityIndex == -1 ? new string[]{
Path.ChangeExtension(file, null)
} : new string[]{
username,
Path.ChangeExtension(file, null),
firstImageLink.Substring(qualityIndex+1)
};
using(SaveFileDialog dialog = new SaveFileDialog{
AutoUpgradeEnabled = true,
OverwritePrompt = urls.Length == 1,
Title = "Save image",
FileName = $"{string.Join(" ", fileNameParts.Where(part => part.Length > 0))}{ext}",
Filter = (urls.Length == 1 ? "Image" : "Images")+(string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}")
}){
if (dialog.ShowDialog() == DialogResult.OK){
void OnFailure(Exception ex){
FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK);
}
if (urls.Length == 1){
BrowserUtils.DownloadFileAsync(firstImageLink, dialog.FileName, null, OnFailure);
}
else{
string pathBase = Path.ChangeExtension(dialog.FileName, null);
string pathExt = Path.GetExtension(dialog.FileName);
for(int index = 0; index < urls.Length; index++){
BrowserUtils.DownloadFileAsync(GetMediaLink(urls[index], quality), $"{pathBase} {index+1}{pathExt}", null, OnFailure);
}
}
}
}
}
public static void DownloadVideo(string url, string username){
string filename = BrowserUtils.GetFileNameFromUrl(url);
string ext = Path.GetExtension(filename);
using(SaveFileDialog dialog = new SaveFileDialog{
AutoUpgradeEnabled = true,
OverwritePrompt = true,
Title = "Save video",
FileName = string.IsNullOrEmpty(username) ? filename : $"{username} {filename}",
Filter = "Video"+(string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}")
}){
if (dialog.ShowDialog() == DialogResult.OK){
BrowserUtils.DownloadFileAsync(url, dialog.FileName, null, ex => {
FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK);
});
}
}
}
}
}

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -11,13 +12,29 @@ namespace TweetDuck.Core.Utils{
private static readonly Lazy<Regex> RegexStripHtmlStyles = new Lazy<Regex>(() => new Regex(@"\s?(?:style|class)="".*?"""), false); private static readonly Lazy<Regex> RegexStripHtmlStyles = new Lazy<Regex>(() => new Regex(@"\s?(?:style|class)="".*?"""), false);
private static readonly Lazy<Regex> RegexOffsetClipboardHtml = new Lazy<Regex>(() => new Regex(@"(?<=EndHTML:|EndFragment:)(\d+)"), false); private static readonly Lazy<Regex> RegexOffsetClipboardHtml = new Lazy<Regex>(() => new Regex(@"(?<=EndHTML:|EndFragment:)(\d+)"), false);
public static int CurrentProcessID { get; }
public static bool ShouldAvoidToolWindow { get; } public static bool ShouldAvoidToolWindow { get; }
static WindowsUtils(){ static WindowsUtils(){
using(Process me = Process.GetCurrentProcess()){
CurrentProcessID = me.Id;
}
Version ver = Environment.OSVersion.Version; Version ver = Environment.OSVersion.Version;
ShouldAvoidToolWindow = ver.Major == 6 && ver.Minor == 2; // windows 8/10 ShouldAvoidToolWindow = ver.Major == 6 && ver.Minor == 2; // windows 8/10
} }
public static void CreateDirectoryForFile(string file){
string dir = Path.GetDirectoryName(file);
if (dir == null){
throw new ArgumentException("Invalid file path: "+file);
}
else if (dir.Length > 0){
Directory.CreateDirectory(dir);
}
}
public static bool CheckFolderWritePermission(string path){ public static bool CheckFolderWritePermission(string path){
string testFile = Path.Combine(path, ".test"); string testFile = Path.Combine(path, ".test");
@@ -32,17 +49,22 @@ namespace TweetDuck.Core.Utils{
} }
} }
public static Process StartProcess(string file, string arguments, bool runElevated){ public static bool OpenAssociatedProgram(string file, string arguments = "", bool runElevated = false){
ProcessStartInfo processInfo = new ProcessStartInfo{ try{
using(Process.Start(new ProcessStartInfo{
FileName = file, FileName = file,
Arguments = arguments Arguments = arguments,
}; Verb = runElevated ? "runas" : string.Empty,
ErrorDialog = true
if (runElevated){ })){
processInfo.Verb = "runas"; return true;
}
}catch(Win32Exception e) when (e.NativeErrorCode == 0x000004C7){ // operation canceled by the user
return false;
}catch(Exception e){
Program.Reporter.HandleException("Error opening file", e.Message, true, e);
return false;
} }
return Process.Start(processInfo);
} }
public static bool TrySleepUntil(Func<bool> test, int timeoutMillis, int timeStepMillis){ public static bool TrySleepUntil(Func<bool> test, int timeoutMillis, int timeStepMillis){
@@ -73,7 +95,7 @@ namespace TweetDuck.Core.Utils{
} }
public static void ClipboardStripHtmlStyles(){ public static void ClipboardStripHtmlStyles(){
if (!Clipboard.ContainsText(TextDataFormat.Html)){ if (!Clipboard.ContainsText(TextDataFormat.Html) || !Clipboard.ContainsText(TextDataFormat.UnicodeText)){
return; return;
} }
@@ -105,7 +127,7 @@ namespace TweetDuck.Core.Utils{
try{ try{
Clipboard.SetDataObject(obj); Clipboard.SetDataObject(obj);
}catch(ExternalException e){ }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); Program.Reporter.HandleException("Clipboard Error", "TweetDuck could not access the clipboard as it is currently used by another process.", true, e);
} }
} }
} }

View File

@@ -1,9 +1,10 @@
using System; using System;
using System.IO; using System.IO;
using System.Text; using System.Text;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Other.Settings.Export{ namespace TweetDuck.Data{
class CombinedFileStream : IDisposable{ sealed class CombinedFileStream : IDisposable{
public const char KeySeparator = '|'; public const char KeySeparator = '|';
private readonly Stream stream; private readonly Stream stream;
@@ -79,8 +80,7 @@ namespace TweetDuck.Core.Other.Settings.Export{
stream.Position += BitConverter.ToInt32(contentLength, 0); stream.Position += BitConverter.ToInt32(contentLength, 0);
string keyName = Encoding.UTF8.GetString(name); string keyName = Encoding.UTF8.GetString(name);
int separatorIndex = keyName.IndexOf(KeySeparator); return StringUtils.ExtractBefore(keyName, KeySeparator);
return separatorIndex == -1 ? keyName : keyName.Substring(0, separatorIndex);
} }
public void Flush(){ public void Flush(){
@@ -91,20 +91,19 @@ namespace TweetDuck.Core.Other.Settings.Export{
stream.Dispose(); stream.Dispose();
} }
public class Entry{ public sealed class Entry{
public string Identifier { get; } public string Identifier { get; }
public string KeyName{ public string KeyName{
get{ get{
int index = Identifier.IndexOf(KeySeparator); return StringUtils.ExtractBefore(Identifier, KeySeparator);
return index == -1 ? Identifier : Identifier.Substring(0, index);
} }
} }
public string[] KeyValue{ public string[] KeyValue{
get{ get{
int index = Identifier.IndexOf(KeySeparator); int index = Identifier.IndexOf(KeySeparator);
return index == -1 ? new string[0] : Identifier.Substring(index+1).Split(KeySeparator); return index == -1 ? StringUtils.EmptyArray : Identifier.Substring(index+1).Split(KeySeparator);
} }
} }
@@ -121,11 +120,7 @@ namespace TweetDuck.Core.Other.Settings.Export{
public void WriteToFile(string path, bool createDirectory){ public void WriteToFile(string path, bool createDirectory){
if (createDirectory){ if (createDirectory){
string dir = Path.GetDirectoryName(path); WindowsUtils.CreateDirectoryForFile(path);
if (!string.IsNullOrEmpty(dir)){
Directory.CreateDirectory(dir);
}
} }
File.WriteAllBytes(path, contents); File.WriteAllBytes(path, contents);

View File

@@ -1,8 +1,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
namespace TweetDuck.Core.Utils{ namespace TweetDuck.Data{
class CommandLineArgs{ sealed class CommandLineArgs{
public static CommandLineArgs FromStringArray(char entryChar, string[] array){ public static CommandLineArgs FromStringArray(char entryChar, string[] array){
CommandLineArgs args = new CommandLineArgs(); CommandLineArgs args = new CommandLineArgs();
ReadStringArray(entryChar, array, args); ReadStringArray(entryChar, array, args);
@@ -32,37 +33,67 @@ namespace TweetDuck.Core.Utils{
} }
} }
public static CommandLineArgs ReadCefArguments(string argumentString){
CommandLineArgs args = new CommandLineArgs();
if (string.IsNullOrWhiteSpace(argumentString)){
return args;
}
foreach(Match match in Regex.Matches(argumentString, @"([^=\s]+(?:=(?:\S*""[^""]*?""\S*|\S*))?)")){
string matchValue = match.Value;
int indexEquals = matchValue.IndexOf('=');
string key, value;
if (indexEquals == -1){
key = matchValue.TrimStart('-');
value = "1";
}
else{
key = matchValue.Substring(0, indexEquals).TrimStart('-');
value = matchValue.Substring(indexEquals+1).Trim('"');
}
if (key.Length != 0){
args.SetValue(key, value);
}
}
return args;
}
private readonly HashSet<string> flags = new HashSet<string>(); private readonly HashSet<string> flags = new HashSet<string>();
private readonly Dictionary<string, string> values = new Dictionary<string, string>(); private readonly Dictionary<string, string> values = new Dictionary<string, string>();
public int Count => flags.Count+values.Count; public int Count => flags.Count+values.Count;
public void AddFlag(string flag){ public void AddFlag(string flag){
flags.Add(flag.ToLowerInvariant()); flags.Add(flag.ToLower());
} }
public bool HasFlag(string flag){ public bool HasFlag(string flag){
return flags.Contains(flag.ToLowerInvariant()); return flags.Contains(flag.ToLower());
} }
public void RemoveFlag(string flag){ public void RemoveFlag(string flag){
flags.Remove(flag.ToLowerInvariant()); flags.Remove(flag.ToLower());
} }
public void SetValue(string key, string value){ public void SetValue(string key, string value){
values[key.ToLowerInvariant()] = value; values[key.ToLower()] = value;
} }
public bool HasValue(string key){ public bool HasValue(string key){
return values.ContainsKey(key.ToLowerInvariant()); return values.ContainsKey(key.ToLower());
} }
public string GetValue(string key, string defaultValue){ public string GetValue(string key, string defaultValue){
return values.TryGetValue(key.ToLowerInvariant(), out string val) ? val : defaultValue; return values.TryGetValue(key.ToLower(), out string val) ? val : defaultValue;
} }
public void RemoveValue(string key){ public void RemoveValue(string key){
values.Remove(key.ToLowerInvariant()); values.Remove(key.ToLower());
} }
public CommandLineArgs Clone(){ public CommandLineArgs Clone(){

View File

@@ -1,7 +1,7 @@
using System; using System;
namespace TweetDuck.Core.Utils{ namespace TweetDuck.Data{
class InjectedHTML{ sealed class InjectedHTML{
public enum Position{ public enum Position{
Before, After Before, After
} }

View File

@@ -0,0 +1,168 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using TweetDuck.Core.Utils;
namespace TweetDuck.Data.Serialization{
sealed class FileSerializer<T>{
private const string NewLineReal = "\r\n";
private const string NewLineCustom = "\r~\n";
private static readonly ITypeConverter BasicSerializerObj = new BasicTypeConverter();
public delegate void HandleUnknownPropertiesHandler(T obj, Dictionary<string, string> data);
public HandleUnknownPropertiesHandler HandleUnknownProperties { get; set; }
private readonly Dictionary<string, PropertyInfo> props;
private readonly Dictionary<Type, ITypeConverter> converters;
public FileSerializer(){
this.props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(prop => prop.CanWrite).ToDictionary(prop => prop.Name);
this.converters = new Dictionary<Type, ITypeConverter>();
}
public void RegisterTypeConverter(Type type, ITypeConverter converter){
converters[type] = converter;
}
public void Write(string file, T obj){
WindowsUtils.CreateDirectoryForFile(file);
using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
foreach(KeyValuePair<string, PropertyInfo> prop in props){
Type type = prop.Value.PropertyType;
object value = prop.Value.GetValue(obj);
if (!converters.TryGetValue(type, out ITypeConverter serializer)) {
serializer = BasicSerializerObj;
}
if (serializer.TryWriteType(type, value, out string converted)){
if (converted != null){
writer.Write($"{prop.Key} {converted.Replace(Environment.NewLine, NewLineCustom)}");
writer.Write(NewLineReal);
}
}
else{
throw new SerializationException($"Invalid serialization type, conversion failed for: {type}");
}
}
}
}
public void Read(string file, T obj){
Dictionary<string, string> unknownProperties = new Dictionary<string, string>(4);
using(StreamReader reader = new StreamReader(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))){
switch(reader.Peek()){
case -1:
throw new FormatException("File is empty.");
case 0:
case 1:
throw new FormatException("Input appears to be a binary file.");
}
foreach(string line in reader.ReadToEnd().Split(new string[]{ NewLineReal }, StringSplitOptions.RemoveEmptyEntries)){
int space = line.IndexOf(' ');
if (space == -1){
throw new SerializationException($"Invalid file format, missing separator: {line}");
}
string property = line.Substring(0, space);
string value = line.Substring(space+1).Replace(NewLineCustom, Environment.NewLine);
if (props.TryGetValue(property, out PropertyInfo info)){
if (!converters.TryGetValue(info.PropertyType, out ITypeConverter serializer)) {
serializer = BasicSerializerObj;
}
if (serializer.TryReadType(info.PropertyType, value, out object converted)){
info.SetValue(obj, converted);
}
else{
throw new SerializationException($"Invalid file format, cannot convert value: {value} (property: {property})");
}
}
else{
unknownProperties[property] = value;
}
}
}
if (unknownProperties.Count > 0){
HandleUnknownProperties?.Invoke(obj, unknownProperties);
if (unknownProperties.Count > 0){
throw new SerializationException($"Invalid file format, unknown properties: {string.Join(", ", unknownProperties.Keys)}");
}
}
}
public void ReadIfExists(string file, T obj){
try{
Read(file, obj);
}catch(FileNotFoundException){
}catch(DirectoryNotFoundException){}
}
public static HandleUnknownPropertiesHandler IgnoreProperties(params string[] properties){
return (obj, data) => {
foreach(string property in properties){
data.Remove(property);
}
};
}
private sealed class BasicTypeConverter : ITypeConverter{
bool ITypeConverter.TryWriteType(Type type, object value, out string converted){
switch(Type.GetTypeCode(type)){
case TypeCode.Boolean:
converted = value.ToString();
return true;
case TypeCode.Int32:
converted = ((int)value).ToString(); // cast required for enums
return true;
case TypeCode.String:
converted = value?.ToString();
return true;
default:
converted = null;
return false;
}
}
bool ITypeConverter.TryReadType(Type type, string value, out object converted){
switch(Type.GetTypeCode(type)){
case TypeCode.Boolean:
if (bool.TryParse(value, out bool b)){
converted = b;
return true;
}
else goto default;
case TypeCode.Int32:
if (int.TryParse(value, out int i)){
converted = i;
return true;
}
else goto default;
case TypeCode.String:
converted = value;
return true;
default:
converted = null;
return false;
}
}
}
}
}

View File

@@ -0,0 +1,8 @@
using System;
namespace TweetDuck.Data.Serialization{
interface ITypeConverter{
bool TryWriteType(Type type, object value, out string converted);
bool TryReadType(Type type, string value, out object converted);
}
}

View File

@@ -0,0 +1,28 @@
using System;
namespace TweetDuck.Data.Serialization{
sealed class SingleTypeConverter<T> : ITypeConverter{
public Func<T, string> ConvertToString { get; set; }
public Func<string, T> ConvertToObject { get; set; }
bool ITypeConverter.TryWriteType(Type type, object value, out string converted){
try{
converted = ConvertToString((T)value);
return true;
}catch{
converted = null;
return false;
}
}
bool ITypeConverter.TryReadType(Type type, string value, out object converted){
try{
converted = ConvertToObject(value);
return true;
}catch{
converted = null;
return false;
}
}
}
}

View File

@@ -1,8 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace TweetDuck.Core.Utils{ namespace TweetDuck.Data{
class TwoKeyDictionary<K1, K2, V>{ sealed class TwoKeyDictionary<K1, K2, V>{
private readonly Dictionary<K1, Dictionary<K2, V>> dict; private readonly Dictionary<K1, Dictionary<K2, V>> dict;
private readonly int innerCapacity; private readonly int innerCapacity;

View File

@@ -1,11 +1,11 @@
using System; using System.Drawing;
using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils;
using TweetDuck.Data.Serialization;
namespace TweetDuck.Core.Utils{ namespace TweetDuck.Data{
[Serializable] sealed class WindowState{
class WindowState{
private Rectangle rect; private Rectangle rect;
private bool isMaximized; private bool isMaximized;
@@ -26,5 +26,17 @@ namespace TweetDuck.Core.Utils{
Save(form); Save(form);
} }
} }
public static readonly SingleTypeConverter<WindowState> Converter = new SingleTypeConverter<WindowState>{
ConvertToString = value => $"{(value.isMaximized ? 'M' : '_')}{value.rect.X} {value.rect.Y} {value.rect.Width} {value.rect.Height}",
ConvertToObject = value => {
int[] elements = StringUtils.ParseInts(value.Substring(1), ' ');
return new WindowState{
rect = new Rectangle(elements[0], elements[1], elements[2], elements[3]),
isMaximized = value[0] == 'M'
};
}
};
} }
} }

View File

@@ -8,7 +8,7 @@ using TweetDuck.Core.Utils;
using TweetDuck.Plugins.Enums; using TweetDuck.Plugins.Enums;
namespace TweetDuck.Plugins.Controls{ namespace TweetDuck.Plugins.Controls{
partial class PluginControl : UserControl{ sealed partial class PluginControl : UserControl{
private readonly PluginManager pluginManager; private readonly PluginManager pluginManager;
private readonly Plugin plugin; private readonly Plugin plugin;
@@ -25,7 +25,7 @@ namespace TweetDuck.Plugins.Controls{
this.dpiScale = this.GetDPIScale(); this.dpiScale = this.GetDPIScale();
this.labelName.Text = plugin.Name; this.labelName.Text = plugin.Name;
this.labelDescription.Text = plugin.CanRun ? plugin.Description : "This plugin requires "+Program.BrandName+" "+plugin.RequiredVersion+" or newer."; this.labelDescription.Text = plugin.CanRun ? plugin.Description : "This plugin requires TweetDuck "+plugin.RequiredVersion+" or newer.";
this.labelVersion.Text = plugin.Version; this.labelVersion.Text = plugin.Version;
this.labelAuthor.Text = plugin.Author; this.labelAuthor.Text = plugin.Author;
this.labelWebsite.Text = plugin.Website; this.labelWebsite.Text = plugin.Website;
@@ -62,9 +62,7 @@ namespace TweetDuck.Plugins.Controls{
} }
private void btnToggleState_Click(object sender, EventArgs e){ private void btnToggleState_Click(object sender, EventArgs e){
bool newState = !pluginManager.Config.IsEnabled(plugin); pluginManager.Config.ToggleEnabled(plugin);
pluginManager.Config.SetEnabled(plugin, newState);
UpdatePluginState(); UpdatePluginState();
} }

View File

@@ -17,7 +17,20 @@ namespace TweetDuck.Plugins.Enums{
} }
} }
public static string GetScriptFile(this PluginEnvironment environment){ public static bool IncludesDisabledPlugins(this PluginEnvironment environment){
return environment == PluginEnvironment.Browser;
}
public static string GetScriptIdentifier(this PluginEnvironment environment){
switch(environment){
case PluginEnvironment.None: return "root:plugins";
case PluginEnvironment.Browser: return "root:plugins.browser";
case PluginEnvironment.Notification: return "root:plugins.notification";
default: return null;
}
}
public static string GetPluginScriptFile(this PluginEnvironment environment){
switch(environment){ switch(environment){
case PluginEnvironment.Browser: return "browser.js"; case PluginEnvironment.Browser: return "browser.js";
case PluginEnvironment.Notification: return "notification.js"; case PluginEnvironment.Notification: return "notification.js";
@@ -25,7 +38,7 @@ namespace TweetDuck.Plugins.Enums{
} }
} }
public static string GetScriptVariables(this PluginEnvironment environment){ public static string GetPluginScriptVariables(this PluginEnvironment environment){
switch(environment){ switch(environment){
case PluginEnvironment.Browser: return "$,$TD,$TDP,TD"; case PluginEnvironment.Browser: return "$,$TD,$TDP,TD";
case PluginEnvironment.Notification: return "$TD,$TDP"; case PluginEnvironment.Notification: return "$TD,$TDP";

View File

@@ -1,7 +1,7 @@
using System; using System;
namespace TweetDuck.Plugins.Events{ namespace TweetDuck.Plugins.Events{
class PluginChangedStateEventArgs : EventArgs{ sealed class PluginChangedStateEventArgs : EventArgs{
public Plugin Plugin { get; } public Plugin Plugin { get; }
public bool IsEnabled { get; } public bool IsEnabled { get; }

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