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

Compare commits

..

243 Commits

Author SHA1 Message Date
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
168 changed files with 5369 additions and 2195 deletions

View File

@@ -12,6 +12,7 @@ namespace TweetDuck.Configuration{
// 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 +30,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,9 +1,10 @@
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 System.Threading;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetLib.Communication;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration{
sealed class LockManager{ sealed class LockManager{
@@ -137,7 +138,7 @@ namespace TweetDuck.Configuration{
public bool RestoreLockingProcess(int failTimeout){ public bool RestoreLockingProcess(int failTimeout){
if (lockingProcess != null){ if (lockingProcess != null){
if (lockingProcess.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray if (lockingProcess.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, Program.WindowRestoreMessage, new UIntPtr((uint)lockingProcess.Id), IntPtr.Zero); Comms.BroadcastMessage(Program.WindowRestoreMessage, (uint)lockingProcess.Id, 0);
if (WindowsUtils.TrySleepUntil(() => CheckLockingProcessExited() || (lockingProcess.MainWindowHandle != IntPtr.Zero && lockingProcess.Responding), failTimeout, RetryDelay)){ if (WindowsUtils.TrySleepUntil(() => CheckLockingProcessExited() || (lockingProcess.MainWindowHandle != IntPtr.Zero && lockingProcess.Responding), failTimeout, RetryDelay)){
return true; return true;

View File

@@ -5,9 +5,7 @@ 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>{ private static readonly FileSerializer<SystemConfig> Serializer = new FileSerializer<SystemConfig>();
OnReadUnknownProperty = (obj, property, value) => false
};
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"));
@@ -16,6 +14,9 @@ namespace TweetDuck.Configuration{
private bool _hardwareAcceleration = true; private bool _hardwareAcceleration = true;
public bool EnableBrowserGCReload { get; set; } = true;
public int BrowserMemoryThreshold { get; set; } = 400;
// SPECIAL PROPERTIES // SPECIAL PROPERTIES
public bool HardwareAcceleration{ public bool HardwareAcceleration{
@@ -47,19 +48,8 @@ namespace TweetDuck.Configuration{
try{ try{
Serializer.Read(file, config); Serializer.Read(file, config);
return config;
}catch(FileNotFoundException){ }catch(FileNotFoundException){
}catch(DirectoryNotFoundException){ }catch(DirectoryNotFoundException){
}catch(FormatException){
try{
using(Stream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)){
config.HardwareAcceleration = stream.ReadByte() > 0;
}
config.Save();
}catch(Exception e){
Program.Reporter.HandleException("Configuration Error", "Could not update the system configuration file.", true, e);
}
}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,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using TweetDuck.Core; using TweetDuck.Core;
@@ -10,9 +11,16 @@ using TweetDuck.Data.Serialization;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration{
sealed class UserConfig{ sealed class UserConfig{
private static readonly FileSerializer<UserConfig> Serializer = new FileSerializer<UserConfig>{ private static readonly FileSerializer<UserConfig> Serializer = new FileSerializer<UserConfig>{ HandleUnknownProperties = HandleUnknownProperties };
OnReadUnknownProperty = (obj, property, value) => false
}; private static void HandleUnknownProperties(UserConfig obj, Dictionary<string, string> data){
data.Remove("EnableBrowserGCReload");
data.Remove("BrowserMemoryThreshold");
if (data.Count == 0){
obj.Save();
}
}
static UserConfig(){ static UserConfig(){
Serializer.RegisterTypeConverter(typeof(WindowState), WindowState.Converter); Serializer.RegisterTypeConverter(typeof(WindowState), WindowState.Converter);
@@ -36,18 +44,23 @@ namespace TweetDuck.Configuration{
// CONFIGURATION DATA // CONFIGURATION DATA
public bool FirstRun { get; set; } = true;
public bool AllowDataCollection { get; set; } = false;
public WindowState BrowserWindow { get; set; } = new WindowState(); public WindowState BrowserWindow { get; set; } = new WindowState();
public WindowState PluginsWindow { get; set; } = new WindowState(); public WindowState PluginsWindow { get; set; } = new WindowState();
public bool ExpandLinksOnHover { get; set; } = true; public bool ExpandLinksOnHover { get; set; } = true;
public bool SwitchAccountSelectors { get; set; } = true; public bool SwitchAccountSelectors { get; set; } = true;
public bool BestImageQuality { get; set; } = true; public bool OpenSearchInFirstColumn { get; set; } = true;
public bool EnableSpellCheck { get; set; } = false; public bool BestImageQuality { get; set; } = true;
public bool EnableSpellCheck { get; set; } = false;
public int VideoPlayerVolume { get; set; } = 50;
private int _zoomLevel = 100; private int _zoomLevel = 100;
private bool _muteNotifications; private bool _muteNotifications;
private TrayIcon.Behavior _trayBehavior = TrayIcon.Behavior.Disabled; private TrayIcon.Behavior _trayBehavior = TrayIcon.Behavior.Disabled;
public bool EnableTrayHighlight { get; set; } = true; public bool EnableTrayHighlight { get; set; } = true;
public bool EnableUpdateCheck { get; set; } = true; public bool EnableUpdateCheck { get; set; } = true;
public string DismissedUpdate { get; set; } = null; public string DismissedUpdate { get; set; } = null;
@@ -71,14 +84,12 @@ namespace TweetDuck.Configuration{
public Size CustomNotificationSize { get; set; } = Size.Empty; public Size CustomNotificationSize { get; set; } = Size.Empty;
public int NotificationScrollSpeed { get; set; } = 10; public int NotificationScrollSpeed { get; set; } = 10;
public int NotificationSoundVolume { get; set; } = 100;
private string _notificationSoundPath; private string _notificationSoundPath;
public string CustomCefArgs { get; set; } = null; public string CustomCefArgs { get; set; } = null;
public string CustomBrowserCSS { get; set; } = null; public string CustomBrowserCSS { get; set; } = null;
public string CustomNotificationCSS { get; set; } = null; public string CustomNotificationCSS { get; set; } = null;
public bool EnableBrowserGCReload { get; set; } = false;
public int BrowserMemoryThreshold { get; set; } = 350;
// SPECIAL PROPERTIES // SPECIAL PROPERTIES
@@ -98,7 +109,7 @@ namespace TweetDuck.Configuration{
set{ set{
if (_muteNotifications != value){ if (_muteNotifications != value){
_muteNotifications = value; _muteNotifications = value;
MuteToggled?.Invoke(this, new EventArgs()); MuteToggled?.Invoke(this, EventArgs.Empty);
} }
} }
} }
@@ -109,7 +120,7 @@ namespace TweetDuck.Configuration{
set{ set{
if (_zoomLevel != value){ if (_zoomLevel != value){
_zoomLevel = value; _zoomLevel = value;
ZoomLevelChanged?.Invoke(this, new EventArgs()); ZoomLevelChanged?.Invoke(this, EventArgs.Empty);
} }
} }
} }
@@ -122,7 +133,7 @@ namespace TweetDuck.Configuration{
set{ set{
if (_trayBehavior != value){ if (_trayBehavior != value){
_trayBehavior = value; _trayBehavior = value;
TrayBehaviorChanged?.Invoke(this, new EventArgs()); TrayBehaviorChanged?.Invoke(this, EventArgs.Empty);
} }
} }
} }
@@ -137,7 +148,7 @@ namespace TweetDuck.Configuration{
private readonly string file; private readonly string file;
public UserConfig(string file){ // TODO make private after removing UserConfigLegacy private UserConfig(string file){
this.file = file; this.file = file;
} }
@@ -158,6 +169,29 @@ namespace TweetDuck.Configuration{
return false; return false;
} }
} }
public bool Reload(){
try{
LoadInternal(false);
return true;
}catch(FileNotFoundException){
try{
Serializer.Write(file, new UserConfig(file));
LoadInternal(false);
return true;
}catch(Exception e){
Program.Reporter.HandleException("Configuration Error", "Could not regenerate configuration file.", true, e);
return false;
}
}catch(Exception e){
Program.Reporter.HandleException("Configuration Error", "Could not reload configuration file.", true, e);
return false;
}
}
private void LoadInternal(bool backup){
Serializer.Read(backup ? GetBackupFile(file) : file, this);
}
public static UserConfig Load(string file){ public static UserConfig Load(string file){
Exception firstException = null; Exception firstException = null;
@@ -165,22 +199,23 @@ namespace TweetDuck.Configuration{
for(int attempt = 0; attempt < 2; attempt++){ for(int attempt = 0; attempt < 2; attempt++){
try{ try{
UserConfig config = new UserConfig(file); UserConfig config = new UserConfig(file);
Serializer.Read(attempt == 0 ? file : GetBackupFile(file), config); config.LoadInternal(attempt > 0);
return config; return config;
}catch(FileNotFoundException){ }catch(FileNotFoundException){
}catch(DirectoryNotFoundException){ }catch(DirectoryNotFoundException){
break; break;
}catch(FormatException){
UserConfig config = UserConfigLegacy.Load(file);
config.Save();
return config;
}catch(Exception e){ }catch(Exception e){
if (attempt == 0){ if (attempt == 0){
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);
} }
} }
} }

View File

@@ -1,210 +0,0 @@
using System;
using System.Drawing;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using TweetDuck.Core;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification;
using TweetDuck.Data;
namespace TweetDuck.Configuration{
[Serializable]
sealed class UserConfigLegacy{ // TODO remove eventually
private static readonly IFormatter Formatter = new BinaryFormatter{ Binder = new LegacyBinder() };
private class LegacyBinder : SerializationBinder{
public override Type BindToType(string assemblyName, string typeName){
return Type.GetType(string.Format("{0}, {1}", typeName.Replace("TweetDck", "TweetDuck").Replace(".UserConfig", ".UserConfigLegacy").Replace("Core.Utils.WindowState", "Data.WindowState"), assemblyName.Replace("TweetDck", "TweetDuck")));
}
}
private const int CurrentFileVersion = 11;
// START OF CONFIGURATION
public WindowState BrowserWindow { get; set; }
public WindowState PluginsWindow { get; set; }
public bool DisplayNotificationColumn { get; set; }
public bool DisplayNotificationTimer { get; set; }
public bool NotificationTimerCountDown { get; set; }
public bool NotificationSkipOnLinkClick { get; set; }
public bool NotificationNonIntrusiveMode { get; set; }
public int NotificationIdlePauseSeconds { get; set; }
public int NotificationDurationValue { get; set; }
public int NotificationScrollSpeed { get; set; }
public TweetNotification.Position NotificationPosition { get; set; }
public Point CustomNotificationPosition { get; set; }
public int NotificationEdgeDistance { get; set; }
public int NotificationDisplay { get; set; }
public TweetNotification.Size NotificationSize { get; set; }
public Size CustomNotificationSize { get; set; }
public bool EnableSpellCheck { get; set; }
public bool ExpandLinksOnHover { get; set; }
public bool SwitchAccountSelectors { get; set; }
public bool EnableTrayHighlight { get; set; }
public bool EnableUpdateCheck { get; set; }
public string DismissedUpdate { get; set; }
public string CustomCefArgs { get; set; }
public string CustomBrowserCSS { get; set; }
public string CustomNotificationCSS { get; set; }
public string NotificationSoundPath{
get => string.IsNullOrEmpty(notificationSoundPath) ? string.Empty : notificationSoundPath;
set => notificationSoundPath = value;
}
public bool MuteNotifications{
get => muteNotifications;
set => muteNotifications = value;
}
public int ZoomLevel{
get => zoomLevel;
set => zoomLevel = value;
}
public TrayIcon.Behavior TrayBehavior{
get => trayBehavior;
set => trayBehavior = value;
}
// END OF CONFIGURATION
[NonSerialized]
private string file;
private int fileVersion;
private bool muteNotifications;
private int zoomLevel;
private string notificationSoundPath;
private TrayIcon.Behavior trayBehavior;
private UserConfigLegacy(string 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(){
if (fileVersion == CurrentFileVersion){
return;
}
// if outdated, cycle through all versions
if (fileVersion <= 5){
DisplayNotificationTimer = true;
EnableUpdateCheck = true;
ExpandLinksOnHover = true;
BrowserWindow = new WindowState();
PluginsWindow = new WindowState();
EnableTrayHighlight = true;
NotificationDurationValue = 25;
fileVersion = 6;
}
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;
}
public UserConfig ConvertLegacy(){
return new UserConfig(file){
BrowserWindow = BrowserWindow,
PluginsWindow = PluginsWindow,
DisplayNotificationColumn = DisplayNotificationColumn,
DisplayNotificationTimer = DisplayNotificationTimer,
NotificationTimerCountDown = NotificationTimerCountDown,
NotificationSkipOnLinkClick = NotificationSkipOnLinkClick,
NotificationNonIntrusiveMode = NotificationNonIntrusiveMode,
NotificationIdlePauseSeconds = NotificationIdlePauseSeconds,
NotificationDurationValue = NotificationDurationValue,
NotificationScrollSpeed = NotificationScrollSpeed,
NotificationPosition = NotificationPosition,
CustomNotificationPosition = CustomNotificationPosition,
NotificationEdgeDistance = NotificationEdgeDistance,
NotificationDisplay = NotificationDisplay,
NotificationSize = NotificationSize,
CustomNotificationSize = CustomNotificationSize,
EnableSpellCheck = EnableSpellCheck,
ExpandLinksOnHover = ExpandLinksOnHover,
SwitchAccountSelectors = SwitchAccountSelectors,
EnableTrayHighlight = EnableTrayHighlight,
EnableUpdateCheck = EnableUpdateCheck,
DismissedUpdate = DismissedUpdate,
CustomCefArgs = CustomCefArgs,
CustomBrowserCSS = CustomBrowserCSS,
CustomNotificationCSS = CustomNotificationCSS,
NotificationSoundPath = NotificationSoundPath,
MuteNotifications = MuteNotifications,
ZoomLevel = ZoomLevel,
TrayBehavior = TrayBehavior
};
}
public static UserConfig Load(string file){
UserConfigLegacy config = null;
try{
using(Stream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)){
if ((config = Formatter.Deserialize(stream) as UserConfigLegacy) != null){
config.file = file;
}
}
config?.UpgradeFile();
}catch(FileNotFoundException){
}catch(DirectoryNotFoundException){
}catch(Exception e){
Program.Reporter.HandleException("Configuration Error", "Could not open the configuration file.", true, e);
}
return (config ?? new UserConfigLegacy(file)).ConvertLegacy();
}
}
}

View File

@@ -17,6 +17,7 @@ namespace TweetDuck.Core.Bridge{
if (environment == Environment.Browser){ if (environment == Environment.Browser){
build.Append("x.switchAccountSelectors=").Append(Bool(Program.UserConfig.SwitchAccountSelectors)); build.Append("x.switchAccountSelectors=").Append(Bool(Program.UserConfig.SwitchAccountSelectors));
build.Append("x.openSearchInFirstColumn=").Append(Bool(Program.UserConfig.OpenSearchInFirstColumn));
build.Append("x.muteNotifications=").Append(Bool(Program.UserConfig.MuteNotifications)); build.Append("x.muteNotifications=").Append(Bool(Program.UserConfig.MuteNotifications));
build.Append("x.hasCustomNotificationSound=").Append(Bool(Program.UserConfig.NotificationSoundPath.Length > 0)); build.Append("x.hasCustomNotificationSound=").Append(Bool(Program.UserConfig.NotificationSoundPath.Length > 0));
build.Append("x.notificationMediaPreviews=").Append(Bool(Program.UserConfig.NotificationMediaPreviews)); build.Append("x.notificationMediaPreviews=").Append(Bool(Program.UserConfig.NotificationMediaPreviews));

View File

@@ -1,20 +1,45 @@
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.Other;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Resources;
namespace TweetDuck.Core.Bridge{ namespace TweetDuck.Core.Bridge{
sealed class TweetDeckBridge{ sealed class TweetDeckBridge{
public static string LastRightClickedLink = string.Empty; public static string FontSize { get; private set; }
public static string LastRightClickedImage = string.Empty; public static string NotificationHeadLayout { get; private set; }
public static string LastHighlightedTweet = string.Empty;
public static string LastHighlightedQuotedTweet = string.Empty; public static string LastHighlightedTweetUrl = string.Empty;
public static string[] LastHighlightedTweetImages = StringUtils.EmptyArray; 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 = LastRightClickedImage = LastHighlightedTweet = LastHighlightedQuotedTweet = string.Empty; FontSize = NotificationHeadLayout = null;
LastHighlightedTweetImages = StringUtils.EmptyArray; 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;
@@ -25,31 +50,29 @@ namespace TweetDuck.Core.Bridge{
this.notification = notification; this.notification = notification;
} }
public void LoadFontSizeClass(string fsClass){ public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){
form.InvokeAsyncSafe(() => { form.InvokeAsyncSafe(() => {
TweetNotification.SetFontSizeClass(fsClass); form.OnIntroductionClosed(showGuide, allowDataCollection);
}); });
} }
public void LoadNotificationHeadContents(string headContents){ public void LoadNotificationLayout(string fontSize, string headLayout){
form.InvokeAsyncSafe(() => { form.InvokeAsyncSafe(() => {
TweetNotification.SetHeadTag(headContents); FontSize = fontSize;
NotificationHeadLayout = headLayout;
}); });
} }
public void SetLastRightClickedLink(string link){ public void SetLastRightClickInfo(string type, string link){
form.InvokeAsyncSafe(() => LastRightClickedLink = link); form.InvokeAsyncSafe(() => ContextMenuBase.SetContextInfo(type, link));
} }
public void SetLastRightClickedImage(string link){ public void SetLastHighlightedTweet(string tweetUrl, string quoteUrl, string authors, string imageList){
form.InvokeAsyncSafe(() => LastRightClickedImage = link);
}
public void SetLastHighlightedTweet(string link, string quotedLink, string imageList){
form.InvokeAsyncSafe(() => { form.InvokeAsyncSafe(() => {
LastHighlightedTweet = link; LastHighlightedTweetUrl = tweetUrl;
LastHighlightedQuotedTweet = quotedLink; LastHighlightedQuoteUrl = quoteUrl;
LastHighlightedTweetImages = imageList.Split(';'); LastHighlightedTweetAuthors = authors;
LastHighlightedTweetImages = imageList;
}); });
} }
@@ -57,10 +80,10 @@ namespace TweetDuck.Core.Bridge{
form.InvokeAsyncSafe(form.OpenContextMenu); form.InvokeAsyncSafe(form.OpenContextMenu);
} }
public void OnTweetPopup(string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl){ public void OnTweetPopup(string columnId, string chirpId, string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl){
notification.InvokeAsyncSafe(() => { notification.InvokeAsyncSafe(() => {
form.OnTweetNotification(); form.OnTweetNotification();
notification.ShowNotification(new TweetNotification(columnName, tweetHtml, tweetCharacters, tweetUrl, quoteUrl)); notification.ShowNotification(new TweetNotification(columnId, chirpId, columnName, tweetHtml, tweetCharacters, tweetUrl, quoteUrl));
}); });
} }
@@ -80,6 +103,12 @@ namespace TweetDuck.Core.Bridge{
} }
} }
public void SetSessionData(string key, string value){
form.InvokeSafe(() => { // do not use InvokeAsyncSafe, return only after invocation
SessionData.Add(key, value);
});
}
public void LoadNextNotification(){ public void LoadNextNotification(){
notification.InvokeAsyncSafe(notification.FinishCurrentNotification); notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
} }
@@ -88,16 +117,20 @@ namespace TweetDuck.Core.Bridge{
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width, height)); form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width, height));
} }
public void PlayVideo(string url){
form.InvokeAsyncSafe(() => form.PlayVideo(url));
}
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){

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{
@@ -67,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, new UIntPtr(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

@@ -23,7 +23,7 @@ namespace TweetDuck.Core.Controls{
Rectangle rect = e.ClipRectangle; Rectangle rect = e.ClipRectangle;
rect.Width = (int)(rect.Width*((double)Value/Maximum)); rect.Width = (int)(rect.Width*((double)Value/Maximum));
e.Graphics.FillRectangle(brush,rect); e.Graphics.FillRectangle(brush, rect);
} }
protected override void Dispose(bool disposing){ protected override void Dispose(bool disposing){

View File

@@ -39,10 +39,10 @@
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.TwitterUtils.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);

View File

@@ -2,7 +2,6 @@
using CefSharp.WinForms; using CefSharp.WinForms;
using System; using System;
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,6 +11,7 @@ using TweetDuck.Core.Handling.General;
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.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;
@@ -19,7 +19,6 @@ using TweetDuck.Plugins.Enums;
using TweetDuck.Plugins.Events; using TweetDuck.Plugins.Events;
using TweetDuck.Resources; using TweetDuck.Resources;
using TweetDuck.Updates; using TweetDuck.Updates;
using TweetDuck.Updates.Events;
using TweetLib.Audio; using TweetLib.Audio;
namespace TweetDuck.Core{ namespace TweetDuck.Core{
@@ -58,15 +57,18 @@ namespace TweetDuck.Core{
private TweetScreenshotManager notificationScreenshotManager; private TweetScreenshotManager notificationScreenshotManager;
private SoundNotification soundNotification; private SoundNotification soundNotification;
private VideoPlayer videoPlayer;
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.Executed += plugins_Executed;
this.plugins.PluginChangedState += plugins_PluginChangedState; this.plugins.PluginChangedState += plugins_PluginChangedState;
this.plugins.Reload();
this.contextMenu = ContextMenuBrowser.CreateMenu(this); this.contextMenu = ContextMenuBrowser.CreateMenu(this);
this.memoryUsageTracker = new MemoryUsageTracker("TDGF_tryRunCleanup"); this.memoryUsageTracker = new MemoryUsageTracker("TDGF_tryRunCleanup");
@@ -82,8 +84,11 @@ namespace TweetDuck.Core{
this.notification.Show(); this.notification.Show();
this.browser = new ChromiumWebBrowser("https://tweetdeck.twitter.com/"){ this.browser = new ChromiumWebBrowser("https://tweetdeck.twitter.com/"){
DialogHandler = new FileDialogHandler(),
DragHandler = new DragHandlerBrowser(),
MenuHandler = new ContextMenuBrowser(this), MenuHandler = new ContextMenuBrowser(this),
JsDialogHandler = new JavaScriptDialogHandler(), JsDialogHandler = new JavaScriptDialogHandler(),
KeyboardHandler = new KeyboardHandlerBrowser(this),
LifeSpanHandler = new LifeSpanHandler(), LifeSpanHandler = new LifeSpanHandler(),
RequestHandler = new RequestHandlerBrowser() RequestHandler = new RequestHandlerBrowser()
}; };
@@ -114,6 +119,7 @@ namespace TweetDuck.Core{
notificationScreenshotManager?.Dispose(); notificationScreenshotManager?.Dispose();
soundNotification?.Dispose(); soundNotification?.Dispose();
videoPlayer?.Dispose();
}; };
this.trayIcon.ClickRestore += trayIcon_ClickRestore; this.trayIcon.ClickRestore += trayIcon_ClickRestore;
@@ -132,16 +138,6 @@ namespace TweetDuck.Core{
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);
@@ -204,19 +200,20 @@ namespace TweetDuck.Core{
e.Frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorFix); e.Frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorFix);
UpdateProperties(PropertyBridge.Environment.Browser); UpdateProperties(PropertyBridge.Environment.Browser);
TweetDeckBridge.RestoreSessionData(e.Frame);
ScriptLoader.ExecuteFile(e.Frame, "code.js"); ScriptLoader.ExecuteFile(e.Frame, "code.js");
InjectBrowserCSS();
ReinjectCustomCSS(Config.CustomBrowserCSS); ReinjectCustomCSS(Config.CustomBrowserCSS);
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Browser);
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(); TweetDeckBridge.ResetStaticProperties();
if (Config.EnableBrowserGCReload){ if (Program.SystemConfig.EnableBrowserGCReload){
memoryUsageTracker.Start(this, e.Browser, Config.BrowserMemoryThreshold); memoryUsageTracker.Start(this, e.Browser, Program.SystemConfig.BrowserMemoryThreshold);
}
if (Config.FirstRun){
ScriptLoader.ExecuteFile(e.Frame, "introduction.js");
} }
} }
} }
@@ -327,20 +324,28 @@ namespace TweetDuck.Core{
} }
private void plugins_Reloaded(object sender, PluginErrorEventArgs e){ private void plugins_Reloaded(object sender, PluginErrorEventArgs e){
browser.GetBrowser().Reload(); 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 plugins_PluginChangedState(object sender, PluginChangedStateEventArgs e){ private void plugins_PluginChangedState(object sender, PluginChangedStateEventArgs e){
browser.ExecuteScriptAsync("window.TDPF_setPluginState", e.Plugin, e.IsEnabled); browser.ExecuteScriptAsync("window.TDPF_setPluginState", e.Plugin, e.IsEnabled);
} }
private void updates_UpdateAccepted(object sender, UpdateAcceptedEventArgs e){ private void updates_UpdateAccepted(object sender, UpdateEventArgs e){
this.InvokeAsyncSafe(() => { this.InvokeAsyncSafe(() => {
foreach(Form form in Application.OpenForms.Cast<Form>().Reverse()){ FormManager.CloseAllDialogs();
if (form is FormSettings || form is FormPlugins || form is FormAbout){
form.Close();
}
}
updates.BeginUpdateDownload(this, e.UpdateInfo, update => { updates.BeginUpdateDownload(this, e.UpdateInfo, update => {
if (update.DownloadStatus == UpdateDownloadStatus.Done){ if (update.DownloadStatus == UpdateDownloadStatus.Done){
@@ -352,9 +357,9 @@ 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();
}); });
} }
@@ -379,7 +384,7 @@ namespace TweetDuck.Core{
if (isLoaded){ if (isLoaded){
if (m.Msg == Program.WindowRestoreMessage){ if (m.Msg == Program.WindowRestoreMessage){
if (WindowsUtils.CurrentProcessID == m.WParam.ToInt32()){ if (WindowsUtils.CurrentProcessID == m.WParam.ToInt32()){
trayIcon_ClickRestore(trayIcon, new EventArgs()); trayIcon_ClickRestore(trayIcon, EventArgs.Empty);
} }
return; return;
@@ -396,7 +401,13 @@ namespace TweetDuck.Core{
} }
if (isBrowserReady && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN){ if (isBrowserReady && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN){
browser.ExecuteScriptAsync("TDGF_onMouseClickExtra", (m.WParam.ToInt32() >> 16) & 0xFFFF); if (videoPlayer != null && videoPlayer.Running){
videoPlayer.Close();
}
else{
browser.ExecuteScriptAsync("TDGF_onMouseClickExtra", (m.WParam.ToInt32() >> 16) & 0xFFFF);
}
return; return;
} }
@@ -419,6 +430,10 @@ namespace TweetDuck.Core{
// javascript calls // javascript calls
public void InjectBrowserCSS(){
browser.ExecuteScriptAsync("TDGF_injectBrowserCSS", ScriptLoader.LoadResource("styles/browser.css").TrimEnd());
}
public void ReinjectCustomCSS(string css){ public void ReinjectCustomCSS(string css){
browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty); browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty);
} }
@@ -428,10 +443,22 @@ namespace TweetDuck.Core{
} }
public void ReloadToTweetDeck(){ public void ReloadToTweetDeck(){
browser.ExecuteScriptAsync($"gc&&gc();window.location.href='{TwitterUtils.TweetDeckURL}'"); browser.ExecuteScriptAsync($"if(window.TDGF_reload)window.TDGF_reload();else window.location.href='{TwitterUtils.TweetDeckURL}'");
} }
// callback handlers // callback handlers
public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){
if (Config.FirstRun){
Config.FirstRun = false;
Config.AllowDataCollection = allowDataCollection;
Config.Save();
}
if (showGuide){
ShowChildForm(new FormGuide());
}
}
public void OpenContextMenu(){ public void OpenContextMenu(){
contextMenu.Show(this, PointToClient(Cursor.Position)); contextMenu.Show(this, PointToClient(Cursor.Position));
@@ -442,29 +469,37 @@ 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, 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;
} }
if (Config.EnableBrowserGCReload){ if (Program.SystemConfig.EnableBrowserGCReload){
memoryUsageTracker.Start(this, browser.GetBrowser(), Config.BrowserMemoryThreshold); memoryUsageTracker.Start(this, browser.GetBrowser(), Program.SystemConfig.BrowserMemoryThreshold);
} }
else{ else{
memoryUsageTracker.Stop(); memoryUsageTracker.Stop();
} }
UpdateProperties(PropertyBridge.Environment.Browser); if (form.ShouldReloadBrowser){
FormManager.TryFind<FormPlugins>()?.Close();
plugins.Reload(); // also reloads the browser
}
else{
UpdateProperties(PropertyBridge.Environment.Browser);
}
notification.RequiresResize = true; notification.RequiresResize = true;
form.Dispose(); form.Dispose();
@@ -475,13 +510,13 @@ namespace TweetDuck.Core{
} }
public void OpenAbout(){ public void OpenAbout(){
if (!TryBringToFront<FormAbout>()){ if (!FormManager.TryBringToFront<FormAbout>()){
ShowChildForm(new FormAbout()); ShowChildForm(new FormAbout());
} }
} }
public void OpenPlugins(){ public void OpenPlugins(){
if (!TryBringToFront<FormPlugins>()){ if (!FormManager.TryBringToFront<FormPlugins>()){
ShowChildForm(new FormPlugins(plugins)); ShowChildForm(new FormPlugins(plugins));
} }
} }
@@ -502,9 +537,55 @@ namespace TweetDuck.Core{
soundNotification.PlaybackError += soundNotification_PlaybackError; soundNotification.PlaybackError += soundNotification_PlaybackError;
} }
soundNotification.SetVolume(Config.NotificationSoundVolume);
soundNotification.Play(Config.NotificationSoundPath); soundNotification.Play(Config.NotificationSoundPath);
} }
public void PlayVideo(string url){
if (string.IsNullOrEmpty(url)){
videoPlayer?.Close();
return;
}
if (videoPlayer == null){
videoPlayer = new VideoPlayer(this);
videoPlayer.ProcessExited += (sender, args) => {
browser.GetBrowser().GetHost().SendFocusEvent(true);
HideVideoOverlay();
};
}
videoPlayer.Launch(url);
}
public void HideVideoOverlay(){
browser.ExecuteScriptAsync("$('#td-video-player-overlay').remove()");
}
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();
using(IFrame frame = browser.GetBrowser().MainFrame){
if (!TwitterUtils.IsTweetDeckWebsite(frame)){
FormMessage.Error("View Tweet Detail", "TweetDeck is not currently loaded.", FormMessage.OK);
return;
}
}
notification.FinishCurrentNotification();
browser.ExecuteScriptAsync("window.TDGF_showTweetDetail", columnId, chirpId, fallbackUrl);
}
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, plugins); notificationScreenshotManager = new TweetScreenshotManager(this, plugins);

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

@@ -6,67 +6,85 @@ 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;
namespace TweetDuck.Core.Handling{ namespace TweetDuck.Core.Handling{
abstract class ContextMenuBase : IContextMenuHandler{ abstract class ContextMenuBase : IContextMenuHandler{
protected 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 static TwitterUtils.ImageQuality ImageQuality => Program.UserConfig.TwitterImageQuality; private static TwitterUtils.ImageQuality ImageQuality => Program.UserConfig.TwitterImageQuality;
private static string GetLink(IContextMenuParams parameters){
return string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedLink) ? parameters.UnfilteredLinkUrl : TweetDeckBridge.LastRightClickedLink;
}
private static string GetImage(IContextMenuParams parameters){
return string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedImage) ? parameters.SourceUrl : TweetDeckBridge.LastRightClickedImage;
}
private const int MenuOpenLinkUrl = 26500;
private const int MenuCopyLinkUrl = 26501;
private const int MenuCopyUsername = 26502;
private const int MenuOpenImageUrl = 26503;
private const int MenuCopyImageUrl = 26504;
private const int MenuSaveImage = 26505;
private const int MenuSaveAllImages = 26506;
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";
public static void SetContextInfo(string type, string link){
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 MenuOpenMediaUrl = (CefMenuCommand)26503;
private const CefMenuCommand MenuCopyMediaUrl = (CefMenuCommand)26504;
private const CefMenuCommand MenuSaveMedia = (CefMenuCommand)26505;
private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand)26506;
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand)26599;
private string[] lastHighlightedTweetAuthors;
private string[] lastHighlightedTweetImageList; private string[] lastHighlightedTweetImageList;
protected ContextMenuBase(Form form){
this.form = form;
}
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){
bool hasTweetImage = !string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedImage);
lastHighlightedTweetImageList = TweetDeckBridge.LastHighlightedTweetImages;
if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){ if (!TwitterUtils.IsTweetDeckWebsite(frame) || browser.IsLoading){
lastHighlightedTweetAuthors = StringUtils.EmptyArray;
lastHighlightedTweetImageList = StringUtils.EmptyArray; lastHighlightedTweetImageList = StringUtils.EmptyArray;
ContextInfo = default(KeyValuePair<string, string>);
}
else{
lastHighlightedTweetAuthors = TweetDeckBridge.LastHighlightedTweetAuthorsArray;
lastHighlightedTweetImageList = TweetDeckBridge.LastHighlightedTweetImagesArray;
} }
if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && !parameters.UnfilteredLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal) && !hasTweetImage){ 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)){ if (TwitterUtils.RegexAccount.IsMatch(parameters.UnfilteredLinkUrl)){
model.AddItem((CefMenuCommand)MenuOpenLinkUrl, "Open account in browser"); model.AddItem(MenuOpenLinkUrl, TextOpen("account"));
model.AddItem((CefMenuCommand)MenuCopyLinkUrl, "Copy account address"); model.AddItem(MenuCopyLinkUrl, TextCopy("account"));
model.AddItem((CefMenuCommand)MenuCopyUsername, "Copy account username"); model.AddItem(MenuCopyUsername, "Copy account username");
} }
else{ else{
model.AddItem((CefMenuCommand)MenuOpenLinkUrl, "Open link in browser"); model.AddItem(MenuOpenLinkUrl, TextOpen("link"));
model.AddItem((CefMenuCommand)MenuCopyLinkUrl, "Copy link address"); model.AddItem(MenuCopyLinkUrl, TextCopy("link"));
} }
model.AddSeparator(); model.AddSeparator();
} }
if ((parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) || hasTweetImage){ if (hasTweetVideo){
model.AddItem((CefMenuCommand)MenuOpenImageUrl, "Open image in browser"); model.AddItem(MenuOpenMediaUrl, TextOpen("video"));
model.AddItem((CefMenuCommand)MenuCopyImageUrl, "Copy image address"); model.AddItem(MenuCopyMediaUrl, TextCopy("video"));
model.AddItem((CefMenuCommand)MenuSaveImage, "Save image as..."); model.AddItem(MenuSaveMedia, TextSave("video"));
model.AddSeparator();
}
else if ((parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) || hasTweetImage){
model.AddItem(MenuOpenMediaUrl, TextOpen("image"));
model.AddItem(MenuCopyMediaUrl, TextCopy("image"));
model.AddItem(MenuSaveMedia, TextSave("image"));
if (lastHighlightedTweetImageList.Length > 1){ if (lastHighlightedTweetImageList.Length > 1){
model.AddItem((CefMenuCommand)MenuSaveAllImages, "Save all images as..."); model.AddItem(MenuSaveTweetImages, TextSave("all images"));
} }
model.AddSeparator(); model.AddSeparator();
@@ -74,34 +92,40 @@ namespace TweetDuck.Core.Handling{
} }
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(GetLink(parameters)); SetClipboardText(browserControl.AsControl(), IsLink ? ContextInfo.Value : parameters.UnfilteredLinkUrl);
break;
case MenuOpenImageUrl:
BrowserUtils.OpenExternalBrowser(TwitterUtils.GetImageLink(GetImage(parameters), ImageQuality));
break;
case MenuSaveImage:
TwitterUtils.DownloadImage(GetImage(parameters), ImageQuality);
break;
case MenuSaveAllImages:
TwitterUtils.DownloadImages(lastHighlightedTweetImageList, ImageQuality);
break;
case MenuCopyImageUrl:
SetClipboardText(TwitterUtils.GetImageLink(GetImage(parameters), ImageQuality));
break; break;
case MenuCopyUsername: case MenuCopyUsername:
Match match = TwitterUtils.RegexAccount.Match(parameters.UnfilteredLinkUrl); Match match = TwitterUtils.RegexAccount.Match(parameters.UnfilteredLinkUrl);
SetClipboardText(match.Success ? match.Groups[1].Value : parameters.UnfilteredLinkUrl); SetClipboardText(browserControl.AsControl(), match.Success ? match.Groups[1].Value : parameters.UnfilteredLinkUrl);
break;
case MenuOpenMediaUrl:
OpenBrowser(browserControl.AsControl(), TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality));
break;
case MenuCopyMediaUrl:
SetClipboardText(browserControl.AsControl(), TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality));
break;
case MenuSaveMedia:
if (IsVideo){
TwitterUtils.DownloadVideo(GetMediaLink(parameters));
}
else{
TwitterUtils.DownloadImage(GetMediaLink(parameters), lastHighlightedTweetAuthors.LastOrDefault(), ImageQuality);
}
break;
case MenuSaveTweetImages:
TwitterUtils.DownloadImages(lastHighlightedTweetImageList, lastHighlightedTweetAuthors.LastOrDefault(), ImageQuality);
break; break;
case MenuOpenDevTools: case MenuOpenDevTools:
@@ -113,20 +137,23 @@ namespace TweetDuck.Core.Handling{
} }
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){ public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
TweetDeckBridge.LastRightClickedLink = string.Empty; ContextInfo = default(KeyValuePair<string, string>);
TweetDeckBridge.LastRightClickedImage = string.Empty;
} }
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 SetClipboardText(Control control, string text){
control.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText));
} }
protected static void AddDebugMenuItems(IMenuModel model){ protected static void AddDebugMenuItems(IMenuModel model){
model.AddItem((CefMenuCommand)MenuOpenDevTools, "Open dev tools"); model.AddItem(MenuOpenDevTools, "Open dev tools");
} }
protected static void RemoveSeparatorIfLast(IMenuModel model){ protected static void RemoveSeparatorIfLast(IMenuModel model){

View File

@@ -5,18 +5,18 @@ using TweetDuck.Core.Controls;
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 string TitleReloadBrowser = "Reload browser"; private const string TitleReloadBrowser = "Reload browser";
private const string TitleMuteNotifications = "Mute notifications"; private const string TitleMuteNotifications = "Mute notifications";
@@ -26,10 +26,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;
} }
@@ -46,23 +46,23 @@ namespace TweetDuck.Core.Handling{
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 (!TwitterUtils.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 +71,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();
@@ -96,8 +96,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 +118,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 +130,11 @@ 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; return true;
} }

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

@@ -3,16 +3,17 @@ using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification; using TweetDuck.Core.Notification;
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,23 +29,26 @@ 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();
model.AddItem(MenuSkipTweet, "Skip tweet");
model.AddCheckItem(MenuFreeze, "Freeze");
model.SetChecked(MenuFreeze, form.FreezeTimer);
if (!string.IsNullOrEmpty(form.CurrentTweetUrl)){ if (!string.IsNullOrEmpty(form.CurrentTweetUrl)){
model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address"); model.AddSeparator();
model.AddItem(MenuCopyTweetUrl, "Copy tweet address");
if (!string.IsNullOrEmpty(form.CurrentQuoteUrl)){ if (!string.IsNullOrEmpty(form.CurrentQuoteUrl)){
model.AddItem((CefMenuCommand)MenuCopyQuotedTweetUrl, "Copy quoted tweet address"); model.AddItem(MenuCopyQuotedTweetUrl, "Copy quoted tweet address");
} }
model.AddSeparator();
} }
} }
if (HasDevTools){ if (HasDevTools){
AddSeparator(model);
AddDebugMenuItems(model); AddDebugMenuItems(model);
} }
@@ -58,7 +62,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 +71,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

@@ -2,7 +2,7 @@
using CefSharp; using CefSharp;
namespace TweetDuck.Core.Handling.General{ namespace TweetDuck.Core.Handling.General{
class BrowserProcessHandler : IBrowserProcessHandler{ sealed class BrowserProcessHandler : IBrowserProcessHandler{
void IBrowserProcessHandler.OnContextInitialized(){ void IBrowserProcessHandler.OnContextInitialized(){
using(IRequestContext ctx = Cef.GetGlobalRequestContext()){ using(IRequestContext ctx = Cef.GetGlobalRequestContext()){
ctx.SetPreference("browser.enable_spellchecking", Program.UserConfig.EnableSpellCheck, out string _); ctx.SetPreference("browser.enable_spellchecking", Program.UserConfig.EnableSpellCheck, out string _);

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

@@ -7,32 +7,52 @@ using TweetDuck.Core.Other;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Handling.General{ namespace TweetDuck.Core.Handling.General{
class JavaScriptDialogHandler : IJsDialogHandler{ 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){ bool IJsDialogHandler.OnJSDialog(IWebBrowser browserControl, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage){
((ChromiumWebBrowser)browserControl).InvokeSafe(() => { ((ChromiumWebBrowser)browserControl).InvokeSafe(() => {
FormMessage form; FormMessage form;
TextBox input = null; TextBox input = null;
if (dialogType == CefJsDialogType.Alert){ if (dialogType == CefJsDialogType.Alert){
form = new FormMessage("Browser Message", messageText, MessageBoxIcon.None); form = CreateMessageForm("Browser Message", messageText);
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused); form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
} }
else if (dialogType == CefJsDialogType.Confirm){ else if (dialogType == CefJsDialogType.Confirm){
form = new FormMessage("Browser Confirmation", messageText, MessageBoxIcon.None); form = CreateMessageForm("Browser Confirmation", messageText);
form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel); form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel);
form.AddButton(FormMessage.Yes, ControlType.Focused); form.AddButton(FormMessage.Yes, ControlType.Focused);
} }
else if (dialogType == CefJsDialogType.Prompt){ else if (dialogType == CefJsDialogType.Prompt){
form = new FormMessage("Browser Prompt", messageText, MessageBoxIcon.None); form = CreateMessageForm("Browser Prompt", messageText);
form.AddButton(FormMessage.Cancel, DialogResult.Cancel, ControlType.Cancel); form.AddButton(FormMessage.Cancel, DialogResult.Cancel, ControlType.Cancel);
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused); form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
float dpiScale = form.GetDPIScale(); float dpiScale = form.GetDPIScale();
int inputPad = form.HasIcon ? 43 : 0;
input = new TextBox{ input = new TextBox{
Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom, Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
Location = new Point(BrowserUtils.Scale(22, dpiScale), form.ActionPanelY-BrowserUtils.Scale(46, dpiScale)), Location = new Point(BrowserUtils.Scale(22+inputPad, dpiScale), form.ActionPanelY-BrowserUtils.Scale(46, dpiScale)),
Size = new Size(form.ClientSize.Width-BrowserUtils.Scale(44, dpiScale), 20) Size = new Size(form.ClientSize.Width-BrowserUtils.Scale(44+inputPad, dpiScale), 20)
}; };
form.Controls.Add(input); form.Controls.Add(input);

View File

@@ -1,8 +1,9 @@
using CefSharp; using CefSharp;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Handling.General{ 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 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; newBrowser = null;
@@ -11,7 +12,7 @@ namespace TweetDuck.Core.Handling.General{
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:

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

@@ -2,7 +2,7 @@
using TweetDuck.Core.Handling.General; using TweetDuck.Core.Handling.General;
namespace TweetDuck.Core.Handling{ namespace TweetDuck.Core.Handling{
class RequestHandlerBrowser : RequestHandlerBase{ 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

@@ -4,13 +4,27 @@ 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.Handling.General;
using TweetDuck.Core.Other.Management;
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 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 Point PrimaryLocation{ protected Point PrimaryLocation{
get{ get{
UserConfig config = Program.UserConfig; UserConfig config = Program.UserConfig;
@@ -72,25 +86,27 @@ namespace TweetDuck.Core.Notification{
protected double SizeScale => dpiScale*Program.UserConfig.ZoomMultiplier; protected double SizeScale => dpiScale*Program.UserConfig.ZoomMultiplier;
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 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;
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;
@@ -142,7 +158,7 @@ namespace TweetDuck.Core.Notification{
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);
int identifier = browser.GetBrowser().Identifier; int identifier = browser.GetBrowser().Identifier;
Disposed += (sender2, args2) => BrowserProcesses.Forget(identifier); Disposed += (sender2, args2) => BrowserProcesses.Forget(identifier);
@@ -157,7 +173,7 @@ namespace TweetDuck.Core.Notification{
} }
Location = ControlExtensions.InvisibleLocation; Location = ControlExtensions.InvisibleLocation;
currentColumn = null; currentNotification = null;
} }
public virtual void FinishCurrentNotification(){} public virtual void FinishCurrentNotification(){}
@@ -180,10 +196,7 @@ namespace TweetDuck.Core.Notification{
} }
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(TwitterUtils.TweetDeckURL); browser.Load(TwitterUtils.TweetDeckURL);
} }
@@ -192,12 +205,13 @@ namespace TweetDuck.Core.Notification{
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(){

View File

@@ -17,10 +17,7 @@ namespace TweetDuck.Core.Notification{
private const int TimerBarHeight = 4; 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 = ScriptLoader.LoadResource(NotificationScriptFile);
private static readonly string PluginJS = ScriptLoader.LoadResource(PluginManager.PluginNotificationScriptFile);
private readonly PluginManager plugins; private readonly PluginManager plugins;
@@ -36,7 +33,7 @@ namespace TweetDuck.Core.Notification{
public bool RequiresResize{ public 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 || CanResizeWindow;
} }
set{ set{
@@ -46,7 +43,7 @@ namespace TweetDuck.Core.Notification{
} }
else{ else{
prevDisplayTimer = Program.UserConfig.DisplayNotificationTimer; prevDisplayTimer = Program.UserConfig.DisplayNotificationTimer;
prevFontSize = TweetNotification.FontSizeLevel; prevFontSize = FontSizeLevel;
} }
} }
} }
@@ -55,7 +52,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;
@@ -67,7 +64,7 @@ namespace TweetDuck.Core.Notification{
get{ get{
switch(Program.UserConfig.NotificationSize){ switch(Program.UserConfig.NotificationSize){
default: default:
return BrowserUtils.Scale(122, 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;
@@ -169,12 +166,7 @@ namespace TweetDuck.Core.Notification{
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.Environment.Notification)); 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);
}
} }
} }
@@ -287,7 +279,7 @@ namespace TweetDuck.Core.Notification{
StartMouseHook(); StartMouseHook();
} }
protected override void OnNotificationReady(){ protected virtual void OnNotificationReady(){
PrepareAndDisplayWindow(); PrepareAndDisplayWindow();
timerProgress.Start(); timerProgress.Start();
} }

View File

@@ -14,7 +14,7 @@ namespace TweetDuck.Core.Notification.Screenshot{
sealed class FormNotificationScreenshotable : FormNotificationBase{ sealed class FormNotificationScreenshotable : FormNotificationBase{
private readonly PluginManager plugins; private readonly PluginManager plugins;
public FormNotificationScreenshotable(Action callback, Form owner, PluginManager pluginManager) : base(owner, false){ public FormNotificationScreenshotable(Action callback, FormBrowser owner, PluginManager pluginManager) : base(owner, false){
this.plugins = pluginManager; this.plugins = pluginManager;
browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new CallbackBridge(this, callback)); browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new CallbackBridge(this, callback));
@@ -22,7 +22,7 @@ namespace TweetDuck.Core.Notification.Screenshot{
browser.LoadingStateChanged += (sender, args) => { browser.LoadingStateChanged += (sender, args) => {
if (!args.IsLoading){ if (!args.IsLoading){
using(IFrame frame = args.Browser.MainFrame){ using(IFrame frame = args.Browser.MainFrame){
ScriptLoader.ExecuteScript(frame, "window.setTimeout($TD_NotificationScreenshot.trigger, 129)", "gen:screenshot"); ScriptLoader.ExecuteScript(frame, "window.setTimeout($TD_NotificationScreenshot.trigger, document.getElementsByTagName('iframe').length ? 267 : 67)", "gen:screenshot");
} }
} }
}; };

View File

@@ -46,7 +46,7 @@ namespace TweetDuck.Core.Notification.Screenshot{
CanMoveWindow = () => false CanMoveWindow = () => false
}; };
screenshot.LoadNotificationForScreenshot(new TweetNotification(string.Empty, html, 0, string.Empty, string.Empty), width, height); screenshot.LoadNotificationForScreenshot(new TweetNotification(string.Empty, string.Empty, string.Empty, html, 0, string.Empty, string.Empty), width, height);
screenshot.Show(); screenshot.Show();
timeout.Start(); timeout.Start();

View File

@@ -17,6 +17,10 @@ namespace TweetDuck.Core.Notification{
player.Play(file); player.Play(file);
} }
public bool SetVolume(int volume){
return player.SetVolume(volume);
}
private void Player_PlaybackError(object sender, PlaybackErrorEventArgs e){ private void Player_PlaybackError(object sender, PlaybackErrorEventArgs e){
PlaybackError?.Invoke(this, e); PlaybackError?.Invoke(this, e);
} }

View File

@@ -1,27 +1,12 @@
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 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";
private const string DefaultHeadTag = @"<meta charset='utf-8'><meta http-equiv='X-UA-Compatible' content='chrome=1'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/font.5ef884f9f9.css'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/app-dark.5631e0dd42.css'><style type='text/css'>body{background:#222426}</style>";
private const string CustomCSS = @"body:before{content:none}body{overflow-y:auto}.scroll-styled-v::-webkit-scrollbar{width:7px}.scroll-styled-v::-webkit-scrollbar-thumb{border-radius:0}.scroll-styled-v::-webkit-scrollbar-track{border-left:0}#td-skip{opacity:0;cursor:pointer;transition:opacity 0.15s ease}.td-hover #td-skip{opacity:0.75}#td-skip:hover{opacity:1}.media-size-medium{height:calc(100vh - 16px)!important;max-height:240px;border-radius:1px!important}.js-quote-detail .media-size-medium{height:calc(100vh - 28px)!important;}.js-media.margin-vm, .js-media-preview-container.margin-vm{margin-bottom:0!important}";
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; private static string ExampleTweetHTML;
@@ -35,18 +20,10 @@ namespace TweetDuck.Core.Notification{
#endif #endif
} }
return new TweetNotification("Home", ExampleTweetHTML, 95, string.Empty, string.Empty, true); return new TweetNotification(string.Empty, string.Empty, "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{
TopLeft, TopRight, BottomLeft, BottomRight, Custom TopLeft, TopRight, BottomLeft, BottomRight, Custom
} }
@@ -55,7 +32,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 +43,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 +65,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>");

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,6 @@
using System.Windows.Forms; using System.ComponentModel;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Other{ namespace TweetDuck.Core.Other{
@@ -21,5 +23,19 @@ 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.OpenExternalBrowserUnsafe(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(){
new 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
}
}

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

@@ -0,0 +1,71 @@
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.Utils;
namespace TweetDuck.Core.Other{
sealed partial class FormGuide : Form{
private const string GuideUrl = "https://tweetduck.chylex.com/guide/v1/";
private readonly ChromiumWebBrowser browser;
public FormGuide(){
InitializeComponent();
Text = Program.BrandName+" Guide";
FormBrowser owner = FormManager.TryFind<FormBrowser>();
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(GuideUrl){
MenuHandler = new ContextMenuGuide(),
JsDialogHandler = new JavaScriptDialogHandler(),
LifeSpanHandler = new LifeSpanHandler(),
RequestHandler = new RequestHandlerBrowser()
};
browser.LoadingStateChanged += browser_LoadingStateChanged;
browser.FrameLoadStart += browser_FrameLoadStart;
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 browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
if (!e.IsLoading){
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 Config_ZoomLevelChanged(object sender, EventArgs e){
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Program.UserConfig.ZoomLevel);
}
}
}

View File

@@ -60,6 +60,7 @@ namespace TweetDuck.Core.Other{
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{

View File

@@ -19,6 +19,8 @@ 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 bool ShouldReloadBrowser { get; private set; }
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, Type startTab){ public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, Type startTab){
InitializeComponent(); InitializeComponent();
@@ -32,8 +34,10 @@ 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(updates));
AddButton("System Tray", () => new TabSettingsTray());
AddButton("Notifications", () => new TabSettingsNotifications(browser.CreateNotificationForm(false))); AddButton("Notifications", () => new TabSettingsNotifications(browser.CreateNotificationForm(false)));
AddButton("Sounds", () => new TabSettingsSounds()); AddButton("Sounds", () => new TabSettingsSounds());
AddButton("Feedback", () => new TabSettingsFeedback());
AddButton("Advanced", () => new TabSettingsAdvanced(browser.ReinjectCustomCSS)); AddButton("Advanced", () => new TabSettingsAdvanced(browser.ReinjectCustomCSS));
SelectTab(tabs[startTab ?? typeof(TabSettingsGeneral)]); SelectTab(tabs[startTab ?? typeof(TabSettingsGeneral)]);
@@ -52,13 +56,19 @@ 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)){ foreach(SettingsTab tab in tabs.Values){
if (dialog.ShowDialog() == DialogResult.OK && dialog.ShouldReloadUI){ if (tab.IsInitialized){
foreach(SettingsTab tab in tabs.Values){ tab.Control.OnClosing();
tab.Control = null; }
} }
SelectTab(currentTab); using(DialogSettingsManage dialog = new DialogSettingsManage(plugins)){
if (dialog.ShowDialog() == DialogResult.OK){
FormClosing -= FormSettings_FormClosing;
browser.ResumeNotification();
ShouldReloadBrowser = dialog.ShouldReloadBrowser;
Close();
} }
} }
} }
@@ -120,11 +130,16 @@ namespace TweetDuck.Core.Other{
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;
@@ -139,7 +154,7 @@ namespace TweetDuck.Core.Other{
panelContents.Focus(); panelContents.Focus();
} }
private class SettingsTab{ private sealed class SettingsTab{
public Button Button { get; } public Button Button { get; }
public BaseTabSettings Control{ public BaseTabSettings Control{

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

@@ -1,8 +1,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using CefSharp; using CefSharp;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Utils{ namespace TweetDuck.Core.Other.Management{
static class BrowserProcesses{ static class BrowserProcesses{
private static readonly Dictionary<int, int> PIDs = new Dictionary<int, int>(); private static readonly Dictionary<int, int> PIDs = new Dictionary<int, int>();

View File

@@ -5,7 +5,7 @@ using System.Windows.Forms;
using CefSharp; using CefSharp;
using Timer = System.Timers.Timer; using Timer = System.Timers.Timer;
namespace TweetDuck.Core.Utils{ namespace TweetDuck.Core.Other.Management{
sealed class MemoryUsageTracker : IDisposable{ sealed class MemoryUsageTracker : IDisposable{
private const int IntervalMemoryCheck = 60000*30; // 30 minutes private const int IntervalMemoryCheck = 60000*30; // 30 minutes
private const int IntervalCleanupAttempt = 60000*5; // 5 minutes private const int IntervalCleanupAttempt = 60000*5; // 5 minutes

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{
private readonly string PlayerExe = Path.Combine(Program.ProgramPath, "TweetDuck.Video.exe");
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 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){
if (Running){
Destroy();
isClosing = false;
}
lastUrl = url;
try{
currentPipe = DuplexPipe.CreateServer();
currentPipe.DataIn += currentPipe_DataIn;
if ((currentProcess = Process.Start(new ProcessStartInfo{
FileName = PlayerExe,
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);
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 a 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 a 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);
} }

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{
@@ -20,7 +22,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
} }
} }
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;
@@ -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 (FormMessage.Warning("Reset TweetDuck Options", "This will reset all of your program options. Plugins will not be affected. Do you want to proceed?", FormMessage.Yes, FormMessage.No)){ currentState = State.Reset;
Program.ResetConfig();
ShouldReloadUI = true; Text = "Restore Defaults";
DialogResult = DialogResult.OK; Flags = ExportFileFlags.Config;
Close();
}
return;
} }
// Import // Import
@@ -109,10 +106,43 @@ 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.Config)){
Program.ResetConfig();
}
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{
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){
Program.Restart(Arguments.ArgImportCookies);
}
else{
ShouldReloadBrowser = true;
} }
} }
else{ else{
@@ -165,6 +195,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,22 @@
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.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.labelLocale = new System.Windows.Forms.Label();
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(216, 217);
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 +52,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(153, 217);
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 +69,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,6 +83,40 @@
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;
// //
// comboLocale
//
this.comboLocale.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.comboLocale.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboLocale.FormattingEnabled = true;
this.comboLocale.Location = new System.Drawing.Point(15, 83);
this.comboLocale.Name = "comboLocale";
this.comboLocale.Size = new System.Drawing.Size(257, 21);
this.comboLocale.TabIndex = 3;
this.toolTip.SetToolTip(this.comboLocale, "Language used for spell checking.");
//
// tbDataFolder
//
this.tbDataFolder.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.tbDataFolder.Location = new System.Drawing.Point(15, 135);
this.tbDataFolder.Name = "tbDataFolder";
this.tbDataFolder.Size = new System.Drawing.Size(257, 20);
this.tbDataFolder.TabIndex = 5;
this.toolTip.SetToolTip(this.tbDataFolder, "Path to the data folder. Must be either an absolute path,\r\nor a simple folder name 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, 186);
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);
//
// labelLocale // labelLocale
// //
this.labelLocale.AutoSize = true; this.labelLocale.AutoSize = true;
@@ -91,16 +127,6 @@
this.labelLocale.TabIndex = 2; this.labelLocale.TabIndex = 2;
this.labelLocale.Text = "Locale"; this.labelLocale.Text = "Locale";
// //
// comboLocale
//
this.comboLocale.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.comboLocale.FormattingEnabled = true;
this.comboLocale.Location = new System.Drawing.Point(15, 83);
this.comboLocale.Name = "comboLocale";
this.comboLocale.Size = new System.Drawing.Size(201, 21);
this.comboLocale.TabIndex = 3;
//
// labelDataFolder // labelDataFolder
// //
this.labelDataFolder.AutoSize = true; this.labelDataFolder.AutoSize = true;
@@ -111,20 +137,23 @@
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, 170);
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, 252);
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.comboLocale);
@@ -155,5 +184,7 @@
private System.Windows.Forms.ComboBox comboLocale; 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

@@ -24,12 +24,26 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
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); comboLocale.SelectedItem = currentArgs.GetValue(Arguments.ArgLocale, DefaultLocale);
tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder, string.Empty);
cbLogging.CheckedChanged += control_Change;
cbDebugUpdates.CheckedChanged += control_Change;
comboLocale.SelectedValueChanged += control_Change;
if (Program.IsPortable){
tbDataFolder.Text = "Not available in portable version";
tbDataFolder.Enabled = false;
}
else{
tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder, string.Empty);
tbDataFolder.TextChanged += control_Change;
}
control_Change(this, EventArgs.Empty);
Text = Program.BrandName+" Arguments"; 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){
@@ -46,10 +60,21 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
Args.SetValue(Arguments.ArgLocale, locale); Args.SetValue(Arguments.ArgLocale, locale);
} }
if (!string.IsNullOrWhiteSpace(tbDataFolder.Text)){ if (!string.IsNullOrWhiteSpace(tbDataFolder.Text) && tbDataFolder.Enabled){
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

@@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using TweetDuck.Configuration;
using TweetDuck.Data; using TweetDuck.Data;
using TweetDuck.Plugins; using TweetDuck.Plugins;
using TweetDuck.Plugins.Enums; using TweetDuck.Plugins.Enums;
@@ -141,13 +140,6 @@ namespace TweetDuck.Core.Other.Settings.Export{
FormMessage.Information("Importing TweetDuck Profile", "Detected missing plugins when importing plugin data:\n"+string.Join("\n", missingPlugins), FormMessage.OK); 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;
}catch(Exception e){ }catch(Exception e){
LastException = e; LastException = e;
@@ -169,6 +161,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 +178,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

@@ -68,7 +68,7 @@
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.toolTip.SetToolTip(this.checkHardwareAcceleration, "Uses graphics card to improve performance. Disable if you experience\r\nvisual glitches. This option will not be exported in a profile.");
this.checkHardwareAcceleration.UseVisualStyleBackColor = true; this.checkHardwareAcceleration.UseVisualStyleBackColor = true;
// //
// btnEditCefArgs // btnEditCefArgs
@@ -142,26 +142,14 @@
0, 0,
0}); 0});
this.numMemoryThreshold.Location = new System.Drawing.Point(202, 82); this.numMemoryThreshold.Location = new System.Drawing.Point(202, 82);
this.numMemoryThreshold.Maximum = new decimal(new int[] { this.numMemoryThreshold.Maximum = 2000;
3000, this.numMemoryThreshold.Minimum = 200;
0,
0,
0});
this.numMemoryThreshold.Minimum = new decimal(new int[] {
200,
0,
0,
0});
this.numMemoryThreshold.Name = "numMemoryThreshold"; this.numMemoryThreshold.Name = "numMemoryThreshold";
this.numMemoryThreshold.Size = new System.Drawing.Size(97, 20); this.numMemoryThreshold.Size = new System.Drawing.Size(97, 20);
this.numMemoryThreshold.TabIndex = 4; this.numMemoryThreshold.TabIndex = 4;
this.numMemoryThreshold.TextSuffix = " MB"; this.numMemoryThreshold.TextSuffix = " MB";
this.toolTip.SetToolTip(this.numMemoryThreshold, "Minimum amount of memory usage by the browser process to trigger the cleanup.\r\nThis is not a limit, the usage is allowed to exceed this value."); this.toolTip.SetToolTip(this.numMemoryThreshold, "Minimum amount of memory usage by the browser process to trigger the cleanup.\r\nThis is not a limit, the usage is allowed to exceed this value.");
this.numMemoryThreshold.Value = new decimal(new int[] { this.numMemoryThreshold.Value = 400;
350,
0,
0,
0});
// //
// checkBrowserGCReload // checkBrowserGCReload
// //
@@ -172,7 +160,7 @@
this.checkBrowserGCReload.Size = new System.Drawing.Size(190, 17); this.checkBrowserGCReload.Size = new System.Drawing.Size(190, 17);
this.checkBrowserGCReload.TabIndex = 3; this.checkBrowserGCReload.TabIndex = 3;
this.checkBrowserGCReload.Text = "Enable Browser Memory Threshold"; this.checkBrowserGCReload.Text = "Enable Browser Memory Threshold";
this.toolTip.SetToolTip(this.checkBrowserGCReload, "Automatically reloads TweetDeck to save memory. This option only works\r\nif the browser is in a \'default state\', i.e. all modals and drawers are closed,\r\nand all columns are scrolled to top. Some notifications may be lost."); this.toolTip.SetToolTip(this.checkBrowserGCReload, "Automatically reloads TweetDeck to save memory. This option only works if\r\nthe browser is in a \'default state\', i.e. all modals and drawers are closed, and\r\nall columns are scrolled to top. This option will not be exported in a profile.");
this.checkBrowserGCReload.UseVisualStyleBackColor = true; this.checkBrowserGCReload.UseVisualStyleBackColor = true;
// //
// labelApp // labelApp

View File

@@ -7,7 +7,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){
@@ -16,16 +18,16 @@ namespace TweetDuck.Core.Other.Settings{
this.reinjectBrowserCSS = reinjectBrowserCSS; this.reinjectBrowserCSS = reinjectBrowserCSS;
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;
} }
checkBrowserGCReload.Checked = Config.EnableBrowserGCReload; checkBrowserGCReload.Checked = SysConfig.EnableBrowserGCReload;
numMemoryThreshold.Enabled = checkBrowserGCReload.Checked; numMemoryThreshold.Enabled = checkBrowserGCReload.Checked;
numMemoryThreshold.SetValueSafe(Config.BrowserMemoryThreshold); numMemoryThreshold.SetValueSafe(SysConfig.BrowserMemoryThreshold);
BrowserCache.CalculateCacheSize(bytes => this.InvokeSafe(() => { BrowserCache.CalculateCacheSize(bytes => this.InvokeSafe(() => {
if (bytes == -1L){ if (bytes == -1L){
@@ -53,6 +55,10 @@ namespace TweetDuck.Core.Other.Settings{
btnRestartArgs.Click += btnRestartArgs_Click; btnRestartArgs.Click += btnRestartArgs_Click;
} }
public override void OnClosing(){
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();
@@ -60,18 +66,17 @@ namespace TweetDuck.Core.Other.Settings{
} }
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 checkBrowserGCReload_CheckedChanged(object sender, EventArgs e){ private void checkBrowserGCReload_CheckedChanged(object sender, EventArgs e){
Config.EnableBrowserGCReload = checkBrowserGCReload.Checked; SysConfig.EnableBrowserGCReload = checkBrowserGCReload.Checked;
numMemoryThreshold.Enabled = checkBrowserGCReload.Checked; numMemoryThreshold.Enabled = checkBrowserGCReload.Checked;
} }
private void numMemoryThreshold_ValueChanged(object sender, EventArgs e){ private void numMemoryThreshold_ValueChanged(object sender, EventArgs e){
Config.BrowserMemoryThreshold = (int)numMemoryThreshold.Value; SysConfig.BrowserMemoryThreshold = (int)numMemoryThreshold.Value;
} }
private void btnEditCefArgs_Click(object sender, EventArgs e){ private void btnEditCefArgs_Click(object sender, EventArgs e){
@@ -82,7 +87,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;
@@ -104,7 +109,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;
@@ -138,5 +143,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,134 @@
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.panelFeedback = new System.Windows.Forms.Panel();
this.labelDataCollectionLink = new System.Windows.Forms.LinkLabel();
this.checkDataCollection = new System.Windows.Forms.CheckBox();
this.labelDataCollection = new System.Windows.Forms.Label();
this.labelFeedback = new System.Windows.Forms.Label();
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.btnSendFeedback = new System.Windows.Forms.Button();
this.panelFeedback.SuspendLayout();
this.SuspendLayout();
//
// panelFeedback
//
this.panelFeedback.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelFeedback.Controls.Add(this.btnSendFeedback);
this.panelFeedback.Controls.Add(this.labelDataCollectionLink);
this.panelFeedback.Controls.Add(this.checkDataCollection);
this.panelFeedback.Controls.Add(this.labelDataCollection);
this.panelFeedback.Location = new System.Drawing.Point(9, 31);
this.panelFeedback.Name = "panelFeedback";
this.panelFeedback.Size = new System.Drawing.Size(322, 80);
this.panelFeedback.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, 60);
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;
this.labelDataCollectionLink.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.labelDataCollectionLink_LinkClicked);
//
// checkDataCollection
//
this.checkDataCollection.AutoSize = true;
this.checkDataCollection.Location = new System.Drawing.Point(6, 59);
this.checkDataCollection.Margin = new System.Windows.Forms.Padding(6, 5, 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;
//
// labelDataCollection
//
this.labelDataCollection.AutoSize = true;
this.labelDataCollection.Location = new System.Drawing.Point(3, 41);
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(6, 8);
this.labelFeedback.Margin = new System.Windows.Forms.Padding(0, 2, 0, 0);
this.labelFeedback.Name = "labelFeedback";
this.labelFeedback.Size = new System.Drawing.Size(80, 20);
this.labelFeedback.TabIndex = 0;
this.labelFeedback.Text = "Feedback";
//
// btnSendFeedback
//
this.btnSendFeedback.AutoSize = true;
this.btnSendFeedback.Location = new System.Drawing.Point(5, 3);
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;
//
// TabSettingsFeedback
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.panelFeedback);
this.Controls.Add(this.labelFeedback);
this.Name = "TabSettingsFeedback";
this.Size = new System.Drawing.Size(340, 122);
this.panelFeedback.ResumeLayout(false);
this.panelFeedback.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Panel panelFeedback;
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;
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.Windows.Forms;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Other.Settings{
sealed partial class TabSettingsFeedback : BaseTabSettings{
public TabSettingsFeedback(){
InitializeComponent();
checkDataCollection.Checked = Config.AllowDataCollection;
}
public override void OnReady(){
btnSendFeedback.Click += btnSendFeedback_Click;
checkDataCollection.CheckedChanged += checkDataCollection_CheckedChanged;
}
private void btnSendFeedback_Click(object sender, EventArgs e){
BrowserUtils.OpenExternalBrowserUnsafe("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.OpenExternalBrowserUnsafe("https://github.com/chylex/TweetDuck/wiki/Send-anonymous-data");
}
}
}

View File

@@ -25,29 +25,24 @@
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.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.panelUI = new System.Windows.Forms.Panel();
this.labelTray = new System.Windows.Forms.Label();
this.panelUpdates = new System.Windows.Forms.Panel(); 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.checkBestImageQuality = new System.Windows.Forms.CheckBox();
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).BeginInit();
this.panelUI.SuspendLayout(); this.panelUI.SuspendLayout();
this.panelUpdates.SuspendLayout(); this.panelUpdates.SuspendLayout();
this.panelTray.SuspendLayout();
this.SuspendLayout(); this.SuspendLayout();
// //
// checkExpandLinks // checkExpandLinks
@@ -62,37 +57,14 @@
this.toolTip.SetToolTip(this.checkExpandLinks, "Expands links inside the tweets. If disabled,\r\nthe full links show up in a tooltip instead."); this.toolTip.SetToolTip(this.checkExpandLinks, "Expands links inside the tweets. If disabled,\r\nthe full links show up in a tooltip 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 popup or audio notifications.\r\nThe icon resets when the main window is restored.");
this.checkTrayHighlight.UseVisualStyleBackColor = true;
//
// checkSpellCheck // checkSpellCheck
// //
this.checkSpellCheck.AutoSize = true; this.checkSpellCheck.AutoSize = true;
this.checkSpellCheck.Location = new System.Drawing.Point(6, 74); this.checkSpellCheck.Location = new System.Drawing.Point(6, 97);
this.checkSpellCheck.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3); this.checkSpellCheck.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkSpellCheck.Name = "checkSpellCheck"; this.checkSpellCheck.Name = "checkSpellCheck";
this.checkSpellCheck.Size = new System.Drawing.Size(119, 17); this.checkSpellCheck.Size = new System.Drawing.Size(119, 17);
this.checkSpellCheck.TabIndex = 3; this.checkSpellCheck.TabIndex = 4;
this.checkSpellCheck.Text = "Enable Spell Check"; this.checkSpellCheck.Text = "Enable Spell Check";
this.toolTip.SetToolTip(this.checkSpellCheck, "Underlines words that are spelled incorrectly."); this.toolTip.SetToolTip(this.checkSpellCheck, "Underlines words that are spelled incorrectly.");
this.checkSpellCheck.UseVisualStyleBackColor = true; this.checkSpellCheck.UseVisualStyleBackColor = true;
@@ -123,11 +95,11 @@
// labelZoomValue // labelZoomValue
// //
this.labelZoomValue.BackColor = System.Drawing.Color.Transparent; this.labelZoomValue.BackColor = System.Drawing.Color.Transparent;
this.labelZoomValue.Location = new System.Drawing.Point(141, 123); this.labelZoomValue.Location = new System.Drawing.Point(147, 146);
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 = 6; this.labelZoomValue.TabIndex = 7;
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."); this.toolTip.SetToolTip(this.labelZoomValue, "Changes the zoom level.\r\nAlso affects notifications and screenshots.");
@@ -144,39 +116,53 @@
this.toolTip.SetToolTip(this.checkSwitchAccountSelectors, "When (re)tweeting, click to select a single account or hold Shift to\r\nselect multiple accounts, instead of TweetDeck\'s default behavior."); this.toolTip.SetToolTip(this.checkSwitchAccountSelectors, "When (re)tweeting, click to select a single account or hold Shift to\r\nselect multiple 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, 74);
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.toolTip.SetToolTip(this.checkBestImageQuality, "When right-clicking a tweet image, the context menu options\r\nwill use links to the original image size (:orig in the URL).");
this.checkBestImageQuality.UseVisualStyleBackColor = true;
//
// checkOpenSearchInFirstColumn
//
this.checkOpenSearchInFirstColumn.AutoSize = true;
this.checkOpenSearchInFirstColumn.Location = new System.Drawing.Point(6, 51);
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.toolTip.SetToolTip(this.checkOpenSearchInFirstColumn, "By default, TweetDeck adds Search columns at the end.\r\nThis option makes them appear before the first column instead.");
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, 122); this.trackBarZoom.Location = new System.Drawing.Point(3, 145);
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 = 5; this.trackBarZoom.TabIndex = 6;
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, 106); this.labelZoom.Location = new System.Drawing.Point(3, 129);
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 = 4; this.labelZoom.TabIndex = 5;
this.labelZoom.Text = "Zoom"; this.labelZoom.Text = "Zoom";
// //
// zoomUpdateTimer // zoomUpdateTimer
@@ -199,94 +185,56 @@
// //
this.panelUI.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) this.panelUI.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.panelUI.Controls.Add(this.checkOpenSearchInFirstColumn);
this.panelUI.Controls.Add(this.checkBestImageQuality); this.panelUI.Controls.Add(this.checkBestImageQuality);
this.panelUI.Controls.Add(this.checkExpandLinks); this.panelUI.Controls.Add(this.checkExpandLinks);
this.panelUI.Controls.Add(this.checkSwitchAccountSelectors); this.panelUI.Controls.Add(this.checkSwitchAccountSelectors);
this.panelUI.Controls.Add(this.checkSpellCheck); this.panelUI.Controls.Add(this.checkSpellCheck);
this.panelUI.Controls.Add(this.labelZoom); this.panelUI.Controls.Add(this.labelZoom);
this.panelUI.Controls.Add(this.labelZoomValue);
this.panelUI.Controls.Add(this.trackBarZoom); this.panelUI.Controls.Add(this.trackBarZoom);
this.panelUI.Controls.Add(this.labelZoomValue);
this.panelUI.Location = new System.Drawing.Point(9, 31); this.panelUI.Location = new System.Drawing.Point(9, 31);
this.panelUI.Name = "panelUI"; this.panelUI.Name = "panelUI";
this.panelUI.Size = new System.Drawing.Size(322, 157); this.panelUI.Size = new System.Drawing.Size(322, 179);
this.panelUI.TabIndex = 1; this.panelUI.TabIndex = 1;
// //
// 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(6, 212);
this.labelTray.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
this.labelTray.Name = "labelTray";
this.labelTray.Size = new System.Drawing.Size(96, 20);
this.labelTray.TabIndex = 2;
this.labelTray.Text = "System Tray";
//
// panelUpdates // panelUpdates
// //
this.panelUpdates.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) this.panelUpdates.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.panelUpdates.Controls.Add(this.checkUpdateNotifications); this.panelUpdates.Controls.Add(this.checkUpdateNotifications);
this.panelUpdates.Controls.Add(this.btnCheckUpdates); this.panelUpdates.Controls.Add(this.btnCheckUpdates);
this.panelUpdates.Location = new System.Drawing.Point(9, 358); this.panelUpdates.Location = new System.Drawing.Point(9, 257);
this.panelUpdates.Name = "panelUpdates"; this.panelUpdates.Name = "panelUpdates";
this.panelUpdates.Size = new System.Drawing.Size(322, 55); this.panelUpdates.Size = new System.Drawing.Size(322, 55);
this.panelUpdates.TabIndex = 5; this.panelUpdates.TabIndex = 3;
//
// 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, 235);
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, 335); this.labelUpdates.Location = new System.Drawing.Point(6, 234);
this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0); this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 21, 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";
// //
// checkBestImageQuality
//
this.checkBestImageQuality.AutoSize = true;
this.checkBestImageQuality.Location = new System.Drawing.Point(6, 51);
this.checkBestImageQuality.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkBestImageQuality.Name = "checkBestImageQuality";
this.checkBestImageQuality.Size = new System.Drawing.Size(114, 17);
this.checkBestImageQuality.TabIndex = 2;
this.checkBestImageQuality.Text = "Best Image Quality";
this.toolTip.SetToolTip(this.checkBestImageQuality, "When right-clicking a tweet image, the context menu options\r\nwill use links to the original image size (:orig in the URL).");
this.checkBestImageQuality.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.labelUpdates);
this.Controls.Add(this.panelTray);
this.Controls.Add(this.panelUpdates); this.Controls.Add(this.panelUpdates);
this.Controls.Add(this.labelTray);
this.Controls.Add(this.panelUI); this.Controls.Add(this.panelUI);
this.Controls.Add(this.labelUI); this.Controls.Add(this.labelUI);
this.Name = "TabSettingsGeneral"; this.Name = "TabSettingsGeneral";
this.Size = new System.Drawing.Size(340, 422); this.Size = new System.Drawing.Size(340, 322);
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).EndInit();
this.panelUI.ResumeLayout(false); this.panelUI.ResumeLayout(false);
this.panelUI.PerformLayout(); this.panelUI.PerformLayout();
this.panelUpdates.ResumeLayout(false); this.panelUpdates.ResumeLayout(false);
this.panelUpdates.PerformLayout(); this.panelUpdates.PerformLayout();
this.panelTray.ResumeLayout(false);
this.panelTray.PerformLayout();
this.ResumeLayout(false); this.ResumeLayout(false);
this.PerformLayout(); this.PerformLayout();
@@ -295,10 +243,7 @@
#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 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;
@@ -309,10 +254,9 @@
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 panelUI;
private System.Windows.Forms.Label labelTray;
private System.Windows.Forms.Panel panelUpdates; 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 checkBestImageQuality;
private System.Windows.Forms.CheckBox checkOpenSearchInFirstColumn;
} }
} }

View File

@@ -1,10 +1,9 @@
using System; using System;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
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 UpdateHandler updates; private readonly UpdateHandler updates;
private int updateCheckEventId = -1; private int updateCheckEventId = -1;
@@ -15,22 +14,15 @@ namespace TweetDuck.Core.Other.Settings{
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");
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);
toolTip.SetToolTip(trackBarZoom, toolTip.GetToolTip(labelZoomValue)); toolTip.SetToolTip(trackBarZoom, toolTip.GetToolTip(labelZoomValue));
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;
checkOpenSearchInFirstColumn.Checked = Config.OpenSearchInFirstColumn;
checkBestImageQuality.Checked = Config.BestImageQuality; checkBestImageQuality.Checked = Config.BestImageQuality;
checkSpellCheck.Checked = Config.EnableSpellCheck; checkSpellCheck.Checked = Config.EnableSpellCheck;
checkTrayHighlight.Checked = Config.EnableTrayHighlight;
checkUpdateNotifications.Checked = Config.EnableUpdateCheck; checkUpdateNotifications.Checked = Config.EnableUpdateCheck;
} }
@@ -38,13 +30,11 @@ 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;
checkOpenSearchInFirstColumn.CheckedChanged += checkOpenSearchInFirstColumn_CheckedChanged;
checkBestImageQuality.CheckedChanged += checkBestImageQuality_CheckedChanged; checkBestImageQuality.CheckedChanged += checkBestImageQuality_CheckedChanged;
checkSpellCheck.CheckedChanged += checkSpellCheck_CheckedChanged; checkSpellCheck.CheckedChanged += checkSpellCheck_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;
} }
@@ -61,6 +51,10 @@ namespace TweetDuck.Core.Other.Settings{
Config.SwitchAccountSelectors = checkSwitchAccountSelectors.Checked; Config.SwitchAccountSelectors = checkSwitchAccountSelectors.Checked;
} }
private void checkOpenSearchInFirstColumn_CheckedChanged(object sender, EventArgs e){
Config.OpenSearchInFirstColumn = checkOpenSearchInFirstColumn.Checked;
}
private void checkBestImageQuality_CheckedChanged(object sender, EventArgs e){ private void checkBestImageQuality_CheckedChanged(object sender, EventArgs e){
Config.BestImageQuality = checkBestImageQuality.Checked; Config.BestImageQuality = checkBestImageQuality.Checked;
} }
@@ -78,36 +72,23 @@ 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){ btnCheckUpdates.Enabled = false;
FormMessage.Warning("Unsupported System", "Sorry, your system is no longer supported.", FormMessage.OK); updateCheckEventId = updates.Check(true);
}
else{
btnCheckUpdates.Enabled = false;
updates.DismissUpdate(string.Empty);
}
} }
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){
FormMessage.Information("No Updates Available", "Your version of TweetDuck is up to date.", FormMessage.OK); FormMessage.Information("No Updates Available", "Your version of TweetDuck is up to date.", FormMessage.OK);
} }
} }

