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

Compare commits

...

146 Commits

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

* Add HW and plugin info to analytics report generation

* Add a way of displaying the full analytics report

* Fix issues in analytics report and include design theme

* Ensure tab config is saved when switching tabs

* Fix compilation error in TabSettingsFeedback and safeguard nulls

* Add locale to analytics report

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

* Add analytics state file and implement sending reports every week

* Send an analytics report after each update
2017-11-02 03:08:43 +01:00
4e26fd9d56 Random refactoring 2017-11-01 04:02:44 +01:00
8c9168a4bf Temporarily move notification with Alt and restore by middle-clicking title bar
Closes #175
2017-10-31 23:40:56 +01:00
97da0b79e4 Fix compile error after removing TimerBarHeight constant 2017-10-31 23:34:00 +01:00
d7e5f6876b Increase notification timer height with higher DPI 2017-10-31 23:10:08 +01:00
1b92b112e2 Refactor FormNotificationBase.GetBorderStyle 2017-10-31 12:50:34 +01:00
ca55119531 Rewrite CanMoveWindow and CanResizeWindow in notification classes 2017-10-31 12:36:46 +01:00
d9da14b5dc Make the example notification its own class 2017-10-31 12:01:53 +01:00
512b5666ac Fix 'Escape' key not clearing notification tweet queue & refactor HideNotification 2017-10-31 11:31:01 +01:00
64977964e8 Bump project versions 2017-10-31 10:53:42 +01:00
2bc13e0de6 Remove subprocess dependency on communication lib & remove Comms class 2017-10-31 10:51:33 +01:00
b90c5f17cf Fix misaligned Plugins form controls when reloading plugins after scrolling down 2017-10-31 07:18:38 +01:00
7d8d0bd43b Refactor awful plugin loading and management code 2017-10-31 07:13:17 +01:00
Alexander
54c1137927 Fix warning about possible null reference in audio playback error event (#174)
* Fix V3083 warnings from PVS-Studio Static Analyzer
2017-10-29 19:32:18 +01:00
e6655219ee Add a context menu item to apply ROT13 to text selection in inputs 2017-10-29 04:39:28 +01:00
5896f8e35a Allow only px values in custom items in edit-design plugin 2017-10-25 22:12:28 +02:00
934cba7251 Fix link expansion & tooltips not working with long domains (ellipsis in front)
Closes #172
2017-10-25 19:23:02 +02:00
9cc1a11bef Fix a rare issue where GIFs open in browser due to a missing source URL 2017-10-23 00:25:23 +02:00
c1bc956d6d Move TrayIcon to a different namespace 2017-10-21 15:46:52 +02:00
351b174b86 Refactor TweetDeck browser into a separate class 2017-10-21 15:35:46 +02:00
0b4aaf80dc Remove unnecessary usings and code 2017-10-21 14:53:50 +02:00
c10c185817 Release 1.10.3 2017-10-19 00:43:06 +02:00
327ef1cbee Restart TweetDuck if user declines UAC when updating and improve error handling 2017-10-19 00:30:37 +02:00
15eb823c7f Replace OpenExternalBrowserUnsafe with the new OpenAssociatedProgram 2017-10-19 00:24:40 +02:00
54613e5242 Handle errors when opening images in associated viewer 2017-10-19 00:21:53 +02:00
df1352cbe3 Update README.md 2017-10-18 23:50:23 +02:00
0559afd972 Make middle-click tweet actions work in detail view 2017-10-18 16:50:34 +02:00
afffca020e Add context menu item to view images in photo viewer 2017-10-18 13:50:20 +02:00
d663cc3f64 Add username to default video download filename & tweak playback error message 2017-10-17 19:39:35 +02:00
110d41e393 Add function to trigger video playback for plugins 2017-10-17 13:16:19 +02:00
1a8823f592 Fix clipboard html stripping crashing with no text data
Closes #171
2017-10-17 11:22:38 +02:00
6374a852b0 Merge branch 'master' of https://github.com/chylex/TweetDuck 2017-10-16 16:37:24 +02:00
a10c7dd7c3 Fix README heading links 2017-10-16 16:36:30 +02:00
547c7ea417 Update README heading links (still bork on GitHub) 2017-10-16 16:33:41 +02:00
760607995a Update README and post build log message 2017-10-16 16:30:55 +02:00
4704197c09 Add option for larger quote font size to edit-design & update modal layout 2017-10-16 15:38:19 +02:00
093ac1ac40 Make middle-click instant quote work with reply-account plugin 2017-10-16 09:32:52 +02:00
9ed8b0d904 Fix used account in updated middle-click actions 2017-10-15 23:48:56 +02:00
7346ce370d Add middle-click actions (reply popout, RT quote, fav modal) & fix popout in temp columns 2017-10-15 22:59:56 +02:00
adefdadc19 Fix border-radius when quoting a tweet 2017-10-15 22:29:44 +02:00
703bce2d00 Fix PostBuild.ps1 errors not causing failed build & refactor 2017-10-13 13:09:50 +02:00
97928ecd84 Check emoji-ordering.txt for carriage return on build 2017-10-13 12:54:15 +02:00
be9ea7f64a Fuck everything about gitattributes 2017-10-13 12:39:03 +02:00
ec2aaa8789 Add exact emoji name match detection to emoji keyboard 2017-10-13 12:20:54 +02:00
ab14b72526 Force LF in emoji-ordering.txt and other txt/js files 2017-10-13 12:16:39 +02:00
d8e304f3c1 Release 1.10.2 2017-10-12 13:01:47 +02:00
ea53ce361f Bump edit-design plugin version 2017-10-12 10:58:12 +02:00
2fce80b347 Fix custom font size in edit-design & time font in example notification 2017-10-11 20:42:42 +02:00
373c0b1cc3 Fix separator before 'Open dev tools' in example notification context menu 2017-10-11 20:34:50 +02:00
e5e1b7e608 Fix TweetDeck being unable to detect OS name 2017-10-11 20:33:39 +02:00
7e9221c9e0 Merge branch 'master' of https://github.com/chylex/TweetDuck 2017-10-11 20:24:09 +02:00
6b849f854e Rewrite font size & theme handling, add <html> attributes to notifications 2017-10-11 20:22:32 +02:00
831f6bc744 Add support section to readme 2017-10-10 12:25:52 +02:00
d282a7a537 Fix border-radius in media upload previews 2017-10-08 05:43:54 +02:00
fb2f1e3031 Release 1.10.1 2017-10-08 05:27:38 +02:00
00a0da3df3 Fix detail view screenshots & tweak screenshot timings for iframes separately 2017-10-08 01:42:53 +02:00
8c447b1ffb Maybe fix dragged tweet links not being recognized from certain drag sources 2017-09-29 18:04:44 +02:00
a4841175e8 Fix more border-radius, "Ready to Tweet" alignment, and docked reply close icon position 2017-09-29 16:52:39 +02:00
9b139132a1 Disable reply middle-click in temporary columns & fix random reloads from middle-clicks 2017-09-29 14:58:58 +02:00
4a404ecabc Fix weird button styles in inline replies in modal dialogs 2017-09-29 12:48:00 +02:00
aee758b559 Update reply-account docs & fix error with temporary columns and advanced selector 2017-09-29 12:47:13 +02:00
be060d0386 Add new info about emoji usage to the guide 2017-09-28 17:29:34 +02:00
121 changed files with 3737 additions and 2005 deletions

5
.gitattributes vendored Normal file
View File

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

View File

@@ -5,7 +5,6 @@ namespace TweetDuck.Configuration{
static class Arguments{
// public args
public const string ArgDataFolder = "-datafolder";
public const string ArgLocale = "-locale";
public const string ArgLogging = "-log";
public const string ArgDebugUpdates = "-debugupdates";

View File

@@ -4,7 +4,6 @@ using System.Diagnostics;
using System.IO;
using System.Threading;
using TweetDuck.Core.Utils;
using TweetLib.Communication;
namespace TweetDuck.Configuration{
sealed class LockManager{
@@ -136,15 +135,13 @@ namespace TweetDuck.Configuration{
// Locking process
public bool RestoreLockingProcess(int failTimeout){
if (lockingProcess != null){
if (lockingProcess.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
Comms.BroadcastMessage(Program.WindowRestoreMessage, (uint)lockingProcess.Id, 0);
if (lockingProcess != null && lockingProcess.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
NativeMethods.BroadcastMessage(Program.WindowRestoreMessage, (uint)lockingProcess.Id, 0);
if (WindowsUtils.TrySleepUntil(() => CheckLockingProcessExited() || (lockingProcess.MainWindowHandle != IntPtr.Zero && lockingProcess.Responding), failTimeout, RetryDelay)){
return true;
}
}
}
return false;
}
@@ -166,16 +163,13 @@ namespace TweetDuck.Configuration{
lockingProcess = null;
return true;
}
}catch(Exception ex){
if (ex is InvalidOperationException || ex is Win32Exception){
}catch(Exception ex) when (ex is InvalidOperationException || ex is Win32Exception){
if (lockingProcess != null){
bool hasExited = CheckLockingProcessExited();
lockingProcess.Dispose();
return hasExited;
}
}
else throw;
}
}
return false;

View File

@@ -1,11 +1,12 @@
using System;
using System.IO;
using TweetDuck.Core.Utils;
using TweetDuck.Data.Serialization;
namespace TweetDuck.Configuration{
sealed class SystemConfig{
private static readonly FileSerializer<SystemConfig> Serializer = new FileSerializer<SystemConfig>();
private static readonly FileSerializer<SystemConfig> Serializer = new FileSerializer<SystemConfig>{
HandleUnknownProperties = FileSerializer<SystemConfig>.IgnoreProperties("EnableBrowserGCReload", "BrowserMemoryThreshold")
};
public static readonly bool IsHardwareAccelerationSupported = File.Exists(Path.Combine(Program.ProgramPath, "libEGL.dll")) &&
File.Exists(Path.Combine(Program.ProgramPath, "libGLESv2.dll"));
@@ -14,8 +15,8 @@ namespace TweetDuck.Configuration{
private bool _hardwareAcceleration = true;
public bool EnableBrowserGCReload { get; set; } = true;
public int BrowserMemoryThreshold { get; set; } = 400;
public bool ClearCacheAutomatically { get; set; } = true;
public int ClearCacheThreshold { get; set; } = 250;
// SPECIAL PROPERTIES
@@ -32,14 +33,11 @@ namespace TweetDuck.Configuration{
this.file = file;
}
public bool Save(){
public void Save(){
try{
WindowsUtils.CreateDirectoryForFile(file);
Serializer.Write(file, this);
return true;
}catch(Exception e){
Program.Reporter.HandleException("Configuration Error", "Could not save the system configuration file.", true, e);
return false;
}
}
@@ -47,9 +45,7 @@ namespace TweetDuck.Configuration{
SystemConfig config = new SystemConfig(file);
try{
Serializer.Read(file, config);
}catch(FileNotFoundException){
}catch(DirectoryNotFoundException){
Serializer.ReadIfExists(file, config);
}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);
}

View File

@@ -1,26 +1,18 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using TweetDuck.Core;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification;
using TweetDuck.Core.Other;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Data.Serialization;
namespace TweetDuck.Configuration{
sealed class UserConfig{
private static readonly FileSerializer<UserConfig> Serializer = new FileSerializer<UserConfig>{ HandleUnknownProperties = HandleUnknownProperties };
private static void HandleUnknownProperties(UserConfig obj, Dictionary<string, string> data){
data.Remove("EnableBrowserGCReload");
data.Remove("BrowserMemoryThreshold");
if (data.Count == 0){
obj.Save();
}
}
private static readonly FileSerializer<UserConfig> Serializer = new FileSerializer<UserConfig>{
HandleUnknownProperties = FileSerializer<UserConfig>.IgnoreProperties("AppLocale")
};
static UserConfig(){
Serializer.RegisterTypeConverter(typeof(WindowState), WindowState.Converter);
@@ -46,6 +38,7 @@ namespace TweetDuck.Configuration{
public bool FirstRun { get; set; } = true;
public bool AllowDataCollection { get; set; } = false;
public bool ShowDataCollectionNotification { get; set; } = true;
public WindowState BrowserWindow { get; set; } = new WindowState();
public WindowState PluginsWindow { get; set; } = new WindowState();
@@ -53,12 +46,17 @@ namespace TweetDuck.Configuration{
public bool ExpandLinksOnHover { get; set; } = true;
public bool SwitchAccountSelectors { get; set; } = true;
public bool OpenSearchInFirstColumn { get; set; } = true;
public bool KeepLikeFollowDialogsOpen { get; set; } = true;
public bool BestImageQuality { get; set; } = true;
public bool EnableSpellCheck { get; set; } = false;
public bool EnableAnimatedImages { get; set; } = true;
public int VideoPlayerVolume { get; set; } = 50;
private int _zoomLevel = 100;
private bool _muteNotifications;
public bool EnableSpellCheck { get; set; } = false;
public string SpellCheckLanguage { get; set; } = "en-US";
public string TranslationTarget { get; set; } = "en";
private TrayIcon.Behavior _trayBehavior = TrayIcon.Behavior.Disabled;
public bool EnableTrayHighlight { get; set; } = true;
@@ -82,7 +80,7 @@ namespace TweetDuck.Configuration{
public TweetNotification.Size NotificationSize { get; set; } = TweetNotification.Size.Auto;
public Size CustomNotificationSize { get; set; } = Size.Empty;
public int NotificationScrollSpeed { get; set; } = 10;
public int NotificationScrollSpeed { get; set; } = 100;
public int NotificationSoundVolume { get; set; } = 100;
private string _notificationSoundPath;
@@ -125,8 +123,6 @@ namespace TweetDuck.Configuration{
}
}
public double ZoomMultiplier => _zoomLevel/100.0;
public TrayIcon.Behavior TrayBehavior{
get => _trayBehavior;
@@ -152,10 +148,8 @@ namespace TweetDuck.Configuration{
this.file = file;
}
public bool Save(){
public void Save(){
try{
WindowsUtils.CreateDirectoryForFile(file);
if (File.Exists(file)){
string backupFile = GetBackupFile(file);
File.Delete(backupFile);
@@ -163,34 +157,33 @@ namespace TweetDuck.Configuration{
}
Serializer.Write(file, this);
return true;
}catch(Exception e){
Program.Reporter.HandleException("Configuration Error", "Could not save the configuration file.", true, e);
return false;
}
}
public bool Reload(){
public void 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);
if (NotificationScrollSpeed == 10){ // incorrect initial value
NotificationScrollSpeed = 100;
Save();
}
}
public static UserConfig Load(string file){

View File

@@ -7,9 +7,8 @@ namespace TweetDuck.Core.Bridge{
}
public static string GenerateScript(Environment environment){
string Bool(bool value){
return value ? "true;" : "false;";
}
string Bool(bool value) => value ? "true;" : "false;";
string Str(string value) => '"'+value+"\";";
StringBuilder build = new StringBuilder().Append("(function(x){");
@@ -18,9 +17,11 @@ namespace TweetDuck.Core.Bridge{
if (environment == Environment.Browser){
build.Append("x.switchAccountSelectors=").Append(Bool(Program.UserConfig.SwitchAccountSelectors));
build.Append("x.openSearchInFirstColumn=").Append(Bool(Program.UserConfig.OpenSearchInFirstColumn));
build.Append("x.keepLikeFollowDialogsOpen=").Append(Bool(Program.UserConfig.KeepLikeFollowDialogsOpen));
build.Append("x.muteNotifications=").Append(Bool(Program.UserConfig.MuteNotifications));
build.Append("x.hasCustomNotificationSound=").Append(Bool(Program.UserConfig.NotificationSoundPath.Length > 0));
build.Append("x.notificationMediaPreviews=").Append(Bool(Program.UserConfig.NotificationMediaPreviews));
build.Append("x.translationTarget=").Append(Str(Program.UserConfig.TranslationTarget));
}
if (environment == Environment.Notification){

View File

@@ -10,9 +10,9 @@ using TweetDuck.Core.Utils;
using TweetDuck.Resources;
namespace TweetDuck.Core.Bridge{
sealed class TweetDeckBridge{
public static string FontSizeClass;
public static string NotificationHeadContents;
class TweetDeckBridge{
public static string FontSize { get; private set; }
public static string NotificationHeadLayout { get; private set; }
public static string LastHighlightedTweetUrl = string.Empty;
public static string LastHighlightedQuoteUrl = string.Empty;
@@ -25,7 +25,7 @@ namespace TweetDuck.Core.Bridge{
private static readonly Dictionary<string, string> SessionData = new Dictionary<string, string>(2);
public static void ResetStaticProperties(){
FontSizeClass = NotificationHeadContents = null;
FontSize = NotificationHeadLayout = null;
LastHighlightedTweetUrl = LastHighlightedQuoteUrl = LastHighlightedTweetAuthors = LastHighlightedTweetImages = string.Empty;
}
@@ -45,27 +45,31 @@ namespace TweetDuck.Core.Bridge{
private readonly FormBrowser form;
private readonly FormNotificationMain notification;
public TweetDeckBridge(FormBrowser form, FormNotificationMain notification){
protected TweetDeckBridge(FormBrowser form, FormNotificationMain notification){
this.form = form;
this.notification = notification;
}
// Browser only
public sealed class Browser : TweetDeckBridge{
public Browser(FormBrowser form, FormNotificationMain notification) : base(form, notification){}
public void OpenContextMenu(){
form.InvokeAsyncSafe(form.OpenContextMenu);
}
public void OnIntroductionClosed(bool showGuide, bool allowDataCollection){
form.InvokeAsyncSafe(() => {
form.OnIntroductionClosed(showGuide, allowDataCollection);
});
}
public void LoadFontSizeClass(string fsClass){
form.InvokeAsyncSafe(() => FontSizeClass = fsClass);
}
public void LoadNotificationHeadContents(string headContents){
form.InvokeAsyncSafe(() => NotificationHeadContents = headContents);
}
public void SetLastRightClickInfo(string type, string link){
form.InvokeAsyncSafe(() => ContextMenuBase.SetContextInfo(type, link));
public void LoadNotificationLayout(string fontSize, string headLayout){
form.InvokeAsyncSafe(() => {
FontSize = fontSize;
NotificationHeadLayout = headLayout;
});
}
public void SetLastHighlightedTweet(string tweetUrl, string quoteUrl, string authors, string imageList){
@@ -77,8 +81,39 @@ namespace TweetDuck.Core.Bridge{
});
}
public void OpenContextMenu(){
form.InvokeAsyncSafe(form.OpenContextMenu);
public void DisplayTooltip(string text){
form.InvokeAsyncSafe(() => form.DisplayTooltip(text));
}
public void SetSessionData(string key, string value){
form.InvokeSafe(() => { // do not use InvokeAsyncSafe, return only after invocation
SessionData.Add(key, value);
});
}
}
// Notification only
public sealed class Notification : TweetDeckBridge{
public Notification(FormBrowser form, FormNotificationMain notification) : base(form, notification){}
public void DisplayTooltip(string text){
notification.InvokeAsyncSafe(() => notification.DisplayTooltip(text));
}
public void LoadNextNotification(){
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
}
public void ShowTweetDetail(){
notification.InvokeAsyncSafe(notification.ShowTweetDetail);
}
}
// Global
public void SetLastRightClickInfo(string type, string link){
form.InvokeAsyncSafe(() => ContextMenuBase.SetContextInfo(type, link));
}
public void OnTweetPopup(string columnId, string chirpId, string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl){
@@ -95,31 +130,12 @@ namespace TweetDuck.Core.Bridge{
});
}
public void DisplayTooltip(string text, bool showInNotification){
if (showInNotification){
notification.InvokeAsyncSafe(() => notification.DisplayTooltip(text));
}
else{
form.InvokeAsyncSafe(() => form.DisplayTooltip(text));
}
}
public void SetSessionData(string key, string value){
form.InvokeSafe(() => { // do not use InvokeAsyncSafe, return only after invocation
SessionData.Add(key, value);
});
}
public void LoadNextNotification(){
notification.InvokeAsyncSafe(notification.FinishCurrentNotification);
}
public void ScreenshotTweet(string html, int width, int height){
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width, height));
}
public void PlayVideo(string url){
form.InvokeAsyncSafe(() => form.PlayVideo(url));
public void PlayVideo(string url, string username){
form.InvokeAsyncSafe(() => form.PlayVideo(url, username));
}
public void FixClipboard(){
@@ -149,13 +165,9 @@ namespace TweetDuck.Core.Bridge{
public void CrashDebug(string message){
#if DEBUG
Log(message);
System.Diagnostics.Debug.WriteLine(message);
System.Diagnostics.Debugger.Break();
#endif
}
public void Log(string data){
System.Diagnostics.Debug.WriteLine(data);
}
}
}

View File

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

View File

@@ -24,7 +24,7 @@
/// </summary>
private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
this.trayIcon = new TweetDuck.Core.TrayIcon(this.components);
this.trayIcon = new TweetDuck.Core.Other.TrayIcon(this.components);
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.timerResize = new System.Windows.Forms.Timer(this.components);
this.SuspendLayout();
@@ -57,7 +57,7 @@
#endregion
private TrayIcon trayIcon;
private TweetDuck.Core.Other.TrayIcon trayIcon;
private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.Timer timerResize;
}

View File

@@ -1,23 +1,19 @@
using CefSharp;
using CefSharp.WinForms;
using System;
using System;
using System.Drawing;
using System.Windows.Forms;
using TweetDuck.Configuration;
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling;
using TweetDuck.Core.Handling.General;
using TweetDuck.Core.Notification;
using TweetDuck.Core.Notification.Screenshot;
using TweetDuck.Core.Other;
using TweetDuck.Core.Other.Analytics;
using TweetDuck.Core.Other.Management;
using TweetDuck.Core.Other.Settings;
using TweetDuck.Core.Utils;
using TweetDuck.Plugins;
using TweetDuck.Plugins.Enums;
using TweetDuck.Plugins.Events;
using TweetDuck.Resources;
using TweetDuck.Updates;
using TweetLib.Audio;
@@ -44,20 +40,19 @@ namespace TweetDuck.Core{
public string UpdateInstallerPath { get; private set; }
private readonly ChromiumWebBrowser browser;
private readonly TweetDeckBrowser browser;
private readonly PluginManager plugins;
private readonly UpdateHandler updates;
private readonly FormNotificationTweet notification;
private readonly ContextMenu contextMenu;
private readonly MemoryUsageTracker memoryUsageTracker;
private bool isLoaded;
private bool isBrowserReady;
private FormWindowState prevState;
private TweetScreenshotManager notificationScreenshotManager;
private SoundNotification soundNotification;
private VideoPlayer videoPlayer;
private AnalyticsManager analytics;
public FormBrowser(UpdaterSettings updaterSettings){
InitializeComponent();
@@ -67,52 +62,21 @@ namespace TweetDuck.Core{
this.plugins = new PluginManager(Program.PluginPath, Program.PluginConfigFilePath);
this.plugins.Reloaded += plugins_Reloaded;
this.plugins.Executed += plugins_Executed;
this.plugins.PluginChangedState += plugins_PluginChangedState;
this.plugins.Reload();
this.contextMenu = ContextMenuBrowser.CreateMenu(this);
this.memoryUsageTracker = new MemoryUsageTracker("TDGF_tryRunCleanup");
this.notification = new FormNotificationTweet(this, plugins){
#if DEBUG
CanMoveWindow = () => (ModifierKeys & Keys.Alt) == Keys.Alt
#else
CanMoveWindow = () => false
#endif
};
this.notification = new FormNotificationTweet(this, plugins);
this.notification.Show();
this.browser = new ChromiumWebBrowser("https://tweetdeck.twitter.com/"){
DialogHandler = new FileDialogHandler(),
DragHandler = new DragHandlerBrowser(),
MenuHandler = new ContextMenuBrowser(this),
JsDialogHandler = new JavaScriptDialogHandler(),
KeyboardHandler = new KeyboardHandlerBrowser(this),
LifeSpanHandler = new LifeSpanHandler(),
RequestHandler = new RequestHandlerBrowser()
};
this.browser = new TweetDeckBrowser(this, plugins, new TweetDeckBridge.Browser(this, notification));
this.browser.PageLoaded += browser_PageLoaded;
#if DEBUG
this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
#endif
this.browser.LoadingStateChanged += browser_LoadingStateChanged;
this.browser.FrameLoadStart += browser_FrameLoadStart;
this.browser.FrameLoadEnd += browser_FrameLoadEnd;
this.browser.LoadError += browser_LoadError;
this.browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(this, notification));
this.browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
browser.BrowserSettings.BackgroundColor = (uint)TwitterUtils.BackgroundColor.ToArgb();
browser.Dock = DockStyle.None;
browser.Location = ControlExtensions.InvisibleLocation;
Controls.Add(browser);
this.contextMenu = ContextMenuBrowser.CreateMenu(this);
Controls.Add(new MenuStrip{ Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update
Disposed += (sender, args) => {
memoryUsageTracker.Dispose();
Config.MuteToggled -= Config_MuteToggled;
Config.TrayBehaviorChanged -= Config_TrayBehaviorChanged;
browser.Dispose();
contextMenu.Dispose();
@@ -120,21 +84,25 @@ namespace TweetDuck.Core{
notificationScreenshotManager?.Dispose();
soundNotification?.Dispose();
videoPlayer?.Dispose();
analytics?.Dispose();
};
Config.MuteToggled += Config_MuteToggled;
this.trayIcon.ClickRestore += trayIcon_ClickRestore;
this.trayIcon.ClickClose += trayIcon_ClickClose;
Config.TrayBehaviorChanged += Config_TrayBehaviorChanged;
UpdateTrayIcon();
Config.MuteToggled += Config_MuteToggled;
Config.ZoomLevelChanged += Config_ZoomLevelChanged;
this.updates = new UpdateHandler(browser, updaterSettings);
this.updates = browser.CreateUpdateHandler(updaterSettings);
this.updates.UpdateAccepted += updates_UpdateAccepted;
this.updates.UpdateDismissed += updates_UpdateDismissed;
if (Config.AllowDataCollection){
analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
}
RestoreWindow();
}
@@ -156,81 +124,11 @@ namespace TweetDuck.Core{
isLoaded = true;
}
private void OnBrowserReady(){
if (!isBrowserReady){
browser.Location = Point.Empty;
browser.Dock = DockStyle.Fill;
isBrowserReady = true;
}
}
private void UpdateTrayIcon(){
trayIcon.Visible = Config.TrayBehavior.ShouldDisplayIcon();
}
// active event handlers
private void browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e){
if (!e.IsLoading){
foreach(string word in TwitterUtils.DictionaryWords){
browser.AddWordToDictionary(word);
}
BeginInvoke(new Action(OnBrowserReady));
browser.LoadingStateChanged -= browser_LoadingStateChanged;
}
}
private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){
if (e.Frame.IsMain){
memoryUsageTracker.Stop();
if (Config.ZoomLevel != 100){
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Config.ZoomLevel);
}
if (TwitterUtils.IsTwitterWebsite(e.Frame)){
ScriptLoader.ExecuteFile(e.Frame, "twitter.js");
}
}
}
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
if (e.Frame.IsMain && TwitterUtils.IsTweetDeckWebsite(e.Frame)){
e.Frame.ExecuteJavaScriptAsync(TwitterUtils.BackgroundColorFix);
UpdateProperties(PropertyBridge.Environment.Browser);
TweetDeckBridge.RestoreSessionData(e.Frame);
ScriptLoader.ExecuteFile(e.Frame, "code.js");
InjectBrowserCSS();
ReinjectCustomCSS(Config.CustomBrowserCSS);
plugins.ExecutePlugins(e.Frame, PluginEnvironment.Browser);
TweetDeckBridge.ResetStaticProperties();
if (Program.SystemConfig.EnableBrowserGCReload){
memoryUsageTracker.Start(this, e.Browser, Program.SystemConfig.BrowserMemoryThreshold);
}
if (Config.FirstRun){
ScriptLoader.ExecuteFile(e.Frame, "introduction.js");
}
}
}
private void browser_LoadError(object sender, LoadErrorEventArgs e){
if (e.ErrorCode == CefErrorCode.Aborted){
return;
}
if (!e.FailedUrl.StartsWith("http://td/", StringComparison.Ordinal)){
string errorPage = ScriptLoader.LoadResource("pages/error.html", true);
if (errorPage != null){
browser.LoadHtml(errorPage.Replace("{err}", BrowserUtils.GetErrorName(e.ErrorCode)), "http://td/error");
}
}
}
// event handlers
private void timerResize_Tick(object sender, EventArgs e){
FormBrowser_ResizeEnd(this, e); // also stops timer
@@ -301,11 +199,7 @@ namespace TweetDuck.Core{
}
private void Config_MuteToggled(object sender, EventArgs e){
UpdateProperties(PropertyBridge.Environment.Browser);
}
private void Config_ZoomLevelChanged(object sender, EventArgs e){
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Config.ZoomLevel);
TriggerAnalyticsEvent(AnalyticsFile.Event.MuteNotification);
}
private void Config_TrayBehaviorChanged(object sender, EventArgs e){
@@ -323,6 +217,38 @@ namespace TweetDuck.Core{
ForceClose();
}
private void browser_PageLoaded(object sender, EventArgs e){
if (Config.ShowDataCollectionNotification){
this.InvokeAsyncSafe(() => {
if (!Config.FirstRun && Config.AllowDataCollection){
FormMessage form = new FormMessage("Anonymous Data Update", "Hi! You can now review your anonymous data report, and opt-out if you've changed your mind. Collected data will be used to focus development on most commonly used features. If you want to opt-out but still support the project, any feedback and donations are appreciated.", MessageBoxIcon.Information);
form.AddButton("OK", ControlType.Accept | ControlType.Focused);
Button btnReviewSettings = new Button{
Anchor = AnchorStyles.Bottom | AnchorStyles.Left,
Font = SystemFonts.MessageBoxFont,
Location = new Point(9, 12),
Margin = new Padding(0, 0, 48, 0),
Size = new Size(160, 26),
Text = "Review Feedback Options",
UseVisualStyleBackColor = true
};
btnReviewSettings.Click += (sender2, args2) => {
form.Close();
OpenSettings(typeof(TabSettingsFeedback));
};
form.AddActionControl(btnReviewSettings);
ShowChildForm(form);
}
Config.ShowDataCollectionNotification = false;
Config.Save();
});
}
}
private void plugins_Reloaded(object sender, PluginErrorEventArgs e){
if (e.HasErrors){
FormMessage.Error("Error Loading Plugins", "The following plugins will not be available until the issues are resolved:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK);
@@ -339,14 +265,15 @@ namespace TweetDuck.Core{
}
}
private void plugins_PluginChangedState(object sender, PluginChangedStateEventArgs e){
browser.ExecuteScriptAsync("window.TDPF_setPluginState", e.Plugin, e.IsEnabled);
}
private void updates_UpdateAccepted(object sender, UpdateEventArgs e){
this.InvokeAsyncSafe(() => {
FormManager.CloseAllDialogs();
if (!string.IsNullOrEmpty(Config.DismissedUpdate)){
Config.DismissedUpdate = null;
Config.Save();
}
updates.BeginUpdateDownload(this, e.UpdateInfo, update => {
if (update.DownloadStatus == UpdateDownloadStatus.Done){
UpdateInstallerPath = update.InstallerPath;
@@ -381,31 +308,21 @@ namespace TweetDuck.Core{
}
protected override void WndProc(ref Message m){
if (isLoaded){
if (m.Msg == Program.WindowRestoreMessage){
if (isLoaded && m.Msg == Program.WindowRestoreMessage){
if (WindowsUtils.CurrentProcessID == m.WParam.ToInt32()){
trayIcon_ClickRestore(trayIcon, EventArgs.Empty);
}
return;
}
else if (m.Msg == Program.SubProcessMessage){
int processId = m.WParam.ToInt32();
if (WindowsUtils.IsChildProcess(processId)){ // child process is checked in two places for safety
BrowserProcesses.Link(m.LParam.ToInt32(), processId);
}
return;
}
}
if (isBrowserReady && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN){
if (browser.Ready && m.Msg == NativeMethods.WM_PARENTNOTIFY && (m.WParam.ToInt32() & 0xFFFF) == NativeMethods.WM_XBUTTONDOWN){
if (videoPlayer != null && videoPlayer.Running){
videoPlayer.Close();
}
else{
browser.ExecuteScriptAsync("TDGF_onMouseClickExtra", (m.WParam.ToInt32() >> 16) & 0xFFFF);
browser.OnMouseClickExtra(m.WParam);
TriggerAnalyticsEvent(AnalyticsFile.Event.BrowserExtraMouseButton);
}
return;
@@ -414,11 +331,7 @@ namespace TweetDuck.Core{
base.WndProc(ref m);
}
// notification helpers
public FormNotificationMain CreateNotificationForm(bool enableContextMenu){
return new FormNotificationMain(this, plugins, enableContextMenu);
}
// bridge methods
public void PauseNotification(){
notification.PauseNotification();
@@ -428,22 +341,28 @@ namespace TweetDuck.Core{
notification.ResumeNotification();
}
// javascript calls
public void InjectBrowserCSS(){
browser.ExecuteScriptAsync("TDGF_injectBrowserCSS", ScriptLoader.LoadResource("styles/browser.css").TrimEnd());
}
public void ReinjectCustomCSS(string css){
browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css?.Replace(Environment.NewLine, " ") ?? string.Empty);
}
public void UpdateProperties(PropertyBridge.Environment environment){
browser.ExecuteScriptAsync(PropertyBridge.GenerateScript(environment));
browser.ReinjectCustomCSS(css);
}
public void ReloadToTweetDeck(){
browser.ExecuteScriptAsync($"if(window.TDGF_reload)window.TDGF_reload();else window.location.href='{TwitterUtils.TweetDeckURL}'");
browser.ReloadToTweetDeck();
}
public void TriggerTweetScreenshot(){
browser.TriggerTweetScreenshot();
}
public void ReloadColumns(){
browser.ReloadColumns();
}
public void ApplyROT13(){
browser.ApplyROT13();
}
public void TriggerAnalyticsEvent(AnalyticsFile.Event e){
analytics?.TriggerEvent(e);
}
// callback handlers
@@ -452,11 +371,16 @@ namespace TweetDuck.Core{
if (Config.FirstRun){
Config.FirstRun = false;
Config.AllowDataCollection = allowDataCollection;
Config.ShowDataCollectionNotification = false;
Config.Save();
if (allowDataCollection && analytics == null){
analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
}
}
if (showGuide){
ShowChildForm(new FormGuide());
FormGuide.Show();
}
}
@@ -472,7 +396,7 @@ namespace TweetDuck.Core{
if (!FormManager.TryBringToFront<FormSettings>()){
bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
FormSettings form = new FormSettings(this, plugins, updates, startTab);
FormSettings form = new FormSettings(this, plugins, updates, analytics, startTab);
form.FormClosed += (sender, args) => {
if (!prevEnableUpdateCheck && Config.EnableUpdateCheck){
@@ -486,37 +410,45 @@ namespace TweetDuck.Core{
trayIcon.HasNotifications = false;
}
if (Program.SystemConfig.EnableBrowserGCReload){
memoryUsageTracker.Start(this, browser.GetBrowser(), Program.SystemConfig.BrowserMemoryThreshold);
if (Config.AllowDataCollection){
if (analytics == null){
analytics = new AnalyticsManager(this, plugins, Program.AnalyticsFilePath);
}
else{
memoryUsageTracker.Stop();
}
else if (analytics != null){
analytics.Dispose();
analytics = null;
}
BrowserCache.RefreshTimer();
if (form.ShouldReloadBrowser){
FormManager.TryFind<FormPlugins>()?.Close();
plugins.Reload(); // also reloads the browser
}
else{
UpdateProperties(PropertyBridge.Environment.Browser);
browser.UpdateProperties();
}
notification.RequiresResize = true;
form.Dispose();
};
TriggerAnalyticsEvent(AnalyticsFile.Event.OpenOptions);
ShowChildForm(form);
}
}
public void OpenAbout(){
if (!FormManager.TryBringToFront<FormAbout>()){
TriggerAnalyticsEvent(AnalyticsFile.Event.OpenAbout);
ShowChildForm(new FormAbout());
}
}
public void OpenPlugins(){
if (!FormManager.TryBringToFront<FormPlugins>()){
TriggerAnalyticsEvent(AnalyticsFile.Event.OpenPlugins);
ShowChildForm(new FormPlugins(plugins));
}
}
@@ -539,9 +471,11 @@ namespace TweetDuck.Core{
soundNotification.SetVolume(Config.NotificationSoundVolume);
soundNotification.Play(Config.NotificationSoundPath);
TriggerAnalyticsEvent(AnalyticsFile.Event.SoundNotification);
}
public void PlayVideo(string url){
public void PlayVideo(string url, string username){
if (string.IsNullOrEmpty(url)){
videoPlayer?.Close();
return;
@@ -551,16 +485,12 @@ namespace TweetDuck.Core{
videoPlayer = new VideoPlayer(this);
videoPlayer.ProcessExited += (sender, args) => {
browser.GetBrowser().GetHost().SendFocusEvent(true);
HideVideoOverlay();
browser.HideVideoOverlay(true);
};
}
videoPlayer.Launch(url);
}
public void HideVideoOverlay(){
browser.ExecuteScriptAsync("$('#td-video-player-overlay').remove()");
videoPlayer.Launch(url, username);
TriggerAnalyticsEvent(AnalyticsFile.Event.VideoPlay);
}
public bool ProcessBrowserKey(Keys key){
@@ -575,15 +505,14 @@ namespace TweetDuck.Core{
public void ShowTweetDetail(string columnId, string chirpId, string fallbackUrl){
Activate();
using(IFrame frame = browser.GetBrowser().MainFrame){
if (!TwitterUtils.IsTweetDeckWebsite(frame)){
if (!browser.IsTweetDeckWebsite){
FormMessage.Error("View Tweet Detail", "TweetDeck is not currently loaded.", FormMessage.OK);
return;
}
}
notification.FinishCurrentNotification();
browser.ExecuteScriptAsync("window.TDGF_showTweetDetail", columnId, chirpId, fallbackUrl);
browser.ShowTweetDetail(columnId, chirpId, fallbackUrl);
TriggerAnalyticsEvent(AnalyticsFile.Event.TweetDetail);
}
public void OnTweetScreenshotReady(string html, int width, int height){
@@ -592,6 +521,7 @@ namespace TweetDuck.Core{
}
notificationScreenshotManager.Trigger(html, width, height);
TriggerAnalyticsEvent(AnalyticsFile.Event.TweetScreenshot);
}
public void DisplayTooltip(string text){
@@ -604,9 +534,5 @@ namespace TweetDuck.Core{
toolTip.Show(text, this, position);
}
}
public void TriggerTweetScreenshot(){
browser.ExecuteScriptAsync("TDGF_triggerScreenshot()");
}
}
}

View File

@@ -8,10 +8,11 @@ using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils;
using System.Collections.Generic;
using System.Linq;
using TweetDuck.Core.Other;
namespace TweetDuck.Core.Handling{
abstract class ContextMenuBase : IContextMenuHandler{
protected static readonly bool HasDevTools = File.Exists(Path.Combine(Program.ProgramPath, "devtools_resources.pak"));
public static readonly bool HasDevTools = File.Exists(Path.Combine(Program.ProgramPath, "devtools_resources.pak"));
private static TwitterUtils.ImageQuality ImageQuality => Program.UserConfig.TwitterImageQuality;
@@ -31,10 +32,11 @@ namespace TweetDuck.Core.Handling{
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 MenuViewImage = (CefMenuCommand)26503;
private const CefMenuCommand MenuOpenMediaUrl = (CefMenuCommand)26504;
private const CefMenuCommand MenuCopyMediaUrl = (CefMenuCommand)26505;
private const CefMenuCommand MenuSaveMedia = (CefMenuCommand)26506;
private const CefMenuCommand MenuSaveTweetImages = (CefMenuCommand)26507;
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand)26599;
private string[] lastHighlightedTweetAuthors;
@@ -79,6 +81,7 @@ namespace TweetDuck.Core.Handling{
model.AddSeparator();
}
else if ((parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) || hasTweetImage){
model.AddItem(MenuViewImage, "View image in photo viewer");
model.AddItem(MenuOpenMediaUrl, TextOpen("image"));
model.AddItem(MenuCopyMediaUrl, TextCopy("image"));
model.AddItem(MenuSaveMedia, TextSave("image"));
@@ -114,9 +117,35 @@ namespace TweetDuck.Core.Handling{
SetClipboardText(browserControl.AsControl(), TwitterUtils.GetMediaLink(GetMediaLink(parameters), ImageQuality));
break;
case MenuViewImage:
string url = GetMediaLink(parameters);
string file = Path.Combine(BrowserCache.CacheFolder, TwitterUtils.GetImageFileName(url));
void ViewFile(){
string ext = Path.GetExtension(file);
if (TwitterUtils.ValidImageExtensions.Contains(ext)){
WindowsUtils.OpenAssociatedProgram(file);
}
else{
FormMessage.Error("Image Download", "Invalid file extension "+ext, FormMessage.OK);
}
}
if (File.Exists(file)){
ViewFile();
}
else{
BrowserUtils.DownloadFileAsync(TwitterUtils.GetMediaLink(url, ImageQuality), file, ViewFile, ex => {
FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK);
});
}
break;
case MenuSaveMedia:
if (IsVideo){
TwitterUtils.DownloadVideo(GetMediaLink(parameters));
TwitterUtils.DownloadVideo(GetMediaLink(parameters), lastHighlightedTweetAuthors.LastOrDefault());
}
else{
TwitterUtils.DownloadImage(GetMediaLink(parameters), lastHighlightedTweetAuthors.LastOrDefault(), ImageQuality);

View File

@@ -2,6 +2,7 @@
using System.Windows.Forms;
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Other.Analytics;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Handling{
@@ -17,6 +18,7 @@ namespace TweetDuck.Core.Handling{
private const CefMenuCommand MenuOpenQuotedTweetUrl = (CefMenuCommand)26612;
private const CefMenuCommand MenuCopyQuotedTweetUrl = (CefMenuCommand)26613;
private const CefMenuCommand MenuScreenshotTweet = (CefMenuCommand)26614;
private const CefMenuCommand MenuInputApplyROT13 = (CefMenuCommand)26615;
private const string TitleReloadBrowser = "Reload browser";
private const string TitleMuteNotifications = "Mute notifications";
@@ -41,6 +43,11 @@ namespace TweetDuck.Core.Handling{
RemoveSeparatorIfLast(model);
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){
if (parameters.TypeFlags.HasFlag(ContextMenuType.Editable)){
model.AddSeparator();
model.AddItem(MenuInputApplyROT13, "Apply ROT13");
}
model.AddSeparator();
}
@@ -89,6 +96,8 @@ namespace TweetDuck.Core.Handling{
}
RemoveSeparatorIfLast(model);
form.InvokeAsyncSafe(() => form.TriggerAnalyticsEvent(AnalyticsFile.Event.BrowserContextMenu));
}
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
@@ -136,6 +145,10 @@ namespace TweetDuck.Core.Handling{
case MenuCopyQuotedTweetUrl:
SetClipboardText(form, lastHighlightedQuoteUrl);
return true;
case MenuInputApplyROT13:
form.InvokeAsyncSafe(form.ApplyROT13);
return true;
}
return false;
@@ -153,6 +166,7 @@ namespace TweetDuck.Core.Handling{
menu.Popup += (sender, args) => {
menu.MenuItems[1].Checked = Program.UserConfig.MuteNotifications;
form.TriggerAnalyticsEvent(AnalyticsFile.Event.BrowserContextMenu);
};
return menu;

View File

@@ -1,6 +1,7 @@
using CefSharp;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification;
using TweetDuck.Core.Other.Analytics;
namespace TweetDuck.Core.Handling{
sealed class ContextMenuNotification : ContextMenuBase{
@@ -48,13 +49,16 @@ namespace TweetDuck.Core.Handling{
}
if (HasDevTools){
model.AddSeparator();
AddSeparator(model);
AddDebugMenuItems(model);
}
RemoveSeparatorIfLast(model);
form.InvokeAsyncSafe(() => form.ContextMenuOpen = true);
form.InvokeAsyncSafe(() => {
form.ContextMenuOpen = true;
form.TriggerAnalyticsEvent(AnalyticsFile.Event.NotificationContextMenu);
});
}
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){

View File

@@ -4,11 +4,18 @@ 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){
browserControl.ExecuteScriptAsync("window.TDGF_onGlobalDragStart", "link", dragData.LinkUrl);
TriggerDragStart("link", dragData.LinkUrl);
}
else if (dragData.IsFragment){
TriggerDragStart("text", dragData.FragmentText.Trim());
}
else{
browserControl.ExecuteScriptAsync("window.TDGF_onGlobalDragStart", "unknown");
TriggerDragStart("unknown");
}
return false;

View File

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

View File

@@ -4,9 +4,7 @@ using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Handling.General{
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){
newBrowser = null;
public static bool HandleLinkClick(IWebBrowser browserControl, WindowOpenDisposition targetDisposition, string targetUrl){
switch(targetDisposition){
case WindowOpenDisposition.NewBackgroundTab:
case WindowOpenDisposition.NewForegroundTab:
@@ -20,6 +18,11 @@ namespace TweetDuck.Core.Handling.General{
}
}
public bool OnBeforePopup(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser){
newBrowser = null;
return HandleLinkClick(browserControl, targetDisposition, targetUrl);
}
public void OnAfterCreated(IWebBrowser browserControl, IBrowser browser){}
public bool DoClose(IWebBrowser browserControl, IBrowser browser){

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
using CefSharp;
using TweetDuck.Core.Handling.General;
namespace TweetDuck.Core.Handling{
sealed class RequestHandlerBrowser : RequestHandlerBase{

View File

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

View File

@@ -8,14 +8,14 @@ using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling;
using TweetDuck.Core.Handling.General;
using TweetDuck.Core.Other.Management;
using TweetDuck.Core.Other.Analytics;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Notification{
partial class FormNotificationBase : Form{
protected static int FontSizeLevel{
get{
switch(TweetDeckBridge.FontSizeClass){
switch(TweetDeckBridge.FontSize){
case "largest": return 4;
case "large": return 3;
case "small": return 1;
@@ -25,7 +25,7 @@ namespace TweetDuck.Core.Notification{
}
}
protected Point PrimaryLocation{
protected virtual Point PrimaryLocation{
get{
UserConfig config = Program.UserConfig;
Screen screen;
@@ -66,31 +66,39 @@ namespace TweetDuck.Core.Notification{
}
public bool IsNotificationVisible => Location != ControlExtensions.InvisibleLocation;
protected virtual bool CanDragWindow => true;
public new Point Location{
get => base.Location;
get{
return base.Location;
}
set{
Visible = (base.Location = value) != ControlExtensions.InvisibleLocation;
FormBorderStyle = GetBorderStyle(CanResizeWindow);
FormBorderStyle = NotificationBorderStyle;
}
}
public bool CanResizeWindow{
get => FormBorderStyle == FormBorderStyle.Sizable || FormBorderStyle == FormBorderStyle.SizableToolWindow;
set => FormBorderStyle = GetBorderStyle(value);
protected virtual FormBorderStyle NotificationBorderStyle{
get{
if (WindowsUtils.ShouldAvoidToolWindow && Visible){ // Visible = workaround for alt+tab
return FormBorderStyle.FixedSingle;
}
else{
return FormBorderStyle.FixedToolWindow;
}
}
}
public Func<bool> CanMoveWindow { get; set; } = () => true;
protected override bool ShowWithoutActivation => true;
protected double SizeScale => dpiScale*Program.UserConfig.ZoomMultiplier;
protected float DpiScale { get; }
protected double SizeScale => DpiScale*Program.UserConfig.ZoomLevel/100.0;
protected readonly FormBrowser owner;
protected readonly ChromiumWebBrowser browser;
private readonly ResourceHandlerNotification resourceHandler = new ResourceHandlerNotification();
private readonly float dpiScale;
private TweetNotification currentNotification;
private int pauseCounter;
@@ -101,6 +109,8 @@ namespace TweetDuck.Core.Notification{
public bool CanViewDetail => currentNotification != null && !string.IsNullOrEmpty(currentNotification.ColumnId) && !string.IsNullOrEmpty(currentNotification.ChirpId);
public bool IsPaused => pauseCounter > 0;
protected bool IsCursorOverBrowser => browser.Bounds.Contains(PointToClient(Cursor.Position));
public bool FreezeTimer { get; set; }
public bool ContextMenuOpen { get; set; }
@@ -115,18 +125,19 @@ namespace TweetDuck.Core.Notification{
this.browser = new ChromiumWebBrowser("about:blank"){
MenuHandler = new ContextMenuNotification(this, enableContextMenu),
JsDialogHandler = new JavaScriptDialogHandler(),
LifeSpanHandler = new LifeSpanHandler()
LifeSpanHandler = new LifeSpanHandler(),
RequestHandler = new RequestHandlerBase()
};
this.browser.Dock = DockStyle.None;
this.browser.ClientSize = ClientSize;
this.browser.IsBrowserInitializedChanged += Browser_IsBrowserInitializedChanged;
this.browser.IsBrowserInitializedChanged += browser_IsBrowserInitializedChanged;
#if DEBUG
this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
#endif
this.dpiScale = this.GetDPIScale();
DpiScale = this.GetDPIScale();
DefaultResourceHandlerFactory handlerFactory = (DefaultResourceHandlerFactory)browser.ResourceHandlerFactory;
handlerFactory.RegisterHandler(TwitterUtils.TweetDeckURL, this.resourceHandler);
@@ -143,34 +154,34 @@ namespace TweetDuck.Core.Notification{
}
protected override void WndProc(ref Message m){
if (m.Msg == 0x0112 && (m.WParam.ToInt32() & 0xFFF0) == 0xF010 && !CanMoveWindow()){ // WM_SYSCOMMAND, SC_MOVE
if (m.Msg == 0x0112 && (m.WParam.ToInt32() & 0xFFF0) == 0xF010 && !CanDragWindow){ // WM_SYSCOMMAND, SC_MOVE
return;
}
base.WndProc(ref m);
}
public void TriggerAnalyticsEvent(AnalyticsFile.Event e){
owner.TriggerAnalyticsEvent(e);
}
// event handlers
private void owner_FormClosed(object sender, FormClosedEventArgs e){
Close();
}
private void Browser_IsBrowserInitializedChanged(object sender, IsBrowserInitializedChangedEventArgs e){
private void browser_IsBrowserInitializedChanged(object sender, IsBrowserInitializedChangedEventArgs e){
if (e.IsBrowserInitialized){
Initialized?.Invoke(this, EventArgs.Empty);
int identifier = browser.GetBrowser().Identifier;
Disposed += (sender2, args2) => BrowserProcesses.Forget(identifier);
}
}
// notification methods
public virtual void HideNotification(bool loadBlank){
if (loadBlank){
public virtual void HideNotification(){
browser.Load("about:blank");
}
DisplayTooltip(null);
Location = ControlExtensions.InvisibleLocation;
currentNotification = null;
@@ -191,14 +202,15 @@ namespace TweetDuck.Core.Notification{
}
protected virtual string GetTweetHTML(TweetNotification tweet){
string bodyClasses = browser.Bounds.Contains(PointToClient(Cursor.Position)) ? "td-hover" : string.Empty;
return tweet.GenerateHtml(bodyClasses);
return tweet.GenerateHtml(IsCursorOverBrowser ? "td-hover" : string.Empty);
}
protected virtual void LoadTweet(TweetNotification tweet){
currentNotification = tweet;
resourceHandler.SetHTML(GetTweetHTML(tweet));
browser.Load(TwitterUtils.TweetDeckURL);
DisplayTooltip(null);
}
protected virtual void SetNotificationSize(int width, int height){
@@ -233,14 +245,5 @@ namespace TweetDuck.Core.Notification{
toolTip.Show(text, this, position);
}
}
private FormBorderStyle GetBorderStyle(bool sizable){
if (WindowsUtils.ShouldAvoidToolWindow && Visible){ // Visible = workaround for alt+tab
return sizable ? FormBorderStyle.Sizable : FormBorderStyle.FixedSingle;
}
else{
return sizable ? FormBorderStyle.SizableToolWindow : FormBorderStyle.FixedToolWindow;
}
}
}
}

View File

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

View File

@@ -5,6 +5,7 @@ using System.Windows.Forms;
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling;
using TweetDuck.Core.Other.Analytics;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Plugins;
@@ -12,14 +13,14 @@ using TweetDuck.Plugins.Enums;
using TweetDuck.Resources;
namespace TweetDuck.Core.Notification{
partial class FormNotificationMain : FormNotificationBase{
abstract partial class FormNotificationMain : FormNotificationBase{
private const string NotificationScriptFile = "notification.js";
private const int TimerBarHeight = 4;
private static readonly string NotificationScriptIdentifier = ScriptLoader.GetRootIdentifier(NotificationScriptFile);
private static readonly string NotificationJS = ScriptLoader.LoadResource(NotificationScriptFile);
private readonly PluginManager plugins;
private readonly int timerBarHeight;
protected int timeLeft, totalTime;
protected bool pausedDuringNotification;
@@ -31,9 +32,9 @@ namespace TweetDuck.Core.Notification{
private bool? prevDisplayTimer;
private int? prevFontSize;
public bool RequiresResize{
public virtual bool RequiresResize{
get{
return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Program.UserConfig.DisplayNotificationTimer || prevFontSize != FontSizeLevel || CanResizeWindow;
return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Program.UserConfig.DisplayNotificationTimer || prevFontSize != FontSizeLevel;
}
set{
@@ -72,18 +73,17 @@ namespace TweetDuck.Core.Notification{
}
}
public Size BrowserSize{
get => Program.UserConfig.DisplayNotificationTimer ? new Size(ClientSize.Width, ClientSize.Height-TimerBarHeight) : ClientSize;
}
public Size BrowserSize => Program.UserConfig.DisplayNotificationTimer ? new Size(ClientSize.Width, ClientSize.Height-timerBarHeight) : ClientSize;
public FormNotificationMain(FormBrowser owner, PluginManager pluginManager, bool enableContextMenu) : base(owner, enableContextMenu){
protected FormNotificationMain(FormBrowser owner, PluginManager pluginManager, bool enableContextMenu) : base(owner, enableContextMenu){
InitializeComponent();
this.plugins = pluginManager;
this.timerBarHeight = BrowserUtils.Scale(4, DpiScale);
browser.KeyboardHandler = new KeyboardHandlerNotification(this);
browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(owner, this));
browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge.Notification(owner, this));
browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
browser.LoadingStateChanged += Browser_LoadingStateChanged;
@@ -113,8 +113,8 @@ namespace TweetDuck.Core.Notification{
if (nCode == 0){
int eventType = wParam.ToInt32();
if (eventType == NativeMethods.WM_MOUSEWHEEL && browser.Bounds.Contains(PointToClient(Cursor.Position))){
browser.SendMouseWheelEvent(0, 0, 0, BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Program.UserConfig.NotificationScrollSpeed/100.0), CefEventFlags.None);
if (eventType == NativeMethods.WM_MOUSEWHEEL && IsCursorOverBrowser){
browser.SendMouseWheelEvent(0, 0, 0, BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Program.UserConfig.NotificationScrollSpeed*0.01), CefEventFlags.None);
return NativeMethods.HOOK_HANDLED;
}
else if (eventType == NativeMethods.WM_XBUTTONDOWN && DesktopBounds.Contains(Cursor.Position)){
@@ -128,6 +128,7 @@ namespace TweetDuck.Core.Notification{
}
blockXButtonUp = true;
this.InvokeAsyncSafe(() => TriggerAnalyticsEvent(AnalyticsFile.Event.NotificationExtraMouseButton));
return NativeMethods.HOOK_HANDLED;
}
else if (eventType == NativeMethods.WM_XBUTTONUP && blockXButtonUp){
@@ -148,7 +149,7 @@ namespace TweetDuck.Core.Notification{
private void FormNotification_FormClosing(object sender, FormClosingEventArgs e){
if (e.CloseReason == CloseReason.UserClosing){
HideNotification(true);
HideNotification();
e.Cancel = true;
}
}
@@ -176,12 +177,14 @@ namespace TweetDuck.Core.Notification{
}
private void timerHideProgress_Tick(object sender, EventArgs e){
if (Bounds.Contains(Cursor.Position) || FreezeTimer || ContextMenuOpen)return;
if (Bounds.Contains(Cursor.Position) || FreezeTimer || ContextMenuOpen){
return;
}
timeLeft -= timerProgress.Interval;
int value = BrowserUtils.Scale(1025, (totalTime-timeLeft)/(double)totalTime);
progressBarTimer.SetValueInstant(Math.Min(1000, Math.Max(0, Program.UserConfig.NotificationTimerCountDown ? 1000-value : value)));
int value = BrowserUtils.Scale(progressBarTimer.Maximum+25, (totalTime-timeLeft)/(double)totalTime);
progressBarTimer.SetValueInstant(Program.UserConfig.NotificationTimerCountDown ? progressBarTimer.Maximum-value : value);
if (timeLeft <= 0){
FinishCurrentNotification();
@@ -194,21 +197,10 @@ namespace TweetDuck.Core.Notification{
LoadTweet(notification);
}
public void ShowNotificationForSettings(bool reset){
if (reset){
LoadTweet(TweetNotification.ExampleTweet);
}
else{
PrepareAndDisplayWindow();
}
public override void HideNotification(){
base.HideNotification();
UpdateTitle();
}
public override void HideNotification(bool loadBlank){
base.HideNotification(loadBlank);
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0;
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
timerProgress.Stop();
totalTime = 0;
@@ -251,14 +243,14 @@ namespace TweetDuck.Core.Notification{
protected override void LoadTweet(TweetNotification tweet){
timerProgress.Stop();
totalTime = timeLeft = tweet.GetDisplayDuration(Program.UserConfig.NotificationDurationValue);
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0;
progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? progressBarTimer.Maximum : progressBarTimer.Minimum;
base.LoadTweet(tweet);
}
protected override void SetNotificationSize(int width, int height){
if (Program.UserConfig.DisplayNotificationTimer){
ClientSize = new Size(width, height+TimerBarHeight);
ClientSize = new Size(width, height+timerBarHeight);
progressBarTimer.Visible = true;
}
else{
@@ -269,7 +261,7 @@ namespace TweetDuck.Core.Notification{
browser.ClientSize = new Size(width, height);
}
private void PrepareAndDisplayWindow(){
protected void PrepareAndDisplayWindow(){
if (RequiresResize){
RequiresResize = false;
SetNotificationSize(BaseClientWidth, BaseClientHeight);

View File

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

View File

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

View File

@@ -12,6 +12,8 @@ using TweetDuck.Resources;
namespace TweetDuck.Core.Notification.Screenshot{
sealed class FormNotificationScreenshotable : FormNotificationBase{
protected override bool CanDragWindow => false;
private readonly PluginManager plugins;
public FormNotificationScreenshotable(Action callback, FormBrowser owner, PluginManager pluginManager) : base(owner, false){
@@ -22,7 +24,7 @@ namespace TweetDuck.Core.Notification.Screenshot{
browser.LoadingStateChanged += (sender, args) => {
if (!args.IsLoading){
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

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

View File

@@ -5,25 +5,11 @@ using TweetDuck.Resources;
namespace TweetDuck.Core.Notification{
sealed class TweetNotification{
private const string DefaultFontSizeClass = "medium";
private const string DefaultHeadContents = @"<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 DefaultHeadLayout = @"<html id='tduck' class='os-windows txt-size--14' data-td-font='medium' data-td-theme='dark'><head><meta charset='utf-8'><meta http-equiv='X-UA-Compatible' content='chrome=1'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/font.5ef884f9f9.css'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/app-dark.5631e0dd42.css'><style type='text/css'>body{background:#222426}</style>";
private static readonly string CustomCSS = ScriptLoader.LoadResource("styles/notification.css");
private static string ExampleTweetHTML;
public static TweetNotification ExampleTweet{
get{
if (ExampleTweetHTML == null){
ExampleTweetHTML = ScriptLoader.LoadResource("pages/example.html", true);
#if DEBUG
ExampleTweetHTML = ExampleTweetHTML.Replace("</p>", @"</p><div style='margin-top:256px'>Scrollbar test padding...</div>");
#endif
}
return new TweetNotification(string.Empty, string.Empty, "Home", ExampleTweetHTML, 95, string.Empty, string.Empty, true);
}
public static TweetNotification Example(string html, int characters){
return new TweetNotification(string.Empty, string.Empty, "Home", html, characters, string.Empty, string.Empty, true);
}
public enum Position{
@@ -67,8 +53,7 @@ namespace TweetDuck.Core.Notification{
public string GenerateHtml(string bodyClasses = null, bool enableCustomCSS = true){
StringBuilder build = new StringBuilder();
build.Append("<!DOCTYPE html>");
build.Append("<html class='os-windows txt-base-").Append(TweetDeckBridge.FontSizeClass ?? DefaultFontSizeClass).Append("'>");
build.Append("<head>").Append(TweetDeckBridge.NotificationHeadContents ?? DefaultHeadContents);
build.Append(TweetDeckBridge.NotificationHeadLayout ?? DefaultHeadLayout);
if (enableCustomCSS){
build.Append("<style type='text/css'>").Append(CustomCSS).Append("</style>");
@@ -85,7 +70,7 @@ namespace TweetDuck.Core.Notification{
build.Append(' ').Append(bodyClasses);
}
build.Append('\'').Append(isExample ? " td-example-notification" : "").Append("><div class='column' style='width:100%;height:auto;overflow:initial;'>");
build.Append('\'').Append(isExample ? " td-example-notification" : "").Append("><div class='column' style='width:100%!important;height:auto!important;overflow:initial!important;'>");
build.Append(html);
build.Append("</div></body>");
build.Append("</html>");

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
using System.ComponentModel;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Other{
@@ -21,7 +20,7 @@ namespace TweetDuck.Core.Other{
}
private void OnLinkClicked(object sender, LinkLabelLinkClickedEventArgs e){
BrowserUtils.OpenExternalBrowserUnsafe(e.Link.LinkData as string);
BrowserUtils.OpenExternalBrowser(e.Link.LinkData as string);
}
private void FormAbout_HelpRequested(object sender, HelpEventArgs hlpevent){
@@ -34,7 +33,7 @@ namespace TweetDuck.Core.Other{
}
private void ShowGuide(){
new FormGuide().Show();
FormGuide.Show();
Close();
}
}

View File

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

View File

@@ -35,8 +35,8 @@ namespace TweetDuck.Core.Other{
}
private void ReloadPluginList(){
flowLayoutPlugins.SuspendLayout();
flowLayoutPlugins.Controls.Clear();
flowLayoutPlugins.SuspendLayout();
foreach(Plugin plugin in pluginManager.Plugins.OrderBy(GetPluginOrderIndex).ThenBy(plugin => plugin.Name)){
flowLayoutPlugins.Controls.Add(new PluginControl(pluginManager, plugin));

View File

@@ -3,6 +3,9 @@ using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling.General;
using TweetDuck.Core.Notification.Example;
using TweetDuck.Core.Other.Analytics;
using TweetDuck.Core.Other.Settings;
using TweetDuck.Core.Other.Settings.Dialogs;
using TweetDuck.Core.Utils;
@@ -21,7 +24,7 @@ namespace TweetDuck.Core.Other{
public bool ShouldReloadBrowser { get; private set; }
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, Type startTab){
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, AnalyticsManager analytics, Type startTab){
InitializeComponent();
Text = Program.BrandName+" Options";
@@ -33,20 +36,22 @@ namespace TweetDuck.Core.Other{
this.buttonHeight = BrowserUtils.Scale(39, this.GetDPIScale()) | 1;
AddButton("General", () => new TabSettingsGeneral(updates));
AddButton("General", () => new TabSettingsGeneral(this.browser, updates));
AddButton("Locales", () => new TabSettingsLocales());
AddButton("System Tray", () => new TabSettingsTray());
AddButton("Notifications", () => new TabSettingsNotifications(browser.CreateNotificationForm(false)));
AddButton("Notifications", () => new TabSettingsNotifications(new FormNotificationExample(this.browser, this.plugins)));
AddButton("Sounds", () => new TabSettingsSounds());
AddButton("Feedback", () => new TabSettingsFeedback());
AddButton("Advanced", () => new TabSettingsAdvanced(browser.ReinjectCustomCSS));
AddButton("Feedback", () => new TabSettingsFeedback(analytics, AnalyticsReportGenerator.ExternalInfo.From(this.browser), this.plugins));
AddButton("Advanced", () => new TabSettingsAdvanced(this.browser.ReinjectCustomCSS));
SelectTab(tabs[startTab ?? typeof(TabSettingsGeneral)]);
}
private void FormSettings_FormClosing(object sender, FormClosingEventArgs e){
currentTab.Control.OnClosing();
foreach(SettingsTab tab in tabs.Values){
if (tab.IsInitialized){
tab.Control.OnClosing();
tab.Control.Dispose();
}
}
@@ -56,20 +61,21 @@ namespace TweetDuck.Core.Other{
}
private void btnManageOptions_Click(object sender, EventArgs e){
foreach(SettingsTab tab in tabs.Values){
if (tab.IsInitialized){
tab.Control.OnClosing();
}
}
currentTab.Control.OnClosing();
using(DialogSettingsManage dialog = new DialogSettingsManage(plugins)){
if (dialog.ShowDialog() == DialogResult.OK){
FormClosing -= FormSettings_FormClosing;
if (dialog.ShowDialog() == DialogResult.OK){
browser.ResumeNotification();
BrowserProcessHandler.UpdatePrefs();
ShouldReloadBrowser = dialog.ShouldReloadBrowser;
Close();
}
else{
FormClosing += FormSettings_FormClosing;
}
}
}
@@ -113,6 +119,7 @@ namespace TweetDuck.Core.Other{
private void SelectTab(SettingsTab tab){
if (currentTab != null){
currentTab.Button.BackColor = SystemColors.Control;
currentTab.Control.OnClosing();
}
tab.Button.BackColor = tab.Button.FlatAppearance.MouseDownBackColor;
@@ -157,11 +164,7 @@ namespace TweetDuck.Core.Other{
private sealed class SettingsTab{
public Button Button { get; }
public BaseTabSettings Control{
get => control ?? (control = constructor());
set => control = value;
}
public BaseTabSettings Control => control ?? (control = constructor());
public bool IsInitialized => control != null;
private readonly Func<BaseTabSettings> constructor;

View File

@@ -1,27 +0,0 @@
using System.Collections.Generic;
using System.Diagnostics;
using CefSharp;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Other.Management{
static class BrowserProcesses{
private static readonly Dictionary<int, int> PIDs = new Dictionary<int, int>();
public static void Link(int identifier, int pid){
PIDs[identifier] = pid;
}
public static void Forget(int identifier){
PIDs.Remove(identifier);
}
public static Process FindProcess(IBrowser browser){
if (PIDs.TryGetValue(browser.Identifier, out int pid) && WindowsUtils.IsChildProcess(pid)){ // child process is checked in two places for safety
return Process.GetProcessById(pid);
}
else{
return null;
}
}
}
}

View File

@@ -1,91 +0,0 @@
using System;
using System.Diagnostics;
using System.Timers;
using System.Windows.Forms;
using CefSharp;
using Timer = System.Timers.Timer;
namespace TweetDuck.Core.Other.Management{
sealed class MemoryUsageTracker : IDisposable{
private const int IntervalMemoryCheck = 60000*30; // 30 minutes
private const int IntervalCleanupAttempt = 60000*5; // 5 minutes
private readonly string script;
private readonly Timer timer;
private Form owner;
private IBrowser browser;
private long threshold;
private bool needsCleanup;
public MemoryUsageTracker(string cleanupFunctionName){
this.script = $"window.{cleanupFunctionName} && window.{cleanupFunctionName}()";
this.timer = new Timer{ Interval = IntervalMemoryCheck };
this.timer.Elapsed += timer_Elapsed;
}
public void Start(Form owner, IBrowser browser, int thresholdMB){
Stop();
this.owner = owner;
this.browser = browser;
this.threshold = thresholdMB*1024L*1024L;
this.timer.SynchronizingObject = owner;
this.timer.Start();
}
public void Stop(){
timer.Stop();
timer.SynchronizingObject = null;
owner = null;
browser = null;
SetNeedsCleanup(false);
}
public void Dispose(){
timer.SynchronizingObject = null;
timer.Dispose();
owner = null;
browser = null;
}
private void SetNeedsCleanup(bool value){
if (needsCleanup != value){
needsCleanup = value;
timer.Interval = value ? IntervalCleanupAttempt : IntervalMemoryCheck; // restarts timer
}
}
private void timer_Elapsed(object sender, ElapsedEventArgs e){
if (owner == null || browser == null){
return;
}
if (needsCleanup){
if (!owner.ContainsFocus){
using(IFrame frame = browser.MainFrame){
frame.EvaluateScriptAsync(script).ContinueWith(task => {
JavascriptResponse response = task.Result;
if (response.Success && (response.Result as bool? ?? false)){
SetNeedsCleanup(false);
}
});
}
}
}
else{
try{
using(Process process = BrowserProcesses.FindProcess(browser)){
if (process?.PrivateMemorySize64 > threshold){
SetNeedsCleanup(true);
}
}
}catch{
// ignore I guess?
}
}
}
}
}

View File

@@ -8,8 +8,6 @@ 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){
@@ -25,6 +23,7 @@ namespace TweetDuck.Core.Other.Management{
private readonly Form owner;
private string lastUrl;
private string lastUsername;
private Process currentProcess;
private DuplexPipe.Server currentPipe;
@@ -35,20 +34,21 @@ namespace TweetDuck.Core.Other.Management{
this.owner.FormClosing += owner_FormClosing;
}
public void Launch(string url){
public void Launch(string url, string username){
if (Running){
Destroy();
isClosing = false;
}
lastUrl = url;
lastUsername = username;
try{
currentPipe = DuplexPipe.CreateServer();
currentPipe.DataIn += currentPipe_DataIn;
if ((currentProcess = Process.Start(new ProcessStartInfo{
FileName = PlayerExe,
FileName = Path.Combine(Program.ProgramPath, "TweetDuck.Video.exe"),
Arguments = $"{owner.Handle} {Program.UserConfig.VideoPlayerVolume} \"{url}\" \"{currentPipe.GenerateToken()}\"",
UseShellExecute = false,
RedirectStandardOutput = true
@@ -84,7 +84,7 @@ namespace TweetDuck.Core.Other.Management{
break;
case "download":
TwitterUtils.DownloadVideo(lastUrl);
TwitterUtils.DownloadVideo(lastUrl, lastUsername);
break;
case "rip":
@@ -157,14 +157,14 @@ namespace TweetDuck.Core.Other.Management{
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)){
if (FormMessage.Error("Video Playback Error", "Error launching video player, this may be caused by missing Windows Media Player. Do you want to open the video in your browser?", FormMessage.Yes, FormMessage.No)){
BrowserUtils.OpenExternalBrowser(lastUrl);
}
break;
case 4: // CODE_MEDIA_ERROR
if (FormMessage.Error("Video Playback Error", "The video could not be loaded, most likely due to unknown format. Do you want to open the video in a browser?", FormMessage.Yes, FormMessage.No)){
if (FormMessage.Error("Video Playback Error", "The video could not be loaded, most likely due to unknown format. Do you want to open the video in your browser?", FormMessage.Yes, FormMessage.No)){
BrowserUtils.OpenExternalBrowser(lastUrl);
}

View File

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

View File

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

View File

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

View File

@@ -19,7 +19,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
}
private void btnHelp_Click(object sender, EventArgs e){
BrowserUtils.OpenExternalBrowserUnsafe("http://peter.sh/experiments/chromium-command-line-switches/");
BrowserUtils.OpenExternalBrowser("http://peter.sh/experiments/chromium-command-line-switches/");
}
private void btnApply_Click(object sender, EventArgs e){

View File

@@ -16,7 +16,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
set{
// this will call events and SetFlag, which also updates the UI
cbConfig.Checked = value.HasFlag(ExportFileFlags.Config);
cbConfig.Checked = value.HasFlag(ExportFileFlags.UserConfig);
cbSession.Checked = value.HasFlag(ExportFileFlags.Session);
cbPluginData.Checked = value.HasFlag(ExportFileFlags.PluginData);
}
@@ -42,7 +42,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
}
private void cbConfig_CheckedChanged(object sender, EventArgs e){
SetFlag(ExportFileFlags.Config, cbConfig.Checked);
SetFlag(ExportFileFlags.UserConfig, cbConfig.Checked);
}
private void cbSession_CheckedChanged(object sender, EventArgs e){
@@ -63,7 +63,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
currentState = State.Reset;
Text = "Restore Defaults";
Flags = ExportFileFlags.Config;
Flags = ExportFileFlags.UserConfig;
}
// Import
@@ -108,10 +108,18 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
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)){
if (Flags.HasFlag(ExportFileFlags.UserConfig)){
Program.ResetConfig();
}
if (Flags.HasFlag(ExportFileFlags.SystemConfig)){
try{
File.Delete(Program.SystemConfigFilePath);
}catch(Exception ex){
Program.Reporter.HandleException("System Config Reset Error", "Could not delete system config.", true, ex);
}
}
if (Flags.HasFlag(ExportFileFlags.PluginData)){
try{
File.Delete(Program.PluginConfigFilePath);
@@ -124,6 +132,9 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
if (Flags.HasFlag(ExportFileFlags.Session)){
Program.Restart(Arguments.ArgDeleteCookies);
}
else if (Flags.HasFlag(ExportFileFlags.SystemConfig)){
Program.Restart();
}
else{
ShouldReloadBrowser = true;
}
@@ -139,8 +150,13 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
Program.UserConfig.Reload();
if (importManager.IsRestarting){
if (Flags.HasFlag(ExportFileFlags.Session)){
Program.Restart(Arguments.ArgImportCookies);
}
else if (Flags.HasFlag(ExportFileFlags.SystemConfig)){
Program.Restart();
}
}
else{
ShouldReloadBrowser = true;
}
@@ -171,6 +187,8 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
}
Program.UserConfig.Save();
Program.SystemConfig.Save();
ExportManager manager = new ExportManager(file, plugins);
if (!manager.Export(Flags)){

View File

@@ -29,10 +29,8 @@
this.cbLogging = new System.Windows.Forms.CheckBox();
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.cbDebugUpdates = new System.Windows.Forms.CheckBox();
this.comboLocale = new System.Windows.Forms.ComboBox();
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();
@@ -40,7 +38,7 @@
// btnCancel
//
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnCancel.Location = new System.Drawing.Point(216, 217);
this.btnCancel.Location = new System.Drawing.Point(215, 163);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnCancel.Size = new System.Drawing.Size(56, 23);
@@ -52,7 +50,7 @@
// btnRestart
//
this.btnRestart.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnRestart.Location = new System.Drawing.Point(153, 217);
this.btnRestart.Location = new System.Drawing.Point(152, 163);
this.btnRestart.Name = "btnRestart";
this.btnRestart.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnRestart.Size = new System.Drawing.Size(57, 23);
@@ -83,54 +81,33 @@
this.toolTip.SetToolTip(this.cbDebugUpdates, "Allows updating to pre-releases.");
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.Location = new System.Drawing.Point(15, 83);
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.");
this.toolTip.SetToolTip(this.tbDataFolder, "Path to the data folder. Must be either an absolute path,\r\nor a simple folder nam" +
"e that will be created in LocalAppData.");
//
// tbShortcutTarget
//
this.tbShortcutTarget.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.tbShortcutTarget.Cursor = System.Windows.Forms.Cursors.Hand;
this.tbShortcutTarget.Location = new System.Drawing.Point(15, 186);
this.tbShortcutTarget.Location = new System.Drawing.Point(15, 134);
this.tbShortcutTarget.Name = "tbShortcutTarget";
this.tbShortcutTarget.ReadOnly = true;
this.tbShortcutTarget.Size = new System.Drawing.Size(257, 20);
this.tbShortcutTarget.TabIndex = 7;
this.tbShortcutTarget.Click += new System.EventHandler(this.tbShortcutTarget_Click);
//
// labelLocale
//
this.labelLocale.AutoSize = true;
this.labelLocale.Location = new System.Drawing.Point(12, 67);
this.labelLocale.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelLocale.Name = "labelLocale";
this.labelLocale.Size = new System.Drawing.Size(39, 13);
this.labelLocale.TabIndex = 2;
this.labelLocale.Text = "Locale";
//
// labelDataFolder
//
this.labelDataFolder.AutoSize = true;
this.labelDataFolder.Location = new System.Drawing.Point(12, 119);
this.labelDataFolder.Location = new System.Drawing.Point(12, 67);
this.labelDataFolder.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelDataFolder.Name = "labelDataFolder";
this.labelDataFolder.Size = new System.Drawing.Size(62, 13);
@@ -140,7 +117,7 @@
// labelShortcutTarget
//
this.labelShortcutTarget.AutoSize = true;
this.labelShortcutTarget.Location = new System.Drawing.Point(12, 170);
this.labelShortcutTarget.Location = new System.Drawing.Point(12, 118);
this.labelShortcutTarget.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelShortcutTarget.Name = "labelShortcutTarget";
this.labelShortcutTarget.Size = new System.Drawing.Size(155, 13);
@@ -151,13 +128,11 @@
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(284, 252);
this.ClientSize = new System.Drawing.Size(284, 198);
this.Controls.Add(this.tbShortcutTarget);
this.Controls.Add(this.labelShortcutTarget);
this.Controls.Add(this.tbDataFolder);
this.Controls.Add(this.labelDataFolder);
this.Controls.Add(this.comboLocale);
this.Controls.Add(this.labelLocale);
this.Controls.Add(this.cbDebugUpdates);
this.Controls.Add(this.cbLogging);
this.Controls.Add(this.btnRestart);
@@ -180,8 +155,6 @@
private System.Windows.Forms.CheckBox cbLogging;
private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.CheckBox cbDebugUpdates;
private System.Windows.Forms.Label labelLocale;
private System.Windows.Forms.ComboBox comboLocale;
private System.Windows.Forms.Label labelDataFolder;
private System.Windows.Forms.TextBox tbDataFolder;
private System.Windows.Forms.TextBox tbShortcutTarget;

View File

@@ -1,33 +1,20 @@
using System;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using TweetDuck.Configuration;
using TweetDuck.Data;
namespace TweetDuck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsRestart : Form{
private const string DefaultLocale = "en-US";
public CommandLineArgs Args { get; private set; }
public DialogSettingsRestart(CommandLineArgs currentArgs){
InitializeComponent();
try{
object[] locales = Directory.EnumerateFiles(Path.Combine(Program.ProgramPath, "locales"), "*.pak", SearchOption.TopDirectoryOnly).Select(Path.GetFileNameWithoutExtension).ToArray<object>();
comboLocale.Items.AddRange(locales);
}catch{
comboLocale.Items.Add(DefaultLocale);
}
cbLogging.Checked = currentArgs.HasFlag(Arguments.ArgLogging);
cbDebugUpdates.Checked = currentArgs.HasFlag(Arguments.ArgDebugUpdates);
comboLocale.SelectedItem = currentArgs.GetValue(Arguments.ArgLocale, DefaultLocale);
cbLogging.CheckedChanged += control_Change;
cbDebugUpdates.CheckedChanged += control_Change;
comboLocale.SelectedValueChanged += control_Change;
if (Program.IsPortable){
tbDataFolder.Text = "Not available in portable version";
@@ -54,12 +41,6 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
Args.AddFlag(Arguments.ArgDebugUpdates);
}
string locale = comboLocale.SelectedItem as string;
if (!string.IsNullOrWhiteSpace(locale) && locale != DefaultLocale){
Args.SetValue(Arguments.ArgLocale, locale);
}
if (!string.IsNullOrWhiteSpace(tbDataFolder.Text) && tbDataFolder.Enabled){
Args.SetValue(Arguments.ArgDataFolder, tbDataFolder.Text);
}

View File

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

View File

@@ -25,10 +25,14 @@ namespace TweetDuck.Core.Other.Settings.Export{
public bool Export(ExportFileFlags flags){
try{
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
if (flags.HasFlag(ExportFileFlags.Config)){
if (flags.HasFlag(ExportFileFlags.UserConfig)){
stream.WriteFile("config", Program.UserConfigFilePath);
}
if (flags.HasFlag(ExportFileFlags.SystemConfig)){
stream.WriteFile("system", Program.SystemConfigFilePath);
}
if (flags.HasFlag(ExportFileFlags.PluginData)){
stream.WriteFile("plugin.config", Program.PluginConfigFilePath);
@@ -67,7 +71,11 @@ namespace TweetDuck.Core.Other.Settings.Export{
while((key = stream.SkipFile()) != null){
switch(key){
case "config":
flags |= ExportFileFlags.Config;
flags |= ExportFileFlags.UserConfig;
break;
case "system":
flags |= ExportFileFlags.SystemConfig;
break;
case "plugin.config":
@@ -99,12 +107,20 @@ namespace TweetDuck.Core.Other.Settings.Export{
while((entry = stream.ReadFile()) != null){
switch(entry.KeyName){
case "config":
if (flags.HasFlag(ExportFileFlags.Config)){
if (flags.HasFlag(ExportFileFlags.UserConfig)){
entry.WriteToFile(Program.UserConfigFilePath);
}
break;
case "system":
if (flags.HasFlag(ExportFileFlags.SystemConfig)){
entry.WriteToFile(Program.SystemConfigFilePath);
IsRestarting = true;
}
break;
case "plugin.config":
if (flags.HasFlag(ExportFileFlags.PluginData)){
entry.WriteToFile(Program.PluginConfigFilePath);

View File

@@ -33,42 +33,42 @@
this.btnRestart = new System.Windows.Forms.Button();
this.btnOpenAppFolder = new System.Windows.Forms.Button();
this.btnOpenDataFolder = new System.Windows.Forms.Button();
this.numMemoryThreshold = new TweetDuck.Core.Controls.NumericUpDownEx();
this.checkBrowserGCReload = new System.Windows.Forms.CheckBox();
this.numClearCacheThreshold = new TweetDuck.Core.Controls.NumericUpDownEx();
this.checkClearCacheAuto = new System.Windows.Forms.CheckBox();
this.labelApp = new System.Windows.Forms.Label();
this.panelApp = new System.Windows.Forms.Panel();
this.panelAppButtons = new System.Windows.Forms.Panel();
this.labelPerformance = new System.Windows.Forms.Label();
this.panelPerformance = new System.Windows.Forms.Panel();
this.labelMemoryUsage = new System.Windows.Forms.Label();
this.panelClearCacheAuto = new System.Windows.Forms.Panel();
this.labelCache = new System.Windows.Forms.Label();
this.panelConfiguration = new System.Windows.Forms.Panel();
this.labelConfiguration = new System.Windows.Forms.Label();
((System.ComponentModel.ISupportInitialize)(this.numMemoryThreshold)).BeginInit();
this.panelApp.SuspendLayout();
this.panelPerformance.SuspendLayout();
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
((System.ComponentModel.ISupportInitialize)(this.numClearCacheThreshold)).BeginInit();
this.panelAppButtons.SuspendLayout();
this.panelClearCacheAuto.SuspendLayout();
this.panelConfiguration.SuspendLayout();
this.flowPanel.SuspendLayout();
this.SuspendLayout();
//
// btnClearCache
//
this.btnClearCache.Location = new System.Drawing.Point(5, 28);
this.btnClearCache.Location = new System.Drawing.Point(5, 172);
this.btnClearCache.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.btnClearCache.Name = "btnClearCache";
this.btnClearCache.Size = new System.Drawing.Size(144, 23);
this.btnClearCache.TabIndex = 1;
this.btnClearCache.Text = "Clear Cache (calculating)";
this.toolTip.SetToolTip(this.btnClearCache, "Clearing cache will free up space taken by downloaded images and other resources.");
this.btnClearCache.UseVisualStyleBackColor = true;
//
// checkHardwareAcceleration
//
this.checkHardwareAcceleration.AutoSize = true;
this.checkHardwareAcceleration.Location = new System.Drawing.Point(6, 5);
this.checkHardwareAcceleration.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3);
this.checkHardwareAcceleration.Location = new System.Drawing.Point(6, 124);
this.checkHardwareAcceleration.Margin = new System.Windows.Forms.Padding(6, 6, 3, 3);
this.checkHardwareAcceleration.Name = "checkHardwareAcceleration";
this.checkHardwareAcceleration.Size = new System.Drawing.Size(134, 17);
this.checkHardwareAcceleration.TabIndex = 0;
this.checkHardwareAcceleration.Text = "Hardware Acceleration";
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;
//
// btnEditCefArgs
@@ -79,7 +79,6 @@
this.btnEditCefArgs.Size = new System.Drawing.Size(144, 23);
this.btnEditCefArgs.TabIndex = 0;
this.btnEditCefArgs.Text = "Edit CEF Arguments";
this.toolTip.SetToolTip(this.btnEditCefArgs, "Set custom command line arguments for Chromium Embedded Framework.");
this.btnEditCefArgs.UseVisualStyleBackColor = true;
//
// btnEditCSS
@@ -89,7 +88,6 @@
this.btnEditCSS.Size = new System.Drawing.Size(144, 23);
this.btnEditCSS.TabIndex = 1;
this.btnEditCSS.Text = "Edit CSS";
this.toolTip.SetToolTip(this.btnEditCSS, "Set custom CSS for browser and notification windows.");
this.btnEditCSS.UseVisualStyleBackColor = true;
//
// btnRestartArgs
@@ -99,7 +97,6 @@
this.btnRestartArgs.Size = new System.Drawing.Size(144, 23);
this.btnRestartArgs.TabIndex = 3;
this.btnRestartArgs.Text = "Restart with Arguments";
this.toolTip.SetToolTip(this.btnRestartArgs, "Restarts the program with customizable\r\ncommand line arguments.");
this.btnRestartArgs.UseVisualStyleBackColor = true;
//
// btnRestart
@@ -109,7 +106,6 @@
this.btnRestart.Size = new System.Drawing.Size(144, 23);
this.btnRestart.TabIndex = 2;
this.btnRestart.Text = "Restart the Program";
this.toolTip.SetToolTip(this.btnRestart, "Restarts the program using the same command\r\nline arguments that were used at launch.");
this.btnRestart.UseVisualStyleBackColor = true;
//
// btnOpenAppFolder
@@ -120,7 +116,6 @@
this.btnOpenAppFolder.Size = new System.Drawing.Size(144, 23);
this.btnOpenAppFolder.TabIndex = 0;
this.btnOpenAppFolder.Text = "Open Program Folder";
this.toolTip.SetToolTip(this.btnOpenAppFolder, "Opens the folder where the app is located.");
this.btnOpenAppFolder.UseVisualStyleBackColor = true;
//
// btnOpenDataFolder
@@ -131,104 +126,94 @@
this.btnOpenDataFolder.Size = new System.Drawing.Size(144, 23);
this.btnOpenDataFolder.TabIndex = 1;
this.btnOpenDataFolder.Text = "Open Data Folder";
this.toolTip.SetToolTip(this.btnOpenDataFolder, "Opens the folder where your profile data is located.");
this.btnOpenDataFolder.UseVisualStyleBackColor = true;
//
// numMemoryThreshold
// numClearCacheThreshold
//
this.numMemoryThreshold.Increment = new decimal(new int[] {
50,
0,
0,
0});
this.numMemoryThreshold.Location = new System.Drawing.Point(202, 82);
this.numMemoryThreshold.Maximum = 2000;
this.numMemoryThreshold.Minimum = 200;
this.numMemoryThreshold.Name = "numMemoryThreshold";
this.numMemoryThreshold.Size = new System.Drawing.Size(97, 20);
this.numMemoryThreshold.TabIndex = 4;
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.numMemoryThreshold.Value = 400;
this.numClearCacheThreshold.Increment = 50;
this.numClearCacheThreshold.Location = new System.Drawing.Point(227, 4);
this.numClearCacheThreshold.Maximum = 1000;
this.numClearCacheThreshold.Minimum = 100;
this.numClearCacheThreshold.Name = "numClearCacheThreshold";
this.numClearCacheThreshold.Size = new System.Drawing.Size(72, 20);
this.numClearCacheThreshold.TabIndex = 4;
this.numClearCacheThreshold.TextSuffix = " MB";
this.numClearCacheThreshold.Value = 250;
//
// checkBrowserGCReload
// checkClearCacheAuto
//
this.checkBrowserGCReload.AutoSize = true;
this.checkBrowserGCReload.Location = new System.Drawing.Point(6, 84);
this.checkBrowserGCReload.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3);
this.checkBrowserGCReload.Name = "checkBrowserGCReload";
this.checkBrowserGCReload.Size = new System.Drawing.Size(190, 17);
this.checkBrowserGCReload.TabIndex = 3;
this.checkBrowserGCReload.Text = "Enable Browser Memory Threshold";
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.checkClearCacheAuto.AutoSize = true;
this.checkClearCacheAuto.Location = new System.Drawing.Point(6, 6);
this.checkClearCacheAuto.Margin = new System.Windows.Forms.Padding(6, 6, 3, 3);
this.checkClearCacheAuto.Name = "checkClearCacheAuto";
this.checkClearCacheAuto.Size = new System.Drawing.Size(215, 17);
this.checkClearCacheAuto.TabIndex = 3;
this.checkClearCacheAuto.Text = "Clear Cache Automatically When Above";
this.checkClearCacheAuto.UseVisualStyleBackColor = true;
//
// labelApp
//
this.labelApp.AutoSize = true;
this.labelApp.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelApp.Location = new System.Drawing.Point(6, 8);
this.labelApp.Margin = new System.Windows.Forms.Padding(0, 2, 0, 0);
this.labelApp.Location = new System.Drawing.Point(0, 0);
this.labelApp.Margin = new System.Windows.Forms.Padding(0);
this.labelApp.Name = "labelApp";
this.labelApp.Size = new System.Drawing.Size(38, 20);
this.labelApp.TabIndex = 0;
this.labelApp.Text = "App";
//
// panelApp
// panelAppButtons
//
this.panelApp.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelApp.Controls.Add(this.btnOpenDataFolder);
this.panelApp.Controls.Add(this.btnOpenAppFolder);
this.panelApp.Controls.Add(this.btnRestart);
this.panelApp.Controls.Add(this.btnRestartArgs);
this.panelApp.Location = new System.Drawing.Point(9, 31);
this.panelApp.Name = "panelApp";
this.panelApp.Size = new System.Drawing.Size(322, 59);
this.panelApp.TabIndex = 1;
this.panelAppButtons.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.panelAppButtons.Controls.Add(this.btnOpenDataFolder);
this.panelAppButtons.Controls.Add(this.btnOpenAppFolder);
this.panelAppButtons.Controls.Add(this.btnRestart);
this.panelAppButtons.Controls.Add(this.btnRestartArgs);
this.panelAppButtons.Location = new System.Drawing.Point(0, 20);
this.panelAppButtons.Margin = new System.Windows.Forms.Padding(0);
this.panelAppButtons.Name = "panelAppButtons";
this.panelAppButtons.Size = new System.Drawing.Size(322, 58);
this.panelAppButtons.TabIndex = 1;
//
// labelPerformance
//
this.labelPerformance.AutoSize = true;
this.labelPerformance.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelPerformance.Location = new System.Drawing.Point(6, 114);
this.labelPerformance.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
this.labelPerformance.Location = new System.Drawing.Point(0, 98);
this.labelPerformance.Margin = new System.Windows.Forms.Padding(0, 20, 0, 0);
this.labelPerformance.Name = "labelPerformance";
this.labelPerformance.Size = new System.Drawing.Size(100, 20);
this.labelPerformance.TabIndex = 2;
this.labelPerformance.Text = "Performance";
//
// panelPerformance
// panelClearCacheAuto
//
this.panelPerformance.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelPerformance.Controls.Add(this.checkBrowserGCReload);
this.panelPerformance.Controls.Add(this.numMemoryThreshold);
this.panelPerformance.Controls.Add(this.labelMemoryUsage);
this.panelPerformance.Controls.Add(this.checkHardwareAcceleration);
this.panelPerformance.Controls.Add(this.btnClearCache);
this.panelPerformance.Location = new System.Drawing.Point(9, 137);
this.panelPerformance.Name = "panelPerformance";
this.panelPerformance.Size = new System.Drawing.Size(322, 105);
this.panelPerformance.TabIndex = 3;
this.panelClearCacheAuto.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.panelClearCacheAuto.Controls.Add(this.checkClearCacheAuto);
this.panelClearCacheAuto.Controls.Add(this.numClearCacheThreshold);
this.panelClearCacheAuto.Location = new System.Drawing.Point(0, 198);
this.panelClearCacheAuto.Margin = new System.Windows.Forms.Padding(0);
this.panelClearCacheAuto.Name = "panelClearCacheAuto";
this.panelClearCacheAuto.Size = new System.Drawing.Size(322, 26);
this.panelClearCacheAuto.TabIndex = 3;
//
// labelMemoryUsage
// labelCache
//
this.labelMemoryUsage.AutoSize = true;
this.labelMemoryUsage.Location = new System.Drawing.Point(3, 66);
this.labelMemoryUsage.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelMemoryUsage.Name = "labelMemoryUsage";
this.labelMemoryUsage.Size = new System.Drawing.Size(78, 13);
this.labelMemoryUsage.TabIndex = 2;
this.labelMemoryUsage.Text = "Memory Usage";
this.labelCache.AutoSize = true;
this.labelCache.Location = new System.Drawing.Point(3, 156);
this.labelCache.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelCache.Name = "labelCache";
this.labelCache.Size = new System.Drawing.Size(38, 13);
this.labelCache.TabIndex = 2;
this.labelCache.Text = "Cache";
//
// panelConfiguration
//
this.panelConfiguration.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelConfiguration.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.panelConfiguration.Controls.Add(this.btnEditCSS);
this.panelConfiguration.Controls.Add(this.btnEditCefArgs);
this.panelConfiguration.Location = new System.Drawing.Point(9, 289);
this.panelConfiguration.Location = new System.Drawing.Point(0, 264);
this.panelConfiguration.Margin = new System.Windows.Forms.Padding(0);
this.panelConfiguration.Name = "panelConfiguration";
this.panelConfiguration.Size = new System.Drawing.Size(322, 29);
this.panelConfiguration.TabIndex = 5;
@@ -237,32 +222,49 @@
//
this.labelConfiguration.AutoSize = true;
this.labelConfiguration.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelConfiguration.Location = new System.Drawing.Point(6, 266);
this.labelConfiguration.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
this.labelConfiguration.Location = new System.Drawing.Point(0, 244);
this.labelConfiguration.Margin = new System.Windows.Forms.Padding(0, 20, 0, 0);
this.labelConfiguration.Name = "labelConfiguration";
this.labelConfiguration.Size = new System.Drawing.Size(104, 20);
this.labelConfiguration.TabIndex = 4;
this.labelConfiguration.Text = "Configuration";
//
// flowPanel
//
this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.flowPanel.Controls.Add(this.labelApp);
this.flowPanel.Controls.Add(this.panelAppButtons);
this.flowPanel.Controls.Add(this.labelPerformance);
this.flowPanel.Controls.Add(this.checkHardwareAcceleration);
this.flowPanel.Controls.Add(this.labelCache);
this.flowPanel.Controls.Add(this.btnClearCache);
this.flowPanel.Controls.Add(this.panelClearCacheAuto);
this.flowPanel.Controls.Add(this.labelConfiguration);
this.flowPanel.Controls.Add(this.panelConfiguration);
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanel.Location = new System.Drawing.Point(9, 9);
this.flowPanel.Name = "flowPanel";
this.flowPanel.Size = new System.Drawing.Size(322, 295);
this.flowPanel.TabIndex = 6;
this.flowPanel.WrapContents = false;
//
// TabSettingsAdvanced
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.labelConfiguration);
this.Controls.Add(this.panelConfiguration);
this.Controls.Add(this.panelPerformance);
this.Controls.Add(this.labelPerformance);
this.Controls.Add(this.panelApp);
this.Controls.Add(this.labelApp);
this.Controls.Add(this.flowPanel);
this.Name = "TabSettingsAdvanced";
this.Size = new System.Drawing.Size(340, 328);
((System.ComponentModel.ISupportInitialize)(this.numMemoryThreshold)).EndInit();
this.panelApp.ResumeLayout(false);
this.panelPerformance.ResumeLayout(false);
this.panelPerformance.PerformLayout();
this.Size = new System.Drawing.Size(340, 313);
((System.ComponentModel.ISupportInitialize)(this.numClearCacheThreshold)).EndInit();
this.panelAppButtons.ResumeLayout(false);
this.panelClearCacheAuto.ResumeLayout(false);
this.panelClearCacheAuto.PerformLayout();
this.panelConfiguration.ResumeLayout(false);
this.flowPanel.ResumeLayout(false);
this.flowPanel.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
@@ -278,13 +280,14 @@
private System.Windows.Forms.Button btnOpenAppFolder;
private System.Windows.Forms.Button btnOpenDataFolder;
private System.Windows.Forms.Label labelApp;
private System.Windows.Forms.Panel panelApp;
private System.Windows.Forms.Panel panelAppButtons;
private System.Windows.Forms.Label labelPerformance;
private System.Windows.Forms.Panel panelPerformance;
private System.Windows.Forms.Panel panelClearCacheAuto;
private System.Windows.Forms.Panel panelConfiguration;
private System.Windows.Forms.Label labelConfiguration;
private System.Windows.Forms.Label labelMemoryUsage;
private Controls.NumericUpDownEx numMemoryThreshold;
private System.Windows.Forms.CheckBox checkBrowserGCReload;
private System.Windows.Forms.Label labelCache;
private Controls.NumericUpDownEx numClearCacheThreshold;
private System.Windows.Forms.CheckBox checkClearCacheAuto;
private System.Windows.Forms.FlowLayoutPanel flowPanel;
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows.Forms;
using TweetDuck.Configuration;
using TweetDuck.Core.Controls;
@@ -17,6 +18,19 @@ namespace TweetDuck.Core.Other.Settings{
this.reinjectBrowserCSS = reinjectBrowserCSS;
toolTip.SetToolTip(btnOpenAppFolder, "Opens the folder where the app is located.");
toolTip.SetToolTip(btnOpenDataFolder, "Opens the folder where your profile data is located.");
toolTip.SetToolTip(btnRestart, "Restarts the program using the same command\r\nline arguments that were used at launch.");
toolTip.SetToolTip(btnRestartArgs, "Restarts the program with customizable\r\ncommand line arguments.");
toolTip.SetToolTip(checkHardwareAcceleration, "Uses graphics card to improve performance. Disable if you experience\r\nvisual glitches. This option will not be exported in a profile.");
toolTip.SetToolTip(btnClearCache, "Clearing cache will free up space taken by downloaded images and other resources.");
toolTip.SetToolTip(checkClearCacheAuto, "Automatically clears cache when its size exceeds the set threshold. Note that cache can only be cleared when closing TweetDuck.");
toolTip.SetToolTip(btnEditCefArgs, "Set custom command line arguments for Chromium Embedded Framework.");
toolTip.SetToolTip(btnEditCSS, "Set custom CSS for browser and notification windows.");
if (SystemConfig.IsHardwareAccelerationSupported){
checkHardwareAcceleration.Checked = SysConfig.HardwareAcceleration;
}
@@ -25,37 +39,34 @@ namespace TweetDuck.Core.Other.Settings{
checkHardwareAcceleration.Checked = false;
}
checkBrowserGCReload.Checked = SysConfig.EnableBrowserGCReload;
numMemoryThreshold.Enabled = checkBrowserGCReload.Checked;
numMemoryThreshold.SetValueSafe(SysConfig.BrowserMemoryThreshold);
checkClearCacheAuto.Checked = SysConfig.ClearCacheAutomatically;
numClearCacheThreshold.Enabled = checkClearCacheAuto.Checked;
numClearCacheThreshold.SetValueSafe(SysConfig.ClearCacheThreshold);
BrowserCache.CalculateCacheSize(bytes => this.InvokeSafe(() => {
if (bytes == -1L){
btnClearCache.Text = "Clear Cache (unknown size)";
}
else{
btnClearCache.Text = "Clear Cache ("+(int)Math.Ceiling(bytes/(1024.0*1024.0))+" MB)";
}
}));
BrowserCache.GetCacheSize(task => {
string text = task.Status == TaskStatus.RanToCompletion ? (int)Math.Ceiling(task.Result/(1024.0*1024.0))+" MB" : "unknown";
this.InvokeSafe(() => btnClearCache.Text = $"Clear Cache ({text})");
});
}
public override void OnReady(){
btnClearCache.Click += btnClearCache_Click;
checkHardwareAcceleration.CheckedChanged += checkHardwareAcceleration_CheckedChanged;
checkBrowserGCReload.CheckedChanged += checkBrowserGCReload_CheckedChanged;
numMemoryThreshold.ValueChanged += numMemoryThreshold_ValueChanged;
btnEditCefArgs.Click += btnEditCefArgs_Click;
btnEditCSS.Click += btnEditCSS_Click;
btnOpenAppFolder.Click += btnOpenAppFolder_Click;
btnOpenDataFolder.Click += btnOpenDataFolder_Click;
btnRestart.Click += btnRestart_Click;
btnRestartArgs.Click += btnRestartArgs_Click;
checkHardwareAcceleration.CheckedChanged += checkHardwareAcceleration_CheckedChanged;
btnClearCache.Click += btnClearCache_Click;
checkClearCacheAuto.CheckedChanged += checkClearCacheAuto_CheckedChanged;
btnEditCefArgs.Click += btnEditCefArgs_Click;
btnEditCSS.Click += btnEditCSS_Click;
}
public override void OnClosing(){
SysConfig.ClearCacheAutomatically = checkClearCacheAuto.Checked;
SysConfig.ClearCacheThreshold = (int)numClearCacheThreshold.Value;
SysConfig.Save();
}
@@ -65,20 +76,15 @@ namespace TweetDuck.Core.Other.Settings{
FormMessage.Information("Clear Cache", "Cache will be automatically cleared when TweetDuck exits.", FormMessage.OK);
}
private void checkClearCacheAuto_CheckedChanged(object sender, EventArgs e){
numClearCacheThreshold.Enabled = checkClearCacheAuto.Checked;
}
private void checkHardwareAcceleration_CheckedChanged(object sender, EventArgs e){
SysConfig.HardwareAcceleration = checkHardwareAcceleration.Checked;
PromptRestart(); // calls OnClosing
}
private void checkBrowserGCReload_CheckedChanged(object sender, EventArgs e){
SysConfig.EnableBrowserGCReload = checkBrowserGCReload.Checked;
numMemoryThreshold.Enabled = checkBrowserGCReload.Checked;
}
private void numMemoryThreshold_ValueChanged(object sender, EventArgs e){
SysConfig.BrowserMemoryThreshold = (int)numMemoryThreshold.Value;
}
private void btnEditCefArgs_Click(object sender, EventArgs e){
DialogSettingsCefArgs form = new DialogSettingsCefArgs();

View File

@@ -24,35 +24,37 @@
/// </summary>
private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
this.panelFeedback = new System.Windows.Forms.Panel();
this.panelDataCollection = new System.Windows.Forms.Panel();
this.labelDataCollectionLink = new System.Windows.Forms.LinkLabel();
this.checkDataCollection = new System.Windows.Forms.CheckBox();
this.labelDataCollectionMessage = new System.Windows.Forms.Label();
this.btnViewReport = new System.Windows.Forms.Button();
this.btnSendFeedback = new System.Windows.Forms.Button();
this.labelDataCollection = new System.Windows.Forms.Label();
this.labelFeedback = new System.Windows.Forms.Label();
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.btnSendFeedback = new System.Windows.Forms.Button();
this.panelFeedback.SuspendLayout();
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
this.panelDataCollection.SuspendLayout();
this.flowPanel.SuspendLayout();
this.SuspendLayout();
//
// panelFeedback
// panelDataCollection
//
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;
this.panelDataCollection.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.panelDataCollection.Controls.Add(this.labelDataCollectionLink);
this.panelDataCollection.Controls.Add(this.checkDataCollection);
this.panelDataCollection.Location = new System.Drawing.Point(0, 74);
this.panelDataCollection.Margin = new System.Windows.Forms.Padding(0);
this.panelDataCollection.Name = "panelDataCollection";
this.panelDataCollection.Size = new System.Drawing.Size(322, 26);
this.panelDataCollection.TabIndex = 1;
//
// labelDataCollectionLink
//
this.labelDataCollectionLink.AutoSize = true;
this.labelDataCollectionLink.LinkArea = new System.Windows.Forms.LinkArea(1, 10);
this.labelDataCollectionLink.LinkBehavior = System.Windows.Forms.LinkBehavior.HoverUnderline;
this.labelDataCollectionLink.Location = new System.Drawing.Point(141, 60);
this.labelDataCollectionLink.Location = new System.Drawing.Point(141, 6);
this.labelDataCollectionLink.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.labelDataCollectionLink.Name = "labelDataCollectionLink";
this.labelDataCollectionLink.Size = new System.Drawing.Size(66, 17);
@@ -60,23 +62,53 @@
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.Location = new System.Drawing.Point(6, 6);
this.checkDataCollection.Margin = new System.Windows.Forms.Padding(6, 6, 0, 3);
this.checkDataCollection.Name = "checkDataCollection";
this.checkDataCollection.Size = new System.Drawing.Size(135, 17);
this.checkDataCollection.TabIndex = 2;
this.checkDataCollection.Text = "Send Anonymous Data";
this.checkDataCollection.UseVisualStyleBackColor = true;
//
// labelDataCollectionMessage
//
this.labelDataCollectionMessage.Location = new System.Drawing.Point(6, 135);
this.labelDataCollectionMessage.Margin = new System.Windows.Forms.Padding(6);
this.labelDataCollectionMessage.Name = "labelDataCollectionMessage";
this.labelDataCollectionMessage.Size = new System.Drawing.Size(310, 67);
this.labelDataCollectionMessage.TabIndex = 5;
//
// btnViewReport
//
this.btnViewReport.AutoSize = true;
this.btnViewReport.Location = new System.Drawing.Point(5, 103);
this.btnViewReport.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.btnViewReport.Name = "btnViewReport";
this.btnViewReport.Size = new System.Drawing.Size(144, 23);
this.btnViewReport.TabIndex = 4;
this.btnViewReport.Text = "View My Analytics Report";
this.btnViewReport.UseVisualStyleBackColor = true;
//
// btnSendFeedback
//
this.btnSendFeedback.AutoSize = true;
this.btnSendFeedback.Location = new System.Drawing.Point(5, 23);
this.btnSendFeedback.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.btnSendFeedback.Name = "btnSendFeedback";
this.btnSendFeedback.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.btnSendFeedback.Size = new System.Drawing.Size(164, 23);
this.btnSendFeedback.TabIndex = 0;
this.btnSendFeedback.Text = "Send Feedback / Bug Report";
this.btnSendFeedback.UseVisualStyleBackColor = true;
//
// labelDataCollection
//
this.labelDataCollection.AutoSize = true;
this.labelDataCollection.Location = new System.Drawing.Point(3, 41);
this.labelDataCollection.Location = new System.Drawing.Point(3, 61);
this.labelDataCollection.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelDataCollection.Name = "labelDataCollection";
this.labelDataCollection.Size = new System.Drawing.Size(79, 13);
@@ -87,48 +119,57 @@
//
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.Location = new System.Drawing.Point(0, 0);
this.labelFeedback.Margin = new System.Windows.Forms.Padding(0);
this.labelFeedback.Name = "labelFeedback";
this.labelFeedback.Size = new System.Drawing.Size(80, 20);
this.labelFeedback.TabIndex = 0;
this.labelFeedback.Text = "Feedback";
//
// btnSendFeedback
// flowPanel
//
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;
this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.flowPanel.Controls.Add(this.labelFeedback);
this.flowPanel.Controls.Add(this.btnSendFeedback);
this.flowPanel.Controls.Add(this.labelDataCollection);
this.flowPanel.Controls.Add(this.panelDataCollection);
this.flowPanel.Controls.Add(this.btnViewReport);
this.flowPanel.Controls.Add(this.labelDataCollectionMessage);
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanel.Location = new System.Drawing.Point(9, 9);
this.flowPanel.Name = "flowPanel";
this.flowPanel.Size = new System.Drawing.Size(322, 209);
this.flowPanel.TabIndex = 2;
this.flowPanel.WrapContents = false;
//
// TabSettingsFeedback
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.panelFeedback);
this.Controls.Add(this.labelFeedback);
this.Controls.Add(this.flowPanel);
this.Name = "TabSettingsFeedback";
this.Size = new System.Drawing.Size(340, 122);
this.panelFeedback.ResumeLayout(false);
this.panelFeedback.PerformLayout();
this.Size = new System.Drawing.Size(340, 227);
this.panelDataCollection.ResumeLayout(false);
this.panelDataCollection.PerformLayout();
this.flowPanel.ResumeLayout(false);
this.flowPanel.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Panel panelFeedback;
private System.Windows.Forms.Panel panelDataCollection;
private System.Windows.Forms.CheckBox checkDataCollection;
private System.Windows.Forms.Label labelDataCollection;
private System.Windows.Forms.Label labelFeedback;
private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.LinkLabel labelDataCollectionLink;
private System.Windows.Forms.Button btnSendFeedback;
private System.Windows.Forms.Button btnViewReport;
private System.Windows.Forms.Label labelDataCollectionMessage;
private System.Windows.Forms.FlowLayoutPanel flowPanel;
}
}

View File

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

View File

@@ -26,7 +26,6 @@
this.components = new System.ComponentModel.Container();
this.checkExpandLinks = new System.Windows.Forms.CheckBox();
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.checkSpellCheck = new System.Windows.Forms.CheckBox();
this.checkUpdateNotifications = new System.Windows.Forms.CheckBox();
this.btnCheckUpdates = new System.Windows.Forms.Button();
this.labelZoomValue = new System.Windows.Forms.Label();
@@ -37,107 +36,90 @@
this.labelZoom = new System.Windows.Forms.Label();
this.zoomUpdateTimer = new System.Windows.Forms.Timer(this.components);
this.labelUI = new System.Windows.Forms.Label();
this.panelUI = new System.Windows.Forms.Panel();
this.panelUpdates = new System.Windows.Forms.Panel();
this.panelZoom = new System.Windows.Forms.Panel();
this.checkAnimatedAvatars = new System.Windows.Forms.CheckBox();
this.labelUpdates = new System.Windows.Forms.Label();
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
this.checkKeepLikeFollowDialogsOpen = new System.Windows.Forms.CheckBox();
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).BeginInit();
this.panelUI.SuspendLayout();
this.panelUpdates.SuspendLayout();
this.panelZoom.SuspendLayout();
this.flowPanel.SuspendLayout();
this.SuspendLayout();
//
// checkExpandLinks
//
this.checkExpandLinks.AutoSize = true;
this.checkExpandLinks.Location = new System.Drawing.Point(6, 5);
this.checkExpandLinks.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3);
this.checkExpandLinks.Location = new System.Drawing.Point(6, 26);
this.checkExpandLinks.Margin = new System.Windows.Forms.Padding(6, 6, 3, 3);
this.checkExpandLinks.Name = "checkExpandLinks";
this.checkExpandLinks.Size = new System.Drawing.Size(166, 17);
this.checkExpandLinks.TabIndex = 0;
this.checkExpandLinks.Text = "Expand Links When Hovered";
this.toolTip.SetToolTip(this.checkExpandLinks, "Expands links inside the tweets. If disabled,\r\nthe full links show up in a tooltip instead.");
this.checkExpandLinks.UseVisualStyleBackColor = true;
//
// checkSpellCheck
//
this.checkSpellCheck.AutoSize = true;
this.checkSpellCheck.Location = new System.Drawing.Point(6, 97);
this.checkSpellCheck.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkSpellCheck.Name = "checkSpellCheck";
this.checkSpellCheck.Size = new System.Drawing.Size(119, 17);
this.checkSpellCheck.TabIndex = 4;
this.checkSpellCheck.Text = "Enable Spell Check";
this.toolTip.SetToolTip(this.checkSpellCheck, "Underlines words that are spelled incorrectly.");
this.checkSpellCheck.UseVisualStyleBackColor = true;
//
// checkUpdateNotifications
//
this.checkUpdateNotifications.AutoSize = true;
this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 5);
this.checkUpdateNotifications.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3);
this.checkUpdateNotifications.Location = new System.Drawing.Point(6, 268);
this.checkUpdateNotifications.Margin = new System.Windows.Forms.Padding(6, 6, 3, 3);
this.checkUpdateNotifications.Name = "checkUpdateNotifications";
this.checkUpdateNotifications.Size = new System.Drawing.Size(165, 17);
this.checkUpdateNotifications.TabIndex = 0;
this.checkUpdateNotifications.Text = "Check Updates Automatically";
this.toolTip.SetToolTip(this.checkUpdateNotifications, "Checks for updates every hour.\r\nIf an update is dismissed, it will not appear again.");
this.checkUpdateNotifications.UseVisualStyleBackColor = true;
//
// btnCheckUpdates
//
this.btnCheckUpdates.Location = new System.Drawing.Point(5, 28);
this.btnCheckUpdates.Location = new System.Drawing.Point(5, 291);
this.btnCheckUpdates.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.btnCheckUpdates.Name = "btnCheckUpdates";
this.btnCheckUpdates.Size = new System.Drawing.Size(144, 23);
this.btnCheckUpdates.TabIndex = 1;
this.btnCheckUpdates.Text = "Check Updates Now";
this.toolTip.SetToolTip(this.btnCheckUpdates, "Forces an update check, even for updates that had been dismissed.");
this.btnCheckUpdates.UseVisualStyleBackColor = true;
//
// labelZoomValue
//
this.labelZoomValue.BackColor = System.Drawing.Color.Transparent;
this.labelZoomValue.Location = new System.Drawing.Point(147, 146);
this.labelZoomValue.Location = new System.Drawing.Point(147, 4);
this.labelZoomValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.labelZoomValue.Name = "labelZoomValue";
this.labelZoomValue.Size = new System.Drawing.Size(38, 13);
this.labelZoomValue.TabIndex = 7;
this.labelZoomValue.TabIndex = 8;
this.labelZoomValue.Text = "100%";
this.labelZoomValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
this.toolTip.SetToolTip(this.labelZoomValue, "Changes the zoom level.\r\nAlso affects notifications and screenshots.");
//
// checkSwitchAccountSelectors
//
this.checkSwitchAccountSelectors.AutoSize = true;
this.checkSwitchAccountSelectors.Location = new System.Drawing.Point(6, 28);
this.checkSwitchAccountSelectors.Location = new System.Drawing.Point(6, 49);
this.checkSwitchAccountSelectors.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkSwitchAccountSelectors.Name = "checkSwitchAccountSelectors";
this.checkSwitchAccountSelectors.Size = new System.Drawing.Size(172, 17);
this.checkSwitchAccountSelectors.TabIndex = 1;
this.checkSwitchAccountSelectors.Text = "Shift Selects Multiple Accounts";
this.toolTip.SetToolTip(this.checkSwitchAccountSelectors, "When (re)tweeting, click to select a single account or hold Shift to\r\nselect multiple accounts, instead of TweetDeck\'s default behavior.");
this.checkSwitchAccountSelectors.UseVisualStyleBackColor = true;
//
// checkBestImageQuality
//
this.checkBestImageQuality.AutoSize = true;
this.checkBestImageQuality.Location = new System.Drawing.Point(6, 74);
this.checkBestImageQuality.Location = new System.Drawing.Point(6, 118);
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 = 3;
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.Location = new System.Drawing.Point(6, 72);
this.checkOpenSearchInFirstColumn.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkOpenSearchInFirstColumn.Name = "checkOpenSearchInFirstColumn";
this.checkOpenSearchInFirstColumn.Size = new System.Drawing.Size(219, 17);
this.checkOpenSearchInFirstColumn.TabIndex = 2;
this.checkOpenSearchInFirstColumn.Text = "Add Search Columns Before First Column";
this.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
@@ -145,24 +127,24 @@
this.trackBarZoom.AutoSize = false;
this.trackBarZoom.BackColor = System.Drawing.SystemColors.Control;
this.trackBarZoom.LargeChange = 25;
this.trackBarZoom.Location = new System.Drawing.Point(3, 145);
this.trackBarZoom.Location = new System.Drawing.Point(3, 3);
this.trackBarZoom.Maximum = 200;
this.trackBarZoom.Minimum = 50;
this.trackBarZoom.Name = "trackBarZoom";
this.trackBarZoom.Size = new System.Drawing.Size(148, 30);
this.trackBarZoom.SmallChange = 5;
this.trackBarZoom.TabIndex = 6;
this.trackBarZoom.TabIndex = 7;
this.trackBarZoom.TickFrequency = 25;
this.trackBarZoom.Value = 100;
//
// labelZoom
//
this.labelZoom.AutoSize = true;
this.labelZoom.Location = new System.Drawing.Point(3, 129);
this.labelZoom.Location = new System.Drawing.Point(3, 173);
this.labelZoom.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelZoom.Name = "labelZoom";
this.labelZoom.Size = new System.Drawing.Size(34, 13);
this.labelZoom.TabIndex = 5;
this.labelZoom.TabIndex = 6;
this.labelZoom.Text = "Zoom";
//
// zoomUpdateTimer
@@ -174,69 +156,93 @@
//
this.labelUI.AutoSize = true;
this.labelUI.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelUI.Location = new System.Drawing.Point(6, 8);
this.labelUI.Margin = new System.Windows.Forms.Padding(0, 2, 0, 0);
this.labelUI.Location = new System.Drawing.Point(0, 0);
this.labelUI.Margin = new System.Windows.Forms.Padding(0);
this.labelUI.Name = "labelUI";
this.labelUI.Size = new System.Drawing.Size(111, 20);
this.labelUI.TabIndex = 0;
this.labelUI.Text = "User Interface";
//
// panelUI
// panelZoom
//
this.panelUI.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelUI.Controls.Add(this.checkOpenSearchInFirstColumn);
this.panelUI.Controls.Add(this.checkBestImageQuality);
this.panelUI.Controls.Add(this.checkExpandLinks);
this.panelUI.Controls.Add(this.checkSwitchAccountSelectors);
this.panelUI.Controls.Add(this.checkSpellCheck);
this.panelUI.Controls.Add(this.labelZoom);
this.panelUI.Controls.Add(this.trackBarZoom);
this.panelUI.Controls.Add(this.labelZoomValue);
this.panelUI.Location = new System.Drawing.Point(9, 31);
this.panelUI.Name = "panelUI";
this.panelUI.Size = new System.Drawing.Size(322, 179);
this.panelUI.TabIndex = 1;
this.panelZoom.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.panelZoom.Controls.Add(this.trackBarZoom);
this.panelZoom.Controls.Add(this.labelZoomValue);
this.panelZoom.Location = new System.Drawing.Point(0, 186);
this.panelZoom.Margin = new System.Windows.Forms.Padding(0);
this.panelZoom.Name = "panelZoom";
this.panelZoom.Size = new System.Drawing.Size(322, 36);
this.panelZoom.TabIndex = 1;
//
// panelUpdates
// checkAnimatedAvatars
//
this.panelUpdates.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelUpdates.Controls.Add(this.checkUpdateNotifications);
this.panelUpdates.Controls.Add(this.btnCheckUpdates);
this.panelUpdates.Location = new System.Drawing.Point(9, 257);
this.panelUpdates.Name = "panelUpdates";
this.panelUpdates.Size = new System.Drawing.Size(322, 55);
this.panelUpdates.TabIndex = 3;
this.checkAnimatedAvatars.AutoSize = true;
this.checkAnimatedAvatars.Location = new System.Drawing.Point(6, 141);
this.checkAnimatedAvatars.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkAnimatedAvatars.Name = "checkAnimatedAvatars";
this.checkAnimatedAvatars.Size = new System.Drawing.Size(145, 17);
this.checkAnimatedAvatars.TabIndex = 4;
this.checkAnimatedAvatars.Text = "Enable Animated Avatars";
this.checkAnimatedAvatars.UseVisualStyleBackColor = true;
//
// labelUpdates
//
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.Location = new System.Drawing.Point(6, 234);
this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
this.labelUpdates.Location = new System.Drawing.Point(0, 242);
this.labelUpdates.Margin = new System.Windows.Forms.Padding(0, 20, 0, 0);
this.labelUpdates.Name = "labelUpdates";
this.labelUpdates.Size = new System.Drawing.Size(70, 20);
this.labelUpdates.TabIndex = 2;
this.labelUpdates.Text = "Updates";
//
// flowPanel
//
this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.flowPanel.Controls.Add(this.labelUI);
this.flowPanel.Controls.Add(this.checkExpandLinks);
this.flowPanel.Controls.Add(this.checkSwitchAccountSelectors);
this.flowPanel.Controls.Add(this.checkOpenSearchInFirstColumn);
this.flowPanel.Controls.Add(this.checkKeepLikeFollowDialogsOpen);
this.flowPanel.Controls.Add(this.checkBestImageQuality);
this.flowPanel.Controls.Add(this.checkAnimatedAvatars);
this.flowPanel.Controls.Add(this.labelZoom);
this.flowPanel.Controls.Add(this.panelZoom);
this.flowPanel.Controls.Add(this.labelUpdates);
this.flowPanel.Controls.Add(this.checkUpdateNotifications);
this.flowPanel.Controls.Add(this.btnCheckUpdates);
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanel.Location = new System.Drawing.Point(9, 9);
this.flowPanel.Name = "flowPanel";
this.flowPanel.Size = new System.Drawing.Size(322, 319);
this.flowPanel.TabIndex = 4;
this.flowPanel.WrapContents = false;
//
// checkKeepLikeFollowDialogsOpen
//
this.checkKeepLikeFollowDialogsOpen.AutoSize = true;
this.checkKeepLikeFollowDialogsOpen.Location = new System.Drawing.Point(6, 95);
this.checkKeepLikeFollowDialogsOpen.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkKeepLikeFollowDialogsOpen.Name = "checkKeepLikeFollowDialogsOpen";
this.checkKeepLikeFollowDialogsOpen.Size = new System.Drawing.Size(176, 17);
this.checkKeepLikeFollowDialogsOpen.TabIndex = 7;
this.checkKeepLikeFollowDialogsOpen.Text = "Keep Like/Follow Dialogs Open";
this.checkKeepLikeFollowDialogsOpen.UseVisualStyleBackColor = true;
//
// TabSettingsGeneral
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.labelUpdates);
this.Controls.Add(this.panelUpdates);
this.Controls.Add(this.panelUI);
this.Controls.Add(this.labelUI);
this.Controls.Add(this.flowPanel);
this.Name = "TabSettingsGeneral";
this.Size = new System.Drawing.Size(340, 322);
this.Size = new System.Drawing.Size(340, 337);
((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).EndInit();
this.panelUI.ResumeLayout(false);
this.panelUI.PerformLayout();
this.panelUpdates.ResumeLayout(false);
this.panelUpdates.PerformLayout();
this.panelZoom.ResumeLayout(false);
this.flowPanel.ResumeLayout(false);
this.flowPanel.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
@@ -244,7 +250,6 @@
private System.Windows.Forms.CheckBox checkExpandLinks;
private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.CheckBox checkSpellCheck;
private System.Windows.Forms.CheckBox checkUpdateNotifications;
private System.Windows.Forms.Button btnCheckUpdates;
private System.Windows.Forms.Label labelZoom;
@@ -253,10 +258,12 @@
private System.Windows.Forms.Timer zoomUpdateTimer;
private System.Windows.Forms.CheckBox checkSwitchAccountSelectors;
private System.Windows.Forms.Label labelUI;
private System.Windows.Forms.Panel panelUI;
private System.Windows.Forms.Panel panelUpdates;
private System.Windows.Forms.Panel panelZoom;
private System.Windows.Forms.Label labelUpdates;
private System.Windows.Forms.CheckBox checkBestImageQuality;
private System.Windows.Forms.CheckBox checkOpenSearchInFirstColumn;
private System.Windows.Forms.CheckBox checkAnimatedAvatars;
private System.Windows.Forms.FlowLayoutPanel flowPanel;
private System.Windows.Forms.CheckBox checkKeepLikeFollowDialogsOpen;
}
}

View File

@@ -1,28 +1,45 @@
using System;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling.General;
using TweetDuck.Updates;
namespace TweetDuck.Core.Other.Settings{
sealed partial class TabSettingsGeneral : BaseTabSettings{
private readonly FormBrowser browser;
private readonly UpdateHandler updates;
private int updateCheckEventId = -1;
public TabSettingsGeneral(UpdateHandler updates){
public TabSettingsGeneral(FormBrowser browser, UpdateHandler updates){
InitializeComponent();
this.browser = browser;
this.updates = updates;
this.updates.CheckFinished += updates_CheckFinished;
Disposed += (sender, args) => this.updates.CheckFinished -= updates_CheckFinished;
toolTip.SetToolTip(checkExpandLinks, "Expands links inside the tweets. If disabled,\r\nthe full links show up in a tooltip instead.");
toolTip.SetToolTip(checkSwitchAccountSelectors, "When (re)tweeting, click to select a single account or hold Shift to\r\nselect multiple accounts, instead of TweetDeck\'s default behavior.");
toolTip.SetToolTip(checkOpenSearchInFirstColumn, "By default, TweetDeck adds Search columns at the end.\r\nThis option makes them appear before the first column instead.");
toolTip.SetToolTip(checkKeepLikeFollowDialogsOpen, "Allows liking and following from multiple accounts at once,\r\ninstead of automatically closing the dialog after taking an action.");
toolTip.SetToolTip(checkBestImageQuality, "When right-clicking a tweet image, the context menu options\r\nwill use links to the original image size (:orig in the URL).");
toolTip.SetToolTip(checkAnimatedAvatars, "Some old Twitter avatars could be uploaded as animated GIFs.");
toolTip.SetToolTip(labelZoomValue, "Changes the zoom level.\r\nAlso affects notifications and screenshots.");
toolTip.SetToolTip(trackBarZoom, toolTip.GetToolTip(labelZoomValue));
toolTip.SetToolTip(checkUpdateNotifications, "Checks for updates every hour.\r\nIf an update is dismissed, it will not appear again.");
toolTip.SetToolTip(btnCheckUpdates, "Forces an update check, even for updates that had been dismissed.");
trackBarZoom.SetValueSafe(Config.ZoomLevel);
labelZoomValue.Text = trackBarZoom.Value+"%";
checkExpandLinks.Checked = Config.ExpandLinksOnHover;
checkSwitchAccountSelectors.Checked = Config.SwitchAccountSelectors;
checkOpenSearchInFirstColumn.Checked = Config.OpenSearchInFirstColumn;
checkKeepLikeFollowDialogsOpen.Checked = Config.KeepLikeFollowDialogsOpen;
checkBestImageQuality.Checked = Config.BestImageQuality;
checkSpellCheck.Checked = Config.EnableSpellCheck;
checkAnimatedAvatars.Checked = Config.EnableAnimatedImages;
checkUpdateNotifications.Checked = Config.EnableUpdateCheck;
}
@@ -31,8 +48,9 @@ namespace TweetDuck.Core.Other.Settings{
checkExpandLinks.CheckedChanged += checkExpandLinks_CheckedChanged;
checkSwitchAccountSelectors.CheckedChanged += checkSwitchAccountSelectors_CheckedChanged;
checkOpenSearchInFirstColumn.CheckedChanged += checkOpenSearchInFirstColumn_CheckedChanged;
checkKeepLikeFollowDialogsOpen.CheckedChanged += checkKeepLikeFollowDialogsOpen_CheckedChanged;
checkBestImageQuality.CheckedChanged += checkBestImageQuality_CheckedChanged;
checkSpellCheck.CheckedChanged += checkSpellCheck_CheckedChanged;
checkAnimatedAvatars.CheckedChanged += checkAnimatedAvatars_CheckedChanged;
trackBarZoom.ValueChanged += trackBarZoom_ValueChanged;
checkUpdateNotifications.CheckedChanged += checkUpdateNotifications_CheckedChanged;
@@ -55,13 +73,17 @@ namespace TweetDuck.Core.Other.Settings{
Config.OpenSearchInFirstColumn = checkOpenSearchInFirstColumn.Checked;
}
private void checkKeepLikeFollowDialogsOpen_CheckedChanged(object sender, EventArgs e){
Config.KeepLikeFollowDialogsOpen = checkKeepLikeFollowDialogsOpen.Checked;
}
private void checkBestImageQuality_CheckedChanged(object sender, EventArgs e){
Config.BestImageQuality = checkBestImageQuality.Checked;
}
private void checkSpellCheck_CheckedChanged(object sender, EventArgs e){
Config.EnableSpellCheck = checkSpellCheck.Checked;
PromptRestart();
private void checkAnimatedAvatars_CheckedChanged(object sender, EventArgs e){
Config.EnableAnimatedImages = checkAnimatedAvatars.Checked;
BrowserProcessHandler.UpdatePrefs().ContinueWith(task => browser.ReloadColumns());
}
private void trackBarZoom_ValueChanged(object sender, EventArgs e){

View File

@@ -0,0 +1,155 @@
namespace TweetDuck.Core.Other.Settings {
partial class TabSettingsLocales {
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.checkSpellCheck = new System.Windows.Forms.CheckBox();
this.labelLocales = new System.Windows.Forms.Label();
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
this.labelSpellCheckLanguage = new System.Windows.Forms.Label();
this.comboBoxSpellCheckLanguage = new System.Windows.Forms.ComboBox();
this.labelTranslations = new System.Windows.Forms.Label();
this.labelTranslationTarget = new System.Windows.Forms.Label();
this.comboBoxTranslationTarget = new System.Windows.Forms.ComboBox();
this.flowPanel.SuspendLayout();
this.SuspendLayout();
//
// checkSpellCheck
//
this.checkSpellCheck.AutoSize = true;
this.checkSpellCheck.Location = new System.Drawing.Point(6, 26);
this.checkSpellCheck.Margin = new System.Windows.Forms.Padding(6, 6, 3, 3);
this.checkSpellCheck.Name = "checkSpellCheck";
this.checkSpellCheck.Size = new System.Drawing.Size(119, 17);
this.checkSpellCheck.TabIndex = 5;
this.checkSpellCheck.Text = "Enable Spell Check";
this.checkSpellCheck.UseVisualStyleBackColor = true;
//
// labelLocales
//
this.labelLocales.AutoSize = true;
this.labelLocales.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelLocales.Location = new System.Drawing.Point(0, 0);
this.labelLocales.Margin = new System.Windows.Forms.Padding(0);
this.labelLocales.Name = "labelLocales";
this.labelLocales.Size = new System.Drawing.Size(64, 20);
this.labelLocales.TabIndex = 0;
this.labelLocales.Text = "Locales";
//
// flowPanel
//
this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.flowPanel.Controls.Add(this.labelLocales);
this.flowPanel.Controls.Add(this.checkSpellCheck);
this.flowPanel.Controls.Add(this.labelSpellCheckLanguage);
this.flowPanel.Controls.Add(this.comboBoxSpellCheckLanguage);
this.flowPanel.Controls.Add(this.labelTranslations);
this.flowPanel.Controls.Add(this.labelTranslationTarget);
this.flowPanel.Controls.Add(this.comboBoxTranslationTarget);
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanel.Location = new System.Drawing.Point(9, 9);
this.flowPanel.Name = "flowPanel";
this.flowPanel.Size = new System.Drawing.Size(322, 193);
this.flowPanel.TabIndex = 4;
this.flowPanel.WrapContents = false;
//
// labelSpellCheckLanguage
//
this.labelSpellCheckLanguage.AutoSize = true;
this.labelSpellCheckLanguage.Location = new System.Drawing.Point(3, 58);
this.labelSpellCheckLanguage.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelSpellCheckLanguage.Name = "labelSpellCheckLanguage";
this.labelSpellCheckLanguage.Size = new System.Drawing.Size(115, 13);
this.labelSpellCheckLanguage.TabIndex = 11;
this.labelSpellCheckLanguage.Text = "Spell Check Language";
//
// comboBoxSpellCheckLanguage
//
this.comboBoxSpellCheckLanguage.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxSpellCheckLanguage.FormattingEnabled = true;
this.comboBoxSpellCheckLanguage.Location = new System.Drawing.Point(5, 74);
this.comboBoxSpellCheckLanguage.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.comboBoxSpellCheckLanguage.Name = "comboBoxSpellCheckLanguage";
this.comboBoxSpellCheckLanguage.Size = new System.Drawing.Size(311, 21);
this.comboBoxSpellCheckLanguage.TabIndex = 9;
//
// labelTranslations
//
this.labelTranslations.AutoSize = true;
this.labelTranslations.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelTranslations.Location = new System.Drawing.Point(0, 118);
this.labelTranslations.Margin = new System.Windows.Forms.Padding(0, 20, 0, 0);
this.labelTranslations.Name = "labelTranslations";
this.labelTranslations.Size = new System.Drawing.Size(116, 20);
this.labelTranslations.TabIndex = 10;
this.labelTranslations.Text = "Bing Translator";
//
// labelTranslationTarget
//
this.labelTranslationTarget.AutoSize = true;
this.labelTranslationTarget.Location = new System.Drawing.Point(3, 150);
this.labelTranslationTarget.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelTranslationTarget.Name = "labelTranslationTarget";
this.labelTranslationTarget.Size = new System.Drawing.Size(89, 13);
this.labelTranslationTarget.TabIndex = 8;
this.labelTranslationTarget.Text = "Target Language";
//
// comboBoxTranslationTarget
//
this.comboBoxTranslationTarget.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxTranslationTarget.FormattingEnabled = true;
this.comboBoxTranslationTarget.Location = new System.Drawing.Point(5, 166);
this.comboBoxTranslationTarget.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.comboBoxTranslationTarget.Name = "comboBoxTranslationTarget";
this.comboBoxTranslationTarget.Size = new System.Drawing.Size(311, 21);
this.comboBoxTranslationTarget.TabIndex = 7;
//
// TabSettingsLocales
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.flowPanel);
this.Name = "TabSettingsLocales";
this.Size = new System.Drawing.Size(340, 211);
this.flowPanel.ResumeLayout(false);
this.flowPanel.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.ToolTip toolTip;
private System.Windows.Forms.CheckBox checkSpellCheck;
private System.Windows.Forms.Label labelLocales;
private System.Windows.Forms.FlowLayoutPanel flowPanel;
private System.Windows.Forms.ComboBox comboBoxTranslationTarget;
private System.Windows.Forms.Label labelTranslationTarget;
private System.Windows.Forms.ComboBox comboBoxSpellCheckLanguage;
private System.Windows.Forms.Label labelTranslations;
private System.Windows.Forms.Label labelSpellCheckLanguage;
}
}

View File

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

View File

@@ -51,7 +51,8 @@
this.radioSizeAuto = new System.Windows.Forms.RadioButton();
this.radioSizeCustom = new System.Windows.Forms.RadioButton();
this.labelGeneral = new System.Windows.Forms.Label();
this.panelGeneral = new System.Windows.Forms.Panel();
this.panelEdgeDistance = new System.Windows.Forms.Panel();
this.checkMediaPreviews = new System.Windows.Forms.CheckBox();
this.labelScrollSpeedValue = new System.Windows.Forms.Label();
this.trackBarScrollSpeed = new System.Windows.Forms.TrackBar();
this.labelScrollSpeed = new System.Windows.Forms.Label();
@@ -61,22 +62,25 @@
this.labelDuration = new System.Windows.Forms.Label();
this.labelTimer = new System.Windows.Forms.Label();
this.labelSize = new System.Windows.Forms.Label();
this.panelMiscellaneous = new System.Windows.Forms.Panel();
this.panelSize = new System.Windows.Forms.Panel();
this.durationUpdateTimer = new System.Windows.Forms.Timer(this.components);
this.checkMediaPreviews = new System.Windows.Forms.CheckBox();
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
this.panelScrollSpeed = new System.Windows.Forms.Panel();
((System.ComponentModel.ISupportInitialize)(this.trackBarEdgeDistance)).BeginInit();
this.tableLayoutDurationButtons.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.trackBarDuration)).BeginInit();
this.panelGeneral.SuspendLayout();
this.panelEdgeDistance.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.trackBarScrollSpeed)).BeginInit();
this.panelLocation.SuspendLayout();
this.panelTimer.SuspendLayout();
this.panelMiscellaneous.SuspendLayout();
this.panelSize.SuspendLayout();
this.flowPanel.SuspendLayout();
this.panelScrollSpeed.SuspendLayout();
this.SuspendLayout();
//
// labelEdgeDistanceValue
//
this.labelEdgeDistanceValue.Location = new System.Drawing.Point(147, 129);
this.labelEdgeDistanceValue.Location = new System.Drawing.Point(145, 4);
this.labelEdgeDistanceValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.labelEdgeDistanceValue.Name = "labelEdgeDistanceValue";
this.labelEdgeDistanceValue.Size = new System.Drawing.Size(40, 13);
@@ -87,7 +91,7 @@
// labelDisplay
//
this.labelDisplay.AutoSize = true;
this.labelDisplay.Location = new System.Drawing.Point(3, 60);
this.labelDisplay.Location = new System.Drawing.Point(3, 451);
this.labelDisplay.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelDisplay.Name = "labelDisplay";
this.labelDisplay.Size = new System.Drawing.Size(41, 13);
@@ -96,11 +100,9 @@
//
// comboBoxDisplay
//
this.comboBoxDisplay.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.comboBoxDisplay.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxDisplay.FormattingEnabled = true;
this.comboBoxDisplay.Location = new System.Drawing.Point(5, 76);
this.comboBoxDisplay.Location = new System.Drawing.Point(5, 467);
this.comboBoxDisplay.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.comboBoxDisplay.Name = "comboBoxDisplay";
this.comboBoxDisplay.Size = new System.Drawing.Size(144, 21);
@@ -109,7 +111,7 @@
// labelEdgeDistance
//
this.labelEdgeDistance.AutoSize = true;
this.labelEdgeDistance.Location = new System.Drawing.Point(3, 112);
this.labelEdgeDistance.Location = new System.Drawing.Point(3, 503);
this.labelEdgeDistance.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelEdgeDistance.Name = "labelEdgeDistance";
this.labelEdgeDistance.Size = new System.Drawing.Size(103, 13);
@@ -125,7 +127,6 @@
this.radioLocCustom.TabIndex = 4;
this.radioLocCustom.TabStop = true;
this.radioLocCustom.Text = "Custom";
this.toolTip.SetToolTip(this.radioLocCustom, "Drag the example notification window to the desired location.");
this.radioLocCustom.UseVisualStyleBackColor = true;
//
// radioLocBR
@@ -176,7 +177,7 @@
//
this.trackBarEdgeDistance.AutoSize = false;
this.trackBarEdgeDistance.LargeChange = 8;
this.trackBarEdgeDistance.Location = new System.Drawing.Point(5, 128);
this.trackBarEdgeDistance.Location = new System.Drawing.Point(3, 3);
this.trackBarEdgeDistance.Maximum = 40;
this.trackBarEdgeDistance.Minimum = 8;
this.trackBarEdgeDistance.Name = "trackBarEdgeDistance";
@@ -195,7 +196,7 @@
this.tableLayoutDurationButtons.Controls.Add(this.btnDurationMedium, 0, 0);
this.tableLayoutDurationButtons.Controls.Add(this.btnDurationLong, 1, 0);
this.tableLayoutDurationButtons.Controls.Add(this.btnDurationShort, 0, 0);
this.tableLayoutDurationButtons.Location = new System.Drawing.Point(3, 113);
this.tableLayoutDurationButtons.Location = new System.Drawing.Point(3, 320);
this.tableLayoutDurationButtons.Name = "tableLayoutDurationButtons";
this.tableLayoutDurationButtons.RowCount = 1;
this.tableLayoutDurationButtons.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
@@ -250,19 +251,18 @@
// labelDurationValue
//
this.labelDurationValue.BackColor = System.Drawing.Color.Transparent;
this.labelDurationValue.Location = new System.Drawing.Point(147, 77);
this.labelDurationValue.Location = new System.Drawing.Point(147, 4);
this.labelDurationValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.labelDurationValue.Name = "labelDurationValue";
this.labelDurationValue.Size = new System.Drawing.Size(52, 13);
this.labelDurationValue.TabIndex = 4;
this.labelDurationValue.Text = "0 ms/c";
this.labelDurationValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
this.toolTip.SetToolTip(this.labelDurationValue, "Milliseconds per character.");
//
// trackBarDuration
//
this.trackBarDuration.AutoSize = false;
this.trackBarDuration.Location = new System.Drawing.Point(3, 76);
this.trackBarDuration.Location = new System.Drawing.Point(3, 3);
this.trackBarDuration.Maximum = 60;
this.trackBarDuration.Minimum = 10;
this.trackBarDuration.Name = "trackBarDuration";
@@ -274,31 +274,29 @@
// checkSkipOnLinkClick
//
this.checkSkipOnLinkClick.AutoSize = true;
this.checkSkipOnLinkClick.Location = new System.Drawing.Point(6, 51);
this.checkSkipOnLinkClick.Location = new System.Drawing.Point(6, 72);
this.checkSkipOnLinkClick.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkSkipOnLinkClick.Name = "checkSkipOnLinkClick";
this.checkSkipOnLinkClick.Size = new System.Drawing.Size(113, 17);
this.checkSkipOnLinkClick.TabIndex = 2;
this.checkSkipOnLinkClick.Text = "Skip On Link Click";
this.toolTip.SetToolTip(this.checkSkipOnLinkClick, "Skips current notification when a link\r\ninside the notification is clicked.");
this.checkSkipOnLinkClick.UseVisualStyleBackColor = true;
//
// checkColumnName
//
this.checkColumnName.AutoSize = true;
this.checkColumnName.Location = new System.Drawing.Point(6, 5);
this.checkColumnName.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3);
this.checkColumnName.Location = new System.Drawing.Point(6, 26);
this.checkColumnName.Margin = new System.Windows.Forms.Padding(6, 6, 3, 3);
this.checkColumnName.Name = "checkColumnName";
this.checkColumnName.Size = new System.Drawing.Size(129, 17);
this.checkColumnName.TabIndex = 0;
this.checkColumnName.Text = "Display Column Name";
this.toolTip.SetToolTip(this.checkColumnName, "Shows column name each notification originated\r\nfrom in the notification window title.");
this.checkColumnName.UseVisualStyleBackColor = true;
//
// labelIdlePause
//
this.labelIdlePause.AutoSize = true;
this.labelIdlePause.Location = new System.Drawing.Point(3, 106);
this.labelIdlePause.Location = new System.Drawing.Point(3, 127);
this.labelIdlePause.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelIdlePause.Name = "labelIdlePause";
this.labelIdlePause.Size = new System.Drawing.Size(89, 13);
@@ -309,42 +307,39 @@
//
this.comboBoxIdlePause.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBoxIdlePause.FormattingEnabled = true;
this.comboBoxIdlePause.Location = new System.Drawing.Point(5, 122);
this.comboBoxIdlePause.Location = new System.Drawing.Point(5, 143);
this.comboBoxIdlePause.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3);
this.comboBoxIdlePause.Name = "comboBoxIdlePause";
this.comboBoxIdlePause.Size = new System.Drawing.Size(144, 21);
this.comboBoxIdlePause.TabIndex = 5;
this.toolTip.SetToolTip(this.comboBoxIdlePause, "Pauses new notifications after going idle for a set amount of time.");
//
// checkNonIntrusive
//
this.checkNonIntrusive.AutoSize = true;
this.checkNonIntrusive.Location = new System.Drawing.Point(6, 74);
this.checkNonIntrusive.Location = new System.Drawing.Point(6, 95);
this.checkNonIntrusive.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkNonIntrusive.Name = "checkNonIntrusive";
this.checkNonIntrusive.Size = new System.Drawing.Size(128, 17);
this.checkNonIntrusive.TabIndex = 3;
this.checkNonIntrusive.Text = "Non-Intrusive Popups";
this.toolTip.SetToolTip(this.checkNonIntrusive, "When not idle and the cursor is within the notification window area,\r\nit will be delayed until the cursor moves away to prevent accidental clicks.");
this.checkNonIntrusive.UseVisualStyleBackColor = true;
//
// checkTimerCountDown
//
this.checkTimerCountDown.AutoSize = true;
this.checkTimerCountDown.Location = new System.Drawing.Point(6, 28);
this.checkTimerCountDown.Location = new System.Drawing.Point(6, 236);
this.checkTimerCountDown.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkTimerCountDown.Name = "checkTimerCountDown";
this.checkTimerCountDown.Size = new System.Drawing.Size(119, 17);
this.checkTimerCountDown.TabIndex = 1;
this.checkTimerCountDown.Text = "Timer Counts Down";
this.toolTip.SetToolTip(this.checkTimerCountDown, "The notification timer counts down instead of up.");
this.checkTimerCountDown.UseVisualStyleBackColor = true;
//
// checkNotificationTimer
//
this.checkNotificationTimer.AutoSize = true;
this.checkNotificationTimer.Location = new System.Drawing.Point(6, 5);
this.checkNotificationTimer.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3);
this.checkNotificationTimer.Location = new System.Drawing.Point(6, 213);
this.checkNotificationTimer.Margin = new System.Windows.Forms.Padding(6, 6, 3, 3);
this.checkNotificationTimer.Name = "checkNotificationTimer";
this.checkNotificationTimer.Size = new System.Drawing.Size(145, 17);
this.checkNotificationTimer.TabIndex = 0;
@@ -353,57 +348,62 @@
//
// radioSizeAuto
//
this.radioSizeAuto.Location = new System.Drawing.Point(6, 4);
this.radioSizeAuto.Location = new System.Drawing.Point(5, 4);
this.radioSizeAuto.Margin = new System.Windows.Forms.Padding(5, 4, 3, 3);
this.radioSizeAuto.Name = "radioSizeAuto";
this.radioSizeAuto.Size = new System.Drawing.Size(92, 17);
this.radioSizeAuto.TabIndex = 0;
this.radioSizeAuto.TabStop = true;
this.radioSizeAuto.Text = "Auto";
this.toolTip.SetToolTip(this.radioSizeAuto, "Notification size is based on the font size and browser zoom level.");
this.radioSizeAuto.UseVisualStyleBackColor = true;
//
// radioSizeCustom
//
this.radioSizeCustom.Location = new System.Drawing.Point(106, 4);
this.radioSizeCustom.Location = new System.Drawing.Point(105, 4);
this.radioSizeCustom.Margin = new System.Windows.Forms.Padding(5, 4, 3, 3);
this.radioSizeCustom.Name = "radioSizeCustom";
this.radioSizeCustom.Size = new System.Drawing.Size(92, 17);
this.radioSizeCustom.TabIndex = 1;
this.radioSizeCustom.TabStop = true;
this.radioSizeCustom.Text = "Custom";
this.toolTip.SetToolTip(this.radioSizeCustom, "Resize the example notification window to the desired size.");
this.radioSizeCustom.UseVisualStyleBackColor = true;
//
// labelGeneral
//
this.labelGeneral.AutoSize = true;
this.labelGeneral.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelGeneral.Location = new System.Drawing.Point(6, 8);
this.labelGeneral.Margin = new System.Windows.Forms.Padding(0, 2, 0, 0);
this.labelGeneral.Location = new System.Drawing.Point(0, 0);
this.labelGeneral.Margin = new System.Windows.Forms.Padding(0);
this.labelGeneral.Name = "labelGeneral";
this.labelGeneral.Size = new System.Drawing.Size(66, 20);
this.labelGeneral.TabIndex = 0;
this.labelGeneral.Text = "General";
//
// panelGeneral
// panelEdgeDistance
//
this.panelGeneral.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelGeneral.Controls.Add(this.checkMediaPreviews);
this.panelGeneral.Controls.Add(this.checkColumnName);
this.panelGeneral.Controls.Add(this.checkSkipOnLinkClick);
this.panelGeneral.Controls.Add(this.checkNonIntrusive);
this.panelGeneral.Controls.Add(this.labelIdlePause);
this.panelGeneral.Controls.Add(this.comboBoxIdlePause);
this.panelGeneral.Location = new System.Drawing.Point(9, 31);
this.panelGeneral.Name = "panelGeneral";
this.panelGeneral.Size = new System.Drawing.Size(322, 149);
this.panelGeneral.TabIndex = 1;
this.panelEdgeDistance.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.panelEdgeDistance.Controls.Add(this.trackBarEdgeDistance);
this.panelEdgeDistance.Controls.Add(this.labelEdgeDistanceValue);
this.panelEdgeDistance.Location = new System.Drawing.Point(0, 516);
this.panelEdgeDistance.Margin = new System.Windows.Forms.Padding(0);
this.panelEdgeDistance.Name = "panelEdgeDistance";
this.panelEdgeDistance.Size = new System.Drawing.Size(322, 36);
this.panelEdgeDistance.TabIndex = 1;
//
// checkMediaPreviews
//
this.checkMediaPreviews.AutoSize = true;
this.checkMediaPreviews.Location = new System.Drawing.Point(6, 49);
this.checkMediaPreviews.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkMediaPreviews.Name = "checkMediaPreviews";
this.checkMediaPreviews.Size = new System.Drawing.Size(131, 17);
this.checkMediaPreviews.TabIndex = 1;
this.checkMediaPreviews.Text = "Show Media Previews";
this.checkMediaPreviews.UseVisualStyleBackColor = true;
//
// labelScrollSpeedValue
//
this.labelScrollSpeedValue.Location = new System.Drawing.Point(147, 53);
this.labelScrollSpeedValue.Location = new System.Drawing.Point(145, 4);
this.labelScrollSpeedValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.labelScrollSpeedValue.Name = "labelScrollSpeedValue";
this.labelScrollSpeedValue.Size = new System.Drawing.Size(38, 13);
@@ -415,7 +415,7 @@
//
this.trackBarScrollSpeed.AutoSize = false;
this.trackBarScrollSpeed.LargeChange = 25;
this.trackBarScrollSpeed.Location = new System.Drawing.Point(5, 52);
this.trackBarScrollSpeed.Location = new System.Drawing.Point(3, 3);
this.trackBarScrollSpeed.Maximum = 200;
this.trackBarScrollSpeed.Minimum = 25;
this.trackBarScrollSpeed.Name = "trackBarScrollSpeed";
@@ -428,7 +428,7 @@
// labelScrollSpeed
//
this.labelScrollSpeed.AutoSize = true;
this.labelScrollSpeed.Location = new System.Drawing.Point(3, 36);
this.labelScrollSpeed.Location = new System.Drawing.Point(3, 629);
this.labelScrollSpeed.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelScrollSpeed.Name = "labelScrollSpeed";
this.labelScrollSpeed.Size = new System.Drawing.Size(67, 13);
@@ -439,8 +439,8 @@
//
this.labelLocation.AutoSize = true;
this.labelLocation.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelLocation.Location = new System.Drawing.Point(6, 395);
this.labelLocation.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
this.labelLocation.Location = new System.Drawing.Point(0, 370);
this.labelLocation.Margin = new System.Windows.Forms.Padding(0, 20, 0, 0);
this.labelLocation.Name = "labelLocation";
this.labelLocation.Size = new System.Drawing.Size(70, 20);
this.labelLocation.TabIndex = 4;
@@ -448,42 +448,33 @@
//
// panelLocation
//
this.panelLocation.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelLocation.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.panelLocation.Controls.Add(this.radioLocTL);
this.panelLocation.Controls.Add(this.labelDisplay);
this.panelLocation.Controls.Add(this.trackBarEdgeDistance);
this.panelLocation.Controls.Add(this.comboBoxDisplay);
this.panelLocation.Controls.Add(this.radioLocTR);
this.panelLocation.Controls.Add(this.labelEdgeDistance);
this.panelLocation.Controls.Add(this.radioLocBL);
this.panelLocation.Controls.Add(this.radioLocCustom);
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(0, 390);
this.panelLocation.Margin = new System.Windows.Forms.Padding(0);
this.panelLocation.Name = "panelLocation";
this.panelLocation.Size = new System.Drawing.Size(322, 165);
this.panelLocation.Size = new System.Drawing.Size(322, 49);
this.panelLocation.TabIndex = 5;
//
// panelTimer
//
this.panelTimer.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelTimer.Controls.Add(this.labelDuration);
this.panelTimer.Controls.Add(this.checkNotificationTimer);
this.panelTimer.Controls.Add(this.tableLayoutDurationButtons);
this.panelTimer.Controls.Add(this.checkTimerCountDown);
this.panelTimer.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.panelTimer.Controls.Add(this.labelDurationValue);
this.panelTimer.Controls.Add(this.trackBarDuration);
this.panelTimer.Location = new System.Drawing.Point(9, 227);
this.panelTimer.Location = new System.Drawing.Point(0, 281);
this.panelTimer.Margin = new System.Windows.Forms.Padding(0);
this.panelTimer.Name = "panelTimer";
this.panelTimer.Size = new System.Drawing.Size(322, 144);
this.panelTimer.Size = new System.Drawing.Size(322, 36);
this.panelTimer.TabIndex = 3;
//
// labelDuration
//
this.labelDuration.AutoSize = true;
this.labelDuration.Location = new System.Drawing.Point(3, 60);
this.labelDuration.Location = new System.Drawing.Point(3, 268);
this.labelDuration.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelDuration.Name = "labelDuration";
this.labelDuration.Size = new System.Drawing.Size(47, 13);
@@ -494,8 +485,8 @@
//
this.labelTimer.AutoSize = true;
this.labelTimer.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelTimer.Location = new System.Drawing.Point(6, 204);
this.labelTimer.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
this.labelTimer.Location = new System.Drawing.Point(0, 187);
this.labelTimer.Margin = new System.Windows.Forms.Padding(0, 20, 0, 0);
this.labelTimer.Name = "labelTimer";
this.labelTimer.Size = new System.Drawing.Size(48, 20);
this.labelTimer.TabIndex = 2;
@@ -505,73 +496,96 @@
//
this.labelSize.AutoSize = true;
this.labelSize.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelSize.Location = new System.Drawing.Point(6, 607);
this.labelSize.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
this.labelSize.Location = new System.Drawing.Point(0, 572);
this.labelSize.Margin = new System.Windows.Forms.Padding(0, 20, 0, 0);
this.labelSize.Name = "labelSize";
this.labelSize.Size = new System.Drawing.Size(40, 20);
this.labelSize.TabIndex = 6;
this.labelSize.Text = "Size";
//
// panelMiscellaneous
// panelSize
//
this.panelMiscellaneous.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
this.panelSize.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelMiscellaneous.Controls.Add(this.radioSizeCustom);
this.panelMiscellaneous.Controls.Add(this.radioSizeAuto);
this.panelMiscellaneous.Controls.Add(this.labelScrollSpeedValue);
this.panelMiscellaneous.Controls.Add(this.trackBarScrollSpeed);
this.panelMiscellaneous.Controls.Add(this.labelScrollSpeed);
this.panelMiscellaneous.Location = new System.Drawing.Point(9, 630);
this.panelMiscellaneous.Name = "panelMiscellaneous";
this.panelMiscellaneous.Size = new System.Drawing.Size(322, 92);
this.panelMiscellaneous.TabIndex = 7;
this.panelSize.Controls.Add(this.radioSizeCustom);
this.panelSize.Controls.Add(this.radioSizeAuto);
this.panelSize.Location = new System.Drawing.Point(0, 592);
this.panelSize.Margin = new System.Windows.Forms.Padding(0);
this.panelSize.Name = "panelSize";
this.panelSize.Size = new System.Drawing.Size(322, 25);
this.panelSize.TabIndex = 7;
//
// durationUpdateTimer
//
this.durationUpdateTimer.Interval = 200;
this.durationUpdateTimer.Tick += new System.EventHandler(this.durationUpdateTimer_Tick);
//
// checkMediaPreviews
// flowPanel
//
this.checkMediaPreviews.AutoSize = true;
this.checkMediaPreviews.Location = new System.Drawing.Point(6, 28);
this.checkMediaPreviews.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3);
this.checkMediaPreviews.Name = "checkMediaPreviews";
this.checkMediaPreviews.Size = new System.Drawing.Size(131, 17);
this.checkMediaPreviews.TabIndex = 1;
this.checkMediaPreviews.Text = "Show Media Previews";
this.toolTip.SetToolTip(this.checkMediaPreviews, "Shows image and video thumbnails in the notification window.");
this.checkMediaPreviews.UseVisualStyleBackColor = true;
this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.flowPanel.Controls.Add(this.labelGeneral);
this.flowPanel.Controls.Add(this.checkColumnName);
this.flowPanel.Controls.Add(this.checkMediaPreviews);
this.flowPanel.Controls.Add(this.checkSkipOnLinkClick);
this.flowPanel.Controls.Add(this.checkNonIntrusive);
this.flowPanel.Controls.Add(this.labelIdlePause);
this.flowPanel.Controls.Add(this.comboBoxIdlePause);
this.flowPanel.Controls.Add(this.labelTimer);
this.flowPanel.Controls.Add(this.checkNotificationTimer);
this.flowPanel.Controls.Add(this.checkTimerCountDown);
this.flowPanel.Controls.Add(this.labelDuration);
this.flowPanel.Controls.Add(this.panelTimer);
this.flowPanel.Controls.Add(this.tableLayoutDurationButtons);
this.flowPanel.Controls.Add(this.labelLocation);
this.flowPanel.Controls.Add(this.panelLocation);
this.flowPanel.Controls.Add(this.labelDisplay);
this.flowPanel.Controls.Add(this.comboBoxDisplay);
this.flowPanel.Controls.Add(this.labelEdgeDistance);
this.flowPanel.Controls.Add(this.panelEdgeDistance);
this.flowPanel.Controls.Add(this.labelSize);
this.flowPanel.Controls.Add(this.panelSize);
this.flowPanel.Controls.Add(this.labelScrollSpeed);
this.flowPanel.Controls.Add(this.panelScrollSpeed);
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanel.Location = new System.Drawing.Point(9, 9);
this.flowPanel.Name = "flowPanel";
this.flowPanel.Size = new System.Drawing.Size(322, 678);
this.flowPanel.TabIndex = 8;
this.flowPanel.WrapContents = false;
//
// panelScrollSpeed
//
this.panelScrollSpeed.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.panelScrollSpeed.Controls.Add(this.trackBarScrollSpeed);
this.panelScrollSpeed.Controls.Add(this.labelScrollSpeedValue);
this.panelScrollSpeed.Location = new System.Drawing.Point(0, 642);
this.panelScrollSpeed.Margin = new System.Windows.Forms.Padding(0);
this.panelScrollSpeed.Name = "panelScrollSpeed";
this.panelScrollSpeed.Size = new System.Drawing.Size(322, 36);
this.panelScrollSpeed.TabIndex = 9;
//
// TabSettingsNotifications
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.panelMiscellaneous);
this.Controls.Add(this.labelSize);
this.Controls.Add(this.labelTimer);
this.Controls.Add(this.panelLocation);
this.Controls.Add(this.labelLocation);
this.Controls.Add(this.panelGeneral);
this.Controls.Add(this.labelGeneral);
this.Controls.Add(this.panelTimer);
this.Controls.Add(this.flowPanel);
this.Name = "TabSettingsNotifications";
this.Size = new System.Drawing.Size(340, 731);
this.Size = new System.Drawing.Size(340, 697);
this.ParentChanged += new System.EventHandler(this.TabSettingsNotifications_ParentChanged);
((System.ComponentModel.ISupportInitialize)(this.trackBarEdgeDistance)).EndInit();
this.tableLayoutDurationButtons.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.trackBarDuration)).EndInit();
this.panelGeneral.ResumeLayout(false);
this.panelGeneral.PerformLayout();
this.panelEdgeDistance.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.trackBarScrollSpeed)).EndInit();
this.panelLocation.ResumeLayout(false);
this.panelLocation.PerformLayout();
this.panelTimer.ResumeLayout(false);
this.panelTimer.PerformLayout();
this.panelMiscellaneous.ResumeLayout(false);
this.panelMiscellaneous.PerformLayout();
this.panelSize.ResumeLayout(false);
this.flowPanel.ResumeLayout(false);
this.flowPanel.PerformLayout();
this.panelScrollSpeed.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
}
@@ -601,7 +615,7 @@
private System.Windows.Forms.CheckBox checkTimerCountDown;
private System.Windows.Forms.CheckBox checkNotificationTimer;
private System.Windows.Forms.Label labelGeneral;
private System.Windows.Forms.Panel panelGeneral;
private System.Windows.Forms.Panel panelEdgeDistance;
private System.Windows.Forms.Label labelLocation;
private System.Windows.Forms.Panel panelLocation;
private System.Windows.Forms.Panel panelTimer;
@@ -610,11 +624,13 @@
private System.Windows.Forms.TrackBar trackBarScrollSpeed;
private System.Windows.Forms.Label labelScrollSpeed;
private System.Windows.Forms.Label labelSize;
private System.Windows.Forms.Panel panelMiscellaneous;
private System.Windows.Forms.Panel panelSize;
private System.Windows.Forms.Label labelDuration;
private System.Windows.Forms.Timer durationUpdateTimer;
private System.Windows.Forms.RadioButton radioSizeCustom;
private System.Windows.Forms.RadioButton radioSizeAuto;
private System.Windows.Forms.CheckBox checkMediaPreviews;
private System.Windows.Forms.FlowLayoutPanel flowPanel;
private System.Windows.Forms.Panel panelScrollSpeed;
}
}

View File

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

View File

@@ -34,8 +34,12 @@
this.panelSoundNotification = new System.Windows.Forms.Panel();
this.labelVolume = new System.Windows.Forms.Label();
this.trackBarVolume = new System.Windows.Forms.TrackBar();
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
this.panelVolume = new System.Windows.Forms.Panel();
this.panelSoundNotification.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.trackBarVolume)).BeginInit();
this.flowPanel.SuspendLayout();
this.panelVolume.SuspendLayout();
this.SuspendLayout();
//
// tbCustomSound
@@ -46,12 +50,11 @@
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.Location = new System.Drawing.Point(147, 4);
this.labelVolumeValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
this.labelVolumeValue.Name = "labelVolumeValue";
this.labelVolumeValue.Size = new System.Drawing.Size(38, 13);
@@ -98,8 +101,8 @@
//
this.labelSoundNotification.AutoSize = true;
this.labelSoundNotification.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.labelSoundNotification.Location = new System.Drawing.Point(6, 8);
this.labelSoundNotification.Margin = new System.Windows.Forms.Padding(0, 2, 0, 0);
this.labelSoundNotification.Location = new System.Drawing.Point(0, 0);
this.labelSoundNotification.Margin = new System.Windows.Forms.Padding(0);
this.labelSoundNotification.Name = "labelSoundNotification";
this.labelSoundNotification.Size = new System.Drawing.Size(198, 20);
this.labelSoundNotification.TabIndex = 1;
@@ -107,24 +110,21 @@
//
// panelSoundNotification
//
this.panelSoundNotification.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panelSoundNotification.Controls.Add(this.labelVolume);
this.panelSoundNotification.Controls.Add(this.trackBarVolume);
this.panelSoundNotification.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.panelSoundNotification.Controls.Add(this.btnPlaySound);
this.panelSoundNotification.Controls.Add(this.tbCustomSound);
this.panelSoundNotification.Controls.Add(this.btnResetSound);
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(0, 20);
this.panelSoundNotification.Margin = new System.Windows.Forms.Padding(0);
this.panelSoundNotification.Name = "panelSoundNotification";
this.panelSoundNotification.Size = new System.Drawing.Size(322, 119);
this.panelSoundNotification.Size = new System.Drawing.Size(322, 55);
this.panelSoundNotification.TabIndex = 2;
//
// labelVolume
//
this.labelVolume.AutoSize = true;
this.labelVolume.Location = new System.Drawing.Point(3, 67);
this.labelVolume.Location = new System.Drawing.Point(3, 87);
this.labelVolume.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
this.labelVolume.Name = "labelVolume";
this.labelVolume.Size = new System.Drawing.Size(42, 13);
@@ -135,29 +135,55 @@
//
this.trackBarVolume.AutoSize = false;
this.trackBarVolume.BackColor = System.Drawing.SystemColors.Control;
this.trackBarVolume.Location = new System.Drawing.Point(3, 83);
this.trackBarVolume.Location = new System.Drawing.Point(3, 3);
this.trackBarVolume.Maximum = 100;
this.trackBarVolume.Name = "trackBarVolume";
this.trackBarVolume.Size = new System.Drawing.Size(148, 30);
this.trackBarVolume.SmallChange = 1;
this.trackBarVolume.TabIndex = 5;
this.trackBarVolume.TickFrequency = 10;
this.trackBarVolume.Value = 100;
this.trackBarVolume.ValueChanged += new System.EventHandler(this.trackBarVolume_ValueChanged);
//
// flowPanel
//
this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.flowPanel.Controls.Add(this.labelSoundNotification);
this.flowPanel.Controls.Add(this.panelSoundNotification);
this.flowPanel.Controls.Add(this.labelVolume);
this.flowPanel.Controls.Add(this.panelVolume);
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanel.Location = new System.Drawing.Point(9, 9);
this.flowPanel.Name = "flowPanel";
this.flowPanel.Size = new System.Drawing.Size(322, 136);
this.flowPanel.TabIndex = 3;
this.flowPanel.WrapContents = false;
//
// panelVolume
//
this.panelVolume.Controls.Add(this.trackBarVolume);
this.panelVolume.Controls.Add(this.labelVolumeValue);
this.panelVolume.Location = new System.Drawing.Point(0, 100);
this.panelVolume.Margin = new System.Windows.Forms.Padding(0);
this.panelVolume.Name = "panelVolume";
this.panelVolume.Size = new System.Drawing.Size(322, 36);
this.panelVolume.TabIndex = 2;
//
// TabSettingsSounds
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.panelSoundNotification);
this.Controls.Add(this.labelSoundNotification);
this.Controls.Add(this.flowPanel);
this.Name = "TabSettingsSounds";
this.Size = new System.Drawing.Size(340, 160);
this.Size = new System.Drawing.Size(340, 154);
this.panelSoundNotification.ResumeLayout(false);
this.panelSoundNotification.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.trackBarVolume)).EndInit();
this.flowPanel.ResumeLayout(false);
this.flowPanel.PerformLayout();
this.panelVolume.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
}
@@ -173,5 +199,7 @@
private System.Windows.Forms.Label labelVolume;
private System.Windows.Forms.Label labelVolumeValue;
private System.Windows.Forms.TrackBar trackBarVolume;
private System.Windows.Forms.FlowLayoutPanel flowPanel;
private System.Windows.Forms.Panel panelVolume;
}
}

View File

@@ -16,17 +16,18 @@ namespace TweetDuck.Core.Other.Settings{
soundNotification = new SoundNotification();
soundNotification.PlaybackError += sound_PlaybackError;
Disposed += (sender, args) => soundNotification.Dispose();
supportsChangingVolume = soundNotification.SetVolume(Config.NotificationSoundVolume);
toolTip.SetToolTip(tbCustomSound, "When empty, the default TweetDeck sound notification is used.");
trackBarVolume.Enabled = supportsChangingVolume && !string.IsNullOrEmpty(Config.NotificationSoundPath);
trackBarVolume.SetValueSafe(Config.NotificationSoundVolume);
labelVolumeValue.Text = trackBarVolume.Value+"%";
tbCustomSound.Text = Config.NotificationSoundPath;
tbCustomSound_TextChanged(tbCustomSound, EventArgs.Empty);
Disposed += (sender, args) => soundNotification.Dispose();
}
public override void OnReady(){

View File

@@ -24,54 +24,40 @@
/// </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.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
this.flowPanel.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.Location = new System.Drawing.Point(6, 77);
this.checkTrayHighlight.Margin = new System.Windows.Forms.Padding(6, 6, 3, 3);
this.checkTrayHighlight.Name = "checkTrayHighlight";
this.checkTrayHighlight.Size = new System.Drawing.Size(103, 17);
this.checkTrayHighlight.TabIndex = 2;
this.checkTrayHighlight.Text = "Enable Highlight";
this.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.Location = new System.Drawing.Point(5, 25);
this.comboBoxTrayType.Margin = new System.Windows.Forms.Padding(5, 5, 3, 3);
this.comboBoxTrayType.Name = "comboBoxTrayType";
this.comboBoxTrayType.Size = new System.Drawing.Size(144, 21);
this.comboBoxTrayType.TabIndex = 0;
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.Location = new System.Drawing.Point(3, 58);
this.labelTrayIcon.Margin = new System.Windows.Forms.Padding(3, 9, 3, 0);
this.labelTrayIcon.Name = "labelTrayIcon";
this.labelTrayIcon.Size = new System.Drawing.Size(52, 13);
@@ -82,35 +68,48 @@
//
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.Location = new System.Drawing.Point(0, 0);
this.labelTray.Margin = new System.Windows.Forms.Padding(0);
this.labelTray.Name = "labelTray";
this.labelTray.Size = new System.Drawing.Size(96, 20);
this.labelTray.TabIndex = 0;
this.labelTray.Text = "System Tray";
//
// flowPanel
//
this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.flowPanel.Controls.Add(this.labelTray);
this.flowPanel.Controls.Add(this.comboBoxTrayType);
this.flowPanel.Controls.Add(this.labelTrayIcon);
this.flowPanel.Controls.Add(this.checkTrayHighlight);
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanel.Location = new System.Drawing.Point(9, 9);
this.flowPanel.Name = "flowPanel";
this.flowPanel.Size = new System.Drawing.Size(322, 97);
this.flowPanel.TabIndex = 2;
this.flowPanel.WrapContents = false;
//
// TabSettingsTray
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.panelTray);
this.Controls.Add(this.labelTray);
this.Controls.Add(this.flowPanel);
this.Name = "TabSettingsTray";
this.Size = new System.Drawing.Size(340, 119);
this.panelTray.ResumeLayout(false);
this.panelTray.PerformLayout();
this.Size = new System.Drawing.Size(340, 115);
this.flowPanel.ResumeLayout(false);
this.flowPanel.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;
private System.Windows.Forms.FlowLayoutPanel flowPanel;
}
}

View File

@@ -5,6 +5,9 @@ namespace TweetDuck.Core.Other.Settings{
public TabSettingsTray(){
InitializeComponent();
toolTip.SetToolTip(comboBoxTrayType, "Changes behavior of the Tray icon.\r\nRight-click the icon for an action menu.");
toolTip.SetToolTip(checkTrayHighlight, "Highlights the tray icon if there are new tweets.\r\nOnly works for columns with popup or audio notifications.\r\nThe icon resets when the main window is restored.");
comboBoxTrayType.Items.Add("Disabled");
comboBoxTrayType.Items.Add("Display Icon Only");
comboBoxTrayType.Items.Add("Minimize to Tray");

View File

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

View File

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

222
Core/TweetDeckBrowser.cs Normal file
View File

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

View File

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

View File

@@ -10,19 +10,7 @@ using TweetDuck.Core.Other;
namespace TweetDuck.Core.Utils{
static class BrowserUtils{
public static string HeaderAcceptLanguage{
get{
string culture = Program.Culture.Name;
if (culture == "en"){
return "en-us,en";
}
else{
return culture.ToLower()+",en;q=0.9";
}
}
}
public static string HeaderAcceptLanguage => "en-us,en";
public static string HeaderUserAgent => Program.BrandName+" "+Application.ProductVersion;
public static void SetupCefArgs(IDictionary<string, string> args){
@@ -31,7 +19,7 @@ namespace TweetDuck.Core.Utils{
args["disable-gpu-vsync"] = "1";
}
args["disable-extensions"] = "1";
args["disable-pdf-extension"] = "1";
args["disable-plugins-discovery"] = "1";
args["enable-system-flash"] = "0";
@@ -70,12 +58,18 @@ namespace TweetDuck.Core.Utils{
switch(CheckUrl(url)){
case UrlCheckResult.Fine:
OpenExternalBrowserUnsafe(url);
if (FormGuide.CheckGuideUrl(url, out string hash)){
FormGuide.Show(hash);
}
else{
WindowsUtils.OpenAssociatedProgram(url);
}
break;
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);
WindowsUtils.OpenAssociatedProgram(url);
}
break;
@@ -86,10 +80,6 @@ namespace TweetDuck.Core.Utils{
}
}
public static void OpenExternalBrowserUnsafe(string url){
using(Process.Start(url)){}
}
public static string GetFileNameFromUrl(string url){
string file = Path.GetFileName(new Uri(url).AbsolutePath);
return string.IsNullOrEmpty(file) ? null : file;
@@ -99,9 +89,14 @@ namespace TweetDuck.Core.Utils{
return StringUtils.ConvertPascalCaseToScreamingSnakeCase(Enum.GetName(typeof(CefErrorCode), code) ?? string.Empty);
}
public static WebClient DownloadFileAsync(string url, string target, Action onSuccess, Action<Exception> onFailure){
public static WebClient CreateWebClient(){
WebClient client = new WebClient{ Proxy = null };
client.Headers[HttpRequestHeader.UserAgent] = HeaderUserAgent;
return client;
}
public static WebClient DownloadFileAsync(string url, string target, Action onSuccess, Action<Exception> onFailure){
WebClient client = CreateWebClient();
client.DownloadFileCompleted += (sender, args) => {
if (args.Cancelled){

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

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

View File

@@ -8,6 +8,7 @@ namespace TweetDuck.Core.Utils{
[SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Local")]
static class NativeMethods{
public static readonly IntPtr HWND_BROADCAST = new IntPtr(0xFFFF);
public static readonly IntPtr HOOK_HANDLED = new IntPtr(-1);
public const int HWND_TOPMOST = -1;
@@ -51,6 +52,12 @@ namespace TweetDuck.Core.Utils{
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
private static extern bool SetWindowPos(int hWnd, int hWndOrder, int x, int y, int width, int height, uint flags);
[DllImport("user32.dll")]
public static extern bool PostMessage(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern uint RegisterWindowMessage(string messageName);
[DllImport("user32.dll")]
private static extern bool GetLastInputInfo(ref LASTINPUTINFO info);
@@ -96,13 +103,18 @@ namespace TweetDuck.Core.Utils{
}
}
public static void BroadcastMessage(uint msg, uint wParam, int lParam){
PostMessage(HWND_BROADCAST, msg, new UIntPtr(wParam), new IntPtr(lParam));
}
public static int GetMouseHookData(IntPtr ptr){
return Marshal.PtrToStructure<MSLLHOOKSTRUCT>(ptr).mouseData >> 16;
}
public static int GetIdleSeconds(){
LASTINPUTINFO info = new LASTINPUTINFO();
info.cbSize = LASTINPUTINFO.Size;
LASTINPUTINFO info = new LASTINPUTINFO{
cbSize = LASTINPUTINFO.Size
};
if (!GetLastInputInfo(ref info)){
return 0;

View File

@@ -18,5 +18,16 @@ namespace TweetDuck.Core.Utils{
public static string ConvertPascalCaseToScreamingSnakeCase(string str){
return Regex.Replace(str, @"(\p{Ll})(\P{Ll})|(\P{Ll})(\P{Ll}\p{Ll})", "$1$3_$2$4").ToUpper();
}
public static int CountOccurrences(string source, string substring){
int count = 0, index = 0;
while((index = source.IndexOf(substring, index)) != -1){
index += substring.Length;
++count;
}
return count;
}
}
}

View File

@@ -21,6 +21,10 @@ namespace TweetDuck.Core.Utils{
"tweetdeck", "TweetDeck", "tweetduck", "TweetDuck", "TD"
};
public static readonly string[] ValidImageExtensions = {
".jpg", ".jpeg", ".png", ".gif"
};
public enum ImageQuality{
Default, Orig
}
@@ -53,6 +57,10 @@ namespace TweetDuck.Core.Utils{
}
}
public static string GetImageFileName(string url){
return BrowserUtils.GetFileNameFromUrl(ExtractMediaBaseLink(url));
}
public static void DownloadImage(string url, string username, ImageQuality quality){
DownloadImages(new string[]{ url }, username, quality);
}
@@ -65,7 +73,7 @@ namespace TweetDuck.Core.Utils{
string firstImageLink = GetMediaLink(urls[0], quality);
int qualityIndex = firstImageLink.IndexOf(':', firstImageLink.LastIndexOf('/'));
string file = BrowserUtils.GetFileNameFromUrl(ExtractMediaBaseLink(firstImageLink));
string file = GetImageFileName(firstImageLink);
string ext = Path.GetExtension(file); // includes dot
string[] fileNameParts = qualityIndex == -1 ? new string[]{
@@ -103,7 +111,7 @@ namespace TweetDuck.Core.Utils{
}
}
public static void DownloadVideo(string url){
public static void DownloadVideo(string url, string username){
string filename = BrowserUtils.GetFileNameFromUrl(url);
string ext = Path.GetExtension(filename);
@@ -111,7 +119,7 @@ namespace TweetDuck.Core.Utils{
AutoUpgradeEnabled = true,
OverwritePrompt = true,
Title = "Save video",
FileName = filename,
FileName = string.IsNullOrEmpty(username) ? filename : $"{username} {filename}",
Filter = "Video"+(string.IsNullOrEmpty(ext) ? " (unknown)|*.*" : $" (*{ext})|*{ext}")
}){
if (dialog.ShowDialog() == DialogResult.OK){

View File

@@ -1,7 +1,7 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Management;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
@@ -49,17 +49,22 @@ namespace TweetDuck.Core.Utils{
}
}
public static Process StartProcess(string file, string arguments, bool runElevated){
ProcessStartInfo processInfo = new ProcessStartInfo{
public static bool OpenAssociatedProgram(string file, string arguments = "", bool runElevated = false){
try{
using(Process.Start(new ProcessStartInfo{
FileName = file,
Arguments = arguments
};
if (runElevated){
processInfo.Verb = "runas";
Arguments = arguments,
Verb = runElevated ? "runas" : string.Empty,
ErrorDialog = true
})){
return true;
}
}catch(Win32Exception e) when (e.NativeErrorCode == 0x000004C7){ // operation canceled by the user
return false;
}catch(Exception e){
Program.Reporter.HandleException("Error opening file", e.Message, true, e);
return false;
}
return Process.Start(processInfo);
}
public static bool TrySleepUntil(Func<bool> test, int timeoutMillis, int timeStepMillis){
@@ -89,22 +94,8 @@ namespace TweetDuck.Core.Utils{
}).Start();
}
public static bool IsChildProcess(int pid){
try{
using(ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = "+pid)){
foreach(ManagementBaseObject obj in searcher.Get()){
return (uint)obj["ParentProcessId"] == CurrentProcessID;
}
}
return false;
}catch{
return false;
}
}
public static void ClipboardStripHtmlStyles(){
if (!Clipboard.ContainsText(TextDataFormat.Html)){
if (!Clipboard.ContainsText(TextDataFormat.Html) || !Clipboard.ContainsText(TextDataFormat.UnicodeText)){
return;
}

View File

@@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using TweetDuck.Core.Utils;
namespace TweetDuck.Data.Serialization{
sealed class FileSerializer<T>{
@@ -28,6 +29,8 @@ namespace TweetDuck.Data.Serialization{
}
public void Write(string file, T obj){
WindowsUtils.CreateDirectoryForFile(file);
using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
foreach(KeyValuePair<string, PropertyInfo> prop in props){
Type type = prop.Value.PropertyType;
@@ -54,7 +57,11 @@ namespace TweetDuck.Data.Serialization{
Dictionary<string, string> unknownProperties = new Dictionary<string, string>(4);
using(StreamReader reader = new StreamReader(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))){
if (reader.Peek() <= 1){
switch(reader.Peek()){
case -1:
throw new FormatException("File is empty.");
case 0:
case 1:
throw new FormatException("Input appears to be a binary file.");
}
@@ -95,6 +102,21 @@ namespace TweetDuck.Data.Serialization{
}
}
public void ReadIfExists(string file, T obj){
try{
Read(file, obj);
}catch(FileNotFoundException){
}catch(DirectoryNotFoundException){}
}
public static HandleUnknownPropertiesHandler IgnoreProperties(params string[] properties){
return (obj, data) => {
foreach(string property in properties){
data.Remove(property);
}
};
}
private sealed class BasicTypeConverter : ITypeConverter{
bool ITypeConverter.TryWriteType(Type type, object value, out string converted){
switch(Type.GetTypeCode(type)){

View File

@@ -62,9 +62,7 @@ namespace TweetDuck.Plugins.Controls{
}
private void btnToggleState_Click(object sender, EventArgs e){
bool newState = !pluginManager.Config.IsEnabled(plugin);
pluginManager.Config.SetEnabled(plugin, newState);
pluginManager.Config.ToggleEnabled(plugin);
UpdatePluginState();
}

View File

@@ -7,12 +7,11 @@ using TweetDuck.Plugins.Enums;
namespace TweetDuck.Plugins{
sealed class Plugin{
private static readonly Version AppVersion = new Version(Program.VersionTag);
private const string VersionWildcard = "*";
public string Identifier { get; }
public PluginGroup Group { get; }
public PluginEnvironment Environments { get; private set; }
public PluginEnvironment Environments { get; }
public string Name => metadata["NAME"];
public string Description => metadata["DESCRIPTION"];
@@ -23,9 +22,7 @@ namespace TweetDuck.Plugins{
public string ConfigDefault => metadata["CONFIGDEFAULT"];
public string RequiredVersion => metadata["REQUIRES"];
public bool CanRun{
get => canRun ?? (canRun = CheckRequiredVersion(RequiredVersion)).Value;
}
public bool CanRun { get; private set; }
public bool HasConfig{
get => ConfigFile.Length > 0 && GetFullPathIfSafe(PluginFolder.Data, ConfigFile).Length > 0;
@@ -56,21 +53,18 @@ namespace TweetDuck.Plugins{
{ "REQUIRES", VersionWildcard }
};
private bool? canRun;
private Plugin(string path, PluginGroup group){
string name = Path.GetFileName(path);
System.Diagnostics.Debug.Assert(name != null);
private Plugin(string path, string name, PluginGroup group, PluginEnvironment environments){
this.pathRoot = path;
this.pathData = Path.Combine(Program.PluginDataPath, group.GetIdentifierPrefix(), name);
this.Identifier = group.GetIdentifierPrefix()+name;
this.Group = group;
this.Environments = PluginEnvironment.None;
this.Environments = environments;
}
private void OnMetadataLoaded(){
CanRun = CheckRequiredVersion(RequiredVersion);
string configPath = ConfigPath, defaultConfigPath = DefaultConfigPath;
if (configPath.Length > 0 && defaultConfigPath.Length > 0 && !File.Exists(configPath) && File.Exists(defaultConfigPath)){
@@ -80,7 +74,7 @@ namespace TweetDuck.Plugins{
Directory.CreateDirectory(dataFolder);
File.Copy(defaultConfigPath, configPath, false);
}catch(Exception e){
Program.Reporter.HandleException("Plugin Loading Error", "Could not generate a configuration file for '"+Identifier+"' plugin.", true, e);
throw new IOException("Could not generate a configuration file for '"+Identifier+"' plugin: "+e.Message, e);
}
}
}
@@ -138,89 +132,74 @@ namespace TweetDuck.Plugins{
return obj is Plugin plugin && plugin.Identifier.Equals(Identifier);
}
public static Plugin CreateFromFolder(string path, PluginGroup group, out string error){
Plugin plugin = new Plugin(path, group);
// Static
if (!LoadMetadata(path, plugin, out error)){
return null;
}
private static readonly Version AppVersion = new Version(Program.VersionTag);
private static readonly string[] EndTag = { "[END]" };
if (!LoadEnvironments(path, plugin, out error)){
return null;
}
error = string.Empty;
public static Plugin CreateFromFolder(string path, PluginGroup group){
Plugin plugin = new Plugin(path, Path.GetFileName(path), group, LoadEnvironments(path));
LoadMetadata(path, plugin);
return plugin;
}
private static bool LoadEnvironments(string path, Plugin plugin, out string error){
private static PluginEnvironment LoadEnvironments(string path){
PluginEnvironment environments = PluginEnvironment.None;
foreach(string file in Directory.EnumerateFiles(path, "*.js", SearchOption.TopDirectoryOnly).Select(Path.GetFileName)){
plugin.Environments |= PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetPluginScriptFile(), StringComparison.Ordinal));
environments |= PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetPluginScriptFile(), StringComparison.Ordinal));
}
if (plugin.Environments == PluginEnvironment.None){
error = "Plugin has no script files.";
return false;
if (environments == PluginEnvironment.None){
throw new ArgumentException("Plugin has no script files");
}
error = string.Empty;
return true;
return environments;
}
private static readonly string[] endTag = { "[END]" };
private static bool LoadMetadata(string path, Plugin plugin, out string error){
private static void LoadMetadata(string path, Plugin plugin){
string metaFile = Path.Combine(path, ".meta");
if (!File.Exists(metaFile)){
error = "Missing .meta file.";
return false;
throw new ArgumentException("Missing .meta file");
}
string[] lines = File.ReadAllLines(metaFile, Encoding.UTF8);
string currentTag = null, currentContents = "";
string currentTag = null, currentContents = string.Empty;
foreach(string line in lines.Concat(endTag).Select(line => line.TrimEnd()).Where(line => line.Length > 0)){
foreach(string line in File.ReadAllLines(metaFile, Encoding.UTF8).Concat(EndTag).Select(line => line.TrimEnd()).Where(line => line.Length > 0)){
if (line[0] == '[' && line[line.Length-1] == ']'){
if (currentTag != null){
plugin.metadata[currentTag] = currentContents;
}
currentTag = line.Substring(1, line.Length-2).ToUpper();
currentContents = "";
currentContents = string.Empty;
if (line.Equals(endTag[0])){
if (line.Equals(EndTag[0])){
break;
}
if (!plugin.metadata.ContainsKey(currentTag)){
error = "Invalid metadata tag: "+currentTag;
return false;
throw new FormatException("Invalid metadata tag: "+currentTag);
}
}
else if (currentTag != null){
currentContents = currentContents.Length == 0 ? line : currentContents+"\r\n"+line;
currentContents = currentContents.Length == 0 ? line : currentContents+Environment.NewLine+line;
}
else{
error = "Missing metadata tag before value: "+line;
return false;
throw new FormatException("Missing metadata tag before value: "+line);
}
}
if (plugin.Name.Length == 0){
error = "Plugin is missing a name in the .meta file.";
return false;
throw new FormatException("Plugin is missing a name in the .meta file");
}
if (plugin.RequiredVersion.Length == 0 || !(plugin.RequiredVersion == VersionWildcard || System.Version.TryParse(plugin.RequiredVersion, out Version _))){
error = "Plugin contains invalid version: "+plugin.RequiredVersion;
return false;
throw new FormatException("Plugin contains invalid version: "+plugin.RequiredVersion);
}
plugin.OnMetadataLoaded();
error = string.Empty;
return true;
}
private static bool CheckRequiredVersion(string requires){

View File

@@ -24,10 +24,28 @@ namespace TweetDuck.Plugins{
}
}
public void ToggleEnabled(Plugin plugin){
SetEnabled(plugin, !IsEnabled(plugin));
}
public bool IsEnabled(Plugin plugin){
return !disabled.Contains(plugin.Identifier);
}
public void Save(string file){
try{
using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None), Encoding.UTF8)){
writer.WriteLine("#Disabled");
foreach(string identifier in disabled){
writer.WriteLine(identifier);
}
}
}catch(Exception e){
Program.Reporter.HandleException("Plugin Configuration Error", "Could not save the plugin configuration file.", true, e);
}
}
public void Load(string file){
try{
using(StreamReader reader = new StreamReader(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read), Encoding.UTF8)){
@@ -43,30 +61,12 @@ namespace TweetDuck.Plugins{
}
}catch(FileNotFoundException){
disabled.Clear();
foreach(string identifier in DefaultDisabled){
disabled.Add(identifier);
}
disabled.UnionWith(DefaultDisabled);
Save(file);
}catch(DirectoryNotFoundException){
}catch(Exception e){
Program.Reporter.HandleException("Plugin Configuration Error", "Could not read the plugin configuration file. If you continue, the list of disabled plugins will be reset to default.", true, e);
}
}
public void Save(string file){
try{
using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None), Encoding.UTF8)){
writer.WriteLine("#Disabled");
foreach(string identifier in disabled){
writer.WriteLine(identifier);
}
}
}catch(Exception e){
Program.Reporter.HandleException("Plugin Configuration Error", "Could not save the plugin configuration file.", true, e);
}
}
}
}

View File

@@ -9,8 +9,6 @@ using TweetDuck.Resources;
namespace TweetDuck.Plugins{
sealed class PluginManager{
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") },
@@ -35,8 +33,6 @@ namespace TweetDuck.Plugins{
private readonly Dictionary<int, Plugin> tokens = new Dictionary<int, Plugin>();
private readonly Random rand = new Random();
private List<string> loadErrors;
public PluginManager(string rootPath, string configPath){
this.rootPath = rootPath;
this.configPath = configPath;
@@ -68,7 +64,18 @@ namespace TweetDuck.Plugins{
}
}
return InvalidToken;
int token, attempts = 1000;
do{
token = rand.Next();
}while(tokens.ContainsKey(token) && --attempts >= 0);
if (attempts < 0){
token = -tokens.Count-1;
}
tokens[token] = plugin;
return token;
}
public Plugin GetPluginFromToken(int token){
@@ -81,28 +88,41 @@ namespace TweetDuck.Plugins{
plugins.Clear();
tokens.Clear();
loadErrors = new List<string>(2);
List<string> loadErrors = new List<string>(2);
foreach(Plugin plugin in LoadPluginsFrom(PathOfficialPlugins, PluginGroup.Official)){
plugins.Add(plugin);
IEnumerable<Plugin> LoadPluginsFrom(string path, PluginGroup group){
if (!Directory.Exists(path)){
yield break;
}
foreach(Plugin plugin in LoadPluginsFrom(PathCustomPlugins, PluginGroup.Custom)){
plugins.Add(plugin);
foreach(string fullDir in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)){
Plugin plugin;
try{
plugin = Plugin.CreateFromFolder(fullDir, group);
}catch(Exception e){
loadErrors.Add(group.GetIdentifierPrefix()+Path.GetFileName(fullDir)+": "+e.Message);
continue;
}
yield return plugin;
}
}
plugins.UnionWith(LoadPluginsFrom(PathOfficialPlugins, PluginGroup.Official));
plugins.UnionWith(LoadPluginsFrom(PathCustomPlugins, PluginGroup.Custom));
Reloaded?.Invoke(this, new PluginErrorEventArgs(loadErrors));
}
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);
}
if (!HasAnyPlugin(environment)){
return;
}
private void ExecutePluginScripts(IFrame frame, PluginEnvironment environment){
ScriptLoader.ExecuteScript(frame, PluginSetupScripts[environment], environment.GetScriptIdentifier());
ScriptLoader.ExecuteScript(frame, PluginSetupScripts[PluginEnvironment.None], PluginEnvironment.None.GetScriptIdentifier());
bool includeDisabled = environment.IncludesDisabledPlugins();
if (includeDisabled){
@@ -113,7 +133,10 @@ namespace TweetDuck.Plugins{
foreach(Plugin plugin in Plugins){
string path = plugin.GetScriptPath(environment);
if (string.IsNullOrEmpty(path) || (!includeDisabled && !Config.IsEnabled(plugin)) || !plugin.CanRun)continue;
if (string.IsNullOrEmpty(path) || (!includeDisabled && !Config.IsEnabled(plugin)) || !plugin.CanRun){
continue;
}
string script;
@@ -124,49 +147,10 @@ namespace TweetDuck.Plugins{
continue;
}
int token;
if (tokens.ContainsValue(plugin)){
token = GetTokenFromPlugin(plugin);
}
else{
token = GenerateToken();
tokens[token] = plugin;
}
ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GeneratePlugin(plugin.Identifier, script, token, environment), "plugin:"+plugin);
ScriptLoader.ExecuteScript(frame, PluginScriptGenerator.GeneratePlugin(plugin.Identifier, script, GetTokenFromPlugin(plugin), environment), "plugin:"+plugin);
}
Executed?.Invoke(this, new PluginErrorEventArgs(failedPlugins));
}
private IEnumerable<Plugin> LoadPluginsFrom(string path, PluginGroup group){
if (!Directory.Exists(path)){
yield break;
}
foreach(string fullDir in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)){
Plugin plugin = Plugin.CreateFromFolder(fullDir, group, out string error);
if (plugin == null){
loadErrors.Add(group.GetIdentifierPrefix()+Path.GetFileName(fullDir)+": "+error);
}
else{
yield return plugin;
}
}
}
private int GenerateToken(){
for(int attempt = 0; attempt < 1000; attempt++){
int token = rand.Next();
if (!tokens.ContainsKey(token) && token != InvalidToken){
return token;
}
}
return -tokens.Count;
}
}
}

View File

@@ -14,14 +14,13 @@ using TweetDuck.Core.Other.Settings.Export;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Updates;
using TweetLib.Communication;
namespace TweetDuck{
static class Program{
public const string BrandName = "TweetDuck";
public const string Website = "https://tweetduck.chylex.com";
public const string VersionTag = "1.10";
public const string VersionTag = "1.12.1.1";
public static readonly bool IsPortable = File.Exists("makeportable");
@@ -37,12 +36,12 @@ namespace TweetDuck{
public static string UserConfigFilePath => Path.Combine(StoragePath, "TD_UserConfig.cfg");
public static string SystemConfigFilePath => Path.Combine(StoragePath, "TD_SystemConfig.cfg");
public static string PluginConfigFilePath => Path.Combine(StoragePath, "TD_PluginConfig.cfg");
public static string AnalyticsFilePath => Path.Combine(StoragePath, "TD_Analytics.cfg");
private static string ErrorLogFilePath => Path.Combine(StoragePath, "TD_Log.txt");
private static string ConsoleLogFilePath => Path.Combine(StoragePath, "TD_Console.txt");
public static uint WindowRestoreMessage;
public static uint SubProcessMessage;
private static readonly LockManager LockManager = new LockManager(Path.Combine(StoragePath, ".lock"));
private static bool HasCleanedUp;
@@ -69,9 +68,9 @@ namespace TweetDuck{
private static void Main(){
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Cef.EnableHighDPISupport();
WindowRestoreMessage = Comms.RegisterMessage("TweetDuckRestore");
SubProcessMessage = Comms.RegisterMessage("TweetDuckSubProcess");
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore");
if (!WindowsUtils.CheckFolderWritePermission(StoragePath)){
FormMessage.Warning("Permission Error", "TweetDuck does not have write permissions to the storage folder: "+StoragePath, FormMessage.OK);
@@ -128,12 +127,12 @@ namespace TweetDuck{
WindowsUtils.TryDeleteFolderWhenAble(InstallerPath, 8000);
}
BrowserCache.RefreshTimer();
CefSharpSettings.WcfEnabled = false;
CefSettings settings = new CefSettings{
AcceptLanguageList = BrowserUtils.HeaderAcceptLanguage,
UserAgent = BrowserUtils.HeaderUserAgent,
Locale = Arguments.GetValue(Arguments.ArgLocale, string.Empty),
BrowserSubprocessPath = BrandName+".Browser.exe",
CachePath = StoragePath,
LogFile = ConsoleLogFilePath,
@@ -145,7 +144,6 @@ namespace TweetDuck{
CommandLineArgs.ReadCefArguments(UserConfig.CustomCefArgs).ToDictionary(settings.CefCommandLineArgs);
BrowserUtils.SetupCefArgs(settings.CefCommandLineArgs);
Cef.EnableHighDPISupport();
Cef.Initialize(settings, false, new BrowserProcessHandler());
Application.ApplicationExit += (sender, args) => ExitCleanup();
@@ -166,9 +164,13 @@ namespace TweetDuck{
string updaterArgs = "/SP- /SILENT /CLOSEAPPLICATIONS /UPDATEPATH=\""+ProgramPath+"\" /RUNARGS=\""+Arguments.GetCurrentForInstallerCmd()+"\""+(IsPortable ? " /PORTABLE=1" : "");
bool runElevated = !IsPortable || !WindowsUtils.CheckFolderWritePermission(ProgramPath);
WindowsUtils.StartProcess(mainForm.UpdateInstallerPath, updaterArgs, runElevated);
if (WindowsUtils.OpenAssociatedProgram(mainForm.UpdateInstallerPath, updaterArgs, runElevated)){
Application.Exit();
}
else{
RestartWithArgsInternal(Arguments.GetCurrentClean());
}
}
}
private static string GetDataStoragePath(){
@@ -211,11 +213,14 @@ namespace TweetDuck{
FormBrowser browserForm = Application.OpenForms.OfType<FormBrowser>().FirstOrDefault();
if (browserForm == null)return;
args.AddFlag(Arguments.ArgRestart);
browserForm.ForceClose();
ExitCleanup();
ExitCleanup();
RestartWithArgsInternal(args);
}
private static void RestartWithArgsInternal(CommandLineArgs args){
args.AddFlag(Arguments.ArgRestart);
Process.Start(Application.ExecutablePath, args.ToString());
Application.Exit();
}

View File

@@ -1,3 +1,7 @@
# 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)
# Build Instructions
### Setup
@@ -10,10 +14,12 @@ The program was built using Visual Studio 2017. Before opening the solution, ple
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 63.0.0-pre01 -Source https://www.myget.org/F/cefsharp/api/v3/index.json
PM> Install-Package Microsoft.VC120.CRT.JetBrains
```
Note that some pre-release builds of CefSharp are not available on NuGet. To correctly restore packages in that case, open **Package Manager Settings**, and add `https://www.myget.org/F/cefsharp/api/v3/index.json` to the list of package sources.
### Debug
It is recommended to create a separate data folder for debugging, otherwise you will not be able to run TweetDuck while debugging the solution.
@@ -25,21 +31,34 @@ To do that, open **TweetDuck Properties**, click the **Debug** tab, make sure yo
### 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.
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, see the [Troubleshooting](#troubleshooting) section.
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.
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.
### Troubleshooting
There are a few quirks in the build process that may catch you off guard:
- **Plugin files are not updated automatically**
- Since official plugins (`Resources/Plugins`) are not included in the project, Visual Studio will not automatically detect changes in the files
- To ensure plugins are updated when testing the app, click **Rebuild Solution** before clicking **Start**
- **Error: The command (...) exited with code 1**
- If the post-build event fails, open the **Output** tab and look for the cause
- Determine if there was an IO error while copying files or modifying folders, or whether the final .ps1 script failed (`Encountered an error while running PostBuild.ps1 on line xyz`)
- Some files are checked for invalid characters:
- `Resources/Plugins/emoji-keyboard/emoji-ordering.txt` line endings must be LF (line feed); any CR (carriage return) in the file will cause a failed build, and you will need to ensure correct line endings in your text editor
### 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!
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:
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**

View File

@@ -28,7 +28,7 @@
<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>Click <em>Settings</em> in the left panel and select <em>TweetDuck</em>, or...</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="">
@@ -57,9 +57,10 @@
<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 first to bring the reply into the large panel.</p>
<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 scroll through the emoji, click on the emoji to insert them, and change the skin tone.</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>
@@ -124,7 +125,7 @@
<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>
<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>
@@ -330,10 +331,10 @@
</details>
<details>
<summary id="popout-replies">How to instantly popout replies</summary>
<summary id="popout-replies">How to instantly popout replies or quote tweets</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>
<p><em>Middle-click the reply icon</em> to instantly open your reply in the New Tweet panel, or <em>middle-click the retweet icon</em> to quote a tweet instead of retweeting it.</p>
<p>Middle-clicking is usually done by pressing your mouse wheel as if it was a button. Some mice or other devices, such as laptop touchpads, may have a dedicated button or button combination instead.</p>
</div>
</details>

View File

@@ -71,7 +71,17 @@ var init = function(){
if (element && element.tagName === "SUMMARY"){
element.click();
element.blur();
element.scrollIntoView(true);
if (window.innerWidth === 0){
var ffs = function(){
element.scrollIntoView(true);
window.removeEventListener("resize", ffs);
};
window.addEventListener("resize", ffs);
}
}
}
};

View File

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

View File

@@ -6,12 +6,15 @@ enabled(){
this.config = null;
this.defaultConfig = {
_theme: "light",
columnWidth: "310px",
fontSize: "12px",
hideTweetActions: true,
moveTweetActionsToRight: true,
themeColorTweaks: true,
revertIcons: true,
showCharacterCount: true,
increaseQuoteTextSize: false,
smallComposeTextSize: false,
optimizeAnimations: true,
avatarRadius: 2
@@ -37,11 +40,13 @@ enabled(){
this.currentStage = 1;
}
else if (this.tmpConfig !== null){
let needsResave = !("_theme" in this.tmpConfig);
this.config = $.extend(this.defaultConfig, this.tmpConfig);
this.tmpConfig = null;
this.reinjectAll();
if (this.firstTimeLoad){
if (this.firstTimeLoad || needsResave){
$TDP.writeFile(this.$token, configFile, JSON.stringify(this.config));
}
}
@@ -59,6 +64,8 @@ enabled(){
}
else{
$(document).one("dataSettingsValues", () => {
this.defaultConfig._theme = TD.settings.getTheme();
switch(TD.settings.getColumnWidth()){
case "wide": this.defaultConfig.columnWidth = "350px"; break;
case "narrow": this.defaultConfig.columnWidth = "270px"; break;
@@ -172,28 +179,44 @@ enabled(){
}
// SELECTS
else if (tag === "SELECT"){
if (!item.val(me.config[key]).val()){
let custom = item.find("option[value='custom']");
let optionCustom = item.find("option[value^='custom']");
if (custom.length === 1){
item.val("custom");
custom.text(getTextForCustom(key));
}
let resetMyValue = () => {
if (!item.val(me.config[key]).val() && optionCustom.length === 1){
item.val(optionCustom.attr("value"));
optionCustom.text(getTextForCustom(key));
}
};
resetMyValue();
item.change(function(){ // TODO change doesn't fire when Custom is already selected
let val = item.val();
if (val === "custom"){
val = prompt("Enter custom value:");
if (val === "custom-px"){
val = (prompt("Enter custom value (px):") || "").trim();
if (val){
updateKey(key, val);
item.find("option[value='custom']").text(getTextForCustom(key));
if (val.endsWith("px")){
val = val.slice(0, -2).trim();
}
if (/^[0-9]+$/.test(val)){
updateKey(key, val+"px");
optionCustom.text(getTextForCustom(key));
}
else{
alert("Invalid value, only px values are supported.");
resetMyValue();
}
}
else{
resetMyValue();
}
}
else{
updateKey(key, item.val());
optionCustom.text("Custom");
}
});
}
@@ -217,9 +240,11 @@ enabled(){
modal.find("[data-td-theme='"+TD.settings.getTheme()+"']").prop("checked", true);
modal.find("[data-td-theme]").change(function(){
me.config._theme = $(this).attr("data-td-theme");
setTimeout(function(){
TD.settings.setTheme($(this).attr("data-td-theme"));
$(document).trigger("uiToggleTheme");
me.saveConfig();
me.reinjectAll();
}, 1);
});
@@ -331,7 +356,7 @@ enabled(){
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(".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 }");
let notificationScrollbarColor = null;
@@ -339,128 +364,140 @@ enabled(){
if (this.config.themeColorTweaks){
switch(TD.settings.getTheme()){
case "dark":
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: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 }");
this.css.insert(".app-content, .app-columns-container { background-color: #444448 !important }");
this.css.insert(".column-drag-handle { opacity: 0.5 !important }");
this.css.insert(".column-drag-handle:hover { opacity: 1 !important }");
this.css.insert(".scroll-styled-v:not(.scroll-alt)::-webkit-scrollbar-thumb, .scroll-styled-h:not(.scroll-alt)::-webkit-scrollbar-thumb { background-color: #666 !important }");
notificationScrollbarColor = "666";
break;
case "light":
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(".scroll-styled-v:not(.scroll-alt)::-webkit-scrollbar-thumb, .scroll-styled-h:not(.scroll-alt)::-webkit-scrollbar-thumb { background-color: #d2d6da !important }");
this.css.insert(".app-columns-container.scroll-styled-h::-webkit-scrollbar-thumb:not(:hover) { background-color: #a5aeb5 !important }");
notificationScrollbarColor = "a5aeb5";
break;
}
}
if (this.config.showCharacterCount){
this.css.insert("#tduck .js-character-count.is-hidden { display: inline !important }");
}
if (this.config.hideTweetActions){
this.css.insert(".tweet-action { opacity: 0; }");
this.css.insert(".tweet-actions.is-visible .tweet-action { opacity: 0.5; }");
this.css.insert(".tweet-actions.is-visible .tweet-action { opacity: 0.5 }");
this.css.insert(".is-favorite .tweet-action, .is-retweet .tweet-action { opacity: 0.5; visibility: visible !important }");
this.css.insert(".tweet:hover .tweet-action, .tweet-action.is-selected, .is-favorite .tweet-action[rel='favorite'], .is-retweet .tweet-action[rel='retweet'] { opacity: 1 !important; visibility: visible !important }");
}
if (this.config.moveTweetActionsToRight){
this.css.insert(".tweet-actions { float: right !important; width: auto !important }");
this.css.insert(".tweet-actions > li:nth-child(4) { margin-right: 2px !important }");
this.css.insert("#tduck .tweet-actions { float: right !important; width: auto !important }");
this.css.insert("#tduck .tweet-actions > li:nth-child(4) { margin-right: 2px !important }");
}
if (this.config.increaseQuoteTextSize){
this.css.insert(".quoted-tweet { font-size: 1em !important }");
}
if (this.config.smallComposeTextSize){
this.css.insert(".compose-text { font-size: 12px !important; height: 120px !important }");
this.css.insert("#tduck .compose-text { font-size: 12px !important; height: 120px !important }");
}
if (this.config.revertIcons){
let iconData = [
[ ".icon-twitter-bird", "00" ],
[ ".icon-mention", "01" ],
[ ".icon-following", "02" ],
[ ".icon-message", "03" ],
[ ".icon-home", "04" ],
[ ".icon-hashtag", "05" ],
[ ".icon-reply", "06" ],
[ ".icon-favorite", "55" ],
[ ".icon-retweet", "08" ],
[ ".icon-drafts", "09" ],
[ ".icon-search", "0a" ],
[ ".icon-trash", "0c" ],
[ ".icon-close", "0d" ],
[ ".icon-arrow-r:before,.Icon--caretRight", "0e" ],
[ ".icon-arrow-l:before,.Icon--caretLeft", "0f" ],
[ ".icon-protected", "13" ],
[ ".icon-list", "14" ],
[ ".icon-camera", "15" ],
[ ".icon-more", "16" ],
[ ".icon-settings", "18" ],
[ ".icon-notifications", "19" ],
[ ".icon-user-dd", "1a" ],
[ ".icon-activity", "1c" ],
[ ".icon-trending", "1d" ],
[ ".icon-minus", "1e" ],
[ ".icon-plus", "1f" ],
[ ".icon-geo", "20" ],
[ ".icon-check", "21" ],
[ ".icon-schedule", "22" ],
[ ".icon-dot", "23" ],
[ ".icon-user", "24" ],
[ ".icon-content", "25" ],
[ ".icon-arrow-d:before,.Icon--caretDown", "26" ],
[ ".icon-arrow-u", "27" ],
[ ".icon-share", "28" ],
[ ".icon-info", "29" ],
[ ".icon-verified", "2a" ],
[ ".icon-translator", "2b" ],
[ ".icon-blocked", "2c" ],
[ ".icon-constrain", "2d" ],
[ ".icon-play-video", "2e" ],
[ ".icon-empty", "2f" ],
[ ".icon-clear-input", "30" ],
[ ".icon-compose", "31" ],
[ ".icon-mark-read", "32" ],
[ ".icon-arrow-r-double", "33" ],
[ ".icon-arrow-l-double", "34" ],
[ ".icon-follow", "35" ],
[ ".icon-image", "36" ],
[ ".icon-popout", "37" ],
[ ".icon-move", "39" ],
[ ".icon-compose-grid", "3a" ],
[ ".icon-compose-minigrid", "3b" ],
[ ".icon-compose-list", "3c" ],
[ ".icon-edit", "40" ],
[ ".icon-clear-timeline", "41" ],
[ ".icon-sliders", "42" ],
[ ".icon-custom-timeline", "43" ],
[ ".icon-compose-dm", "44" ],
[ ".icon-bg-dot", "45" ],
[ ".icon-user-team-mgr", "46" ],
[ ".icon-user-switch", "47" ],
[ ".icon-conversation", "48" ],
[ ".icon-dataminr", "49" ],
[ ".icon-link", "4a", ],
[ ".icon-flash", "50" ],
[ ".icon-pointer-u", "51" ],
[ ".icon-analytics", "54" ],
[ ".icon-heart", "55" ],
[ ".icon-calendar", "56" ],
[ ".icon-attachment", "57" ],
[ ".icon-play", "58" ],
[ ".icon-bookmark", "59" ],
[ ".icon-play-badge", "60" ],
[ ".icon-gif-badge", "61" ],
[ ".icon-poll", "62" ],
[ ".icon-heart-filled", "55" ],
[ ".icon-retweet-filled", "08" ],
[ ".icon-list-filled", "14" ],
[ ".icon-user-filled", "35" ],
];
this.icons = document.createElement("style");
this.icons.innerHTML = `
@font-face {
font-family: 'tweetdeckold';
font-family: '_of';
src: url("https://ton.twimg.com/tweetdeck-web/web/assets/fonts/tweetdeck-regular-webfont.5f4ea87976.woff") format("woff");
font-weight: normal;
font-style: normal;
}
.icon-twitter-bird:before{content:"\\f000";font-family:tweetdeckold}
.icon-mention:before{content:"\\f001";font-family:tweetdeckold}
.icon-following:before{content:"\\f002";font-family:tweetdeckold}
.icon-message:before{content:"\\f003";font-family:tweetdeckold}
.icon-home:before{content:"\\f004";font-family:tweetdeckold}
.icon-hashtag:before{content:"\\f005";font-family:tweetdeckold}
.icon-reply:before{content:"\\f006";font-family:tweetdeckold}
.icon-favorite:before{content:"\\f055";font-family:tweetdeckold}
.icon-retweet:before{content:"\\f008";font-family:tweetdeckold}
.icon-drafts:before{content:"\\f009";font-family:tweetdeckold}
.icon-search:before{content:"\\f00a";font-family:tweetdeckold}
.icon-trash:before{content:"\\f00c";font-family:tweetdeckold}
.icon-close:before{content:"\\f00d";font-family:tweetdeckold}
.icon-arrow-r:before,.Icon--caretRight:before{content:"\\f00e";font-family:tweetdeckold}
.icon-arrow-l:before,.Icon--caretLeft:before{content:"\\f00f";font-family:tweetdeckold}
.icon-protected:before{content:"\\f013";font-family:tweetdeckold}
.icon-list:before{content:"\\f014";font-family:tweetdeckold}
.icon-camera:before{content:"\\f015";font-family:tweetdeckold}
.icon-more:before{content:"\\f016";font-family:tweetdeckold}
.icon-settings:before{content:"\\f018";font-family:tweetdeckold}
.icon-notifications:before{content:"\\f019";font-family:tweetdeckold}
.icon-user-dd:before{content:"\\f01a";font-family:tweetdeckold}
.icon-activity:before{content:"\\f01c";font-family:tweetdeckold}
.icon-trending:before{content:"\\f01d";font-family:tweetdeckold}
.icon-minus:before{content:"\\f01e";font-family:tweetdeckold}
.icon-plus:before{content:"\\f01f";font-family:tweetdeckold}
.icon-geo:before{content:"\\f020";font-family:tweetdeckold}
.icon-check:before{content:"\\f021";font-family:tweetdeckold}
.icon-schedule:before{content:"\\f022";font-family:tweetdeckold}
.icon-dot:before{content:"\\f023";font-family:tweetdeckold}
.icon-user:before{content:"\\f024";font-family:tweetdeckold}
.icon-content:before{content:"\\f025";font-family:tweetdeckold}
.icon-arrow-d:before,.Icon--caretDown:before{content:"\\f026";font-family:tweetdeckold}
.icon-arrow-u:before{content:"\\f027";font-family:tweetdeckold}
.icon-share:before{content:"\\f028";font-family:tweetdeckold}
.icon-info:before{content:"\\f029";font-family:tweetdeckold}
.icon-verified:before{content:"\\f02a";font-family:tweetdeckold}
.icon-translator:before{content:"\\f02b";font-family:tweetdeckold}
.icon-blocked:before{content:"\\f02c";font-family:tweetdeckold}
.icon-constrain:before{content:"\\f02d";font-family:tweetdeckold}
.icon-play-video:before{content:"\\f02e";font-family:tweetdeckold}
.icon-empty:before{content:"\\f02f";font-family:tweetdeckold}
.icon-clear-input:before{content:"\\f030";font-family:tweetdeckold}
.icon-compose:before{content:"\\f031";font-family:tweetdeckold}
.icon-mark-read:before{content:"\\f032";font-family:tweetdeckold}
.icon-arrow-r-double:before{content:"\\f033";font-family:tweetdeckold}
.icon-arrow-l-double:before{content:"\\f034";font-family:tweetdeckold}
.icon-follow:before{content:"\\f035";font-family:tweetdeckold}
.icon-image:before{content:"\\f036";font-family:tweetdeckold}
.icon-popout:before{content:"\\f037";font-family:tweetdeckold}
.icon-move:before{content:"\\f039";font-family:tweetdeckold}
.icon-compose-grid:before{content:"\\f03a";font-family:tweetdeckold}
.icon-compose-minigrid:before{content:"\\f03b";font-family:tweetdeckold}
.icon-compose-list:before{content:"\\f03c";font-family:tweetdeckold}
.icon-edit:before{content:"\\f040";font-family:tweetdeckold}
.icon-clear-timeline:before{content:"\\f041";font-family:tweetdeckold}
.icon-sliders:before{content:"\\f042";font-family:tweetdeckold}
.icon-custom-timeline:before{content:"\\f043";font-family:tweetdeckold}
.icon-compose-dm:before{content:"\\f044";font-family:tweetdeckold}
.icon-bg-dot:before{content:"\\f045";font-family:tweetdeckold}
.icon-user-team-mgr:before{content:"\\f046";font-family:tweetdeckold}
.icon-user-switch:before{content:"\\f047";font-family:tweetdeckold}
.icon-conversation:before{content:"\\f048";font-family:tweetdeckold}
.icon-dataminr:before{content:"\\f049";font-family:tweetdeckold}
.icon-link:before{content:"\\f04a";font-family:tweetdeckold}
.icon-flash:before{content:"\\f050";font-family:tweetdeckold}
.icon-pointer-u:before{content:"\\f051";font-family:tweetdeckold}
.icon-analytics:before{content:"\\f054";font-family:tweetdeckold}
.icon-heart:before{content:"\\f055";font-family:tweetdeckold}
.icon-calendar:before{content:"\\f056";font-family:tweetdeckold}
.icon-attachment:before{content:"\\f057";font-family:tweetdeckold}
.icon-play:before{content:"\\f058";font-family:tweetdeckold}
.icon-bookmark:before{content:"\\f059";font-family:tweetdeckold}
.icon-play-badge:before{content:"\\f060";font-family:tweetdeckold}
.icon-gif-badge:before{content:"\\f061";font-family:tweetdeckold}
.icon-poll:before{content:"\\f062";font-family:tweetdeckold}
.icon-heart-filled:before{content:"\\f055";font-family:tweetdeckold}
.icon-retweet-filled:before{content:"\\f008";font-family:tweetdeckold}
.icon-list-filled:before{content:"\\f014";font-family:tweetdeckold}
.icon-user-filled:before{content:"\\f035";font-family:tweetdeckold}
${iconData.map(entry => `#tduck ${entry[0]}:before{content:\"\\f0${entry[1]}\";font-family:_of!important}`).join("")}
.drawer .btn .icon, .app-header .btn .icon { line-height: 1em !important }
.column-header .column-type-icon { bottom: 26px !important }
@@ -507,21 +544,25 @@ enabled(){
$TDP.injectIntoNotificationsBefore(this.$token, "css", "</head>", `
<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 }
${this.config.increaseQuoteTextSize ? `
.quoted-tweet { font-size: 1em !important }
` : ``}
${this.config.revertIcons ? `
@font-face { font-family: 'tweetdeckold'; src: url(\"https://ton.twimg.com/tweetdeck-web/web/assets/fonts/tweetdeck-regular-webfont.5f4ea87976.woff\") format(\"woff\"); font-weight: normal; font-style: normal }
.icon-reply:before{content:"\\f006";font-family:tweetdeckold}
.icon-heart-filled:before{content:"\\f055";font-family:tweetdeckold}
.icon-retweet-filled:before{content:"\\f008";font-family:tweetdeckold}
.icon-list-filled:before{content:"\\f014";font-family:tweetdeckold}
.icon-user-filled:before{content:"\\f035";font-family:tweetdeckold}
.icon-user-dd:before{content:"\\f01a";font-family:tweetdeckold}
@font-face { font-family: '_of'; src: url(\"https://ton.twimg.com/tweetdeck-web/web/assets/fonts/tweetdeck-regular-webfont.5f4ea87976.woff\") format(\"woff\"); font-weight: normal; font-style: normal }
#tduck .icon-reply:before{content:"\\f006";font-family:_of!important}
#tduck .icon-heart-filled:before{content:"\\f055";font-family:_of!important}
#tduck .icon-retweet-filled:before{content:"\\f008";font-family:_of!important}
#tduck .icon-list-filled:before{content:"\\f014";font-family:_of!important}
#tduck .icon-user-filled:before{content:"\\f035";font-family:_of!important}
#tduck .icon-user-dd:before{content:"\\f01a";font-family:_of!important}
` : ``}
${notificationScrollbarColor ? `
.scroll-styled-v::-webkit-scrollbar-thumb, .scroll-styled-h::-webkit-scrollbar-thumb { background-color: #${notificationScrollbarColor} }
.scroll-styled-v::-webkit-scrollbar-thumb, .scroll-styled-h::-webkit-scrollbar-thumb { background-color: #${notificationScrollbarColor} !important }
` : ``}
</style>`);
};

View File

@@ -1,4 +1,4 @@
<div class="td-modal-panel js-modal-panel mdl s-tall-fixed is-inverted-dark">
<div id="edit-design-panel" class="js-modal-panel mdl s-tall-fixed is-inverted-dark">
<header class="js-mdl-header mdl-header js-drag-handle">
<h3 class="mdl-header-title js-header-title">TweetDuck - Layout &amp; Design</h3>
<a href="#" class="mdl-dismiss js-dismiss link-normal-dark">
@@ -6,8 +6,8 @@
</a>
</header>
<div class="mdl-inner">
<div class="td-modal-content mdl-content js-mdl-content horizontal-flow-container">
<div class="td-modal-inner-cols">
<div id="edit-design-panel-content" class="mdl-content js-mdl-content horizontal-flow-container">
<div id="edit-design-panel-inner-cols">
<div class="l-column mdl-column">
<!-- THEME -->
@@ -36,7 +36,7 @@
<option value="310px">Medium (310px)</option>
<option value="350px">Wide (350px)</option>
<option value="400px">Extreme (400px)</option>
<option value="custom">Custom</option>
<option value="custom-px">Custom</option>
</optgroup>
<option disabled></option>
<optgroup label="Dynamic width">
@@ -59,17 +59,15 @@
<option value="14px">Medium (14px)</option>
<option value="15px">Large (15px)</option>
<option value="16px">Largest (16px)</option>
<option value="custom">Custom</option>
<option value="custom-px">Custom</option>
</select>
<!-- ADVANCED -->
<label class="txt-uppercase touch-larger-label">
<b>Advanced</b>
<label class="checkbox">
<input data-td-key="increaseQuoteTextSize" class="js-theme-checkbox touch-larger-label" type="checkbox">
Increase quoted tweet font size
</label>
<label class="checkbox">
<input data-td-key="optimizeAnimations" class="js-theme-checkbox touch-larger-label" type="checkbox">
Use more memory for smoother animations
<input data-td-key="smallComposeTextSize" class="js-theme-checkbox touch-larger-label" type="checkbox">
Small tweet input font size
</label>
</div>
@@ -94,17 +92,27 @@
<label class="txt-uppercase touch-larger-label">
<b>Design</b>
</label>
<label class="checkbox">
<input data-td-key="themeColorTweaks" class="js-theme-checkbox touch-larger-label" type="checkbox">
Theme color tweaks
</label>
<label class="checkbox">
<input data-td-key="revertIcons" class="js-theme-checkbox touch-larger-label" type="checkbox">
Revert icon design
</label>
<label class="checkbox">
<input data-td-key="smallComposeTextSize" class="js-theme-checkbox touch-larger-label" type="checkbox">
Small compose tweet font size
<input data-td-key="showCharacterCount" class="js-theme-checkbox touch-larger-label" type="checkbox">
Always show character count
</label>
<label class="checkbox">
<input data-td-key="themeColorTweaks" class="js-theme-checkbox touch-larger-label" type="checkbox">
Theme color tweaks
</label>
<!-- ADVANCED -->
<label class="txt-uppercase touch-larger-label">
<b>Advanced</b>
</label>
<label class="checkbox">
<input data-td-key="optimizeAnimations" class="js-theme-checkbox touch-larger-label" type="checkbox">
Use more memory for smoother animations
</label>
</div>
@@ -151,54 +159,55 @@
/* Containers */
.td-modal-panel {
#edit-design-panel {
width: 693px;
height: 380px;
}
.td-modal-inner-cols {
#edit-design-panel-inner-cols {
padding: 0 6px;
}
.td-modal-inner-cols .l-column {
#edit-design-panel-inner-cols .l-column {
padding: 15px 9px;
box-sizing: border-box;
width: 225px;
font-size: 0; /* fix custom font size breaking the modal layout */
}
.td-modal-inner-cols .l-column:nth-child(2) {
width: 250px;
}
.td-modal-inner-full {
padding: 15px;
}
.td-modal-inner-full .txt-center {
margin-bottom: 10px;
#edit-design-panel-inner-cols .l-column:nth-child(3) {
width: 200px;
}
/* Elements */
.td-modal-content label {
#edit-design-panel-content label.txt-uppercase {
margin-top: 18px;
}
.td-modal-content label:first-child {
#edit-design-panel-content label.txt-uppercase:first-child {
margin-top: 0;
}
.td-modal-content label.radio {
#edit-design-panel-content label.radio {
display: inline-block;
margin: 0 16px 5px 4px;
cursor: pointer;
}
.td-modal-content label.checkbox {
#edit-design-panel-content label.checkbox {
margin: 0 0 5px 4px;
cursor: pointer;
}
#edit-design-panel-content select + label.checkbox {
margin-top: 9px;
}
#edit-design-panel-content input.js-theme-checkbox, #edit-design-panel-content input.js-theme-radio {
margin-top: 1px;
}
/* Avatar shape */
.td-avatar-shape-container {

View File

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

View File

@@ -303,23 +303,26 @@ enabled(){
let firstColon = val.lastIndexOf(':', ele[0].selectionStart);
return if firstColon === -1;
let search = val.substring(firstColon+1, ele[0].selectionStart);
return if !/^[a-z_]+$/i.test(search);
let search = val.substring(firstColon+1, ele[0].selectionStart).toLowerCase();
return if !/^[a-z_]+$/.test(search);
let keywords = search.split("_").filter(kw => kw.length > 0).map(kw => kw.toLowerCase());
return if keywords.length === 0;
let foundName = me.emojiNames.filter(name => keywords.every(kw => name.includes(kw)));
let foundNames = me.emojiNames.filter(name => keywords.every(kw => name.includes(kw)));
if (foundName.length === 0){
if (foundNames.length === 0){
return;
}
else if (foundNames.length > 1 && foundNames.includes(search)){
foundNames = [ search ];
}
lastEmojiKeyword = `:${search}:`;
lastEmojiPosition = lastEmojiLength = 0;
if (foundName.length === 1){
let foundIndex = me.emojiNames.indexOf(foundName[0]);
if (foundNames.length === 1){
let foundIndex = me.emojiNames.indexOf(foundNames[0]);
let foundEmoji;
for(let array of [ me.emojiData1, me.emojiData2[me.selectedSkinTone], me.emojiData3 ]){
@@ -346,7 +349,7 @@ enabled(){
lastEmojiLength = foundEmoji.length;
}
}
else if (foundName.length > 1){
else if (foundNames.length > 1 && $(".js-app-content").is(".is-open")){
e.preventDefault();
ele.val(val.substring(0, firstColon)+val.substring(ele[0].selectionStart));
ele[0].selectionEnd = ele[0].selectionStart = firstColon;
@@ -396,6 +399,7 @@ enabled(){
ready(){
this.composeDrawer = $("[data-drawer='compose']");
this.composeInput = $(".js-compose-text", ".js-docked-compose");
this.composeSelector = ".js-compose-text,.js-reply-tweetbox";
this.composePanelScroller = $(".js-compose-scroller", ".js-docked-compose").first().children().first();
this.composePanelScroller.on("scroll", this.composerScrollEvent);
@@ -404,11 +408,12 @@ ready(){
$(document).on("click", this.documentClickEvent);
$(document).on("keydown", this.documentKeyEvent);
$(document).on("uiComposeImageAdded", this.uploadFilesEvent);
this.composeInput.on("keydown", this.composeInputKeyDownEvent);
this.composeInput.on("keypress", this.composeInputKeyPressEvent);
this.composeInput.on("focus", this.composeInputFocusEvent);
this.composeDrawer.on("uiComposeTweetSending", this.composerSendingEvent);
$(document).on("keydown", this.composeSelector, this.composeInputKeyDownEvent);
$(document).on("keypress", this.composeSelector, this.composeInputKeyPressEvent);
$(document).on("focus", this.composeSelector, this.composeInputFocusEvent);
// HTML generation
var convUnicode = function(codePt){
@@ -530,10 +535,11 @@ disabled(){
$(document).off("click", this.documentClickEvent);
$(document).off("keydown", this.documentKeyEvent);
$(document).off("uiComposeImageAdded", this.uploadFilesEvent);
this.composeInput.off("keydown", this.composeInputKeyDownEvent);
this.composeInput.off("keypress", this.composeInputKeyPressEvent);
this.composeInput.off("focus", this.composeInputFocusEvent);
this.composeDrawer.off("uiComposeTweetSending", this.composerSendingEvent);
$(document).off("keydown", this.composeSelector, this.composeInputKeyDownEvent);
$(document).off("keypress", this.composeSelector, this.composeInputKeyPressEvent);
$(document).off("focus", this.composeSelector, this.composeInputFocusEvent);
TD.mustaches["compose/docked_compose.mustache"] = this.prevComposeMustache;
}

View File

@@ -8,7 +8,7 @@ Custom reply account
chylex
[version]
1.2.2
1.2.4
[website]
https://tweetduck.chylex.com
@@ -20,4 +20,4 @@ configuration.js
configuration.default.js
[requires]
1.3.3
1.10.3

View File

@@ -6,7 +6,7 @@ enabled(){
this.lastSelectedAccount = null;
this.uiComposeTweetEvent = (e, data) => {
return if data.type !== "reply" || data.popFromInline || !("element" in data);
return if !(data.type === "reply" || (data.type === "tweet" && "quotedTweet" in data)) || data.popFromInline || !("element" in data);
var query;
@@ -17,7 +17,7 @@ enabled(){
return;
}
var section = data.element.closest("section.column");
var section = data.element.closest("section.js-column");
var column = TD.controller.columnManager.get(section.attr("data-column"));
var header = $(".column-title", section);
@@ -35,7 +35,7 @@ enabled(){
}
try{
query = configuration.customSelector(column.getColumnType(), columnTitle, columnAccount, column);
query = configuration.customSelector(column.getColumnType(), columnTitle, columnAccount, column, section.hasClass("column-temp"));
}catch(e){
$TD.alert("warning", "Plugin reply-account has invalid configuration: customSelector threw an error: "+e.message);
return;
@@ -117,19 +117,9 @@ enabled(){
}
ready(){
var events = $._data(document, "events");
for(var event of [ "uiInlineComposeTweet", "uiDockedComposeTweet" ]){
for(let event of [ "uiInlineComposeTweet", "uiDockedComposeTweet" ]){
$(document).on(event, this.uiComposeTweetEvent);
var handlers = events[event];
var newHandler = handlers[handlers.length-1];
for(var index = handlers.length-1; index > 0; index--){
handlers[index] = handlers[index-1];
}
handlers[0] = newHandler;
window.TDPF_prioritizeNewestEvent(document, event);
}
$(document).on("click", ".js-account-list .js-account-item", this.onSelectedAccountChanged);

View File

@@ -21,11 +21,14 @@
* This assumes a basic knowledge of JavaScript and jQuery.
*
* 1. Set value of 'useAdvancedSelector' to true
* 2. Uncomment the 'customSelector' function, and replace the example code with your desired behavior
* 2. Replace the example code in 'customSelector' function with your desired behavior
*
* The 'customSelector' function should return a string in one of the formats supported by 'defaultAccount'.
* If it returns anything else (for example, false or undefined), it falls back to 'defaultAccount' behavior.
*
* When writing a custom function, you can install Dev Tools to access the browser console:
* https://tweetduck.chylex.com/guide/#dev-tools
*
*
* The 'type' parameter is TweetDeck column type. Here is the full list of column types, note that some are
* unused and have misleading names (for example, Home columns are 'col_timeline' instead of 'col_home'):
@@ -46,28 +49,33 @@
* contain other text (for example, the Scheduled column contains the string 'All accounts').
*
*
* The 'column' parameter is a TweetDeck column object. If you want to see all properties of the object,
* run the following code in your browser console, which will return an array containing all of your
* current column objects in order:
* The 'column' parameter is a TweetDeck column object. If you want to see the object structure,
* run the following code in the console for an array containing all of your column objects:
* TD.controller.columnManager.getAllOrdered()
*
*
* The 'isTemporary' parameter is true if the column is not attached to the main column list,
* for example when clicking on a profile and viewing their tweets in a modal dialog.
*
*/
useAdvancedSelector: false,
/*customSelector: function(type, title, account, column){
customSelector: function(type, title, account, column, isTemporary){
console.info(arguments); // Prints all arguments into the console
if (type === "col_search" && title === "TweetDuck"){
// This is a search column that looks for 'TweetDuck' in the tweets,
// search columns are normally linked to the preferred account
// so this forces the @TryTweetDuck account to be used instead.
// so this forces the @TryTweetDuck account to be used instead
return "@TryTweetDuck";
}
else if (type === "col_timeline" && account === "@chylexcz"){
// This is a Home column of my test account @chylexcz,
// but I want to reply to tweets from my official account.
// but I want to reply to tweets from my official account
return "@chylexmc";
}
// otherwise returns 'undefined' which falls back to 'defaultAccount' behavior
}*/
}
}

View File

@@ -8,7 +8,7 @@ Templates
chylex
[version]
1.0.2
1.0.3
[website]
https://tweetduck.chylex.com

View File

@@ -50,25 +50,25 @@ enabled(){
this.css = window.TDPF_createCustomStyle(this);
this.css.insert(".manage-templates-btn.active { color: #fff; box-shadow: 0 0 2px 3px #50a5e6; outline: 0; }");
this.css.insert(".templates-modal-wrap { width: 100%; height: 100%; padding: 49px; position: absolute; z-index: 999; box-sizing: border-box; background-color: rgba(0, 0, 0, 0.5); }");
this.css.insert(".templates-modal { width: 100%; height: 100%; background-color: #fff; display: flex; }");
this.css.insert(".templates-modal > div { display: flex; flex-direction: column; }");
this.css.insert("#templates-modal-wrap { width: 100%; height: 100%; padding: 49px; position: absolute; z-index: 999; box-sizing: border-box; background-color: rgba(0, 0, 0, 0.5); }");
this.css.insert("#templates-modal { width: 100%; height: 100%; background-color: #fff; display: flex; }");
this.css.insert("#templates-modal > div { display: flex; flex-direction: column; }");
this.css.insert(".templates-modal-bottom { flex: 0 0 auto; padding: 16px; }");
this.css.insert(".template-list .templates-modal-bottom { display: flex; justify-content: space-between; }");
this.css.insert(".template-editor .templates-modal-bottom { text-align: right; }");
this.css.insert("#template-list .templates-modal-bottom { display: flex; justify-content: space-between; }");
this.css.insert("#template-editor .templates-modal-bottom { text-align: right; }");
this.css.insert(".template-list { height: 100%; flex: 1 1 auto; }");
this.css.insert(".template-list ul { list-style-type: none; font-size: 24px; color: #222; flex: 1 1 auto; padding: 12px; overflow-y: auto; }");
this.css.insert(".template-list li { display: block; width: 100%; padding: 4px 8px; box-sizing: border-box; }");
this.css.insert(".template-list li[data-template] { cursor: pointer; }");
this.css.insert(".template-list li[data-template]:hover { background-color: #d8d8d8; }");
this.css.insert(".template-list li span { white-space: nowrap; }");
this.css.insert(".template-list li .icon { opacity: 0.6; margin-left: 4px; padding: 3px; }");
this.css.insert(".template-list li .icon:hover { opacity: 1; }");
this.css.insert(".template-list li .template-actions { float: right; }");
this.css.insert("#template-list { height: 100%; flex: 1 1 auto; }");
this.css.insert("#template-list ul { list-style-type: none; font-size: 24px; color: #222; flex: 1 1 auto; padding: 12px; overflow-y: auto; }");
this.css.insert("#template-list li { display: block; width: 100%; padding: 4px 8px; box-sizing: border-box; }");
this.css.insert("#template-list li[data-template] { cursor: pointer; }");
this.css.insert("#template-list li[data-template]:hover { background-color: #d8d8d8; }");
this.css.insert("#template-list li span { white-space: nowrap; }");
this.css.insert("#template-list li .icon { opacity: 0.6; margin-left: 4px; padding: 3px; }");
this.css.insert("#template-list li .icon:hover { opacity: 1; }");
this.css.insert("#template-list li .template-actions { float: right; }");
this.css.insert(".template-editor { height: 100%; flex: 0 0 auto; width: 25vw; min-width: 150px; max-width: 400px; background-color: #485865; }");
this.css.insert("#template-editor { height: 100%; flex: 0 0 auto; width: 25vw; min-width: 150px; max-width: 400px; background-color: #485865; }");
this.css.insert(".template-editor-form { flex: 1 1 auto; padding: 12px 16px; font-size: 14px; overflow-y: auto; }");
this.css.insert(".template-editor-form .compose-text-title { margin: 24px 0 9px; }");
this.css.insert(".template-editor-form .compose-text-title:first-child { margin-top: 0; }");
@@ -261,9 +261,9 @@ enabled(){
$(".manage-templates-btn").addClass("active");
let html = `
<div class="templates-modal-wrap">
<div class="templates-modal">
<div class="template-list">
<div id="templates-modal-wrap">
<div id="templates-modal">
<div id="template-list">
<ul></ul>
<div class="templates-modal-bottom">
@@ -272,7 +272,7 @@ enabled(){
</div>
</div>
<div class="template-editor invisible">
<div id="template-editor" class="invisible">
<div class="template-editor-form">
<div class="compose-text-title">Template Name</div>
<input name="template-name" type="text">
@@ -320,7 +320,7 @@ enabled(){
$(".js-app-content").prepend(html);
let ele = $(".templates-modal-wrap").first();
let ele = $("#templates-modal-wrap").first();
ele.on("click", "li[data-template]", function(e){
let template = me.config.templates[$(this).attr("data-template")];
@@ -332,7 +332,7 @@ enabled(){
switch($(this).attr("data-action")){
case "edit-template":
let editor = $(".template-editor");
let editor = $("#template-editor");
if (editor.hasClass("invisible")){
toggleEditor();
@@ -372,7 +372,7 @@ enabled(){
break;
case "editor-confirm":
let editor = $(".template-editor");
let editor = $("#template-editor");
if (me.editingTemplate !== null){
delete me.config.templates[me.editingTemplate];
@@ -408,15 +408,15 @@ enabled(){
};
var hideTemplateModal = function(){
$(".templates-modal-wrap").remove();
$("#templates-modal-wrap").remove();
$(".manage-templates-btn").removeClass("active");
};
var toggleEditor = function(){
let editor = $(".template-editor");
let editor = $("#template-editor");
$("[name]", editor).val("");
if ($("button[data-action='new-template']", ".template-list").add(editor).toggleClass("invisible").hasClass("invisible")){
if ($("button[data-action='new-template']", "#template-list").add(editor).toggleClass("invisible").hasClass("invisible")){
me.editingTemplate = null;
}
};
@@ -435,7 +435,7 @@ enabled(){
eles.push("<li>No templates available</li>");
}
$(".template-list").children("ul").html(eles.join(""));
$("#template-list").children("ul").html(eles.join(""));
if (save){
this.saveConfig();
@@ -445,7 +445,7 @@ enabled(){
// event handlers
this.manageTemplatesButtonClickEvent = function(e){
if ($(".templates-modal-wrap").length){
if ($("#templates-modal-wrap").length){
hideTemplateModal();
}
else{
@@ -471,7 +471,7 @@ disabled(){
this.css.remove();
$(".manage-templates-btn").remove();
$(".templates-modal-wrap").remove();
$("#templates-modal-wrap").remove();
$(document).off("uiDrawerActive", this.drawerToggleEvent);

View File

@@ -1,8 +1,4 @@
enabled(){
this.reloadColumns = () => {
Object.values(TD.controller.columnManager.getAll()).forEach(column => column.reloadTweets());
};
// styles
this.css = window.TDPF_createCustomStyle(this);
@@ -10,17 +6,17 @@ enabled(){
// utility functions
var hasPoll = function(tweet){
const hasPoll = function(tweet){
return tweet.hasPoll && tweet.hasPoll();
};
var renderTweetPoll = function(tweet){
const renderTweetPoll = function(tweet){
return `<div class='td-timeline-poll'>${TD.ui.template.render("status/poll", $.extend({}, tweet, {
chirp: tweet
}))}</div>`;
};
var renderPollHook = function(tweet, html){
const renderPollHook = function(tweet, html){
let ele = null;
if (hasPoll(tweet)){
@@ -67,7 +63,7 @@ enabled(){
};
this.prevRenderFuncs = funcs;
this.reloadColumns();
window.TDPF_reloadColumns();
}
disabled(){
@@ -76,5 +72,5 @@ disabled(){
TD.components.TweetDetailView.prototype._renderChirp = this.prevRenderFuncs.TweetDetailView;
this.css.remove();
this.reloadColumns();
window.TDPF_reloadColumns();
}

View File

@@ -3,6 +3,18 @@ $ErrorActionPreference = "Stop"
Set-Location $dir
function Check-Carriage-Return{
Param([Parameter(Mandatory = $True, Position = 1)] $fname)
$file = @(Get-ChildItem -Include $fname -Recurse)[0]
if ((Get-Content -Path $file.FullName -Raw).Contains("`r")){
Throw "$fname cannot contain carriage return"
}
Write-Host "Verified" $file.FullName.Substring($dir.Length)
}
function Rewrite-File{
[CmdletBinding()]
Param([Parameter(Mandatory = $True, ValueFromPipeline = $True)][array] $lines, [Parameter(Mandatory = $True, Position = 1)] $file)
@@ -11,7 +23,10 @@ function Rewrite-File{
Write-Host "Processed" $file.FullName.Substring($dir.Length)
}
ForEach($file in Get-ChildItem -Include *.js -Exclude configuration.default.js -Recurse){
try{
Check-Carriage-Return("emoji-ordering.txt")
ForEach($file in Get-ChildItem -Filter *.js -Exclude configuration.default.js -Recurse){
$lines = Get-Content -Path $file.FullName
$lines = $lines | % { $_.TrimStart() }
$lines = $lines -Replace '^(.*?)((?<=^|[;{}()])\s?//(?:\s.*|$))?$', '$1'
@@ -19,7 +34,7 @@ ForEach($file in Get-ChildItem -Include *.js -Exclude configuration.default.js -
,$lines | Rewrite-File $file
}
ForEach($file in Get-ChildItem -Include *.css -Recurse){
ForEach($file in Get-ChildItem -Filter *.css -Recurse){
$lines = Get-Content -Path $file.FullName
$lines = $lines -Replace '\s*/\*.*?\*/', ''
$lines = $lines -Replace '^\s+(.+):\s?(.+?)(?:\s?(!important))?;$', '$1:$2$3;'
@@ -27,7 +42,12 @@ ForEach($file in Get-ChildItem -Include *.css -Recurse){
@(($lines | Where { $_ -ne '' }) -Join ' ') | Rewrite-File $file
}
ForEach($file in Get-ChildItem -Include *.html -Recurse){
ForEach($file in Get-ChildItem -Filter *.html -Recurse){
$lines = Get-Content -Path $file.FullName
,($lines | % { $_.TrimStart() }) | Rewrite-File $file
}
}catch{
Write-Host "Encountered an error while running PostBuild.ps1 on line" $_.InvocationInfo.ScriptLineNumber
Write-Host $_
Exit 1
}

View File

@@ -14,10 +14,15 @@
//
var onAppReady = [];
//
// Variable: DOM HTML element.
//
const doc = document.documentElement;
//
// Variable: DOM object containing the main app element.
//
var app = $(document.body).children(".js-app");
const app = $(document.body).children(".js-app");
//
// Constant: Column types mapped to their titles.
@@ -45,7 +50,7 @@
//
// Function: Prepends code at the beginning of a function. If the prepended function returns true, execution of the original function is cancelled.
//
var prependToFunction = function(func, extension){
const prependToFunction = function(func, extension){
return function(){
return extension.apply(this, arguments) === true ? undefined : func.apply(this, arguments);
};
@@ -54,7 +59,7 @@
//
// Function: Appends code at the end of a function.
//
var appendToFunction = function(func, extension){
const appendToFunction = function(func, extension){
return function(){
let res = func.apply(this, arguments);
extension.apply(this, arguments);
@@ -65,7 +70,7 @@
//
// Function: Returns true if an object has a specified property, otherwise returns false without throwing an error.
//
var ensurePropertyExists = function(obj, ...chain){
const ensurePropertyExists = function(obj, ...chain){
for(let index = 0; index < chain.length; index++){
if (!obj.hasOwnProperty(chain[index])){
$TD.crashDebug("Missing property "+chain[index]+" in chain [obj]."+chain.join("."));
@@ -81,7 +86,7 @@
//
// Function: Retrieves a property of an element with a specified class.
//
var getClassStyleProperty = function(cls, property){
const getClassStyleProperty = function(cls, property){
let column = document.createElement("div");
column.classList.add(cls);
column.style.display = "none";
@@ -96,7 +101,7 @@
//
// Function: Event callback for a new tweet.
//
var onNewTweet = (function(){
const onNewTweet = (function(){
let recentMessages = new Set();
let recentTweets = new Set();
let recentTweetTimer = null;
@@ -123,7 +128,7 @@
};
let fixMedia = (html, media) => {
return html.find(".js-media a[data-media-entity-id='"+media.mediaId+"']").css("background-image", 'url("'+media.thumb()+'")').removeClass("is-zoomable");
return html.find(".js-media a[data-media-entity-id='"+media.mediaId+"']").css("background-image", 'url("'+media.small()+'")').removeClass("is-zoomable");
};
return function(column, tweet){
@@ -177,6 +182,12 @@
this.outerHTML = this.innerHTML;
});
html.find("p.link-complex-target").filter(function(){
return $(this).text() === "Show this thread";
}).first().each(function(){
this.id = "tduck-show-thread";
});
let type = tweet.getChirpType();
if (type === "follow"){
@@ -214,7 +225,7 @@
// Function: Shows tweet detail, used in notification context menu.
//
(function(){
var showTweetDetailInternal = function(column, chirp){
const showTweetDetailInternal = function(column, chirp){
TD.ui.updates.showDetailView(column, chirp, column.findChirp(chirp) || chirp);
TD.controller.columnManager.showColumn(column.model.privateState.key);
@@ -258,49 +269,66 @@
})();
//
// Function: Retrieves the tags to be put into <head> for notification HTML code.
// Block: Hook into settings object to detect when the settings change, and update html attributes and notification layout.
//
var getNotificationHeadContents = function(){
let tags = [];
(function(){
const refreshSettings = function(){
let fontSizeName = TD.settings.getFontSize();
let themeName = TD.settings.getTheme();
$(document.head).children("link[rel='stylesheet']:not([title]),link[title='"+TD.settings.getTheme()+"'],meta[charset],meta[http-equiv]").each(function(){
let tags = [
"<html "+Array.prototype.map.call(document.documentElement.attributes, ele => `${ele.name}="${ele.value}"`).join(" ")+"><head>"
];
$(document.head).children("link[href*='css/font.']:first,link[href*='css/app-"+themeName+".']:first,meta[charset],meta[http-equiv]").each(function(){
tags.push($(this)[0].outerHTML);
});
tags.push("<style type='text/css'>");
tags.push("body { background: "+getClassStyleProperty("column", "background-color")+" }"); // set background color
tags.push("a[data-full-url] { word-break: break-all }"); // break long urls
tags.push(".txt-base-smallest .badge-verified:before { width: 13px !important; height: 13px !important; background-position: -223px -98px !important }"); // fix cut off badge icon
tags.push("body { background: "+getClassStyleProperty("column", "background-color")+" !important }"); // set background color
tags.push("a[data-full-url] { word-break: break-all !important }"); // break long urls
tags.push(".media-item, .media-preview { border-radius: 1px !important }"); // square-ify media
tags.push(".quoted-tweet { border-radius: 0 !important }"); // square-ify quoted tweets
tags.push(".activity-header { align-items: center !important; margin-bottom: 4px }"); // tweak alignment of avatar and text in notifications
tags.push(".activity-header .tweet-timestamp { line-height: unset }"); // fix timestamp position in notifications
tags.push(".tweet-context .nbfc { text-overflow: ellipsis !important; white-space: nowrap !important }"); // force ellipsis on long usernames
tags.push(".activity-header { align-items: center !important; margin-bottom: 4px !important }"); // tweak alignment of avatar and text in notifications
tags.push(".activity-header .tweet-timestamp { line-height: unset !important }"); // fix timestamp position in notifications
tags.push("#tduck-show-thread { display: inline-block !important; cursor: pointer }");
if (fontSizeName === "smallest"){
tags.push(".badge-verified:before { width: 13px !important; height: 13px !important; background-position: -223px -98px !important }"); // fix cut off badge icon
}
tags.push("</style>");
return tags.join("");
doc.setAttribute("data-td-font", fontSizeName);
doc.setAttribute("data-td-theme", themeName);
$TD.loadNotificationLayout(fontSizeName, tags.join(""));
};
//
// Block: Hook into settings object to detect when the settings change.
//
TD.settings.setFontSize = appendToFunction(TD.settings.setFontSize, function(name){
$TD.loadFontSizeClass(name);
setTimeout(refreshSettings, 0);
});
TD.settings.setTheme = appendToFunction(TD.settings.setTheme, function(name){
document.documentElement.setAttribute("data-td-theme", name);
setTimeout(function(){
$TD.loadNotificationHeadContents(getNotificationHeadContents());
}, 0);
setTimeout(refreshSettings, 0);
});
onAppReady.push(function(){
document.documentElement.setAttribute("data-td-theme", TD.settings.getTheme());
onAppReady.push(refreshSettings);
})();
$TD.loadFontSizeClass(TD.settings.getFontSize());
$TD.loadNotificationHeadContents(getNotificationHeadContents());
});
//
// Block: Fix OS name and add ID to the document for priority CSS selectors.
//
if (ensurePropertyExists(TD, "util", "getOSName")){
TD.util.getOSName = function(){
return "windows";
};
doc.classList.remove("os-");
doc.classList.add("os-windows");
}
doc.id = "tduck";
//
// Block: Enable popup notifications.
@@ -357,7 +385,7 @@
mouseenter: function(){
let me = $(this);
let text = me.text();
return if text.charCodeAt(text.length-1) !== 8230; // horizontal ellipsis
return if text.charCodeAt(text.length-1) !== 8230 && text.charCodeAt(0) !== 8230; // horizontal ellipsis
if ($TDX.expandLinksOnHover){
tooltipTimer = window.setTimeout(function(){
@@ -367,7 +395,7 @@
}
else{
tooltipTimer = window.setTimeout(function(){
$TD.displayTooltip(me.attr("data-full-url"), false);
$TD.displayTooltip(me.attr("data-full-url"));
tooltipDisplayed = true;
}, 400);
}
@@ -384,13 +412,13 @@
if (tooltipDisplayed){
tooltipDisplayed = false;
$TD.displayTooltip(null, false);
$TD.displayTooltip(null);
}
},
mousemove: function(e){
if (tooltipDisplayed && (prevMouseX !== e.clientX || prevMouseY !== e.clientY)){
$TD.displayTooltip($(this).attr("data-full-url"), false);
$TD.displayTooltip($(this).attr("data-full-url"));
prevMouseX = e.clientX;
prevMouseY = e.clientY;
}
@@ -399,7 +427,7 @@
})();
//
// Block: Bypass t.co when clicking links and media.
// Block: Bypass t.co when clicking/dragging links and media.
//
$(document.body).delegate("a[data-full-url]", "click auxclick", function(e){
if (e.button === 0 || e.button === 1){ // event.which seems to be borked in auxclick
@@ -408,8 +436,18 @@
}
});
$(document.body).delegate("a[data-full-url]", "dragstart", function(e){
let url = $(this).attr("data-full-url");
let data = e.originalEvent.dataTransfer;
data.clearData();
data.setData("text/uri-list", url);
data.setData("text/plain", url);
data.setData("text/html", `<a href="${url}">${url}</a>`);
});
if (ensurePropertyExists(TD, "services", "TwitterUser", "prototype", "fromJSONObject")){
let prevFunc = TD.services.TwitterUser.prototype.fromJSONObject;
const prevFunc = TD.services.TwitterUser.prototype.fromJSONObject;
TD.services.TwitterUser.prototype.fromJSONObject = function(){
let obj = prevFunc.apply(this, arguments);
@@ -424,7 +462,7 @@
}
if (ensurePropertyExists(TD, "services", "TwitterMedia", "prototype", "fromMediaEntity")){
let prevFunc = TD.services.TwitterMedia.prototype.fromMediaEntity;
const prevFunc = TD.services.TwitterMedia.prototype.fromMediaEntity;
TD.services.TwitterMedia.prototype.fromMediaEntity = function(){
let obj = prevFunc.apply(this, arguments);
@@ -479,13 +517,13 @@
(function(){
var lastTweet = "";
var updateHighlightedColumn = function(ele){
const updateHighlightedColumn = function(ele){
highlightedColumnEle = ele;
highlightedColumnObj = ele ? TD.controller.columnManager.get(ele.attr("data-column")) : null;
return !!highlightedColumnObj;
};
var updateHighlightedTweet = function(ele, obj, tweetUrl, quoteUrl, authors, imageList){
const updateHighlightedTweet = function(ele, obj, tweetUrl, quoteUrl, authors, imageList){
highlightedTweetEle = ele;
highlightedTweetObj = obj;
@@ -495,7 +533,7 @@
}
};
var processMedia = function(chirp){
const processMedia = function(chirp){
return chirp.getMedia().filter(item => !item.isAnimatedGif).map(item => item.entity.media_url_https+":small").join(";");
};
@@ -544,7 +582,7 @@
(function(){
var selectedTweet;
var setImportantProperty = function(obj, property, value){
const setImportantProperty = function(obj, property, value){
if (obj.length === 1){
obj[0].style.setProperty(property, value, "important");
}
@@ -580,7 +618,7 @@
setImportantProperty(selectedTweet.find(".js-media-preview-container"), "margin-bottom", "4px");
selectedTweet.find(".js-translate-call-to-action").first().remove();
selectedTweet.find(".js-tweet").first().nextAll().remove();
selectedTweet.find("footer").last().prevUntil(":not(.txt-mute.txt-small)").addBack().remove(); // footer, date, location
selectedTweet.find("footer").last().prevUntil(":not(.txt-mute)").addBack().remove(); // footer, date, location
}
else{
setImportantProperty(selectedTweet.find(".js-media-preview-container"), "margin-bottom", "10px");
@@ -613,7 +651,7 @@
// Block: Paste images when tweeting.
//
onAppReady.push(function(){
var uploader = $._data(document, "events")["uiComposeAddImageClick"][0].handler.context;
const uploader = $._data(document, "events")["uiComposeAddImageClick"][0].handler.context;
app.delegate(".js-compose-text,.js-reply-tweetbox,.td-detect-image-paste", "paste", function(e){
for(let item of e.originalEvent.clipboardData.items){
@@ -631,25 +669,41 @@
});
});
//
// Block: Setup a global function to reorder event priority.
//
window.TDGF_prioritizeNewestEvent = function(element, event){
let events = $._data(element, "events");
let handlers = events[event];
let newHandler = handlers[handlers.length-1];
for(let index = handlers.length-1; index > 0; index--){
handlers[index] = handlers[index-1];
}
handlers[0] = newHandler;
};
//
// Block: Support for extra mouse buttons.
//
(function(){
var tryClickSelector = function(selector, parent){
const tryClickSelector = function(selector, parent){
return $(selector, parent).click().length;
};
var tryCloseModal1 = function(){
const tryCloseModal1 = function(){
let modal = $("#open-modal");
return modal.is(":visible") && tryClickSelector("a.mdl-dismiss", modal);
};
var tryCloseModal2 = function(){
const tryCloseModal2 = function(){
let modal = $(".js-modals-container");
return modal.length && tryClickSelector("a.mdl-dismiss", modal);
};
var tryCloseHighlightedColumn = function(){
const tryCloseHighlightedColumn = function(){
if (highlightedColumnEle){
let column = highlightedColumnEle.closest(".js-column");
return (column.is(".is-shifted-2") && tryClickSelector(".js-tweet-social-proof-back", column)) || (column.is(".is-shifted-1") && tryClickSelector(".js-column-back", column));
@@ -684,7 +738,7 @@
let isDraggingValid = false;
window.TDGF_onGlobalDragStart = function(type, data){
isDraggingValid = type === "link" && tweetRegex.test(data);
isDraggingValid = (type === "link" || type === "text") && tweetRegex.test(data);
};
app.delegate("section.js-column", {
@@ -737,14 +791,14 @@
(function(){
var holdingShift = false;
var updateShiftState = (pressed) => {
const updateShiftState = (pressed) => {
if (pressed != holdingShift){
holdingShift = pressed;
$("button[data-action='clear']").children("span").text(holdingShift ? "Restore" : "Clear");
}
};
var resetActiveFocus = () => {
const resetActiveFocus = () => {
document.activeElement.blur();
};
@@ -775,7 +829,7 @@
// Block: Swap shift key functionality for selecting accounts, and refocus the textbox afterwards.
//
onAppReady.push(function(){
var onAccountClick = function(e){
const onAccountClick = function(e){
if ($TDX.switchAccountSelectors){
e.shiftKey = !e.shiftKey;
}
@@ -796,23 +850,63 @@
});
//
// Block: Make middle click on tweet reply icon open the compose drawer.
// Block: Make middle click on tweet reply icon open the compose drawer, retweet icon trigger a quote, and favorite icon open a 'Like from accounts...' modal.
//
app.delegate(".js-reply-action", "mousedown", function(e){
if (e.which === 2){
if ($("[data-drawer='compose']").hasClass("is-hidden")){
$(document).trigger("uiDrawerShowDrawer", {
drawer: "compose",
withAnimation: true
app.delegate(".tweet-action,.tweet-detail-action", "auxclick", function(e){
return if e.which !== 2;
let column = TD.controller.columnManager.get($(this).closest("section.js-column").attr("data-column"));
return if !column;
let ele = $(this).closest("article");
let tweet = column.findChirp(ele.attr("data-tweet-id")) || column.findChirp(ele.attr("data-key"));
return if !tweet;
switch($(this).attr("rel")){
case "reply":
let main = tweet.getMainTweet();
$(document).trigger("uiDockedComposeTweet", {
type: "reply",
from: [ tweet.account.getKey() ],
inReplyTo: {
id: tweet.id,
htmlText: main.htmlText,
user: {
screenName: main.user.screenName,
name: main.user.name,
profileImageURL: main.user.profileImageURL
}
},
mentions: tweet.getReplyUsers(),
element: ele
});
window.setTimeout(() => $(this).trigger("click"), 1);
break;
case "favorite":
$(document).trigger("uiShowFavoriteFromOptions", { tweet });
break;
case "retweet":
TD.controller.stats.quoteTweet();
$(document).trigger("uiComposeTweet", {
type: "tweet",
from: [ tweet.account.getKey() ],
quotedTweet: tweet.getMainTweet(),
element: ele // triggers reply-account plugin
});
break;
default:
return;
}
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
}
});
//
@@ -826,7 +920,7 @@
// Block: Inject custom CSS and layout into the page.
//
(function(){
var createStyle = function(id, styles){
const createStyle = function(id, styles){
let ele = document.createElement("style");
ele.id = id;
ele.innerText = styles;
@@ -870,26 +964,34 @@
// Block: Setup video player hooks.
//
(function(){
var playVideo = function(url){
window.TDGF_playVideo = function(url, username){
$('<div id="td-video-player-overlay" class="ovl" style="display:block"></div>').on("click contextmenu", function(){
$TD.playVideo(null);
$TD.playVideo(null, null);
}).appendTo(app);
$TD.playVideo(url);
$TD.playVideo(url, username || null);
};
var getVideoTweetLink = function(obj){
const getGifLink = function(ele){
return ele.attr("src") || ele.children("source[video-src]").first().attr("video-src");
};
const getVideoTweetLink = function(obj){
let parent = obj.closest(".js-tweet").first();
let link = (parent.hasClass("tweet-detail") ? parent.find("a[rel='url']") : parent.find("time").first().children("a")).first();
return link.attr("href");
}
const getUsername = function(tweet){
return tweet && (tweet.quotedTweet || tweet).getMainUser().screenName;
}
app.delegate(".js-gif-play", {
click: function(e){
let src = !e.ctrlKey && $(this).closest(".js-media-gif-container").find("video").attr("src");
let src = !e.ctrlKey && getGifLink($(this).closest(".js-media-gif-container").find("video"));
if (src){
playVideo(src);
window.TDGF_playVideo(src, getUsername(highlightedTweetObj));
}
else{
$TD.openBrowser(getVideoTweetLink($(this)));
@@ -925,7 +1027,7 @@
let media = this.chirp.getMedia().find(media => media.mediaId === this.clickedMediaEntityId);
if (media && media.isVideo && media.service === "twitter"){
playVideo(media.chooseVideoVariant().url);
window.TDGF_playVideo(media.chooseVideoVariant().url, getUsername(this.chirp));
cancelModal = true;
}
});
@@ -1006,6 +1108,80 @@
}
});
//
// Block: Make submitting search queries while holding Ctrl or middle-clicking the search icon open the search externally.
//
onAppReady.push(function(){
const openSearchExternally = function(event, input){
$TD.openBrowser("https://twitter.com/search/?q="+encodeURIComponent(input.val() || ""));
event.preventDefault();
event.stopPropagation();
input.val("").blur();
app.click(); // unfocus everything
};
$(".js-app-search-input").keydown(function(e){
(e.ctrlKey && e.keyCode === 13) && openSearchExternally(e, $(this)); // enter
});
$(".js-perform-search").on("click auxclick", function(e){
(e.ctrlKey || e.button === 1) && openSearchExternally(e, $(".js-app-search-input:visible"));
}).each(function(){
window.TDGF_prioritizeNewestEvent($(this)[0], "click");
});
$("[data-action='show-search']").on("click auxclick", function(e){
(e.ctrlKey || e.button === 1) && openSearchExternally(e, $());
});
});
//
// Block: Override language used for translations.
//
if (ensurePropertyExists(TD, "languages", "getSystemLanguageCode")){
const prevFunc = TD.languages.getSystemLanguageCode;
TD.languages.getSystemLanguageCode = function(returnShortCode){
return returnShortCode ? ($TDX.translationTarget || "en") : prevFunc.apply(this, arguments);
};
}
//
// Block: Setup global function to refresh all columns.
//
window.TDGF_reloadColumns = function(){
Object.values(TD.controller.columnManager.getAll()).forEach(column => column.reloadTweets());
};
//
// Block: Allow applying ROT13 to input selection.
//
window.TDGF_applyROT13 = function(){
let ele = document.activeElement;
return if !ele || !ele.value;
let selection = ele.value.substring(ele.selectionStart, ele.selectionEnd);
return if !selection;
document.execCommand("insertText", false, selection.replace(/[a-zA-Z]/g, function(chr){
let code = chr.charCodeAt(0);
let start = code <= 90 ? 65 : 97;
return String.fromCharCode(start+(code-start+13)%26);
}));
};
//
// Block: Revert Like/Follow dialogs being closed after clicking an action.
//
if (ensurePropertyExists(TD, "decider", "get")){
const prevFunc = TD.decider.get;
TD.decider.get = function(key){
return $TDX.keepLikeFollowDialogsOpen && key.startsWith("tweetdeck_subsequent_") ? false : prevFunc.apply(this, arguments);
};
}
//
// Block: Fix DM reply input box not getting focused after opening a conversation.
//
@@ -1021,7 +1197,7 @@
// Block: Fix DM notifications not showing if the conversation is open.
//
if (ensurePropertyExists(TD, "services", "TwitterConversation", "prototype", "getUnreadChirps")){
var prevFunc = TD.services.TwitterConversation.prototype.getUnreadChirps;
const prevFunc = TD.services.TwitterConversation.prototype.getUnreadChirps;
TD.services.TwitterConversation.prototype.getUnreadChirps = function(e){
return (e && e.sortIndex && !e.id && !this.notificationsDisabled)
@@ -1082,35 +1258,6 @@
});
};
(function(){
var lastActivity = Date.now();
$(document).click(function(e){
lastActivity = Date.now();
});
window.TDGF_tryRunCleanup = function(){
// no recent activity
return false if Date.now()-lastActivity < 15e3;
// no modals are visible
return false if $(".js-modal").is(":visible") || !$(".js-modals-container").is(":empty");
// all columns are in a default state
return false if $("section.js-column").is(".is-shifted-1,.is-shifted-2");
// all textareas are empty
return false if Array.prototype.some.call(document.getElementsByTagName("textarea"), ele => ele.value.length > 0);
// all columns are scrolled to top
return false if Array.prototype.some.call(document.getElementsByClassName("js-column-scroller"), ele => ele.scrollTop > 0);
// cleanup
window.TDGF_reload();
return true;
};
})();
if (window.TD_SESSION && window.TD_SESSION.gc){
var state;
@@ -1163,15 +1310,24 @@
});
}
//
// Block: Disable default TweetDeck update notification.
//
onAppReady.push(function(){
let events = $._data(document, "events");
delete events["uiSuggestRefreshToggle"];
});
//
// Block: Disable TweetDeck metrics.
//
if (ensurePropertyExists(TD, "metrics")){
TD.metrics.inflate = function(){};
TD.metrics.inflateMetricTriple = function(){};
TD.metrics.log = function(){};
TD.metrics.makeKey = function(){};
TD.metrics.send = function(){};
const noop = function(){};
TD.metrics.inflate = noop;
TD.metrics.inflateMetricTriple = noop;
TD.metrics.log = noop;
TD.metrics.makeKey = noop;
TD.metrics.send = noop;
}
onAppReady.push(function(){

View File

@@ -8,8 +8,8 @@
#td-introduction-modal .mdl {
width: 90%;
max-width: 800px;
height: 372px;
max-width: 830px;
height: 328px;
}
#td-introduction-modal .mdl-header-title {
@@ -31,6 +31,19 @@
text-shadow: 0 0 #000;
}
#td-introduction-modal .main-menu {
float: left;
width: 187px;
height: 124px;
margin-right: 12px;
box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.33);
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALsAAAB8CAMAAAAGozFUAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACZUExURQAAAAAANgAAYQA2YQA2iABhYQBhrDYAADYANjYAYTY2ADY2NjY2YTY2iDZhrDaIiDaIz2EAAGEANmEAYWE2NmE2iGFhNmFhYWGs8og2AIg2Nog2YYiIYYisiIjPrIjPz4jP8qxhAKxhNqzPiKzyrKzy8szMzM+INs/PiM/yrM/yz8/y8tfX1/Dw8PKsYfLPiPLyrPLyz/Ly8nG9GIQAAARDSURBVHja7ZyPd9IwEMdDxbI5cemcWJjbWrWrDi2Q//+PM7m7pOGHfcORlD6v7w1ok2wfwqXkvrs78TTcQzypoR6DZ//dcTA7szM7s8dgX2dCiJxGrG+Kvd+C1w619M+uoZqkGiy7AxvivI+XajURSWVO9AtjQ6UQqTIn18R+PxFSP82SCnuUOYxdfdjMzSmNn9mPMJa9p/QWpH5cZ5rjgqbanNQjOMnGS315nWl+7NGkqrmSqpb6hRuvm+PO++qygtkWKZ2YOW2EGBWr6dK3mTKHmcYeuu3r/fR5UawmUrXjY9tMLRFItez6abPYYd/MffbN4uHT89033UPVInfje1ir+Glbm7msGjKR1mbg/UFv7KFqYzGfwUhq6cbHZle1Jp3QZ65fjMwcize3hbEcu1ZnZkVCb+wBq8L86D4JGl1sm+E9AbMzO7MzO7MzO7Mze586QQqdcav7ggO3juMfF8Whlsj+6hXuzx17x0aWmswO/nBLbPaZ8RqatzdHsO/36In94WOltAOnXSZwoR7A+Qa3H5q/CEH+aA5+OfaQ6ASCYGAuZq3nAi4KjML2oH6T9qxX05+W3T5pt5+WQ5NUpA1s99jMpZt05zpiPxzVpOF1gukSFACPDN3+1p0l/3qbHf1rVBQO9gMFISz7ZnE/Xe6wX3oqXzc7KgoH+4GCEFoXA8VL20CuGprCTG6xkzZwyGZIUTjUDxSEwOybu4regzC3mxLXqm8zVhsoYUXai2aF5qQolL7KQB1QQeDvVWZndmZndmZn9nNn/3WuB9sMszM7szM7sw+WXXvEQgYTRYOyNxA9IEMJiyHZjSijWi1pUOxIDcqWiao6uSgalN0E70BYT7YncJ1CFI0074Uvg55KFI1i715U1SlF0bD3maRSGH4nfbn0VKJo+Pt77qKqTi2KRvlePZObI7PzXozZmZ3ZmZ3Zmf1/YjdO3qjY3xH0rBW8PN9jvDyv3cyR+R7DZvdCNq7pQk8iwfE243l7NUTR9CYSHLNWk8qPRsKcJu9CdJHgmHlXqos9vkhwPLsNuWptpi+R4Hh2G3KF+Vgte3yR4FXfq620Orw9QTleDpLd3M6Tvqed95HMzuzMzuzMzuw9sJep6v7XQSnEXsyBd0AZmN3WVzrAL2Rfvb8t/vK3OgukKM/ddelpkdlraVyLV7LvbZujsLuyG1CkxcURtGlalsQVbaHELfP43YYfeP9WRmGBfPWw7Bpc47dFWnaTsBy7K9ri6r3Q4w47BrSsbx7neeh5NwZTp7ZIy34ik2P3iraYxK22aguyb+c46RfvZGibgZsElfzRc9fB7oq2uJysHXvfZs+u0tDspl4RhqBgOAHGEXhpWu3Ks0VbqF6Lq9piC7vQIGszRSkDs+MfqFMbTkBJWF6almO3RVsocQsfS0yihOxXGgQpW2D4/+728p6A2Zmd2Zmd2c+SfaCHZh/u8QdspaeBZ15I7gAAAABJRU5ErkJggg==);
}
#td-introduction-modal .main-menu + p {
margin-top: 15px;
}
#td-introduction-modal footer {
padding: 10px 0;
}
@@ -65,10 +78,10 @@
<div class="mdl-inner">
<div class="mdl-content">
<p>Thank you for downloading TweetDuck!</p>
<p><strong>Right-click anywhere</strong> or click <strong>Settings TweetDuck</strong> in the left panel to open the main menu, where you can access the <strong>Options</strong>, <strong>Plugins</strong>, and more.</p>
<p>You can also right-click links, media, tweets, notifications, etc. to access their context menu.</p>
<p>If you're using TweetDuck for the first time, check out the <strong>guide</strong> that answers common questions and showcases important features. You can open the main menu, select <strong>About TweetDuck</strong>, and click the help button to view the guide later.</p>
<p>Before you continue, please consider helping development by allowing TweetDuck to send anonymous data in the future. You can always disable it in <strong>Options Feedback</strong>.</p>
<p><a id="td-introduction-follow" href="#">Follow @TryTweetDuck</a> for latest news and updates about the app.</p>
<div class="main-menu"></div>
<p><strong>Right-click anywhere</strong> or click <strong>Settings&nbsp;&nbsp;TweetDuck</strong> in the left panel to open the main menu. You can also right-click links, tweets, images and videos, and desktop notifications to access their respective context menus.</p>
<p>Click <strong>Show Guide</strong> to see awesome features TweetDuck offers, or view the guide later by going to <strong>About TweetDuck</strong> and clicking the help button on top.</p>
</div>
<footer class="txt-right">
<div class="anondata">
@@ -83,6 +96,30 @@
</div>
</div>`).appendTo(".js-app");
let tdUser = null;
let loadTweetDuckUser = (onSuccess, onError) => {
if (tdUser !== null){
onSuccess(tdUser);
}
else{
TD.controller.clients.getPreferredClient().getUsersByIds([ "731137856052269056" ], users => onSuccess(users[0]), onError);
}
};
loadTweetDuckUser(user => tdUser = user);
ele.find("#td-introduction-follow").click(function(){
loadTweetDuckUser(user => {
$(document).trigger("uiShowFollowFromOptions", { userToFollow: user });
$(".js-modals-container").find("header a[rel='user']").each(function(){
this.outerHTML = this.innerText;
});
}, () => {
alert("An error occurred when retrieving the account information.");
});
});
ele.find("button, a.mdl-dismiss").click(function(){
let showGuide = $(this)[0].hasAttribute("data-guide");
let allowDataCollection = $("#td-anonymous-data").is(":checked");

View File

@@ -52,7 +52,7 @@
return if !url;
var text = me.textContent;
return if text.charCodeAt(text.length-1) !== 8230; // horizontal ellipsis
return if text.charCodeAt(text.length-1) !== 8230 && text.charCodeAt(0) !== 8230; // horizontal ellipsis
if ($TDX.expandLinksOnHover){
tooltipTimer = window.setTimeout(function(){
@@ -62,7 +62,7 @@
}
else{
tooltipTimer = window.setTimeout(function(){
$TD.displayTooltip(url, true);
$TD.displayTooltip(url);
tooltipDisplayed = true;
}, 400);
}
@@ -83,7 +83,7 @@
if (tooltipDisplayed){
tooltipDisplayed = false;
$TD.displayTooltip(null, true);
$TD.displayTooltip(null);
}
});
@@ -92,13 +92,32 @@
var url = e.currentTarget.getAttribute("data-full-url");
return if !url;
$TD.displayTooltip(url, true);
$TD.displayTooltip(url);
prevMouseX = e.clientX;
prevMouseY = e.clientY;
}
});
})();
//
// Block: Work around clipboard HTML formatting.
//
document.addEventListener("copy", function(e){
window.setTimeout($TD.fixClipboard, 0);
});
//
// Block: Setup a handler for 'Show this thread'.
//
(function(){
var btn = document.getElementById("tduck-show-thread");
return if !btn;
btn.addEventListener("click", function(){
$TD.showTweetDetail();
});
})();
//
// Block: Setup a skip button.
//

View File

@@ -3,7 +3,7 @@
<div class="js-tweet tweet">
<header class="tweet-header">
<time class="tweet-timestamp js-timestamp pull-right txt-mute">
<a target="_blank" rel="url" href="https://twitter.com/chylexmc" class="txt-small">0s</a>
<a target="_blank" rel="url" href="https://twitter.com/chylexmc" class="txt-size-variable--12">0s</a>
</time>
<a target="_blank" rel="user" href="https://twitter.com/chylexmc" class="account-link link-complex block">
<div class="obj-left item-img tweet-img">

View File

@@ -87,14 +87,14 @@
};
//
// Block: Setup global function to change plugin state.
// Block: Setup a function to change plugin state.
//
window.TDPF_setPluginState = function(identifier, enable){
window.TD_PLUGINS.setState(window.TD_PLUGINS.findObject(identifier), enable);
};
//
// Block: Setup global function to reload the page.
// Block: Setup a function to reload the page.
//
window.TDPF_requestReload = function(){
if (!isReloading){
@@ -102,4 +102,11 @@
isReloading = true;
}
};
//
// Block: Setup bridges to global functions.
//
window.TDPF_playVideo = window.TDGF_playVideo;
window.TDPF_reloadColumns = window.TDGF_reloadColumns;
window.TDPF_prioritizeNewestEvent = window.TDGF_prioritizeNewestEvent;
})();

View File

@@ -3,23 +3,23 @@
/***********************/
.scroll-styled-v::-webkit-scrollbar-thumb, .scroll-styled-h::-webkit-scrollbar-thumb, .antiscroll-scrollbar {
border-radius: 0;
border-radius: 0 !important;
}
.antiscroll-scrollbar-vertical {
margin-top: 0;
margin-top: 0 !important;
}
.antiscroll-scrollbar-horizontal {
margin-left: 0;
margin-left: 0 !important;
}
.scroll-styled-v:not(.antiscroll-inner)::-webkit-scrollbar {
width: 8px;
width: 8px !important;
}
.scroll-styled-h:not(.antiscroll-inner)::-webkit-scrollbar {
height: 8px;
height: 8px !important;
}
.app-columns-container::-webkit-scrollbar {
@@ -27,7 +27,7 @@
}
.app-columns-container::-webkit-scrollbar-track {
border-left: 0;
border-left: 0 !important;
}
.app-columns-container {
@@ -38,11 +38,19 @@
/* Square-ify stuff */
/********************/
.btn, .mdl, .mdl-content, .app-search-fake, .app-search-input, .popover, .lst-modal, .media-item, .media-preview, .tooltip-inner {
.btn, .mdl, .mdl-content, .app-search-fake, .app-search-input, .popover, .lst-modal, .tooltip-inner {
border-radius: 1px !important;
}
.compose-text-container, .dropdown-menu, .list-item-last, .quoted-tweet, .input-group-button, input, textarea, select, .prf-header, .accs li, .accs img {
.media-item, .media-preview, .media-image, .js-media-added .br--4 {
border-radius: 1px !important;
}
.compose-text-container, .compose-reply-tweet, .compose-message-recipient-input-container, .compose-message-recipient, .compose-media-bar-holder, .media-grid-container, .js-quote-tweet-holder {
border-radius: 0 !important;
}
.dropdown-menu, .list-item-last, .quoted-tweet, .input-group-button, input, textarea, select, .prf-header, .accs li, .accs img {
border-radius: 0 !important;
}
@@ -79,20 +87,25 @@
}
.app-header:not(.is-condensed) .nav-user-info {
padding: 0 5px;
padding: 0 5px !important;
}
/****************************************/
/* Tweak notification layout and design */
/****************************************/
.tweet-context .nbfc {
text-overflow: ellipsis !important;
white-space: nowrap !important;
}
.activity-header {
align-items: center !important;
margin-bottom: 4px;
margin-bottom: 4px !important;
}
.activity-header .tweet-timestamp {
line-height: unset;
line-height: unset !important;
}
.account-bio.padding-t--5 {
@@ -118,17 +131,12 @@ html[data-td-theme='dark'] .stream-item:not(:hover) .js-user-actions-menu {
a[data-full-url] {
/* break long urls */
word-break: break-all;
}
.character-count-compose {
/* fix strangely wide character count element */
width: 40px !important;
word-break: break-all !important;
}
.is-video a:not([href*='youtu']) .icon-bg-dot, .is-gif .icon-bg-dot {
/* change play icon on mp4s */
color: #9f51cf;
color: #9f51cf !important;
}
/***************************************/
@@ -144,7 +152,7 @@ a[data-full-url] {
}
.js-docked-compose footer {
display: none;
display: none !important;
}
.compose-content {
@@ -157,23 +165,35 @@ a[data-full-url] {
.account-inline .username {
/* move usernames a bit higher */
vertical-align: 10%;
vertical-align: 10% !important;
}
.txt-base-smallest .sprite-verified-mini {
.account-inline .position-rel + .username {
/* except for follow notifications because consistency eh */
vertical-align: -10% !important;
}
html[data-td-font='smallest'] .sprite-verified-mini {
/* fix cut off badge when zoomed in */
width: 13px !important;
height: 13px !important;
background-position: -223px -99px !important;
}
.txt-base-smallest .badge-verified:before {
html[data-td-font='smallest'] .badge-verified:before {
/* fix cut off badge in notifications */
width: 13px !important;
height: 13px !important;
background-position: -223px -98px !important;
}
html[data-td-font='smallest'] .tweet-detail-wrapper .badge-verified:before {
/* fix cut off badge in detail view */
width: 13px !important;
height: 14px !important;
background-position: -223px -97px !important;
}
.accs-header {
/* fix retweet account selector heading */
padding-left: 0 !important;
@@ -181,7 +201,7 @@ a[data-full-url] {
.keyboard-shortcut-list {
/* fix keyboard navigation alignment */
vertical-align: top;
vertical-align: top !important;
}
.tweet-detail-wrapper .js-media-gif-container {
@@ -189,20 +209,31 @@ a[data-full-url] {
cursor: pointer;
}
/********************************************************************/
/* Fix glaring visual issues because twitter is fucking incompetent */
/********************************************************************/
/***************************************************************/
/* Fix glaring visual issues that twitter hasn't fixed yet smh */
/***************************************************************/
.column-nav-link .attribution {
/* fix cut off account names */
position: absolute;
position: absolute !important;
}
.js-docked-compose .js-drawer-close {
#tduck .js-docked-compose .js-drawer-close {
/* fix close drawer button position */
margin: 20px 0 0 !important;
}
.compose-reply-tweet-remove {
/* fix close reply button position; this would make sense if the reply account name became full size, but it still cuts off where the button used to be */
top: 3px !important;
right: -2px !important;
}
#account-safeguard-checkbox, #inline-account-safeguard-checkbox {
/* fix "Ready to Tweet/send" alignment with the checkbox */
vertical-align: -15% !important;
}
/************************************************/
/* Fix tooltips in navigation and column header */
/************************************************/
@@ -212,7 +243,7 @@ a[data-full-url] {
}
.js-column-header .column-header-link {
padding: 0;
padding: 0 !important;
}
.js-column-header .column-header-link .icon {
@@ -231,9 +262,28 @@ a[data-full-url] {
}
.column:not(.is-options-open) .column-header {
border-bottom: none;
border-bottom: none !important;
}
.is-options-open .column-type-icon {
bottom: 27px;
bottom: 27px !important;
}
/********************************************/
/* Fix cut off usernames in Messages column */
/********************************************/
.column-type-message.is-shifted-1 .column-title-container {
height: 100%;
border-bottom-color: transparent;
}
#tduck .column-type-message.is-shifted-1 .column-title-items {
height: 100%;
margin-left: 4px !important;
padding-top: 1px;
}
.column-type-message.is-shifted-1 .username {
vertical-align: bottom;
}

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