mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-09-14 19:32:10 +02:00
Compare commits
67 Commits
Author | SHA1 | Date | |
---|---|---|---|
34726c533e | |||
4a0d72d2cc | |||
fe3fc5c9f7 | |||
441228e2b0 | |||
7538aee4f2 | |||
acf809268e | |||
4ebc0c10b6 | |||
a453888ca2 | |||
530b44762b | |||
f85587fb0b | |||
edb8799b1a | |||
e47aeb37f0 | |||
776e9968dc | |||
1898bf4731 | |||
78df020737 | |||
b93f9a4b9a | |||
748b230ef5 | |||
deb8dde9e1 | |||
dbb2f10754 | |||
0ded03ab92 | |||
2198e84f3b | |||
14d44528b0 | |||
eb8159ca0f | |||
9811f40a53 | |||
8de7e13aa3 | |||
c63e6a1e49 | |||
5a21d2cb10 | |||
424c0e596c | |||
d431b63c27 | |||
38c2781cd3 | |||
796fb348a3 | |||
71b306d5fd | |||
4c610ea32d | |||
4bff006743 | |||
1645079bc0 | |||
9afb58e4a7 | |||
2820fc8acf | |||
4d77a498f6 | |||
d77de3bb12 | |||
29e7ad6ce6 | |||
1712b5120e | |||
06c0153cf5 | |||
44f7ecda6d | |||
fb94bf1b80 | |||
4818652582 | |||
c69b9784fc | |||
0ac244a3ea | |||
19a445fdab | |||
c90a18a2c0 | |||
502310c413 | |||
6f9424d4ec | |||
bb379fe667 | |||
0fd86bf214 | |||
29b75d4391 | |||
a7124e5449 | |||
a714f3480a | |||
c10e0df898 | |||
fba734fd5a | |||
27e2372097 | |||
7f5b99495c | |||
1efe2a56af | |||
850873aec8 | |||
d9e6afbf36 | |||
7f3bd2715c | |||
c81cb393e9 | |||
4800faa783 | |||
1087b5e1d1 |
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data;
|
||||
|
||||
namespace TweetDuck.Configuration{
|
||||
static class Arguments{
|
||||
|
@@ -21,7 +21,7 @@ namespace TweetDuck.Configuration{
|
||||
|
||||
private void CreateLockFileStream(){
|
||||
lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
WriteIntToStream(lockStream, GetCurrentProcessId());
|
||||
WriteIntToStream(lockStream, WindowsUtils.CurrentProcessID);
|
||||
lockStream.Flush(true);
|
||||
}
|
||||
|
||||
@@ -166,11 +166,5 @@ namespace TweetDuck.Configuration{
|
||||
stream.Read(bytes, 0, 4);
|
||||
return BitConverter.ToInt32(bytes, 0);
|
||||
}
|
||||
|
||||
private static int GetCurrentProcessId(){
|
||||
using(Process process = Process.GetCurrentProcess()){
|
||||
return process.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Configuration{
|
||||
sealed class SystemConfig{
|
||||
@@ -31,10 +32,7 @@ namespace TweetDuck.Configuration{
|
||||
|
||||
public bool Save(){
|
||||
try{
|
||||
string directory = Path.GetDirectoryName(file);
|
||||
if (directory == null)return false;
|
||||
|
||||
Directory.CreateDirectory(directory);
|
||||
WindowsUtils.CreateDirectoryForFile(file);
|
||||
|
||||
using(Stream stream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None)){
|
||||
WriteToStream(stream);
|
||||
|
@@ -1,206 +1,145 @@
|
||||
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]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private const int CurrentFileVersion = 10;
|
||||
|
||||
// START OF CONFIGURATION
|
||||
|
||||
public WindowState BrowserWindow { get; set; }
|
||||
public bool DisplayNotificationColumn { get; set; }
|
||||
public bool DisplayNotificationTimer { get; set; }
|
||||
public bool NotificationTimerCountDown { get; set; }
|
||||
public bool NotificationSkipOnLinkClick { get; set; }
|
||||
public bool NotificationNonIntrusiveMode { get; set; }
|
||||
|
||||
public TweetNotification.Position NotificationPosition { get; set; }
|
||||
public Point CustomNotificationPosition { get; set; }
|
||||
public int NotificationEdgeDistance { get; set; }
|
||||
public int NotificationDisplay { get; set; }
|
||||
public int NotificationIdlePauseSeconds { get; set; }
|
||||
public int NotificationDurationValue { get; set; }
|
||||
public int NotificationScrollSpeed { 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 WindowState PluginsWindow { get; set; }
|
||||
// CONFIGURATION DATA
|
||||
|
||||
public string CustomCefArgs { get; set; }
|
||||
public string CustomBrowserCSS { get; set; }
|
||||
public string CustomNotificationCSS { get; set; }
|
||||
public WindowState BrowserWindow { get; set; } = new WindowState();
|
||||
public WindowState PluginsWindow { get; set; } = new WindowState();
|
||||
|
||||
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 bool EnableUpdateCheck { get; set; } = true;
|
||||
public string DismissedUpdate { get; set; } = null;
|
||||
|
||||
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 bool DisplayNotificationTimer { get; set; } = true;
|
||||
public bool NotificationTimerCountDown { get; set; } = false;
|
||||
public int NotificationDurationValue { get; set; } = 25;
|
||||
|
||||
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; } = TweetNotification.Size.Auto;
|
||||
public Size CustomNotificationSize { get; set; } = Size.Empty;
|
||||
public int NotificationScrollSpeed { get; set; } = 10;
|
||||
|
||||
private string _notificationSoundPath;
|
||||
|
||||
public string CustomCefArgs { get; set; } = null;
|
||||
public string CustomBrowserCSS { get; set; } = null;
|
||||
public string CustomNotificationCSS { get; set; } = null;
|
||||
|
||||
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;
|
||||
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 == 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;
|
||||
}
|
||||
|
||||
// update the version
|
||||
fileVersion = CurrentFileVersion;
|
||||
Save();
|
||||
bool ISerializedObject.OnReadUnknownProperty(string property, string value){
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Save(){
|
||||
try{
|
||||
string directory = Path.GetDirectoryName(file);
|
||||
if (directory == null)return false;
|
||||
|
||||
Directory.CreateDirectory(directory);
|
||||
WindowsUtils.CreateDirectoryForFile(file);
|
||||
|
||||
if (File.Exists(file)){
|
||||
string backupFile = GetBackupFile(file);
|
||||
@@ -208,10 +147,7 @@ namespace TweetDuck.Configuration{
|
||||
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);
|
||||
@@ -220,22 +156,20 @@ namespace TweetDuck.Configuration{
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -247,11 +181,11 @@ namespace TweetDuck.Configuration{
|
||||
}
|
||||
}
|
||||
|
||||
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){
|
||||
|
210
Configuration/UserConfigLegacy.cs
Normal file
210
Configuration/UserConfigLegacy.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Notification;
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Bridge{
|
||||
@@ -101,7 +102,7 @@ namespace TweetDuck.Core.Bridge{
|
||||
default: icon = MessageBoxIcon.None; break;
|
||||
}
|
||||
|
||||
MessageBox.Show(contents, Program.BrandName+" Browser Message", MessageBoxButtons.OK, icon);
|
||||
FormMessage.Show("TweetDuck Browser Message", contents, icon, FormMessage.OK);
|
||||
}
|
||||
|
||||
public void CrashDebug(string message){
|
||||
|
@@ -21,6 +21,12 @@ namespace TweetDuck.Core.Controls{
|
||||
control.BeginInvoke(func);
|
||||
}
|
||||
|
||||
public static float GetDPIScale(this Control control){
|
||||
using(Graphics graphics = control.CreateGraphics()){
|
||||
return graphics.DpiY/96F;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsFullyOutsideView(this Form form){
|
||||
return !Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(form.Bounds));
|
||||
}
|
||||
@@ -41,6 +47,12 @@ namespace TweetDuck.Core.Controls{
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetValueSafe(this NumericUpDown numUpDown, int value){
|
||||
if (value >= numUpDown.Minimum && value <= numUpDown.Maximum){
|
||||
numUpDown.Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetValueSafe(this TrackBar trackBar, int value){
|
||||
if (value >= trackBar.Minimum && value <= trackBar.Maximum){
|
||||
trackBar.Value = value;
|
||||
@@ -58,7 +70,7 @@ namespace TweetDuck.Core.Controls{
|
||||
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){
|
||||
|
18
Core/Controls/NumericUpDownEx.cs
Normal file
18
Core/Controls/NumericUpDownEx.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TweetDuck.Core.Controls{
|
||||
sealed class NumericUpDownEx : NumericUpDown{
|
||||
public string TextSuffix { get; set ; }
|
||||
|
||||
protected override void UpdateEditText(){
|
||||
base.UpdateEditText();
|
||||
|
||||
if (LicenseManager.UsageMode != LicenseUsageMode.Designtime){
|
||||
ChangingText = true;
|
||||
Text += TextSuffix;
|
||||
ChangingText = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
8
Core/FormBrowser.Designer.cs
generated
8
Core/FormBrowser.Designer.cs
generated
@@ -26,8 +26,14 @@
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.trayIcon = new TweetDuck.Core.TrayIcon(this.components);
|
||||
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
||||
this.timerResize = new System.Windows.Forms.Timer(this.components);
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// timerResize
|
||||
//
|
||||
this.timerResize.Interval = 500;
|
||||
this.timerResize.Tick += new System.EventHandler(this.timerResize_Tick);
|
||||
//
|
||||
// FormBrowser
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
@@ -42,6 +48,7 @@
|
||||
this.Activated += new System.EventHandler(this.FormBrowser_Activated);
|
||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormBrowser_FormClosing);
|
||||
this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.FormBrowser_FormClosed);
|
||||
this.LocationChanged += new System.EventHandler(this.FormBrowser_LocationChanged);
|
||||
this.ResizeEnd += new System.EventHandler(this.FormBrowser_ResizeEnd);
|
||||
this.Resize += new System.EventHandler(this.FormBrowser_Resize);
|
||||
this.ResumeLayout(false);
|
||||
@@ -52,6 +59,7 @@
|
||||
|
||||
private TrayIcon trayIcon;
|
||||
private System.Windows.Forms.ToolTip toolTip;
|
||||
private System.Windows.Forms.Timer timerResize;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,6 @@
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
@@ -20,7 +19,7 @@ using TweetDuck.Plugins.Events;
|
||||
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{
|
||||
@@ -33,6 +32,7 @@ namespace TweetDuck.Core{
|
||||
private readonly UpdateHandler updates;
|
||||
private readonly FormNotificationTweet notification;
|
||||
private readonly ContextMenu contextMenu;
|
||||
private readonly MemoryUsageTracker memoryUsageTracker;
|
||||
|
||||
private bool isLoaded;
|
||||
private bool isBrowserReady;
|
||||
@@ -51,6 +51,7 @@ namespace TweetDuck.Core{
|
||||
this.plugins.PluginChangedState += plugins_PluginChangedState;
|
||||
|
||||
this.contextMenu = ContextMenuBrowser.CreateMenu(this);
|
||||
this.memoryUsageTracker = new MemoryUsageTracker("TDGF_tryRunCleanup");
|
||||
|
||||
this.notification = new FormNotificationTweet(this, plugins){
|
||||
#if DEBUG
|
||||
@@ -88,6 +89,8 @@ namespace TweetDuck.Core{
|
||||
Controls.Add(new MenuStrip{ Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update
|
||||
|
||||
Disposed += (sender, args) => {
|
||||
memoryUsageTracker.Dispose();
|
||||
|
||||
browser.Dispose();
|
||||
contextMenu.Dispose();
|
||||
|
||||
@@ -104,7 +107,7 @@ namespace TweetDuck.Core{
|
||||
Config.MuteToggled += Config_MuteToggled;
|
||||
Config.ZoomLevelChanged += Config_ZoomLevelChanged;
|
||||
|
||||
this.updates = new UpdateHandler(browser, this, updaterSettings);
|
||||
this.updates = new UpdateHandler(browser, updaterSettings);
|
||||
this.updates.UpdateAccepted += updates_UpdateAccepted;
|
||||
this.updates.UpdateDismissed += updates_UpdateDismissed;
|
||||
|
||||
@@ -166,6 +169,8 @@ namespace TweetDuck.Core{
|
||||
|
||||
private void browser_FrameLoadStart(object sender, FrameLoadStartEventArgs e){
|
||||
if (e.Frame.IsMain){
|
||||
memoryUsageTracker.Stop();
|
||||
|
||||
if (Config.ZoomLevel != 100){
|
||||
BrowserUtils.SetZoomLevel(browser.GetBrowser(), Config.ZoomLevel);
|
||||
}
|
||||
@@ -191,6 +196,10 @@ namespace TweetDuck.Core{
|
||||
}
|
||||
|
||||
TweetDeckBridge.ResetStaticProperties();
|
||||
|
||||
if (Config.EnableBrowserGCReload){
|
||||
memoryUsageTracker.Start(this, e.Browser, Config.BrowserMemoryThreshold);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +208,7 @@ namespace TweetDuck.Core{
|
||||
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){
|
||||
@@ -208,12 +217,23 @@ namespace TweetDuck.Core{
|
||||
}
|
||||
}
|
||||
|
||||
private void timerResize_Tick(object sender, EventArgs e){
|
||||
FormBrowser_ResizeEnd(this, e); // also stops timer
|
||||
}
|
||||
|
||||
private void FormBrowser_Activated(object sender, EventArgs e){
|
||||
if (!isLoaded)return;
|
||||
|
||||
trayIcon.HasNotifications = false;
|
||||
}
|
||||
|
||||
private void FormBrowser_LocationChanged(object sender, EventArgs e){
|
||||
if (!isLoaded)return;
|
||||
|
||||
timerResize.Stop();
|
||||
timerResize.Start();
|
||||
}
|
||||
|
||||
private void FormBrowser_Resize(object sender, EventArgs e){
|
||||
if (!isLoaded)return;
|
||||
|
||||
@@ -229,11 +249,17 @@ namespace TweetDuck.Core{
|
||||
FormBrowser_ResizeEnd(sender, e);
|
||||
}
|
||||
}
|
||||
else{
|
||||
timerResize.Stop();
|
||||
timerResize.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private void FormBrowser_ResizeEnd(object sender, EventArgs e){ // also triggers when the window moves
|
||||
if (!isLoaded)return;
|
||||
|
||||
timerResize.Stop();
|
||||
|
||||
if (Location != ControlExtensions.InvisibleLocation){
|
||||
Config.BrowserWindow.Save(this);
|
||||
Config.Save();
|
||||
@@ -287,31 +313,35 @@ namespace TweetDuck.Core{
|
||||
}
|
||||
|
||||
private void updates_UpdateAccepted(object sender, UpdateAcceptedEventArgs e){
|
||||
foreach(Form form in Application.OpenForms.Cast<Form>().Reverse()){
|
||||
if (form is FormSettings || form is FormPlugins || form is FormAbout){
|
||||
form.Close();
|
||||
this.InvokeAsyncSafe(() => {
|
||||
foreach(Form form in Application.OpenForms.Cast<Form>().Reverse()){
|
||||
if (form is FormSettings || form is FormPlugins || form is FormAbout){
|
||||
form.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updates.BeginUpdateDownload(this, e.UpdateInfo, update => {
|
||||
if (update.DownloadStatus == UpdateDownloadStatus.Done){
|
||||
UpdateInstallerPath = update.InstallerPath;
|
||||
}
|
||||
updates.BeginUpdateDownload(this, e.UpdateInfo, update => {
|
||||
if (update.DownloadStatus == UpdateDownloadStatus.Done){
|
||||
UpdateInstallerPath = update.InstallerPath;
|
||||
}
|
||||
|
||||
ForceClose();
|
||||
ForceClose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void updates_UpdateDismissed(object sender, UpdateDismissedEventArgs e){
|
||||
Config.DismissedUpdate = e.VersionTag;
|
||||
Config.Save();
|
||||
this.InvokeAsyncSafe(() => {
|
||||
Config.DismissedUpdate = e.VersionTag;
|
||||
Config.Save();
|
||||
});
|
||||
}
|
||||
|
||||
private void soundNotification_PlaybackError(object sender, PlaybackErrorEventArgs e){
|
||||
e.Ignore = true;
|
||||
|
||||
using(FormMessage form = new FormMessage("Notification Sound Error", "Could not play custom notification sound."+Environment.NewLine+e.Message, MessageBoxIcon.Error)){
|
||||
form.CancelButton = form.AddButton("Ignore");
|
||||
using(FormMessage form = new FormMessage("Notification Sound Error", "Could not play custom notification sound.\n"+e.Message, MessageBoxIcon.Error)){
|
||||
form.AddButton(FormMessage.Ignore, ControlType.Cancel | ControlType.Focused);
|
||||
|
||||
Button btnOpenSettings = form.AddButton("View Options");
|
||||
btnOpenSettings.Width += 16;
|
||||
@@ -324,16 +354,25 @@ namespace TweetDuck.Core{
|
||||
}
|
||||
|
||||
protected override void WndProc(ref Message m){
|
||||
if (isLoaded && m.Msg == Program.WindowRestoreMessage){
|
||||
using(Process process = Process.GetCurrentProcess()){
|
||||
if (process.Id == m.WParam.ToInt32()){
|
||||
if (isLoaded){
|
||||
if (m.Msg == Program.WindowRestoreMessage){
|
||||
if (WindowsUtils.CurrentProcessID == m.WParam.ToInt32()){
|
||||
trayIcon_ClickRestore(trayIcon, new EventArgs());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else if (m.Msg == Program.SubProcessMessage){
|
||||
int processId = m.WParam.ToInt32();
|
||||
|
||||
return;
|
||||
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){
|
||||
browser.ExecuteScriptAsync("TDGF_onMouseClickExtra", (m.WParam.ToInt32() >> 16) & 0xFFFF);
|
||||
return;
|
||||
@@ -367,7 +406,7 @@ namespace TweetDuck.Core{
|
||||
}
|
||||
|
||||
public void ReloadToTweetDeck(){
|
||||
browser.ExecuteScriptAsync("window.location.href = 'https://tweetdeck.twitter.com'");
|
||||
browser.ExecuteScriptAsync($"gc&&gc();window.location.href='{BrowserUtils.TweetDeckURL}'");
|
||||
}
|
||||
|
||||
// callback handlers
|
||||
@@ -396,7 +435,16 @@ namespace TweetDuck.Core{
|
||||
trayIcon.HasNotifications = false;
|
||||
}
|
||||
|
||||
if (Config.EnableBrowserGCReload){
|
||||
memoryUsageTracker.Start(this, browser.GetBrowser(), Config.BrowserMemoryThreshold);
|
||||
}
|
||||
else{
|
||||
memoryUsageTracker.Stop();
|
||||
}
|
||||
|
||||
UpdateProperties(PropertyBridge.Properties.ExpandLinksOnHover | PropertyBridge.Properties.SwitchAccountSelectors | PropertyBridge.Properties.HasCustomNotificationSound);
|
||||
|
||||
notification.RequiresResize = true;
|
||||
form.Dispose();
|
||||
};
|
||||
|
||||
@@ -437,7 +485,7 @@ namespace TweetDuck.Core{
|
||||
|
||||
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);
|
||||
|
@@ -117,11 +117,13 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
||||
<data name="trayIcon.TrayLocation" type="System.Drawing.Point, System.Drawing">
|
||||
<metadata name="trayIcon.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</data>
|
||||
<data name="toolTip.TrayLocation" type="System.Drawing.Point, System.Drawing">
|
||||
</metadata>
|
||||
<metadata name="toolTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>112, 17</value>
|
||||
</data>
|
||||
</metadata>
|
||||
<metadata name="timerResize.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>202, 17</value>
|
||||
</metadata>
|
||||
</root>
|
@@ -5,6 +5,7 @@ using System.Text.RegularExpressions;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
@@ -80,7 +81,7 @@ namespace TweetDuck.Core.Handling{
|
||||
|
||||
if (saveTarget != null){
|
||||
BrowserUtils.DownloadFileAsync(parameters.SourceUrl, saveTarget, null, ex => {
|
||||
MessageBox.Show("An error occurred while downloading the image: "+ex.Message, Program.BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
FormMessage.Error("Image Download", "An error occurred while downloading the image: "+ex.Message, FormMessage.OK);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -113,7 +114,7 @@ namespace TweetDuck.Core.Handling{
|
||||
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 +135,7 @@ namespace TweetDuck.Core.Handling{
|
||||
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
|
||||
|
@@ -9,19 +9,22 @@ namespace TweetDuck.Core.Handling {
|
||||
class JavaScriptDialogHandler : IJsDialogHandler{
|
||||
bool IJsDialogHandler.OnJSDialog(IWebBrowser browserControl, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage){
|
||||
((ChromiumWebBrowser)browserControl).InvokeSafe(() => {
|
||||
FormMessage form = new FormMessage(Program.BrandName, messageText, MessageBoxIcon.None);
|
||||
FormMessage form;
|
||||
TextBox input = null;
|
||||
|
||||
if (dialogType == CefJsDialogType.Alert){
|
||||
form.AcceptButton = form.AddButton("OK");
|
||||
form = new FormMessage("TweetDuck Browser Message", messageText, MessageBoxIcon.None);
|
||||
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
|
||||
}
|
||||
else if (dialogType == CefJsDialogType.Confirm){
|
||||
form.CancelButton = form.AddButton("No", DialogResult.No);
|
||||
form.AcceptButton = form.AddButton("Yes");
|
||||
form = new FormMessage("TweetDuck Browser Confirmation", messageText, MessageBoxIcon.None);
|
||||
form.AddButton(FormMessage.No, DialogResult.No, ControlType.Cancel);
|
||||
form.AddButton(FormMessage.Yes, ControlType.Focused);
|
||||
}
|
||||
else if (dialogType == CefJsDialogType.Prompt){
|
||||
form.CancelButton = form.AddButton("Cancel", DialogResult.Cancel);
|
||||
form.AcceptButton = form.AddButton("OK");
|
||||
form = new FormMessage("TweetDuck Browser Prompt", messageText, MessageBoxIcon.None);
|
||||
form.AddButton(FormMessage.Cancel, DialogResult.Cancel, ControlType.Cancel);
|
||||
form.AddButton(FormMessage.OK, ControlType.Accept | ControlType.Focused);
|
||||
|
||||
input = new TextBox{
|
||||
Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom,
|
||||
@@ -33,6 +36,10 @@ namespace TweetDuck.Core.Handling {
|
||||
form.ActiveControl = input;
|
||||
form.Height += input.Size.Height+input.Margin.Vertical;
|
||||
}
|
||||
else{
|
||||
callback.Continue(false);
|
||||
return;
|
||||
}
|
||||
|
||||
bool success = form.ShowDialog() == DialogResult.OK;
|
||||
|
||||
|
@@ -57,20 +57,25 @@ namespace TweetDuck.Core.Notification{
|
||||
|
||||
set{
|
||||
Visible = (base.Location = value) != ControlExtensions.InvisibleLocation;
|
||||
|
||||
if (WindowsUtils.ShouldAvoidToolWindow){
|
||||
FormBorderStyle = Visible ? FormBorderStyle.FixedSingle : FormBorderStyle.FixedToolWindow; // workaround for alt+tab
|
||||
}
|
||||
FormBorderStyle = GetBorderStyle(CanResizeWindow);
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanResizeWindow{
|
||||
get => FormBorderStyle == FormBorderStyle.Sizable || FormBorderStyle == FormBorderStyle.SizableToolWindow;
|
||||
set => FormBorderStyle = GetBorderStyle(value);
|
||||
}
|
||||
|
||||
public Func<bool> CanMoveWindow = () => true;
|
||||
public Func<bool> CanMoveWindow { get; set; } = () => true;
|
||||
protected override bool ShowWithoutActivation => true;
|
||||
|
||||
protected double SizeScale => dpiScale*Program.UserConfig.ZoomMultiplier;
|
||||
|
||||
protected readonly Form owner;
|
||||
protected readonly ChromiumWebBrowser browser;
|
||||
|
||||
|
||||
private readonly ResourceHandlerNotification resourceHandler = new ResourceHandlerNotification();
|
||||
private readonly float dpiScale;
|
||||
|
||||
private string currentColumn;
|
||||
private int pauseCounter;
|
||||
@@ -103,8 +108,10 @@ namespace TweetDuck.Core.Notification{
|
||||
this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
|
||||
#endif
|
||||
|
||||
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);
|
||||
|
||||
@@ -134,6 +141,9 @@ namespace TweetDuck.Core.Notification{
|
||||
private void Browser_IsBrowserInitializedChanged(object sender, IsBrowserInitializedChangedEventArgs e){
|
||||
if (e.IsBrowserInitialized){
|
||||
Initialized?.Invoke(this, new EventArgs());
|
||||
|
||||
int identifier = browser.GetBrowser().Identifier;
|
||||
Disposed += (sender2, args2) => BrowserProcesses.Forget(identifier);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,11 +183,11 @@ namespace TweetDuck.Core.Notification{
|
||||
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){
|
||||
browser.ClientSize = ClientSize = new Size((int)Math.Round(width*Program.UserConfig.ZoomMultiplier), (int)Math.Round(height*Program.UserConfig.ZoomMultiplier));
|
||||
browser.ClientSize = ClientSize = new Size(BrowserUtils.Scale(width, SizeScale), BrowserUtils.Scale(height, SizeScale));
|
||||
}
|
||||
|
||||
protected virtual void OnNotificationReady(){
|
||||
@@ -207,5 +217,14 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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, 4);
|
||||
this.progressBarTimer.Size = new System.Drawing.Size(284, TimerBarHeight);
|
||||
this.progressBarTimer.TabIndex = 1;
|
||||
//
|
||||
// FormNotification
|
||||
|
@@ -5,6 +5,7 @@ using System.Windows.Forms;
|
||||
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;
|
||||
@@ -12,32 +13,13 @@ using TweetDuck.Resources;
|
||||
namespace TweetDuck.Core.Notification{
|
||||
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 PluginScriptIdentifier = ScriptLoader.GetRootIdentifier(PluginManager.PluginNotificationScriptFile);
|
||||
|
||||
private static readonly string NotificationJS, PluginJS;
|
||||
|
||||
static FormNotificationMain(){
|
||||
NotificationJS = ScriptLoader.LoadResource(NotificationScriptFile);
|
||||
PluginJS = ScriptLoader.LoadResource(PluginManager.PluginNotificationScriptFile);
|
||||
}
|
||||
|
||||
private static int BaseClientWidth{
|
||||
get{
|
||||
int level = TweetNotification.FontSizeLevel;
|
||||
int width = level == 0 ? 284 : (int)Math.Round(284.0*(1.0+0.05*level));
|
||||
return (int)Math.Round(width*Program.UserConfig.ZoomMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
private static int BaseClientHeight{
|
||||
get{
|
||||
int level = TweetNotification.FontSizeLevel;
|
||||
int height = level == 0 ? 118 : (int)Math.Round(118.0*(1.0+0.075*level));
|
||||
return (int)Math.Round(height*Program.UserConfig.ZoomMultiplier);
|
||||
}
|
||||
}
|
||||
private static readonly string NotificationJS = ScriptLoader.LoadResource(NotificationScriptFile);
|
||||
private static readonly string PluginJS = ScriptLoader.LoadResource(PluginManager.PluginNotificationScriptFile);
|
||||
|
||||
private readonly PluginManager plugins;
|
||||
|
||||
@@ -51,9 +33,9 @@ namespace TweetDuck.Core.Notification{
|
||||
private bool? prevDisplayTimer;
|
||||
private int? prevFontSize;
|
||||
|
||||
private bool RequiresResize{
|
||||
public bool RequiresResize{
|
||||
get{
|
||||
return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Program.UserConfig.DisplayNotificationTimer || prevFontSize != TweetNotification.FontSizeLevel;
|
||||
return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Program.UserConfig.DisplayNotificationTimer || prevFontSize != TweetNotification.FontSizeLevel || CanResizeWindow;
|
||||
}
|
||||
|
||||
set{
|
||||
@@ -68,6 +50,34 @@ namespace TweetDuck.Core.Notification{
|
||||
}
|
||||
}
|
||||
|
||||
private int BaseClientWidth{
|
||||
get{
|
||||
switch(Program.UserConfig.NotificationSize){
|
||||
default:
|
||||
return BrowserUtils.Scale(284, SizeScale*(1.0+0.05*TweetNotification.FontSizeLevel));
|
||||
|
||||
case TweetNotification.Size.Custom:
|
||||
return Program.UserConfig.CustomNotificationSize.Width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int BaseClientHeight{
|
||||
get{
|
||||
switch(Program.UserConfig.NotificationSize){
|
||||
default:
|
||||
return BrowserUtils.Scale(118, SizeScale*(1.0+0.075*TweetNotification.FontSizeLevel));
|
||||
|
||||
case TweetNotification.Size.Custom:
|
||||
return Program.UserConfig.CustomNotificationSize.Height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Size BrowserSize{
|
||||
get => Program.UserConfig.DisplayNotificationTimer ? new Size(ClientSize.Width, ClientSize.Height-TimerBarHeight) : ClientSize;
|
||||
}
|
||||
|
||||
public FormNotificationMain(FormBrowser owner, PluginManager pluginManager, bool enableContextMenu) : base(owner, enableContextMenu){
|
||||
InitializeComponent();
|
||||
|
||||
@@ -104,9 +114,7 @@ namespace TweetDuck.Core.Notification{
|
||||
int eventType = wParam.ToInt32();
|
||||
|
||||
if (eventType == NativeMethods.WM_MOUSEWHEEL && browser.Bounds.Contains(PointToClient(Cursor.Position))){
|
||||
int distance = (int)Math.Round(NativeMethods.GetMouseHookData(lParam)*(Program.UserConfig.NotificationScrollSpeed/100.0));
|
||||
|
||||
browser.SendMouseWheelEvent(0, 0, 0, distance, CefEventFlags.None);
|
||||
browser.SendMouseWheelEvent(0, 0, 0, BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Program.UserConfig.NotificationScrollSpeed/100.0), CefEventFlags.None);
|
||||
return NativeMethods.HOOK_HANDLED;
|
||||
}
|
||||
else if (eventType == NativeMethods.WM_XBUTTONDOWN && DesktopBounds.Contains(Cursor.Position)){
|
||||
@@ -177,7 +185,7 @@ namespace TweetDuck.Core.Notification{
|
||||
|
||||
timeLeft -= timerProgress.Interval;
|
||||
|
||||
int value = (int)Math.Round(1025.0*(totalTime-timeLeft)/totalTime);
|
||||
int value = BrowserUtils.Scale(1025, (totalTime-timeLeft)/(double)totalTime);
|
||||
progressBarTimer.SetValueInstant(Math.Min(1000, Math.Max(0, Program.UserConfig.NotificationTimerCountDown ? 1000-value : value)));
|
||||
|
||||
if (timeLeft <= 0){
|
||||
@@ -255,7 +263,7 @@ namespace TweetDuck.Core.Notification{
|
||||
|
||||
protected override void SetNotificationSize(int width, int height){
|
||||
if (Program.UserConfig.DisplayNotificationTimer){
|
||||
ClientSize = new Size(width, height+4);
|
||||
ClientSize = new Size(width, height+TimerBarHeight);
|
||||
progressBarTimer.Visible = true;
|
||||
}
|
||||
else{
|
||||
|
@@ -3,12 +3,19 @@ using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Other;
|
||||
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 +24,15 @@ namespace TweetDuck.Core.Notification.Screenshot{
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
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){
|
||||
@@ -31,7 +44,7 @@ namespace TweetDuck.Core.Notification.Screenshot{
|
||||
IntPtr context = NativeMethods.GetDC(this.Handle);
|
||||
|
||||
if (context == IntPtr.Zero){
|
||||
MessageBox.Show("Could not retrieve a graphics context handle for the notification window to take the screenshot.", "Screenshot Failed", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
FormMessage.Error("Screenshot Failed", "Could not retrieve a graphics context handle for the notification window to take the screenshot.", FormMessage.OK);
|
||||
}
|
||||
else{
|
||||
using(Bitmap bmp = new Bitmap(ClientSize.Width, ClientSize.Height, PixelFormat.Format32bppRgb)){
|
||||
|
@@ -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 @@ namespace TweetDuck.Core.Notification.Screenshot{
|
||||
return;
|
||||
}
|
||||
|
||||
screenshot = new FormNotificationScreenshotable(Callback, owner){
|
||||
screenshot = new FormNotificationScreenshotable(Callback, owner, plugins){
|
||||
CanMoveWindow = () => false
|
||||
};
|
||||
|
||||
|
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using TweetLib.Audio;
|
||||
using TweetLib.Audio.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Notification{
|
||||
sealed class SoundNotification : IDisposable{
|
||||
|
@@ -51,6 +51,10 @@ namespace TweetDuck.Core.Notification{
|
||||
TopLeft, TopRight, BottomLeft, BottomRight, Custom
|
||||
}
|
||||
|
||||
public enum Size{
|
||||
Auto, Custom
|
||||
}
|
||||
|
||||
public string Column { get; }
|
||||
public string TweetUrl { get; }
|
||||
public string QuoteUrl { get; }
|
||||
|
@@ -11,7 +11,7 @@ namespace TweetDuck.Core.Other{
|
||||
|
||||
Text = "About "+Program.BrandName+" "+Program.VersionTag;
|
||||
|
||||
labelDescription.Text = Program.BrandName+" was created by chylex as a replacement to the discontinued official TweetDeck client for Windows.\n\nThe program is available for free under the open source MIT license.";
|
||||
labelDescription.Text = "TweetDuck was created by chylex as a replacement to the discontinued official TweetDeck client for Windows.\n\nThe program is available for free under the open source MIT license.";
|
||||
|
||||
labelWebsite.Links.Add(new LinkLabel.Link(0, labelWebsite.Text.Length, Program.Website));
|
||||
labelTips.Links.Add(new LinkLabel.Link(0, labelTips.Text.Length, TipsLink));
|
||||
|
@@ -1,9 +1,63 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Other{
|
||||
[Flags]
|
||||
enum ControlType{
|
||||
None = 0,
|
||||
Accept = 1, // triggered by pressing enter when a non-button is focused
|
||||
Cancel = 2, // triggered by closing the dialog without pressing a button
|
||||
Focused = 4 // active control after the dialog is showed
|
||||
}
|
||||
|
||||
sealed partial class FormMessage : Form{
|
||||
public const string OK = "OK";
|
||||
public const string Yes = "Yes";
|
||||
public const string No = "No";
|
||||
public const string Cancel = "Cancel";
|
||||
public const string Retry = "Retry";
|
||||
public const string Ignore = "Ignore";
|
||||
public const string Exit = "Exit";
|
||||
|
||||
public static bool Information(string caption, string text, string buttonAccept, string buttonCancel = null){
|
||||
return Show(caption, text, MessageBoxIcon.Information, buttonAccept, buttonCancel);
|
||||
}
|
||||
|
||||
public static bool Warning(string caption, string text, string buttonAccept, string buttonCancel = null){
|
||||
return Show(caption, text, MessageBoxIcon.Warning, buttonAccept, buttonCancel);
|
||||
}
|
||||
|
||||
public static bool Error(string caption, string text, string buttonAccept, string buttonCancel = null){
|
||||
return Show(caption, text, MessageBoxIcon.Error, buttonAccept, buttonCancel);
|
||||
}
|
||||
|
||||
public static bool Question(string caption, string text, string buttonAccept, string buttonCancel = null){
|
||||
return Show(caption, text, MessageBoxIcon.Question, buttonAccept, buttonCancel);
|
||||
}
|
||||
|
||||
public static bool Show(string caption, string text, MessageBoxIcon icon, string button){
|
||||
return Show(caption, text, icon, button, null);
|
||||
}
|
||||
|
||||
public static bool Show(string caption, string text, MessageBoxIcon icon, string buttonAccept, string buttonCancel){
|
||||
using(FormMessage message = new FormMessage(caption, text, icon)){
|
||||
if (buttonCancel == null){
|
||||
message.AddButton(buttonAccept, DialogResult.OK, ControlType.Cancel | ControlType.Focused);
|
||||
}
|
||||
else{
|
||||
message.AddButton(buttonCancel, DialogResult.Cancel, ControlType.Cancel);
|
||||
message.AddButton(buttonAccept, DialogResult.OK, ControlType.Accept | ControlType.Focused);
|
||||
}
|
||||
|
||||
return message.ShowDialog() == DialogResult.OK;
|
||||
}
|
||||
}
|
||||
|
||||
// Instance
|
||||
|
||||
public Button ClickedButton { get; private set; }
|
||||
|
||||
public int ActionPanelY => panelActions.Location.Y;
|
||||
@@ -13,8 +67,13 @@ namespace TweetDuck.Core.Other{
|
||||
set => ClientSize = new Size(value, ClientSize.Height);
|
||||
}
|
||||
|
||||
private int ButtonDistance{
|
||||
get => BrowserUtils.Scale(96, dpiScale);
|
||||
}
|
||||
|
||||
private readonly Icon icon;
|
||||
private readonly bool isReady;
|
||||
private readonly float dpiScale;
|
||||
|
||||
private int realFormWidth, minFormWidth;
|
||||
private int buttonCount;
|
||||
@@ -24,9 +83,11 @@ namespace TweetDuck.Core.Other{
|
||||
public FormMessage(string caption, string text, MessageBoxIcon messageIcon){
|
||||
InitializeComponent();
|
||||
|
||||
this.dpiScale = this.GetDPIScale();
|
||||
|
||||
this.prevLabelWidth = labelMessage.Width;
|
||||
this.prevLabelHeight = labelMessage.Height;
|
||||
this.minFormWidth = 40;
|
||||
this.minFormWidth = BrowserUtils.Scale(40, dpiScale);
|
||||
|
||||
switch(messageIcon){
|
||||
case MessageBoxIcon.Information:
|
||||
@@ -54,19 +115,23 @@ namespace TweetDuck.Core.Other{
|
||||
this.isReady = true;
|
||||
|
||||
this.Text = caption;
|
||||
this.labelMessage.Text = text;
|
||||
this.labelMessage.Text = text.Replace("\r", "").Replace("\n", Environment.NewLine);
|
||||
}
|
||||
|
||||
private void FormMessage_SizeChanged(object sender, EventArgs e){
|
||||
RecalculateButtonLocation();
|
||||
}
|
||||
|
||||
public Button AddButton(string title, DialogResult result = DialogResult.OK){
|
||||
public Button AddButton(string title, ControlType type){
|
||||
return AddButton(title, DialogResult.OK, type);
|
||||
}
|
||||
|
||||
public Button AddButton(string title, DialogResult result = DialogResult.OK, ControlType type = ControlType.None){
|
||||
Button button = new Button{
|
||||
Anchor = AnchorStyles.Bottom,
|
||||
Font = SystemFonts.MessageBoxFont,
|
||||
Location = new Point(0, 12),
|
||||
Size = new Size(88, 26),
|
||||
Size = new Size(BrowserUtils.Scale(88, dpiScale), BrowserUtils.Scale(26, dpiScale)),
|
||||
TabIndex = buttonCount,
|
||||
Text = title,
|
||||
UseVisualStyleBackColor = true
|
||||
@@ -81,24 +146,41 @@ namespace TweetDuck.Core.Other{
|
||||
panelActions.Controls.Add(button);
|
||||
++buttonCount;
|
||||
|
||||
minFormWidth += 96;
|
||||
minFormWidth += ButtonDistance;
|
||||
ClientWidth = Math.Max(realFormWidth, minFormWidth);
|
||||
RecalculateButtonLocation();
|
||||
|
||||
if (type.HasFlag(ControlType.Accept)){
|
||||
AcceptButton = button;
|
||||
}
|
||||
|
||||
if (type.HasFlag(ControlType.Cancel)){
|
||||
CancelButton = button;
|
||||
}
|
||||
|
||||
if (type.HasFlag(ControlType.Focused)){
|
||||
ActiveControl = button;
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
public void AddActionControl(Control control){
|
||||
panelActions.Controls.Add(control);
|
||||
|
||||
control.Size = new Size(BrowserUtils.Scale(control.Width, dpiScale), BrowserUtils.Scale(control.Height, dpiScale));
|
||||
|
||||
minFormWidth += control.Width+control.Margin.Horizontal;
|
||||
ClientWidth = Math.Max(realFormWidth, minFormWidth);
|
||||
}
|
||||
|
||||
private void RecalculateButtonLocation(){
|
||||
int dist = ButtonDistance;
|
||||
int start = ClientWidth-dist-BrowserUtils.Scale(1, dpiScale);
|
||||
|
||||
for(int index = 0; index < buttonCount; index++){
|
||||
Control control = panelActions.Controls[index];
|
||||
control.Location = new Point(ClientWidth-97-index*96, control.Location.Y);
|
||||
control.Location = new Point(start-index*dist, control.Location.Y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,14 +190,15 @@ namespace TweetDuck.Core.Other{
|
||||
}
|
||||
|
||||
bool isMultiline = labelMessage.Height > labelMessage.MinimumSize.Height;
|
||||
int labelOffset = BrowserUtils.Scale(8, dpiScale);
|
||||
|
||||
if (isMultiline && !wasLabelMultiline){
|
||||
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y-8);
|
||||
prevLabelHeight += 8;
|
||||
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y-labelOffset);
|
||||
prevLabelHeight += labelOffset;
|
||||
}
|
||||
else if (!isMultiline && wasLabelMultiline){
|
||||
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y+8);
|
||||
prevLabelHeight -= 8;
|
||||
labelMessage.Location = new Point(labelMessage.Location.X, labelMessage.Location.Y+labelOffset);
|
||||
prevLabelHeight -= labelOffset;
|
||||
}
|
||||
|
||||
realFormWidth = ClientWidth-(icon == null ? 50 : 0)+labelMessage.Width-prevLabelWidth;
|
||||
@@ -129,7 +212,7 @@ namespace TweetDuck.Core.Other{
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e){
|
||||
if (icon != null){
|
||||
e.Graphics.DrawIcon(icon, 25, 26);
|
||||
e.Graphics.DrawIcon(icon, BrowserUtils.Scale(25, dpiScale), BrowserUtils.Scale(26, dpiScale));
|
||||
}
|
||||
|
||||
base.OnPaint(e);
|
||||
|
@@ -80,7 +80,7 @@ namespace TweetDuck.Core.Other{
|
||||
}
|
||||
|
||||
private void btnReload_Click(object sender, EventArgs e){
|
||||
if (MessageBox.Show("This will also reload the browser window. Do you want to proceed?", "Reloading Plugins", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
|
||||
if (FormMessage.Warning("Reloading Plugins", "This will also reload the browser window. Do you want to proceed?", FormMessage.Yes, FormMessage.No)){
|
||||
pluginManager.Reload();
|
||||
ReloadPluginList();
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Other.Settings;
|
||||
using TweetDuck.Core.Other.Settings.Dialogs;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Updates;
|
||||
|
||||
@@ -13,6 +14,8 @@ namespace TweetDuck.Core.Other{
|
||||
private readonly FormBrowser browser;
|
||||
private readonly PluginManager plugins;
|
||||
|
||||
private readonly int buttonHeight;
|
||||
|
||||
private readonly Dictionary<Type, SettingsTab> tabs = new Dictionary<Type, SettingsTab>(4);
|
||||
private SettingsTab currentTab;
|
||||
|
||||
@@ -25,6 +28,8 @@ namespace TweetDuck.Core.Other{
|
||||
this.browser.PauseNotification();
|
||||
|
||||
this.plugins = plugins;
|
||||
|
||||
this.buttonHeight = BrowserUtils.Scale(39, this.GetDPIScale()) | 1;
|
||||
|
||||
AddButton("General", () => new TabSettingsGeneral(updates));
|
||||
AddButton("Notifications", () => new TabSettingsNotifications(browser.CreateNotificationForm(false)));
|
||||
@@ -63,14 +68,12 @@ namespace TweetDuck.Core.Other{
|
||||
}
|
||||
|
||||
private void AddButton<T>(string title, Func<T> constructor) where T : BaseTabSettings{
|
||||
const int btnHeight = 39;
|
||||
|
||||
FlatButton btn = new FlatButton{
|
||||
BackColor = SystemColors.Control,
|
||||
FlatStyle = FlatStyle.Flat,
|
||||
Location = new Point(0, (btnHeight+1)*(panelButtons.Controls.Count/2)),
|
||||
Location = new Point(0, (buttonHeight+1)*(panelButtons.Controls.Count/2)),
|
||||
Margin = new Padding(0),
|
||||
Size = new Size(panelButtons.Width, btnHeight),
|
||||
Size = new Size(panelButtons.Width, buttonHeight),
|
||||
Text = title,
|
||||
UseVisualStyleBackColor = true
|
||||
};
|
||||
@@ -83,7 +86,7 @@ namespace TweetDuck.Core.Other{
|
||||
|
||||
panelButtons.Controls.Add(new Panel{
|
||||
BackColor = Color.DimGray,
|
||||
Location = new Point(0, panelButtons.Controls[panelButtons.Controls.Count-1].Location.Y+btnHeight),
|
||||
Location = new Point(0, panelButtons.Controls[panelButtons.Controls.Count-1].Location.Y+buttonHeight),
|
||||
Margin = new Padding(0),
|
||||
Size = new Size(panelButtons.Width, 1)
|
||||
});
|
||||
@@ -106,7 +109,12 @@ namespace TweetDuck.Core.Other{
|
||||
|
||||
if (!tab.IsInitialized){
|
||||
foreach(Control control in tab.Control.InteractiveControls){
|
||||
control.MouseLeave += control_MouseLeave;
|
||||
if (control is ComboBox){
|
||||
control.MouseLeave += control_MouseLeave;
|
||||
}
|
||||
else if (control is TrackBar){
|
||||
control.MouseWheel += control_MouseWheel;
|
||||
}
|
||||
}
|
||||
|
||||
tab.Control.OnReady();
|
||||
@@ -126,6 +134,11 @@ namespace TweetDuck.Core.Other{
|
||||
panelContents.Focus();
|
||||
}
|
||||
|
||||
private void control_MouseWheel(object sender, MouseEventArgs e){
|
||||
((HandledMouseEventArgs)e).Handled = true;
|
||||
panelContents.Focus();
|
||||
}
|
||||
|
||||
private class SettingsTab{
|
||||
public Button Button { get; }
|
||||
|
||||
|
@@ -25,7 +25,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
public virtual void OnClosing(){}
|
||||
|
||||
protected static void PromptRestart(){
|
||||
if (MessageBox.Show("The application must restart for the option to take place. Do you want to restart now?", Program.BrandName+" Options", MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.Yes){
|
||||
if (FormMessage.Information("TweetDuck Options", "The application must restart for the option to take place. Do you want to restart now?", FormMessage.Yes, FormMessage.No)){
|
||||
Program.Restart();
|
||||
}
|
||||
}
|
||||
|
@@ -42,16 +42,14 @@
|
||||
//
|
||||
// textBoxBrowserCSS
|
||||
//
|
||||
this.textBoxBrowserCSS.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.textBoxBrowserCSS.Dock = System.Windows.Forms.DockStyle.Bottom;
|
||||
this.textBoxBrowserCSS.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||
this.textBoxBrowserCSS.Location = new System.Drawing.Point(0, 16);
|
||||
this.textBoxBrowserCSS.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0);
|
||||
this.textBoxBrowserCSS.Multiline = true;
|
||||
this.textBoxBrowserCSS.Name = "textBoxBrowserCSS";
|
||||
this.textBoxBrowserCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both;
|
||||
this.textBoxBrowserCSS.Size = new System.Drawing.Size(373, 253);
|
||||
this.textBoxBrowserCSS.Size = new System.Drawing.Size(378, 253);
|
||||
this.textBoxBrowserCSS.TabIndex = 1;
|
||||
this.textBoxBrowserCSS.WordWrap = false;
|
||||
this.textBoxBrowserCSS.KeyUp += new System.Windows.Forms.KeyEventHandler(this.textBoxBrowserCSS_KeyUp);
|
||||
@@ -100,7 +98,7 @@
|
||||
this.splitContainer.Panel2.Controls.Add(this.textBoxNotificationCSS);
|
||||
this.splitContainer.Panel2MinSize = 64;
|
||||
this.splitContainer.Size = new System.Drawing.Size(760, 269);
|
||||
this.splitContainer.SplitterDistance = 373;
|
||||
this.splitContainer.SplitterDistance = 378;
|
||||
this.splitContainer.SplitterWidth = 5;
|
||||
this.splitContainer.TabIndex = 0;
|
||||
//
|
||||
@@ -126,16 +124,14 @@
|
||||
//
|
||||
// textBoxNotificationCSS
|
||||
//
|
||||
this.textBoxNotificationCSS.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.textBoxNotificationCSS.Dock = System.Windows.Forms.DockStyle.Bottom;
|
||||
this.textBoxNotificationCSS.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||
this.textBoxNotificationCSS.Location = new System.Drawing.Point(0, 16);
|
||||
this.textBoxNotificationCSS.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0);
|
||||
this.textBoxNotificationCSS.Multiline = true;
|
||||
this.textBoxNotificationCSS.Name = "textBoxNotificationCSS";
|
||||
this.textBoxNotificationCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both;
|
||||
this.textBoxNotificationCSS.Size = new System.Drawing.Size(372, 253);
|
||||
this.textBoxNotificationCSS.Size = new System.Drawing.Size(377, 253);
|
||||
this.textBoxNotificationCSS.TabIndex = 1;
|
||||
this.textBoxNotificationCSS.WordWrap = false;
|
||||
//
|
||||
@@ -152,7 +148,6 @@
|
||||
// btnOpenWiki
|
||||
//
|
||||
this.btnOpenWiki.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.btnOpenWiki.AutoSize = true;
|
||||
this.btnOpenWiki.Location = new System.Drawing.Point(12, 287);
|
||||
this.btnOpenWiki.Name = "btnOpenWiki";
|
||||
this.btnOpenWiki.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
||||
|
@@ -69,7 +69,6 @@
|
||||
// btnHelp
|
||||
//
|
||||
this.btnHelp.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.btnHelp.AutoSize = true;
|
||||
this.btnHelp.Location = new System.Drawing.Point(12, 227);
|
||||
this.btnHelp.Name = "btnHelp";
|
||||
this.btnHelp.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
||||
|
@@ -33,7 +33,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
int count = CommandLineArgsParser.ReadCefArguments(CefArgs).Count;
|
||||
string prompt = count == 0 && !string.IsNullOrWhiteSpace(prevArgs) ? "All current arguments will be removed. Continue?" : count+(count == 1 ? " argument was" : " arguments were")+" detected. Continue?";
|
||||
|
||||
if (MessageBox.Show(prompt, "Confirm CEF Arguments", MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.OK){
|
||||
if (FormMessage.Question("Confirm CEF Arguments", prompt, FormMessage.OK, FormMessage.Cancel)){
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
}
|
||||
|
@@ -42,7 +42,6 @@
|
||||
// btnCancel
|
||||
//
|
||||
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.btnCancel.AutoSize = true;
|
||||
this.btnCancel.Location = new System.Drawing.Point(176, 97);
|
||||
this.btnCancel.Name = "btnCancel";
|
||||
this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
||||
@@ -56,7 +55,6 @@
|
||||
//
|
||||
this.btnContinue.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.btnContinue.AutoSize = true;
|
||||
this.btnContinue.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
|
||||
this.btnContinue.Enabled = false;
|
||||
this.btnContinue.Location = new System.Drawing.Point(125, 97);
|
||||
this.btnContinue.Name = "btnContinue";
|
||||
|
@@ -58,7 +58,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
case State.Deciding:
|
||||
// Reset
|
||||
if (radioReset.Checked){
|
||||
if (MessageBox.Show("This will reset all of your program options. Plugins will not be affected. Do you want to proceed?", "Reset "+Program.BrandName+" Options", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
|
||||
if (FormMessage.Warning("Reset TweetDuck Options", "This will reset all of your program options. Plugins will not be affected. Do you want to proceed?", FormMessage.Yes, FormMessage.No)){
|
||||
Program.ResetConfig();
|
||||
|
||||
ShouldReloadUI = true;
|
||||
@@ -74,8 +74,8 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
using(OpenFileDialog dialog = new OpenFileDialog{
|
||||
AutoUpgradeEnabled = true,
|
||||
DereferenceLinks = true,
|
||||
Title = "Import "+Program.BrandName+" Profile",
|
||||
Filter = Program.BrandName+" Profile (*.tdsettings)|*.tdsettings"
|
||||
Title = "Import TweetDuck Profile",
|
||||
Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings"
|
||||
}){
|
||||
if (dialog.ShowDialog() != DialogResult.OK){
|
||||
return;
|
||||
@@ -116,7 +116,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
}
|
||||
}
|
||||
else{
|
||||
Program.Reporter.HandleException("Profile Import Error", "An exception happened while importing "+Program.BrandName+" profile.", true, importManager.LastException);
|
||||
Program.Reporter.HandleException("Profile Import Error", "An exception happened while importing TweetDuck profile.", true, importManager.LastException);
|
||||
}
|
||||
|
||||
DialogResult = DialogResult.OK;
|
||||
@@ -129,9 +129,9 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
AutoUpgradeEnabled = true,
|
||||
OverwritePrompt = true,
|
||||
DefaultExt = "tdsettings",
|
||||
FileName = Program.BrandName+".tdsettings",
|
||||
Title = "Export "+Program.BrandName+" Profile",
|
||||
Filter = Program.BrandName+" Profile (*.tdsettings)|*.tdsettings"
|
||||
FileName = "TweetDuck.tdsettings",
|
||||
Title = "Export TweetDuck Profile",
|
||||
Filter = "TweetDuck Profile (*.tdsettings)|*.tdsettings"
|
||||
}){
|
||||
if (dialog.ShowDialog() != DialogResult.OK){
|
||||
return;
|
||||
@@ -144,7 +144,7 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
ExportManager manager = new ExportManager(file, plugins);
|
||||
|
||||
if (!manager.Export(Flags)){
|
||||
Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting "+Program.BrandName+" profile.", true, manager.LastException);
|
||||
Program.Reporter.HandleException("Profile Export Error", "An exception happened while exporting TweetDuck profile.", true, manager.LastException);
|
||||
}
|
||||
|
||||
DialogResult = DialogResult.OK;
|
||||
|
@@ -38,7 +38,6 @@
|
||||
// btnCancel
|
||||
//
|
||||
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.btnCancel.AutoSize = true;
|
||||
this.btnCancel.Location = new System.Drawing.Point(160, 171);
|
||||
this.btnCancel.Name = "btnCancel";
|
||||
this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
||||
@@ -51,7 +50,6 @@
|
||||
// btnRestart
|
||||
//
|
||||
this.btnRestart.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.btnRestart.AutoSize = true;
|
||||
this.btnRestart.Location = new System.Drawing.Point(97, 171);
|
||||
this.btnRestart.Name = "btnRestart";
|
||||
this.btnRestart.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
||||
|
@@ -3,7 +3,7 @@ using System.IO;
|
||||
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{
|
||||
|
@@ -2,8 +2,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Data;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace TweetDuck.Core.Other.Settings.Export{
|
||||
try{
|
||||
stream.WriteFile(new string[]{ "plugin.data", plugin.Identifier, path.Relative }, path.Full);
|
||||
}catch(ArgumentOutOfRangeException e){
|
||||
MessageBox.Show("Could not include a plugin file in the export. "+e.Message, "Export Profile", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
FormMessage.Warning("Export Profile", "Could not include a plugin file in the export. "+e.Message, FormMessage.OK);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,11 +138,11 @@ namespace TweetDuck.Core.Other.Settings.Export{
|
||||
}
|
||||
|
||||
if (missingPlugins.Count > 0){
|
||||
MessageBox.Show("Detected missing plugins when importing plugin data:"+Environment.NewLine+string.Join(Environment.NewLine, missingPlugins), "Importing "+Program.BrandName+" Profile", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
FormMessage.Information("Importing TweetDuck Profile", "Detected missing plugins when importing plugin data:\n"+string.Join("\n", missingPlugins), FormMessage.OK);
|
||||
}
|
||||
|
||||
if (IsRestarting){
|
||||
Program.Restart(new string[]{ Arguments.ArgImportCookies });
|
||||
Program.Restart(Arguments.ArgImportCookies);
|
||||
}
|
||||
else{
|
||||
Program.ReloadConfig();
|
||||
|
79
Core/Other/Settings/TabSettingsAdvanced.Designer.cs
generated
79
Core/Other/Settings/TabSettingsAdvanced.Designer.cs
generated
@@ -33,12 +33,16 @@
|
||||
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.labelApp = new System.Windows.Forms.Label();
|
||||
this.panelApp = 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.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.panelConfiguration.SuspendLayout();
|
||||
@@ -52,8 +56,7 @@
|
||||
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.toolTip.SetToolTip(this.btnClearCache, "Clearing cache will free up space taken by downloaded images and other resources.");
|
||||
this.btnClearCache.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// checkHardwareAcceleration
|
||||
@@ -65,8 +68,7 @@
|
||||
this.checkHardwareAcceleration.Size = new System.Drawing.Size(134, 17);
|
||||
this.checkHardwareAcceleration.TabIndex = 0;
|
||||
this.checkHardwareAcceleration.Text = "Hardware Acceleration";
|
||||
this.toolTip.SetToolTip(this.checkHardwareAcceleration, "Uses your graphics card to improve performance.\r\nDisable if you experience issues" +
|
||||
" with rendering.");
|
||||
this.toolTip.SetToolTip(this.checkHardwareAcceleration, "Uses your graphics card to improve performance.\r\nDisable if you experience issues with rendering.");
|
||||
this.checkHardwareAcceleration.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// btnEditCefArgs
|
||||
@@ -107,8 +109,7 @@
|
||||
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 lau" +
|
||||
"nch.");
|
||||
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
|
||||
@@ -133,6 +134,47 @@
|
||||
this.toolTip.SetToolTip(this.btnOpenDataFolder, "Opens the folder where your profile data is located.");
|
||||
this.btnOpenDataFolder.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// numMemoryThreshold
|
||||
//
|
||||
this.numMemoryThreshold.Increment = new decimal(new int[] {
|
||||
50,
|
||||
0,
|
||||
0,
|
||||
0});
|
||||
this.numMemoryThreshold.Location = new System.Drawing.Point(202, 82);
|
||||
this.numMemoryThreshold.Maximum = new decimal(new int[] {
|
||||
3000,
|
||||
0,
|
||||
0,
|
||||
0});
|
||||
this.numMemoryThreshold.Minimum = new decimal(new int[] {
|
||||
200,
|
||||
0,
|
||||
0,
|
||||
0});
|
||||
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 = new decimal(new int[] {
|
||||
350,
|
||||
0,
|
||||
0,
|
||||
0});
|
||||
//
|
||||
// checkBrowserGCReload
|
||||
//
|
||||
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\r\nif the browser is in a \'default state\', i.e. all modals and drawers are closed,\r\nand all columns are scrolled to top. Some notifications may be lost.");
|
||||
this.checkBrowserGCReload.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// labelApp
|
||||
//
|
||||
this.labelApp.AutoSize = true;
|
||||
@@ -172,20 +214,33 @@
|
||||
//
|
||||
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, 54);
|
||||
this.panelPerformance.Size = new System.Drawing.Size(322, 105);
|
||||
this.panelPerformance.TabIndex = 3;
|
||||
//
|
||||
// labelMemoryUsage
|
||||
//
|
||||
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";
|
||||
//
|
||||
// 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.Controls.Add(this.btnEditCSS);
|
||||
this.panelConfiguration.Controls.Add(this.btnEditCefArgs);
|
||||
this.panelConfiguration.Location = new System.Drawing.Point(9, 238);
|
||||
this.panelConfiguration.Location = new System.Drawing.Point(9, 289);
|
||||
this.panelConfiguration.Name = "panelConfiguration";
|
||||
this.panelConfiguration.Size = new System.Drawing.Size(322, 29);
|
||||
this.panelConfiguration.TabIndex = 5;
|
||||
@@ -194,7 +249,7 @@
|
||||
//
|
||||
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, 215);
|
||||
this.labelConfiguration.Location = new System.Drawing.Point(6, 266);
|
||||
this.labelConfiguration.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
|
||||
this.labelConfiguration.Name = "labelConfiguration";
|
||||
this.labelConfiguration.Size = new System.Drawing.Size(104, 20);
|
||||
@@ -212,7 +267,8 @@
|
||||
this.Controls.Add(this.panelApp);
|
||||
this.Controls.Add(this.labelApp);
|
||||
this.Name = "TabSettingsAdvanced";
|
||||
this.Size = new System.Drawing.Size(340, 277);
|
||||
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();
|
||||
@@ -239,5 +295,8 @@
|
||||
private System.Windows.Forms.Panel panelPerformance;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@@ -23,6 +23,10 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
checkHardwareAcceleration.Checked = false;
|
||||
}
|
||||
|
||||
checkBrowserGCReload.Checked = Config.EnableBrowserGCReload;
|
||||
numMemoryThreshold.Enabled = checkBrowserGCReload.Checked;
|
||||
numMemoryThreshold.SetValueSafe(Config.BrowserMemoryThreshold);
|
||||
|
||||
BrowserCache.CalculateCacheSize(bytes => this.InvokeSafe(() => {
|
||||
if (bytes == -1L){
|
||||
btnClearCache.Text = "Clear Cache (unknown size)";
|
||||
@@ -37,6 +41,9 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
btnClearCache.Click += btnClearCache_Click;
|
||||
checkHardwareAcceleration.CheckedChanged += checkHardwareAcceleration_CheckedChanged;
|
||||
|
||||
checkBrowserGCReload.CheckedChanged += checkBrowserGCReload_CheckedChanged;
|
||||
numMemoryThreshold.ValueChanged += numMemoryThreshold_ValueChanged;
|
||||
|
||||
btnEditCefArgs.Click += btnEditCefArgs_Click;
|
||||
btnEditCSS.Click += btnEditCSS_Click;
|
||||
|
||||
@@ -49,8 +56,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
private void btnClearCache_Click(object sender, EventArgs e){
|
||||
btnClearCache.Enabled = false;
|
||||
BrowserCache.SetClearOnExit();
|
||||
|
||||
MessageBox.Show("Cache will be automatically cleared when "+Program.BrandName+" exits.", "Clear Cache", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
FormMessage.Information("Clear Cache", "Cache will be automatically cleared when TweetDuck exits.", FormMessage.OK);
|
||||
}
|
||||
|
||||
private void checkHardwareAcceleration_CheckedChanged(object sender, EventArgs e){
|
||||
@@ -59,6 +65,15 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
PromptRestart();
|
||||
}
|
||||
|
||||
private void checkBrowserGCReload_CheckedChanged(object sender, EventArgs e){
|
||||
Config.EnableBrowserGCReload = checkBrowserGCReload.Checked;
|
||||
numMemoryThreshold.Enabled = checkBrowserGCReload.Checked;
|
||||
}
|
||||
|
||||
private void numMemoryThreshold_ValueChanged(object sender, EventArgs e){
|
||||
Config.BrowserMemoryThreshold = (int)numMemoryThreshold.Value;
|
||||
}
|
||||
|
||||
private void btnEditCefArgs_Click(object sender, EventArgs e){
|
||||
DialogSettingsCefArgs form = new DialogSettingsCefArgs();
|
||||
|
||||
|
12
Core/Other/Settings/TabSettingsGeneral.Designer.cs
generated
12
Core/Other/Settings/TabSettingsGeneral.Designer.cs
generated
@@ -58,8 +58,7 @@
|
||||
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 toolti" +
|
||||
"p instead.");
|
||||
this.toolTip.SetToolTip(this.checkExpandLinks, "Expands links inside the tweets. If disabled,\r\nthe full links show up in a tooltip instead.");
|
||||
this.checkExpandLinks.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// comboBoxTrayType
|
||||
@@ -82,8 +81,7 @@
|
||||
this.checkTrayHighlight.Size = new System.Drawing.Size(103, 17);
|
||||
this.checkTrayHighlight.TabIndex = 2;
|
||||
this.checkTrayHighlight.Text = "Enable Highlight";
|
||||
this.toolTip.SetToolTip(this.checkTrayHighlight, "Highlights the tray icon if there are new tweets.\r\nOnly works for columns with po" +
|
||||
"pup or audio notifications.\r\nThe icon resets when the main window is restored.");
|
||||
this.toolTip.SetToolTip(this.checkTrayHighlight, "Highlights the tray icon if there are new tweets.\r\nOnly works for columns with popup or audio notifications.\r\nThe icon resets when the main window is restored.");
|
||||
this.checkTrayHighlight.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// checkSpellCheck
|
||||
@@ -107,8 +105,7 @@
|
||||
this.checkUpdateNotifications.Size = new System.Drawing.Size(165, 17);
|
||||
this.checkUpdateNotifications.TabIndex = 0;
|
||||
this.checkUpdateNotifications.Text = "Check Updates Automatically";
|
||||
this.toolTip.SetToolTip(this.checkUpdateNotifications, "Checks for updates every hour.\r\nIf an update is dismissed, it will not appear aga" +
|
||||
"in.");
|
||||
this.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
|
||||
@@ -143,8 +140,7 @@
|
||||
this.checkSwitchAccountSelectors.Size = new System.Drawing.Size(172, 17);
|
||||
this.checkSwitchAccountSelectors.TabIndex = 1;
|
||||
this.checkSwitchAccountSelectors.Text = "Shift Selects Multiple Accounts";
|
||||
this.toolTip.SetToolTip(this.checkSwitchAccountSelectors, "When (re)tweeting, click to select a single account or hold Shift to\r\nselect mult" +
|
||||
"iple accounts, instead of TweetDeck\'s default behavior.");
|
||||
this.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;
|
||||
//
|
||||
// labelTrayIcon
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Updates;
|
||||
using TweetDuck.Updates.Events;
|
||||
@@ -89,7 +88,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
updateCheckEventId = updates.Check(true);
|
||||
|
||||
if (updateCheckEventId == -1){
|
||||
MessageBox.Show("Sorry, your system is no longer supported.", "Unsupported System", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
FormMessage.Warning("Unsupported System", "Sorry, your system is no longer supported.", FormMessage.OK);
|
||||
}
|
||||
else{
|
||||
btnCheckUpdates.Enabled = false;
|
||||
@@ -98,13 +97,15 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
}
|
||||
|
||||
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){
|
||||
if (e.EventId == updateCheckEventId){
|
||||
btnCheckUpdates.Enabled = true;
|
||||
this.InvokeAsyncSafe(() => {
|
||||
if (e.EventId == updateCheckEventId){
|
||||
btnCheckUpdates.Enabled = true;
|
||||
|
||||
if (!e.UpdateAvailable){
|
||||
MessageBox.Show("Your version of "+Program.BrandName+" is up to date.", "No Updates Available", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
if (!e.UpdateAvailable){
|
||||
FormMessage.Information("No Updates Available", "Your version of TweetDuck is up to date.", FormMessage.OK);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void zoomUpdateTimer_Tick(object sender, EventArgs e){
|
||||
|
123
Core/Other/Settings/TabSettingsNotifications.Designer.cs
generated
123
Core/Other/Settings/TabSettingsNotifications.Designer.cs
generated
@@ -48,6 +48,8 @@
|
||||
this.checkTimerCountDown = new System.Windows.Forms.CheckBox();
|
||||
this.checkNotificationTimer = new System.Windows.Forms.CheckBox();
|
||||
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
||||
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.labelScrollSpeedValue = new System.Windows.Forms.Label();
|
||||
@@ -58,8 +60,9 @@
|
||||
this.panelTimer = new System.Windows.Forms.Panel();
|
||||
this.labelDuration = new System.Windows.Forms.Label();
|
||||
this.labelTimer = new System.Windows.Forms.Label();
|
||||
this.labelMiscellaneous = new System.Windows.Forms.Label();
|
||||
this.labelSize = new System.Windows.Forms.Label();
|
||||
this.panelMiscellaneous = new System.Windows.Forms.Panel();
|
||||
this.durationUpdateTimer = new System.Windows.Forms.Timer(this.components);
|
||||
((System.ComponentModel.ISupportInitialize)(this.trackBarEdgeDistance)).BeginInit();
|
||||
this.tableLayoutDurationButtons.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(this.trackBarDuration)).BeginInit();
|
||||
@@ -121,7 +124,7 @@
|
||||
this.radioLocCustom.TabIndex = 4;
|
||||
this.radioLocCustom.TabStop = true;
|
||||
this.radioLocCustom.Text = "Custom";
|
||||
this.toolTip.SetToolTip(this.radioLocCustom, "Drag the notification window to the desired location.");
|
||||
this.toolTip.SetToolTip(this.radioLocCustom, "Drag the example notification window to the desired location.");
|
||||
this.radioLocCustom.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// radioLocBR
|
||||
@@ -288,42 +291,40 @@
|
||||
this.checkColumnName.Size = new System.Drawing.Size(129, 17);
|
||||
this.checkColumnName.TabIndex = 0;
|
||||
this.checkColumnName.Text = "Display Column Name";
|
||||
this.toolTip.SetToolTip(this.checkColumnName, "Shows column name each notification originated\r\nfrom in the notification window t" +
|
||||
"itle.");
|
||||
this.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, 60);
|
||||
this.labelIdlePause.Location = new System.Drawing.Point(3, 83);
|
||||
this.labelIdlePause.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0);
|
||||
this.labelIdlePause.Name = "labelIdlePause";
|
||||
this.labelIdlePause.Size = new System.Drawing.Size(89, 13);
|
||||
this.labelIdlePause.TabIndex = 2;
|
||||
this.labelIdlePause.TabIndex = 3;
|
||||
this.labelIdlePause.Text = "Pause When Idle";
|
||||
//
|
||||
// comboBoxIdlePause
|
||||
//
|
||||
this.comboBoxIdlePause.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this.comboBoxIdlePause.FormattingEnabled = true;
|
||||
this.comboBoxIdlePause.Location = new System.Drawing.Point(5, 76);
|
||||
this.comboBoxIdlePause.Location = new System.Drawing.Point(5, 99);
|
||||
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 = 3;
|
||||
this.comboBoxIdlePause.TabIndex = 4;
|
||||
this.toolTip.SetToolTip(this.comboBoxIdlePause, "Pauses new notifications after going idle for a set amount of time.");
|
||||
//
|
||||
// checkNonIntrusive
|
||||
//
|
||||
this.checkNonIntrusive.AutoSize = true;
|
||||
this.checkNonIntrusive.Location = new System.Drawing.Point(6, 5);
|
||||
this.checkNonIntrusive.Margin = new System.Windows.Forms.Padding(6, 5, 3, 3);
|
||||
this.checkNonIntrusive.Location = new System.Drawing.Point(6, 51);
|
||||
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 = 0;
|
||||
this.checkNonIntrusive.TabIndex = 2;
|
||||
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.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
|
||||
@@ -349,6 +350,30 @@
|
||||
this.checkNotificationTimer.Text = "Display Notification Timer";
|
||||
this.checkNotificationTimer.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// radioSizeAuto
|
||||
//
|
||||
this.radioSizeAuto.Location = new System.Drawing.Point(6, 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.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;
|
||||
@@ -366,20 +391,21 @@
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
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, 103);
|
||||
this.panelGeneral.Size = new System.Drawing.Size(322, 126);
|
||||
this.panelGeneral.TabIndex = 1;
|
||||
//
|
||||
// labelScrollSpeedValue
|
||||
//
|
||||
this.labelScrollSpeedValue.Location = new System.Drawing.Point(147, 54);
|
||||
this.labelScrollSpeedValue.Location = new System.Drawing.Point(147, 53);
|
||||
this.labelScrollSpeedValue.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0);
|
||||
this.labelScrollSpeedValue.Name = "labelScrollSpeedValue";
|
||||
this.labelScrollSpeedValue.Size = new System.Drawing.Size(34, 13);
|
||||
this.labelScrollSpeedValue.TabIndex = 3;
|
||||
this.labelScrollSpeedValue.TabIndex = 4;
|
||||
this.labelScrollSpeedValue.Text = "100%";
|
||||
this.labelScrollSpeedValue.TextAlign = System.Drawing.ContentAlignment.TopRight;
|
||||
//
|
||||
@@ -387,35 +413,35 @@
|
||||
//
|
||||
this.trackBarScrollSpeed.AutoSize = false;
|
||||
this.trackBarScrollSpeed.LargeChange = 25;
|
||||
this.trackBarScrollSpeed.Location = new System.Drawing.Point(5, 53);
|
||||
this.trackBarScrollSpeed.Location = new System.Drawing.Point(5, 52);
|
||||
this.trackBarScrollSpeed.Maximum = 200;
|
||||
this.trackBarScrollSpeed.Minimum = 25;
|
||||
this.trackBarScrollSpeed.Name = "trackBarScrollSpeed";
|
||||
this.trackBarScrollSpeed.Size = new System.Drawing.Size(148, 30);
|
||||
this.trackBarScrollSpeed.SmallChange = 25;
|
||||
this.trackBarScrollSpeed.TabIndex = 2;
|
||||
this.trackBarScrollSpeed.SmallChange = 5;
|
||||
this.trackBarScrollSpeed.TabIndex = 3;
|
||||
this.trackBarScrollSpeed.TickFrequency = 25;
|
||||
this.trackBarScrollSpeed.Value = 100;
|
||||
//
|
||||
// labelScrollSpeed
|
||||
//
|
||||
this.labelScrollSpeed.AutoSize = true;
|
||||
this.labelScrollSpeed.Location = new System.Drawing.Point(3, 37);
|
||||
this.labelScrollSpeed.Location = new System.Drawing.Point(3, 36);
|
||||
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);
|
||||
this.labelScrollSpeed.TabIndex = 1;
|
||||
this.labelScrollSpeed.TabIndex = 2;
|
||||
this.labelScrollSpeed.Text = "Scroll Speed";
|
||||
//
|
||||
// labelLocation
|
||||
//
|
||||
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, 158);
|
||||
this.labelLocation.Location = new System.Drawing.Point(6, 372);
|
||||
this.labelLocation.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
|
||||
this.labelLocation.Name = "labelLocation";
|
||||
this.labelLocation.Size = new System.Drawing.Size(70, 20);
|
||||
this.labelLocation.TabIndex = 2;
|
||||
this.labelLocation.TabIndex = 4;
|
||||
this.labelLocation.Text = "Location";
|
||||
//
|
||||
// panelLocation
|
||||
@@ -432,10 +458,10 @@
|
||||
this.panelLocation.Controls.Add(this.radioLocBL);
|
||||
this.panelLocation.Controls.Add(this.radioLocCustom);
|
||||
this.panelLocation.Controls.Add(this.radioLocBR);
|
||||
this.panelLocation.Location = new System.Drawing.Point(9, 181);
|
||||
this.panelLocation.Location = new System.Drawing.Point(9, 395);
|
||||
this.panelLocation.Name = "panelLocation";
|
||||
this.panelLocation.Size = new System.Drawing.Size(322, 165);
|
||||
this.panelLocation.TabIndex = 3;
|
||||
this.panelLocation.TabIndex = 5;
|
||||
//
|
||||
// panelTimer
|
||||
//
|
||||
@@ -447,10 +473,10 @@
|
||||
this.panelTimer.Controls.Add(this.checkTimerCountDown);
|
||||
this.panelTimer.Controls.Add(this.labelDurationValue);
|
||||
this.panelTimer.Controls.Add(this.trackBarDuration);
|
||||
this.panelTimer.Location = new System.Drawing.Point(9, 393);
|
||||
this.panelTimer.Location = new System.Drawing.Point(9, 204);
|
||||
this.panelTimer.Name = "panelTimer";
|
||||
this.panelTimer.Size = new System.Drawing.Size(322, 144);
|
||||
this.panelTimer.TabIndex = 5;
|
||||
this.panelTimer.TabIndex = 3;
|
||||
//
|
||||
// labelDuration
|
||||
//
|
||||
@@ -466,43 +492,49 @@
|
||||
//
|
||||
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, 370);
|
||||
this.labelTimer.Location = new System.Drawing.Point(6, 181);
|
||||
this.labelTimer.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
|
||||
this.labelTimer.Name = "labelTimer";
|
||||
this.labelTimer.Size = new System.Drawing.Size(48, 20);
|
||||
this.labelTimer.TabIndex = 4;
|
||||
this.labelTimer.TabIndex = 2;
|
||||
this.labelTimer.Text = "Timer";
|
||||
//
|
||||
// labelMiscellaneous
|
||||
// labelSize
|
||||
//
|
||||
this.labelMiscellaneous.AutoSize = true;
|
||||
this.labelMiscellaneous.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||
this.labelMiscellaneous.Location = new System.Drawing.Point(6, 561);
|
||||
this.labelMiscellaneous.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
|
||||
this.labelMiscellaneous.Name = "labelMiscellaneous";
|
||||
this.labelMiscellaneous.Size = new System.Drawing.Size(109, 20);
|
||||
this.labelMiscellaneous.TabIndex = 6;
|
||||
this.labelMiscellaneous.Text = "Miscellaneous";
|
||||
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, 584);
|
||||
this.labelSize.Margin = new System.Windows.Forms.Padding(0, 21, 0, 0);
|
||||
this.labelSize.Name = "labelSize";
|
||||
this.labelSize.Size = new System.Drawing.Size(40, 20);
|
||||
this.labelSize.TabIndex = 6;
|
||||
this.labelSize.Text = "Size";
|
||||
//
|
||||
// panelMiscellaneous
|
||||
//
|
||||
this.panelMiscellaneous.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.checkNonIntrusive);
|
||||
this.panelMiscellaneous.Controls.Add(this.labelScrollSpeed);
|
||||
this.panelMiscellaneous.Location = new System.Drawing.Point(9, 584);
|
||||
this.panelMiscellaneous.Location = new System.Drawing.Point(9, 607);
|
||||
this.panelMiscellaneous.Name = "panelMiscellaneous";
|
||||
this.panelMiscellaneous.Size = new System.Drawing.Size(322, 90);
|
||||
this.panelMiscellaneous.Size = new System.Drawing.Size(322, 92);
|
||||
this.panelMiscellaneous.TabIndex = 7;
|
||||
//
|
||||
// durationUpdateTimer
|
||||
//
|
||||
this.durationUpdateTimer.Interval = 200;
|
||||
this.durationUpdateTimer.Tick += new System.EventHandler(this.durationUpdateTimer_Tick);
|
||||
//
|
||||
// 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.labelMiscellaneous);
|
||||
this.Controls.Add(this.labelSize);
|
||||
this.Controls.Add(this.labelTimer);
|
||||
this.Controls.Add(this.panelLocation);
|
||||
this.Controls.Add(this.labelLocation);
|
||||
@@ -510,7 +542,7 @@
|
||||
this.Controls.Add(this.labelGeneral);
|
||||
this.Controls.Add(this.panelTimer);
|
||||
this.Name = "TabSettingsNotifications";
|
||||
this.Size = new System.Drawing.Size(340, 684);
|
||||
this.Size = new System.Drawing.Size(340, 708);
|
||||
this.ParentChanged += new System.EventHandler(this.TabSettingsNotifications_ParentChanged);
|
||||
((System.ComponentModel.ISupportInitialize)(this.trackBarEdgeDistance)).EndInit();
|
||||
this.tableLayoutDurationButtons.ResumeLayout(false);
|
||||
@@ -563,8 +595,11 @@
|
||||
private System.Windows.Forms.Label labelScrollSpeedValue;
|
||||
private System.Windows.Forms.TrackBar trackBarScrollSpeed;
|
||||
private System.Windows.Forms.Label labelScrollSpeed;
|
||||
private System.Windows.Forms.Label labelMiscellaneous;
|
||||
private System.Windows.Forms.Label labelSize;
|
||||
private System.Windows.Forms.Panel panelMiscellaneous;
|
||||
private System.Windows.Forms.Label labelDuration;
|
||||
private System.Windows.Forms.Timer durationUpdateTimer;
|
||||
private System.Windows.Forms.RadioButton radioSizeCustom;
|
||||
private System.Windows.Forms.RadioButton radioSizeAuto;
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Notification;
|
||||
@@ -14,16 +13,13 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
InitializeComponent();
|
||||
|
||||
this.notification = notification;
|
||||
this.notification.CanMoveWindow = () => radioLocCustom.Checked;
|
||||
|
||||
this.notification.Move += (sender, args) => {
|
||||
if (radioLocCustom.Checked && this.notification.Location != ControlExtensions.InvisibleLocation){
|
||||
Config.CustomNotificationPosition = this.notification.Location;
|
||||
}
|
||||
};
|
||||
|
||||
this.notification.Initialized += (sender, args) => {
|
||||
this.InvokeAsyncSafe(() => this.notification.ShowNotificationForSettings(true));
|
||||
this.InvokeAsyncSafe(() => {
|
||||
this.notification.ShowNotificationForSettings(true);
|
||||
this.notification.Move += notification_Move;
|
||||
this.notification.ResizeEnd += notification_ResizeEnd;
|
||||
});
|
||||
};
|
||||
|
||||
this.notification.Activated += notification_Activated;
|
||||
@@ -38,6 +34,11 @@ 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);
|
||||
@@ -50,7 +51,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
comboBoxIdlePause.Items.Add("5 minutes");
|
||||
comboBoxIdlePause.SelectedIndex = Math.Max(0, Array.FindIndex(IdlePauseSeconds, val => val == Config.NotificationIdlePauseSeconds));
|
||||
|
||||
comboBoxDisplay.Items.Add("(Same As "+Program.BrandName+")");
|
||||
comboBoxDisplay.Items.Add("(Same as TweetDuck)");
|
||||
|
||||
foreach(Screen screen in Screen.AllScreens){
|
||||
comboBoxDisplay.Items.Add(screen.DeviceName.TrimStart('\\', '.')+" ("+screen.Bounds.Width+"x"+screen.Bounds.Height+")");
|
||||
@@ -66,10 +67,13 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
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;
|
||||
|
||||
Disposed += (sender, args) => this.notification.Dispose();
|
||||
}
|
||||
@@ -81,6 +85,9 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
radioLocBR.CheckedChanged += radioLoc_CheckedChanged;
|
||||
radioLocCustom.Click += radioLocCustom_Click;
|
||||
|
||||
radioSizeAuto.CheckedChanged += radioSize_CheckedChanged;
|
||||
radioSizeCustom.Click += radioSizeCustom_Click;
|
||||
|
||||
trackBarDuration.ValueChanged += trackBarDuration_ValueChanged;
|
||||
btnDurationShort.Click += btnDurationShort_Click;
|
||||
btnDurationMedium.Click += btnDurationMedium_Click;
|
||||
@@ -113,6 +120,19 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
notification.Activated -= notification_Activated;
|
||||
}
|
||||
|
||||
private void notification_Move(object sender, EventArgs e){
|
||||
if (radioLocCustom.Checked && notification.Location != ControlExtensions.InvisibleLocation){
|
||||
Config.CustomNotificationPosition = notification.Location;
|
||||
}
|
||||
}
|
||||
|
||||
private void notification_ResizeEnd(object sender, EventArgs e){
|
||||
if (radioSizeCustom.Checked){
|
||||
Config.CustomNotificationSize = notification.BrowserSize;
|
||||
notification.ShowNotificationForSettings(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void radioLoc_CheckedChanged(object sender, EventArgs e){
|
||||
if (radioLocTL.Checked)Config.NotificationPosition = TweetNotification.Position.TopLeft;
|
||||
else if (radioLocTR.Checked)Config.NotificationPosition = TweetNotification.Position.TopRight;
|
||||
@@ -133,7 +153,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = false;
|
||||
notification.ShowNotificationForSettings(false);
|
||||
|
||||
if (notification.IsFullyOutsideView() && MessageBox.Show("The notification seems to be outside of view, would you like to reset its position?", "Notification is outside view", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
|
||||
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;
|
||||
notification.MoveToVisibleLocation();
|
||||
|
||||
@@ -144,11 +164,30 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
}
|
||||
}
|
||||
|
||||
private void radioSize_CheckedChanged(object sender, EventArgs e){
|
||||
if (radioSizeAuto.Checked)Config.NotificationSize = TweetNotification.Size.Auto;
|
||||
|
||||
notification.ShowNotificationForSettings(false);
|
||||
notification.CanResizeWindow = false; // must be after ShowNotificationForSettings
|
||||
}
|
||||
|
||||
private void radioSizeCustom_Click(object sender, EventArgs e){
|
||||
if (!Config.IsCustomNotificationSizeSet){
|
||||
Config.CustomNotificationSize = notification.BrowserSize;
|
||||
}
|
||||
|
||||
Config.NotificationSize = TweetNotification.Size.Custom;
|
||||
|
||||
notification.CanResizeWindow = true;
|
||||
notification.ShowNotificationForSettings(false);
|
||||
}
|
||||
|
||||
private void trackBarDuration_ValueChanged(object sender, EventArgs e){
|
||||
durationUpdateTimer.Stop();
|
||||
durationUpdateTimer.Start();
|
||||
|
||||
Config.NotificationDurationValue = trackBarDuration.Value;
|
||||
labelDurationValue.Text = Config.NotificationDurationValue+" ms/c";
|
||||
|
||||
notification.ShowNotificationForSettings(true);
|
||||
}
|
||||
|
||||
private void btnDurationShort_Click(object sender, EventArgs e){
|
||||
@@ -193,7 +232,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -204,9 +243,14 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private void durationUpdateTimer_Tick(object sender, EventArgs e){
|
||||
notification.ShowNotificationForSettings(true);
|
||||
durationUpdateTimer.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -77,6 +77,7 @@
|
||||
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
|
||||
//
|
||||
|
@@ -3,7 +3,7 @@ using System.Drawing;
|
||||
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{
|
||||
@@ -44,7 +44,7 @@ namespace TweetDuck.Core.Other.Settings{
|
||||
}
|
||||
|
||||
private void sound_PlaybackError(object sender, PlaybackErrorEventArgs e){
|
||||
MessageBox.Show("Could not play custom notification sound."+Environment.NewLine+e.Message, "Notification Sound Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
FormMessage.Error("Notification Sound Error", "Could not play custom notification sound.\n"+e.Message, FormMessage.OK);
|
||||
}
|
||||
|
||||
private void btnBrowseSound_Click(object sender, EventArgs e){
|
||||
|
26
Core/Utils/BrowserProcesses.cs
Normal file
26
Core/Utils/BrowserProcesses.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using CefSharp;
|
||||
|
||||
namespace TweetDuck.Core.Utils{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,24 +1,26 @@
|
||||
using CefSharp;
|
||||
using System;
|
||||
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;
|
||||
using TweetDuck.Core.Other;
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +34,24 @@ namespace TweetDuck.Core.Utils{
|
||||
"tweetdeck", "TweetDeck", "tweetduck", "TweetDuck", "TD"
|
||||
};
|
||||
|
||||
public static void SetupCefArgs(IDictionary<string, string> args){
|
||||
if (!Program.SystemConfig.HardwareAcceleration){
|
||||
args["disable-gpu"] = "1";
|
||||
args["disable-gpu-vsync"] = "1";
|
||||
}
|
||||
|
||||
args["disable-extensions"] = "1";
|
||||
args["disable-plugins-discovery"] = "1";
|
||||
args["enable-system-flash"] = "0";
|
||||
|
||||
if (args.TryGetValue("js-flags", out string jsFlags)){
|
||||
args["js-flags"] = "--expose-gc "+jsFlags;
|
||||
}
|
||||
else{
|
||||
args["js-flags"] = "--expose-gc";
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsValidUrl(string url){
|
||||
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){
|
||||
string scheme = uri.Scheme;
|
||||
@@ -48,7 +68,7 @@ namespace TweetDuck.Core.Utils{
|
||||
OpenExternalBrowserUnsafe(url);
|
||||
}
|
||||
else{
|
||||
MessageBox.Show("A potentially malicious URL was blocked from opening:"+Environment.NewLine+url, "Blocked URL", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
FormMessage.Warning("Blocked URL", "A potentially malicious URL was blocked from opening:\n"+url, FormMessage.OK);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,12 +81,8 @@ namespace TweetDuck.Core.Utils{
|
||||
return string.IsNullOrEmpty(file) ? null : file;
|
||||
}
|
||||
|
||||
public static string ConvertPascalCaseToScreamingSnakeCase(string str){
|
||||
return Regex.Replace(str, @"(\p{Ll})(\P{Ll})|(\P{Ll})(\P{Ll}\p{Ll})", "$1$3_$2$4").ToUpperInvariant();
|
||||
}
|
||||
|
||||
public static string GetErrorName(CefErrorCode code){
|
||||
return ConvertPascalCaseToScreamingSnakeCase(Enum.GetName(typeof(CefErrorCode), code) ?? string.Empty);
|
||||
return StringUtils.ConvertPascalCaseToScreamingSnakeCase(Enum.GetName(typeof(CefErrorCode), code) ?? string.Empty);
|
||||
}
|
||||
|
||||
public static WebClient DownloadFileAsync(string url, string target, Action onSuccess, Action<Exception> onFailure){
|
||||
@@ -93,6 +109,10 @@ namespace TweetDuck.Core.Utils{
|
||||
return client;
|
||||
}
|
||||
|
||||
public static int Scale(int baseValue, double scaleFactor){
|
||||
return (int)Math.Round(baseValue*scaleFactor);
|
||||
}
|
||||
|
||||
public static void SetZoomLevel(IBrowser browser, int percentage){
|
||||
browser.GetHost().SetZoomLevel(Math.Log(percentage/100.0, 1.2));
|
||||
}
|
||||
|
@@ -1,9 +1,10 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using TweetDuck.Data;
|
||||
|
||||
namespace TweetDuck.Core.Utils{
|
||||
static class CommandLineArgsParser{
|
||||
private static readonly Lazy<Regex> SplitRegex = new Lazy<Regex>(() => new Regex(@"([^=\s]+(?:=(?:[^ ]*""[^""]*?""[^ ]*|[^ ]*))?)", RegexOptions.Compiled), false);
|
||||
private static readonly Lazy<Regex> SplitRegex = new Lazy<Regex>(() => new Regex(@"([^=\s]+(?:=(?:\S*""[^""]*?""\S*|\S*))?)", RegexOptions.Compiled), false);
|
||||
|
||||
public static CommandLineArgs ReadCefArguments(string argumentString){
|
||||
CommandLineArgs args = new CommandLineArgs();
|
||||
|
91
Core/Utils/MemoryUsageTracker.cs
Normal file
91
Core/Utils/MemoryUsageTracker.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Timers;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace TweetDuck.Core.Utils{
|
||||
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?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -67,7 +67,10 @@ namespace TweetDuck.Core.Utils{
|
||||
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, UIntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern uint RegisterWindowMessage(string messageName);
|
||||
|
20
Core/Utils/StringUtils.cs
Normal file
20
Core/Utils/StringUtils.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Management;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
@@ -11,13 +12,29 @@ namespace TweetDuck.Core.Utils{
|
||||
private static readonly Lazy<Regex> RegexStripHtmlStyles = new Lazy<Regex>(() => new Regex(@"\s?(?:style|class)="".*?"""), false);
|
||||
private static readonly Lazy<Regex> RegexOffsetClipboardHtml = new Lazy<Regex>(() => new Regex(@"(?<=EndHTML:|EndFragment:)(\d+)"), false);
|
||||
|
||||
public static int CurrentProcessID { get; }
|
||||
public static bool ShouldAvoidToolWindow { get; }
|
||||
|
||||
static WindowsUtils(){
|
||||
using(Process me = Process.GetCurrentProcess()){
|
||||
CurrentProcessID = me.Id;
|
||||
}
|
||||
|
||||
Version ver = Environment.OSVersion.Version;
|
||||
ShouldAvoidToolWindow = ver.Major == 6 && ver.Minor == 2; // windows 8/10
|
||||
}
|
||||
|
||||
public static void CreateDirectoryForFile(string file){
|
||||
string dir = Path.GetDirectoryName(file);
|
||||
|
||||
if (dir == null){
|
||||
throw new ArgumentException("Invalid file path: "+file);
|
||||
}
|
||||
else if (dir.Length > 0){
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool CheckFolderWritePermission(string path){
|
||||
string testFile = Path.Combine(path, ".test");
|
||||
|
||||
@@ -72,6 +89,20 @@ 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)){
|
||||
return;
|
||||
@@ -105,7 +136,7 @@ namespace TweetDuck.Core.Utils{
|
||||
try{
|
||||
Clipboard.SetDataObject(obj);
|
||||
}catch(ExternalException e){
|
||||
Program.Reporter.HandleException("Clipboard Error", Program.BrandName+" could not access the clipboard as it is currently used by another process.", true, e);
|
||||
Program.Reporter.HandleException("Clipboard Error", "TweetDuck could not access the clipboard as it is currently used by another process.", true, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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 @@ namespace TweetDuck.Core.Other.Settings.Export{
|
||||
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 @@ namespace TweetDuck.Core.Other.Settings.Export{
|
||||
|
||||
public string KeyName{
|
||||
get{
|
||||
int index = Identifier.IndexOf(KeySeparator);
|
||||
return index == -1 ? Identifier : Identifier.Substring(0, index);
|
||||
return StringUtils.ExtractBefore(Identifier, KeySeparator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,11 +120,7 @@ namespace TweetDuck.Core.Other.Settings.Export{
|
||||
|
||||
public void WriteToFile(string path, bool createDirectory){
|
||||
if (createDirectory){
|
||||
string dir = Path.GetDirectoryName(path);
|
||||
|
||||
if (!string.IsNullOrEmpty(dir)){
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
WindowsUtils.CreateDirectoryForFile(path);
|
||||
}
|
||||
|
||||
File.WriteAllBytes(path, contents);
|
@@ -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 @@ namespace TweetDuck.Core.Utils{
|
||||
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(){
|
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
|
||||
namespace TweetDuck.Core.Utils{
|
||||
class InjectedHTML{
|
||||
namespace TweetDuck.Data{
|
||||
sealed class InjectedHTML{
|
||||
public enum Position{
|
||||
Before, After
|
||||
}
|
133
Data/Serialization/FileSerializer.cs
Normal file
133
Data/Serialization/FileSerializer.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
5
Data/Serialization/ISerializedObject.cs
Normal file
5
Data/Serialization/ISerializedObject.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace TweetDuck.Data.Serialization{
|
||||
interface ISerializedObject{
|
||||
bool OnReadUnknownProperty(string property, string value);
|
||||
}
|
||||
}
|
8
Data/Serialization/ITypeConverter.cs
Normal file
8
Data/Serialization/ITypeConverter.cs
Normal 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);
|
||||
}
|
||||
}
|
31
Data/Serialization/SingleTypeConverter.cs
Normal file
31
Data/Serialization/SingleTypeConverter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
@@ -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 @@ namespace TweetDuck.Core.Utils{
|
||||
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'
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
1
Plugins/Controls/PluginControl.Designer.cs
generated
1
Plugins/Controls/PluginControl.Designer.cs
generated
@@ -152,7 +152,6 @@
|
||||
| System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.labelType.BackColor = System.Drawing.Color.DarkGray;
|
||||
this.labelType.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||
this.labelType.LineHeight = 9;
|
||||
this.labelType.Location = new System.Drawing.Point(0, 0);
|
||||
this.labelType.Name = "labelType";
|
||||
this.labelType.Size = new System.Drawing.Size(18, 109);
|
||||
|
@@ -3,6 +3,7 @@ using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
|
||||
@@ -11,6 +12,8 @@ namespace TweetDuck.Plugins.Controls{
|
||||
private readonly PluginManager pluginManager;
|
||||
private readonly Plugin plugin;
|
||||
|
||||
private readonly float dpiScale;
|
||||
|
||||
public PluginControl(){
|
||||
InitializeComponent();
|
||||
}
|
||||
@@ -19,11 +22,15 @@ namespace TweetDuck.Plugins.Controls{
|
||||
this.pluginManager = pluginManager;
|
||||
this.plugin = plugin;
|
||||
|
||||
this.dpiScale = this.GetDPIScale();
|
||||
|
||||
this.labelName.Text = plugin.Name;
|
||||
this.labelDescription.Text = plugin.CanRun ? plugin.Description : "This plugin requires "+Program.BrandName+" "+plugin.RequiredVersion+" or newer.";
|
||||
this.labelDescription.Text = plugin.CanRun ? plugin.Description : "This plugin requires TweetDuck "+plugin.RequiredVersion+" or newer.";
|
||||
this.labelVersion.Text = plugin.Version;
|
||||
this.labelAuthor.Text = plugin.Author;
|
||||
this.labelWebsite.Text = plugin.Website;
|
||||
|
||||
this.labelType.LineHeight = BrowserUtils.Scale(9, dpiScale);
|
||||
|
||||
UpdatePluginState();
|
||||
|
||||
@@ -40,7 +47,7 @@ namespace TweetDuck.Plugins.Controls{
|
||||
}
|
||||
else{
|
||||
labelDescription.MaximumSize = new Size(panelDescription.Width-SystemInformation.VerticalScrollBarWidth, 0);
|
||||
Height = Math.Min(MinimumSize.Height+9+labelDescription.Height, MaximumSize.Height);
|
||||
Height = Math.Min(MinimumSize.Height+BrowserUtils.Scale(9, dpiScale)+labelDescription.Height, MaximumSize.Height);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -6,7 +6,7 @@ using System.Text;
|
||||
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 @@ namespace TweetDuck.Plugins{
|
||||
}
|
||||
|
||||
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 @@ namespace TweetDuck.Plugins{
|
||||
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])){
|
||||
|
@@ -3,11 +3,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 +48,9 @@ namespace TweetDuck.Plugins{
|
||||
|
||||
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 TweetDuck error.");
|
||||
}
|
||||
}
|
||||
else{
|
||||
@@ -67,9 +68,9 @@ namespace TweetDuck.Plugins{
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,9 +79,7 @@ namespace TweetDuck.Plugins{
|
||||
public void WriteFile(int token, string path, string contents){
|
||||
string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
|
||||
|
||||
// ReSharper disable once AssignNullToNotNullAttribute
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
|
||||
|
||||
WindowsUtils.CreateDirectoryForFile(fullPath);
|
||||
File.WriteAllText(fullPath, contents, Encoding.UTF8);
|
||||
fileCache[token, SanitizeCacheKey(path)] = contents;
|
||||
}
|
||||
|
@@ -28,8 +28,7 @@ namespace TweetDuck.Plugins{
|
||||
|
||||
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 @@ namespace TweetDuck.Plugins{
|
||||
|
||||
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){
|
||||
|
@@ -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 @@ namespace TweetDuck.Plugins{
|
||||
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 @@ namespace TweetDuck.Plugins{
|
||||
|
||||
/* PluginGen
|
||||
|
||||
(function(%params, $i, $d){
|
||||
(function(%params, $d){
|
||||
let tmp = {
|
||||
id: '%id',
|
||||
obj: new class extends PluginBase{%contents}
|
||||
|
84
Program.cs
84
Program.cs
@@ -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.Handling;
|
||||
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;
|
||||
@@ -20,8 +22,8 @@ namespace TweetDuck{
|
||||
public const string BrandName = "TweetDuck";
|
||||
public const string Website = "https://tweetduck.chylex.com";
|
||||
|
||||
public const string VersionTag = "1.8";
|
||||
public const string VersionFull = "1.8.0.0";
|
||||
public const string VersionTag = "1.8.3";
|
||||
public const string VersionFull = "1.8.3.0";
|
||||
|
||||
public static readonly Version Version = new Version(VersionTag);
|
||||
public static readonly bool IsPortable = File.Exists("makeportable");
|
||||
@@ -43,31 +45,40 @@ namespace TweetDuck{
|
||||
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;
|
||||
|
||||
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("TweetDuck Has Failed :(");
|
||||
}
|
||||
|
||||
[STAThread]
|
||||
private static void Main(){
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
|
||||
|
||||
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore");
|
||||
SubProcessMessage = NativeMethods.RegisterWindowMessage("TweetDuckSubProcess");
|
||||
|
||||
if (!WindowsUtils.CheckFolderWritePermission(StoragePath)){
|
||||
MessageBox.Show(BrandName+" does not have write permissions to the storage folder: "+StoragePath, "Permission Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
FormMessage.Warning("Permission Error", "TweetDuck does not have write permissions to the storage folder: "+StoragePath, FormMessage.OK);
|
||||
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();
|
||||
@@ -76,21 +87,16 @@ namespace TweetDuck{
|
||||
break;
|
||||
}
|
||||
else if (lockResult == LockManager.Result.Fail){
|
||||
MessageBox.Show("An unknown error occurred accessing the data folder. Please, make sure "+BrandName+" is not already running. If the problem persists, try restarting your system.", BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
FormMessage.Error("TweetDuck Has Failed :(", "An unknown error occurred accessing the data folder. Please, make sure TweetDuck is not already running. If the problem persists, try restarting your system.", FormMessage.OK);
|
||||
return;
|
||||
}
|
||||
else if (attempt == 20){
|
||||
using(FormMessage form = new FormMessage(BrandName+" Cannot Restart", BrandName+" is taking too long to close.", MessageBoxIcon.Warning)){
|
||||
form.CancelButton = form.AddButton("Exit");
|
||||
form.ActiveControl = form.AddButton("Retry", DialogResult.Retry);
|
||||
|
||||
if (form.ShowDialog() == DialogResult.Retry){
|
||||
attempt /= 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
return;
|
||||
if (FormMessage.Warning("TweetDuck Cannot Restart", "TweetDuck is taking too long to close.", FormMessage.Retry, FormMessage.Exit)){
|
||||
attempt /= 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else Thread.Sleep(500);
|
||||
}
|
||||
@@ -100,19 +106,19 @@ namespace TweetDuck{
|
||||
|
||||
if (lockResult == LockManager.Result.HasProcess){
|
||||
if (LockManager.LockingProcess.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
|
||||
NativeMethods.SendMessage(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();
|
||||
return LockManager.LockingProcess.HasExited || (LockManager.LockingProcess.MainWindowHandle != IntPtr.Zero && LockManager.LockingProcess.Responding);
|
||||
}, 2000, 250)){
|
||||
return; // should trigger on first attempt if succeeded, but wait just in case
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (MessageBox.Show("Another instance of "+BrandName+" is already running.\r\nDo you want to close it?", BrandName+" is Already Running", MessageBoxButtons.YesNo, MessageBoxIcon.Error, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
|
||||
if (FormMessage.Error("TweetDuck is Already Running", "Another instance of TweetDuck is already running.\nDo you want to close it?", FormMessage.Yes, FormMessage.No)){
|
||||
if (!LockManager.CloseLockingProcess(10000, 5000)){
|
||||
MessageBox.Show("Could not close the other process.", BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
FormMessage.Error("TweetDuck Has Failed :(", "Could not close the other process.", FormMessage.OK);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -121,7 +127,7 @@ namespace TweetDuck{
|
||||
else return;
|
||||
}
|
||||
else if (lockResult != LockManager.Result.Success){
|
||||
MessageBox.Show("An unknown error occurred accessing the data folder. Please, make sure "+BrandName+" is not already running. If the problem persists, try restarting your system.", BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
FormMessage.Error("TweetDuck Has Failed :(", "An unknown error occurred accessing the data folder. Please, make sure TweetDuck is not already running. If the problem persists, try restarting your system.", FormMessage.OK);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -152,16 +158,9 @@ namespace TweetDuck{
|
||||
};
|
||||
|
||||
CommandLineArgsParser.ReadCefArguments(UserConfig.CustomCefArgs).ToDictionary(settings.CefCommandLineArgs);
|
||||
|
||||
if (!SystemConfig.HardwareAcceleration){
|
||||
settings.CefCommandLineArgs["disable-gpu"] = "1";
|
||||
settings.CefCommandLineArgs["disable-gpu-vsync"] = "1";
|
||||
}
|
||||
BrowserUtils.SetupCefArgs(settings.CefCommandLineArgs);
|
||||
|
||||
settings.CefCommandLineArgs["disable-extensions"] = "1";
|
||||
settings.CefCommandLineArgs["disable-plugins-discovery"] = "1";
|
||||
settings.CefCommandLineArgs["enable-system-flash"] = "0";
|
||||
|
||||
Cef.EnableHighDPISupport();
|
||||
Cef.Initialize(settings, false, new BrowserProcessHandler());
|
||||
|
||||
Application.ApplicationExit += (sender, args) => ExitCleanup();
|
||||
@@ -171,12 +170,13 @@ namespace TweetDuck{
|
||||
plugins.Executed += plugins_Executed;
|
||||
plugins.Reload();
|
||||
|
||||
FormBrowser mainForm = new FormBrowser(plugins, new UpdaterSettings{
|
||||
UpdaterSettings updaterSettings = new UpdaterSettings{
|
||||
AllowPreReleases = Arguments.HasFlag(Arguments.ArgDebugUpdates),
|
||||
DismissedUpdate = UserConfig.DismissedUpdate,
|
||||
InstallerDownloadFolder = InstallerPath
|
||||
});
|
||||
};
|
||||
|
||||
FormBrowser mainForm = new FormBrowser(plugins, updaterSettings);
|
||||
Application.Run(mainForm);
|
||||
|
||||
if (mainForm.UpdateInstallerPath != null){
|
||||
@@ -193,15 +193,13 @@ namespace TweetDuck{
|
||||
|
||||
private static void plugins_Reloaded(object sender, PluginErrorEventArgs e){
|
||||
if (e.HasErrors){
|
||||
string doubleNL = Environment.NewLine+Environment.NewLine;
|
||||
MessageBox.Show("The following plugins will not be available until the issues are resolved:"+doubleNL+string.Join(doubleNL, e.Errors), "Error Loading Plugins", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
FormMessage.Error("Error Loading Plugins", "The following plugins will not be available until the issues are resolved:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK);
|
||||
}
|
||||
}
|
||||
|
||||
private static void plugins_Executed(object sender, PluginErrorEventArgs e){
|
||||
if (e.HasErrors){
|
||||
string doubleNL = Environment.NewLine+Environment.NewLine;
|
||||
MessageBox.Show("Failed to execute the following plugins:"+doubleNL+string.Join(doubleNL, e.Errors), "Error Executing Plugins", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
FormMessage.Error("Error Executing Plugins", "Failed to execute the following plugins:\n\n"+string.Join("\n\n", e.Errors), FormMessage.OK);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,11 +238,7 @@ namespace TweetDuck{
|
||||
ReloadConfig();
|
||||
}
|
||||
|
||||
public static void Restart(){
|
||||
Restart(new string[0]);
|
||||
}
|
||||
|
||||
public static void Restart(string[] extraArgs){
|
||||
public static void Restart(params string[] extraArgs){
|
||||
CommandLineArgs args = Arguments.GetCurrentClean();
|
||||
CommandLineArgs.ReadStringArray('-', extraArgs, args);
|
||||
RestartWithArgs(args);
|
||||
|
24
Reporter.cs
24
Reporter.cs
@@ -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 @@ namespace TweetDuck{
|
||||
|
||||
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 @@ namespace TweetDuck{
|
||||
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{
|
||||
@@ -46,22 +43,21 @@ namespace TweetDuck{
|
||||
public void HandleException(string caption, string message, bool canIgnore, Exception e){
|
||||
bool loggedSuccessfully = Log(e.ToString());
|
||||
|
||||
FormMessage form = new FormMessage(caption, message+"\r\nError: "+e.Message, canIgnore ? MessageBoxIcon.Warning : MessageBoxIcon.Error);
|
||||
FormMessage form = new FormMessage(caption, message+"\nError: "+e.Message, canIgnore ? MessageBoxIcon.Warning : MessageBoxIcon.Error);
|
||||
|
||||
Button btnExit = form.AddButton("Exit");
|
||||
Button btnIgnore = form.AddButton("Ignore", DialogResult.Ignore);
|
||||
Button btnExit = form.AddButton(FormMessage.Exit);
|
||||
Button btnIgnore = form.AddButton(FormMessage.Ignore, DialogResult.Ignore, ControlType.Cancel);
|
||||
|
||||
btnIgnore.Enabled = canIgnore;
|
||||
form.ActiveControl = canIgnore ? btnIgnore : btnExit;
|
||||
form.CancelButton = btnIgnore;
|
||||
|
||||
Button btnOpenLog = new Button{
|
||||
Anchor = AnchorStyles.Bottom | AnchorStyles.Left,
|
||||
Enabled = loggedSuccessfully,
|
||||
Font = SystemFonts.MessageBoxFont,
|
||||
Location = new Point(6, 12),
|
||||
Location = new Point(9, 12),
|
||||
Margin = new Padding(0, 0, 48, 0),
|
||||
Size = new Size(88, 26),
|
||||
Size = new Size(106, 26),
|
||||
Text = "Show Error Log",
|
||||
UseVisualStyleBackColor = true
|
||||
};
|
||||
@@ -87,9 +83,7 @@ namespace TweetDuck{
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
|
||||
FormMessage form = new FormMessage(caption, message, MessageBoxIcon.Error);
|
||||
form.ActiveControl = form.AddButton("Exit");
|
||||
form.ShowDialog();
|
||||
FormMessage.Error(caption, message, "Exit");
|
||||
|
||||
try{
|
||||
Process.GetCurrentProcess().Kill();
|
||||
|
@@ -9,7 +9,7 @@ Emoji keyboard
|
||||
chylex
|
||||
|
||||
[version]
|
||||
1.1
|
||||
1.2
|
||||
|
||||
[website]
|
||||
https://tweetduck.chylex.com
|
||||
|
@@ -306,6 +306,9 @@ ready(){
|
||||
case 2: this.emojiData3.push("___"); break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
else if (line[0] === '#'){
|
||||
if (line[1] === '1'){
|
||||
skinToneState = 1;
|
||||
}
|
||||
|
@@ -1,6 +1,9 @@
|
||||
Emoji list: http://unicode.org/emoji/charts/emoji-ordering.html
|
||||
Emoji order: http://unicode.org/emoji/charts/emoji-ordering.txt
|
||||
|
||||
The instructions contain search & replace regexes. Make sure your editor supports LF characters (\n).
|
||||
For Brackets, use Find -> Replace in Files with any regex that includes LF.
|
||||
|
||||
|
||||
------------------------
|
||||
Remove unnecessary info:
|
||||
@@ -8,8 +11,8 @@ Remove unnecessary info:
|
||||
Search: \s;.+?#.+?\s
|
||||
Replace: ;
|
||||
|
||||
Search: U+
|
||||
Replace:
|
||||
Search: U\+
|
||||
Replace:
|
||||
|
||||
|
||||
-----------------------------
|
||||
@@ -27,7 +30,18 @@ Replace skin tone variations:
|
||||
1F9D2;child
|
||||
1F9D2 $;child
|
||||
|
||||
TODO: Update this section with exact regexes
|
||||
|
||||
Search: 1F3FB
|
||||
Replace: $
|
||||
|
||||
Search: ^.*1F3F[C-F].*$
|
||||
Replace:
|
||||
|
||||
Search: tone\n\n\n\n
|
||||
Replace:
|
||||
|
||||
Search: : light skin.*$
|
||||
Replace:
|
||||
|
||||
|
||||
----------------
|
||||
@@ -36,20 +50,10 @@ Move some emoji:
|
||||
1F443 $;nose
|
||||
> 1F91D;handshake
|
||||
1F463;footprints
|
||||
|
||||
1F939 $ 200D 2640 FE0F;woman juggling
|
||||
> 1F6CC;person in bed
|
||||
> 1F6CC $;person in bed
|
||||
> 1F6C0;person taking bath
|
||||
> 1F6C0 $;person taking bath
|
||||
1F46B;man and woman holding hands
|
||||
|
||||
|
||||
------------------
|
||||
Remove some emoji:
|
||||
|
||||
1F469 $ 200D 1F692;woman firefighter
|
||||
> remove all non-gendered duplicates below here
|
||||
-------------------------
|
||||
Remove unsupported emoji:
|
||||
|
||||
3030;wavy dash
|
||||
> remove copyright
|
||||
@@ -59,12 +63,81 @@ Remove some emoji:
|
||||
|
||||
1F441;eye
|
||||
> remove eye in speech bubble
|
||||
1F445;tongue
|
||||
1F9E0;brain
|
||||
|
||||
(more may be present after an emoji update)
|
||||
|
||||
|
||||
-------------------------
|
||||
Add preprocessor symbols:
|
||||
-------------------------------
|
||||
Remove non-gendered duplicates:
|
||||
|
||||
@ = group separator
|
||||
@1 = enable skin tones below
|
||||
@2 = disable skin tones below
|
||||
Search: ^(1F(46E|575|482|477|473|471|9D9|9DA|9DB|9DC|9DD|9DE|9DF|64D|64E|645|646|481|64B|647|926|937|486|487|6B6|3C3|46F|9D6|9D7|9D8|3CC|3C4|6A3|3CA|3CB|6B4|6B5|938|93C|93D|93E|939)|26F9)(\s\$;|;).*?\n
|
||||
Replace:
|
||||
|
||||
(new ones may be added with an emoji update)
|
||||
|
||||
|
||||
-----------------------------------
|
||||
Add skin tone preprocessor symbols:
|
||||
|
||||
#1 = enable skin tones below
|
||||
find and replace '$;light skin ' with #1
|
||||
|
||||
#2 = disable skin tones below
|
||||
find the last entry containing '$' and insert #2 below
|
||||
|
||||
|
||||
----------------------
|
||||
Add separator symbols:
|
||||
|
||||
1F64A;speak-no-evil monkey
|
||||
> @
|
||||
1F476;baby
|
||||
|
||||
1F469 200D 1F467 200D 1F467;family: woman, girl, girl
|
||||
> @
|
||||
1F933;selfie
|
||||
|
||||
|
||||
1F48E;gem stone
|
||||
> @
|
||||
1F435;monkey face
|
||||
|
||||
|
||||
1F982;scorpion
|
||||
> @
|
||||
1F490;bouquet
|
||||
|
||||
|
||||
1F52A;kitchen knife
|
||||
> @
|
||||
1F3FA;amphora
|
||||
|
||||
|
||||
1F6C1;bathtub
|
||||
> @
|
||||
231B;hourglass done
|
||||
|
||||
|
||||
1F6D2;shopping cart
|
||||
> @
|
||||
1F3E7;ATM sign
|
||||
|
||||
|
||||
1F3F3 FE0F 200D 1F308;rainbow flag
|
||||
> @
|
||||
1F1E6 1F1E8;Ascension Island
|
||||
|
||||
|
||||
--------------
|
||||
Final cleanup:
|
||||
|
||||
Search: & |,|“|”|o’
|
||||
Replace:
|
||||
|
||||
---
|
||||
Make sure all emoji are formatted correctly and there are no empty lines (no line at the end of file).
|
||||
|
||||
Test emoji search by searching for the first and last emoji. If broken, a single emoji could be formatted wrong, or an emoji is not supported by Twitter (such as the 'eye in speech bubble' which causes two images to display and messes up the offsets).
|
||||
|
||||
If there is an error in 'td:plugin:official/emoji-keyboard' when typing into the search bar, add a breakpoint to the erroring line and check if 'me.emojiNames.length' equals 'emoji.length'.
|
||||
|
@@ -1,14 +1,14 @@
|
||||
1F600;grinning face
|
||||
1F601;grinning face with smiling eyes
|
||||
1F601;beaming face with smiling eyes
|
||||
1F602;face with tears of joy
|
||||
1F923;rolling on the floor laughing
|
||||
1F603;smiling face with open mouth
|
||||
1F604;smiling face with open mouth & smiling eyes
|
||||
1F605;smiling face with open mouth & cold sweat
|
||||
1F606;smiling face with open mouth & closed eyes
|
||||
1F603;grinning face with big eyes
|
||||
1F604;grinning face with smiling eyes
|
||||
1F605;grinning face with sweat
|
||||
1F606;grinning squinting face
|
||||
1F609;winking face
|
||||
1F60A;smiling face with smiling eyes
|
||||
1F60B;face savouring delicious food
|
||||
1F60B;face savoring food
|
||||
1F60E;smiling face with sunglasses
|
||||
1F60D;smiling face with heart-eyes
|
||||
1F618;face blowing a kiss
|
||||
@@ -18,14 +18,16 @@
|
||||
263A;smiling face
|
||||
1F642;slightly smiling face
|
||||
1F917;hugging face
|
||||
1F929;star-struck
|
||||
1F914;thinking face
|
||||
1F928;face with raised eyebrow
|
||||
1F610;neutral face
|
||||
1F611;expressionless face
|
||||
1F636;face without mouth
|
||||
1F644;face with rolling eyes
|
||||
1F60F;smirking face
|
||||
1F623;persevering face
|
||||
1F625;disappointed but relieved face
|
||||
1F625;sad but relieved face
|
||||
1F62E;face with open mouth
|
||||
1F910;zipper-mouth face
|
||||
1F62F;hushed face
|
||||
@@ -33,13 +35,12 @@
|
||||
1F62B;tired face
|
||||
1F634;sleeping face
|
||||
1F60C;relieved face
|
||||
1F913;nerd face
|
||||
1F61B;face with stuck-out tongue
|
||||
1F61C;face with stuck-out tongue & winking eye
|
||||
1F61D;face with stuck-out tongue & closed eyes
|
||||
1F61B;face with tongue
|
||||
1F61C;winking face with tongue
|
||||
1F61D;squinting face with tongue
|
||||
1F924;drooling face
|
||||
1F612;unamused face
|
||||
1F613;face with cold sweat
|
||||
1F613;downcast face with sweat
|
||||
1F614;pensive face
|
||||
1F615;confused face
|
||||
1F643;upside-down face
|
||||
@@ -57,22 +58,30 @@
|
||||
1F627;anguished face
|
||||
1F628;fearful face
|
||||
1F629;weary face
|
||||
1F92F;exploding head
|
||||
1F62C;grimacing face
|
||||
1F630;face with open mouth & cold sweat
|
||||
1F630;anxious face with sweat
|
||||
1F631;face screaming in fear
|
||||
1F633;flushed face
|
||||
1F92A;crazy face
|
||||
1F635;dizzy face
|
||||
1F621;pouting face
|
||||
1F620;angry face
|
||||
1F607;smiling face with halo
|
||||
1F920;cowboy hat face
|
||||
1F921;clown face
|
||||
1F925;lying face
|
||||
1F92C;face with symbols on mouth
|
||||
1F637;face with medical mask
|
||||
1F912;face with thermometer
|
||||
1F915;face with head-bandage
|
||||
1F922;nauseated face
|
||||
1F92E;face vomiting
|
||||
1F927;sneezing face
|
||||
1F607;smiling face with halo
|
||||
1F920;cowboy hat face
|
||||
1F921;clown face
|
||||
1F925;lying face
|
||||
1F92B;shushing face
|
||||
1F92D;face with hand over mouth
|
||||
1F9D0;face with monocle
|
||||
1F913;nerd face
|
||||
1F608;smiling face with horns
|
||||
1F47F;angry face with horns
|
||||
1F479;ogre
|
||||
@@ -84,35 +93,40 @@
|
||||
1F47E;alien monster
|
||||
1F916;robot face
|
||||
1F4A9;pile of poo
|
||||
1F63A;smiling cat face with open mouth
|
||||
1F63A;grinning cat face
|
||||
1F638;grinning cat face with smiling eyes
|
||||
1F639;cat face with tears of joy
|
||||
1F63B;smiling cat face with heart-eyes
|
||||
1F63C;cat face with wry smile
|
||||
1F63D;kissing cat face with closed eyes
|
||||
1F63D;kissing cat face
|
||||
1F640;weary cat face
|
||||
1F63F;crying cat face
|
||||
1F63E;pouting cat face
|
||||
1F648;see-no-evil monkey
|
||||
1F649;hear-no-evil monkey
|
||||
1F64A;speak-no-evil monkey
|
||||
@1 enable skin tones
|
||||
@
|
||||
#1 enable skin tones
|
||||
1F476;baby
|
||||
1F476 $;baby
|
||||
1F9D2;child
|
||||
1F9D2 $;child
|
||||
1F466;boy
|
||||
1F466 $;boy
|
||||
1F467;girl
|
||||
1F467 $;girl
|
||||
1F9D1;adult
|
||||
1F9D1 $;adult
|
||||
1F468;man
|
||||
1F468 $;man
|
||||
1F469;woman
|
||||
1F469 $;woman
|
||||
1F9D3;older adult
|
||||
1F9D3 $;older adult
|
||||
1F474;old man
|
||||
1F474 $;old man
|
||||
1F475;old woman
|
||||
1F475 $;old woman
|
||||
1F476;baby
|
||||
1F476 $;baby
|
||||
1F47C;baby angel
|
||||
1F47C $;baby angel
|
||||
1F468 200D 2695 FE0F;man health worker
|
||||
1F468 $ 200D 2695 FE0F;man health worker
|
||||
1F469 200D 2695 FE0F;woman health worker
|
||||
@@ -193,30 +207,62 @@
|
||||
1F477 $ 200D 2642 FE0F;man construction worker
|
||||
1F477 200D 2640 FE0F;woman construction worker
|
||||
1F477 $ 200D 2640 FE0F;woman construction worker
|
||||
1F934;prince
|
||||
1F934 $;prince
|
||||
1F478;princess
|
||||
1F478 $;princess
|
||||
1F473 200D 2642 FE0F;man wearing turban
|
||||
1F473 $ 200D 2642 FE0F;man wearing turban
|
||||
1F473 200D 2640 FE0F;woman wearing turban
|
||||
1F473 $ 200D 2640 FE0F;woman wearing turban
|
||||
1F472;man with Chinese cap
|
||||
1F472 $;man with Chinese cap
|
||||
1F9D5;woman with headscarf
|
||||
1F9D5 $;woman with headscarf
|
||||
1F9D4;bearded person
|
||||
1F9D4 $;bearded person
|
||||
1F471 200D 2642 FE0F;blond-haired man
|
||||
1F471 $ 200D 2642 FE0F;blond-haired man
|
||||
1F471 200D 2640 FE0F;blond-haired woman
|
||||
1F471 $ 200D 2640 FE0F;blond-haired woman
|
||||
1F935;man in tuxedo
|
||||
1F935 $;man in tuxedo
|
||||
1F470;bride with veil
|
||||
1F470 $;bride with veil
|
||||
1F930;pregnant woman
|
||||
1F930 $;pregnant woman
|
||||
1F931;breast-feeding
|
||||
1F931 $;breast-feeding
|
||||
1F47C;baby angel
|
||||
1F47C $;baby angel
|
||||
1F385;Santa Claus
|
||||
1F385 $;Santa Claus
|
||||
1F936;Mrs. Claus
|
||||
1F936 $;Mrs. Claus
|
||||
1F478;princess
|
||||
1F478 $;princess
|
||||
1F934;prince
|
||||
1F934 $;prince
|
||||
1F470;bride with veil
|
||||
1F470 $;bride with veil
|
||||
1F935;man in tuxedo
|
||||
1F935 $;man in tuxedo
|
||||
1F930;pregnant woman
|
||||
1F930 $;pregnant woman
|
||||
1F472;man with Chinese cap
|
||||
1F472 $;man with Chinese cap
|
||||
1F9D9 200D 2640 FE0F;woman mage
|
||||
1F9D9 $ 200D 2640 FE0F;woman mage
|
||||
1F9D9 200D 2642 FE0F;man mage
|
||||
1F9D9 $ 200D 2642 FE0F;man mage
|
||||
1F9DA 200D 2640 FE0F;woman fairy
|
||||
1F9DA $ 200D 2640 FE0F;woman fairy
|
||||
1F9DA 200D 2642 FE0F;man fairy
|
||||
1F9DA $ 200D 2642 FE0F;man fairy
|
||||
1F9DB 200D 2640 FE0F;woman vampire
|
||||
1F9DB $ 200D 2640 FE0F;woman vampire
|
||||
1F9DB 200D 2642 FE0F;man vampire
|
||||
1F9DB $ 200D 2642 FE0F;man vampire
|
||||
1F9DC 200D 2640 FE0F;mermaid
|
||||
1F9DC $ 200D 2640 FE0F;mermaid
|
||||
1F9DC 200D 2642 FE0F;merman
|
||||
1F9DC $ 200D 2642 FE0F;merman
|
||||
1F9DD 200D 2640 FE0F;woman elf
|
||||
1F9DD $ 200D 2640 FE0F;woman elf
|
||||
1F9DD 200D 2642 FE0F;man elf
|
||||
1F9DD $ 200D 2642 FE0F;man elf
|
||||
1F9DE 200D 2640 FE0F;woman genie
|
||||
1F9DE 200D 2642 FE0F;man genie
|
||||
1F9DF 200D 2640 FE0F;woman zombie
|
||||
1F9DF 200D 2642 FE0F;man zombie
|
||||
1F64D 200D 2642 FE0F;man frowning
|
||||
1F64D $ 200D 2642 FE0F;man frowning
|
||||
1F64D 200D 2640 FE0F;woman frowning
|
||||
@@ -273,10 +319,26 @@
|
||||
1F483 $;woman dancing
|
||||
1F57A;man dancing
|
||||
1F57A $;man dancing
|
||||
1F46F 200D 2642 FE0F;men with bunny ears partying
|
||||
1F46F 200D 2640 FE0F;women with bunny ears partying
|
||||
1F574;man in business suit levitating
|
||||
1F574 $;man in business suit levitating
|
||||
1F46F 200D 2642 FE0F;men with bunny ears
|
||||
1F46F 200D 2640 FE0F;women with bunny ears
|
||||
1F9D6 200D 2640 FE0F;woman in steamy room
|
||||
1F9D6 $ 200D 2640 FE0F;woman in steamy room
|
||||
1F9D6 200D 2642 FE0F;man in steamy room
|
||||
1F9D6 $ 200D 2642 FE0F;man in steamy room
|
||||
1F9D7 200D 2640 FE0F;woman climbing
|
||||
1F9D7 $ 200D 2640 FE0F;woman climbing
|
||||
1F9D7 200D 2642 FE0F;man climbing
|
||||
1F9D7 $ 200D 2642 FE0F;man climbing
|
||||
1F9D8 200D 2640 FE0F;woman in lotus position
|
||||
1F9D8 $ 200D 2640 FE0F;woman in lotus position
|
||||
1F9D8 200D 2642 FE0F;man in lotus position
|
||||
1F9D8 $ 200D 2642 FE0F;man in lotus position
|
||||
1F6C0;person taking bath
|
||||
1F6C0 $;person taking bath
|
||||
1F6CC;person in bed
|
||||
1F6CC $;person in bed
|
||||
1F574;man in suit levitating
|
||||
1F574 $;man in suit levitating
|
||||
1F5E3;speaking head
|
||||
1F464;bust in silhouette
|
||||
1F465;busts in silhouette
|
||||
@@ -338,52 +400,48 @@
|
||||
1F939 $ 200D 2642 FE0F;man juggling
|
||||
1F939 200D 2640 FE0F;woman juggling
|
||||
1F939 $ 200D 2640 FE0F;woman juggling
|
||||
1F6CC;person in bed
|
||||
1F6CC $;person in bed
|
||||
1F6C0;person taking bath
|
||||
1F6C0 $;person taking bath
|
||||
1F46B;man and woman holding hands
|
||||
1F46C;two men holding hands
|
||||
1F46D;two women holding hands
|
||||
1F48F;kiss
|
||||
1F469 200D 2764 FE0F 200D 1F48B 200D 1F468;kiss
|
||||
1F468 200D 2764 FE0F 200D 1F48B 200D 1F468;kiss
|
||||
1F469 200D 2764 FE0F 200D 1F48B 200D 1F469;kiss
|
||||
1F469 200D 2764 FE0F 200D 1F48B 200D 1F468;kiss woman man
|
||||
1F468 200D 2764 FE0F 200D 1F48B 200D 1F468;kiss man man
|
||||
1F469 200D 2764 FE0F 200D 1F48B 200D 1F469;kiss woman woman
|
||||
1F491;couple with heart
|
||||
1F469 200D 2764 FE0F 200D 1F468;couple with heart
|
||||
1F468 200D 2764 FE0F 200D 1F468;couple with heart
|
||||
1F469 200D 2764 FE0F 200D 1F469;couple with heart
|
||||
1F469 200D 2764 FE0F 200D 1F468;couple with heart woman man
|
||||
1F468 200D 2764 FE0F 200D 1F468;couple with heart man man
|
||||
1F469 200D 2764 FE0F 200D 1F469;couple with heart woman woman
|
||||
1F46A;family
|
||||
1F468 200D 1F469 200D 1F466;family
|
||||
1F468 200D 1F469 200D 1F467;family
|
||||
1F468 200D 1F469 200D 1F467 200D 1F466;family
|
||||
1F468 200D 1F469 200D 1F466 200D 1F466;family
|
||||
1F468 200D 1F469 200D 1F467 200D 1F467;family
|
||||
1F468 200D 1F468 200D 1F466;family
|
||||
1F468 200D 1F468 200D 1F467;family
|
||||
1F468 200D 1F468 200D 1F467 200D 1F466;family
|
||||
1F468 200D 1F468 200D 1F466 200D 1F466;family
|
||||
1F468 200D 1F468 200D 1F467 200D 1F467;family
|
||||
1F469 200D 1F469 200D 1F466;family
|
||||
1F469 200D 1F469 200D 1F467;family
|
||||
1F469 200D 1F469 200D 1F467 200D 1F466;family
|
||||
1F469 200D 1F469 200D 1F466 200D 1F466;family
|
||||
1F469 200D 1F469 200D 1F467 200D 1F467;family
|
||||
1F468 200D 1F466;family
|
||||
1F468 200D 1F466 200D 1F466;family
|
||||
1F468 200D 1F467;family
|
||||
1F468 200D 1F467 200D 1F466;family
|
||||
1F468 200D 1F467 200D 1F467;family
|
||||
1F469 200D 1F466;family
|
||||
1F469 200D 1F466 200D 1F466;family
|
||||
1F469 200D 1F467;family
|
||||
1F469 200D 1F467 200D 1F466;family
|
||||
1F469 200D 1F467 200D 1F467;family
|
||||
1F468 200D 1F469 200D 1F466;family man woman boy
|
||||
1F468 200D 1F469 200D 1F467;family man woman girl
|
||||
1F468 200D 1F469 200D 1F467 200D 1F466;family man woman girl boy
|
||||
1F468 200D 1F469 200D 1F466 200D 1F466;family man woman boy boy
|
||||
1F468 200D 1F469 200D 1F467 200D 1F467;family man woman girl girl
|
||||
1F468 200D 1F468 200D 1F466;family man man boy
|
||||
1F468 200D 1F468 200D 1F467;family man man girl
|
||||
1F468 200D 1F468 200D 1F467 200D 1F466;family man man girl boy
|
||||
1F468 200D 1F468 200D 1F466 200D 1F466;family man man boy boy
|
||||
1F468 200D 1F468 200D 1F467 200D 1F467;family man man girl girl
|
||||
1F469 200D 1F469 200D 1F466;family woman woman boy
|
||||
1F469 200D 1F469 200D 1F467;family woman woman girl
|
||||
1F469 200D 1F469 200D 1F467 200D 1F466;family woman woman girl boy
|
||||
1F469 200D 1F469 200D 1F466 200D 1F466;family woman woman boy boy
|
||||
1F469 200D 1F469 200D 1F467 200D 1F467;family woman woman girl girl
|
||||
1F468 200D 1F466;family man boy
|
||||
1F468 200D 1F466 200D 1F466;family man boy boy
|
||||
1F468 200D 1F467;family man girl
|
||||
1F468 200D 1F467 200D 1F466;family man girl boy
|
||||
1F468 200D 1F467 200D 1F467;family man girl girl
|
||||
1F469 200D 1F466;family woman boy
|
||||
1F469 200D 1F466 200D 1F466;family woman boy boy
|
||||
1F469 200D 1F467;family woman girl
|
||||
1F469 200D 1F467 200D 1F466;family woman girl boy
|
||||
1F469 200D 1F467 200D 1F467;family woman girl girl
|
||||
@
|
||||
1F4AA;flexed biceps
|
||||
1F4AA $;flexed biceps
|
||||
1F933;selfie
|
||||
1F933 $;selfie
|
||||
1F4AA;flexed biceps
|
||||
1F4AA $;flexed biceps
|
||||
1F448;backhand index pointing left
|
||||
1F448 $;backhand index pointing left
|
||||
1F449;backhand index pointing right
|
||||
@@ -406,8 +464,8 @@
|
||||
1F918 $;sign of the horns
|
||||
1F919;call me hand
|
||||
1F919 $;call me hand
|
||||
1F590;raised hand with fingers splayed
|
||||
1F590 $;raised hand with fingers splayed
|
||||
1F590;hand with fingers splayed
|
||||
1F590 $;hand with fingers splayed
|
||||
270B;raised hand
|
||||
270B $;raised hand
|
||||
1F44C;OK hand
|
||||
@@ -428,14 +486,18 @@
|
||||
1F91A $;raised back of hand
|
||||
1F44B;waving hand
|
||||
1F44B $;waving hand
|
||||
1F44F;clapping hands
|
||||
1F44F $;clapping hands
|
||||
1F91F;love-you gesture
|
||||
1F91F $;love-you gesture
|
||||
270D;writing hand
|
||||
270D $;writing hand
|
||||
1F44F;clapping hands
|
||||
1F44F $;clapping hands
|
||||
1F450;open hands
|
||||
1F450 $;open hands
|
||||
1F64C;raising hands
|
||||
1F64C $;raising hands
|
||||
1F932;palms up together
|
||||
1F932 $;palms up together
|
||||
1F64F;folded hands
|
||||
1F64F $;folded hands
|
||||
1F485;nail polish
|
||||
@@ -444,10 +506,12 @@
|
||||
1F442 $;ear
|
||||
1F443;nose
|
||||
1F443 $;nose
|
||||
#2 no more skin tones beyond this point
|
||||
1F91D;handshake
|
||||
1F463;footprints
|
||||
1F440;eyes
|
||||
1F441;eye
|
||||
1F9E0;brain
|
||||
1F445;tongue
|
||||
1F444;mouth
|
||||
1F48B;kiss mark
|
||||
@@ -461,6 +525,7 @@
|
||||
1F499;blue heart
|
||||
1F49A;green heart
|
||||
1F49B;yellow heart
|
||||
1F9E1;orange heart
|
||||
1F49C;purple heart
|
||||
1F5A4;black heart
|
||||
1F49D;heart with ribbon
|
||||
@@ -485,6 +550,10 @@
|
||||
1F454;necktie
|
||||
1F455;t-shirt
|
||||
1F456;jeans
|
||||
1F9E3;scarf
|
||||
1F9E4;gloves
|
||||
1F9E5;coat
|
||||
1F9E6;socks
|
||||
1F457;dress
|
||||
1F458;kimono
|
||||
1F459;bikini
|
||||
@@ -503,12 +572,13 @@
|
||||
1F452;woman’s hat
|
||||
1F3A9;top hat
|
||||
1F393;graduation cap
|
||||
1F9E2;billed cap
|
||||
26D1;rescue worker’s helmet
|
||||
1F4FF;prayer beads
|
||||
1F484;lipstick
|
||||
1F48D;ring
|
||||
1F48E;gem stone
|
||||
@2 no more skin tones beyond this point
|
||||
@
|
||||
1F435;monkey face
|
||||
1F412;monkey
|
||||
1F98D;gorilla
|
||||
@@ -525,8 +595,9 @@
|
||||
1F406;leopard
|
||||
1F434;horse face
|
||||
1F40E;horse
|
||||
1F98C;deer
|
||||
1F984;unicorn face
|
||||
1F993;zebra
|
||||
1F98C;deer
|
||||
1F42E;cow face
|
||||
1F402;ox
|
||||
1F403;water buffalo
|
||||
@@ -540,6 +611,7 @@
|
||||
1F410;goat
|
||||
1F42A;camel
|
||||
1F42B;two-hump camel
|
||||
1F992;giraffe
|
||||
1F418;elephant
|
||||
1F98F;rhinoceros
|
||||
1F42D;mouse face
|
||||
@@ -549,6 +621,7 @@
|
||||
1F430;rabbit face
|
||||
1F407;rabbit
|
||||
1F43F;chipmunk
|
||||
1F994;hedgehog
|
||||
1F987;bat
|
||||
1F43B;bear face
|
||||
1F428;koala
|
||||
@@ -573,6 +646,8 @@
|
||||
1F40D;snake
|
||||
1F432;dragon face
|
||||
1F409;dragon
|
||||
1F995;sauropod
|
||||
1F996;T-Rex
|
||||
1F433;spouting whale
|
||||
1F40B;whale
|
||||
1F42C;dolphin
|
||||
@@ -585,12 +660,13 @@
|
||||
1F980;crab
|
||||
1F990;shrimp
|
||||
1F991;squid
|
||||
1F98B;butterfly
|
||||
1F40C;snail
|
||||
1F98B;butterfly
|
||||
1F41B;bug
|
||||
1F41C;ant
|
||||
1F41D;honeybee
|
||||
1F41E;lady beetle
|
||||
1F997;cricket
|
||||
1F577;spider
|
||||
1F578;spider web
|
||||
1F982;scorpion
|
||||
@@ -618,7 +694,6 @@
|
||||
1F342;fallen leaf
|
||||
1F343;leaf fluttering in wind
|
||||
1F347;grapes
|
||||
@
|
||||
1F348;melon
|
||||
1F349;watermelon
|
||||
1F34A;tangerine
|
||||
@@ -633,6 +708,7 @@
|
||||
1F353;strawberry
|
||||
1F95D;kiwi fruit
|
||||
1F345;tomato
|
||||
1F965;coconut
|
||||
1F951;avocado
|
||||
1F346;eggplant
|
||||
1F954;potato
|
||||
@@ -640,21 +716,25 @@
|
||||
1F33D;ear of corn
|
||||
1F336;hot pepper
|
||||
1F952;cucumber
|
||||
1F966;broccoli
|
||||
1F344;mushroom
|
||||
1F95C;peanuts
|
||||
1F330;chestnut
|
||||
1F35E;bread
|
||||
1F950;croissant
|
||||
1F956;baguette bread
|
||||
1F968;pretzel
|
||||
1F95E;pancakes
|
||||
1F9C0;cheese wedge
|
||||
1F356;meat on bone
|
||||
1F357;poultry leg
|
||||
1F969;cut of meat
|
||||
1F953;bacon
|
||||
1F354;hamburger
|
||||
1F35F;french fries
|
||||
1F355;pizza
|
||||
1F32D;hot dog
|
||||
1F96A;sandwich
|
||||
1F32E;taco
|
||||
1F32F;burrito
|
||||
1F959;stuffed flatbread
|
||||
@@ -662,8 +742,10 @@
|
||||
1F373;cooking
|
||||
1F958;shallow pan of food
|
||||
1F372;pot of food
|
||||
1F963;bowl with spoon
|
||||
1F957;green salad
|
||||
1F37F;popcorn
|
||||
1F96B;canned food
|
||||
1F371;bento box
|
||||
1F358;rice cracker
|
||||
1F359;rice ball
|
||||
@@ -677,6 +759,9 @@
|
||||
1F364;fried shrimp
|
||||
1F365;fish cake with swirl
|
||||
1F361;dango
|
||||
1F95F;dumpling
|
||||
1F960;fortune cookie
|
||||
1F961;takeout box
|
||||
1F366;soft ice cream
|
||||
1F367;shaved ice
|
||||
1F368;ice cream
|
||||
@@ -684,6 +769,7 @@
|
||||
1F36A;cookie
|
||||
1F382;birthday cake
|
||||
1F370;shortcake
|
||||
1F967;pie
|
||||
1F36B;chocolate bar
|
||||
1F36C;candy
|
||||
1F36D;lollipop
|
||||
@@ -702,6 +788,8 @@
|
||||
1F37B;clinking beer mugs
|
||||
1F942;clinking glasses
|
||||
1F943;tumbler glass
|
||||
1F964;cup with straw
|
||||
1F962;chopsticks
|
||||
1F37D;fork and knife with plate
|
||||
1F374;fork and knife
|
||||
1F944;spoon
|
||||
@@ -726,7 +814,7 @@
|
||||
1F3DF;stadium
|
||||
1F3DB;classical building
|
||||
1F3D7;building construction
|
||||
1F3D8;house
|
||||
1F3D8;houses
|
||||
1F3D9;cityscape
|
||||
1F3DA;derelict house
|
||||
1F3E0;house
|
||||
@@ -775,7 +863,7 @@
|
||||
1F682;locomotive
|
||||
1F683;railway car
|
||||
1F684;high-speed train
|
||||
1F685;high-speed train with bullet nose
|
||||
1F685;bullet train
|
||||
1F686;train
|
||||
1F687;metro
|
||||
1F688;light rail
|
||||
@@ -829,8 +917,9 @@
|
||||
1F69F;suspension railway
|
||||
1F6A0;mountain cableway
|
||||
1F6A1;aerial tramway
|
||||
1F680;rocket
|
||||
1F6F0;satellite
|
||||
1F680;rocket
|
||||
1F6F8;flying saucer
|
||||
1F6CE;bellhop bell
|
||||
1F6AA;door
|
||||
1F6CF;bed
|
||||
@@ -839,36 +928,36 @@
|
||||
1F6BF;shower
|
||||
1F6C1;bathtub
|
||||
@
|
||||
231B;hourglass
|
||||
23F3;hourglass with flowing sand
|
||||
231B;hourglass done
|
||||
23F3;hourglass not done
|
||||
231A;watch
|
||||
23F0;alarm clock
|
||||
23F1;stopwatch
|
||||
23F2;timer clock
|
||||
1F570;mantelpiece clock
|
||||
1F55B;twelve o’clock
|
||||
1F55B;twelve clock
|
||||
1F567;twelve-thirty
|
||||
1F550;one o’clock
|
||||
1F550;one clock
|
||||
1F55C;one-thirty
|
||||
1F551;two o’clock
|
||||
1F551;two clock
|
||||
1F55D;two-thirty
|
||||
1F552;three o’clock
|
||||
1F552;three clock
|
||||
1F55E;three-thirty
|
||||
1F553;four o’clock
|
||||
1F553;four clock
|
||||
1F55F;four-thirty
|
||||
1F554;five o’clock
|
||||
1F554;five clock
|
||||
1F560;five-thirty
|
||||
1F555;six o’clock
|
||||
1F555;six clock
|
||||
1F561;six-thirty
|
||||
1F556;seven o’clock
|
||||
1F556;seven clock
|
||||
1F562;seven-thirty
|
||||
1F557;eight o’clock
|
||||
1F557;eight clock
|
||||
1F563;eight-thirty
|
||||
1F558;nine o’clock
|
||||
1F558;nine clock
|
||||
1F564;nine-thirty
|
||||
1F559;ten o’clock
|
||||
1F559;ten clock
|
||||
1F565;ten-thirty
|
||||
1F55A;eleven o’clock
|
||||
1F55A;eleven clock
|
||||
1F566;eleven-thirty
|
||||
1F311;new moon
|
||||
1F312;waxing crescent moon
|
||||
@@ -880,11 +969,11 @@
|
||||
1F318;waning crescent moon
|
||||
1F319;crescent moon
|
||||
1F31A;new moon face
|
||||
1F31B;first quarter moon with face
|
||||
1F31C;last quarter moon with face
|
||||
1F31B;first quarter moon face
|
||||
1F31C;last quarter moon face
|
||||
1F321;thermometer
|
||||
2600;sun
|
||||
1F31D;full moon with face
|
||||
1F31D;full moon face
|
||||
1F31E;sun with face
|
||||
2B50;white medium star
|
||||
1F31F;glowing star
|
||||
@@ -949,7 +1038,7 @@
|
||||
1F3BE;tennis
|
||||
1F3B1;pool 8 ball
|
||||
1F3B3;bowling
|
||||
1F3CF;cricket
|
||||
1F3CF;cricket game
|
||||
1F3D1;field hockey
|
||||
1F3D2;ice hockey
|
||||
1F3D3;ping pong
|
||||
@@ -963,6 +1052,8 @@
|
||||
1F3A3;fishing pole
|
||||
1F3BD;running shirt
|
||||
1F3BF;skis
|
||||
1F6F7;sled
|
||||
1F94C;curling stone
|
||||
1F3AE;video game
|
||||
1F579;joystick
|
||||
1F3B2;game die
|
||||
@@ -1024,8 +1115,8 @@
|
||||
1F4F8;camera with flash
|
||||
1F4F9;video camera
|
||||
1F4FC;videocassette
|
||||
1F50D;left-pointing magnifying glass
|
||||
1F50E;right-pointing magnifying glass
|
||||
1F50D;magnifying glass tilted left
|
||||
1F50E;magnifying glass tilted right
|
||||
1F52C;microscope
|
||||
1F52D;telescope
|
||||
1F4E1;satellite antenna
|
||||
@@ -1177,7 +1268,7 @@
|
||||
2934;right arrow curving up
|
||||
2935;right arrow curving down
|
||||
1F503;clockwise vertical arrows
|
||||
1F504;anticlockwise arrows button
|
||||
1F504;counterclockwise arrows button
|
||||
1F519;BACK arrow
|
||||
1F51A;END arrow
|
||||
1F51B;ON! arrow
|
||||
@@ -1232,11 +1323,14 @@
|
||||
1F4F6;antenna bars
|
||||
1F4F3;vibration mode
|
||||
1F4F4;mobile phone off
|
||||
2640;female sign
|
||||
2642;male sign
|
||||
2695;medical symbol
|
||||
267B;recycling symbol
|
||||
1F4DB;name badge
|
||||
269C;fleur-de-lis
|
||||
1F530;Japanese symbol for beginner
|
||||
1F531;trident emblem
|
||||
1F4DB;name badge
|
||||
1F530;Japanese symbol for beginner
|
||||
2B55;heavy large circle
|
||||
2705;white heavy check mark
|
||||
2611;ballot box with check
|
||||
@@ -1245,9 +1339,6 @@
|
||||
274C;cross mark
|
||||
274E;cross mark button
|
||||
2795;heavy plus sign
|
||||
2640;female sign
|
||||
2642;male sign
|
||||
2695;medical symbol
|
||||
2796;heavy minus sign
|
||||
2797;heavy division sign
|
||||
27B0;curly loop
|
||||
@@ -1263,18 +1354,18 @@
|
||||
2755;white exclamation mark
|
||||
2757;exclamation mark
|
||||
3030;wavy dash
|
||||
0023 FE0F 20E3;keycap
|
||||
002A FE0F 20E3;keycap
|
||||
0030 FE0F 20E3;keycap
|
||||
0031 FE0F 20E3;keycap
|
||||
0032 FE0F 20E3;keycap
|
||||
0033 FE0F 20E3;keycap
|
||||
0034 FE0F 20E3;keycap
|
||||
0035 FE0F 20E3;keycap
|
||||
0036 FE0F 20E3;keycap
|
||||
0037 FE0F 20E3;keycap
|
||||
0038 FE0F 20E3;keycap
|
||||
0039 FE0F 20E3;keycap
|
||||
0023 FE0F 20E3;keycap #
|
||||
002A FE0F 20E3;keycap *
|
||||
0030 FE0F 20E3;keycap 0
|
||||
0031 FE0F 20E3;keycap 1
|
||||
0032 FE0F 20E3;keycap 2
|
||||
0033 FE0F 20E3;keycap 3
|
||||
0034 FE0F 20E3;keycap 4
|
||||
0035 FE0F 20E3;keycap 5
|
||||
0036 FE0F 20E3;keycap 6
|
||||
0037 FE0F 20E3;keycap 7
|
||||
0038 FE0F 20E3;keycap 8
|
||||
0039 FE0F 20E3;keycap 9
|
||||
1F51F;keycap 10
|
||||
1F4AF;hundred points
|
||||
1F520;input latin uppercase
|
||||
@@ -1299,23 +1390,23 @@
|
||||
1F198;SOS button
|
||||
1F199;UP! button
|
||||
1F19A;VS button
|
||||
1F201;Japanese “here” button
|
||||
1F202;Japanese “service charge” button
|
||||
1F237;Japanese “monthly amount” button
|
||||
1F236;Japanese “not free of charge” button
|
||||
1F22F;Japanese “reserved” button
|
||||
1F250;Japanese “bargain” button
|
||||
1F239;Japanese “discount” button
|
||||
1F21A;Japanese “free of charge” button
|
||||
1F232;Japanese “prohibited” button
|
||||
1F251;Japanese “acceptable” button
|
||||
1F238;Japanese “application” button
|
||||
1F234;Japanese “passing grade” button
|
||||
1F233;Japanese “vacancy” button
|
||||
3297;Japanese “congratulations” button
|
||||
3299;Japanese “secret” button
|
||||
1F23A;Japanese “open for business” button
|
||||
1F235;Japanese “no vacancy” button
|
||||
1F201;Japanese here button
|
||||
1F202;Japanese service charge button
|
||||
1F237;Japanese monthly amount button
|
||||
1F236;Japanese not free of charge button
|
||||
1F22F;Japanese reserved button
|
||||
1F250;Japanese bargain button
|
||||
1F239;Japanese discount button
|
||||
1F21A;Japanese free of charge button
|
||||
1F232;Japanese prohibited button
|
||||
1F251;Japanese acceptable button
|
||||
1F238;Japanese application button
|
||||
1F234;Japanese passing grade button
|
||||
1F233;Japanese vacancy button
|
||||
3297;Japanese congratulations button
|
||||
3299;Japanese secret button
|
||||
1F23A;Japanese open for business button
|
||||
1F235;Japanese no vacancy button
|
||||
25AA;black small square
|
||||
25AB;white small square
|
||||
25FB;white medium square
|
||||
@@ -1349,7 +1440,7 @@
|
||||
1F1E6 1F1E9;Andorra
|
||||
1F1E6 1F1EA;United Arab Emirates
|
||||
1F1E6 1F1EB;Afghanistan
|
||||
1F1E6 1F1EC;Antigua & Barbuda
|
||||
1F1E6 1F1EC;Antigua Barbuda
|
||||
1F1E6 1F1EE;Anguilla
|
||||
1F1E6 1F1F1;Albania
|
||||
1F1E6 1F1F2;Armenia
|
||||
@@ -1362,7 +1453,7 @@
|
||||
1F1E6 1F1FC;Aruba
|
||||
1F1E6 1F1FD;Åland Islands
|
||||
1F1E6 1F1FF;Azerbaijan
|
||||
1F1E7 1F1E6;Bosnia & Herzegovina
|
||||
1F1E7 1F1E6;Bosnia Herzegovina
|
||||
1F1E7 1F1E7;Barbados
|
||||
1F1E7 1F1E9;Bangladesh
|
||||
1F1E7 1F1EA;Belgium
|
||||
@@ -1402,7 +1493,7 @@
|
||||
1F1E8 1F1FC;Curaçao
|
||||
1F1E8 1F1FD;Christmas Island
|
||||
1F1E8 1F1FE;Cyprus
|
||||
1F1E8 1F1FF;Czech Republic
|
||||
1F1E8 1F1FF;Czechia
|
||||
1F1E9 1F1EA;Germany
|
||||
1F1E9 1F1EC;Diego Garcia
|
||||
1F1E9 1F1EF;Djibouti
|
||||
@@ -1410,7 +1501,7 @@
|
||||
1F1E9 1F1F2;Dominica
|
||||
1F1E9 1F1F4;Dominican Republic
|
||||
1F1E9 1F1FF;Algeria
|
||||
1F1EA 1F1E6;Ceuta & Melilla
|
||||
1F1EA 1F1E6;Ceuta Melilla
|
||||
1F1EA 1F1E8;Ecuador
|
||||
1F1EA 1F1EA;Estonia
|
||||
1F1EA 1F1EC;Egypt
|
||||
@@ -1439,13 +1530,13 @@
|
||||
1F1EC 1F1F5;Guadeloupe
|
||||
1F1EC 1F1F6;Equatorial Guinea
|
||||
1F1EC 1F1F7;Greece
|
||||
1F1EC 1F1F8;South Georgia & South Sandwich Islands
|
||||
1F1EC 1F1F8;South Georgia South Sandwich Islands
|
||||
1F1EC 1F1F9;Guatemala
|
||||
1F1EC 1F1FA;Guam
|
||||
1F1EC 1F1FC;Guinea-Bissau
|
||||
1F1EC 1F1FE;Guyana
|
||||
1F1ED 1F1F0;Hong Kong SAR China
|
||||
1F1ED 1F1F2;Heard & McDonald Islands
|
||||
1F1ED 1F1F2;Heard McDonald Islands
|
||||
1F1ED 1F1F3;Honduras
|
||||
1F1ED 1F1F7;Croatia
|
||||
1F1ED 1F1F9;Haiti
|
||||
@@ -1470,7 +1561,7 @@
|
||||
1F1F0 1F1ED;Cambodia
|
||||
1F1F0 1F1EE;Kiribati
|
||||
1F1F0 1F1F2;Comoros
|
||||
1F1F0 1F1F3;St. Kitts & Nevis
|
||||
1F1F0 1F1F3;St. Kitts Nevis
|
||||
1F1F0 1F1F5;North Korea
|
||||
1F1F0 1F1F7;South Korea
|
||||
1F1F0 1F1FC;Kuwait
|
||||
@@ -1530,7 +1621,7 @@
|
||||
1F1F5 1F1ED;Philippines
|
||||
1F1F5 1F1F0;Pakistan
|
||||
1F1F5 1F1F1;Poland
|
||||
1F1F5 1F1F2;St. Pierre & Miquelon
|
||||
1F1F5 1F1F2;St. Pierre Miquelon
|
||||
1F1F5 1F1F3;Pitcairn Islands
|
||||
1F1F5 1F1F7;Puerto Rico
|
||||
1F1F5 1F1F8;Palestinian Territories
|
||||
@@ -1551,7 +1642,7 @@
|
||||
1F1F8 1F1EC;Singapore
|
||||
1F1F8 1F1ED;St. Helena
|
||||
1F1F8 1F1EE;Slovenia
|
||||
1F1F8 1F1EF;Svalbard & Jan Mayen
|
||||
1F1F8 1F1EF;Svalbard Jan Mayen
|
||||
1F1F8 1F1F0;Slovakia
|
||||
1F1F8 1F1F1;Sierra Leone
|
||||
1F1F8 1F1F2;San Marino
|
||||
@@ -1559,13 +1650,13 @@
|
||||
1F1F8 1F1F4;Somalia
|
||||
1F1F8 1F1F7;Suriname
|
||||
1F1F8 1F1F8;South Sudan
|
||||
1F1F8 1F1F9;São Tomé & Príncipe
|
||||
1F1F8 1F1F9;São Tomé Príncipe
|
||||
1F1F8 1F1FB;El Salvador
|
||||
1F1F8 1F1FD;Sint Maarten
|
||||
1F1F8 1F1FE;Syria
|
||||
1F1F8 1F1FF;Swaziland
|
||||
1F1F9 1F1E6;Tristan da Cunha
|
||||
1F1F9 1F1E8;Turks & Caicos Islands
|
||||
1F1F9 1F1E8;Turks Caicos Islands
|
||||
1F1F9 1F1E9;Chad
|
||||
1F1F9 1F1EB;French Southern Territories
|
||||
1F1F9 1F1EC;Togo
|
||||
@@ -1577,7 +1668,7 @@
|
||||
1F1F9 1F1F3;Tunisia
|
||||
1F1F9 1F1F4;Tonga
|
||||
1F1F9 1F1F7;Turkey
|
||||
1F1F9 1F1F9;Trinidad & Tobago
|
||||
1F1F9 1F1F9;Trinidad Tobago
|
||||
1F1F9 1F1FB;Tuvalu
|
||||
1F1F9 1F1FC;Taiwan
|
||||
1F1F9 1F1FF;Tanzania
|
||||
@@ -1589,17 +1680,20 @@
|
||||
1F1FA 1F1FE;Uruguay
|
||||
1F1FA 1F1FF;Uzbekistan
|
||||
1F1FB 1F1E6;Vatican City
|
||||
1F1FB 1F1E8;St. Vincent & Grenadines
|
||||
1F1FB 1F1E8;St. Vincent Grenadines
|
||||
1F1FB 1F1EA;Venezuela
|
||||
1F1FB 1F1EC;British Virgin Islands
|
||||
1F1FB 1F1EE;U.S. Virgin Islands
|
||||
1F1FB 1F1F3;Vietnam
|
||||
1F1FB 1F1FA;Vanuatu
|
||||
1F1FC 1F1EB;Wallis & Futuna
|
||||
1F1FC 1F1EB;Wallis Futuna
|
||||
1F1FC 1F1F8;Samoa
|
||||
1F1FD 1F1F0;Kosovo
|
||||
1F1FE 1F1EA;Yemen
|
||||
1F1FE 1F1F9;Mayotte
|
||||
1F1FF 1F1E6;South Africa
|
||||
1F1FF 1F1F2;Zambia
|
||||
1F1FF 1F1FC;Zimbabwe
|
||||
1F1FF 1F1FC;Zimbabwe
|
||||
1F3F4 E0067 E0062 E0065 E006E E0067 E007F;England
|
||||
1F3F4 E0067 E0062 E0073 E0063 E0074 E007F;Scotland
|
||||
1F3F4 E0067 E0062 E0077 E006C E0073 E007F;Wales
|
@@ -3,7 +3,7 @@ using CefSharp.WinForms;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Other;
|
||||
|
||||
namespace TweetDuck.Resources{
|
||||
static class ScriptLoader{
|
||||
@@ -14,7 +14,7 @@ namespace TweetDuck.Resources{
|
||||
return File.ReadAllText(Path.Combine(Program.ScriptPath, name), Encoding.UTF8);
|
||||
}catch(Exception ex){
|
||||
if (!silent){
|
||||
MessageBox.Show("Unfortunately, "+Program.BrandName+" could not load the "+name+" file. The program will continue running with limited functionality.\r\n\r\n"+ex.Message, Program.BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
FormMessage.Error("TweetDuck Has Failed :(", "Unfortunately, TweetDuck could not load the "+name+" file. The program will continue running with limited functionality.\n\n"+ex.Message, FormMessage.OK);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@@ -482,6 +482,8 @@
|
||||
if (item.type.startsWith("image/")){
|
||||
$(this).closest(".rpl").find(".js-reply-popout").click(); // popout direct messages
|
||||
uploader.addFilesToUpload([ item.getAsFile() ]);
|
||||
|
||||
$(".js-compose-text", ".js-docked-compose").focus();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -496,9 +498,14 @@
|
||||
return $(selector, parent).click().length;
|
||||
};
|
||||
|
||||
var tryCloseModal = function(){
|
||||
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(){
|
||||
var modal = $(".js-modals-container");
|
||||
return modal.length && tryClickSelector("a.mdl-dismiss", modal);
|
||||
};
|
||||
|
||||
var tryCloseHighlightedColumn = function(){
|
||||
@@ -510,7 +517,8 @@
|
||||
|
||||
window.TDGF_onMouseClickExtra = function(button){
|
||||
if (button === 1){ // back button
|
||||
tryCloseModal() ||
|
||||
tryCloseModal1() ||
|
||||
tryCloseModal2() ||
|
||||
tryClickSelector(".js-inline-compose-close") ||
|
||||
tryCloseHighlightedColumn() ||
|
||||
tryClickSelector(".js-app-content.is-open .js-drawer-close:visible") ||
|
||||
@@ -778,6 +786,40 @@
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Block: Memory cleanup check and execution.
|
||||
//
|
||||
window.TDGF_tryRunCleanup = function(){
|
||||
// all textareas are empty
|
||||
if ($("textarea").is(function(){
|
||||
return $(this).val().length > 0;
|
||||
})){
|
||||
return false;
|
||||
}
|
||||
|
||||
// no modals are visible
|
||||
if ($("#open-modal").is(":visible") || !$(".js-modals-container").is(":empty")){
|
||||
return false;
|
||||
}
|
||||
|
||||
// all columns are in a default state
|
||||
if ($("section.js-column").is(".is-shifted-1,.is-shifted-2")){
|
||||
return false;
|
||||
}
|
||||
|
||||
// all columns are scrolled to top
|
||||
if ($(".js-column-scroller").is(function(){
|
||||
return $(this).scrollTop() > 0;
|
||||
})){
|
||||
return false;
|
||||
}
|
||||
|
||||
// cleanup
|
||||
window.gc && window.gc();
|
||||
window.location.reload();
|
||||
return true;
|
||||
};
|
||||
|
||||
//
|
||||
// Block: Disable TweetDeck metrics.
|
||||
//
|
||||
|
@@ -27,9 +27,10 @@
|
||||
//
|
||||
// Function: Creates the update notification element. Removes the old one if already exists.
|
||||
//
|
||||
var displayNotification = function(version, download){
|
||||
var displayNotification = function(version, download, changelog){
|
||||
var outdated = version === "unsupported";
|
||||
|
||||
// styles
|
||||
var css = $("#tweetduck-update-css");
|
||||
|
||||
if (!css.length){
|
||||
@@ -39,25 +40,30 @@
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 200px;
|
||||
height: 170px;
|
||||
height: 178px;
|
||||
z-index: 9999;
|
||||
color: #fff;
|
||||
background-color: rgb(32, 94, 138);
|
||||
text-align: center;
|
||||
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.tdu-title {
|
||||
font-size: 17px;
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
letter-spacing: 0.2px;
|
||||
margin: 8px auto 2px;
|
||||
margin: 8px 0 2px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.tdu-info {
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
margin: 3px auto 0;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
margin: 3px 0;
|
||||
}
|
||||
|
||||
.tdu-showlog {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tdu-buttons button {
|
||||
@@ -86,10 +92,82 @@
|
||||
background-color: #607a8e;
|
||||
color: #dfdfdf;
|
||||
}
|
||||
|
||||
#tweetduck-changelog {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 9998;
|
||||
}
|
||||
|
||||
#tweetduck-changelog-box {
|
||||
position: absolute;
|
||||
width: 60%;
|
||||
height: 75%;
|
||||
max-width: calc(90% - 200px);
|
||||
max-height: 90%;
|
||||
left: calc(50% + 100px);
|
||||
top: 50%;
|
||||
padding: 12px;
|
||||
overflow-y: auto;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
font-size: 14px;
|
||||
color: #000;
|
||||
background-color: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#tweetduck-changelog h2 {
|
||||
margin: 0 0 7px;
|
||||
font-size: 23px;
|
||||
}
|
||||
|
||||
#tweetduck-changelog h3 {
|
||||
margin: 0 0 5px 7px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
#tweetduck-changelog p {
|
||||
margin: 0 0 2px 30px;
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
#tweetduck-changelog p.l2 {
|
||||
margin-left: 50px;
|
||||
}
|
||||
|
||||
#tweetduck-changelog a {
|
||||
color: #247fbb;
|
||||
}
|
||||
|
||||
#tweetduck-changelog code {
|
||||
padding: 0 4px;
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
color: #24292e;
|
||||
background-color: rgba(27, 31, 35, 0.05);
|
||||
}
|
||||
</style>
|
||||
`).appendTo(document.head);
|
||||
}
|
||||
|
||||
// changelog
|
||||
var log = $("#tweetduck-changelog");
|
||||
|
||||
if (!log.length){
|
||||
var log = $(`
|
||||
<div id='tweetduck-changelog'>
|
||||
<div id='tweetduck-changelog-box'>
|
||||
<h2>TweetDuck Update ${version}</h2>
|
||||
${changelog}
|
||||
</div>
|
||||
</div>
|
||||
`).appendTo(document.body).css("display", "none");
|
||||
}
|
||||
|
||||
// notification
|
||||
var ele = $("#tweetduck-update");
|
||||
var existed = ele.length > 0;
|
||||
|
||||
@@ -100,7 +178,7 @@
|
||||
ele = $(outdated ? `
|
||||
<div id='tweetduck-update'>
|
||||
<p class='tdu-title'>Unsupported System</p>
|
||||
<p class='tdu-info'>You will not receive updates.</p>
|
||||
<p class='tdu-info'>You will not receive updates</p>
|
||||
<div class='tdu-buttons'>
|
||||
<button class='tdu-btn-unsupported'>Read more</button>
|
||||
<button class='tdu-btn-ignore'>Dismiss</button>
|
||||
@@ -108,8 +186,8 @@
|
||||
</div>
|
||||
` : `
|
||||
<div id='tweetduck-update'>
|
||||
<p class='tdu-title'>TweetDuck Update</p>
|
||||
<p class='tdu-info'>Version ${version} is now available.</p>
|
||||
<p class='tdu-title'>T weetDuck Update ${version}</p>
|
||||
<p class='tdu-info tdu-showlog'>View update information</p>
|
||||
<div class='tdu-buttons'>
|
||||
<button class='tdu-btn-download'>Update now</button>
|
||||
<button class='tdu-btn-later'>Remind me later</button>
|
||||
@@ -118,11 +196,28 @@
|
||||
</div>
|
||||
`).appendTo(document.body).css("display", existed ? "block" : "none");
|
||||
|
||||
// ui logic
|
||||
var hide = function(){
|
||||
ele.remove();
|
||||
log.remove();
|
||||
css.remove();
|
||||
};
|
||||
|
||||
var slide = function(){
|
||||
log.hide();
|
||||
ele.slideUp(hide);
|
||||
};
|
||||
|
||||
ele.children(".tdu-showlog").click(function(){
|
||||
log.toggle();
|
||||
});
|
||||
|
||||
log.click(function(){
|
||||
log.hide();
|
||||
}).children().first().click(function(e){
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
var buttonDiv = ele.children(".tdu-buttons").first();
|
||||
|
||||
buttonDiv.children(".tdu-btn-download").click(function(){
|
||||
@@ -138,7 +233,7 @@
|
||||
|
||||
buttonDiv.children(".tdu-btn-later").click(function(){
|
||||
clearTimeout(updateCheckTimeoutID);
|
||||
ele.slideUp(hide);
|
||||
slide();
|
||||
});
|
||||
|
||||
buttonDiv.children(".tdu-btn-unsupported").click(function(){
|
||||
@@ -147,7 +242,7 @@
|
||||
|
||||
buttonDiv.children(".tdu-btn-ignore,.tdu-btn-unsupported").click(function(){
|
||||
$TDU.onUpdateDismissed();
|
||||
ele.slideUp(hide);
|
||||
slide();
|
||||
});
|
||||
|
||||
if (!existed){
|
||||
@@ -166,6 +261,23 @@
|
||||
return new Date(offset.getFullYear(), offset.getMonth(), offset.getDate(), offset.getHours()+1, 0, 0)-now;
|
||||
};
|
||||
|
||||
//
|
||||
// Function: Ghetto-converts markdown to HTML.
|
||||
//
|
||||
var markdown = function(md){
|
||||
return md.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/^### (.*?)$/gm, "<h3>$1</h3>")
|
||||
.replace(/^- (.*?)$/gm, "<p>$1</p>")
|
||||
.replace(/^ - (.*?)$/gm, "<p class='l2'>$1</p>")
|
||||
.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
|
||||
.replace(/\*(.*?)\*/g, "<em>$1</em>")
|
||||
.replace(/`(.*?)`/g, "<code>$1</code>")
|
||||
.replace(/\[(.*?)\]\((.*?)\)/g, "<a href='$2'>$1</a>")
|
||||
.replace(/\n\r?\n/g, "<br>");
|
||||
};
|
||||
|
||||
//
|
||||
// Function: Runs an update check and updates all DOM elements appropriately.
|
||||
//
|
||||
@@ -181,7 +293,7 @@
|
||||
|
||||
if (hasUpdate){
|
||||
var obj = release.assets.find(asset => asset.name === updateFileName) || { browser_download_url: "" };
|
||||
displayNotification(tagName, obj.browser_download_url);
|
||||
displayNotification(tagName, obj.browser_download_url, markdown(release.body));
|
||||
|
||||
if (eventID){ // ignore undefined and 0
|
||||
$TDU.onUpdateCheckFinished(eventID, tagName, obj.browser_download_url);
|
||||
|
@@ -65,6 +65,7 @@
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Management" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -72,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">
|
||||
@@ -83,6 +85,9 @@
|
||||
<Compile Include="Core\Controls\LabelVertical.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Core\Controls\NumericUpDownEx.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Core\Handling\BrowserProcessHandler.cs" />
|
||||
<Compile Include="Core\Handling\ContextMenuBase.cs" />
|
||||
<Compile Include="Core\Handling\ContextMenuBrowser.cs" />
|
||||
@@ -160,7 +165,9 @@
|
||||
<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\BrowserProcesses.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">
|
||||
@@ -191,15 +198,20 @@
|
||||
<DependentUpon>TabSettingsNotifications.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Core\Bridge\CallbackBridge.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="Core\Utils\TwoKeyDictionary.cs" />
|
||||
<Compile Include="Core\Utils\WindowState.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\WindowsUtils.cs" />
|
||||
<Compile Include="Core\Bridge\TweetDeckBridge.cs" />
|
||||
<Compile Include="Core\Other\FormSettings.cs">
|
||||
|
2
Updates/FormUpdateDownload.Designer.cs
generated
2
Updates/FormUpdateDownload.Designer.cs
generated
@@ -33,8 +33,6 @@
|
||||
// btnCancel
|
||||
//
|
||||
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.btnCancel.AutoSize = true;
|
||||
this.btnCancel.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
|
||||
this.btnCancel.Location = new System.Drawing.Point(195, 34);
|
||||
this.btnCancel.Name = "btnCancel";
|
||||
this.btnCancel.Padding = new System.Windows.Forms.Padding(3, 0, 3, 0);
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Updates{
|
||||
@@ -29,7 +30,7 @@ namespace TweetDuck.Updates{
|
||||
else if (updateInfo.DownloadStatus == UpdateDownloadStatus.Failed){
|
||||
timerDownloadCheck.Stop();
|
||||
|
||||
if (MessageBox.Show("Could not download the update: "+(updateInfo.DownloadError?.Message ?? "unknown error")+"\r\n\r\nDo you want to open the website and try downloading the update manually?", "Update Has Failed", MessageBoxButtons.YesNo, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1) == DialogResult.Yes){
|
||||
if (FormMessage.Error("Update Has Failed", "Could not download the update: "+(updateInfo.DownloadError?.Message ?? "unknown error")+"\n\nDo you want to open the website and try downloading the update manually?", FormMessage.Yes, FormMessage.No)){
|
||||
BrowserUtils.OpenExternalBrowserUnsafe(Program.Website);
|
||||
DialogResult = DialogResult.OK;
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@
|
||||
using CefSharp.WinForms;
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Resources;
|
||||
@@ -13,7 +12,6 @@ namespace TweetDuck.Updates{
|
||||
private static bool IsSystemSupported => true; // Environment.OSVersion.Version >= new Version("6.1"); // 6.1 NT version = Windows 7
|
||||
|
||||
private readonly ChromiumWebBrowser browser;
|
||||
private readonly FormBrowser form;
|
||||
private readonly UpdaterSettings settings;
|
||||
|
||||
public event EventHandler<UpdateAcceptedEventArgs> UpdateAccepted;
|
||||
@@ -23,9 +21,8 @@ namespace TweetDuck.Updates{
|
||||
private int lastEventId;
|
||||
private UpdateInfo lastUpdateInfo;
|
||||
|
||||
public UpdateHandler(ChromiumWebBrowser browser, FormBrowser form, UpdaterSettings settings){
|
||||
public UpdateHandler(ChromiumWebBrowser browser, UpdaterSettings settings){
|
||||
this.browser = browser;
|
||||
this.form = form;
|
||||
this.settings = settings;
|
||||
|
||||
browser.FrameLoadEnd += browser_FrameLoadEnd;
|
||||
@@ -92,27 +89,20 @@ namespace TweetDuck.Updates{
|
||||
}
|
||||
|
||||
public void DismissUpdate(string tag){
|
||||
settings.DismissedUpdate = tag;
|
||||
UpdateDismissed?.Invoke(this, new UpdateDismissedEventArgs(tag));
|
||||
TriggerUpdateDismissedEvent(new UpdateDismissedEventArgs(tag));
|
||||
}
|
||||
|
||||
private void TriggerUpdateAcceptedEvent(UpdateAcceptedEventArgs args){
|
||||
if (UpdateAccepted != null){
|
||||
form.InvokeAsyncSafe(() => UpdateAccepted(this, args));
|
||||
}
|
||||
UpdateAccepted?.Invoke(this, args);
|
||||
}
|
||||
|
||||
private void TriggerUpdateDismissedEvent(UpdateDismissedEventArgs args){
|
||||
form.InvokeAsyncSafe(() => {
|
||||
settings.DismissedUpdate = args.VersionTag;
|
||||
UpdateDismissed?.Invoke(this, args);
|
||||
});
|
||||
settings.DismissedUpdate = args.VersionTag;
|
||||
UpdateDismissed?.Invoke(this, args);
|
||||
}
|
||||
|
||||
private void TriggerCheckFinishedEvent(UpdateCheckEventArgs args){
|
||||
if (CheckFinished != null){
|
||||
form.InvokeAsyncSafe(() => CheckFinished(this, args));
|
||||
}
|
||||
CheckFinished?.Invoke(this, args);
|
||||
}
|
||||
|
||||
public class Bridge{
|
||||
|
@@ -4,7 +4,7 @@ using System.Net;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Updates{
|
||||
class UpdateInfo{
|
||||
sealed class UpdateInfo{
|
||||
public string VersionTag { get; }
|
||||
public string InstallerPath { get; }
|
||||
|
||||
|
@@ -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; }
|
||||
|
@@ -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 @@ namespace TweetLib.Audio{
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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 @@ namespace TweetLib.Audio.Impl{
|
||||
player.Stop();
|
||||
}
|
||||
|
||||
public override void Dispose(){
|
||||
protected override void Dispose(bool disposing){
|
||||
player.Dispose();
|
||||
}
|
||||
|
||||
|
@@ -56,7 +56,7 @@ namespace TweetLib.Audio.Impl{
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose(){
|
||||
protected override void Dispose(bool disposing){
|
||||
player.close();
|
||||
Marshal.ReleaseComObject(player);
|
||||
}
|
||||
|
@@ -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; }
|
@@ -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" />
|
||||
|
@@ -82,9 +82,7 @@ namespace TweetLib.Audio.Utils{
|
||||
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)){
|
||||
|
14
subprocess/NativeMethods.cs
Normal file
14
subprocess/NativeMethods.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace TweetDuck.Browser{
|
||||
static class NativeMethods{
|
||||
public static readonly IntPtr HWND_BROADCAST = new IntPtr(0xFFFF);
|
||||
|
||||
[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);
|
||||
}
|
||||
}
|
@@ -1,20 +1,34 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using CefSharp;
|
||||
using CefSharp.BrowserSubprocess;
|
||||
|
||||
namespace TweetDuck.Browser{
|
||||
static class Program{
|
||||
private static int Main(string[] args){
|
||||
SubProcess.EnableHighDPISupport();
|
||||
|
||||
|
||||
const string typePrefix = "--type=";
|
||||
string type = Array.Find(args, arg => arg.StartsWith(typePrefix, StringComparison.OrdinalIgnoreCase)).Substring(typePrefix.Length);
|
||||
|
||||
|
||||
if (type == "renderer"){
|
||||
using(SubProcess subProcess = new SubProcess(args)){
|
||||
using(RendererProcess subProcess = new RendererProcess(args)){
|
||||
return subProcess.Run();
|
||||
}
|
||||
}
|
||||
else return SubProcess.ExecuteProcess();
|
||||
}
|
||||
|
||||
private class RendererProcess : SubProcess{
|
||||
public RendererProcess(string[] args) : base(args){}
|
||||
|
||||
public override void OnBrowserCreated(CefBrowserWrapper wrapper){
|
||||
base.OnBrowserCreated(wrapper);
|
||||
|
||||
using(Process me = Process.GetCurrentProcess()){
|
||||
NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, NativeMethods.RegisterWindowMessage("TweetDuckSubProcess"), new UIntPtr((uint)me.Id), new IntPtr(wrapper.BrowserId));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -24,12 +24,14 @@
|
||||
<StartupObject />
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="CefSharp, Version=57.0.0.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138" />
|
||||
<Reference Include="CefSharp.BrowserSubprocess.Core">
|
||||
<HintPath>..\packages\CefSharp.Common.57.0.0\CefSharp\x86\CefSharp.BrowserSubprocess.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="NativeMethods.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
@@ -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 @@ namespace UnitTests.Core.Utils{
|
||||
|
||||
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"));
|
||||
}
|
||||
}
|
||||
}
|
@@ -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]
|
||||
@@ -12,7 +13,7 @@ namespace UnitTests.Core.Utils{
|
||||
|
||||
[TestMethod]
|
||||
public void TestValidString(){
|
||||
CommandLineArgs args = CommandLineArgsParser.ReadCefArguments("--aaa --bbb --first-value=123 --SECOND-VALUE=\"a b c d e\" --ccc");
|
||||
CommandLineArgs args = CommandLineArgsParser.ReadCefArguments("--aaa --bbb --first-value=123 --SECOND-VALUE=\"a b c d e\"\r\n--ccc");
|
||||
// cef has no flags, flag arguments have a value of 1
|
||||
// the processing removes all dashes in front of each key
|
||||
|
36
tests/Core/TestStringUtils.cs
Normal file
36
tests/Core/TestStringUtils.cs
Normal 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"));
|
||||
}
|
||||
}
|
||||
}
|
@@ -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]
|
@@ -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]
|
54
tests/Data/TestFileSerializer.cs
Normal file
54
tests/Data/TestFileSerializer.cs
Normal 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
|
||||
}
|
||||
}
|
@@ -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>();
|
@@ -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(){
|
@@ -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>
|
||||
|
Reference in New Issue
Block a user