View File

@@ -79,7 +79,7 @@
this.labelEdgeDistanceValue.Location = new System.Drawing.Point(147, 129); this.labelEdgeDistanceValue.Location = new System.Drawing.Point(147, 129);
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;
@@ -253,7 +253,7 @@
this.labelDurationValue.Location = new System.Drawing.Point(147, 77); this.labelDurationValue.Location = new System.Drawing.Point(147, 77);
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;
@@ -406,7 +406,7 @@
this.labelScrollSpeedValue.Location = new System.Drawing.Point(147, 53); this.labelScrollSpeedValue.Location = new System.Drawing.Point(147, 53);
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;
@@ -450,7 +450,6 @@
// //
this.panelLocation.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) this.panelLocation.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.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.labelDisplay);
this.panelLocation.Controls.Add(this.trackBarEdgeDistance); this.panelLocation.Controls.Add(this.trackBarEdgeDistance);
@@ -460,6 +459,7 @@
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.Controls.Add(this.labelEdgeDistanceValue);
this.panelLocation.Location = new System.Drawing.Point(9, 418); this.panelLocation.Location = new System.Drawing.Point(9, 418);
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, 165);

View File

@@ -4,7 +4,7 @@ using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification; using TweetDuck.Core.Notification;
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 FormNotificationMain notification;

