mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-09-14 10:32:10 +02:00
Compare commits
47 Commits
Author | SHA1 | Date | |
---|---|---|---|
00acc677e6 | |||
1a799881e8 | |||
f75677593a | |||
19e3bd19f0 | |||
85701b0a3c | |||
014cb18dcb | |||
e71e1c853f | |||
ee9d9196f5 | |||
53c8272e01 | |||
7f7b6b1e2a | |||
405777e0f5 | |||
df2b624cb5 | |||
8a48d5c2f9 | |||
c55ee71442 | |||
3f82745f5b | |||
404187a1ae | |||
2b7b3f586b | |||
04959a3493 | |||
97cf4932ae | |||
b0d88a0a37 | |||
67a2e40622 | |||
3a28556c7f | |||
9ecc92b9a5 | |||
ca023be98a | |||
11a1423f76 | |||
79f6df121b | |||
71eade7e86 | |||
5f81d29036 | |||
ec1cb5dc5f | |||
fd969e2d55 | |||
37e33b77ff | |||
f7ed7703b4 | |||
4bb35295ca | |||
1e4f673f9e | |||
7cadb1c403 | |||
37148f5093 | |||
f6bc26789f | |||
b3f5a88525 | |||
1e538d2b28 | |||
7d7bfb7b01 | |||
41d86ba440 | |||
3df474a8a5 | |||
a50d6e8f47 | |||
6081e5b9c1 | |||
66ccea920c | |||
470d63093f | |||
eae0507831 |
@@ -1,7 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using TweetDck.Core.Utils;
|
||||||
|
|
||||||
namespace TweetDck.Configuration{
|
namespace TweetDck.Configuration{
|
||||||
sealed class LockManager{
|
sealed class LockManager{
|
||||||
@@ -114,13 +115,16 @@ namespace TweetDck.Configuration{
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CloseLockingProcess(int timeout){
|
public bool CloseLockingProcess(int closeTimeout, int killTimeout){
|
||||||
if (LockingProcess != null){
|
if (LockingProcess != null){
|
||||||
LockingProcess.CloseMainWindow();
|
try{
|
||||||
|
if (LockingProcess.CloseMainWindow()){
|
||||||
|
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, closeTimeout, 250);
|
||||||
|
}
|
||||||
|
|
||||||
for(int waited = 0; waited < timeout && !LockingProcess.HasExited; waited += 250){
|
if (!LockingProcess.HasExited){
|
||||||
LockingProcess.Refresh();
|
LockingProcess.Kill();
|
||||||
Thread.Sleep(250);
|
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, killTimeout, 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LockingProcess.HasExited){
|
if (LockingProcess.HasExited){
|
||||||
@@ -128,11 +132,28 @@ namespace TweetDck.Configuration{
|
|||||||
LockingProcess = null;
|
LockingProcess = null;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}catch(Exception ex){
|
||||||
|
if (ex is InvalidOperationException || ex is Win32Exception){
|
||||||
|
if (LockingProcess != null){
|
||||||
|
LockingProcess.Refresh();
|
||||||
|
|
||||||
|
bool hasExited = LockingProcess.HasExited;
|
||||||
|
LockingProcess.Dispose();
|
||||||
|
return hasExited;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool CheckLockingProcessExited(){
|
||||||
|
LockingProcess.Refresh();
|
||||||
|
return LockingProcess.HasExited;
|
||||||
|
}
|
||||||
|
|
||||||
// Utility functions
|
// Utility functions
|
||||||
|
|
||||||
private static void WriteIntToStream(Stream stream, int value){
|
private static void WriteIntToStream(Stream stream, int value){
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<packages xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
<packages xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||||
<package id="cef.redist.x64" version="3.2785.1486" targetFramework="net452" />
|
<package id="cef.redist.x64" version="3.2883.1552" targetFramework="net452" xmlns="" />
|
||||||
<package id="cef.redist.x86" version="3.2785.1486" targetFramework="net452" />
|
<package id="cef.redist.x86" version="3.2883.1552" targetFramework="net452" xmlns="" />
|
||||||
<package id="CefSharp.Common" version="53.0.1" targetFramework="net452" />
|
<package id="CefSharp.Common" version="55.0.0" targetFramework="net452" xmlns="" />
|
||||||
<package id="CefSharp.WinForms" version="53.0.1" targetFramework="net452" />
|
<package id="CefSharp.WinForms" version="55.0.0" targetFramework="net452" xmlns="" />
|
||||||
<package id="Microsoft.VC120.CRT.JetBrains" version="12.0.21005.2" targetFramework="net452" />
|
<package id="Microsoft.VC120.CRT.JetBrains" version="12.0.21005.2" targetFramework="net452" xmlns="" />
|
||||||
</packages>
|
</packages>
|
@@ -27,49 +27,49 @@ namespace TweetDck.Core.Bridge{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void LoadFontSizeClass(string fsClass){
|
public void LoadFontSizeClass(string fsClass){
|
||||||
form.InvokeSafe(() => {
|
form.InvokeAsyncSafe(() => {
|
||||||
TweetNotification.SetFontSizeClass(fsClass);
|
TweetNotification.SetFontSizeClass(fsClass);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadNotificationHeadContents(string headContents){
|
public void LoadNotificationHeadContents(string headContents){
|
||||||
form.InvokeSafe(() => {
|
form.InvokeAsyncSafe(() => {
|
||||||
TweetNotification.SetHeadTag(headContents);
|
TweetNotification.SetHeadTag(headContents);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetLastRightClickedLink(string link){
|
public void SetLastRightClickedLink(string link){
|
||||||
form.InvokeSafe(() => LastRightClickedLink = link);
|
form.InvokeAsyncSafe(() => LastRightClickedLink = link);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetLastHighlightedTweet(string link, string quotedLink){
|
public void SetLastHighlightedTweet(string link, string quotedLink){
|
||||||
form.InvokeSafe(() => {
|
form.InvokeAsyncSafe(() => {
|
||||||
LastHighlightedTweet = link;
|
LastHighlightedTweet = link;
|
||||||
LastHighlightedQuotedTweet = quotedLink;
|
LastHighlightedQuotedTweet = quotedLink;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetNotificationQuotedTweet(string link){
|
public void SetNotificationQuotedTweet(string link){
|
||||||
notification.InvokeSafe(() => notification.CurrentQuotedTweetUrl = link);
|
notification.InvokeAsyncSafe(() => notification.CurrentQuotedTweetUrl = link);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OpenSettingsMenu(){
|
public void OpenSettingsMenu(){
|
||||||
form.InvokeSafe(form.OpenSettings);
|
form.InvokeAsyncSafe(form.OpenSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OpenPluginsMenu(){
|
public void OpenPluginsMenu(){
|
||||||
form.InvokeSafe(form.OpenPlugins);
|
form.InvokeAsyncSafe(form.OpenPlugins);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnTweetPopup(string tweetHtml, string tweetUrl, int tweetCharacters){
|
public void OnTweetPopup(string tweetHtml, string tweetUrl, int tweetCharacters){
|
||||||
notification.InvokeSafe(() => {
|
notification.InvokeAsyncSafe(() => {
|
||||||
form.OnTweetNotification();
|
form.OnTweetNotification();
|
||||||
notification.ShowNotification(new TweetNotification(tweetHtml, tweetUrl, tweetCharacters));
|
notification.ShowNotification(new TweetNotification(tweetHtml, tweetUrl, tweetCharacters));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnTweetSound(){
|
public void OnTweetSound(){
|
||||||
form.InvokeSafe(() => {
|
form.InvokeAsyncSafe(() => {
|
||||||
form.OnTweetNotification();
|
form.OnTweetNotification();
|
||||||
form.PlayNotificationSound();
|
form.PlayNotificationSound();
|
||||||
});
|
});
|
||||||
@@ -77,15 +77,15 @@ namespace TweetDck.Core.Bridge{
|
|||||||
|
|
||||||
public void DisplayTooltip(string text, bool showInNotification){
|
public void DisplayTooltip(string text, bool showInNotification){
|
||||||
if (showInNotification){
|
if (showInNotification){
|
||||||
notification.InvokeSafe(() => notification.DisplayTooltip(text));
|
notification.InvokeAsyncSafe(() => notification.DisplayTooltip(text));
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
form.InvokeSafe(() => form.DisplayTooltip(text));
|
form.InvokeAsyncSafe(() => form.DisplayTooltip(text));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadNextNotification(){
|
public void LoadNextNotification(){
|
||||||
notification.InvokeSafe(notification.FinishCurrentTweet);
|
notification.InvokeAsyncSafe(notification.FinishCurrentTweet);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TryPasteImage(){
|
public void TryPasteImage(){
|
||||||
@@ -109,23 +109,15 @@ namespace TweetDck.Core.Bridge{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void ClickUploadImage(int offsetX, int offsetY){
|
public void ClickUploadImage(int offsetX, int offsetY){
|
||||||
form.InvokeSafe(() => {
|
form.InvokeAsyncSafe(() => form.TriggerImageUpload(offsetX, offsetY));
|
||||||
Point prevPos = Cursor.Position;
|
|
||||||
|
|
||||||
Cursor.Position = form.PointToScreen(new Point(offsetX, offsetY));
|
|
||||||
NativeMethods.SimulateMouseClick(NativeMethods.MouseButton.Left);
|
|
||||||
Cursor.Position = prevPos;
|
|
||||||
|
|
||||||
form.OnImagePastedFinish();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ScreenshotTweet(string html, int width, int height){
|
public void ScreenshotTweet(string html, int width, int height){
|
||||||
form.InvokeSafe(() => form.OnTweetScreenshotReady(html, width, height));
|
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width, height));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FixClipboard(){
|
public void FixClipboard(){
|
||||||
form.InvokeSafe(WindowsUtils.ClipboardStripHtmlStyles);
|
form.InvokeAsyncSafe(WindowsUtils.ClipboardStripHtmlStyles);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OpenBrowser(string url){
|
public void OpenBrowser(string url){
|
||||||
|
@@ -16,6 +16,10 @@ namespace TweetDck.Core.Controls{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void InvokeAsyncSafe(this Control control, Action func){
|
||||||
|
control.BeginInvoke(func);
|
||||||
|
}
|
||||||
|
|
||||||
public static void MoveToCenter(this Form targetForm, Form parentForm){
|
public static void MoveToCenter(this Form targetForm, Form parentForm){
|
||||||
targetForm.Location = new Point(parentForm.Location.X+parentForm.Width/2-targetForm.Width/2, parentForm.Location.Y+parentForm.Height/2-targetForm.Height/2);
|
targetForm.Location = new Point(parentForm.Location.X+parentForm.Width/2-targetForm.Width/2, parentForm.Location.Y+parentForm.Height/2-targetForm.Height/2);
|
||||||
}
|
}
|
||||||
|
@@ -13,12 +13,11 @@ using TweetDck.Updates;
|
|||||||
using TweetDck.Plugins;
|
using TweetDck.Plugins;
|
||||||
using TweetDck.Plugins.Enums;
|
using TweetDck.Plugins.Enums;
|
||||||
using TweetDck.Plugins.Events;
|
using TweetDck.Plugins.Events;
|
||||||
using System.Media;
|
|
||||||
using TweetDck.Core.Bridge;
|
using TweetDck.Core.Bridge;
|
||||||
using TweetDck.Core.Notification;
|
using TweetDck.Core.Notification;
|
||||||
using TweetDck.Core.Notification.Screenshot;
|
using TweetDck.Core.Notification.Screenshot;
|
||||||
using TweetDck.Updates.Events;
|
using TweetDck.Updates.Events;
|
||||||
using System.IO;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace TweetDck.Core{
|
namespace TweetDck.Core{
|
||||||
sealed partial class FormBrowser : Form{
|
sealed partial class FormBrowser : Form{
|
||||||
@@ -43,8 +42,7 @@ namespace TweetDck.Core{
|
|||||||
private FormWindowState prevState;
|
private FormWindowState prevState;
|
||||||
|
|
||||||
private TweetScreenshotManager notificationScreenshotManager;
|
private TweetScreenshotManager notificationScreenshotManager;
|
||||||
private SoundPlayer notificationSound;
|
private SoundNotification soundNotification;
|
||||||
private bool ignoreNotificationSoundError;
|
|
||||||
|
|
||||||
public FormBrowser(PluginManager pluginManager, UpdaterSettings updaterSettings){
|
public FormBrowser(PluginManager pluginManager, UpdaterSettings updaterSettings){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -90,8 +88,8 @@ namespace TweetDck.Core{
|
|||||||
notificationScreenshotManager.Dispose();
|
notificationScreenshotManager.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notificationSound != null){
|
if (soundNotification != null){
|
||||||
notificationSound.Dispose();
|
soundNotification.Dispose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -233,7 +231,7 @@ namespace TweetDck.Core{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void plugins_Reloaded(object sender, PluginLoadEventArgs e){
|
private void plugins_Reloaded(object sender, PluginLoadEventArgs e){
|
||||||
ReloadBrowser();
|
browser.GetBrowser().Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void plugins_PluginChangedState(object sender, PluginChangedStateEventArgs e){
|
private void plugins_PluginChangedState(object sender, PluginChangedStateEventArgs e){
|
||||||
@@ -246,6 +244,7 @@ namespace TweetDck.Core{
|
|||||||
FormUpdateDownload downloadForm = new FormUpdateDownload(e.UpdateInfo);
|
FormUpdateDownload downloadForm = new FormUpdateDownload(e.UpdateInfo);
|
||||||
downloadForm.MoveToCenter(this);
|
downloadForm.MoveToCenter(this);
|
||||||
downloadForm.ShowDialog();
|
downloadForm.ShowDialog();
|
||||||
|
downloadForm.Dispose();
|
||||||
|
|
||||||
if (downloadForm.UpdateStatus == FormUpdateDownload.Status.Succeeded){
|
if (downloadForm.UpdateStatus == FormUpdateDownload.Status.Succeeded){
|
||||||
UpdateInstallerPath = downloadForm.InstallerPath;
|
UpdateInstallerPath = downloadForm.InstallerPath;
|
||||||
@@ -266,7 +265,12 @@ namespace TweetDck.Core{
|
|||||||
|
|
||||||
protected override void WndProc(ref Message m){
|
protected override void WndProc(ref Message m){
|
||||||
if (isLoaded && m.Msg == Program.WindowRestoreMessage){
|
if (isLoaded && m.Msg == Program.WindowRestoreMessage){
|
||||||
|
using(Process process = Process.GetCurrentProcess()){
|
||||||
|
if (process.Id == m.WParam.ToInt32()){
|
||||||
trayIcon_ClickRestore(trayIcon, new EventArgs());
|
trayIcon_ClickRestore(trayIcon, new EventArgs());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,13 +309,17 @@ namespace TweetDck.Core{
|
|||||||
// callback handlers
|
// callback handlers
|
||||||
|
|
||||||
public void OpenSettings(){
|
public void OpenSettings(){
|
||||||
|
OpenSettings(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenSettings(int tabIndex){
|
||||||
if (currentFormSettings != null){
|
if (currentFormSettings != null){
|
||||||
currentFormSettings.BringToFront();
|
currentFormSettings.BringToFront();
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
|
bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
|
||||||
|
|
||||||
currentFormSettings = new FormSettings(this, plugins, updates);
|
currentFormSettings = new FormSettings(this, plugins, updates, tabIndex);
|
||||||
|
|
||||||
currentFormSettings.FormClosed += (sender, args) => {
|
currentFormSettings.FormClosed += (sender, args) => {
|
||||||
currentFormSettings = null;
|
currentFormSettings = null;
|
||||||
@@ -368,45 +376,11 @@ namespace TweetDck.Core{
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notificationSound == null){
|
if (soundNotification == null){
|
||||||
notificationSound = new SoundPlayer{
|
soundNotification = new SoundNotification(this);
|
||||||
LoadTimeout = 5000
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notificationSound.SoundLocation != Config.NotificationSoundPath){
|
soundNotification.Play(Config.NotificationSoundPath);
|
||||||
notificationSound.SoundLocation = Config.NotificationSoundPath;
|
|
||||||
ignoreNotificationSoundError = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
|
||||||
notificationSound.Play();
|
|
||||||
}catch(FileNotFoundException e){
|
|
||||||
OnNotificationSoundError("File not found: "+e.FileName);
|
|
||||||
}catch(InvalidOperationException){
|
|
||||||
OnNotificationSoundError("File is not a valid sound file.");
|
|
||||||
}catch(TimeoutException){
|
|
||||||
OnNotificationSoundError("File took too long to load.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnNotificationSoundError(string message){
|
|
||||||
if (!ignoreNotificationSoundError){
|
|
||||||
ignoreNotificationSoundError = true;
|
|
||||||
|
|
||||||
using(FormMessage form = new FormMessage("Notification Sound Error", "Could not play custom notification sound."+Environment.NewLine+message, MessageBoxIcon.Error)){
|
|
||||||
form.AddButton("Ignore");
|
|
||||||
|
|
||||||
Button btnOpenSettings = form.AddButton("Open Settings");
|
|
||||||
btnOpenSettings.Width += 16;
|
|
||||||
btnOpenSettings.Location = new Point(btnOpenSettings.Location.X-16, btnOpenSettings.Location.Y);
|
|
||||||
|
|
||||||
if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnOpenSettings){
|
|
||||||
OpenSettings();
|
|
||||||
currentFormSettings.SelectTab(FormSettings.TabIndexNotification);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnTweetScreenshotReady(string html, int width, int height){
|
public void OnTweetScreenshotReady(string html, int width, int height){
|
||||||
@@ -432,16 +406,14 @@ namespace TweetDck.Core{
|
|||||||
browser.ExecuteScriptAsync("TDGF_tryPasteImage()");
|
browser.ExecuteScriptAsync("TDGF_tryPasteImage()");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnImagePastedFinish(){
|
public void TriggerImageUpload(int offsetX, int offsetY){
|
||||||
browser.ExecuteScriptAsync("TDGF_tryPasteImageFinish()");
|
IBrowserHost host = browser.GetBrowser().GetHost();
|
||||||
|
host.SendMouseClickEvent(offsetX, offsetY, MouseButtonType.Left, false, 1, CefEventFlags.None);
|
||||||
|
host.SendMouseClickEvent(offsetX, offsetY, MouseButtonType.Left, true, 1, CefEventFlags.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TriggerTweetScreenshot(){
|
public void TriggerTweetScreenshot(){
|
||||||
browser.ExecuteScriptAsync("TDGF_triggerScreenshot()");
|
browser.ExecuteScriptAsync("TDGF_triggerScreenshot()");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReloadBrowser(){
|
|
||||||
browser.ExecuteScriptAsync("window.location.reload()");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -101,7 +101,7 @@ namespace TweetDck.Core.Handling{
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void SetClipboardText(string text){
|
protected void SetClipboardText(string text){
|
||||||
form.InvokeSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText));
|
form.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void RemoveSeparatorIfLast(IMenuModel model){
|
protected static void RemoveSeparatorIfLast(IMenuModel model){
|
||||||
|
@@ -95,19 +95,19 @@ namespace TweetDck.Core.Handling{
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuSettings:
|
case MenuSettings:
|
||||||
form.InvokeSafe(form.OpenSettings);
|
form.InvokeAsyncSafe(form.OpenSettings);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuAbout:
|
case MenuAbout:
|
||||||
form.InvokeSafe(form.OpenAbout);
|
form.InvokeAsyncSafe(form.OpenAbout);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuPlugins:
|
case MenuPlugins:
|
||||||
form.InvokeSafe(form.OpenPlugins);
|
form.InvokeAsyncSafe(form.OpenPlugins);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuMute:
|
case MenuMute:
|
||||||
form.InvokeSafe(() => {
|
form.InvokeAsyncSafe(() => {
|
||||||
Program.UserConfig.MuteNotifications = !Program.UserConfig.MuteNotifications;
|
Program.UserConfig.MuteNotifications = !Program.UserConfig.MuteNotifications;
|
||||||
Program.UserConfig.Save();
|
Program.UserConfig.Save();
|
||||||
});
|
});
|
||||||
@@ -123,7 +123,7 @@ namespace TweetDck.Core.Handling{
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuScreenshotTweet:
|
case MenuScreenshotTweet:
|
||||||
form.InvokeSafe(form.TriggerTweetScreenshot);
|
form.InvokeAsyncSafe(form.TriggerTweetScreenshot);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuOpenQuotedTweetUrl:
|
case MenuOpenQuotedTweetUrl:
|
||||||
|
@@ -49,7 +49,7 @@ namespace TweetDck.Core.Handling{
|
|||||||
|
|
||||||
RemoveSeparatorIfLast(model);
|
RemoveSeparatorIfLast(model);
|
||||||
|
|
||||||
form.InvokeSafe(() => form.ContextMenuOpen = true);
|
form.InvokeAsyncSafe(() => form.ContextMenuOpen = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
|
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
|
||||||
@@ -59,11 +59,11 @@ namespace TweetDck.Core.Handling{
|
|||||||
|
|
||||||
switch((int)commandId){
|
switch((int)commandId){
|
||||||
case MenuSkipTweet:
|
case MenuSkipTweet:
|
||||||
form.InvokeSafe(form.FinishCurrentTweet);
|
form.InvokeAsyncSafe(form.FinishCurrentTweet);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuFreeze:
|
case MenuFreeze:
|
||||||
form.InvokeSafe(() => form.FreezeTimer = !form.FreezeTimer);
|
form.InvokeAsyncSafe(() => form.FreezeTimer = !form.FreezeTimer);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MenuCopyTweetUrl:
|
case MenuCopyTweetUrl:
|
||||||
@@ -80,7 +80,7 @@ namespace TweetDck.Core.Handling{
|
|||||||
|
|
||||||
public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
|
public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
|
||||||
base.OnContextMenuDismissed(browserControl, browser, frame);
|
base.OnContextMenuDismissed(browserControl, browser, frame);
|
||||||
form.InvokeSafe(() => form.ContextMenuOpen = false);
|
form.InvokeAsyncSafe(() => form.ContextMenuOpen = false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
66
Core/Notification/SoundNotification.cs
Normal file
66
Core/Notification/SoundNotification.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.IO;
|
||||||
|
using System.Media;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDck.Core.Other;
|
||||||
|
|
||||||
|
namespace TweetDck.Core.Notification{
|
||||||
|
class SoundNotification : IDisposable{
|
||||||
|
private readonly FormBrowser browserForm;
|
||||||
|
|
||||||
|
private SoundPlayer notificationSound;
|
||||||
|
private bool ignoreNotificationSoundError;
|
||||||
|
|
||||||
|
public SoundNotification(FormBrowser browserForm){
|
||||||
|
this.browserForm = browserForm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Play(string file){
|
||||||
|
if (notificationSound == null){
|
||||||
|
notificationSound = new SoundPlayer{
|
||||||
|
LoadTimeout = 5000
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notificationSound.SoundLocation != file){
|
||||||
|
notificationSound.SoundLocation = file;
|
||||||
|
ignoreNotificationSoundError = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
notificationSound.Play();
|
||||||
|
}catch(FileNotFoundException e){
|
||||||
|
OnNotificationSoundError("File not found: "+e.FileName);
|
||||||
|
}catch(InvalidOperationException){
|
||||||
|
OnNotificationSoundError("File is not a valid sound file.");
|
||||||
|
}catch(TimeoutException){
|
||||||
|
OnNotificationSoundError("File took too long to load.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnNotificationSoundError(string message){
|
||||||
|
if (!ignoreNotificationSoundError){
|
||||||
|
ignoreNotificationSoundError = true;
|
||||||
|
|
||||||
|
using(FormMessage form = new FormMessage("Notification Sound Error", "Could not play custom notification sound."+Environment.NewLine+message, MessageBoxIcon.Error)){
|
||||||
|
form.AddButton("Ignore");
|
||||||
|
|
||||||
|
Button btnOpenSettings = form.AddButton("Open Settings");
|
||||||
|
btnOpenSettings.Width += 16;
|
||||||
|
btnOpenSettings.Location = new Point(btnOpenSettings.Location.X-16, btnOpenSettings.Location.Y);
|
||||||
|
|
||||||
|
if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnOpenSettings){
|
||||||
|
browserForm.OpenSettings(FormSettings.TabIndexNotification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose(){
|
||||||
|
if (notificationSound != null){
|
||||||
|
notificationSound.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -13,10 +13,9 @@ namespace TweetDck.Core.Other{
|
|||||||
|
|
||||||
private readonly FormBrowser browser;
|
private readonly FormBrowser browser;
|
||||||
private readonly Dictionary<Type, BaseTabSettings> tabs = new Dictionary<Type, BaseTabSettings>(4);
|
private readonly Dictionary<Type, BaseTabSettings> tabs = new Dictionary<Type, BaseTabSettings>(4);
|
||||||
|
private readonly bool hasFinishedLoading;
|
||||||
|
|
||||||
private bool wasTabSelectedAutomatically;
|
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, int startTabIndex = 0){
|
||||||
|
|
||||||
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates){
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName+" Settings";
|
Text = Program.BrandName+" Settings";
|
||||||
@@ -26,16 +25,12 @@ namespace TweetDck.Core.Other{
|
|||||||
|
|
||||||
this.tabPanel.SetupTabPanel(100);
|
this.tabPanel.SetupTabPanel(100);
|
||||||
this.tabPanel.AddButton("General", SelectTab<TabSettingsGeneral>);
|
this.tabPanel.AddButton("General", SelectTab<TabSettingsGeneral>);
|
||||||
this.tabPanel.AddButton("Notifications", () => SelectTab(() => new TabSettingsNotifications(browser.CreateNotificationForm(NotificationFlags.DisableContextMenu), wasTabSelectedAutomatically)));
|
this.tabPanel.AddButton("Notifications", () => SelectTab(() => new TabSettingsNotifications(browser.CreateNotificationForm(NotificationFlags.DisableContextMenu), !hasFinishedLoading)));
|
||||||
this.tabPanel.AddButton("Updates", () => SelectTab(() => new TabSettingsUpdates(updates)));
|
this.tabPanel.AddButton("Updates", () => SelectTab(() => new TabSettingsUpdates(updates)));
|
||||||
this.tabPanel.AddButton("Advanced", () => SelectTab(() => new TabSettingsAdvanced(browser.ReinjectCustomCSS, plugins)));
|
this.tabPanel.AddButton("Advanced", () => SelectTab(() => new TabSettingsAdvanced(browser.ReinjectCustomCSS, plugins)));
|
||||||
this.tabPanel.SelectTab(tabPanel.Buttons.First());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SelectTab(int index){
|
this.tabPanel.SelectTab(tabPanel.Buttons.ElementAt(startTabIndex));
|
||||||
wasTabSelectedAutomatically = true;
|
hasFinishedLoading = true;
|
||||||
this.tabPanel.SelectTab(tabPanel.Buttons.ElementAt(index));
|
|
||||||
wasTabSelectedAutomatically = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SelectTab<T>() where T : BaseTabSettings, new(){
|
private void SelectTab<T>() where T : BaseTabSettings, new(){
|
||||||
|
@@ -104,6 +104,7 @@
|
|||||||
this.Controls.Add(this.cbConfig);
|
this.Controls.Add(this.cbConfig);
|
||||||
this.Controls.Add(this.btnApply);
|
this.Controls.Add(this.btnApply);
|
||||||
this.Controls.Add(this.btnCancel);
|
this.Controls.Add(this.btnCancel);
|
||||||
|
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
|
||||||
this.MinimumSize = new System.Drawing.Size(200, 170);
|
this.MinimumSize = new System.Drawing.Size(200, 170);
|
||||||
this.Name = "DialogSettingsExport";
|
this.Name = "DialogSettingsExport";
|
||||||
this.ShowIcon = false;
|
this.ShowIcon = false;
|
||||||
|
100
Core/Utils/TwoKeyDictionary.cs
Normal file
100
Core/Utils/TwoKeyDictionary.cs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace TweetDck.Core.Utils{
|
||||||
|
class TwoKeyDictionary<K1, K2, V>{
|
||||||
|
private readonly Dictionary<K1, Dictionary<K2, V>> dict;
|
||||||
|
private readonly int innerCapacity;
|
||||||
|
|
||||||
|
public TwoKeyDictionary() : this(16, 16){}
|
||||||
|
|
||||||
|
public TwoKeyDictionary(int outerCapacity, int innerCapacity){
|
||||||
|
this.dict = new Dictionary<K1, Dictionary<K2, V>>(outerCapacity);
|
||||||
|
this.innerCapacity = innerCapacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
|
||||||
|
public V this[K1 outerKey, K2 innerKey]{
|
||||||
|
get{ // throws on missing key
|
||||||
|
return dict[outerKey][innerKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
set{
|
||||||
|
Dictionary<K2, V> innerDict;
|
||||||
|
|
||||||
|
if (!dict.TryGetValue(outerKey, out innerDict)){
|
||||||
|
dict.Add(outerKey, innerDict = new Dictionary<K2, V>(innerCapacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
innerDict[innerKey] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Members
|
||||||
|
|
||||||
|
public void Add(K1 outerKey, K2 innerKey, V value){ // throws on duplicate
|
||||||
|
Dictionary<K2, V> innerDict;
|
||||||
|
|
||||||
|
if (!dict.TryGetValue(outerKey, out innerDict)){
|
||||||
|
dict.Add(outerKey, innerDict = new Dictionary<K2, V>(innerCapacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
innerDict.Add(innerKey, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear(){
|
||||||
|
this.dict.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear(K1 outerKey){ // throws on missing key, but keeps the key unlike Remove(K1)
|
||||||
|
dict[outerKey].Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(K1 outerKey){
|
||||||
|
return dict.ContainsKey(outerKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(K1 outerKey, K2 innerKey){
|
||||||
|
Dictionary<K2, V> innerDict;
|
||||||
|
return dict.TryGetValue(outerKey, out innerDict) && innerDict.ContainsKey(innerKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count(){
|
||||||
|
return dict.Values.Sum(d => d.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count(K1 outerKey){ // throws on missing key
|
||||||
|
return dict[outerKey].Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(K1 outerKey){
|
||||||
|
return dict.Remove(outerKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(K1 outerKey, K2 innerKey){
|
||||||
|
Dictionary<K2, V> innerDict;
|
||||||
|
|
||||||
|
if (dict.TryGetValue(outerKey, out innerDict) && innerDict.Remove(innerKey)){
|
||||||
|
if (innerDict.Count == 0){
|
||||||
|
dict.Remove(outerKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetValue(K1 outerKey, K2 innerKey, out V value){
|
||||||
|
Dictionary<K2, V> innerDict;
|
||||||
|
|
||||||
|
if (dict.TryGetValue(outerKey, out innerDict)){
|
||||||
|
return innerDict.TryGetValue(innerKey, out value);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
value = default(V);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,8 +1,11 @@
|
|||||||
using System.Diagnostics;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
using Timer = System.Windows.Forms.Timer;
|
||||||
|
|
||||||
namespace TweetDck.Core.Utils{
|
namespace TweetDck.Core.Utils{
|
||||||
static class WindowsUtils{
|
static class WindowsUtils{
|
||||||
@@ -45,6 +48,18 @@ namespace TweetDck.Core.Utils{
|
|||||||
return timer;
|
return timer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool TrySleepUntil(Func<bool> test, int timeoutMillis, int timeStepMillis){
|
||||||
|
for(int waited = 0; waited < timeoutMillis; waited += timeStepMillis){
|
||||||
|
if (test()){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.Sleep(timeStepMillis);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public static void ClipboardStripHtmlStyles(){
|
public static void ClipboardStripHtmlStyles(){
|
||||||
if (!Clipboard.ContainsText(TextDataFormat.Html)){
|
if (!Clipboard.ContainsText(TextDataFormat.Html)){
|
||||||
return;
|
return;
|
||||||
|
@@ -1,24 +1,35 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using TweetDck.Core.Utils;
|
||||||
using TweetDck.Plugins.Enums;
|
using TweetDck.Plugins.Enums;
|
||||||
using TweetDck.Plugins.Events;
|
using TweetDck.Plugins.Events;
|
||||||
|
|
||||||
namespace TweetDck.Plugins{
|
namespace TweetDck.Plugins{
|
||||||
class PluginBridge{
|
class PluginBridge{
|
||||||
|
private static string SanitizeCacheKey(string key){
|
||||||
|
return key.Replace('\\', '/').Trim();
|
||||||
|
}
|
||||||
|
|
||||||
private readonly PluginManager manager;
|
private readonly PluginManager manager;
|
||||||
private readonly Dictionary<string, string> fileCache = new Dictionary<string, string>(2);
|
private readonly TwoKeyDictionary<int, string, string> fileCache = new TwoKeyDictionary<int, string, string>(4, 2);
|
||||||
|
|
||||||
public PluginBridge(PluginManager manager){
|
public PluginBridge(PluginManager manager){
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.manager.Reloaded += manager_Reloaded;
|
this.manager.Reloaded += manager_Reloaded;
|
||||||
|
this.manager.PluginChangedState += manager_PluginChangedState;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void manager_Reloaded(object sender, PluginLoadEventArgs e){
|
private void manager_Reloaded(object sender, PluginLoadEventArgs e){
|
||||||
fileCache.Clear();
|
fileCache.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void manager_PluginChangedState(object sender, PluginChangedStateEventArgs e){
|
||||||
|
if (!e.IsEnabled){
|
||||||
|
fileCache.Remove(manager.GetTokenFromPlugin(e.Plugin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private string GetFullPathOrThrow(int token, PluginFolder folder, string path){
|
private string GetFullPathOrThrow(int token, PluginFolder folder, string path){
|
||||||
Plugin plugin = manager.GetPluginFromToken(token);
|
Plugin plugin = manager.GetPluginFromToken(token);
|
||||||
string fullPath = plugin == null ? string.Empty : plugin.GetFullPathIfSafe(folder, path);
|
string fullPath = plugin == null ? string.Empty : plugin.GetFullPathIfSafe(folder, path);
|
||||||
@@ -35,15 +46,17 @@ namespace TweetDck.Plugins{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ReadFileUnsafe(string fullPath, bool readCached){
|
private string ReadFileUnsafe(int token, string cacheKey, string fullPath, bool readCached){
|
||||||
|
cacheKey = SanitizeCacheKey(cacheKey);
|
||||||
|
|
||||||
string cachedContents;
|
string cachedContents;
|
||||||
|
|
||||||
if (readCached && fileCache.TryGetValue(fullPath, out cachedContents)){
|
if (readCached && fileCache.TryGetValue(token, cacheKey, out cachedContents)){
|
||||||
return cachedContents;
|
return cachedContents;
|
||||||
}
|
}
|
||||||
|
|
||||||
try{
|
try{
|
||||||
return fileCache[fullPath] = File.ReadAllText(fullPath, Encoding.UTF8);
|
return fileCache[token, cacheKey] = File.ReadAllText(fullPath, Encoding.UTF8);
|
||||||
}catch(FileNotFoundException){
|
}catch(FileNotFoundException){
|
||||||
throw new Exception("File not found.");
|
throw new Exception("File not found.");
|
||||||
}catch(DirectoryNotFoundException){
|
}catch(DirectoryNotFoundException){
|
||||||
@@ -60,17 +73,17 @@ namespace TweetDck.Plugins{
|
|||||||
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
|
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
|
||||||
|
|
||||||
File.WriteAllText(fullPath, contents, Encoding.UTF8);
|
File.WriteAllText(fullPath, contents, Encoding.UTF8);
|
||||||
fileCache[fullPath] = contents;
|
fileCache[token, SanitizeCacheKey(path)] = contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ReadFile(int token, string path, bool cache){
|
public string ReadFile(int token, string path, bool cache){
|
||||||
return ReadFileUnsafe(GetFullPathOrThrow(token, PluginFolder.Data, path), cache);
|
return ReadFileUnsafe(token, path, GetFullPathOrThrow(token, PluginFolder.Data, path), cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteFile(int token, string path){
|
public void DeleteFile(int token, string path){
|
||||||
string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
|
string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
|
||||||
|
|
||||||
fileCache.Remove(fullPath);
|
fileCache.Remove(token, SanitizeCacheKey(path));
|
||||||
File.Delete(fullPath);
|
File.Delete(fullPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +92,7 @@ namespace TweetDck.Plugins{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public string ReadFileRoot(int token, string path){
|
public string ReadFileRoot(int token, string path){
|
||||||
return ReadFileUnsafe(GetFullPathOrThrow(token, PluginFolder.Root, path), true);
|
return ReadFileUnsafe(token, "root*"+path, GetFullPathOrThrow(token, PluginFolder.Root, path), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CheckFileExistsRoot(int token, string path){
|
public bool CheckFileExistsRoot(int token, string path){
|
||||||
|
@@ -13,6 +13,8 @@ namespace TweetDck.Plugins{
|
|||||||
public const string PluginNotificationScriptFile = "plugins.notification.js";
|
public const string PluginNotificationScriptFile = "plugins.notification.js";
|
||||||
public const string PluginGlobalScriptFile = "plugins.js";
|
public const string PluginGlobalScriptFile = "plugins.js";
|
||||||
|
|
||||||
|
private const int InvalidToken = 0;
|
||||||
|
|
||||||
public string PathOfficialPlugins { get { return Path.Combine(rootPath, "official"); } }
|
public string PathOfficialPlugins { get { return Path.Combine(rootPath, "official"); } }
|
||||||
public string PathCustomPlugins { get { return Path.Combine(rootPath, "user"); } }
|
public string PathCustomPlugins { get { return Path.Combine(rootPath, "user"); } }
|
||||||
|
|
||||||
@@ -63,6 +65,16 @@ namespace TweetDck.Plugins{
|
|||||||
return plugins.Any(plugin => plugin.Environments.HasFlag(environment));
|
return plugins.Any(plugin => plugin.Environments.HasFlag(environment));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int GetTokenFromPlugin(Plugin plugin){
|
||||||
|
foreach(KeyValuePair<int, Plugin> kvp in tokens){
|
||||||
|
if (kvp.Value.Equals(plugin)){
|
||||||
|
return kvp.Key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return InvalidToken;
|
||||||
|
}
|
||||||
|
|
||||||
public Plugin GetPluginFromToken(int token){
|
public Plugin GetPluginFromToken(int token){
|
||||||
Plugin plugin;
|
Plugin plugin;
|
||||||
return tokens.TryGetValue(token, out plugin) ? plugin : null;
|
return tokens.TryGetValue(token, out plugin) ? plugin : null;
|
||||||
@@ -108,7 +120,7 @@ namespace TweetDck.Plugins{
|
|||||||
int token;
|
int token;
|
||||||
|
|
||||||
if (tokens.ContainsValue(plugin)){
|
if (tokens.ContainsValue(plugin)){
|
||||||
token = tokens.First(kvp => kvp.Value.Equals(plugin)).Key;
|
token = GetTokenFromPlugin(plugin);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
token = GenerateToken();
|
token = GenerateToken();
|
||||||
@@ -141,7 +153,7 @@ namespace TweetDck.Plugins{
|
|||||||
for(int attempt = 0; attempt < 1000; attempt++){
|
for(int attempt = 0; attempt < 1000; attempt++){
|
||||||
int token = rand.Next();
|
int token = rand.Next();
|
||||||
|
|
||||||
if (!tokens.ContainsKey(token)){
|
if (!tokens.ContainsKey(token) && token != InvalidToken){
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
28
Program.cs
28
Program.cs
@@ -21,8 +21,8 @@ namespace TweetDck{
|
|||||||
public const string BrandName = "TweetDuck";
|
public const string BrandName = "TweetDuck";
|
||||||
public const string Website = "https://tweetduck.chylex.com";
|
public const string Website = "https://tweetduck.chylex.com";
|
||||||
|
|
||||||
public const string VersionTag = "1.6.3";
|
public const string VersionTag = "1.6.5";
|
||||||
public const string VersionFull = "1.6.3.0";
|
public const string VersionFull = "1.6.5.0";
|
||||||
|
|
||||||
public static readonly Version Version = new Version(VersionTag);
|
public static readonly Version Version = new Version(VersionTag);
|
||||||
|
|
||||||
@@ -89,21 +89,26 @@ namespace TweetDck{
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else Thread.Sleep(500);
|
||||||
Thread.Sleep(500);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
LockManager.Result lockResult = LockManager.Lock();
|
LockManager.Result lockResult = LockManager.Lock();
|
||||||
|
|
||||||
if (lockResult == LockManager.Result.HasProcess){
|
if (lockResult == LockManager.Result.HasProcess){
|
||||||
if (LockManager.LockingProcess.MainWindowHandle == IntPtr.Zero && LockManager.LockingProcess.Responding){ // restore if the original process is in tray
|
if (LockManager.LockingProcess.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
|
||||||
NativeMethods.SendMessage(NativeMethods.HWND_BROADCAST, WindowRestoreMessage, 0, IntPtr.Zero);
|
NativeMethods.SendMessage(NativeMethods.HWND_BROADCAST, WindowRestoreMessage, LockManager.LockingProcess.Id, IntPtr.Zero);
|
||||||
return;
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
else 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 (!LockManager.CloseLockingProcess(30000)){
|
|
||||||
|
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 (!LockManager.CloseLockingProcess(10000, 5000)){
|
||||||
MessageBox.Show("Could not close the other process.", BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
MessageBox.Show("Could not close the other process.", BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -166,10 +171,11 @@ namespace TweetDck{
|
|||||||
if (mainForm.UpdateInstallerPath != null){
|
if (mainForm.UpdateInstallerPath != null){
|
||||||
ExitCleanup();
|
ExitCleanup();
|
||||||
|
|
||||||
|
// ProgramPath has a trailing backslash
|
||||||
string updaterArgs = "/SP- /SILENT /CLOSEAPPLICATIONS /UPDATEPATH=\""+ProgramPath+"\" /RUNARGS=\""+GetArgsClean().ToString().Replace("\"", "^\"")+"\""+(IsPortable ? " /PORTABLE=1" : "");
|
string updaterArgs = "/SP- /SILENT /CLOSEAPPLICATIONS /UPDATEPATH=\""+ProgramPath+"\" /RUNARGS=\""+GetArgsClean().ToString().Replace("\"", "^\"")+"\""+(IsPortable ? " /PORTABLE=1" : "");
|
||||||
bool runElevated = !IsPortable || !WindowsUtils.CheckFolderWritePermission(ProgramPath);
|
bool runElevated = !IsPortable || !WindowsUtils.CheckFolderWritePermission(ProgramPath);
|
||||||
|
|
||||||
WindowsUtils.StartProcess(mainForm.UpdateInstallerPath, updaterArgs, runElevated); // ProgramPath has a trailing backslash
|
WindowsUtils.StartProcess(mainForm.UpdateInstallerPath, updaterArgs, runElevated);
|
||||||
Application.Exit();
|
Application.Exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
# Build Instructions
|
# Build Instructions
|
||||||
|
|
||||||
The program was build using Visual Studio 2013. After opening the solution, make sure you have **CefSharp.WinForms** and **Microsoft.VC120.CRT.JetBrains** included - if not, download them using NuGet. For **CefSharp**, you will need version 53 or newer currently available as a pre-release.
|
The program was build using Visual Studio 2013. After opening the solution, make sure you have **CefSharp.WinForms** and **Microsoft.VC120.CRT.JetBrains** included - if not, download them using NuGet.
|
||||||
```
|
```
|
||||||
PM> Install-Package CefSharp.WinForms -Version 53.0.1
|
PM> Install-Package CefSharp.WinForms -Version 55.0.0
|
||||||
PM> Install-Package Microsoft.VC120.CRT.JetBrains
|
PM> Install-Package Microsoft.VC120.CRT.JetBrains
|
||||||
```
|
```
|
||||||
|
|
||||||
After building, run **_postbuild.bat** which deletes unnecessary files that CefSharp adds after post-build events >_>
|
After building, run either `_postbuild.bat` if you want to package the files yourself, or `bld/RUN BUILD.bat` to generate installer files using Inno Setup. Do not run both files consecutively, otherwise the program will crash - if you want to do both, rebuild the project before running each file.
|
||||||
|
|
||||||
Built files are then available in **bin/x86** and/or **bin/x64**.
|
Built files are then available in **bin/x86** and/or **bin/x64**, installer files are generated in **bld/Output**. If you decide to release a custom version publicly, please make it clear that it is not the original TweetDuck.
|
||||||
|
14
Resources/Plugins/.debug/.meta
Normal file
14
Resources/Plugins/.debug/.meta
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[name]
|
||||||
|
Debug plugin
|
||||||
|
|
||||||
|
[description]
|
||||||
|
- Runs several tests on startup, only included in debug configuration
|
||||||
|
|
||||||
|
[author]
|
||||||
|
chylex
|
||||||
|
|
||||||
|
[version]
|
||||||
|
1.0
|
||||||
|
|
||||||
|
[website]
|
||||||
|
https://tweetduck.chylex.com
|
3
Resources/Plugins/.debug/browser.js
Normal file
3
Resources/Plugins/.debug/browser.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
enabled(){
|
||||||
|
|
||||||
|
}
|
@@ -2,6 +2,7 @@
|
|||||||
Revert TweetDeck design changes
|
Revert TweetDeck design changes
|
||||||
|
|
||||||
[description]
|
[description]
|
||||||
|
- Individually configurable options to revert and tweak TweetDeck design changes
|
||||||
- Moves action menu to the right and hides it by default
|
- Moves action menu to the right and hides it by default
|
||||||
- Reverts interactive texts around tweets (such as 'Details' or 'Conversation')
|
- Reverts interactive texts around tweets (such as 'Details' or 'Conversation')
|
||||||
|
|
||||||
@@ -9,10 +10,16 @@ Revert TweetDeck design changes
|
|||||||
chylex
|
chylex
|
||||||
|
|
||||||
[version]
|
[version]
|
||||||
1.1
|
1.2
|
||||||
|
|
||||||
[website]
|
[website]
|
||||||
https://tweetduck.chylex.com
|
https://tweetduck.chylex.com
|
||||||
|
|
||||||
|
[configfile]
|
||||||
|
configuration.js
|
||||||
|
|
||||||
|
[configdefault]
|
||||||
|
configuration.default.js
|
||||||
|
|
||||||
[requires]
|
[requires]
|
||||||
1.4.1
|
1.4.1
|
@@ -1,20 +1,36 @@
|
|||||||
enabled(){
|
enabled(){
|
||||||
// add a stylesheet to change tweet actions
|
|
||||||
this.css = window.TDPF_createCustomStyle(this);
|
this.css = window.TDPF_createCustomStyle(this);
|
||||||
this.css.insert(".tweet-actions { float: right !important; width: auto !important; }");
|
this.prevFooterMustache = TD.mustaches["status/tweet_single_footer.mustache"];
|
||||||
|
|
||||||
|
// load configuration
|
||||||
|
window.TDPF_loadConfigurationFile(this, "configuration.js", "configuration.default.js", config => {
|
||||||
|
if (config.hideTweetActions){
|
||||||
this.css.insert(".tweet-action { opacity: 0; }");
|
this.css.insert(".tweet-action { opacity: 0; }");
|
||||||
this.css.insert(".is-favorite .tweet-action, .is-retweet .tweet-action { opacity: 0.5; visibility: visible !important; }");
|
this.css.insert(".is-favorite .tweet-action, .is-retweet .tweet-action { opacity: 0.5; visibility: visible !important; }");
|
||||||
this.css.insert(".tweet:hover .tweet-action, .is-favorite .tweet-action[rel='favorite'], .is-retweet .tweet-action[rel='retweet'] { opacity: 1; visibility: visible !important; }");
|
this.css.insert(".tweet:hover .tweet-action, .is-favorite .tweet-action[rel='favorite'], .is-retweet .tweet-action[rel='retweet'] { opacity: 1; visibility: visible !important; }");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.moveTweetActionsToRight){
|
||||||
|
this.css.insert(".tweet-actions { float: right !important; width: auto !important; }");
|
||||||
this.css.insert(".tweet-actions > li:nth-child(4) { margin-right: 2px !important; }");
|
this.css.insert(".tweet-actions > li:nth-child(4) { margin-right: 2px !important; }");
|
||||||
|
}
|
||||||
|
|
||||||
// revert small links around the tweet
|
if (config.smallComposeTextSize){
|
||||||
this.prevFooterMustache = TD.mustaches["status/tweet_single_footer.mustache"];
|
this.css.insert(".compose-text { font-size: 12px !important; height: 120px !important; }");
|
||||||
|
}
|
||||||
|
|
||||||
var footerLayout = TD.mustaches["status/tweet_single_footer.mustache"];
|
if (config.revertConversationLinks){
|
||||||
footerLayout = footerLayout.replace('txt-mute txt-size--12', 'txt-mute txt-small');
|
var footer = TD.mustaches["status/tweet_single_footer.mustache"];
|
||||||
footerLayout = footerLayout.replace('{{#inReplyToID}}', '{{^inReplyToID}} <a class="pull-left margin-txs txt-mute txt-small is-vishidden-narrow" href="#" rel="viewDetails">{{_i}}Details{{/i}}</a> <a class="pull-left margin-txs txt-mute txt-small is-vishidden is-visshown-narrow" href="#" rel="viewDetails">{{_i}}Open{{/i}}</a> {{/inReplyToID}} {{#inReplyToID}}');
|
footer = footer.replace('txt-mute txt-size--12', 'txt-mute txt-small');
|
||||||
footerLayout = footerLayout.replace('<span class="link-complex-target"> {{_i}}View Conversation{{/i}}', '<i class="icon icon-conversation icon-small-context"></i> <span class="link-complex-target"> <span class="is-vishidden-wide is-vishidden-narrow">{{_i}}View{{/i}}</span> <span class="is-vishidden is-visshown-wide">{{_i}}Conversation{{/i}}</span>');
|
footer = footer.replace('{{#inReplyToID}}', '{{^inReplyToID}} <a class="pull-left margin-txs txt-mute txt-small is-vishidden-narrow" href="#" rel="viewDetails">{{_i}}Details{{/i}}</a> <a class="pull-left margin-txs txt-mute txt-small is-vishidden is-visshown-narrow" href="#" rel="viewDetails">{{_i}}Open{{/i}}</a> {{/inReplyToID}} {{#inReplyToID}}');
|
||||||
TD.mustaches["status/tweet_single_footer.mustache"] = footerLayout;
|
footer = footer.replace('<span class="link-complex-target"> {{_i}}View Conversation{{/i}}', '<i class="icon icon-conversation icon-small-context"></i> <span class="link-complex-target"> <span class="is-vishidden-wide is-vishidden-narrow">{{_i}}View{{/i}}</span> <span class="is-vishidden is-visshown-wide">{{_i}}Conversation{{/i}}</span>');
|
||||||
|
TD.mustaches["status/tweet_single_footer.mustache"] = footer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.moveTweetActionsToRight){
|
||||||
|
$(document).on("uiShowActionsMenu", this.uiShowActionsMenuEvent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// fix layout for right-aligned actions menu
|
// fix layout for right-aligned actions menu
|
||||||
this.uiShowActionsMenuEvent = function(){
|
this.uiShowActionsMenuEvent = function(){
|
||||||
@@ -22,13 +38,9 @@ enabled(){
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
ready(){
|
|
||||||
$(document).on("uiShowActionsMenu", this.uiShowActionsMenuEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
disabled(){
|
disabled(){
|
||||||
this.css.remove();
|
this.css.remove();
|
||||||
|
TD.mustaches["status/tweet_single_footer.mustache"] = this.prevFooterMustache;
|
||||||
|
|
||||||
$(document).off("uiShowActionsMenu", this.uiShowActionsMenuEvent);
|
$(document).off("uiShowActionsMenu", this.uiShowActionsMenuEvent);
|
||||||
TD.mustaches["status/tweet_single_footer.mustache"] = this.prevFooterMustache;
|
|
||||||
}
|
}
|
22
Resources/Plugins/design-revert/configuration.default.js
Normal file
22
Resources/Plugins/design-revert/configuration.default.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
/*
|
||||||
|
* Hides the action bar below each tweet.
|
||||||
|
* The action bar fully appears when the mouse is over the tweet, or at half the opacity when the tweet is liked/retweeted.
|
||||||
|
*/
|
||||||
|
hideTweetActions: true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Moves the action bar to the right side of the tweet.
|
||||||
|
*/
|
||||||
|
moveTweetActionsToRight: true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reverts New Tweet font size to a smaller one.
|
||||||
|
*/
|
||||||
|
smallComposeTextSize: false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reverts design changes to the 'View Conversation' and 'Details' links.
|
||||||
|
*/
|
||||||
|
revertConversationLinks: true
|
||||||
|
}
|
18
Resources/Plugins/emoji-keyboard/.meta
Normal file
18
Resources/Plugins/emoji-keyboard/.meta
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
[name]
|
||||||
|
Emoji keyboard
|
||||||
|
|
||||||
|
[description]
|
||||||
|
- Adds an emoji keyboard when writing tweets
|
||||||
|
- Emoji list provided by http://unicode.org/emoji/charts/emoji-ordering.html
|
||||||
|
|
||||||
|
[author]
|
||||||
|
chylex
|
||||||
|
|
||||||
|
[version]
|
||||||
|
1.0
|
||||||
|
|
||||||
|
[website]
|
||||||
|
https://tweetduck.chylex.com
|
||||||
|
|
||||||
|
[requires]
|
||||||
|
1.5.3
|
178
Resources/Plugins/emoji-keyboard/browser.js
Normal file
178
Resources/Plugins/emoji-keyboard/browser.js
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
enabled(){
|
||||||
|
this.emojiHTML = "";
|
||||||
|
|
||||||
|
var me = this;
|
||||||
|
|
||||||
|
// styles
|
||||||
|
|
||||||
|
this.css = window.TDPF_createCustomStyle(this);
|
||||||
|
this.css.insert(".emoji-keyboard { position: absolute; width: 15.35em; height: 11.2em; background-color: white; overflow-y: auto; padding: 0.1em; box-sizing: border-box; border-radius: 2px; font-size: 24px; z-index: 9999 }");
|
||||||
|
this.css.insert(".emoji-keyboard .separator { height: 26px; }");
|
||||||
|
this.css.insert(".emoji-keyboard .emoji { padding: 0.1em !important; cursor: pointer }");
|
||||||
|
|
||||||
|
// layout
|
||||||
|
|
||||||
|
var buttonHTML = '<button class="needsclick btn btn-on-blue txt-left padding-v--9 emoji-keyboard-popup-btn"><i class="icon icon-heart"></i></button>';
|
||||||
|
|
||||||
|
this.prevComposeMustache = TD.mustaches["compose/docked_compose.mustache"];
|
||||||
|
TD.mustaches["compose/docked_compose.mustache"] = TD.mustaches["compose/docked_compose.mustache"].replace('<div class="cf margin-t--12 margin-b--30">', '<div class="cf margin-t--12 margin-b--30">'+buttonHTML);
|
||||||
|
|
||||||
|
var dockedComposePanel = $(".js-docked-compose");
|
||||||
|
|
||||||
|
if (dockedComposePanel.length){
|
||||||
|
dockedComposePanel.find(".cf.margin-t--12.margin-b--30").first().append(buttonHTML);
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyboard generation
|
||||||
|
|
||||||
|
this.currentKeyboard = null;
|
||||||
|
|
||||||
|
var hideKeyboard = () => {
|
||||||
|
$(this.currentKeyboard).remove();
|
||||||
|
this.currentKeyboard = null;
|
||||||
|
|
||||||
|
$(".emoji-keyboard-popup-btn").removeClass("is-selected");
|
||||||
|
};
|
||||||
|
|
||||||
|
this.generateKeyboardFor = (input, left, top) => {
|
||||||
|
var created = document.createElement("div");
|
||||||
|
document.body.appendChild(created);
|
||||||
|
|
||||||
|
created.classList.add("emoji-keyboard");
|
||||||
|
created.style.left = left+"px";
|
||||||
|
created.style.top = top+"px";
|
||||||
|
created.innerHTML = this.emojiHTML;
|
||||||
|
|
||||||
|
created.addEventListener("click", function(e){
|
||||||
|
if (e.target.tagName === "IMG"){
|
||||||
|
input.val(input.val()+e.target.getAttribute("alt"));
|
||||||
|
input.trigger("change");
|
||||||
|
input.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
|
||||||
|
return created;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.prevTryPasteImage = window.TDGF_tryPasteImage;
|
||||||
|
var prevTryPasteImageF = this.prevTryPasteImage;
|
||||||
|
|
||||||
|
window.TDGF_tryPasteImage = function(){
|
||||||
|
if (me.currentKeyboard){
|
||||||
|
hideKeyboard();
|
||||||
|
}
|
||||||
|
|
||||||
|
return prevTryPasteImageF.apply(this, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
// event handlers
|
||||||
|
|
||||||
|
this.emojiKeyboardButtonClickEvent = function(e){
|
||||||
|
if (me.currentKeyboard){
|
||||||
|
hideKeyboard();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
var pos = $(this).offset();
|
||||||
|
me.currentKeyboard = me.generateKeyboardFor($(".js-compose-text").first(), pos.left, pos.top+$(this).outerHeight()+8);
|
||||||
|
|
||||||
|
$(this).addClass("is-selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
$(this).blur();
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.documentClickEvent = function(e){
|
||||||
|
if (me.currentKeyboard && !e.target.classList.contains("js-compose-text")){
|
||||||
|
hideKeyboard();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.documentKeyEvent = function(e){
|
||||||
|
if (me.currentKeyboard && e.keyCode === 27){ // escape
|
||||||
|
hideKeyboard();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO
|
||||||
|
* ----
|
||||||
|
* add emoji search if I can be bothered
|
||||||
|
* lazy emoji loading
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
ready(){
|
||||||
|
$(".emoji-keyboard-popup-btn").on("click", this.emojiKeyboardButtonClickEvent);
|
||||||
|
$(document).on("click", this.documentClickEvent);
|
||||||
|
$(document).on("keydown", this.documentKeyEvent);
|
||||||
|
|
||||||
|
// HTML generation
|
||||||
|
|
||||||
|
var convUnicode = function(codePt){
|
||||||
|
if (codePt > 0xFFFF){
|
||||||
|
codePt -= 0x10000;
|
||||||
|
return String.fromCharCode(0xD800+(codePt>>10), 0xDC00+(codePt&0x3FF));
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return String.fromCharCode(codePt);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$TDP.readFileRoot(this.$token, "emoji-ordering.txt").then(contents => {
|
||||||
|
let generated = [];
|
||||||
|
|
||||||
|
let addDeclaration = decl => {
|
||||||
|
generated.push(decl.split(" ").map(pt => convUnicode(parseInt(pt, 16))).join(""));
|
||||||
|
};
|
||||||
|
|
||||||
|
let skinTones = [
|
||||||
|
"1F3FB", "1F3FC", "1F3FD", "1F3FE", "1F3FF"
|
||||||
|
];
|
||||||
|
|
||||||
|
for(let line of contents.split("\n")){
|
||||||
|
if (line[0] === '@'){
|
||||||
|
generated.push("___");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
let decl = line.slice(0, line.indexOf(";"));
|
||||||
|
let skinIndex = decl.indexOf('$');
|
||||||
|
|
||||||
|
if (skinIndex !== -1){
|
||||||
|
let declPre = decl.slice(0, skinIndex);
|
||||||
|
let declPost = decl.slice(skinIndex+1);
|
||||||
|
|
||||||
|
skinTones.map(skinTone => declPre+skinTone+declPost).forEach(addDeclaration);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
addDeclaration(decl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = "<p style='font-size:13px;color:#444;margin:4px;text-align:center'>Please, note that most emoji will not show up properly in the text box above, but they will display in the tweet.</p>";
|
||||||
|
this.emojiHTML = start+TD.util.cleanWithEmoji(generated.join("")).replace(/___/g, "<div class='separator'></div>");
|
||||||
|
}).catch(err => {
|
||||||
|
$TD.alert("error", "Problem loading emoji keyboard: "+err.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
disabled(){
|
||||||
|
this.css.remove();
|
||||||
|
|
||||||
|
if (this.currentKeyboard){
|
||||||
|
$(this.currentKeyboard).remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.TDGF_tryPasteImage = this.prevTryPasteImage;
|
||||||
|
|
||||||
|
$(".emoji-keyboard-popup-btn").off("click", this.emojiKeyboardButtonClickEvent);
|
||||||
|
$(".emoji-keyboard-popup-btn").remove();
|
||||||
|
|
||||||
|
$(document).off("click", this.documentClickEvent);
|
||||||
|
$(document).off("keydown", this.documentKeyEvent);
|
||||||
|
TD.mustaches["compose/docked_compose.mustache"] = this.prevComposeMustache;
|
||||||
|
}
|
1671
Resources/Plugins/emoji-keyboard/emoji-ordering.txt
Normal file
1671
Resources/Plugins/emoji-keyboard/emoji-ordering.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -10,71 +10,14 @@
|
|||||||
var highlightedTweetEle;
|
var highlightedTweetEle;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Function: Initializes TweetD*ck events. Called after the website app is loaded.
|
// Variable: Array of functions called after the website app is loaded.
|
||||||
//
|
//
|
||||||
var initializeTweetDck = function(){
|
var onAppReady = [];
|
||||||
// Settings button hook
|
|
||||||
$("[data-action='settings-menu']").click(function(){
|
|
||||||
setTimeout(function(){
|
|
||||||
var menu = $(".js-dropdown-content").children("ul").first();
|
|
||||||
if (menu.length === 0)return;
|
|
||||||
|
|
||||||
menu.children(".drp-h-divider").last().after([
|
//
|
||||||
'<li class="is-selectable" data-std><a href="#" data-action="td-settings">TweetDuck settings</a></li>',
|
// Variable: DOM object containing the main app element.
|
||||||
'<li class="is-selectable" data-std><a href="#" data-action="td-plugins">TweetDuck plugins</a></li>',
|
//
|
||||||
'<li class="drp-h-divider"></li>'
|
var app = $(document.body).children(".js-app");
|
||||||
].join(""));
|
|
||||||
|
|
||||||
var buttons = menu.children("[data-std]");
|
|
||||||
|
|
||||||
buttons.on("click", "a", function(){
|
|
||||||
var action = $(this).attr("data-action");
|
|
||||||
|
|
||||||
if (action === "td-settings"){
|
|
||||||
$TD.openSettingsMenu();
|
|
||||||
}
|
|
||||||
else if (action === "td-plugins"){
|
|
||||||
$TD.openPluginsMenu();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
buttons.hover(function(){
|
|
||||||
$(this).addClass("is-selected");
|
|
||||||
}, function(){
|
|
||||||
$(this).removeClass("is-selected");
|
|
||||||
});
|
|
||||||
}, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Notification handling
|
|
||||||
$.subscribe("/notifications/new", function(obj){
|
|
||||||
for(let index = obj.items.length-1; index >= 0; index--){
|
|
||||||
onNewTweet(obj.column, obj.items[index]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Setup video element replacement and fix missing target in user links
|
|
||||||
new MutationObserver(function(){
|
|
||||||
$("video").each(function(){
|
|
||||||
$(this).parent().replaceWith("<a href='"+$(this).attr("src")+"' rel='url' target='_blank' style='display:block; border:1px solid #555; padding:3px 6px'>► Open video in browser</a>");
|
|
||||||
});
|
|
||||||
|
|
||||||
$("a[rel='user']").attr("target", "_blank");
|
|
||||||
}).observe($(".js-app-columns")[0], {
|
|
||||||
childList: true,
|
|
||||||
subtree: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// Finish init and load plugins
|
|
||||||
$TD.loadFontSizeClass(TD.settings.getFontSize());
|
|
||||||
$TD.loadNotificationHeadContents(getNotificationHeadContents());
|
|
||||||
|
|
||||||
window.TD_APP_READY = true;
|
|
||||||
|
|
||||||
if (window.TD_PLUGINS){
|
|
||||||
window.TD_PLUGINS.onReady();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Function: Prepends code at the beginning of a function. If the prepended function returns true, execution of the original function is cancelled.
|
// Function: Prepends code at the beginning of a function. If the prepended function returns true, execution of the original function is cancelled.
|
||||||
@@ -137,23 +80,6 @@
|
|||||||
return tags.join("");
|
return tags.join("");
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
|
||||||
// Block: Observe the app <div> element and initialize TweetD*ck whenever possible.
|
|
||||||
//
|
|
||||||
var app = $("body").children(".js-app");
|
|
||||||
|
|
||||||
new MutationObserver(function(){
|
|
||||||
if (window.TD_APP_READY && app.hasClass("is-hidden")){
|
|
||||||
window.TD_APP_READY = false;
|
|
||||||
}
|
|
||||||
else if (!window.TD_APP_READY && !app.hasClass("is-hidden")){
|
|
||||||
initializeTweetDck();
|
|
||||||
}
|
|
||||||
}).observe(app[0], {
|
|
||||||
attributes: true,
|
|
||||||
attributeFilter: [ "class" ]
|
|
||||||
});
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Block: Hook into settings object to detect when the settings change.
|
// Block: Hook into settings object to detect when the settings change.
|
||||||
//
|
//
|
||||||
@@ -168,7 +94,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
// Block: Force popup notification settings.
|
// Block: Enable popup notifications.
|
||||||
//
|
//
|
||||||
TD.controller.notifications.hasNotifications = function(){
|
TD.controller.notifications.hasNotifications = function(){
|
||||||
return true;
|
return true;
|
||||||
@@ -178,6 +104,49 @@
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$.subscribe("/notifications/new", function(obj){
|
||||||
|
for(let index = obj.items.length-1; index >= 0; index--){
|
||||||
|
onNewTweet(obj.column, obj.items[index]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Block: Add TweetDuck buttons to the settings menu.
|
||||||
|
//
|
||||||
|
onAppReady.push(function(){
|
||||||
|
$("[data-action='settings-menu']").click(function(){
|
||||||
|
setTimeout(function(){
|
||||||
|
var menu = $(".js-dropdown-content").children("ul").first();
|
||||||
|
if (menu.length === 0)return;
|
||||||
|
|
||||||
|
menu.children(".drp-h-divider").last().after([
|
||||||
|
'<li class="is-selectable" data-std><a href="#" data-action="td-settings">TweetDuck settings</a></li>',
|
||||||
|
'<li class="is-selectable" data-std><a href="#" data-action="td-plugins">TweetDuck plugins</a></li>',
|
||||||
|
'<li class="drp-h-divider"></li>'
|
||||||
|
].join(""));
|
||||||
|
|
||||||
|
var buttons = menu.children("[data-std]");
|
||||||
|
|
||||||
|
buttons.on("click", "a", function(){
|
||||||
|
var action = $(this).attr("data-action");
|
||||||
|
|
||||||
|
if (action === "td-settings"){
|
||||||
|
$TD.openSettingsMenu();
|
||||||
|
}
|
||||||
|
else if (action === "td-plugins"){
|
||||||
|
$TD.openPluginsMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
buttons.hover(function(){
|
||||||
|
$(this).addClass("is-selected");
|
||||||
|
}, function(){
|
||||||
|
$(this).removeClass("is-selected");
|
||||||
|
});
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
// Block: Expand shortened links on hover or display tooltip.
|
// Block: Expand shortened links on hover or display tooltip.
|
||||||
//
|
//
|
||||||
@@ -375,6 +344,11 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
var clickUpload = function(){
|
var clickUpload = function(){
|
||||||
|
$(document).one("uiFilesAdded", function(){
|
||||||
|
getScroller().scrollTop(prevScrollTop);
|
||||||
|
$(".js-drawer").find(".js-compose-text").first()[0].focus();
|
||||||
|
});
|
||||||
|
|
||||||
var button = $(".js-add-image-button").first();
|
var button = $(".js-add-image-button").first();
|
||||||
|
|
||||||
var scroller = getScroller();
|
var scroller = getScroller();
|
||||||
@@ -387,7 +361,7 @@
|
|||||||
$TD.clickUploadImage(Math.floor(buttonPos.left), Math.floor(buttonPos.top));
|
$TD.clickUploadImage(Math.floor(buttonPos.left), Math.floor(buttonPos.top));
|
||||||
};
|
};
|
||||||
|
|
||||||
$(".js-app").delegate(".js-compose-text,.js-reply-tweetbox", "paste", function(){
|
app.delegate(".js-compose-text,.js-reply-tweetbox", "paste", function(){
|
||||||
lastPasteElement = $(this);
|
lastPasteElement = $(this);
|
||||||
$TD.tryPasteImage();
|
$TD.tryPasteImage();
|
||||||
});
|
});
|
||||||
@@ -426,13 +400,6 @@
|
|||||||
lastPasteElement = null;
|
lastPasteElement = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.TDGF_tryPasteImageFinish = function(){
|
|
||||||
setTimeout(function(){
|
|
||||||
getScroller().scrollTop(prevScrollTop);
|
|
||||||
$(".js-drawer").find(".js-compose-text").first()[0].focus();
|
|
||||||
}, 10);
|
|
||||||
};
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -525,6 +492,25 @@
|
|||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Block: Swap shift key functionality for selecting accounts.
|
||||||
|
//
|
||||||
|
onAppReady.push(function(){
|
||||||
|
$(".js-drawer[data-drawer='compose']").delegate(".js-account-list > .js-account-item", "click", function(e){
|
||||||
|
e.shiftKey = !e.shiftKey;
|
||||||
|
});
|
||||||
|
|
||||||
|
TD.components.AccountSelector.prototype.refreshPostingAccounts = appendToFunction(TD.components.AccountSelector.prototype.refreshPostingAccounts, function(){
|
||||||
|
if (!this.$node.attr("td-account-selector-hook")){
|
||||||
|
this.$node.attr("td-account-selector-hook", "1");
|
||||||
|
|
||||||
|
this.$node.delegate(".js-account-item", "click", function(e){
|
||||||
|
e.shiftKey = !e.shiftKey;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
// Block: Work around clipboard HTML formatting.
|
// Block: Work around clipboard HTML formatting.
|
||||||
//
|
//
|
||||||
@@ -544,6 +530,9 @@
|
|||||||
styleOfficial.sheet.insertRule(".txt-base-smallest .badge-verified:before { height: 13px !important; }", 0); // fix cut off badge icon
|
styleOfficial.sheet.insertRule(".txt-base-smallest .badge-verified:before { height: 13px !important; }", 0); // fix cut off badge icon
|
||||||
styleOfficial.sheet.insertRule(".keyboard-shortcut-list { vertical-align: top; }", 0); // fix keyboard navigation alignment
|
styleOfficial.sheet.insertRule(".keyboard-shortcut-list { vertical-align: top; }", 0); // fix keyboard navigation alignment
|
||||||
|
|
||||||
|
styleOfficial.sheet.insertRule(".is-video a:not([href*='youtu']), .is-gif .js-media-gif-container { cursor: alias; }", 0); // change cursor on unsupported videos
|
||||||
|
styleOfficial.sheet.insertRule(".is-video a:not([href*='youtu']) .icon-bg-dot, .is-gif .icon-bg-dot { color: #bd3d37; }", 0); // change play icon color on unsupported videos
|
||||||
|
|
||||||
TD.services.TwitterActionRetweetedRetweet.prototype.iconClass = "icon-retweet icon-retweet-color txt-base-medium"; // fix retweet icon mismatch
|
TD.services.TwitterActionRetweetedRetweet.prototype.iconClass = "icon-retweet icon-retweet-color txt-base-medium"; // fix retweet icon mismatch
|
||||||
|
|
||||||
window.TDGF_reinjectCustomCSS = function(styles){
|
window.TDGF_reinjectCustomCSS = function(styles){
|
||||||
@@ -554,4 +543,66 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Block: Setup unsupported video element hook.
|
||||||
|
//
|
||||||
|
(function(){
|
||||||
|
var cancelModal = false;
|
||||||
|
|
||||||
|
TD.components.MediaGallery.prototype._loadTweet = appendToFunction(TD.components.MediaGallery.prototype._loadTweet, function(){
|
||||||
|
var media = this.chirp.getMedia().find(media => media.mediaId === this.clickedMediaEntityId);
|
||||||
|
|
||||||
|
if (media && media.isVideo && media.service !== "youtube"){
|
||||||
|
$TD.openBrowser(this.clickedLink);
|
||||||
|
cancelModal = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
TD.components.BaseModal.prototype.setAndShowContainer = prependToFunction(TD.components.BaseModal.prototype.setAndShowContainer, function(){
|
||||||
|
if (cancelModal){
|
||||||
|
cancelModal = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
TD.ui.Column.prototype.playGifIfNotManuallyPaused = function(){};
|
||||||
|
TD.mustaches["status/media_thumb.mustache"] = TD.mustaches["status/media_thumb.mustache"].replace("is-gif", "is-gif is-paused");
|
||||||
|
|
||||||
|
app.delegate(".js-gif-play", "click", function(e){
|
||||||
|
var parent = $(e.target).closest(".js-tweet").first();
|
||||||
|
var link = (parent.hasClass("tweet-detail") ? parent.find("a[rel='url']") : parent.find("time").first().children("a")).first();
|
||||||
|
|
||||||
|
$TD.openBrowser(link.attr("href"));
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Block: Finish initialization and load plugins.
|
||||||
|
//
|
||||||
|
onAppReady.push(function(){
|
||||||
|
$TD.loadFontSizeClass(TD.settings.getFontSize());
|
||||||
|
$TD.loadNotificationHeadContents(getNotificationHeadContents());
|
||||||
|
|
||||||
|
if (window.TD_PLUGINS){
|
||||||
|
window.TD_PLUGINS.onReady();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Block: Observe the main app element and call the ready event whenever the contents are loaded.
|
||||||
|
//
|
||||||
|
new MutationObserver(function(){
|
||||||
|
if (window.TD_APP_READY && app.hasClass("is-hidden")){
|
||||||
|
window.TD_APP_READY = false;
|
||||||
|
}
|
||||||
|
else if (!window.TD_APP_READY && !app.hasClass("is-hidden")){
|
||||||
|
onAppReady.forEach(func => func());
|
||||||
|
window.TD_APP_READY = true;
|
||||||
|
}
|
||||||
|
}).observe(app[0], {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: [ "class" ]
|
||||||
|
});
|
||||||
})($, $TD, $TDX, TD);
|
})($, $TD, $TDX, TD);
|
||||||
|
@@ -19,6 +19,11 @@
|
|||||||
//
|
//
|
||||||
const updateCheckUrlAll = "https://api.github.com/repos/chylex/TweetDuck/releases";
|
const updateCheckUrlAll = "https://api.github.com/repos/chylex/TweetDuck/releases";
|
||||||
|
|
||||||
|
//
|
||||||
|
// Constant: Fallback url in case the update installer file is missing.
|
||||||
|
//
|
||||||
|
const updateDownloadFallback = "https://tweetduck.chylex.com/#download";
|
||||||
|
|
||||||
//
|
//
|
||||||
// Function: Creates the update notification element. Removes the old one if already exists.
|
// Function: Creates the update notification element. Removes the old one if already exists.
|
||||||
//
|
//
|
||||||
@@ -28,7 +33,7 @@
|
|||||||
var ele = $("#tweetduck-update");
|
var ele = $("#tweetduck-update");
|
||||||
var existed = ele.length > 0;
|
var existed = ele.length > 0;
|
||||||
|
|
||||||
if (existed > 0){
|
if (existed){
|
||||||
ele.remove();
|
ele.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +111,13 @@
|
|||||||
|
|
||||||
buttonDiv.children(".tdu-btn-download").click(function(){
|
buttonDiv.children(".tdu-btn-download").click(function(){
|
||||||
ele.remove();
|
ele.remove();
|
||||||
|
|
||||||
|
if (download){
|
||||||
$TDU.onUpdateAccepted(version, download);
|
$TDU.onUpdateAccepted(version, download);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$TDU.openBrowser(updateDownloadFallback);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
buttonDiv.children(".tdu-btn-unsupported").click(function(){
|
buttonDiv.children(".tdu-btn-unsupported").click(function(){
|
||||||
@@ -125,12 +136,21 @@
|
|||||||
return ele;
|
return ele;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Function: Returns milliseconds until the start of the next hour, with an offset in seconds that can skip an hour if the clock would roll over too soon.
|
||||||
|
//
|
||||||
|
var getTimeUntilNextHour = function(offset){
|
||||||
|
var now = new Date();
|
||||||
|
var offset = new Date(+now+offset*1000);
|
||||||
|
return new Date(offset.getFullYear(), offset.getMonth(), offset.getDate(), offset.getHours()+1, 0, 0)-now;
|
||||||
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Function: Runs an update check and updates all DOM elements appropriately.
|
// Function: Runs an update check and updates all DOM elements appropriately.
|
||||||
//
|
//
|
||||||
var runUpdateCheck = function(eventID, versionTag, dismissedVersionTag, allowPre){
|
var runUpdateCheck = function(eventID, versionTag, dismissedVersionTag, allowPre){
|
||||||
clearTimeout(updateCheckTimeoutID);
|
clearTimeout(updateCheckTimeoutID);
|
||||||
updateCheckTimeoutID = setTimeout($TDU.triggerUpdateCheck, 1000*60*60); // 1 hour
|
updateCheckTimeoutID = setTimeout($TDU.triggerUpdateCheck, getTimeUntilNextHour(60*30)); // 30 minute offset
|
||||||
|
|
||||||
$.getJSON(allowPre ? updateCheckUrlAll : updateCheckUrlLatest, function(response){
|
$.getJSON(allowPre ? updateCheckUrlAll : updateCheckUrlLatest, function(response){
|
||||||
var release = allowPre ? response[0] : response;
|
var release = allowPre ? response[0] : response;
|
||||||
@@ -139,7 +159,7 @@
|
|||||||
var hasUpdate = tagName !== versionTag && tagName !== dismissedVersionTag && release.assets.length > 0;
|
var hasUpdate = tagName !== versionTag && tagName !== dismissedVersionTag && release.assets.length > 0;
|
||||||
|
|
||||||
if (hasUpdate){
|
if (hasUpdate){
|
||||||
var obj = release.assets.find(asset => asset.name === updateFileName) || release.assets[0];
|
var obj = release.assets.find(asset => asset.name === updateFileName) || { browser_download_url: "" };
|
||||||
displayNotification(tagName, obj.browser_download_url);
|
displayNotification(tagName, obj.browser_download_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<Import Project="packages\CefSharp.WinForms.53.0.1\build\CefSharp.WinForms.props" Condition="Exists('packages\CefSharp.WinForms.53.0.1\build\CefSharp.WinForms.props')" />
|
<Import Project="packages\CefSharp.WinForms.55.0.0\build\CefSharp.WinForms.props" Condition="Exists('packages\CefSharp.WinForms.55.0.0\build\CefSharp.WinForms.props')" />
|
||||||
<Import Project="packages\CefSharp.Common.53.0.1\build\CefSharp.Common.props" Condition="Exists('packages\CefSharp.Common.53.0.1\build\CefSharp.Common.props')" />
|
<Import Project="packages\CefSharp.Common.55.0.0\build\CefSharp.Common.props" Condition="Exists('packages\CefSharp.Common.55.0.0\build\CefSharp.Common.props')" />
|
||||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
@@ -10,11 +10,10 @@
|
|||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
<RootNamespace>TweetDck</RootNamespace>
|
<RootNamespace>TweetDck</RootNamespace>
|
||||||
<AssemblyName Condition=" '$(Configuration)' == 'Debug' ">TweetDick</AssemblyName>
|
<AssemblyName>TweetDuck</AssemblyName>
|
||||||
<AssemblyName Condition=" '$(Configuration)' == 'Release' ">TweetDuck</AssemblyName>
|
|
||||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||||
<FileAlignment>512</FileAlignment>
|
<FileAlignment>512</FileAlignment>
|
||||||
<NuGetPackageImportStamp>783c0e30</NuGetPackageImportStamp>
|
<NuGetPackageImportStamp>886d3074</NuGetPackageImportStamp>
|
||||||
<TargetFrameworkProfile>
|
<TargetFrameworkProfile>
|
||||||
</TargetFrameworkProfile>
|
</TargetFrameworkProfile>
|
||||||
<PublishUrl>publish\</PublishUrl>
|
<PublishUrl>publish\</PublishUrl>
|
||||||
@@ -59,9 +58,6 @@
|
|||||||
</DefineConstants>
|
</DefineConstants>
|
||||||
<Prefer32Bit>false</Prefer32Bit>
|
<Prefer32Bit>false</Prefer32Bit>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup>
|
|
||||||
<AssemblyName>TweetDuck</AssemblyName>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
<Reference Include="System.Core" />
|
<Reference Include="System.Core" />
|
||||||
@@ -113,6 +109,7 @@
|
|||||||
<Compile Include="Core\Handling\FileDialogHandler.cs" />
|
<Compile Include="Core\Handling\FileDialogHandler.cs" />
|
||||||
<Compile Include="Core\Handling\JavaScriptDialogHandler.cs" />
|
<Compile Include="Core\Handling\JavaScriptDialogHandler.cs" />
|
||||||
<Compile Include="Core\Handling\LifeSpanHandler.cs" />
|
<Compile Include="Core\Handling\LifeSpanHandler.cs" />
|
||||||
|
<Compile Include="Core\Notification\SoundNotification.cs" />
|
||||||
<Compile Include="Core\Notification\TweetNotification.cs" />
|
<Compile Include="Core\Notification\TweetNotification.cs" />
|
||||||
<Compile Include="Core\Other\FormAbout.cs">
|
<Compile Include="Core\Other\FormAbout.cs">
|
||||||
<SubType>Form</SubType>
|
<SubType>Form</SubType>
|
||||||
@@ -191,6 +188,7 @@
|
|||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Core\Notification\NotificationFlags.cs" />
|
<Compile Include="Core\Notification\NotificationFlags.cs" />
|
||||||
<Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" />
|
<Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" />
|
||||||
|
<Compile Include="Core\Utils\TwoKeyDictionary.cs" />
|
||||||
<Compile Include="Core\Utils\WindowState.cs" />
|
<Compile Include="Core\Utils\WindowState.cs" />
|
||||||
<Compile Include="Core\Utils\WindowsUtils.cs" />
|
<Compile Include="Core\Utils\WindowsUtils.cs" />
|
||||||
<Compile Include="Core\Bridge\TweetDeckBridge.cs" />
|
<Compile Include="Core\Bridge\TweetDeckBridge.cs" />
|
||||||
@@ -254,7 +252,6 @@
|
|||||||
<Compile Include="Resources\ScriptLoader.cs" />
|
<Compile Include="Resources\ScriptLoader.cs" />
|
||||||
<Compile Include="Updates\UpdaterSettings.cs" />
|
<Compile Include="Updates\UpdaterSettings.cs" />
|
||||||
<None Include="Configuration\app.config" />
|
<None Include="Configuration\app.config" />
|
||||||
<None Include="Configuration\packages.config" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<BootstrapperPackage Include=".NETFramework,Version=v4.0,Profile=Client">
|
<BootstrapperPackage Include=".NETFramework,Version=v4.0,Profile=Client">
|
||||||
@@ -306,6 +303,7 @@
|
|||||||
</ContentWithTargetPath>
|
</ContentWithTargetPath>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<None Include="Configuration\packages.config" />
|
||||||
<None Include="Resources\icon-small.ico" />
|
<None Include="Resources\icon-small.ico" />
|
||||||
<None Include="Resources\icon-tray-new.ico" />
|
<None Include="Resources\icon-tray-new.ico" />
|
||||||
<None Include="Resources\icon-tray.ico" />
|
<None Include="Resources\icon-tray.ico" />
|
||||||
@@ -327,12 +325,12 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Error Condition="!Exists('packages\cef.redist.x86.3.2785.1486\build\cef.redist.x86.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x86.3.2785.1486\build\cef.redist.x86.targets'))" />
|
<Error Condition="!Exists('packages\cef.redist.x86.3.2883.1552\build\cef.redist.x86.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x86.3.2883.1552\build\cef.redist.x86.targets'))" />
|
||||||
<Error Condition="!Exists('packages\cef.redist.x64.3.2785.1486\build\cef.redist.x64.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x64.3.2785.1486\build\cef.redist.x64.targets'))" />
|
<Error Condition="!Exists('packages\cef.redist.x64.3.2883.1552\build\cef.redist.x64.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x64.3.2883.1552\build\cef.redist.x64.targets'))" />
|
||||||
<Error Condition="!Exists('packages\CefSharp.Common.53.0.1\build\CefSharp.Common.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.53.0.1\build\CefSharp.Common.props'))" />
|
<Error Condition="!Exists('packages\CefSharp.Common.55.0.0\build\CefSharp.Common.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.55.0.0\build\CefSharp.Common.props'))" />
|
||||||
<Error Condition="!Exists('packages\CefSharp.Common.53.0.1\build\CefSharp.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.53.0.1\build\CefSharp.Common.targets'))" />
|
<Error Condition="!Exists('packages\CefSharp.Common.55.0.0\build\CefSharp.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.55.0.0\build\CefSharp.Common.targets'))" />
|
||||||
<Error Condition="!Exists('packages\CefSharp.WinForms.53.0.1\build\CefSharp.WinForms.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.53.0.1\build\CefSharp.WinForms.props'))" />
|
<Error Condition="!Exists('packages\CefSharp.WinForms.55.0.0\build\CefSharp.WinForms.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.55.0.0\build\CefSharp.WinForms.props'))" />
|
||||||
<Error Condition="!Exists('packages\CefSharp.WinForms.53.0.1\build\CefSharp.WinForms.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.53.0.1\build\CefSharp.WinForms.targets'))" />
|
<Error Condition="!Exists('packages\CefSharp.WinForms.55.0.0\build\CefSharp.WinForms.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.55.0.0\build\CefSharp.WinForms.targets'))" />
|
||||||
</Target>
|
</Target>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PostBuildEvent>del "$(TargetPath).config"
|
<PostBuildEvent>del "$(TargetPath).config"
|
||||||
@@ -351,12 +349,20 @@ mkdir "$(TargetDir)plugins\official"
|
|||||||
mkdir "$(TargetDir)plugins\user"
|
mkdir "$(TargetDir)plugins\user"
|
||||||
xcopy "$(ProjectDir)Resources\Plugins\*" "$(TargetDir)plugins\official\" /E /Y
|
xcopy "$(ProjectDir)Resources\Plugins\*" "$(TargetDir)plugins\official\" /E /Y
|
||||||
rmdir "$(ProjectDir)\bin\Debug"
|
rmdir "$(ProjectDir)\bin\Debug"
|
||||||
rmdir "$(ProjectDir)\bin\Release"</PostBuildEvent>
|
rmdir "$(ProjectDir)\bin\Release"
|
||||||
|
|
||||||
|
rmdir "$(TargetDir)plugins\official\.debug" /S /Q
|
||||||
|
|
||||||
|
if $(ConfigurationName) == Debug (
|
||||||
|
rmdir "$(TargetDir)plugins\official\.debug" /S /Q
|
||||||
|
mkdir "$(TargetDir)plugins\user\.debug"
|
||||||
|
xcopy "$(ProjectDir)Resources\Plugins\.debug\*" "$(TargetDir)plugins\user\.debug\" /E /Y
|
||||||
|
)</PostBuildEvent>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Import Project="packages\cef.redist.x86.3.2785.1486\build\cef.redist.x86.targets" Condition="Exists('packages\cef.redist.x86.3.2785.1486\build\cef.redist.x86.targets')" />
|
<Import Project="packages\cef.redist.x86.3.2883.1552\build\cef.redist.x86.targets" Condition="Exists('packages\cef.redist.x86.3.2883.1552\build\cef.redist.x86.targets')" />
|
||||||
<Import Project="packages\cef.redist.x64.3.2785.1486\build\cef.redist.x64.targets" Condition="Exists('packages\cef.redist.x64.3.2785.1486\build\cef.redist.x64.targets')" />
|
<Import Project="packages\cef.redist.x64.3.2883.1552\build\cef.redist.x64.targets" Condition="Exists('packages\cef.redist.x64.3.2883.1552\build\cef.redist.x64.targets')" />
|
||||||
<Import Project="packages\CefSharp.Common.53.0.1\build\CefSharp.Common.targets" Condition="Exists('packages\CefSharp.Common.53.0.1\build\CefSharp.Common.targets')" />
|
<Import Project="packages\CefSharp.Common.55.0.0\build\CefSharp.Common.targets" Condition="Exists('packages\CefSharp.Common.55.0.0\build\CefSharp.Common.targets')" />
|
||||||
<Import Project="packages\CefSharp.WinForms.53.0.1\build\CefSharp.WinForms.targets" Condition="Exists('packages\CefSharp.WinForms.53.0.1\build\CefSharp.WinForms.targets')" />
|
<Import Project="packages\CefSharp.WinForms.55.0.0\build\CefSharp.WinForms.targets" Condition="Exists('packages\CefSharp.WinForms.55.0.0\build\CefSharp.WinForms.targets')" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
<Target Name="BeforeBuild">
|
<Target Name="BeforeBuild">
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
@@ -69,7 +68,7 @@ namespace TweetDck.Updates{
|
|||||||
progressDownload.SetValueInstant(1000);
|
progressDownload.SetValueInstant(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
labelStatus.Text = (e.BytesReceived/BytesToKB).ToString("0.0", CultureInfo.CurrentCulture)+" kB";
|
labelStatus.Text = (long)(e.BytesReceived/BytesToKB)+" kB";
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
if (progressDownload.Style != ProgressBarStyle.Continuous){
|
if (progressDownload.Style != ProgressBarStyle.Continuous){
|
||||||
@@ -77,7 +76,7 @@ namespace TweetDck.Updates{
|
|||||||
}
|
}
|
||||||
|
|
||||||
progressDownload.SetValueInstant(e.ProgressPercentage*10);
|
progressDownload.SetValueInstant(e.ProgressPercentage*10);
|
||||||
labelStatus.Text = (e.BytesReceived/BytesToKB).ToString("0.0", CultureInfo.CurrentCulture)+" / "+(e.TotalBytesToReceive/BytesToKB).ToString("0.0", CultureInfo.CurrentCulture)+" kB";
|
labelStatus.Text = (long)(e.BytesReceived/BytesToKB)+" / "+(long)(e.TotalBytesToReceive/BytesToKB)+" kB";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -67,12 +67,12 @@ namespace TweetDck.Updates{
|
|||||||
|
|
||||||
private void TriggerUpdateAcceptedEvent(UpdateAcceptedEventArgs args){
|
private void TriggerUpdateAcceptedEvent(UpdateAcceptedEventArgs args){
|
||||||
if (UpdateAccepted != null){
|
if (UpdateAccepted != null){
|
||||||
form.InvokeSafe(() => UpdateAccepted(this, args));
|
form.InvokeAsyncSafe(() => UpdateAccepted(this, args));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TriggerUpdateDismissedEvent(UpdateDismissedEventArgs args){
|
private void TriggerUpdateDismissedEvent(UpdateDismissedEventArgs args){
|
||||||
form.InvokeSafe(() => {
|
form.InvokeAsyncSafe(() => {
|
||||||
settings.DismissedUpdate = args.VersionTag;
|
settings.DismissedUpdate = args.VersionTag;
|
||||||
|
|
||||||
if (UpdateDismissed != null){
|
if (UpdateDismissed != null){
|
||||||
@@ -83,7 +83,7 @@ namespace TweetDck.Updates{
|
|||||||
|
|
||||||
private void TriggerCheckFinishedEvent(UpdateCheckEventArgs args){
|
private void TriggerCheckFinishedEvent(UpdateCheckEventArgs args){
|
||||||
if (CheckFinished != null){
|
if (CheckFinished != null){
|
||||||
form.InvokeSafe(() => CheckFinished(this, args));
|
form.InvokeAsyncSafe(() => CheckFinished(this, args));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
del "bin\x86\Release\*.xml"
|
del "bin\x86\Release\*.xml"
|
||||||
|
del "bin\x86\Release\*.pdb"
|
||||||
del "bin\x86\Release\devtools_resources.pak"
|
del "bin\x86\Release\devtools_resources.pak"
|
||||||
del "bin\x86\Release\d3dcompiler_43.dll"
|
del "bin\x86\Release\d3dcompiler_43.dll"
|
||||||
del "bin\x86\Release\widevinecdmadapter.dll"
|
del "bin\x86\Release\widevinecdmadapter.dll"
|
||||||
|
@@ -39,7 +39,7 @@ Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{
|
|||||||
|
|
||||||
[Files]
|
[Files]
|
||||||
Source: "..\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
Source: "..\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,devtools_resources.pak,d3dcompiler_43.dll,widevinecdmadapter.dll,debug.js"
|
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,*.pdb,devtools_resources.pak,d3dcompiler_43.dll,widevinecdmadapter.dll,debug.js"
|
||||||
|
|
||||||
[Icons]
|
[Icons]
|
||||||
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: TDIsUninstallable
|
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: TDIsUninstallable
|
||||||
|
@@ -17,7 +17,7 @@ AppPublisher={#MyAppPublisher}
|
|||||||
AppPublisherURL={#MyAppURL}
|
AppPublisherURL={#MyAppURL}
|
||||||
AppSupportURL={#MyAppURL}
|
AppSupportURL={#MyAppURL}
|
||||||
AppUpdatesURL={#MyAppURL}
|
AppUpdatesURL={#MyAppURL}
|
||||||
DefaultDirName={pf}\{#MyAppName}
|
DefaultDirName={sd}\{#MyAppName}
|
||||||
DefaultGroupName={#MyAppName}
|
DefaultGroupName={#MyAppName}
|
||||||
OutputBaseFilename={#MyAppName}.Portable
|
OutputBaseFilename={#MyAppName}.Portable
|
||||||
VersionInfoVersion={#MyAppVersion}
|
VersionInfoVersion={#MyAppVersion}
|
||||||
@@ -36,7 +36,7 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
|
|||||||
|
|
||||||
[Files]
|
[Files]
|
||||||
Source: "..\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
Source: "..\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,devtools_resources.pak,d3dcompiler_43.dll,widevinecdmadapter.dll,debug.js"
|
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,*.pdb,devtools_resources.pak,d3dcompiler_43.dll,widevinecdmadapter.dll,debug.js"
|
||||||
|
|
||||||
[Run]
|
[Run]
|
||||||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall shellexec
|
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall shellexec
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
#define MyAppID "8C25A716-7E11-4AAD-9992-8B5D0C78AE06"
|
#define MyAppID "8C25A716-7E11-4AAD-9992-8B5D0C78AE06"
|
||||||
#define MyAppVersion GetFileVersion("..\bin\x86\Release\TweetDuck.exe")
|
#define MyAppVersion GetFileVersion("..\bin\x86\Release\TweetDuck.exe")
|
||||||
#define CefVersion "3.2785.1486.0"
|
#define CefVersion "3.2883.1552.0"
|
||||||
|
|
||||||
[Setup]
|
[Setup]
|
||||||
AppId={{{#MyAppID}}
|
AppId={{{#MyAppID}}
|
||||||
@@ -41,7 +41,7 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
|
|||||||
|
|
||||||
[Files]
|
[Files]
|
||||||
Source: "..\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
Source: "..\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,*.dll,*.pak,*.bin,*.dat,debug.js"
|
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,*.pdb,*.dll,*.pak,*.bin,*.dat,debug.js"
|
||||||
|
|
||||||
[Icons]
|
[Icons]
|
||||||
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: TDIsUninstallable
|
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: TDIsUninstallable
|
||||||
|
201
tests/Core/Utils/TestTwoKeyDictionary.cs
Normal file
201
tests/Core/Utils/TestTwoKeyDictionary.cs
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using TweetDck.Core.Utils;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace UnitTests.Core.Utils{
|
||||||
|
[TestClass]
|
||||||
|
public class TestTwoKeyDictionary{
|
||||||
|
private static TwoKeyDictionary<string, int, string> CreateDict(){
|
||||||
|
TwoKeyDictionary<string, int, string> dict = new TwoKeyDictionary<string, int, string>();
|
||||||
|
|
||||||
|
dict.Add("aaa", 0, "x");
|
||||||
|
dict.Add("aaa", 1, "y");
|
||||||
|
dict.Add("aaa", 2, "z");
|
||||||
|
|
||||||
|
dict.Add("bbb", 0, "test 1");
|
||||||
|
dict.Add("bbb", 10, "test 2");
|
||||||
|
dict.Add("bbb", 20, "test 3");
|
||||||
|
dict.Add("bbb", 30, "test 4");
|
||||||
|
|
||||||
|
dict.Add("ccc", -5, "");
|
||||||
|
dict.Add("", 0, "");
|
||||||
|
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestAdd(){
|
||||||
|
CreateDict();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(ArgumentException))]
|
||||||
|
public void TestAddDuplicate(){
|
||||||
|
var dict = new TwoKeyDictionary<string, int, string>();
|
||||||
|
|
||||||
|
dict.Add("aaa", 0, "test");
|
||||||
|
dict.Add("aaa", 0, "oops");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestAccessor(){
|
||||||
|
var dict = CreateDict();
|
||||||
|
|
||||||
|
// get
|
||||||
|
|
||||||
|
Assert.AreEqual("x", dict["aaa", 0]);
|
||||||
|
Assert.AreEqual("y", dict["aaa", 1]);
|
||||||
|
Assert.AreEqual("z", dict["aaa", 2]);
|
||||||
|
|
||||||
|
Assert.AreEqual("test 3", dict["bbb", 20]);
|
||||||
|
|
||||||
|
Assert.AreEqual("", dict["ccc", -5]);
|
||||||
|
Assert.AreEqual("", dict["", 0]);
|
||||||
|
|
||||||
|
// set
|
||||||
|
|
||||||
|
dict["aaa", 0] = "replaced entry";
|
||||||
|
Assert.AreEqual("replaced entry", dict["aaa", 0]);
|
||||||
|
|
||||||
|
dict["aaa", 3] = "new entry";
|
||||||
|
Assert.AreEqual("new entry", dict["aaa", 3]);
|
||||||
|
|
||||||
|
dict["xxxxx", 150] = "new key and entry";
|
||||||
|
Assert.AreEqual("new key and entry", dict["xxxxx", 150]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(KeyNotFoundException))]
|
||||||
|
public void TestAccessorMissingKey1(){
|
||||||
|
var _ = CreateDict()["missing", 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(KeyNotFoundException))]
|
||||||
|
public void TestAccessorMissingKey2(){
|
||||||
|
var _ = CreateDict()["aaa", 3];
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestClear(){
|
||||||
|
var dict = CreateDict();
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.Contains("bbb"));
|
||||||
|
dict.Clear("bbb");
|
||||||
|
Assert.IsTrue(dict.Contains("bbb"));
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.Contains(""));
|
||||||
|
dict.Clear("");
|
||||||
|
Assert.IsTrue(dict.Contains(""));
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.Contains("aaa"));
|
||||||
|
Assert.IsTrue(dict.Contains("ccc"));
|
||||||
|
dict.Clear();
|
||||||
|
Assert.IsFalse(dict.Contains("aaa"));
|
||||||
|
Assert.IsFalse(dict.Contains("ccc"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(KeyNotFoundException))]
|
||||||
|
public void TestClearMissingKey(){
|
||||||
|
CreateDict().Clear("missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestContains(){
|
||||||
|
var dict = CreateDict();
|
||||||
|
|
||||||
|
// positive
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.Contains("aaa"));
|
||||||
|
Assert.IsTrue(dict.Contains("aaa", 0));
|
||||||
|
Assert.IsTrue(dict.Contains("aaa", 1));
|
||||||
|
Assert.IsTrue(dict.Contains("aaa", 2));
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.Contains("ccc"));
|
||||||
|
Assert.IsTrue(dict.Contains("ccc", -5));
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.Contains(""));
|
||||||
|
Assert.IsTrue(dict.Contains("", 0));
|
||||||
|
|
||||||
|
// negative
|
||||||
|
|
||||||
|
Assert.IsFalse(dict.Contains("missing"));
|
||||||
|
Assert.IsFalse(dict.Contains("missing", 999));
|
||||||
|
|
||||||
|
Assert.IsFalse(dict.Contains("aaa", 3));
|
||||||
|
Assert.IsFalse(dict.Contains("", -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestCount(){
|
||||||
|
var dict = CreateDict();
|
||||||
|
|
||||||
|
Assert.AreEqual(9, dict.Count());
|
||||||
|
Assert.AreEqual(3, dict.Count("aaa"));
|
||||||
|
Assert.AreEqual(4, dict.Count("bbb"));
|
||||||
|
Assert.AreEqual(1, dict.Count("ccc"));
|
||||||
|
Assert.AreEqual(1, dict.Count(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(KeyNotFoundException))]
|
||||||
|
public void TestCountMissingKey(){
|
||||||
|
CreateDict().Count("missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestRemove(){
|
||||||
|
var dict = CreateDict();
|
||||||
|
|
||||||
|
// negative
|
||||||
|
Assert.IsFalse(dict.Remove("missing"));
|
||||||
|
Assert.IsFalse(dict.Remove("aaa", 3));
|
||||||
|
|
||||||
|
// positive
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.Contains("aaa"));
|
||||||
|
Assert.IsTrue(dict.Remove("aaa"));
|
||||||
|
Assert.IsFalse(dict.Contains("aaa"));
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.Contains("bbb", 10));
|
||||||
|
Assert.IsTrue(dict.Remove("bbb", 10));
|
||||||
|
Assert.IsFalse(dict.Contains("bbb", 10));
|
||||||
|
Assert.IsTrue(dict.Contains("bbb"));
|
||||||
|
Assert.IsTrue(dict.Contains("bbb", 20));
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.Remove("bbb", 0));
|
||||||
|
Assert.IsTrue(dict.Remove("bbb", 20));
|
||||||
|
Assert.IsTrue(dict.Remove("bbb", 30));
|
||||||
|
Assert.IsFalse(dict.Contains("bbb"));
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.Contains(""));
|
||||||
|
Assert.IsTrue(dict.Remove("", 0));
|
||||||
|
Assert.IsFalse(dict.Contains(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestTryGetValue(){
|
||||||
|
var dict = CreateDict();
|
||||||
|
string val;
|
||||||
|
|
||||||
|
// positive
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.TryGetValue("bbb", 10, out val));
|
||||||
|
Assert.AreEqual("test 2", val);
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.TryGetValue("ccc", -5, out val));
|
||||||
|
Assert.AreEqual("", val);
|
||||||
|
|
||||||
|
Assert.IsTrue(dict.TryGetValue("", 0, out val));
|
||||||
|
Assert.AreEqual("", val);
|
||||||
|
|
||||||
|
// negative
|
||||||
|
|
||||||
|
Assert.IsFalse(dict.TryGetValue("ccc", -50, out val));
|
||||||
|
Assert.IsFalse(dict.TryGetValue("", 1, out val));
|
||||||
|
Assert.IsFalse(dict.TryGetValue("missing", 0, out val));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -51,6 +51,7 @@
|
|||||||
<Compile Include="Core\Utils\TestBrowserUtils.cs" />
|
<Compile Include="Core\Utils\TestBrowserUtils.cs" />
|
||||||
<Compile Include="Core\Utils\TestCommandLineArgs.cs" />
|
<Compile Include="Core\Utils\TestCommandLineArgs.cs" />
|
||||||
<Compile Include="Core\Utils\TestCommandLineArgsParser.cs" />
|
<Compile Include="Core\Utils\TestCommandLineArgsParser.cs" />
|
||||||
|
<Compile Include="Core\Utils\TestTwoKeyDictionary.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="TestUtils.cs" />
|
<Compile Include="TestUtils.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
Reference in New Issue
Block a user