diff --git a/Greenshot/Forms/SettingsForm.cs b/Greenshot/Forms/SettingsForm.cs
index 9e7fd5ad4..00588bdee 100644
--- a/Greenshot/Forms/SettingsForm.cs
+++ b/Greenshot/Forms/SettingsForm.cs
@@ -295,7 +295,7 @@ namespace Greenshot {
private void UpdateClipboardFormatDescriptions() {
foreach(ListViewItem item in listview_clipboardformats.Items) {
ClipboardFormat cf = (ClipboardFormat) item.Tag;
- item.Text = Language.Translate(cf);
+ item.Text = Language.Translate(cf);
}
}
@@ -592,12 +592,12 @@ namespace Greenshot {
CheckDestinationSettings();
}
- protected override void OnFieldsFilled() {
- // the color radio button is not actually bound to a setting, but checked when monochrome/grayscale are not checked
- if(!radioBtnGrayScale.Checked && !radioBtnMonochrome.Checked) {
- radioBtnColorPrint.Checked = true;
- }
- }
+ protected override void OnFieldsFilled() {
+ // the color radio button is not actually bound to a setting, but checked when monochrome/grayscale are not checked
+ if(!radioBtnGrayScale.Checked && !radioBtnMonochrome.Checked) {
+ radioBtnColorPrint.Checked = true;
+ }
+ }
///
/// Set the enable state of the expert settings
diff --git a/GreenshotJiraPlugin/Forms/JiraForm.cs b/GreenshotJiraPlugin/Forms/JiraForm.cs
index e6fbf6717..efee62973 100644
--- a/GreenshotJiraPlugin/Forms/JiraForm.cs
+++ b/GreenshotJiraPlugin/Forms/JiraForm.cs
@@ -65,7 +65,7 @@ namespace GreenshotJiraPlugin.Forms {
{
if (!_jiraConnector.IsLoggedIn)
{
- await _jiraConnector.Login();
+ await _jiraConnector.LoginAsync();
}
}
catch (Exception e)
diff --git a/GreenshotJiraPlugin/GreenshotJiraPlugin.csproj b/GreenshotJiraPlugin/GreenshotJiraPlugin.csproj
index 2c02694f4..850aafdbf 100644
--- a/GreenshotJiraPlugin/GreenshotJiraPlugin.csproj
+++ b/GreenshotJiraPlugin/GreenshotJiraPlugin.csproj
@@ -78,12 +78,19 @@
SettingsForm.cs
+
+
+
+
+
+
+
+
-
diff --git a/GreenshotJiraPlugin/Hooking/TitleChangeEventArgs.cs b/GreenshotJiraPlugin/Hooking/TitleChangeEventArgs.cs
new file mode 100644
index 000000000..6ec8878fd
--- /dev/null
+++ b/GreenshotJiraPlugin/Hooking/TitleChangeEventArgs.cs
@@ -0,0 +1,50 @@
+/*
+ * dapplo - building blocks for desktop applications
+ * Copyright (C) Dapplo 2015-2016
+ *
+ * For more information see: http://dapplo.net/
+ * dapplo repositories are hosted on GitHub: https://github.com/dapplo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using System;
+
+namespace GreenshotJiraPlugin.Hooking
+{
+ ///
+ /// Event arguments for the TitleChangeEvent
+ ///
+ public class TitleChangeEventArgs : EventArgs
+ {
+ ///
+ /// HWnd of the window which has a changed title
+ ///
+ public IntPtr HWnd
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Title which is changed
+ ///
+ public string Title
+ {
+ get;
+ set;
+ }
+ }
+
+}
diff --git a/GreenshotJiraPlugin/Hooking/TitleChangeEventDelegate.cs b/GreenshotJiraPlugin/Hooking/TitleChangeEventDelegate.cs
new file mode 100644
index 000000000..29847a1e5
--- /dev/null
+++ b/GreenshotJiraPlugin/Hooking/TitleChangeEventDelegate.cs
@@ -0,0 +1,29 @@
+/*
+ * dapplo - building blocks for desktop applications
+ * Copyright (C) Dapplo 2015-2016
+ *
+ * For more information see: http://dapplo.net/
+ * dapplo repositories are hosted on GitHub: https://github.com/dapplo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+namespace GreenshotJiraPlugin.Hooking
+{
+ ///
+ /// Delegate for the title change event
+ ///
+ ///
+ public delegate void TitleChangeEventDelegate(TitleChangeEventArgs eventArgs);
+}
\ No newline at end of file
diff --git a/GreenshotJiraPlugin/Hooking/WindowsEventHook.cs b/GreenshotJiraPlugin/Hooking/WindowsEventHook.cs
new file mode 100644
index 000000000..e2be9d0b0
--- /dev/null
+++ b/GreenshotJiraPlugin/Hooking/WindowsEventHook.cs
@@ -0,0 +1,152 @@
+/*
+ * dapplo - building blocks for desktop applications
+ * Copyright (C) Dapplo 2015-2016
+ *
+ * For more information see: http://dapplo.net/
+ * dapplo repositories are hosted on GitHub: https://github.com/dapplo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using GreenshotPlugin.UnmanagedHelpers;
+
+namespace GreenshotJiraPlugin.Hooking
+{
+ ///
+ /// The WinEventHook can register handlers to become important windows events
+ /// This makes it possible to know a.o. when a window is created, moved, updated and closed.
+ ///
+ public class WindowsEventHook : IDisposable
+ {
+ private readonly WinEventDelegate _winEventHandler;
+ private GCHandle _gcHandle;
+
+ ///
+ /// Used with Register hook
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public delegate void WinEventHandler(WinEvent eventType, IntPtr hwnd, EventObjects idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
+
+ ///
+ /// Create a WindowsEventHook object
+ ///
+ public WindowsEventHook()
+ {
+ _winEventHandler = WinEventDelegateHandler;
+ _gcHandle = GCHandle.Alloc(_winEventHandler);
+ }
+
+ #region native code
+ [DllImport("user32", SetLastError = true)]
+ private static extern bool UnhookWinEvent(IntPtr hWinEventHook);
+ [DllImport("user32", SetLastError = true)]
+ private static extern IntPtr SetWinEventHook(WinEvent eventMin, WinEvent eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, int idProcess, int idThread, WinEventHookFlags dwFlags);
+
+ ///
+ /// Used with SetWinEventHook
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private delegate void WinEventDelegate(IntPtr hWinEventHook, WinEvent eventType, IntPtr hwnd, EventObjects idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
+ #endregion
+
+ private readonly IDictionary _winEventHandlers = new Dictionary();
+
+ ///
+ /// Are hooks active?
+ ///
+ public bool IsHooked => _winEventHandlers.Count > 0;
+
+ ///
+ /// Hook a WinEvent
+ ///
+ ///
+ ///
+ /// true if success
+ public void Hook(WinEvent winEvent, WinEventHandler winEventHandler)
+ {
+ Hook(winEvent, winEvent, winEventHandler);
+ }
+
+ ///
+ /// Hook a WinEvent
+ ///
+ ///
+ ///
+ ///
+ public void Hook(WinEvent winEventStart, WinEvent winEventEnd, WinEventHandler winEventHandler)
+ {
+ var hookPtr = SetWinEventHook(winEventStart, winEventEnd, IntPtr.Zero, _winEventHandler, 0, 0, WinEventHookFlags.WINEVENT_SKIPOWNPROCESS | WinEventHookFlags.WINEVENT_OUTOFCONTEXT);
+ _winEventHandlers.Add(hookPtr, winEventHandler);
+ }
+
+ ///
+ /// Remove all hooks
+ ///
+ private void Unhook()
+ {
+ foreach (var hookPtr in _winEventHandlers.Keys)
+ {
+ if (hookPtr != IntPtr.Zero)
+ {
+ UnhookWinEvent(hookPtr);
+ }
+ }
+ _winEventHandlers.Clear();
+ _gcHandle.Free();
+ }
+
+ ///
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ ///
+ public void Dispose()
+ {
+ Unhook();
+ }
+
+ ///
+ /// Call the WinEventHandler for this event
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private void WinEventDelegateHandler(IntPtr hWinEventHook, WinEvent eventType, IntPtr hWnd, EventObjects idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
+ {
+ WinEventHandler handler;
+ if (_winEventHandlers.TryGetValue(hWinEventHook, out handler))
+ {
+ handler(eventType, hWnd, idObject, idChild, dwEventThread, dwmsEventTime);
+ }
+ }
+
+ }
+
+}
diff --git a/GreenshotJiraPlugin/Hooking/WindowsTitleMonitor.cs b/GreenshotJiraPlugin/Hooking/WindowsTitleMonitor.cs
new file mode 100644
index 000000000..5e0a05cf7
--- /dev/null
+++ b/GreenshotJiraPlugin/Hooking/WindowsTitleMonitor.cs
@@ -0,0 +1,139 @@
+/*
+ * dapplo - building blocks for desktop applications
+ * Copyright (C) Dapplo 2015-2016
+ *
+ * For more information see: http://dapplo.net/
+ * dapplo repositories are hosted on GitHub: https://github.com/dapplo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using System;
+using GreenshotPlugin.Core;
+using GreenshotPlugin.UnmanagedHelpers;
+
+namespace GreenshotJiraPlugin.Hooking
+{
+ ///
+ /// Monitor all title changes
+ ///
+ public sealed class WindowsTitleMonitor : IDisposable
+ {
+ private WindowsEventHook _hook;
+ private readonly object _lockObject = new object();
+ // ReSharper disable once InconsistentNaming
+ private event TitleChangeEventDelegate _titleChangeEvent;
+
+ ///
+ /// Add / remove event handler to the title monitor
+ ///
+ public event TitleChangeEventDelegate TitleChangeEvent
+ {
+ add
+ {
+ lock (_lockObject)
+ {
+ if (_hook == null)
+ {
+ _hook = new WindowsEventHook();
+ _hook.Hook(WinEvent.EVENT_OBJECT_NAMECHANGE, WinEventHandler);
+ }
+ _titleChangeEvent += value;
+ }
+ }
+ remove
+ {
+ lock (_lockObject)
+ {
+ _titleChangeEvent -= value;
+ if (_titleChangeEvent == null || _titleChangeEvent.GetInvocationList().Length == 0)
+ {
+ if (_hook != null)
+ {
+ _hook.Dispose();
+ _hook = null;
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// WinEventDelegate for the creation & destruction
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private void WinEventHandler(WinEvent eventType, IntPtr hWnd, EventObjects idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
+ {
+ if (hWnd == IntPtr.Zero || idObject != EventObjects.OBJID_WINDOW)
+ {
+ return;
+ }
+ if (eventType == WinEvent.EVENT_OBJECT_NAMECHANGE)
+ {
+ if (_titleChangeEvent != null)
+ {
+ string newTitle = new WindowDetails(hWnd).Text;
+ _titleChangeEvent(new TitleChangeEventArgs { HWnd = hWnd, Title = newTitle });
+ }
+ }
+ }
+
+ #region IDisposable Support
+
+ private bool _disposedValue; // To detect redundant calls
+
+ ///
+ /// Dispose the underlying hook
+ ///
+ public void Dispose(bool disposing)
+ {
+ if (_disposedValue)
+ {
+ return;
+ }
+ lock (_lockObject)
+ {
+ _hook?.Dispose();
+ }
+ _disposedValue = true;
+ }
+
+ ///
+ /// Make sure the finalizer disposes the underlying hook
+ ///
+ ~WindowsTitleMonitor()
+ {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ Dispose(false);
+ }
+
+ ///
+ /// Dispose the underlying hook
+ ///
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ #endregion
+ }
+
+}
diff --git a/GreenshotJiraPlugin/JiraConnector.cs b/GreenshotJiraPlugin/JiraConnector.cs
index 65f2baa66..d3ded2449 100644
--- a/GreenshotJiraPlugin/JiraConnector.cs
+++ b/GreenshotJiraPlugin/JiraConnector.cs
@@ -47,12 +47,13 @@ namespace GreenshotJiraPlugin {
private DateTimeOffset _loggedInTime = DateTimeOffset.MinValue;
private bool _loggedIn;
private readonly int _timeout;
- private readonly object _lock = new object();
- private string _url;
private JiraApi _jiraApi;
private IssueTypeBitmapCache _issueTypeBitmapCache;
private static readonly SvgBitmapHttpContentConverter SvgBitmapHttpContentConverterInstance = new SvgBitmapHttpContentConverter();
+ ///
+ /// Initialize some basic stuff, in the case the SVG to bitmap converter
+ ///
static JiraConnector()
{
if (HttpExtensionsGlobals.HttpContentConverters.All(x => x.GetType() != typeof(SvgBitmapHttpContentConverter)))
@@ -72,46 +73,48 @@ namespace GreenshotJiraPlugin {
}
+ ///
+ /// Dispose, logout the users
+ ///
public void Dispose() {
if (_jiraApi != null)
{
- Task.Run(async () => await Logout()).Wait();
+ Task.Run(async () => await LogoutAsync()).Wait();
}
}
+ ///
+ /// Constructor
+ ///
public JiraConnector()
{
- _url = JiraConfig.Url.Replace(DefaultPostfix, "");
+ JiraConfig.Url = JiraConfig.Url.Replace(DefaultPostfix, "");
_timeout = JiraConfig.Timeout;
}
+ ///
+ /// Access the jira monitor
+ ///
+ public JiraMonitor Monitor { get; private set; }
+
///
/// Internal login which catches the exceptions
///
/// true if login was done sucessfully
- private async Task DoLogin(string user, string password)
+ private async Task DoLoginAsync(string user, string password)
{
- lock (_lock)
+ if (string.IsNullOrEmpty(user) || string.IsNullOrEmpty(password))
{
- if (_url.EndsWith("wsdl"))
- {
- _url = _url.Replace(DefaultPostfix, "");
- }
- if (_jiraApi == null)
- {
- // recreate the service with the new url
- _jiraApi = new JiraApi(new Uri(_url));
- _issueTypeBitmapCache = new IssueTypeBitmapCache(_jiraApi);
- }
+ return false;
}
-
+ _jiraApi = new JiraApi(new Uri(JiraConfig.Url));
+ _issueTypeBitmapCache = new IssueTypeBitmapCache(_jiraApi);
+ Monitor = new JiraMonitor();
+ await Monitor.AddJiraInstanceAsync(_jiraApi);
LoginInfo loginInfo;
try
{
loginInfo = await _jiraApi.StartSessionAsync(user, password);
- // Worked, store the url in the configuration
- JiraConfig.Url = _url;
- IniConfig.Save();
}
catch (Exception)
{
@@ -120,17 +123,21 @@ namespace GreenshotJiraPlugin {
return loginInfo != null;
}
- public async Task Login() {
- await Logout();
+ ///
+ /// Use the credentials dialog, this will show if there are not correct credentials.
+ /// If there are credentials, call the real login.
+ ///
+ /// Task
+ public async Task LoginAsync() {
+ await LogoutAsync();
try {
// Get the system name, so the user knows where to login to
- string systemName = _url.Replace(DefaultPostfix,"");
- var credentialsDialog = new CredentialsDialog(systemName)
+ var credentialsDialog = new CredentialsDialog(JiraConfig.Url)
{
Name = null
};
while (credentialsDialog.Show(credentialsDialog.Name) == DialogResult.OK) {
- if (await DoLogin(credentialsDialog.Name, credentialsDialog.Password)) {
+ if (await DoLoginAsync(credentialsDialog.Name, credentialsDialog.Password)) {
if (credentialsDialog.SaveChecked) {
credentialsDialog.Confirm(true);
}
@@ -159,9 +166,10 @@ namespace GreenshotJiraPlugin {
///
/// End the session, if there was one
///
- public async Task Logout() {
+ public async Task LogoutAsync() {
if (_jiraApi != null && _loggedIn)
{
+ Monitor.Dispose();
await _jiraApi.EndSessionAsync();
_loggedIn = false;
}
@@ -172,14 +180,14 @@ namespace GreenshotJiraPlugin {
/// Do not use ConfigureAwait to call this, as it will move await from the UI thread.
///
///
- private async Task CheckCredentials() {
+ private async Task CheckCredentialsAsync() {
if (_loggedIn) {
if (_loggedInTime.AddMinutes(_timeout-1).CompareTo(DateTime.Now) < 0) {
- await Logout();
- await Login();
+ await LogoutAsync();
+ await LoginAsync();
}
} else {
- await Login();
+ await LoginAsync();
}
}
@@ -189,7 +197,7 @@ namespace GreenshotJiraPlugin {
/// List with filters
public async Task> GetFavoriteFiltersAsync()
{
- await CheckCredentials();
+ await CheckCredentialsAsync();
return await _jiraApi.GetFavoriteFiltersAsync().ConfigureAwait(false);
}
@@ -200,7 +208,7 @@ namespace GreenshotJiraPlugin {
/// Issue
public async Task GetIssueAsync(string issueKey)
{
- await CheckCredentials();
+ await CheckCredentialsAsync();
try
{
return await _jiraApi.GetIssueAsync(issueKey).ConfigureAwait(false);
@@ -220,7 +228,7 @@ namespace GreenshotJiraPlugin {
///
public async Task AttachAsync(string issueKey, IBinaryContainer content, CancellationToken cancellationToken = default(CancellationToken))
{
- await CheckCredentials();
+ await CheckCredentialsAsync();
using (var memoryStream = new MemoryStream())
{
content.WriteToStream(memoryStream);
@@ -238,7 +246,7 @@ namespace GreenshotJiraPlugin {
/// CancellationToken
public async Task AddCommentAsync(string issueKey, string body, string visibility = null, CancellationToken cancellationToken = default(CancellationToken))
{
- await CheckCredentials();
+ await CheckCredentialsAsync();
await _jiraApi.AddCommentAsync(issueKey, body, visibility, cancellationToken).ConfigureAwait(false);
}
@@ -250,7 +258,7 @@ namespace GreenshotJiraPlugin {
///
public async Task> SearchAsync(Filter filter, CancellationToken cancellationToken = default(CancellationToken))
{
- await CheckCredentials();
+ await CheckCredentialsAsync();
var searchResult = await _jiraApi.SearchAsync(filter.Jql, 20, new[] { "summary", "reporter", "assignee", "created", "issuetype" }, cancellationToken).ConfigureAwait(false);
return searchResult.Issues;
}
@@ -266,8 +274,14 @@ namespace GreenshotJiraPlugin {
return await _issueTypeBitmapCache.GetOrCreateAsync(issue.Fields.IssueType, cancellationToken).ConfigureAwait(false);
}
+ ///
+ /// Get the base uri
+ ///
public Uri JiraBaseUri => _jiraApi.JiraBaseUri;
+ ///
+ /// Is the user "logged in?
+ ///
public bool IsLoggedIn => _loggedIn;
}
}
\ No newline at end of file
diff --git a/GreenshotJiraPlugin/JiraDestination.cs b/GreenshotJiraPlugin/JiraDestination.cs
index 6d472535a..d221ba996 100644
--- a/GreenshotJiraPlugin/JiraDestination.cs
+++ b/GreenshotJiraPlugin/JiraDestination.cs
@@ -61,7 +61,7 @@ namespace GreenshotJiraPlugin {
return Language.GetString("jira", LangKey.upload_menu_item);
}
// Format the title of this destination
- return Designation + " - " + _jiraIssue.Key + ": " + _jiraIssue.Fields.Summary.Substring(0, Math.Min(20, _jiraIssue.Fields.Summary.Length));
+ return _jiraIssue.Key + ": " + _jiraIssue.Fields.Summary.Substring(0, Math.Min(20, _jiraIssue.Fields.Summary.Length));
}
}
@@ -86,15 +86,15 @@ namespace GreenshotJiraPlugin {
}
}
- public override IEnumerable DynamicDestinations() {
- if (JiraPlugin.Instance.CurrentJiraConnector == null || !JiraPlugin.Instance.CurrentJiraConnector.IsLoggedIn) {
+ public override IEnumerable DynamicDestinations()
+ {
+ var jiraConnector = JiraPlugin.Instance.CurrentJiraConnector;
+ if (jiraConnector == null || !jiraConnector.IsLoggedIn) {
yield break;
}
- var issues = JiraUtils.GetCurrentJirasAsync().Result;
- if (issues != null) {
- foreach(var jiraIssue in issues) {
- yield return new JiraDestination(_jiraPlugin, jiraIssue);
- }
+ foreach(var jiraDetails in jiraConnector.Monitor.RecentJiras)
+ {
+ yield return new JiraDestination(_jiraPlugin,jiraDetails.JiraIssue);
}
}
diff --git a/GreenshotJiraPlugin/JiraDetails.cs b/GreenshotJiraPlugin/JiraDetails.cs
new file mode 100644
index 000000000..e03172873
--- /dev/null
+++ b/GreenshotJiraPlugin/JiraDetails.cs
@@ -0,0 +1,71 @@
+/*
+ * Greenshot - a free and open source screenshot tool
+ * Copyright (C) 2007-2016 Thomas Braun, Jens Klingen, Robin Krom
+ *
+ * For more information see: http://getgreenshot.org/
+ * The Greenshot project is hosted on GitHub: https://github.com/greenshot
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using System;
+using Dapplo.Jira.Entities;
+
+namespace GreenshotJiraPlugin
+{
+ public class JiraDetails : IComparable
+ {
+ public JiraDetails()
+ {
+ FirstSeenAt = SeenAt = DateTimeOffset.Now;
+ }
+
+ public string ProjectKey
+ {
+ get;
+ set;
+ }
+
+ public string Id
+ {
+ get;
+ set;
+ }
+
+ public string JiraKey => ProjectKey + "-" + Id;
+
+ public Issue JiraIssue
+ {
+ get;
+ set;
+ }
+
+ public DateTimeOffset FirstSeenAt
+ {
+ get;
+ private set;
+ }
+
+ public DateTimeOffset SeenAt
+ {
+ get;
+ set;
+ }
+
+ public int CompareTo(JiraDetails other)
+ {
+ return SeenAt.CompareTo(other.SeenAt);
+ }
+ }
+}
\ No newline at end of file
diff --git a/GreenshotJiraPlugin/JiraEventArgs.cs b/GreenshotJiraPlugin/JiraEventArgs.cs
new file mode 100644
index 000000000..cbe7caa0d
--- /dev/null
+++ b/GreenshotJiraPlugin/JiraEventArgs.cs
@@ -0,0 +1,40 @@
+/*
+ * Greenshot - a free and open source screenshot tool
+ * Copyright (C) 2007-2016 Thomas Braun, Jens Klingen, Robin Krom
+ *
+ * For more information see: http://getgreenshot.org/
+ * The Greenshot project is hosted on GitHub: https://github.com/greenshot
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using System;
+
+namespace GreenshotJiraPlugin
+{
+ public class JiraEventArgs : EventArgs
+ {
+ public JiraEventTypes EventType
+ {
+ get;
+ set;
+ }
+
+ public JiraDetails Details
+ {
+ get;
+ set;
+ }
+ }
+}
diff --git a/GreenshotJiraPlugin/JiraEventTypes.cs b/GreenshotJiraPlugin/JiraEventTypes.cs
new file mode 100644
index 000000000..06b95db22
--- /dev/null
+++ b/GreenshotJiraPlugin/JiraEventTypes.cs
@@ -0,0 +1,29 @@
+/*
+ * Greenshot - a free and open source screenshot tool
+ * Copyright (C) 2007-2016 Thomas Braun, Jens Klingen, Robin Krom
+ *
+ * For more information see: http://getgreenshot.org/
+ * The Greenshot project is hosted on GitHub: https://github.com/greenshot
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+namespace GreenshotJiraPlugin
+{
+ public enum JiraEventTypes
+ {
+ OrderChanged,
+ DetectedNewJiraIssue
+ }
+}
\ No newline at end of file
diff --git a/GreenshotJiraPlugin/JiraMonitor.cs b/GreenshotJiraPlugin/JiraMonitor.cs
new file mode 100644
index 000000000..1d3784fd0
--- /dev/null
+++ b/GreenshotJiraPlugin/JiraMonitor.cs
@@ -0,0 +1,225 @@
+/*
+ * Greenshot - a free and open source screenshot tool
+ * Copyright (C) 2007-2016 Thomas Braun, Jens Klingen, Robin Krom
+ *
+ * For more information see: http://getgreenshot.org/
+ * The Greenshot project is hosted on GitHub: https://github.com/greenshot
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using Dapplo.Jira;
+using Dapplo.Log.Facade;
+using GreenshotJiraPlugin.Hooking;
+
+namespace GreenshotJiraPlugin
+{
+
+ ///
+ /// This class will monitor all _jira activity by registering for title changes
+ /// It keeps a list of the last "accessed" jiras, and makes it easy to upload to one.
+ /// Make sure this is instanciated on the UI thread!
+ ///
+ public class JiraMonitor : IDisposable
+ {
+ private static readonly LogSource Log = new LogSource();
+ private readonly Regex _jiraKeyPattern = new Regex(@"[A-Z][A-Z0-9]+\-[0-9]+");
+ private readonly WindowsTitleMonitor _monitor;
+ private readonly IList _jiraInstances = new List();
+ private readonly IDictionary _projectJiraApiMap = new Dictionary();
+ private readonly int _maxEntries;
+ private IDictionary _recentJiras = new Dictionary();
+
+ ///
+ /// Register to this event to get events when new jira issues are detected
+ ///
+ public event EventHandler JiraEvent;
+
+ public JiraMonitor(int maxEntries = 40)
+ {
+ _maxEntries = maxEntries;
+ _monitor = new WindowsTitleMonitor();
+ _monitor.TitleChangeEvent += MonitorTitleChangeEvent;
+ }
+
+ #region Dispose
+
+ ///
+ /// Dispose
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Dispose all managed resources
+ ///
+ /// when true is passed all managed resources are disposed.
+ protected void Dispose(bool disposing)
+ {
+ if (!disposing)
+ {
+ return;
+ }
+ // free managed resources
+ _monitor.TitleChangeEvent -= MonitorTitleChangeEvent;
+ _monitor.Dispose();
+ // free native resources if there are any.
+ }
+
+ #endregion
+
+ ///
+ /// Retrieve the API belonging to a JiraDetails
+ ///
+ ///
+ /// JiraAPI
+ public JiraApi GetJiraApiForKey(JiraDetails jiraDetails)
+ {
+ return _projectJiraApiMap[jiraDetails.ProjectKey];
+ }
+
+ ///
+ /// Get the "list" of recently seen Jiras
+ ///
+ public IEnumerable RecentJiras =>
+ (from jiraDetails in _recentJiras.Values
+ orderby jiraDetails.SeenAt descending
+ select jiraDetails);
+
+ ///
+ /// Check if this monitor has active instances
+ ///
+ public bool HasJiraInstances => _jiraInstances.Count > 0;
+
+ ///
+ /// Add an instance of a JIRA system
+ ///
+ ///
+ ///
+ public async Task AddJiraInstanceAsync(JiraApi jiraInstance, CancellationToken token = default(CancellationToken))
+ {
+ _jiraInstances.Add(jiraInstance);
+ var projects = await jiraInstance.GetProjectsAsync(token).ConfigureAwait(false);
+ if (projects != null)
+ {
+ foreach (var project in projects)
+ {
+ if (!_projectJiraApiMap.ContainsKey(project.Key))
+ {
+ _projectJiraApiMap.Add(project.Key, jiraInstance);
+ }
+ }
+ }
+ }
+
+ ///
+ /// This method will update details, like the title, and send an event to registed listeners of the JiraEvent
+ ///
+ /// Contains the jira key to retrieve the title (XYZ-1234)
+ /// Task
+ private async Task DetectedNewJiraIssueAsync(JiraDetails jiraDetails)
+ {
+ try
+ {
+ JiraApi jiraApi;
+ if (_projectJiraApiMap.TryGetValue(jiraDetails.ProjectKey, out jiraApi))
+ {
+ var issue = await jiraApi.GetIssueAsync(jiraDetails.JiraKey).ConfigureAwait(false);
+ jiraDetails.JiraIssue = issue;
+ }
+ // Send event
+ JiraEvent?.Invoke(this, new JiraEventArgs { Details = jiraDetails, EventType = JiraEventTypes.DetectedNewJiraIssue });
+ }
+ catch (Exception ex)
+ {
+ Log.Warn().WriteLine("Couldn't retrieve JIRA title: {0}", ex.Message);
+ }
+ }
+
+ ///
+ /// Handle title changes, check for JIRA
+ ///
+ ///
+ private void MonitorTitleChangeEvent(TitleChangeEventArgs eventArgs)
+ {
+ string windowTitle = eventArgs.Title;
+ if (string.IsNullOrEmpty(windowTitle))
+ {
+ return;
+ }
+ var jiraKeyMatch = _jiraKeyPattern.Match(windowTitle);
+ if (!jiraKeyMatch.Success)
+ {
+ return;
+ }
+ // Found a possible JIRA title
+ var jiraKey = jiraKeyMatch.Value;
+ var jiraKeyParts = jiraKey.Split('-');
+ var projectKey = jiraKeyParts[0];
+ var jiraId = jiraKeyParts[1];
+
+ JiraApi jiraApi;
+ // Check if we have a JIRA instance with a project for this key
+ if (_projectJiraApiMap.TryGetValue(projectKey, out jiraApi))
+ {
+ // We have found a project for this _jira key, so it must be a valid & known JIRA
+ JiraDetails currentJiraDetails;
+ if (_recentJiras.TryGetValue(jiraKey, out currentJiraDetails))
+ {
+ // update
+ currentJiraDetails.SeenAt = DateTimeOffset.Now;
+
+ // Notify the order change
+ JiraEvent?.Invoke(this, new JiraEventArgs { Details = currentJiraDetails, EventType = JiraEventTypes.OrderChanged });
+ // Nothing else to do
+
+ return;
+ }
+ // We detected an unknown JIRA, so add it to our list
+ currentJiraDetails = new JiraDetails()
+ {
+ Id = jiraId,
+ ProjectKey = projectKey
+ };
+ _recentJiras.Add(currentJiraDetails.JiraKey, currentJiraDetails);
+
+ // Make sure we don't collect _jira's until the memory is full
+ if (_recentJiras.Count > _maxEntries)
+ {
+ // Add it to the list of recent Jiras
+ IList clonedList = new List(_recentJiras.Values);
+ _recentJiras = (from jiraDetails in clonedList
+ orderby jiraDetails.SeenAt descending
+ select jiraDetails).Take(_maxEntries).ToDictionary(jd => jd.JiraKey, jd => jd);
+ }
+ // Now we can get the title from JIRA itself
+ // ReSharper disable once UnusedVariable
+ var updateTitleTask = DetectedNewJiraIssueAsync(currentJiraDetails);
+ }
+ else
+ {
+ Log.Info().WriteLine("Couldn't match possible JIRA key {0} to projects in a configured JIRA instance, ignoring", projectKey);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/GreenshotJiraPlugin/JiraPlugin.cs b/GreenshotJiraPlugin/JiraPlugin.cs
index 6f9e499e9..009ac9ce3 100644
--- a/GreenshotJiraPlugin/JiraPlugin.cs
+++ b/GreenshotJiraPlugin/JiraPlugin.cs
@@ -33,9 +33,9 @@ namespace GreenshotJiraPlugin {
///
public class JiraPlugin : IGreenshotPlugin {
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(JiraPlugin));
- private JiraConnector _jiraConnector;
private JiraConfiguration _config;
private static JiraPlugin _instance;
+ private JiraConnector _jiraConnector;
public void Dispose() {
Dispose(true);
@@ -44,9 +44,9 @@ namespace GreenshotJiraPlugin {
protected void Dispose(bool disposing) {
if (disposing) {
- if (_jiraConnector != null) {
- _jiraConnector.Dispose();
- _jiraConnector = null;
+ if (JiraConnector != null) {
+ JiraConnector.Dispose();
+ JiraConnector = null;
}
}
}
@@ -66,9 +66,23 @@ namespace GreenshotJiraPlugin {
}
//Needed for a fail-fast
- public JiraConnector CurrentJiraConnector => _jiraConnector;
+ public JiraConnector CurrentJiraConnector => JiraConnector;
- public JiraConnector JiraConnector => _jiraConnector ?? (_jiraConnector = new JiraConnector());
+ public JiraConnector JiraConnector
+ {
+ get
+ {
+ lock (_instance)
+ {
+ if (_jiraConnector == null)
+ {
+ JiraConnector = new JiraConnector();
+ }
+ }
+ return _jiraConnector;
+ }
+ private set { _jiraConnector = value; }
+ }
///
/// Implementation of the IGreenshotPlugin.Initialize
@@ -80,14 +94,15 @@ namespace GreenshotJiraPlugin {
// Register configuration (don't need the configuration itself)
_config = IniConfig.GetIniSection();
LogSettings.RegisterDefaultLogger();
+
return true;
}
public void Shutdown() {
Log.Debug("Jira Plugin shutdown.");
- if (_jiraConnector != null)
+ if (JiraConnector != null)
{
- Task.Run(async () => await _jiraConnector.Logout());
+ Task.Run(async () => await JiraConnector.LogoutAsync());
}
}
@@ -98,12 +113,12 @@ namespace GreenshotJiraPlugin {
string url = _config.Url;
if (ShowConfigDialog()) {
// check for re-login
- if (_jiraConnector != null && _jiraConnector.IsLoggedIn && !string.IsNullOrEmpty(url)) {
+ if (JiraConnector != null && JiraConnector.IsLoggedIn && !string.IsNullOrEmpty(url)) {
if (!url.Equals(_config.Url)) {
Task.Run(async () =>
{
- await _jiraConnector.Logout();
- await _jiraConnector.Login();
+ await JiraConnector.LogoutAsync();
+ await JiraConnector.LoginAsync();
});
}
}
diff --git a/GreenshotJiraPlugin/JiraUtils.cs b/GreenshotJiraPlugin/JiraUtils.cs
deleted file mode 100644
index 94984f555..000000000
--- a/GreenshotJiraPlugin/JiraUtils.cs
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Greenshot - a free and open source screenshot tool
- * Copyright (C) 2007-2016 Thomas Braun, Jens Klingen, Robin Krom
- *
- * For more information see: http://getgreenshot.org/
- * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 1 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-using System;
-using System.Collections.Generic;
-using System.Text.RegularExpressions;
-using System.Threading.Tasks;
-using Dapplo.Jira.Entities;
-using Greenshot.IniFile;
-using GreenshotPlugin.Core;
-
-namespace GreenshotJiraPlugin {
- ///
- /// Description of JiraUtils.
- ///
- public static class JiraUtils {
- private static readonly Regex JiraKeyRegex = new Regex(@"/browse/([A-Z0-9]+\-[0-9]+)");
- private static readonly JiraConfiguration Config = IniConfig.GetIniSection();
- private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(JiraUtils));
-
- public static async Task> GetCurrentJirasAsync() {
- // Make sure we suppress the login
- var jirakeys = new List();
- foreach(string url in IEHelper.GetIEUrls()) {
- if (url == null) {
- continue;
- }
- var jiraKeyMatch = JiraKeyRegex.Matches(url);
- if (jiraKeyMatch.Count > 0) {
- string jiraKey = jiraKeyMatch[0].Groups[1].Value;
- jirakeys.Add(jiraKey);
- }
- }
- if (!string.IsNullOrEmpty(Config.LastUsedJira) && !jirakeys.Contains(Config.LastUsedJira)) {
- jirakeys.Add(Config.LastUsedJira);
- }
- if (jirakeys.Count > 0) {
- var jiraIssues = new List();
- foreach(string jiraKey in jirakeys) {
- try
- {
- var issue = await JiraPlugin.Instance.JiraConnector.GetIssueAsync(jiraKey).ConfigureAwait(false);
- if (issue != null)
- {
- jiraIssues.Add(issue);
- }
- }
- catch (Exception ex)
- {
- Log.Error(ex);
- // Remove issue from the last used jira config, as it caused an issue (probably not there)
- if (Config.LastUsedJira == jiraKey)
- {
- Config.LastUsedJira = null;
- }
- }
- }
- if (jiraIssues.Count > 0) {
- return jiraIssues;
- }
- }
- return null;
- }
- }
-}
diff --git a/GreenshotPlugin/Core/CredentialsHelper.cs b/GreenshotPlugin/Core/CredentialsHelper.cs
index 5f9eaa8ab..6aa8d2bff 100644
--- a/GreenshotPlugin/Core/CredentialsHelper.cs
+++ b/GreenshotPlugin/Core/CredentialsHelper.cs
@@ -59,10 +59,10 @@ namespace GreenshotPlugin.Core {
/// Encapsulates dialog functionality from the Credential Management API.
public sealed class CredentialsDialog {
[DllImport("gdi32.dll", SetLastError=true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- private static extern bool DeleteObject(IntPtr hObject);
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool DeleteObject(IntPtr hObject);
- /// The only valid bitmap height (in pixels) of a user-defined banner.
+ /// The only valid bitmap height (in pixels) of a user-defined banner.
private const int ValidBannerHeight = 60;
/// The only valid bitmap width (in pixels) of a user-defined banner.
@@ -102,65 +102,25 @@ namespace GreenshotPlugin.Core {
Banner = banner;
}
- private bool _alwaysDisplay;
///
/// Gets or sets if the dialog will be shown even if the credentials
/// can be returned from an existing credential in the credential manager.
///
- public bool AlwaysDisplay {
- get {
- return _alwaysDisplay;
- }
- set {
- _alwaysDisplay = value;
- }
- }
+ public bool AlwaysDisplay { get; set; }
- private bool _excludeCertificates = true;
/// Gets or sets if the dialog is populated with name/password only.
- public bool ExcludeCertificates {
- get {
- return _excludeCertificates;
- }
- set {
- _excludeCertificates = value;
- }
- }
+ public bool ExcludeCertificates { get; set; } = true;
- private bool _persist = true;
/// Gets or sets if the credentials are to be persisted in the credential manager.
- public bool Persist {
- get {
- return _persist;
- }
- set {
- _persist = value;
- }
- }
+ public bool Persist { get; set; } = true;
- private bool _incorrectPassword;
/// Gets or sets if the incorrect password balloontip needs to be shown. Introduced AFTER Windows XPGets>
- public bool IncorrectPassword {
- get {
- return _incorrectPassword;
- }
- set {
- _incorrectPassword = value;
- }
- }
+ public bool IncorrectPassword { get; set; }
- private bool _keepName;
/// Gets or sets if the name is read-only.
- public bool KeepName {
- get {
- return _keepName;
- }
- set {
- _keepName = value;
- }
- }
+ public bool KeepName { get; set; }
- private string _name = String.Empty;
+ private string _name = string.Empty;
/// Gets or sets the name for the credentials.
public string Name {
get {
@@ -169,18 +129,18 @@ namespace GreenshotPlugin.Core {
set {
if (value != null) {
if (value.Length > CREDUI.MAX_USERNAME_LENGTH) {
- string message = String.Format(
+ string message = string.Format(
Thread.CurrentThread.CurrentUICulture,
"The name has a maximum length of {0} characters.",
CREDUI.MAX_USERNAME_LENGTH);
- throw new ArgumentException(message, "Name");
+ throw new ArgumentException(message, nameof(Name));
}
}
_name = value;
}
}
- private string _password = String.Empty;
+ private string _password = string.Empty;
/// Gets or sets the password for the credentials.
public string Password {
get {
@@ -189,41 +149,25 @@ namespace GreenshotPlugin.Core {
set {
if (value != null) {
if (value.Length > CREDUI.MAX_PASSWORD_LENGTH) {
- string message = String.Format(
+ string message = string.Format(
Thread.CurrentThread.CurrentUICulture,
"The password has a maximum length of {0} characters.",
CREDUI.MAX_PASSWORD_LENGTH);
- throw new ArgumentException(message, "Password");
+ throw new ArgumentException(message, nameof(Password));
}
}
_password = value;
}
}
- private bool _saveChecked;
/// Gets or sets if the save checkbox status.
- public bool SaveChecked {
- get {
- return _saveChecked;
- }
- set {
- _saveChecked = value;
- }
- }
+ public bool SaveChecked { get; set; }
- private bool _saveDisplayed = true;
/// Gets or sets if the save checkbox is displayed.
/// This value only has effect if Persist is true.
- public bool SaveDisplayed {
- get {
- return _saveDisplayed;
- }
- set {
- _saveDisplayed = value;
- }
- }
+ public bool SaveDisplayed { get; set; } = true;
- private string _target = String.Empty;
+ private string _target = string.Empty;
/// Gets or sets the name of the target for the credentials, typically a server name.
public string Target {
get {
@@ -232,18 +176,19 @@ namespace GreenshotPlugin.Core {
set {
if (value == null) {
throw new ArgumentException("The target cannot be a null value.", "Target");
- } else if (value.Length > CREDUI.MAX_GENERIC_TARGET_LENGTH) {
- string message = String.Format(
+ }
+ if (value.Length > CREDUI.MAX_GENERIC_TARGET_LENGTH) {
+ string message = string.Format(
Thread.CurrentThread.CurrentUICulture,
"The target has a maximum length of {0} characters.",
CREDUI.MAX_GENERIC_TARGET_LENGTH);
- throw new ArgumentException(message, "Target");
+ throw new ArgumentException(message, nameof(Target));
}
_target = value;
}
}
- private string _caption = String.Empty;
+ private string _caption = string.Empty;
/// Gets or sets the caption of the dialog.
/// A null value will cause a system default caption to be used.
public string Caption {
@@ -253,18 +198,18 @@ namespace GreenshotPlugin.Core {
set {
if (value != null) {
if (value.Length > CREDUI.MAX_CAPTION_LENGTH) {
- string message = String.Format(
+ string message = string.Format(
Thread.CurrentThread.CurrentUICulture,
"The caption has a maximum length of {0} characters.",
CREDUI.MAX_CAPTION_LENGTH);
- throw new ArgumentException(message, "Caption");
+ throw new ArgumentException(message, nameof(Caption));
}
}
_caption = value;
}
}
- private string _message = String.Empty;
+ private string _message = string.Empty;
/// Gets or sets the message of the dialog.
/// A null value will cause a system default message to be used.
public string Message {
@@ -274,11 +219,11 @@ namespace GreenshotPlugin.Core {
set {
if (value != null) {
if (value.Length > CREDUI.MAX_MESSAGE_LENGTH) {
- string message = String.Format(
+ string message = string.Format(
Thread.CurrentThread.CurrentUICulture,
"The message has a maximum length of {0} characters.",
CREDUI.MAX_MESSAGE_LENGTH);
- throw new ArgumentException(message, "Message");
+ throw new ArgumentException(message, nameof(Message));
}
}
_message = value;
@@ -295,10 +240,10 @@ namespace GreenshotPlugin.Core {
set {
if (value != null) {
if (value.Width != ValidBannerWidth) {
- throw new ArgumentException("The banner image width must be 320 pixels.", "Banner");
+ throw new ArgumentException("The banner image width must be 320 pixels.", nameof(Banner));
}
if (value.Height != ValidBannerHeight) {
- throw new ArgumentException("The banner image height must be 60 pixels.", "Banner");
+ throw new ArgumentException("The banner image height must be 60 pixels.", nameof(Banner));
}
}
_banner = value;
diff --git a/GreenshotPlugin/UnmanagedHelpers/Enumerations.cs b/GreenshotPlugin/UnmanagedHelpers/Enumerations.cs
index c5bcb7daa..0d73ebeef 100644
--- a/GreenshotPlugin/UnmanagedHelpers/Enumerations.cs
+++ b/GreenshotPlugin/UnmanagedHelpers/Enumerations.cs
@@ -112,7 +112,7 @@ namespace GreenshotPlugin.UnmanagedHelpers {
WS_EX_LAYOUTRTL = 0x00400000, // Right to left mirroring
WS_EX_COMPOSITED = 0x02000000,
WS_EX_NOACTIVATE = 0x08000000
- }
+ }
[Flags]
[SuppressMessage("ReSharper", "InconsistentNaming")]
@@ -202,38 +202,38 @@ namespace GreenshotPlugin.UnmanagedHelpers {
[SuppressMessage("ReSharper", "InconsistentNaming")]
public enum SYSCOLOR
{
- SCROLLBAR = 0,
- BACKGROUND = 1,
- DESKTOP = 1,
- ACTIVECAPTION = 2,
- INACTIVECAPTION = 3,
- MENU = 4,
- WINDOW = 5,
- WINDOWFRAME = 6,
- MENUTEXT = 7,
- WINDOWTEXT = 8,
- CAPTIONTEXT = 9,
- ACTIVEBORDER = 10,
- INACTIVEBORDER = 11,
- APPWORKSPACE = 12,
- HIGHLIGHT = 13,
- HIGHLIGHTTEXT = 14,
- BTNFACE = 15,
- THREEDFACE = 15,
- BTNSHADOW = 16,
- THREEDSHADOW = 16,
- GRAYTEXT = 17,
- BTNTEXT = 18,
- INACTIVECAPTIONTEXT = 19,
- BTNHIGHLIGHT = 20,
- TREEDHIGHLIGHT = 20,
- THREEDHILIGHT = 20,
- BTNHILIGHT = 20,
- THREEDDKSHADOW = 21,
- THREEDLIGHT = 22,
- INFOTEXT = 23,
- INFOBK = 24
- }
+ SCROLLBAR = 0,
+ BACKGROUND = 1,
+ DESKTOP = 1,
+ ACTIVECAPTION = 2,
+ INACTIVECAPTION = 3,
+ MENU = 4,
+ WINDOW = 5,
+ WINDOWFRAME = 6,
+ MENUTEXT = 7,
+ WINDOWTEXT = 8,
+ CAPTIONTEXT = 9,
+ ACTIVEBORDER = 10,
+ INACTIVEBORDER = 11,
+ APPWORKSPACE = 12,
+ HIGHLIGHT = 13,
+ HIGHLIGHTTEXT = 14,
+ BTNFACE = 15,
+ THREEDFACE = 15,
+ BTNSHADOW = 16,
+ THREEDSHADOW = 16,
+ GRAYTEXT = 17,
+ BTNTEXT = 18,
+ INACTIVECAPTIONTEXT = 19,
+ BTNHIGHLIGHT = 20,
+ TREEDHIGHLIGHT = 20,
+ THREEDHILIGHT = 20,
+ BTNHILIGHT = 20,
+ THREEDDKSHADOW = 21,
+ THREEDLIGHT = 22,
+ INFOTEXT = 23,
+ INFOBK = 24
+ }
///
/// Flags used with the Windows API (User32.dll):GetSystemMetrics(SystemMetric smIndex)
///
@@ -642,12 +642,12 @@ namespace GreenshotPlugin.UnmanagedHelpers {
DWMWA_FORCE_ICONIC_REPRESENTATION,
DWMWA_FLIP3D_POLICY,
DWMWA_EXTENDED_FRAME_BOUNDS, // This is the one we need for retrieving the Window size since Windows Vista
- DWMWA_HAS_ICONIC_BITMAP, // Since Windows 7
- DWMWA_DISALLOW_PEEK, // Since Windows 7
- DWMWA_EXCLUDED_FROM_PEEK, // Since Windows 7
- DWMWA_CLOAK, // Since Windows 8
- DWMWA_CLOAKED, // Since Windows 8
- DWMWA_FREEZE_REPRESENTATION, // Since Windows 8
+ DWMWA_HAS_ICONIC_BITMAP, // Since Windows 7
+ DWMWA_DISALLOW_PEEK, // Since Windows 7
+ DWMWA_EXCLUDED_FROM_PEEK, // Since Windows 7
+ DWMWA_CLOAK, // Since Windows 8
+ DWMWA_CLOAKED, // Since Windows 8
+ DWMWA_FREEZE_REPRESENTATION, // Since Windows 8
DWMWA_LAST
}
@@ -987,199 +987,199 @@ namespace GreenshotPlugin.UnmanagedHelpers {
Synchronize = 0x00100000
}
- ///
- /// See: http://msdn.microsoft.com/en-us/library/aa909766.aspx
- ///
- [Flags]
- [SuppressMessage("ReSharper", "InconsistentNaming")]
- public enum SoundFlags
- {
- SND_SYNC = 0x0000, // play synchronously (default)
- SND_ASYNC = 0x0001, // play asynchronously
- SND_NODEFAULT = 0x0002, // silence (!default) if sound not found
- SND_MEMORY = 0x0004, // pszSound points to a memory file
- SND_LOOP = 0x0008, // loop the sound until next sndPlaySound
- SND_NOSTOP = 0x0010, // don't stop any currently playing sound
- SND_NOWAIT = 0x00002000, // don't wait if the driver is busy
- SND_ALIAS = 0x00010000, // name is a registry alias
- SND_ALIAS_ID = 0x00110000, // alias is a predefined id
- SND_FILENAME = 0x00020000, // name is file name
- }
-
+ ///
+ /// See: http://msdn.microsoft.com/en-us/library/aa909766.aspx
+ ///
+ [Flags]
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ public enum SoundFlags
+ {
+ SND_SYNC = 0x0000, // play synchronously (default)
+ SND_ASYNC = 0x0001, // play asynchronously
+ SND_NODEFAULT = 0x0002, // silence (!default) if sound not found
+ SND_MEMORY = 0x0004, // pszSound points to a memory file
+ SND_LOOP = 0x0008, // loop the sound until next sndPlaySound
+ SND_NOSTOP = 0x0010, // don't stop any currently playing sound
+ SND_NOWAIT = 0x00002000, // don't wait if the driver is busy
+ SND_ALIAS = 0x00010000, // name is a registry alias
+ SND_ALIAS_ID = 0x00110000, // alias is a predefined id
+ SND_FILENAME = 0x00020000, // name is file name
+ }
+
///
/// Used by GDI32.GetDeviceCaps
/// See: http://msdn.microsoft.com/en-us/library/windows/desktop/dd144877%28v=vs.85%29.aspx
///
[SuppressMessage("ReSharper", "InconsistentNaming")]
public enum DeviceCaps {
- ///
- /// Device driver version
- ///
- DRIVERVERSION = 0,
- ///
- /// Device classification
- ///
- TECHNOLOGY = 2,
- ///
- /// Horizontal size in millimeters
- ///
- HORZSIZE = 4,
- ///
- /// Vertical size in millimeters
- ///
- VERTSIZE = 6,
- ///
- /// Horizontal width in pixels
- ///
- HORZRES = 8,
- ///
- /// Vertical height in pixels
- ///
- VERTRES = 10,
- ///
- /// Number of bits per pixel
- ///
- BITSPIXEL = 12,
- ///
- /// Number of planes
- ///
- PLANES = 14,
- ///
- /// Number of brushes the device has
- ///
- NUMBRUSHES = 16,
- ///
- /// Number of pens the device has
- ///
- NUMPENS = 18,
- ///
- /// Number of markers the device has
- ///
- NUMMARKERS = 20,
- ///
- /// Number of fonts the device has
- ///
- NUMFONTS = 22,
- ///
- /// Number of colors the device supports
- ///
- NUMCOLORS = 24,
- ///
- /// Size required for device descriptor
- ///
- PDEVICESIZE = 26,
- ///
- /// Curve capabilities
- ///
- CURVECAPS = 28,
- ///
- /// Line capabilities
- ///
- LINECAPS = 30,
- ///
- /// Polygonal capabilities
- ///
- POLYGONALCAPS = 32,
- ///
- /// Text capabilities
- ///
- TEXTCAPS = 34,
- ///
- /// Clipping capabilities
- ///
- CLIPCAPS = 36,
- ///
- /// Bitblt capabilities
- ///
- RASTERCAPS = 38,
- ///
- /// Length of the X leg
- ///
- ASPECTX = 40,
- ///
- /// Length of the Y leg
- ///
- ASPECTY = 42,
- ///
- /// Length of the hypotenuse
- ///
- ASPECTXY = 44,
- ///
- /// Shading and Blending caps
- ///
- SHADEBLENDCAPS = 45,
+ ///
+ /// Device driver version
+ ///
+ DRIVERVERSION = 0,
+ ///
+ /// Device classification
+ ///
+ TECHNOLOGY = 2,
+ ///
+ /// Horizontal size in millimeters
+ ///
+ HORZSIZE = 4,
+ ///
+ /// Vertical size in millimeters
+ ///
+ VERTSIZE = 6,
+ ///
+ /// Horizontal width in pixels
+ ///
+ HORZRES = 8,
+ ///
+ /// Vertical height in pixels
+ ///
+ VERTRES = 10,
+ ///
+ /// Number of bits per pixel
+ ///
+ BITSPIXEL = 12,
+ ///
+ /// Number of planes
+ ///
+ PLANES = 14,
+ ///
+ /// Number of brushes the device has
+ ///
+ NUMBRUSHES = 16,
+ ///
+ /// Number of pens the device has
+ ///
+ NUMPENS = 18,
+ ///
+ /// Number of markers the device has
+ ///
+ NUMMARKERS = 20,
+ ///
+ /// Number of fonts the device has
+ ///
+ NUMFONTS = 22,
+ ///
+ /// Number of colors the device supports
+ ///
+ NUMCOLORS = 24,
+ ///
+ /// Size required for device descriptor
+ ///
+ PDEVICESIZE = 26,
+ ///
+ /// Curve capabilities
+ ///
+ CURVECAPS = 28,
+ ///
+ /// Line capabilities
+ ///
+ LINECAPS = 30,
+ ///
+ /// Polygonal capabilities
+ ///
+ POLYGONALCAPS = 32,
+ ///
+ /// Text capabilities
+ ///
+ TEXTCAPS = 34,
+ ///
+ /// Clipping capabilities
+ ///
+ CLIPCAPS = 36,
+ ///
+ /// Bitblt capabilities
+ ///
+ RASTERCAPS = 38,
+ ///
+ /// Length of the X leg
+ ///
+ ASPECTX = 40,
+ ///
+ /// Length of the Y leg
+ ///
+ ASPECTY = 42,
+ ///
+ /// Length of the hypotenuse
+ ///
+ ASPECTXY = 44,
+ ///
+ /// Shading and Blending caps
+ ///
+ SHADEBLENDCAPS = 45,
- ///
- /// Logical pixels inch in X
- ///
- LOGPIXELSX = 88,
- ///
- /// Logical pixels inch in Y
- ///
- LOGPIXELSY = 90,
+ ///
+ /// Logical pixels inch in X
+ ///
+ LOGPIXELSX = 88,
+ ///
+ /// Logical pixels inch in Y
+ ///
+ LOGPIXELSY = 90,
- ///
- /// Number of entries in physical palette
- ///
- SIZEPALETTE = 104,
- ///
- /// Number of reserved entries in palette
- ///
- NUMRESERVED = 106,
- ///
- /// Actual color resolution
- ///
- COLORRES = 108,
+ ///
+ /// Number of entries in physical palette
+ ///
+ SIZEPALETTE = 104,
+ ///
+ /// Number of reserved entries in palette
+ ///
+ NUMRESERVED = 106,
+ ///
+ /// Actual color resolution
+ ///
+ COLORRES = 108,
- // Printing related DeviceCaps. These replace the appropriate Escapes
- ///
- /// Physical Width in device units
- ///
- PHYSICALWIDTH = 110,
- ///
- /// Physical Height in device units
- ///
- PHYSICALHEIGHT = 111,
- ///
- /// Physical Printable Area x margin
- ///
- PHYSICALOFFSETX = 112,
- ///
- /// Physical Printable Area y margin
- ///
- PHYSICALOFFSETY = 113,
- ///
- /// Scaling factor x
- ///
- SCALINGFACTORX = 114,
- ///
- /// Scaling factor y
- ///
- SCALINGFACTORY = 115,
+ // Printing related DeviceCaps. These replace the appropriate Escapes
+ ///
+ /// Physical Width in device units
+ ///
+ PHYSICALWIDTH = 110,
+ ///
+ /// Physical Height in device units
+ ///
+ PHYSICALHEIGHT = 111,
+ ///
+ /// Physical Printable Area x margin
+ ///
+ PHYSICALOFFSETX = 112,
+ ///
+ /// Physical Printable Area y margin
+ ///
+ PHYSICALOFFSETY = 113,
+ ///
+ /// Scaling factor x
+ ///
+ SCALINGFACTORX = 114,
+ ///
+ /// Scaling factor y
+ ///
+ SCALINGFACTORY = 115,
- ///
- /// Current vertical refresh rate of the display device (for displays only) in Hz
- ///
- VREFRESH = 116,
- ///
- /// Horizontal width of entire desktop in pixels
- ///
- DESKTOPVERTRES = 117,
- ///
- /// Vertical height of entire desktop in pixels
- ///
- DESKTOPHORZRES = 118,
- ///
- /// Preferred blt alignment
- ///
- BLTALIGNMENT = 119
- }
+ ///
+ /// Current vertical refresh rate of the display device (for displays only) in Hz
+ ///
+ VREFRESH = 116,
+ ///
+ /// Horizontal width of entire desktop in pixels
+ ///
+ DESKTOPVERTRES = 117,
+ ///
+ /// Vertical height of entire desktop in pixels
+ ///
+ DESKTOPHORZRES = 118,
+ ///
+ /// Preferred blt alignment
+ ///
+ BLTALIGNMENT = 119
+ }
///
/// Used for User32.SetWinEventHook
/// See: http://msdn.microsoft.com/en-us/library/windows/desktop/dd373640%28v=vs.85%29.aspx
///
- [SuppressMessage("ReSharper", "InconsistentNaming")]
+ [SuppressMessage("ReSharper", "InconsistentNaming"), Flags]
public enum WinEventHookFlags
{
WINEVENT_SKIPOWNTHREAD = 1,