View File

@@ -25,15 +25,40 @@
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.panelSoundNotification.SuspendLayout(); this.panelSoundNotification.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.trackBarVolume)).BeginInit();
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;
this.toolTip.SetToolTip(this.tbCustomSound, "When empty, the default TweetDeck sound notification is used.");
//
// labelVolumeValue
//
this.labelVolumeValue.BackColor = System.Drawing.Color.Transparent;
this.labelVolumeValue.Location = new System.Drawing.Point(147, 84);
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,16 +94,6 @@
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;
this.toolTip.SetToolTip(this.tbCustomSound, "When empty, the default TweetDeck sound notification is used.");
//
// labelSoundNotification // labelSoundNotification
// //
this.labelSoundNotification.AutoSize = true; this.labelSoundNotification.AutoSize = true;
@@ -94,15 +109,42 @@
// //
this.panelSoundNotification.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) this.panelSoundNotification.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.panelSoundNotification.Controls.Add(this.labelVolume);
this.panelSoundNotification.Controls.Add(this.trackBarVolume);
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.Controls.Add(this.labelVolumeValue);
this.panelSoundNotification.Location = new System.Drawing.Point(9, 31); this.panelSoundNotification.Location = new System.Drawing.Point(9, 31);
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, 119);
this.panelSoundNotification.TabIndex = 2; this.panelSoundNotification.TabIndex = 2;
// //
// labelVolume
//
this.labelVolume.AutoSize = true;
this.labelVolume.Location = new System.Drawing.Point(3, 67);
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, 83);
this.trackBarVolume.Maximum = 100;
this.trackBarVolume.Name = "trackBarVolume";
this.trackBarVolume.Size = new System.Drawing.Size(148, 30);
this.trackBarVolume.SmallChange = 1;
this.trackBarVolume.TabIndex = 5;
this.trackBarVolume.TickFrequency = 10;
this.trackBarVolume.Value = 100;
this.trackBarVolume.ValueChanged += new System.EventHandler(this.trackBarVolume_ValueChanged);
//
// TabSettingsSounds // TabSettingsSounds
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
@@ -110,9 +152,10 @@
this.Controls.Add(this.panelSoundNotification); this.Controls.Add(this.panelSoundNotification);
this.Controls.Add(this.labelSoundNotification); 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, 160);
this.panelSoundNotification.ResumeLayout(false); this.panelSoundNotification.ResumeLayout(false);
this.panelSoundNotification.PerformLayout(); this.panelSoundNotification.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.trackBarVolume)).EndInit();
this.ResumeLayout(false); this.ResumeLayout(false);
this.PerformLayout(); this.PerformLayout();
@@ -127,5 +170,8 @@
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;
} }
} }

