1
0
mirror of https://github.com/chylex/TweetDuck.git synced 2025-04-13 18:15:48 +02:00

Update from master

This commit is contained in:
chylex 2017-07-08 02:49:21 +02:00
commit dbb2f10754
54 changed files with 755 additions and 319 deletions

View File

@ -1,5 +1,5 @@
using System;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
namespace TweetDuck.Configuration{
static class Arguments{

View File

@ -1,217 +1,140 @@
using System;
using System.Drawing;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using TweetDuck.Core;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Notification;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Data.Serialization;
namespace TweetDuck.Configuration{
[Serializable]
sealed class UserConfig{
private static readonly IFormatter Formatter = new BinaryFormatter{ Binder = new LegacyBinder() };
sealed class UserConfig : ISerializedObject{
private static readonly FileSerializer<UserConfig> Serializer = new FileSerializer<UserConfig>();
private class LegacyBinder : SerializationBinder{
public override Type BindToType(string assemblyName, string typeName){
return Type.GetType(string.Format("{0}, {1}", typeName.Replace("TweetDck", "TweetDuck"), assemblyName.Replace("TweetDck", "TweetDuck")));
}
static UserConfig(){
Serializer.RegisterTypeConverter(typeof(WindowState), WindowState.Converter);
Serializer.RegisterTypeConverter(typeof(Point), new SingleTypeConverter<Point>{
ConvertToString = value => $"{value.X} {value.Y}",
ConvertToObject = value => {
int[] elements = StringUtils.ParseInts(value, ' ');
return new Point(elements[0], elements[1]);
}
});
Serializer.RegisterTypeConverter(typeof(Size), new SingleTypeConverter<Size>{
ConvertToString = value => $"{value.Width} {value.Height}",
ConvertToObject = value => {
int[] elements = StringUtils.ParseInts(value, ' ');
return new Size(elements[0], elements[1]);
}
});
}
// CONFIGURATION DATA
private const int CurrentFileVersion = 12;
public WindowState BrowserWindow { get; set; } = new WindowState();
public WindowState PluginsWindow { get; set; } = new WindowState();
// START OF CONFIGURATION
public bool ExpandLinksOnHover { get; set; } = true;
public bool SwitchAccountSelectors { get; set; } = true;
public bool EnableSpellCheck { get; set; } = false;
private int _zoomLevel = 100;
private bool _muteNotifications;
private TrayIcon.Behavior _trayBehavior = TrayIcon.Behavior.Disabled;
public bool EnableTrayHighlight { get; set; } = true;
public WindowState BrowserWindow { get; set; }
public WindowState PluginsWindow { get; set; }
public bool EnableUpdateCheck { get; set; } = true;
public string DismissedUpdate { get; set; } = null;
public bool DisplayNotificationColumn { get; set; }
public bool DisplayNotificationTimer { get; set; }
public bool NotificationTimerCountDown { get; set; }
public bool NotificationSkipOnLinkClick { get; set; }
public bool NotificationNonIntrusiveMode { get; set; }
public bool DisplayNotificationColumn { get; set; } = false;
public bool NotificationSkipOnLinkClick { get; set; } = false;
public bool NotificationNonIntrusiveMode { get; set; } = true;
public int NotificationIdlePauseSeconds { get; set; } = 0;
public int NotificationIdlePauseSeconds { get; set; }
public int NotificationDurationValue { get; set; }
public int NotificationScrollSpeed { get; set; }
public bool DisplayNotificationTimer { get; set; } = true;
public bool NotificationTimerCountDown { get; set; } = false;
public int NotificationDurationValue { get; set; } = 25;
public TweetNotification.Position NotificationPosition { get; set; }
public Point CustomNotificationPosition { get; set; }
public int NotificationEdgeDistance { get; set; }
public int NotificationDisplay { get; set; }
public TweetNotification.Position NotificationPosition { get; set; } = TweetNotification.Position.TopRight;
public Point CustomNotificationPosition { get; set; } = ControlExtensions.InvisibleLocation;
public int NotificationDisplay { get; set; } = 0;
public int NotificationEdgeDistance { get; set; } = 8;
public TweetNotification.Size NotificationSize { get; set; }
public Size CustomNotificationSize { get; set; }
public TweetNotification.Size NotificationSize { get; set; } = TweetNotification.Size.Auto;
public Size CustomNotificationSize { get; set; } = Size.Empty;
public int NotificationScrollSpeed { get; set; } = 10;
public bool EnableSpellCheck { get; set; }
public bool ExpandLinksOnHover { get; set; }
public bool SwitchAccountSelectors { get; set; }
public bool EnableTrayHighlight { get; set; }
private string _notificationSoundPath;
public bool EnableUpdateCheck { get; set; }
public string DismissedUpdate { get; set; }
public string CustomCefArgs { get; set; } = null;
public string CustomBrowserCSS { get; set; } = null;
public string CustomNotificationCSS { get; set; } = null;
public string CustomCefArgs { get; set; }
public string CustomBrowserCSS { get; set; }
public string CustomNotificationCSS { get; set; }
public bool EnableBrowserGCReload { get; set; }
public int BrowserMemoryThreshold { get; set; }
public bool EnableBrowserGCReload { get; set; } = false;
public int BrowserMemoryThreshold { get; set; } = 350;
// SPECIAL PROPERTIES
public bool IsCustomNotificationPositionSet => CustomNotificationPosition != ControlExtensions.InvisibleLocation;
public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty;
public string NotificationSoundPath{
get => string.IsNullOrEmpty(notificationSoundPath) ? string.Empty : notificationSoundPath;
set => notificationSoundPath = value;
get => string.IsNullOrEmpty(_notificationSoundPath) ? string.Empty : _notificationSoundPath;
set => _notificationSoundPath = value;
}
public bool MuteNotifications{
get => muteNotifications;
get => _muteNotifications;
set{
if (muteNotifications != value){
muteNotifications = value;
if (_muteNotifications != value){
_muteNotifications = value;
MuteToggled?.Invoke(this, new EventArgs());
}
}
}
public int ZoomLevel{
get => zoomLevel;
get => _zoomLevel;
set{
if (zoomLevel != value){
zoomLevel = value;
if (_zoomLevel != value){
_zoomLevel = value;
ZoomLevelChanged?.Invoke(this, new EventArgs());
}
}
}
public double ZoomMultiplier => zoomLevel/100.0;
public double ZoomMultiplier => _zoomLevel/100.0;
public TrayIcon.Behavior TrayBehavior{
get => trayBehavior;
get => _trayBehavior;
set{
if (trayBehavior != value){
trayBehavior = value;
if (_trayBehavior != value){
_trayBehavior = value;
TrayBehaviorChanged?.Invoke(this, new EventArgs());
}
}
}
// END OF CONFIGURATION
// EVENTS
[field:NonSerialized]
public event EventHandler MuteToggled;
[field:NonSerialized]
public event EventHandler ZoomLevelChanged;
[field:NonSerialized]
public event EventHandler TrayBehaviorChanged;
private readonly string file;
[NonSerialized]
private string file;
private int fileVersion;
private bool muteNotifications;
private int zoomLevel;
private string notificationSoundPath;
private TrayIcon.Behavior trayBehavior;
private UserConfig(string file){
public UserConfig(string file){ // TODO make private after removing UserConfigLegacy
this.file = file;
BrowserWindow = new WindowState();
ZoomLevel = 100;
DisplayNotificationTimer = true;
NotificationNonIntrusiveMode = true;
NotificationPosition = TweetNotification.Position.TopRight;
CustomNotificationPosition = ControlExtensions.InvisibleLocation;
NotificationSize = TweetNotification.Size.Auto;
NotificationEdgeDistance = 8;
NotificationDurationValue = 25;
NotificationScrollSpeed = 100;
BrowserMemoryThreshold = 350;
EnableUpdateCheck = true;
ExpandLinksOnHover = true;
SwitchAccountSelectors = true;
EnableTrayHighlight = true;
PluginsWindow = new WindowState();
}
private void UpgradeFile(){
if (fileVersion == CurrentFileVersion){
return;
}
// if outdated, cycle through all versions
if (fileVersion == 0){
DisplayNotificationTimer = true;
EnableUpdateCheck = true;
++fileVersion;
}
if (fileVersion == 1){
ExpandLinksOnHover = true;
++fileVersion;
}
if (fileVersion == 2){
BrowserWindow = new WindowState();
PluginsWindow = new WindowState();
++fileVersion;
}
if (fileVersion == 3){
EnableTrayHighlight = true;
NotificationDurationValue = 25;
++fileVersion;
}
if (fileVersion == 4){
++fileVersion;
}
if (fileVersion == 5){
++fileVersion;
}
if (fileVersion == 6){
NotificationNonIntrusiveMode = true;
++fileVersion;
}
if (fileVersion == 7){
ZoomLevel = 100;
++fileVersion;
}
if (fileVersion == 8){
SwitchAccountSelectors = true;
++fileVersion;
}
if (fileVersion == 9){
NotificationScrollSpeed = 100;
++fileVersion;
}
if (fileVersion == 10){
NotificationSize = TweetNotification.Size.Auto;
}
if (fileVersion == 11){
BrowserMemoryThreshold = 350;
++fileVersion;
}
// update the version
fileVersion = CurrentFileVersion;
Save();
bool ISerializedObject.OnReadUnknownProperty(string property, string value){
return false;
}
public bool Save(){
@ -227,10 +150,7 @@ public bool Save(){
File.Move(file, backupFile);
}
using(Stream stream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None)){
Formatter.Serialize(stream, this);
}
Serializer.Write(file, this);
return true;
}catch(Exception e){
Program.Reporter.HandleException("Configuration Error", "Could not save the configuration file.", true, e);
@ -239,22 +159,20 @@ public bool Save(){
}
public static UserConfig Load(string file){
UserConfig config = null;
Exception firstException = null;
for(int attempt = 0; attempt < 2; attempt++){
try{
using(Stream stream = new FileStream(attempt == 0 ? file : GetBackupFile(file), FileMode.Open, FileAccess.Read, FileShare.Read)){
if ((config = Formatter.Deserialize(stream) as UserConfig) != null){
config.file = file;
}
}
config?.UpgradeFile();
break;
UserConfig config = new UserConfig(file);
Serializer.Read(attempt == 0 ? file : GetBackupFile(file), config);
return config;
}catch(FileNotFoundException){
}catch(DirectoryNotFoundException){
break;
}catch(FormatException){
UserConfig config = UserConfigLegacy.Load(file);
config.Save();
return config;
}catch(Exception e){
if (attempt == 0){
firstException = e;
@ -266,11 +184,11 @@ public static UserConfig Load(string file){
}
}
if (firstException != null && config == null){
if (firstException != null){
Program.Reporter.HandleException("Configuration Error", "Could not open the configuration file.", true, firstException);
}
return config ?? new UserConfig(file);
return new UserConfig(file);
}
public static string GetBackupFile(string file){

View File

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

View File

@ -70,7 +70,7 @@ public static bool AlignValueToTick(this TrackBar trackBar){
public static void SetElevated(this Button button){
button.Text = " "+button.Text;
button.FlatStyle = FlatStyle.System;
NativeMethods.SendMessage(button.Handle, NativeMethods.BCM_SETSHIELD, 0, new IntPtr(1));
NativeMethods.SendMessage(button.Handle, NativeMethods.BCM_SETSHIELD, new UIntPtr(0), new IntPtr(1));
}
public static void EnableMultilineShortcuts(this TextBox textBox){

View File

@ -19,7 +19,7 @@
using TweetDuck.Resources;
using TweetDuck.Updates;
using TweetDuck.Updates.Events;
using TweetLib.Audio.Utils;
using TweetLib.Audio;
namespace TweetDuck.Core{
sealed partial class FormBrowser : Form{
@ -208,7 +208,7 @@ private void browser_LoadError(object sender, LoadErrorEventArgs e){
return;
}
if (!e.FailedUrl.StartsWith("http://td/")){
if (!e.FailedUrl.StartsWith("http://td/", StringComparison.Ordinal)){
string errorPage = ScriptLoader.LoadResource("pages/error.html", true);
if (errorPage != null){
@ -406,7 +406,7 @@ public void UpdateProperties(PropertyBridge.Properties properties){
}
public void ReloadToTweetDeck(){
browser.ExecuteScriptAsync("gc&&gc();window.location.href='https://tweetdeck.twitter.com'");
browser.ExecuteScriptAsync($"gc&&gc();window.location.href='{BrowserUtils.TweetDeckURL}'");
}
// callback handlers
@ -485,7 +485,7 @@ public void PlayNotificationSound(){
public void OnTweetScreenshotReady(string html, int width, int height){
if (notificationScreenshotManager == null){
notificationScreenshotManager = new TweetScreenshotManager(this);
notificationScreenshotManager = new TweetScreenshotManager(this, plugins);
}
notificationScreenshotManager.Trigger(html, width, height);

View File

@ -113,7 +113,7 @@ protected void SetClipboardText(string text){
form.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText));
}
protected void AddDebugMenuItems(IMenuModel model){
protected static void AddDebugMenuItems(IMenuModel model){
model.AddItem((CefMenuCommand)MenuOpenDevTools, "Open dev tools");
}
@ -134,11 +134,7 @@ private static string GetImageFileName(string url){
int dot = url.LastIndexOf('.');
if (dot != -1){
int colon = url.IndexOf(':', dot);
if (colon != -1){
url = url.Substring(0, colon);
}
url = StringUtils.ExtractBefore(url, ':', dot);
}
// return file name

View File

@ -111,7 +111,7 @@ public FormNotificationBase(Form owner, bool enableContextMenu){
this.dpiScale = this.GetDPIScale();
DefaultResourceHandlerFactory handlerFactory = (DefaultResourceHandlerFactory)browser.ResourceHandlerFactory;
handlerFactory.RegisterHandler("https://tweetdeck.twitter.com", this.resourceHandler);
handlerFactory.RegisterHandler(BrowserUtils.TweetDeckURL, this.resourceHandler);
Controls.Add(browser);
@ -183,7 +183,7 @@ protected virtual void LoadTweet(TweetNotification tweet){
currentColumn = tweet.Column;
resourceHandler.SetHTML(GetTweetHTML(tweet));
browser.Load("https://tweetdeck.twitter.com");
browser.Load(BrowserUtils.TweetDeckURL);
}
protected virtual void SetNotificationSize(int width, int height){

View File

@ -5,6 +5,7 @@
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Plugins;
using TweetDuck.Plugins.Enums;
using TweetDuck.Resources;
@ -17,12 +18,8 @@ partial class FormNotificationMain : FormNotificationBase{
private static readonly string NotificationScriptIdentifier = ScriptLoader.GetRootIdentifier(NotificationScriptFile);
private static readonly string PluginScriptIdentifier = ScriptLoader.GetRootIdentifier(PluginManager.PluginNotificationScriptFile);
private static readonly string NotificationJS, PluginJS;
static FormNotificationMain(){
NotificationJS = ScriptLoader.LoadResource(NotificationScriptFile);
PluginJS = ScriptLoader.LoadResource(PluginManager.PluginNotificationScriptFile);
}
private static readonly string NotificationJS = ScriptLoader.LoadResource(NotificationScriptFile);
private static readonly string PluginJS = ScriptLoader.LoadResource(PluginManager.PluginNotificationScriptFile);
private readonly PluginManager plugins;

View File

@ -4,11 +4,17 @@
using System.Windows.Forms;
using TweetDuck.Core.Bridge;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Plugins;
using TweetDuck.Resources;
namespace TweetDuck.Core.Notification.Screenshot{
sealed class FormNotificationScreenshotable : FormNotificationBase{
public FormNotificationScreenshotable(Action callback, Form owner) : base(owner, false){
private readonly PluginManager plugins;
public FormNotificationScreenshotable(Action callback, Form owner, PluginManager pluginManager) : base(owner, false){
this.plugins = pluginManager;
browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new CallbackBridge(this, callback));
browser.FrameLoadEnd += (sender, args) => {
@ -17,9 +23,15 @@ public FormNotificationScreenshotable(Action callback, Form owner) : base(owner,
}
};
}
protected override string GetTweetHTML(TweetNotification tweet){
return tweet.GenerateHtml(enableCustomCSS: false);
string html = tweet.GenerateHtml("td-screenshot", false);
foreach(InjectedHTML injection in plugins.Bridge.NotificationInjections){
html = injection.Inject(html);
}
return html;
}
public void LoadNotificationForScreenshot(TweetNotification tweet, int width, int height){

View File

@ -4,17 +4,20 @@
using System;
using System.Windows.Forms;
using TweetDuck.Core.Controls;
using TweetDuck.Plugins;
namespace TweetDuck.Core.Notification.Screenshot{
sealed class TweetScreenshotManager : IDisposable{
private readonly Form owner;
private readonly PluginManager plugins;
private readonly Timer timeout;
private readonly Timer disposer;
private FormNotificationScreenshotable screenshot;
public TweetScreenshotManager(Form owner){
public TweetScreenshotManager(Form owner, PluginManager pluginManager){
this.owner = owner;
this.plugins = pluginManager;
this.timeout = new Timer{ Interval = 8000 };
this.timeout.Tick += timeout_Tick;
@ -40,7 +43,7 @@ public void Trigger(string html, int width, int height){
return;
}
screenshot = new FormNotificationScreenshotable(Callback, owner){
screenshot = new FormNotificationScreenshotable(Callback, owner, plugins){
CanMoveWindow = () => false
};

View File

@ -1,6 +1,5 @@
using System;
using TweetLib.Audio;
using TweetLib.Audio.Utils;
namespace TweetDuck.Core.Notification{
sealed class SoundNotification : IDisposable{

View File

@ -3,7 +3,7 @@
using System.Linq;
using System.Windows.Forms;
using TweetDuck.Configuration;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
namespace TweetDuck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsRestart : Form{

View File

@ -4,6 +4,7 @@
using System.Linq;
using System.Windows.Forms;
using TweetDuck.Configuration;
using TweetDuck.Data;
using TweetDuck.Plugins;
using TweetDuck.Plugins.Enums;

View File

@ -68,10 +68,10 @@ public TabSettingsNotifications(FormNotificationMain notification){
checkNonIntrusive.Checked = Config.NotificationNonIntrusiveMode;
trackBarScrollSpeed.SetValueSafe(Config.NotificationScrollSpeed);
labelScrollSpeedValue.Text = trackBarScrollSpeed.Value.ToString(CultureInfo.InvariantCulture)+"%";
labelScrollSpeedValue.Text = trackBarScrollSpeed.Value+"%";
trackBarEdgeDistance.SetValueSafe(Config.NotificationEdgeDistance);
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value.ToString(CultureInfo.InvariantCulture)+" px";
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value+" px";
this.notification.CanMoveWindow = () => radioLocCustom.Checked;
this.notification.CanResizeWindow = radioSizeCustom.Checked;
@ -233,7 +233,7 @@ private void comboBoxIdlePause_SelectedValueChanged(object sender, EventArgs e){
private void trackBarScrollSpeed_ValueChanged(object sender, EventArgs e){
if (trackBarScrollSpeed.AlignValueToTick()){
labelScrollSpeedValue.Text = trackBarScrollSpeed.Value.ToString(CultureInfo.InvariantCulture)+"%";
labelScrollSpeedValue.Text = trackBarScrollSpeed.Value+"%";
Config.NotificationScrollSpeed = trackBarScrollSpeed.Value;
}
}
@ -244,7 +244,7 @@ private void comboBoxDisplay_SelectedValueChanged(object sender, EventArgs e){
}
private void trackBarEdgeDistance_ValueChanged(object sender, EventArgs e){
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value.ToString(CultureInfo.InvariantCulture)+" px";
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value+" px";
Config.NotificationEdgeDistance = trackBarEdgeDistance.Value;
notification.ShowNotificationForSettings(false);
}

View File

@ -77,6 +77,7 @@ private void InitializeComponent() {
this.tbCustomSound.Name = "tbCustomSound";
this.tbCustomSound.Size = new System.Drawing.Size(316, 20);
this.tbCustomSound.TabIndex = 0;
this.toolTip.SetToolTip(this.tbCustomSound, "When empty, the default TweetDeck sound notification is used.");
//
// labelSoundNotification
//

View File

@ -3,7 +3,7 @@
using System.IO;
using System.Windows.Forms;
using TweetDuck.Core.Notification;
using TweetLib.Audio.Utils;
using TweetLib.Audio;
namespace TweetDuck.Core.Other.Settings{
partial class TabSettingsSounds : BaseTabSettings{

View File

@ -3,23 +3,23 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;
using System.Windows.Forms;
namespace TweetDuck.Core.Utils{
static class BrowserUtils{
public const string TweetDeckURL = "https://tweetdeck.twitter.com";
public static string HeaderAcceptLanguage{
get{
string culture = CultureInfo.CurrentCulture.Name;
string culture = Program.Culture.Name;
if (culture == "en"){
return "en-us,en";
}
else{
return culture.ToLowerInvariant()+",en;q=0.9";
return culture.ToLower()+",en;q=0.9";
}
}
}
@ -80,12 +80,8 @@ public static string GetFileNameFromUrl(string url){
return string.IsNullOrEmpty(file) ? null : file;
}
public static string ConvertPascalCaseToScreamingSnakeCase(string str){
return Regex.Replace(str, @"(\p{Ll})(\P{Ll})|(\P{Ll})(\P{Ll}\p{Ll})", "$1$3_$2$4").ToUpperInvariant();
}
public static string GetErrorName(CefErrorCode code){
return ConvertPascalCaseToScreamingSnakeCase(Enum.GetName(typeof(CefErrorCode), code) ?? string.Empty);
return StringUtils.ConvertPascalCaseToScreamingSnakeCase(Enum.GetName(typeof(CefErrorCode), code) ?? string.Empty);
}
public static WebClient DownloadFileAsync(string url, string target, Action onSuccess, Action<Exception> onFailure){

View File

@ -1,5 +1,6 @@
using System;
using System.Text.RegularExpressions;
using TweetDuck.Data;
namespace TweetDuck.Core.Utils{
static class CommandLineArgsParser{

View File

@ -67,10 +67,10 @@ private struct MSLLHOOKSTRUCT{
private static extern bool BitBlt(IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, uint dwRop);
[DllImport("user32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, int wParam, IntPtr lParam);
public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, IntPtr lParam);
public static extern bool PostMessage(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern uint RegisterWindowMessage(string messageName);

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

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

View File

@ -1,9 +1,10 @@
using System;
using System.IO;
using System.Text;
using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Other.Settings.Export{
class CombinedFileStream : IDisposable{
namespace TweetDuck.Data{
sealed class CombinedFileStream : IDisposable{
public const char KeySeparator = '|';
private readonly Stream stream;
@ -79,8 +80,7 @@ public string SkipFile(){
stream.Position += BitConverter.ToInt32(contentLength, 0);
string keyName = Encoding.UTF8.GetString(name);
int separatorIndex = keyName.IndexOf(KeySeparator);
return separatorIndex == -1 ? keyName : keyName.Substring(0, separatorIndex);
return StringUtils.ExtractBefore(keyName, KeySeparator);
}
public void Flush(){
@ -96,8 +96,7 @@ public class Entry{
public string KeyName{
get{
int index = Identifier.IndexOf(KeySeparator);
return index == -1 ? Identifier : Identifier.Substring(0, index);
return StringUtils.ExtractBefore(Identifier, KeySeparator);
}
}

View File

@ -1,8 +1,8 @@
using System.Collections.Generic;
using System.Text;
namespace TweetDuck.Core.Utils{
class CommandLineArgs{
namespace TweetDuck.Data{
sealed class CommandLineArgs{
public static CommandLineArgs FromStringArray(char entryChar, string[] array){
CommandLineArgs args = new CommandLineArgs();
ReadStringArray(entryChar, array, args);
@ -38,31 +38,31 @@ public static void ReadStringArray(char entryChar, string[] array, CommandLineAr
public int Count => flags.Count+values.Count;
public void AddFlag(string flag){
flags.Add(flag.ToLowerInvariant());
flags.Add(flag.ToLower());
}
public bool HasFlag(string flag){
return flags.Contains(flag.ToLowerInvariant());
return flags.Contains(flag.ToLower());
}
public void RemoveFlag(string flag){
flags.Remove(flag.ToLowerInvariant());
flags.Remove(flag.ToLower());
}
public void SetValue(string key, string value){
values[key.ToLowerInvariant()] = value;
values[key.ToLower()] = value;
}
public bool HasValue(string key){
return values.ContainsKey(key.ToLowerInvariant());
return values.ContainsKey(key.ToLower());
}
public string GetValue(string key, string defaultValue){
return values.TryGetValue(key.ToLowerInvariant(), out string val) ? val : defaultValue;
return values.TryGetValue(key.ToLower(), out string val) ? val : defaultValue;
}
public void RemoveValue(string key){
values.Remove(key.ToLowerInvariant());
values.Remove(key.ToLower());
}
public CommandLineArgs Clone(){

View File

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

View File

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

View File

@ -0,0 +1,5 @@
namespace TweetDuck.Data.Serialization{
interface ISerializedObject{
bool OnReadUnknownProperty(string property, string value);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@
using TweetDuck.Plugins.Enums;
namespace TweetDuck.Plugins{
class Plugin{
sealed class Plugin{
public string Identifier { get; }
public PluginGroup Group { get; }
public PluginEnvironment Environments { get; private set; }
@ -132,8 +132,7 @@ public override int GetHashCode(){
}
public override bool Equals(object obj){
Plugin plugin = obj as Plugin;
return plugin != null && plugin.Identifier.Equals(Identifier);
return obj is Plugin plugin && plugin.Identifier.Equals(Identifier);
}
public static Plugin CreateFromFolder(string path, PluginGroup group, out string error){
@ -184,7 +183,7 @@ private static bool LoadMetadata(string path, Plugin plugin, out string error){
plugin.metadata[currentTag] = currentContents;
}
currentTag = line.Substring(1, line.Length-2).ToUpperInvariant();
currentTag = line.Substring(1, line.Length-2).ToUpper();
currentContents = "";
if (line.Equals(endTag[0])){

View File

@ -2,12 +2,12 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Plugins.Enums;
using TweetDuck.Plugins.Events;
namespace TweetDuck.Plugins{
class PluginBridge{
sealed class PluginBridge{
private static string SanitizeCacheKey(string key){
return key.Replace('\\', '/').Trim();
}
@ -47,9 +47,9 @@ private string GetFullPathOrThrow(int token, PluginFolder folder, string path){
if (fullPath.Length == 0){
switch(folder){
case PluginFolder.Data: throw new Exception("File path has to be relative to the plugin data folder.");
case PluginFolder.Root: throw new Exception("File path has to be relative to the plugin root folder.");
default: throw new Exception("Invalid folder type "+folder+", this is a "+Program.BrandName+" error.");
case PluginFolder.Data: throw new ArgumentException("File path has to be relative to the plugin data folder.");
case PluginFolder.Root: throw new ArgumentException("File path has to be relative to the plugin root folder.");
default: throw new ArgumentException("Invalid folder type "+folder+", this is a "+Program.BrandName+" error.");
}
}
else{
@ -67,9 +67,9 @@ private string ReadFileUnsafe(int token, string cacheKey, string fullPath, bool
try{
return fileCache[token, cacheKey] = File.ReadAllText(fullPath, Encoding.UTF8);
}catch(FileNotFoundException){
throw new Exception("File not found.");
throw new FileNotFoundException("File not found.");
}catch(DirectoryNotFoundException){
throw new Exception("Directory not found.");
throw new DirectoryNotFoundException("Directory not found.");
}
}

View File

@ -28,8 +28,7 @@ public bool IsEnabled(Plugin plugin){
public void Load(string file){
try{
using(FileStream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))
using(StreamReader reader = new StreamReader(stream, Encoding.UTF8)){
using(StreamReader reader = new StreamReader(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read), Encoding.UTF8)){
string line = reader.ReadLine();
if (line == "#Disabled"){
@ -49,8 +48,7 @@ public void Load(string file){
public void Save(string file){
try{
using(FileStream stream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))
using(StreamWriter writer = new StreamWriter(stream, Encoding.UTF8)){
using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None), Encoding.UTF8)){
writer.WriteLine("#Disabled");
foreach(string disabled in Disabled){

View File

@ -1,5 +1,4 @@
using System.Globalization;
using TweetDuck.Plugins.Enums;
using TweetDuck.Plugins.Enums;
namespace TweetDuck.Plugins{
static class PluginScriptGenerator{
@ -11,7 +10,7 @@ public static string GeneratePlugin(string pluginIdentifier, string pluginConten
return PluginGen
.Replace("%params", environment.GetScriptVariables())
.Replace("%id", pluginIdentifier)
.Replace("%token", pluginToken.ToString(CultureInfo.InvariantCulture))
.Replace("%token", pluginToken.ToString())
.Replace("%contents", pluginContents);
}
@ -19,7 +18,7 @@ public static string GeneratePlugin(string pluginIdentifier, string pluginConten
/* PluginGen
(function(%params, $i, $d){
(function(%params, $d){
let tmp = {
id: '%id',
obj: new class extends PluginBase{%contents}

View File

@ -1,6 +1,7 @@
using CefSharp;
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
@ -11,6 +12,7 @@
using TweetDuck.Core.Other;
using TweetDuck.Core.Other.Settings.Export;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Plugins;
using TweetDuck.Plugins.Events;
using TweetDuck.Updates;
@ -50,15 +52,25 @@ static class Program{
public static UserConfig UserConfig { get; private set; }
public static SystemConfig SystemConfig { get; private set; }
public static Reporter Reporter { get; private set; }
public static Reporter Reporter { get; }
public static CultureInfo Culture { get; }
public static event EventHandler UserConfigReplaced;
static Program(){
Culture = CultureInfo.CurrentCulture;
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
Reporter = new Reporter(ErrorLogFilePath);
Reporter.SetupUnhandledExceptionHandler(BrandName+" Has Failed :(");
}
[STAThread]
private static void Main(){
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore");
SubProcessMessage = NativeMethods.RegisterWindowMessage("TweetDuckSubProcess");
@ -66,10 +78,7 @@ private static void Main(){
MessageBox.Show(BrandName+" does not have write permissions to the storage folder: "+StoragePath, "Permission Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
Reporter = new Reporter(ErrorLogFilePath);
Reporter.SetupUnhandledExceptionHandler(BrandName+" Has Failed :(");
if (Arguments.HasFlag(Arguments.ArgRestart)){
for(int attempt = 0; attempt < 21; attempt++){
LockManager.Result lockResult = LockManager.Lock();
@ -102,7 +111,7 @@ private static void Main(){
if (lockResult == LockManager.Result.HasProcess){
if (LockManager.LockingProcess.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, WindowRestoreMessage, LockManager.LockingProcess.Id, IntPtr.Zero);
NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, WindowRestoreMessage, new UIntPtr((uint)LockManager.LockingProcess.Id), IntPtr.Zero);
if (WindowsUtils.TrySleepUntil(() => {
LockManager.LockingProcess.Refresh();

View File

@ -1,14 +1,13 @@
using System;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Text;
using System.Windows.Forms;
using TweetDuck.Core.Other;
namespace TweetDuck{
class Reporter{
sealed class Reporter{
private readonly string logFile;
public Reporter(string logFile){
@ -17,9 +16,7 @@ public Reporter(string logFile){
public void SetupUnhandledExceptionHandler(string caption){
AppDomain.CurrentDomain.UnhandledException += (sender, args) => {
Exception ex = args.ExceptionObject as Exception;
if (ex != null){
if (args.ExceptionObject is Exception ex) {
HandleException(caption, "An unhandled exception has occurred.", false, ex);
}
};
@ -32,7 +29,7 @@ public bool Log(string data){
build.Append("Please, report all issues to: https://github.com/chylex/TweetDuck/issues\r\n\r\n");
}
build.Append("[").Append(DateTime.Now.ToString("G", CultureInfo.CurrentCulture)).Append("]\r\n");
build.Append("[").Append(DateTime.Now.ToString("G", Program.Culture)).Append("]\r\n");
build.Append(data).Append("\r\n\r\n");
try{
@ -87,9 +84,10 @@ public static void HandleEarlyFailure(string caption, string message){
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
FormMessage form = new FormMessage(caption, message, MessageBoxIcon.Error);
form.ActiveControl = form.AddButton("Exit");
form.ShowDialog();
using(FormMessage form = new FormMessage(caption, message, MessageBoxIcon.Error)){
form.ActiveControl = form.AddButton("Exit");
form.ShowDialog();
}
try{
Process.GetCurrentProcess().Kill();

View File

@ -498,7 +498,7 @@
var tryCloseModal1 = function(){
var modal = $("#open-modal");
return modal.is(":visible") && tryClickSelector("a[rel=dismiss]", modal);
return modal.is(":visible") && tryClickSelector("a.mdl-dismiss", modal);
};
var tryCloseModal2 = function(){

View File

@ -73,6 +73,7 @@
<Compile Include="Configuration\LockManager.cs" />
<Compile Include="Configuration\SystemConfig.cs" />
<Compile Include="Configuration\UserConfig.cs" />
<Compile Include="Configuration\UserConfigLegacy.cs" />
<Compile Include="Core\Bridge\PropertyBridge.cs" />
<Compile Include="Core\Controls\ControlExtensions.cs" />
<Compile Include="Core\Controls\FlatButton.cs">
@ -164,7 +165,8 @@
<Compile Include="Core\Other\Settings\Dialogs\DialogSettingsRestart.Designer.cs">
<DependentUpon>DialogSettingsRestart.cs</DependentUpon>
</Compile>
<Compile Include="Core\Other\Settings\Export\CombinedFileStream.cs" />
<Compile Include="Core\Utils\StringUtils.cs" />
<Compile Include="Data\CombinedFileStream.cs" />
<Compile Include="Core\Other\Settings\Export\ExportFileFlags.cs" />
<Compile Include="Core\Other\Settings\Export\ExportManager.cs" />
<Compile Include="Core\Other\Settings\TabSettingsAdvanced.cs">
@ -195,17 +197,20 @@
<DependentUpon>TabSettingsNotifications.cs</DependentUpon>
</Compile>
<Compile Include="Core\Bridge\CallbackBridge.cs" />
<Compile Include="Core\Utils\BrowserProcesses.cs" />
<Compile Include="Core\Utils\CommandLineArgs.cs" />
<Compile Include="Data\CommandLineArgs.cs" />
<Compile Include="Core\Utils\CommandLineArgsParser.cs" />
<Compile Include="Core\Notification\Screenshot\FormNotificationScreenshotable.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" />
<Compile Include="Core\Utils\InjectedHTML.cs" />
<Compile Include="Data\Serialization\FileSerializer.cs" />
<Compile Include="Data\InjectedHTML.cs" />
<Compile Include="Data\Serialization\ISerializedObject.cs" />
<Compile Include="Data\Serialization\ITypeConverter.cs" />
<Compile Include="Data\Serialization\SingleTypeConverter.cs" />
<Compile Include="Data\TwoKeyDictionary.cs" />
<Compile Include="Data\WindowState.cs" />
<Compile Include="Core\Utils\MemoryUsageTracker.cs" />
<Compile Include="Core\Utils\TwoKeyDictionary.cs" />
<Compile Include="Core\Utils\WindowState.cs" />
<Compile Include="Core\Utils\WindowsUtils.cs" />
<Compile Include="Core\Bridge\TweetDeckBridge.cs" />
<Compile Include="Core\Other\FormSettings.cs">

View File

@ -4,7 +4,7 @@
using TweetDuck.Core.Utils;
namespace TweetDuck.Updates{
class UpdateInfo{
sealed class UpdateInfo{
public string VersionTag { get; }
public string InstallerPath { get; }

View File

@ -1,5 +1,5 @@
namespace TweetDuck.Updates{
class UpdaterSettings{
sealed class UpdaterSettings{
public bool AllowPreReleases { get; set; }
public string DismissedUpdate { get; set; }
public string InstallerDownloadFolder { get; set; }

View File

@ -1,7 +1,6 @@
using System;
using System.Runtime.InteropServices;
using TweetLib.Audio.Impl;
using TweetLib.Audio.Utils;
namespace TweetLib.Audio{
public abstract class AudioPlayer : IDisposable{
@ -32,6 +31,11 @@ public static AudioPlayer New(){
public abstract void Play(string file);
public abstract void Stop();
public abstract void Dispose();
protected abstract void Dispose(bool disposing);
public void Dispose(){
Dispose(true);
GC.SuppressFinalize(this);
}
}
}

View File

@ -1,7 +1,6 @@
using System;
using System.IO;
using System.Media;
using TweetLib.Audio.Utils;
namespace TweetLib.Audio.Impl{
sealed class SoundPlayerImplFallback : AudioPlayer{
@ -39,7 +38,7 @@ public override void Stop(){
player.Stop();
}
public override void Dispose(){
protected override void Dispose(bool disposing){
player.Dispose();
}

View File

@ -56,7 +56,7 @@ public override void Stop(){
}
}
public override void Dispose(){
protected override void Dispose(bool disposing){
player.close();
Marshal.ReleaseComObject(player);
}

View File

@ -1,6 +1,6 @@
using System;
namespace TweetLib.Audio.Utils{
namespace TweetLib.Audio{
public sealed class PlaybackErrorEventArgs : EventArgs{
public string Message { get; }
public bool Ignore { get; set; }

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
@ -30,7 +30,7 @@
<ItemGroup>
<Compile Include="AudioPlayer.cs" />
<Compile Include="Utils\NativeCoreAudio.cs" />
<Compile Include="Utils\PlaybackErrorEventArgs.cs" />
<Compile Include="PlaybackErrorEventArgs.cs" />
<Compile Include="Impl\SoundPlayerImplFallback.cs" />
<Compile Include="Impl\SoundPlayerImplWMP.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />

View File

@ -82,9 +82,7 @@ private static ISimpleAudioVolume GetVolumeObject(string name){
ISimpleAudioVolume volumeObj = null;
for(int index = sessions.GetCount()-1; index >= 0; index--){
IAudioSessionControl2 ctl = sessions.GetSession(index) as IAudioSessionControl2;
if (ctl != null){
if (sessions.GetSession(index) is IAudioSessionControl2 ctl){
string identifier = ctl.GetSessionIdentifier();
if (identifier != null && identifier.Contains(name)){

View File

@ -1,7 +1,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TweetDuck.Core.Utils;
namespace UnitTests.Core.Utils{
namespace UnitTests.Core{
[TestClass]
public class TestBrowserUtils{
[TestMethod]
@ -46,15 +46,5 @@ public void TestGetFileNameFromUrl(){
Assert.IsNull(BrowserUtils.GetFileNameFromUrl("http://test.com/"));
}
[TestMethod]
public void TestConvertPascalCaseToScreamingSnakeCase(){
Assert.AreEqual("HELP", BrowserUtils.ConvertPascalCaseToScreamingSnakeCase("Help"));
Assert.AreEqual("HELP_ME", BrowserUtils.ConvertPascalCaseToScreamingSnakeCase("HelpMe"));
Assert.AreEqual("HELP_ME_PLEASE", BrowserUtils.ConvertPascalCaseToScreamingSnakeCase("HelpMePlease"));
Assert.AreEqual("HTML_CODE", BrowserUtils.ConvertPascalCaseToScreamingSnakeCase("HTMLCode"));
Assert.AreEqual("CHECK_OUT_MY_HTML_CODE", BrowserUtils.ConvertPascalCaseToScreamingSnakeCase("CheckOutMyHTMLCode"));
}
}
}

View File

@ -1,7 +1,8 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
namespace UnitTests.Core.Utils{
namespace UnitTests.Core{
[TestClass]
public class TestCommandLineArgsParser{
[TestMethod]

View File

@ -0,0 +1,36 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TweetDuck.Core.Utils;
namespace UnitTests.Core{
[TestClass]
public class TestStringUtils{
[TestMethod]
public void TestExtractBefore(){
Assert.AreEqual("missing", StringUtils.ExtractBefore("missing", '_'));
Assert.AreEqual("", StringUtils.ExtractBefore("_empty", '_'));
Assert.AreEqual("some", StringUtils.ExtractBefore("some_text", '_'));
Assert.AreEqual("first", StringUtils.ExtractBefore("first_separator_only", '_'));
Assert.AreEqual("start_index", StringUtils.ExtractBefore("start_index_test", '_', 8));
}
[TestMethod]
public void TestParseInts(){
CollectionAssert.AreEqual(new int[0], StringUtils.ParseInts("", ','));
CollectionAssert.AreEqual(new int[]{ 1 }, StringUtils.ParseInts("1", ','));
CollectionAssert.AreEqual(new int[]{ 1, 2, 3 }, StringUtils.ParseInts("1,2,3", ','));
CollectionAssert.AreEqual(new int[]{ 1, 2, 3 }, StringUtils.ParseInts("1,2,3,", ','));
CollectionAssert.AreEqual(new int[]{ 1, 2, 3 }, StringUtils.ParseInts(",1,2,,3,", ','));
CollectionAssert.AreEqual(new int[]{ -50, 50 }, StringUtils.ParseInts("-50,50", ','));
}
[TestMethod]
public void TestConvertPascalCaseToScreamingSnakeCase(){
Assert.AreEqual("HELP", StringUtils.ConvertPascalCaseToScreamingSnakeCase("Help"));
Assert.AreEqual("HELP_ME", StringUtils.ConvertPascalCaseToScreamingSnakeCase("HelpMe"));
Assert.AreEqual("HELP_ME_PLEASE", StringUtils.ConvertPascalCaseToScreamingSnakeCase("HelpMePlease"));
Assert.AreEqual("HTML_CODE", StringUtils.ConvertPascalCaseToScreamingSnakeCase("HTMLCode"));
Assert.AreEqual("CHECK_OUT_MY_HTML_CODE", StringUtils.ConvertPascalCaseToScreamingSnakeCase("CheckOutMyHTMLCode"));
}
}
}

View File

@ -1,9 +1,9 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System;
using System.IO;
using TweetDuck.Core.Other.Settings.Export;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TweetDuck.Data;
namespace UnitTests.Core.Settings{
namespace UnitTests.Data{
[TestClass]
public class TestCombinedFileStream{
[TestMethod]

View File

@ -1,8 +1,8 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using TweetDuck.Core.Utils;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TweetDuck.Data;
namespace UnitTests.Core.Utils{
namespace UnitTests.Data{
[TestClass]
public class TestCommandLineArgs{
[TestMethod]

View File

@ -0,0 +1,54 @@
using System;
using System.IO;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TweetDuck.Data.Serialization;
namespace UnitTests.Data{
[TestClass]
public class TestFileSerializer{
private enum TestEnum{
A, B, C, D, E
}
private class SerializationTestBasic : ISerializedObject{
public bool TestBool { get; set; }
public int TestInt { get; set; }
public string TestString { get; set; }
public string TestStringNull { get; set; }
public TestEnum TestEnum { get; set; }
bool ISerializedObject.OnReadUnknownProperty(string property, string value){
return false;
}
}
[TestMethod]
public void TestBasicWriteRead(){
FileSerializer<SerializationTestBasic> serializer = new FileSerializer<SerializationTestBasic>();
SerializationTestBasic write = new SerializationTestBasic{
TestBool = true,
TestInt = -100,
TestString = "abc"+Environment.NewLine+"def",
TestStringNull = null,
TestEnum = TestEnum.D
};
serializer.Write("serialized_basic", write);
Assert.IsTrue(File.Exists("serialized_basic"));
TestUtils.DeleteFileOnExit("serialized_basic");
SerializationTestBasic read = new SerializationTestBasic();
serializer.Read("serialized_basic", read);
Assert.IsTrue(read.TestBool);
Assert.AreEqual(-100, read.TestInt);
Assert.AreEqual("abc"+Environment.NewLine+"def", read.TestString);
Assert.IsNull(read.TestStringNull);
Assert.AreEqual(TestEnum.D, read.TestEnum);
}
// TODO more complex tests
}
}

View File

@ -2,9 +2,9 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
namespace UnitTests.Core.Utils{
namespace UnitTests.Data{
[TestClass]
public class TestInjectedHTML{
private static IEnumerable<InjectedHTML.Position> Positions => Enum.GetValues(typeof(InjectedHTML.Position)).Cast<InjectedHTML.Position>();

View File

@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TweetDuck.Core.Utils;
using System.Collections.Generic;
using TweetDuck.Data;
namespace UnitTests.Core.Utils{
namespace UnitTests.Data{
[TestClass]
public class TestTwoKeyDictionary{
private static TwoKeyDictionary<string, int, string> CreateDict(){

View File

@ -47,12 +47,14 @@
</Otherwise>
</Choose>
<ItemGroup>
<Compile Include="Core\Settings\TestCombinedFileStream.cs" />
<Compile Include="Core\Utils\TestBrowserUtils.cs" />
<Compile Include="Core\Utils\TestCommandLineArgs.cs" />
<Compile Include="Core\Utils\TestCommandLineArgsParser.cs" />
<Compile Include="Core\Utils\TestInjectedHTML.cs" />
<Compile Include="Core\Utils\TestTwoKeyDictionary.cs" />
<Compile Include="Core\TestStringUtils.cs" />
<Compile Include="Data\TestCombinedFileStream.cs" />
<Compile Include="Core\TestBrowserUtils.cs" />
<Compile Include="Data\TestCommandLineArgs.cs" />
<Compile Include="Core\TestCommandLineArgsParser.cs" />
<Compile Include="Data\TestFileSerializer.cs" />
<Compile Include="Data\TestInjectedHTML.cs" />
<Compile Include="Data\TestTwoKeyDictionary.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TestUtils.cs" />
</ItemGroup>