View File

@@ -2,21 +2,29 @@
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; using TweetLib.Audio;
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 SoundNotification soundNotification;
private readonly bool supportsChangingVolume;
public TabSettingsSounds(){ public TabSettingsSounds(){
InitializeComponent(); InitializeComponent();
soundNotification = new SoundNotification(); soundNotification = new SoundNotification();
soundNotification.PlaybackError += sound_PlaybackError; soundNotification.PlaybackError += sound_PlaybackError;
supportsChangingVolume = soundNotification.SetVolume(Config.NotificationSoundVolume);
trackBarVolume.Enabled = supportsChangingVolume && !string.IsNullOrEmpty(Config.NotificationSoundPath);
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(); Disposed += (sender, args) => soundNotification.Dispose();
} }
@@ -34,9 +42,10 @@ namespace TweetDuck.Core.Other.Settings{
private void tbCustomSound_TextChanged(object sender, EventArgs e){ private void tbCustomSound_TextChanged(object sender, EventArgs e){
bool isEmpty = string.IsNullOrEmpty(tbCustomSound.Text); bool isEmpty = string.IsNullOrEmpty(tbCustomSound.Text);
tbCustomSound.ForeColor = isEmpty || File.Exists(tbCustomSound.Text) ? SystemColors.WindowText : Color.Maroon; tbCustomSound.ForeColor = isEmpty || File.Exists(tbCustomSound.Text) ? SystemColors.WindowText : Color.Red;
btnPlaySound.Enabled = !isEmpty; btnPlaySound.Enabled = !isEmpty;
btnResetSound.Enabled = !isEmpty; btnResetSound.Enabled = !isEmpty;
trackBarVolume.Enabled = supportsChangingVolume && !isEmpty;
} }
private void btnPlaySound_Click(object sender, EventArgs e){ private void btnPlaySound_Click(object sender, EventArgs e){
@@ -63,5 +72,11 @@ 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){
Config.NotificationSoundVolume = trackBarVolume.Value;
soundNotification.SetVolume(Config.NotificationSoundVolume);
labelVolumeValue.Text = Config.NotificationSoundVolume+"%";
}
} }
} }

View File

@@ -0,0 +1,116 @@
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.panelTray = new System.Windows.Forms.Panel();
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.panelTray.SuspendLayout();
this.SuspendLayout();
//
// 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, 31);
this.panelTray.Name = "panelTray";
this.panelTray.Size = new System.Drawing.Size(322, 76);
this.panelTray.TabIndex = 1;
//
// 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 popup or audio notifications.\r\nThe icon resets when the main window is restored.");
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, 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.");
//
// labelTrayIcon
//
this.labelTrayIcon.AutoSize = true;
this.labelTrayIcon.Location = new System.Drawing.Point(3, 38);
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(6, 8);
this.labelTray.Margin = new System.Windows.Forms.Padding(0, 2, 0, 0);
this.labelTray.Name = "labelTray";
this.labelTray.Size = new System.Drawing.Size(96, 20);
this.labelTray.TabIndex = 0;
this.labelTray.Text = "System Tray";
//
// TabSettingsTray
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.panelTray);
this.Controls.Add(this.labelTray);
this.Name = "TabSettingsTray";
this.Size = new System.Drawing.Size(340, 119);
this.panelTray.ResumeLayout(false);
this.panelTray.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Panel panelTray;
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;
}
}

View File

@@ -0,0 +1,33 @@
using System;
namespace TweetDuck.Core.Other.Settings{
sealed partial class TabSettingsTray : BaseTabSettings{
public TabSettingsTray(){
InitializeComponent();
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

@@ -3,7 +3,7 @@ using System.ComponentModel;
using System.Windows.Forms; using System.Windows.Forms;
namespace TweetDuck.Core{ namespace TweetDuck.Core{
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
} }

View File

@@ -5,6 +5,7 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp.WinForms;
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
namespace TweetDuck.Core.Utils{ namespace TweetDuck.Core.Utils{
@@ -42,23 +43,46 @@ namespace TweetDuck.Core.Utils{
} }
} }
public static bool IsValidUrl(string url){ public static ChromiumWebBrowser AsControl(this IWebBrowser browserControl){
return (ChromiumWebBrowser)browserControl;
}
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)){ if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){
string scheme = uri.Scheme; string scheme = uri.Scheme;
return scheme == Uri.UriSchemeHttp || scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeFtp || scheme == Uri.UriSchemeMailto;
if (scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeHttp || scheme == Uri.UriSchemeFtp || scheme == Uri.UriSchemeMailto){
return uri.Host == TwitterTrackingUrl ? UrlCheckResult.Tracking : UrlCheckResult.Fine;
}
} }
return false; 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:
} OpenExternalBrowserUnsafe(url);
else{ break;
FormMessage.Warning("Blocked URL", "A potentially malicious URL was blocked from opening:\n"+url, FormMessage.OK);
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)){
OpenExternalBrowserUnsafe(url);
}
break;
case UrlCheckResult.Invalid:
FormMessage.Warning("Blocked URL", "A potentially malicious URL was blocked from opening:\n"+url, FormMessage.OK);
break;
} }
} }

View File

@@ -8,7 +8,6 @@ namespace TweetDuck.Core.Utils{
[SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")] [SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Local")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Local")]
static class NativeMethods{ static class NativeMethods{
public static readonly IntPtr HWND_BROADCAST = new IntPtr(0xFFFF);
public static readonly IntPtr HOOK_HANDLED = new IntPtr(-1); public static readonly IntPtr HOOK_HANDLED = new IntPtr(-1);
public const int HWND_TOPMOST = -1; public const int HWND_TOPMOST = -1;
@@ -17,7 +16,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;
@@ -65,16 +63,7 @@ namespace TweetDuck.Core.Utils{
[DllImport("gdi32.dll")] [DllImport("gdi32.dll")]
[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, UIntPtr wParam, IntPtr lParam);
[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")]
[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);

View File

@@ -2,6 +2,7 @@
using CefSharp; using CefSharp;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
@@ -13,7 +14,7 @@ namespace TweetDuck.Core.Utils{
public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153); public static readonly Color BackgroundColor = Color.FromArgb(28, 99, 153);
public const string BackgroundColorFix = "let e=document.createElement('style');document.head.appendChild(e);e.innerHTML='body::before{background:#1c6399!important}'"; public 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/([^/]+)/?$", RegexOptions.Compiled), false); 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 Regex RegexAccount => RegexAccountLazy.Value;
public static readonly string[] DictionaryWords = { public static readonly string[] DictionaryWords = {
@@ -32,16 +33,16 @@ namespace TweetDuck.Core.Utils{
return frame.Url.Contains("//twitter.com/"); return frame.Url.Contains("//twitter.com/");
} }
private static string ExtractImageBaseLink(string url){ private static string ExtractMediaBaseLink(string url){
int dot = url.LastIndexOf('.'); int dot = url.LastIndexOf('/');
return dot == -1 ? url : StringUtils.ExtractBefore(url, ':', dot); return dot == -1 ? url : StringUtils.ExtractBefore(url, ':', dot);
} }
public static string GetImageLink(string url, ImageQuality quality){ public static string GetMediaLink(string url, ImageQuality quality){
if (quality == ImageQuality.Orig){ if (quality == ImageQuality.Orig){
string result = ExtractImageBaseLink(url); string result = ExtractMediaBaseLink(url);
if (result != url){ if (result != url || url.Contains("//pbs.twimg.com/media/")){
result += ":orig"; result += ":orig";
} }
@@ -52,23 +53,34 @@ namespace TweetDuck.Core.Utils{
} }
} }
public static void DownloadImage(string url, ImageQuality quality){ public static void DownloadImage(string url, string username, ImageQuality quality){
DownloadImages(new string[]{ url }, quality); DownloadImages(new string[]{ url }, username, quality);
} }
public static void DownloadImages(string[] urls, ImageQuality quality){ public static void DownloadImages(string[] urls, string username, ImageQuality quality){
if (urls.Length == 0){ if (urls.Length == 0){
return; return;
} }
string file = BrowserUtils.GetFileNameFromUrl(ExtractImageBaseLink(urls[0])); string firstImageLink = GetMediaLink(urls[0], quality);
int qualityIndex = firstImageLink.IndexOf(':', firstImageLink.LastIndexOf('/'));
string file = BrowserUtils.GetFileNameFromUrl(ExtractMediaBaseLink(firstImageLink));
string ext = Path.GetExtension(file); // includes dot 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{ using(SaveFileDialog dialog = new SaveFileDialog{
AutoUpgradeEnabled = true, AutoUpgradeEnabled = true,
OverwritePrompt = urls.Length == 1, OverwritePrompt = urls.Length == 1,
Title = "Save image", Title = "Save image",
FileName = file, FileName = $"{string.Join(" ", fileNameParts.Where(part => part.Length > 0))}{ext}",
Filter = (urls.Length == 1 ? "Image" : "Images")+(string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}") Filter = (urls.Length == 1 ? "Image" : "Images")+(string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}")
}){ }){
if (dialog.ShowDialog() == DialogResult.OK){ if (dialog.ShowDialog() == DialogResult.OK){
@@ -77,18 +89,37 @@ namespace TweetDuck.Core.Utils{
} }
if (urls.Length == 1){ if (urls.Length == 1){
BrowserUtils.DownloadFileAsync(GetImageLink(urls[0], quality), dialog.FileName, null, OnFailure); BrowserUtils.DownloadFileAsync(firstImageLink, dialog.FileName, null, OnFailure);
} }
else{ else{
string pathBase = Path.ChangeExtension(dialog.FileName, null); string pathBase = Path.ChangeExtension(dialog.FileName, null);
string pathExt = Path.GetExtension(dialog.FileName); string pathExt = Path.GetExtension(dialog.FileName);
for(int index = 0; index < urls.Length; index++){ for(int index = 0; index < urls.Length; index++){
BrowserUtils.DownloadFileAsync(GetImageLink(urls[index], quality), pathBase+(index+1)+pathExt, null, OnFailure); BrowserUtils.DownloadFileAsync(GetMediaLink(urls[index], quality), $"{pathBase} {index+1}{pathExt}", null, OnFailure);
} }
} }
} }
} }
} }
public static void DownloadVideo(string url){
string filename = BrowserUtils.GetFileNameFromUrl(url);
string ext = Path.GetExtension(filename);
using(SaveFileDialog dialog = new SaveFileDialog{
AutoUpgradeEnabled = true,
OverwritePrompt = true,
Title = "Save video",
FileName = 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

@@ -91,7 +91,7 @@ namespace TweetDuck.Data{
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{

View File

@@ -12,8 +12,8 @@ namespace TweetDuck.Data.Serialization{
private static readonly ITypeConverter BasicSerializerObj = new BasicTypeConverter(); private static readonly ITypeConverter BasicSerializerObj = new BasicTypeConverter();
public delegate bool OnReadUnknownPropertyHandler(T obj, string property, string value); public delegate void HandleUnknownPropertiesHandler(T obj, Dictionary<string, string> data);
public OnReadUnknownPropertyHandler OnReadUnknownProperty { get; set; } public HandleUnknownPropertiesHandler HandleUnknownProperties { get; set; }
private readonly Dictionary<string, PropertyInfo> props; private readonly Dictionary<string, PropertyInfo> props;
private readonly Dictionary<Type, ITypeConverter> converters; private readonly Dictionary<Type, ITypeConverter> converters;
@@ -51,6 +51,8 @@ namespace TweetDuck.Data.Serialization{
} }
public void Read(string file, T obj){ 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))){ using(StreamReader reader = new StreamReader(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))){
if (reader.Peek() <= 1){ if (reader.Peek() <= 1){
throw new FormatException("Input appears to be a binary file."); throw new FormatException("Input appears to be a binary file.");
@@ -78,14 +80,22 @@ namespace TweetDuck.Data.Serialization{
throw new SerializationException($"Invalid file format, cannot convert value: {value} (property: {property})"); throw new SerializationException($"Invalid file format, cannot convert value: {value} (property: {property})");
} }
} }
else if (!(OnReadUnknownProperty?.Invoke(obj, property, value) ?? false)){ else{
throw new SerializationException($"Invalid file format, unknown property: {property}+"); 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)}");
}
}
} }
private class BasicTypeConverter : ITypeConverter{ private sealed class BasicTypeConverter : ITypeConverter{
bool ITypeConverter.TryWriteType(Type type, object value, out string converted){ bool ITypeConverter.TryWriteType(Type type, object value, out string converted){
switch(Type.GetTypeCode(type)){ switch(Type.GetTypeCode(type)){
case TypeCode.Boolean: case TypeCode.Boolean:

View File

@@ -1,12 +1,10 @@
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.Core.Utils;
using TweetDuck.Data.Serialization; using TweetDuck.Data.Serialization;
namespace TweetDuck.Data{ namespace TweetDuck.Data{
[Serializable] // TODO remove attribute with UserConfigLegacy
sealed class WindowState{ sealed class WindowState{
private Rectangle rect; private Rectangle rect;
private bool isMaximized; private bool isMaximized;

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;

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

View File

@@ -2,10 +2,10 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace TweetDuck.Plugins.Events{ namespace TweetDuck.Plugins.Events{
class PluginErrorEventArgs : EventArgs{ sealed class PluginErrorEventArgs : EventArgs{
public bool HasErrors => Errors.Count > 0; public bool HasErrors => Errors.Count > 0;
public IList<string> Errors; public IList<string> Errors { get; }
public PluginErrorEventArgs(IList<string> errors){ public PluginErrorEventArgs(IList<string> errors){
this.Errors = errors; this.Errors = errors;

View File

@@ -7,6 +7,9 @@ using TweetDuck.Plugins.Enums;
namespace TweetDuck.Plugins{ namespace TweetDuck.Plugins{
sealed class Plugin{ sealed class Plugin{
private static readonly Version AppVersion = new Version(Program.VersionTag);
private const string VersionWildcard = "*";
public string Identifier { get; } public string Identifier { get; }
public PluginGroup Group { get; } public PluginGroup Group { get; }
public PluginEnvironment Environments { get; private set; } public PluginEnvironment Environments { get; private set; }
@@ -50,7 +53,7 @@ namespace TweetDuck.Plugins{
{ "WEBSITE", "" }, { "WEBSITE", "" },
{ "CONFIGFILE", "" }, { "CONFIGFILE", "" },
{ "CONFIGDEFAULT", "" }, { "CONFIGDEFAULT", "" },
{ "REQUIRES", "*" } { "REQUIRES", VersionWildcard }
}; };
private bool? canRun; private bool? canRun;
@@ -84,7 +87,7 @@ namespace TweetDuck.Plugins{
public string GetScriptPath(PluginEnvironment environment){ public string GetScriptPath(PluginEnvironment environment){
if (Environments.HasFlag(environment)){ if (Environments.HasFlag(environment)){
string file = environment.GetScriptFile(); string file = environment.GetPluginScriptFile();
return file != null ? Path.Combine(pathRoot, file) : string.Empty; return file != null ? Path.Combine(pathRoot, file) : string.Empty;
} }
else{ else{
@@ -152,7 +155,7 @@ namespace TweetDuck.Plugins{
private static bool LoadEnvironments(string path, Plugin plugin, out string error){ private static bool LoadEnvironments(string path, Plugin plugin, out string error){
foreach(string file in Directory.EnumerateFiles(path, "*.js", SearchOption.TopDirectoryOnly).Select(Path.GetFileName)){ foreach(string file in Directory.EnumerateFiles(path, "*.js", SearchOption.TopDirectoryOnly).Select(Path.GetFileName)){
plugin.Environments |= PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetScriptFile(), StringComparison.Ordinal)); plugin.Environments |= PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetPluginScriptFile(), StringComparison.Ordinal));
} }
if (plugin.Environments == PluginEnvironment.None){ if (plugin.Environments == PluginEnvironment.None){
@@ -209,7 +212,7 @@ namespace TweetDuck.Plugins{
return false; return false;
} }
if (plugin.RequiredVersion.Length == 0 || !(plugin.RequiredVersion.Equals("*") || System.Version.TryParse(plugin.RequiredVersion, out Version _))){ if (plugin.RequiredVersion.Length == 0 || !(plugin.RequiredVersion == VersionWildcard || System.Version.TryParse(plugin.RequiredVersion, out Version _))){
error = "Plugin contains invalid version: "+plugin.RequiredVersion; error = "Plugin contains invalid version: "+plugin.RequiredVersion;
return false; return false;
} }
@@ -221,7 +224,7 @@ namespace TweetDuck.Plugins{
} }
private static bool CheckRequiredVersion(string requires){ private static bool CheckRequiredVersion(string requires){
return requires.Equals("*", StringComparison.Ordinal) || Program.Version >= new Version(requires); return requires == VersionWildcard || AppVersion >= new Version(requires);
} }
} }
} }

View File

@@ -8,22 +8,24 @@ namespace TweetDuck.Plugins{
sealed class PluginConfig{ sealed class PluginConfig{
public event EventHandler<PluginChangedStateEventArgs> InternalPluginChangedState; // should only be accessed from PluginManager public event EventHandler<PluginChangedStateEventArgs> InternalPluginChangedState; // should only be accessed from PluginManager
public IEnumerable<string> DisabledPlugins => Disabled; public IEnumerable<string> DisabledPlugins => disabled;
public bool AnyDisabled => Disabled.Count > 0; public bool AnyDisabled => disabled.Count > 0;
private readonly HashSet<string> Disabled = new HashSet<string>{ private static readonly string[] DefaultDisabled = {
"official/clear-columns", "official/clear-columns",
"official/reply-account" "official/reply-account"
}; };
private readonly HashSet<string> disabled = new HashSet<string>();
public void SetEnabled(Plugin plugin, bool enabled){ public void SetEnabled(Plugin plugin, bool enabled){
if ((enabled && Disabled.Remove(plugin.Identifier)) || (!enabled && Disabled.Add(plugin.Identifier))){ if ((enabled && disabled.Remove(plugin.Identifier)) || (!enabled && disabled.Add(plugin.Identifier))){
InternalPluginChangedState?.Invoke(this, new PluginChangedStateEventArgs(plugin, enabled)); InternalPluginChangedState?.Invoke(this, new PluginChangedStateEventArgs(plugin, enabled));
} }
} }
public bool IsEnabled(Plugin plugin){ public bool IsEnabled(Plugin plugin){
return !Disabled.Contains(plugin.Identifier); return !disabled.Contains(plugin.Identifier);
} }
public void Load(string file){ public void Load(string file){
@@ -32,14 +34,20 @@ namespace TweetDuck.Plugins{
string line = reader.ReadLine(); string line = reader.ReadLine();
if (line == "#Disabled"){ if (line == "#Disabled"){
Disabled.Clear(); disabled.Clear();
while((line = reader.ReadLine()) != null){ while((line = reader.ReadLine()) != null){
Disabled.Add(line); disabled.Add(line);
} }
} }
} }
}catch(FileNotFoundException){ }catch(FileNotFoundException){
disabled.Clear();
foreach(string identifier in DefaultDisabled){
disabled.Add(identifier);
}
Save(file); Save(file);
}catch(DirectoryNotFoundException){ }catch(DirectoryNotFoundException){
}catch(Exception e){ }catch(Exception e){
@@ -52,8 +60,8 @@ namespace TweetDuck.Plugins{
using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None), Encoding.UTF8)){ using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None), Encoding.UTF8)){
writer.WriteLine("#Disabled"); writer.WriteLine("#Disabled");
foreach(string disabled in Disabled){ foreach(string identifier in disabled){
writer.WriteLine(disabled); writer.WriteLine(identifier);
} }
} }
}catch(Exception e){ }catch(Exception e){

View File

@@ -9,12 +9,14 @@ using TweetDuck.Resources;
namespace TweetDuck.Plugins{ namespace TweetDuck.Plugins{
sealed class PluginManager{ sealed class PluginManager{
public const string PluginBrowserScriptFile = "plugins.browser.js";
public const string PluginNotificationScriptFile = "plugins.notification.js";
public const string PluginGlobalScriptFile = "plugins.js";
private const int InvalidToken = 0; private const int InvalidToken = 0;
private static readonly Dictionary<PluginEnvironment, string> PluginSetupScripts = new Dictionary<PluginEnvironment, string>(4){
{ PluginEnvironment.None, ScriptLoader.LoadResource("plugins.js") },
{ PluginEnvironment.Browser, ScriptLoader.LoadResource("plugins.browser.js") },
{ PluginEnvironment.Notification, ScriptLoader.LoadResource("plugins.notification.js") }
};
public string PathOfficialPlugins => Path.Combine(rootPath, "official"); public string PathOfficialPlugins => Path.Combine(rootPath, "official");
public string PathCustomPlugins => Path.Combine(rootPath, "user"); public string PathCustomPlugins => Path.Combine(rootPath, "user");
@@ -43,14 +45,7 @@ namespace TweetDuck.Plugins{
this.Bridge = new PluginBridge(this); this.Bridge = new PluginBridge(this);
Config.Load(configPath); Config.Load(configPath);
Config.InternalPluginChangedState += Config_InternalPluginChangedState; Config.InternalPluginChangedState += Config_InternalPluginChangedState;
Program.UserConfigReplaced += Program_UserConfigReplaced;
}
private void Program_UserConfigReplaced(object sender, EventArgs e){
Config.Load(configPath);
Reload();
} }
private void Config_InternalPluginChangedState(object sender, PluginChangedStateEventArgs e){ private void Config_InternalPluginChangedState(object sender, PluginChangedStateEventArgs e){
@@ -81,6 +76,8 @@ namespace TweetDuck.Plugins{
} }
public void Reload(){ public void Reload(){
Config.Load(configPath);
plugins.Clear(); plugins.Clear();
tokens.Clear(); tokens.Clear();
@@ -97,7 +94,17 @@ namespace TweetDuck.Plugins{
Reloaded?.Invoke(this, new PluginErrorEventArgs(loadErrors)); Reloaded?.Invoke(this, new PluginErrorEventArgs(loadErrors));
} }
public void ExecutePlugins(IFrame frame, PluginEnvironment environment, bool includeDisabled){ public void ExecutePlugins(IFrame frame, PluginEnvironment environment){
if (HasAnyPlugin(environment)){
ScriptLoader.ExecuteScript(frame, PluginSetupScripts[environment], environment.GetScriptIdentifier());
ScriptLoader.ExecuteScript(frame, PluginSetupScripts[PluginEnvironment.None], PluginEnvironment.None.GetScriptIdentifier());
ExecutePluginScripts(frame, environment);
}
}
private void ExecutePluginScripts(IFrame frame, PluginEnvironment environment){
bool includeDisabled = environment.IncludesDisabledPlugins();
if (includeDisabled){ if (includeDisabled){
ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GenerateConfig(Config), "gen:pluginconfig"); ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GenerateConfig(Config), "gen:pluginconfig");
} }

View File

@@ -8,7 +8,7 @@ namespace TweetDuck.Plugins{
public static string GeneratePlugin(string pluginIdentifier, string pluginContents, int pluginToken, PluginEnvironment environment){ public static string GeneratePlugin(string pluginIdentifier, string pluginContents, int pluginToken, PluginEnvironment environment){
return PluginGen return PluginGen
.Replace("%params", environment.GetScriptVariables()) .Replace("%params", environment.GetPluginScriptVariables())
.Replace("%id", pluginIdentifier) .Replace("%id", pluginIdentifier)
.Replace("%token", pluginToken.ToString()) .Replace("%token", pluginToken.ToString())
.Replace("%contents", pluginContents); .Replace("%contents", pluginContents);

View File

@@ -1,4 +1,4 @@
using CefSharp; using CefSharp;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
@@ -13,19 +13,16 @@ using TweetDuck.Core.Other;
using TweetDuck.Core.Other.Settings.Export; using TweetDuck.Core.Other.Settings.Export;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Data; using TweetDuck.Data;
using TweetDuck.Plugins;
using TweetDuck.Plugins.Events;
using TweetDuck.Updates; using TweetDuck.Updates;
using TweetLib.Communication;
namespace TweetDuck{ namespace TweetDuck{
static class Program{ static class Program{
public const string BrandName = "TweetDuck"; public const string BrandName = "TweetDuck";
public const string Website = "https://tweetduck.chylex.com"; public const string Website = "https://tweetduck.chylex.com";
public const string VersionTag = "1.8.4"; public const string VersionTag = "1.10.1";
public const string VersionFull = "1.8.4";
public static readonly Version Version = new Version(VersionTag);
public static readonly bool IsPortable = File.Exists("makeportable"); public static readonly bool IsPortable = File.Exists("makeportable");
public static readonly string ProgramPath = AppDomain.CurrentDomain.BaseDirectory; public static readonly string ProgramPath = AppDomain.CurrentDomain.BaseDirectory;
@@ -49,19 +46,21 @@ namespace TweetDuck{
private static readonly LockManager LockManager = new LockManager(Path.Combine(StoragePath, ".lock")); private static readonly LockManager LockManager = new LockManager(Path.Combine(StoragePath, ".lock"));
private static bool HasCleanedUp; private static bool HasCleanedUp;
public static UserConfig UserConfig { get; private set; } public static UserConfig UserConfig { get; private set; }
public static SystemConfig SystemConfig { get; private set; } public static SystemConfig SystemConfig { get; private set; }
public static Reporter Reporter { get; } public static Reporter Reporter { get; }
public static CultureInfo Culture { get; } public static CultureInfo Culture { get; }
public static event EventHandler UserConfigReplaced;
static Program(){ static Program(){
Culture = CultureInfo.CurrentCulture; Culture = CultureInfo.CurrentCulture;
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture; CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
#if DEBUG
CultureInfo.DefaultThreadCurrentUICulture = Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-us"); // force english exceptions
#endif
Reporter = new Reporter(ErrorLogFilePath); Reporter = new Reporter(ErrorLogFilePath);
Reporter.SetupUnhandledExceptionHandler("TweetDuck Has Failed :("); Reporter.SetupUnhandledExceptionHandler("TweetDuck Has Failed :(");
} }
@@ -71,8 +70,8 @@ namespace TweetDuck{
Application.EnableVisualStyles(); Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false); Application.SetCompatibleTextRenderingDefault(false);
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore"); WindowRestoreMessage = Comms.RegisterMessage("TweetDuckRestore");
SubProcessMessage = NativeMethods.RegisterWindowMessage("TweetDuckSubProcess"); SubProcessMessage = Comms.RegisterMessage("TweetDuckSubProcess");
if (!WindowsUtils.CheckFolderWritePermission(StoragePath)){ if (!WindowsUtils.CheckFolderWritePermission(StoragePath)){
FormMessage.Warning("Permission Error", "TweetDuck does not have write permissions to the storage folder: "+StoragePath, FormMessage.OK); FormMessage.Warning("Permission Error", "TweetDuck does not have write permissions to the storage folder: "+StoragePath, FormMessage.OK);
@@ -114,13 +113,16 @@ namespace TweetDuck{
return; return;
} }
} }
ReloadConfig(); UserConfig = UserConfig.Load(UserConfigFilePath);
SystemConfig = SystemConfig.Load(SystemConfigFilePath); SystemConfig = SystemConfig.Load(SystemConfigFilePath);
if (Arguments.HasFlag(Arguments.ArgImportCookies)){ if (Arguments.HasFlag(Arguments.ArgImportCookies)){
ExportManager.ImportCookies(); ExportManager.ImportCookies();
} }
else if (Arguments.HasFlag(Arguments.ArgDeleteCookies)){
ExportManager.DeleteCookies();
}
if (Arguments.HasFlag(Arguments.ArgUpdated)){ if (Arguments.HasFlag(Arguments.ArgUpdated)){
WindowsUtils.TryDeleteFolderWhenAble(InstallerPath, 8000); WindowsUtils.TryDeleteFolderWhenAble(InstallerPath, 8000);
@@ -148,18 +150,13 @@ namespace TweetDuck{
Application.ApplicationExit += (sender, args) => ExitCleanup(); Application.ApplicationExit += (sender, args) => ExitCleanup();
PluginManager plugins = new PluginManager(PluginPath, PluginConfigFilePath);
plugins.Reloaded += plugins_Reloaded;
plugins.Executed += plugins_Executed;
plugins.Reload();
UpdaterSettings updaterSettings = new UpdaterSettings{ UpdaterSettings updaterSettings = new UpdaterSettings{
AllowPreReleases = Arguments.HasFlag(Arguments.ArgDebugUpdates), AllowPreReleases = Arguments.HasFlag(Arguments.ArgDebugUpdates),
DismissedUpdate = UserConfig.DismissedUpdate, DismissedUpdate = UserConfig.DismissedUpdate,
InstallerDownloadFolder = InstallerPath InstallerDownloadFolder = InstallerPath
}; };
FormBrowser mainForm = new FormBrowser(plugins, updaterSettings); FormBrowser mainForm = new FormBrowser(updaterSettings);
Application.Run(mainForm); Application.Run(mainForm);
if (mainForm.UpdateInstallerPath != null){ if (mainForm.UpdateInstallerPath != null){
@@ -174,18 +171,6 @@ namespace TweetDuck{
} }
} }
private static 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);
}
}
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 static string GetDataStoragePath(){ private static string GetDataStoragePath(){
string custom = Arguments.GetValue(Arguments.ArgDataFolder, null); string custom = Arguments.GetValue(Arguments.ArgDataFolder, null);
@@ -204,11 +189,6 @@ namespace TweetDuck{
} }
} }
public static void ReloadConfig(){
UserConfig = UserConfig.Load(UserConfigFilePath);
UserConfigReplaced?.Invoke(UserConfig, new EventArgs());
}
public static void ResetConfig(){ public static void ResetConfig(){
try{ try{
File.Delete(UserConfigFilePath); File.Delete(UserConfigFilePath);
@@ -217,8 +197,8 @@ namespace TweetDuck{
Reporter.HandleException("Configuration Reset Error", "Could not delete configuration files to reset the options.", true, e); Reporter.HandleException("Configuration Reset Error", "Could not delete configuration files to reset the options.", true, e);
return; return;
} }
ReloadConfig(); UserConfig.Reload();
} }
public static void Restart(params string[] extraArgs){ public static void Restart(params string[] extraArgs){

View File

@@ -34,8 +34,8 @@ using TweetDuck;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion(Program.VersionFull)] [assembly: AssemblyVersion(Program.VersionTag)]
[assembly: AssemblyFileVersion(Program.VersionFull)] [assembly: AssemblyFileVersion(Program.VersionTag)]
[assembly: NeutralResourcesLanguage("en")] [assembly: NeutralResourcesLanguage("en")]

View File

@@ -1,11 +1,64 @@
# Support
[Follow TweetDuck on Twitter](https://twitter.com/TryTweetDuck) &nbsp;|&nbsp; [Support via PayPal](https://paypal.me/chylex) &nbsp;|&nbsp; [Support via Patreon](https://www.patreon.com/chylex)
<a target='_blank' rel='nofollow' href='https://app.codesponsor.io/link/2W76v7hRmUe7NfaeUKq9K8Wq/chylex/TweetDuck'>
<img alt='Sponsor' width='888' height='68' src='https://app.codesponsor.io/embed/2W76v7hRmUe7NfaeUKq9K8Wq/chylex/TweetDuck.svg' />
</a>
# Build Instructions # Build Instructions
The program was built using Visual Studio 2017. After opening the solution, make sure you have **CefSharp.WinForms** and **Microsoft.VC120.CRT.JetBrains** included - if not, download them using NuGet. ### Setup
The program was built using Visual Studio 2017. Before opening the solution, please make sure you have the following workloads and components installed (optional components that are not listed can be deselected to save space):
* **.NET desktop development**
* .NET Framework 4 4.6 development tools
* **Desktop development with C++**
* VC++ 2017 v141 toolset
After opening the solution, download the following NuGet packages by right-clicking on the solution and selecting **Restore NuGet Packages**, or manually running these commands in the **Package Manager Console**:
``` ```
PM> Install-Package CefSharp.WinForms -Version 57.0.0 PM> Install-Package CefSharp.WinForms -Version 57.0.0
PM> Install-Package Microsoft.VC120.CRT.JetBrains PM> Install-Package Microsoft.VC120.CRT.JetBrains
``` ```
After building, run either `_postbuild.bat` if you want to package the files yourself, or `bld/RUN BUILD.bat` to generate installer files using Inno Setup (make sure the Inno Setup binaries are on your PATH). ### Debug
Built files are then available in **bin/x86**, installer files are generated in **bld/Output**. If you decide to release a custom version publicly, please make it clear that it is not the original TweetDuck. It is recommended to create a separate data folder for debugging, otherwise you will not be able to run TweetDuck while debugging the solution.
To do that, open **TweetDuck Properties**, click the **Debug** tab, make sure your **Configuration** is set to `Active (Debug)` (or just `Debug`), and insert this into the **Command line arguments** field:
```
-datafolder TweetDuckDebug
```
### Build
To make a release build of TweetDuck, open **Batch Build**, tick all `Release` configurations except for the `UnitTest` project (otherwise the build will fail), and click **Rebuild**. Check the status bar to make sure it says **Rebuild All succeeded**; if not, open the **Output** view and see which part of the build failed.
After the build succeeds, the **bin/x86/Release** folder will contain files intended for distribution (no debug symbols or other unnecessary files). You may package these files yourself, or see the [Installers](#Installers) section for automated installer generation.
If you decide to release a custom version publicly, please make it clear that it is not an official release of TweetDuck.
### Installers
TweetDuck uses **Inno Setup** to automate the creation of installers. First, download and install [InnoSetup QuickStart Pack](http://www.jrsoftware.org/isdl.php) (non-unicode; editor and encryption support not required) and the [Inno Download Plugin](https://code.google.com/archive/p/inno-download-plugin).
Next, add the Inno Setup installation folder (usually `C:\Program Files (x86)\Inno Setup 5`) into your **PATH** environment variable. You may need to restart File Explorer for the change to take place.
Now you can generate installers after a build by running **bld/RUN BUILD.bat**. Note that despite the name, this will only package the files, you still need to run the [build](#Build) in Visual Studio!
After the window closes, three installers will be generated inside the **bld/Output** folder:
* **TweetDuck.exe**
* This is the main installer that creates entries in the Start Menu & Programs and Features, and an optional desktop icon
* **TweetDuck.Update.exe**
* This is a lightweight update installer that only contains the most important files that usually change across releases
* It will automatically download and apply the full installer if the user's current version of CEF does not match (the download link is in `gen_upd.iss` and points to this repository by default)
* **TweetDuck.Portable.exe**
* This is a portable installer that does not need administrator privileges
* It automatically creates a `makeportable` file in the program folder, which forces TweetDuck to run in portable mode
Note: There is a small chance you will see a resource error when running `RUN BUILD.bat`. If that happens, close the console window (which will terminate all Inno Setup processes and leave corrupted installer files in the output folder), and run it again.
### Code Notes
There are many references to the official TweetDuck website and this repository in the code and installers, so if you plan to release your own version, make sure to search for `tweetduck.chylex.com` and `github.com` in the whole repository and replace them appropriately.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

404
Resources/Guide/index.html Normal file
View File

@@ -0,0 +1,404 @@
<!DOCTYPE html>
<html>
<head>
<title>TweetDuck - Guide</title>
<meta charset="utf-8">
<meta name="robots" content="index,follow">
<meta name="author" content="chylex">
<meta name="description" content="TweetDuck guide">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="img/icon.ico">
<link rel="stylesheet" href="style.css" type="text/css">
<script type="text/javascript" src="script.js" async></script>
</head>
<body>
<div id="guide">
<section>
<article>
<h2>General</h2>
<details>
<summary id="main-menu">How to open the main menu</summary>
<div>
<p>The main menu gives you access to the following items:</p>
<img src="img/app-menu.png" alt="">
<p>There are two ways to open the main menu:</p>
<ul>
<li>Click <em>Settings</em> in the left panel, then select <em>TweetDuck</em></li>
<li><em>Right-click anywhere</em> and you will either see the listed options, or a <em>TweetDuck</em> entry that contains these options</li>
</ul>
<img src="img/settings-dropdown.png" alt="">
</div>
</details>
<details>
<summary id="customize-theme">How to customize the theme and layout</summary>
<div>
<ol>
<li>Click <em>Settings</em> in the left panel</li>
<li>Continue to <em>Edit layout &amp; design</em></li>
<li>Now you can customize many aspects of the website; note that unlike the default TweetDeck settings, the column width and font size can be configured here in much greater detail</li>
</ol>
<img src="img/settings-editdesign.png" alt="">
<p>This is done using an official plugin called <em>Edit layout &amp; design</em>, which is enabled by default. If the plugin is disabled, you can still access the default TweetDeck settings:</p>
<ol>
<li>Click <em>Settings</em> on the bottom left</li>
<li>Continue to <em>Settings</em> (again)</li>
<li>Here you can customize the theme, column width, and font size</li>
</ol>
<img src="img/settings-dropdown.png" alt="">
</div>
</details>
<details>
<summary id="emoji">How to add emoji to tweets</summary>
<div>
<p>When writing a new tweet, click the heart icon to open an emoji picker. If you're writing a reply, click the <em>Popout</em> icon to bring the reply into the New Tweet panel.</p>
<p>Then you can immediately type into the <em>Search</em> field, which accepts keywords separated by space. Pressing <em>Enter</em> in the search field (when not empty) will insert the first result into your tweet. Pressing <em>Escape</em> closes the emoji picker.</p>
<p>You can also use your mouse to change the skin tone, scroll through the emoji, and click on the emoji to insert them.</p>
<p>Typing <em>:emoji_name:</em> in the New Tweet panel will automatically search for an emoji using those keywords. If a single emoji is found, it will be inserted into the tweet (press <em>Backspace</em> or <em>Escape</em> to revert it). If multiple emojis are found, it will open the emoji picker where you can refine the search, or press <em>Escape</em> to go back.</p>
<p>Emoji are provided by an official plugin called <em>Emoji keyboard</em>, which is enabled by default. The heart icon will not show if the plugin is disabled.</p>
<img src="img/new-tweet-emoji.png" alt="">
</div>
</details>
<details>
<summary id="templates">How to use tweet templates</summary>
<div>
<p>To create a simple template to use when writing a new tweet or reply:</p>
<ol>
<li>Click <em>Manage templates</em> in the New Tweet panel; if you're writing a reply, click the <em>Popout</em> icon first to bring the reply into the large panel</li>
<li>Click <em>New template</em> on the bottom right</li>
<li>Fill in the template name and contents</li>
<li>Click <em>Confirm</em> to create the template</li>
</ol>
<p>After you create a template, it will be added to the list. There are two icons next to each entry:</p>
<ul>
<li>Click the <em>pencil icon to edit</em> the template</li>
<li>Click the <em>cross icon to delete</em> the template</li>
</ul>
<p>To use the template, <em>click the template name</em> to replace your current tweet text with the template, or click while holding <em>Shift</em> to append the template to your tweet instead. You can use the Shift+click functionality to quickly chain multiple templates.</p>
<img src="img/new-tweet-template-basic.png" alt="">
<p>When writing a template, you can use special <em>tokens</em> listed in the <em>Advanced</em> section. Here is an example of one of the tokens:</p>
<img src="img/new-tweet-template-advanced.png" alt="">
</div>
</details>
<details>
<summary id="upload-from-clipboard">How to upload images from clipboard</summary>
<div>
<p>When writing a tweet/reply, press <em>Ctrl+V</em> to upload an image from clipboard. You can use this to quickly paste a selection from an image editor such as Paint, or after copying an image in your browser.</p>
<p>Make sure you're in the tweet input field before you press Ctrl+V, otherwise the keyboard shortcut won't trigger.</p>
<p>Note that this will only work when your clipboard contains the image itself; it will not work if you copy a file or URL.</p>
</div>
</details>
</article>
<article>
<h2>Columns</h2>
<details>
<summary id="copy-tweet-links">How to copy links to tweets</summary>
<div>
<p>When you right-click anywhere inside a tweet, you will be given these options:</p>
<ul>
<li>Open tweet in browser</li>
<li><em>Copy tweet address</em></li>
<li>Screenshot tweet to clipboard</li>
</ul>
<p>If the tweet contains a quote, you will also be able to directly open the quote or copy its address.</p>
<p>Note that these options will not appear when you right-click a private message.</p>
</div>
</details>
<details>
<summary id="download-media">How to download images and videos</summary>
<div>
<p>When you right-click an image or a video thumbnail with a purple play button, you will be given these options:</p>
<ul>
<li>Open image/video in browser</li>
<li>Copy image/video address</li>
<li><em>Save image/video as...</em></li>
</ul>
<p>TweetDuck will attempt to fetch the highest quality image when you click any of these options. You can disable that by going to the <em>main menu</em>, selecting <em>Options</em>, and then unchecking <em>Best Image Quality</em>.</p>
<p>Whenever possible, the username and quality are included in the filename by default, for convenience. After you select a folder and click Save, the image/video will be downloaded in the background. There is no notification for when the download finishes, but you will be notified if it fails.</p>
</div>
</details>
<details>
<summary id="screenshot-tweets">How to take screenshots of individual tweets</summary>
<div>
<p>When you right-click anywhere inside a tweet, you will be given these options:</p>
<ul>
<li>Open tweet in browser</li>
<li>Copy tweet address</li>
<li><em>Screenshot tweet to clipboard</em></li>
</ul>
<p>Taking a screenshot may take several seconds, especially if it contains large images or previews. After taking a screenshot, you can paste it into an image editor such as Paint.</p>
<p>Note that these options will not appear when you right-click a private message.</p>
</div>
</details>
<details>
<summary id="clear-columns">How to clear and restore column contents</summary>
<div>
<p>TweetDeck lets you clear a column, which can help keep things organized by hiding tweets you have already read. To clear a column, click the <em>slider icon</em> on top of the column, and then click the <em>Clear</em> button.</p>
<p>If you need to revert this decision and restore a column, normally you would need to delete and re-create it, but in TweetDuck you can simply hold Shift which turns the Clear button into a <em>Restore</em> button.</p>
<img src="img/column-clear-preferences.png" alt="">
<p>If you clear columns frequently, you can enable an official plugin that lets you clear columns much quicker:</p>
<ol>
<li>Open the <em>main menu</em>, and select <em>Plugins</em> to open the list of available plugins</li>
<li>Find an entry that says <em>Clear columns</em>, it will be somewhere near the bottom as the plugin is disabled by default</li>
<li>Click <em>Enable</em> on the right side to enable the plugin and reload the browser</li>
</ol>
<p>Now you can clear...</p>
<ul>
<li>...a single column by clicking the <em>droplet icon</em> on top of each column</li>
<li>...a single column by holding the <em>1-9 number key</em> and pressing <em>Delete</em></li>
<li>...all columns by clicking <em>Clear columns</em> in the left panel</li>
<li>...all columns by pressing <em>Alt+Delete</em></li>
</ul>
<img src="img/column-clear-header.png" alt="">
<p>As mentioned before, hold Shift with any of these to restore the columns instead. Note that some keyboard layouts require using the Shift key when pressing number keys; if that is the case for you, please use the numpad or mouse instead.</p>
</div>
</details>
</article>
<article>
<h2>Notifications</h2>
<details>
<summary id="enable-notifications">How to enable desktop or sound notifications</summary>
<div>
<p>New columns have disabled notifications by default. To enable them, click the <em>slider icon</em> on top of the column, and expand the <em>Preferences</em> section.</p>
<p>Now you can toggle either, or both of the notification options:</p>
<img src="img/column-preferences.png" alt="">
</div>
</details>
<details>
<summary id="move-resize-notifications">How to move or resize desktop notifications</summary>
<div>
<p>Open the <em>main menu</em>, select <em>Options</em>, and then click the <em>Notifications</em> tab. Here, you can customize many aspects of desktop notifications.</p>
<p>Scroll down to the <em>Location</em> section where you can customize where the notification shows up. You can either pick one of the 4 corners of your screen and the distance from the corner, or select <em>Custom</em> and then you'll be able to freely move the example notification window.</p>
<img src="img/options-notifications-location.png" alt="">
<p>Scroll down to the <em>Size</em> section to customize the size of the notification window. By default, TweetDuck sets the size based on your font size setting, the zoom level you can customize in the General tab, and your system DPI. If you pick <em>Custom</em>, you will be able to freely resize the example notification window.</p>
<img src="img/options-notifications-size.png" alt="">
<p>Note that moving and resizing the notification only works while you're inside the Options dialog, that is to prevent accidental clicks messing up your settings.</p>
</div>
</details>
<details>
<summary id="sound-notifications">How to customize sound notifications</summary>
<div>
<p>Open the <em>main menu</em>, select <em>Options</em>, and then click the <em>Sounds</em> tab. Here, you can pick a sound file that will be used instead of the default TweetDeck sound notification.</p>
<p>Keep in mind that you're only linking to the sound file, so make sure not to delete the file afterwards, otherwise TweetDuck won't find it anymore.</p>
<img src="img/options-sounds.png" alt="">
<p>If you're unable to select MP3 files or other common audio file types, please ensure that you have Windows Media Player installed on your system, otherwise you will only be able to select basic WAV files.</p>
</div>
</details>
<details>
<summary id="mute-notifications">How to temporarily mute all notifications</summary>
<div>
<p>There are two ways you can mute/unmute notifications:</p>
<ul>
<li>Open the <em>main menu</em> and click <em>Mute notifications</em></li>
<li>If you've enabled the <em>tray icon</em>, right-click it and then click <em>Mute notifications</em></li>
</ul>
<p>The option persists across restarts &ndash; if you mute notifications and then restart TweetDuck, don't forget to unmute the notifications again.</p>
<p>Unmuting notifications will display all missed desktop notifications (unless TweetDuck was restarted in the meantime).</p>
</div>
</details>
</article>
</section>
<section>
<article>
<h2>Options</h2>
<details>
<summary id="configure-tweetduck">How to configure TweetDuck</summary>
<div>
<p>Open the <em>main menu</em> and select <em>Options</em>. Here you can configure various parts of TweetDuck; the dialog is split into several tabs:</p>
<ul>
<li><em>General</em> tab for user interface, zoom, and update options</li>
<li><em>System Tray</em> tab to enable and configure the tray icon</li>
<li><em>Notifications</em> tab to configure desktop notifications</li>
<li><em>Sounds</em> tab to set a custom sound notification</li>
<li><em>Advanced</em> tab for highly technical options</li>
</ul>
<p>You can move your cursor over most options to display a <em>tooltip with a detailed explanation</em> of what that option does.</p>
</div>
</details>
<details>
<summary id="manage-plugins">How to view and manage plugins</summary>
<div>
<p>TweetDuck has several offical plugins that extend the website and notifications with new functionality.</p>
<p>Open the <em>main menu</em> and select <em>Plugins</em> to open the plugin list. Here you can see what each plugin does, and enable/disable them individually. </p>
<p>If you want to install a custom plugin, click <em>Open Plugin Folder</em>. A plugin is a folder that contains a <em>.meta</em> file and several others, make sure you copy and paste the folder itself into the opened plugin folder. To verify that you installed it correctly, click <em>Reload All</em> (which also reloads the website) and the plugin should appear.</p>
<p>Please, be careful when installing new plugins, and ensure that you get them from trustworthy sources. If you're unsure about a plugin, feel free to <a href="https://github.com/chylex/TweetDuck/issues/new" rel="nofollow">create an issue</a> and upload the plugin there (<a href="https://github.com" rel="nofollow">GitHub</a> account required).</p>
</div>
</details>
<details>
<summary id="manage-profile">How to backup your profile or move it to another computer</summary>
<div>
<ol>
<li>Open the <em>main menu</em>, select <em>Options</em>, and click <em>Manage Options</em> on the bottom left</li>
<li>Select <em>Export profile</em> and proceed with <em>Next</em></li>
<li>Select items you want to save in your profile (note that <em>Plugin Data</em> includes data from official plugins, such as those that let you customize the website or create tweet templates)</li>
<li>Click <em>Export Profile</em></li>
</ol>
<img src="img/options-manage.png" alt="" style="margin-right:6px">
<img src="img/options-manage-export.png" alt="">
<p>You can save your profile into a cloud storage (Dropbox, Google Drive, etc.) or an external drive, for example. When you want to restore it, follow the same steps but select <em>Import profile</em> and then select the file instead.</p>
<p>When importing a profile, you will be again able to pick which items you want to restore. You can for example export a full profile including your login session, but then only import program options and plugin data if you want to login to a different account.</p>
</div>
</details>
<details>
<summary id="restore-options">How to restore default options</summary>
<div>
<ol>
<li>Open the <em>main menu</em>, select <em>Options</em>, and click <em>Manage Options</em> on the bottom left</li>
<li>Select <em>Restore defaults</em> and proceed with <em>Next</em></li>
<li>Select items you want reset (note that <em>Plugin Data</em> includes data from official plugins, such as those that let you customize the website or create tweet templates)</li>
<li>Click <em>Restore Defaults</em></li>
</ol>
<img src="img/options-manage.png" alt="" style="margin-right:6px">
<img src="img/options-manage-reset.png" alt="">
</div>
</details>
</article>
<article>
<h2>Efficiency</h2>
<details>
<summary id="keyboard-shortcuts">How to use keyboard shortcuts</summary>
<div>
<ol>
<li>Click <em>Settings</em> in the left panel</li>
<li>Continue to <em>Keyboard shortcuts</em></li>
<li>Here you can see most available keyboard shortcuts you can use in the browser window</li>
</ol>
<img src="img/settings-dropdown.png" alt="">
<p>You can also often hold Ctrl or Shift to trigger alternative actions:</p>
<ul>
<li>When <em>selecting accounts</em>, hold <em>Shift</em> to select multiple accounts (can be configured in the Options)</li>
<li>When <em>clearing columns</em>, hold <em>Shift</em> to restore the column instead</li>
<li>When <em>clicking video thumbnails</em>, hold <em>Ctrl</em> to open them in your browser</li>
</ul>
<p>Finally, if you click into a desktop notification window, you can use these keyboard shortcuts:</p>
<ul>
<li><em>Enter</em> to skip the current notification</li>
<li><em>Escape</em> to close the window (skips all notifications in the queue)</li>
<li><em>Space</em> to pause/unpause the timer</li>
</ul>
</div>
</details>
<details>
<summary id="extra-mouse-buttons">How to use the forward / back mouse buttons</summary>
<div>
<p>If you have a mouse that supports the forward and back buttons, you can use them in both the browser and a desktop notification. All you need to do is move the cursor over the window (even if it's not focused), and press one of the buttons.</p>
<p>In the browser:</p>
<ul>
<li>Press <em>forward</em> over a <em>tweet to open it in detail view</em> (unlike clicking, this will not trigger links or media thumbnails)</li>
<li>Press <em>back</em> anywhere to <em>close modal dialogs</em> or the <em>New Tweet panel</em>, or over a <em>column to return back from detail view</em> (if there are no dialogs or panels open, pressing the button outside a column will trigger it for all columns at once)</li>
</ul>
<p>In the desktop notification:</p>
<ul>
<li>Press <em>forward</em> to skip the current notification</li>
<li>Press <em>back</em> to close the window (skips all notifications in the queue)</li>
</ul>
</div>
</details>
<details>
<summary id="pin-new-tweet">How to keep the New Tweet panel open</summary>
<div>
<p>Open the New Tweet panel, and click the <em>pin icon</em> on top. When the pin points to the left, the panel will stay open after tweeting or restarting TweetDuck.</p>
<img src="img/new-tweet-pin.png" alt="">
</div>
</details>
<details>
<summary id="popout-replies">How to instantly popout replies</summary>
<div>
<p><em>Middle-click the reply icon</em> to instantly open your reply in the New Tweet panel.</p>
<p>Middle-clicks are usually done by pressing your mouse wheel as if it was a button. When using a laptop touchpad or certain mice, the ways of triggering a middle click vary.</p>
</div>
</details>
<details>
<summary id="reply-account">How to change which account will be pre-selected for replies</summary>
<div>
<p>By default, TweetDeck pre-selects the account mentioned in the column header. The ability to change this is provided by an official plugin which is disabled by default, as it's a bit more difficult to setup, but it can be very powerful. To enable the plugin:</p>
<ol>
<li>Open the <em>main menu</em> and select <em>Plugins</em></li>
<li>Find an entry that says <em>Custom reply account</em>, it will be somewhere near the bottom as the plugin is disabled by default</li>
<li>Click <em>Enable</em> on the right side to enable the plugin</li>
</ol>
<p>After you enable the plugin, it will use your preferred account for all replies by default. If that's your intention, you can simply enable the plugin and leave it, otherwise continue reading:</p>
<ol>
<li>Click <em>Configure</em> next to the plugin to open a folder with the configuration file
<li>Open <em>configuration.js</em> in a text editor that can edit and save JavaScript or any pure text files, therefore office suits or WordPad are not suitable; if you don't have any specific editor, use Notepad.</li>
<li>The configuration file includes very detailed instructions &ndash; you can use one of the <em>presets</em>, a <em>specific account</em> for all replies, or use JavaScript to <em>fully customize</em> the reply behavior</li>
</ol>
<p>After editing the configuration, return back to Plugins and click <em>Reload All</em> on the bottom left. Now you can close Plugins and test if replies work the way you want.</p>
<p>Note that this will not affect the Messages column, that one will always pre-select the account which received the private message.</p>
</div>
</details>
</article>
<article>
<h2>Advanced</h2>
<details>
<summary id="dev-tools">How to open Chrome Dev Tools</summary>
<div>
<ol>
<li>Open the <em>main menu</em>, select <em>Options</em>, and click the <em>Advanced</em> tab</li>
<li>Click <em>Open Program Folder</em></li>
<li>Download <a href="https://github.com/chylex/TweetDuck/raw/master/bld/Resources/devtools_resources.pak" rel="nofollow">devtools_resources.pak</a> and place it into the opened folder</li>
<li>Click <em>Restart the Program</em></li>
<li>Now, open the <em>main menu</em> again and you should see <em>Open dev tools</em>; you can also right-click inside a notification and see the same option (make sure to pause the notification first by clicking Freeze in the context menu)</li>
</ol>
</div>
</details>
<details>
<summary id="custom-css">How to customize styles using CSS</summary>
<div>
<ol>
<li>Open the <em>main menu</em>, select <em>Options</em>, and click the <em>Advanced</em> tab</li>
<li>Click <em>Edit CSS</em></li>
</ol>
<p>Now you can write custom CSS into the <em>Browser</em> and <em>Notification</em> sections.</p>
<p>Note that the Browser section will immediately take effect as you type. You can also still access the browser and Dev Tools, as the dialog does not block the browser window.</p>
<p>Basic knowledge of HTML and CSS is recommended. Mozilla Development Network has a huge library of resources on both <a href="https://developer.mozilla.org/en-US/Learn/Getting_started_with_the_web/HTML_basics" rel="nofollow">HTML</a> and <a href="https://developer.mozilla.org/en-US/Learn/Getting_started_with_the_web/CSS_basics" rel="nofollow">CSS</a>.</p>
</div>
</details>
<details>
<summary id="plugin-development">How to develop plugins</summary>
<div>
<p>Before creating a plugin, you should have at least basic knowledge of <a href="https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web" rel="nofollow">web development</a> (namely HTML, CSS, JavaScript), and several JS libraries TweetDeck uses, such as <a href="https://learn.jquery.com" rel="nofollow">jQuery 2</a>, <a href="https://mustache.github.io" rel="nofollow">Mustache</a>, <a href="https://github.com/ded/klass" rel="nofollow">klass</a>, and <a href="https://flightjs.github.io/" rel="nofollow">Flight</a>.</p>
<p>Working with the TweetDeck source code involves a lot of reverse-engineering. You can visit <a href="https://github.com/DeckHack/discoveries" rel="nofollow">DeckHack</a> which is working to document its source code, and view <a href="https://github.com/chylex/TweetDuck/tree/master/Resources/Scripts" rel="nofollow">TweetDuck sources</a> which includes all scripts and official plugins.</p>
<p>Once you're ready to start creating your own plugins, visit the official <a href="https://github.com/chylex/TweetDuck/wiki/Plugins%EA%9E%89-1.-The-Basics" rel="nofollow">plugin development documentation</a> which will explain the structure of a plugin, and show you all TweetDuck-specific functionality you cannot normally use in browsers.</p>
</div>
</details>
</article>
</section>
</div>
</body>
</html>

84
Resources/Guide/script.js Normal file
View File

@@ -0,0 +1,84 @@
var init = function(){
if (!("open" in document.getElementsByTagName("details")[0])){
var elements = document.getElementsByTagName("details");
var onClick = function(e){
var summary = e.target;
var parent = e.target.parentElement;
var contents = e.target.nextElementSibling;
if (parent.hasAttribute("open")){
parent.removeAttribute("open");
summary.setAttribute("aria-expanded", "false");
contents.setAttribute("aria-hidden", "true");
contents.style.display = "none";
}
else{
parent.setAttribute("open", "");
summary.setAttribute("aria-expanded", "true");
contents.setAttribute("aria-hidden", "false");
contents.style.display = "block";
}
};
var onKey = function(e){
if (e.keyCode === 13 || e.keyCode === 32){
onClick(e);
}
};
for(var index = 0; index < elements.length; index++){
var ele = elements[index];
if (ele.childElementCount === 2){
var summary = ele.children[0];
var contents = ele.children[1];
ele.style.display = "block";
ele.setAttribute("role", "group");
summary.setAttribute("role", "button");
summary.setAttribute("aria-expanded", "false");
summary.setAttribute("tabindex", 0);
summary.addEventListener("click", onClick);
summary.addEventListener("keydown", onKey);
contents.setAttribute("aria-hidden", "true");
contents.style.display = "none";
}
}
}
else if ("WebkitAppearance" in document.documentElement.style){
var elements = document.getElementsByTagName("summary");
var onMouseDown = function(e){
e.target.classList.add("webkit-workaround");
};
var onMouseUp = function(e){
e.target.classList.remove("webkit-workaround");
e.target.blur();
};
for(var index = 0; index < elements.length; index++){
elements[index].addEventListener("mousedown", onMouseDown);
elements[index].addEventListener("mouseup", onMouseUp);
}
}
if (location.hash.length > 1){
var element = document.getElementById(location.hash.substring(1));
if (element && element.tagName === "SUMMARY"){
element.click();
element.scrollIntoView(true);
}
}
};
if (document.readyState !== "loading"){
init();
}
else{
document.addEventListener("DOMContentLoaded", init);
}

132
Resources/Guide/style.css Normal file
View File

@@ -0,0 +1,132 @@
body {
margin: 0;
font-family: "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, Verdana, sans-serif;
background-color: #222;
color: #ddd;
overflow-y: scroll;
}
#guide {
width: 100%;
max-width: 1440px;
margin: 0 auto;
display: flex;
flex-wrap: wrap;
align-self: stretch;
}
#guide section {
flex: 1 1 0;
margin: 0 24px;
min-width: 360px;
display: flex;
flex-direction: column;
}
@media(max-width: 408px) {
#guide section {
min-width: calc(100vw - 48px);
}
}
#guide h2 {
margin: 20px 0 10px;
font-size: 32px;
cursor: default;
}
#guide details {
transition: background-color 0.2s ease;
}
#guide details[open] {
background-color: rgba(255, 255, 255, 0.05);
}
#guide summary {
display: inline-block;
margin: 0 0 11px;
padding: 4px 10px;
font-size: 19px;
font-weight: bold;
color: #afdfff;
cursor: pointer;
opacity: 0.8;
transition: opacity 0.15s ease;
}
#guide summary::-webkit-details-marker {
display: none;
}
#guide summary.webkit-workaround {
outline: none;
}
#guide summary::before {
content: "▶";
display: inline-block;
font-size: 13px;
vertical-align: top;
margin-right: 6px;
padding-top: 2px;
transform-origin: 35% 50%;
transition: transform 0.1s ease;
}
#guide summary:hover, #guide details[open] summary {
opacity: 1;
}
#guide details[open] summary::before {
transform: rotateZ(90deg) translate(0.5px, -3px);
}
#guide details > div {
margin-bottom: 22px;
padding: 0 25px 20px;
font-size: 17px;
}
#guide details > div > *:first-child {
margin-top: 0;
}
#guide details > div > *:last-child {
margin-bottom: 0;
}
em {
color: #f8d88b;
font-style: normal;
}
a {
color: #8bc6f8;
opacity: 0.85;
}
a:hover {
opacity: 1;
}
p, li {
line-height: 130%;
}
ul {
padding-left: 26px;
}
ol {
padding-left: 18px;
}
li {
margin-bottom: 6px;
}
img {
border: 2px groove rgba(255, 255, 255, 0.6);
max-width: 100%;
}

View File

@@ -53,9 +53,7 @@ enabled(){
}; };
this.eventKeyDown = function(e){ this.eventKeyDown = function(e){
if (!(document.activeElement === null || document.activeElement === document.body)){ return if !(document.activeElement === null || document.activeElement === document.body);
return;
}
updateShiftState(e.shiftKey); updateShiftState(e.shiftKey);

View File

@@ -8,10 +8,10 @@ Edit layout & design
chylex chylex
[version] [version]
1.1.1 1.1.4
[website] [website]
https://tweetduck.chylex.com https://tweetduck.chylex.com
[requires] [requires]
1.7 1.10.2

View File

@@ -102,23 +102,21 @@ enabled(){
// settings click event // settings click event
this.onSettingsMenuClickedEvent = () => { this.onSettingsMenuClickedEvent = () => {
if (this.htmlModal === null || this.config === null){ return if this.htmlModal === null || this.config === null;
return;
}
setTimeout(() => { setTimeout(() => {
let menu = $(".js-dropdown-content").children("ul").first(); let menu = $(".js-dropdown-content").children("ul").first();
if (menu.length === 0)return; return if menu.length === 0;
let itemTD = menu.children("[data-std]").first(); let itemTD = menu.children("[data-tweetduck]").first();
if (itemTD.length === 0)return; return if itemTD.length === 0;
if (!itemTD.prev().hasClass("drp-h-divider")){ if (!itemTD.prev().hasClass("drp-h-divider")){
itemTD.before('<li class="drp-h-divider"></li>'); itemTD.before('<li class="drp-h-divider"></li>');
} }
let itemEditDesign = $('<li class="is-selectable"><a href="#" data-action>Edit layout &amp; design</a></li>'); let itemEditDesign = $('<li class="is-selectable"><a href="#" data-action>Edit layout &amp; design</a></li>');
itemTD.after(itemEditDesign); itemEditDesign.insertAfter(itemTD);
itemEditDesign.on("click", "a", this.openEditDesignDialog); itemEditDesign.on("click", "a", this.openEditDesignDialog);
@@ -333,20 +331,25 @@ enabled(){
this.css.insert("#general_settings .cf { display: none !important }"); this.css.insert("#general_settings .cf { display: none !important }");
this.css.insert("#settings-modal .js-setting-list li:nth-child(3) { border-bottom: 1px solid #ccd6dd }"); this.css.insert("#settings-modal .js-setting-list li:nth-child(3) { border-bottom: 1px solid #ccd6dd }");
this.css.insert(".txt-base-smallest:not(.icon), .txt-base-largest:not(.icon) { font-size: "+this.config.fontSize+" !important }"); this.css.insert("html[data-td-font] { font-size: "+this.config.fontSize+" !important }");
this.css.insert(".avatar { border-radius: "+this.config.avatarRadius+"% !important }"); this.css.insert(".avatar { border-radius: "+this.config.avatarRadius+"% !important }");
let notificationScrollbarColor = null;
if (this.config.themeColorTweaks){ if (this.config.themeColorTweaks){
switch(TD.settings.getTheme()){ switch(TD.settings.getTheme()){
case "dark": case "dark":
this.css.insert(".app-content, .app-columns-container { background-color: #444448 }"); this.css.insert(".app-content, .app-columns-container { background-color: #444448 }");
this.css.insert(".column-drag-handle { opacity: 0.5 }"); this.css.insert(".column-drag-handle { opacity: 0.5 }");
this.css.insert(".column-drag-handle:hover { opacity: 1 }"); this.css.insert(".column-drag-handle:hover { opacity: 1 }");
this.css.insert(".scroll-styled-v:not(.scroll-alt)::-webkit-scrollbar-thumb, .scroll-styled-h:not(.scroll-alt)::-webkit-scrollbar-thumb { background-color: #666 }");
notificationScrollbarColor = "666";
break; break;
case "light": case "light":
this.css.insert(".scroll-styled-v::-webkit-scrollbar-thumb, .scroll-styled-h::-webkit-scrollbar-thumb { background-color: #d2d6da }"); this.css.insert(".scroll-styled-v:not(.scroll-alt)::-webkit-scrollbar-thumb, .scroll-styled-h:not(.scroll-alt)::-webkit-scrollbar-thumb { background-color: #d2d6da }");
this.css.insert(".app-columns-container.scroll-styled-h::-webkit-scrollbar-thumb:not(:hover) { background-color: #a5aeb5 }"); this.css.insert(".app-columns-container.scroll-styled-h::-webkit-scrollbar-thumb:not(:hover) { background-color: #a5aeb5 }");
notificationScrollbarColor = "a5aeb5";
break; break;
} }
} }
@@ -459,8 +462,12 @@ enabled(){
.icon-list-filled:before{content:"\\f014";font-family:tweetdeckold} .icon-list-filled:before{content:"\\f014";font-family:tweetdeckold}
.icon-user-filled:before{content:"\\f035";font-family:tweetdeckold} .icon-user-filled:before{content:"\\f035";font-family:tweetdeckold}
.column-type-icon { bottom: 26px !important } .drawer .btn .icon, .app-header .btn .icon { line-height: 1em !important }
.column-header .column-type-icon { bottom: 26px !important }
.is-options-open .column-type-icon { bottom: 25px !important } .is-options-open .column-type-icon { bottom: 25px !important }
.tweet-action-item .icon-favorite-toggle { font-size: 16px !important; }
.tweet-action-item .heartsprite { top: -260% !important; left: -260% !important; transform: scale(0.4, 0.39) translateY(0.5px) !important; }
.tweet-footer { margin-top: 6px !important }`; .tweet-footer { margin-top: 6px !important }`;
document.head.appendChild(this.icons); document.head.appendChild(this.icons);
@@ -500,7 +507,7 @@ enabled(){
$TDP.injectIntoNotificationsBefore(this.$token, "css", "</head>", ` $TDP.injectIntoNotificationsBefore(this.$token, "css", "</head>", `
<style type='text/css'> <style type='text/css'>
.txt-base-smallest:not(.icon), .txt-base-largest:not(.icon) { font-size: ${this.config.fontSize} !important } html[data-td-font] { font-size: ${this.config.fontSize} !important }
.avatar { border-radius: ${this.config.avatarRadius}% !important } .avatar { border-radius: ${this.config.avatarRadius}% !important }
${this.config.revertIcons ? ` ${this.config.revertIcons ? `
@@ -512,6 +519,10 @@ ${this.config.revertIcons ? `
.icon-user-filled:before{content:"\\f035";font-family:tweetdeckold} .icon-user-filled:before{content:"\\f035";font-family:tweetdeckold}
.icon-user-dd:before{content:"\\f01a";font-family:tweetdeckold} .icon-user-dd:before{content:"\\f01a";font-family:tweetdeckold}
` : ``} ` : ``}
${notificationScrollbarColor ? `
.scroll-styled-v::-webkit-scrollbar-thumb, .scroll-styled-h::-webkit-scrollbar-thumb { background-color: #${notificationScrollbarColor} }
` : ``}
</style>`); </style>`);
}; };

View File

@@ -9,7 +9,7 @@ Emoji keyboard
chylex chylex
[version] [version]
1.2 1.4
[website] [website]
https://tweetduck.chylex.com https://tweetduck.chylex.com

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