FEATURE-731: Backport of the title detection code

This commit is contained in:
Robin 2016-09-03 23:32:24 +02:00
parent 4a0ec2448f
commit 1d0bdf23c1
17 changed files with 1080 additions and 447 deletions

View file

@ -295,7 +295,7 @@ namespace Greenshot {
private void UpdateClipboardFormatDescriptions() { private void UpdateClipboardFormatDescriptions() {
foreach(ListViewItem item in listview_clipboardformats.Items) { foreach(ListViewItem item in listview_clipboardformats.Items) {
ClipboardFormat cf = (ClipboardFormat) item.Tag; ClipboardFormat cf = (ClipboardFormat) item.Tag;
item.Text = Language.Translate(cf); item.Text = Language.Translate(cf);
} }
} }
@ -592,12 +592,12 @@ namespace Greenshot {
CheckDestinationSettings(); CheckDestinationSettings();
} }
protected override void OnFieldsFilled() { protected override void OnFieldsFilled() {
// the color radio button is not actually bound to a setting, but checked when monochrome/grayscale are not checked // the color radio button is not actually bound to a setting, but checked when monochrome/grayscale are not checked
if(!radioBtnGrayScale.Checked && !radioBtnMonochrome.Checked) { if(!radioBtnGrayScale.Checked && !radioBtnMonochrome.Checked) {
radioBtnColorPrint.Checked = true; radioBtnColorPrint.Checked = true;
} }
} }
/// <summary> /// <summary>
/// Set the enable state of the expert settings /// Set the enable state of the expert settings

View file

@ -65,7 +65,7 @@ namespace GreenshotJiraPlugin.Forms {
{ {
if (!_jiraConnector.IsLoggedIn) if (!_jiraConnector.IsLoggedIn)
{ {
await _jiraConnector.Login(); await _jiraConnector.LoginAsync();
} }
} }
catch (Exception e) catch (Exception e)

View file

@ -78,12 +78,19 @@
<Compile Include="Forms\SettingsForm.Designer.cs"> <Compile Include="Forms\SettingsForm.Designer.cs">
<DependentUpon>SettingsForm.cs</DependentUpon> <DependentUpon>SettingsForm.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Hooking\TitleChangeEventArgs.cs" />
<Compile Include="Hooking\TitleChangeEventDelegate.cs" />
<Compile Include="Hooking\WindowsEventHook.cs" />
<Compile Include="Hooking\WindowsTitleMonitor.cs" />
<Compile Include="IssueTypeBitmapCache.cs" /> <Compile Include="IssueTypeBitmapCache.cs" />
<Compile Include="JiraConnector.cs" /> <Compile Include="JiraConnector.cs" />
<Compile Include="JiraConfiguration.cs" /> <Compile Include="JiraConfiguration.cs" />
<Compile Include="JiraDestination.cs" /> <Compile Include="JiraDestination.cs" />
<Compile Include="JiraDetails.cs" />
<Compile Include="JiraEventArgs.cs" />
<Compile Include="JiraEventTypes.cs" />
<Compile Include="JiraMonitor.cs" />
<Compile Include="JiraPlugin.cs" /> <Compile Include="JiraPlugin.cs" />
<Compile Include="JiraUtils.cs" />
<Compile Include="LanguageKeys.cs" /> <Compile Include="LanguageKeys.cs" />
<Compile Include="Log4NetLogger.cs" /> <Compile Include="Log4NetLogger.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
using System;
namespace GreenshotJiraPlugin.Hooking
{
/// <summary>
/// Event arguments for the TitleChangeEvent
/// </summary>
public class TitleChangeEventArgs : EventArgs
{
/// <summary>
/// HWnd of the window which has a changed title
/// </summary>
public IntPtr HWnd
{
get;
set;
}
/// <summary>
/// Title which is changed
/// </summary>
public string Title
{
get;
set;
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
namespace GreenshotJiraPlugin.Hooking
{
/// <summary>
/// Delegate for the title change event
/// </summary>
/// <param name="eventArgs"></param>
public delegate void TitleChangeEventDelegate(TitleChangeEventArgs eventArgs);
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using GreenshotPlugin.UnmanagedHelpers;
namespace GreenshotJiraPlugin.Hooking
{
/// <summary>
/// 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.
/// </summary>
public class WindowsEventHook : IDisposable
{
private readonly WinEventDelegate _winEventHandler;
private GCHandle _gcHandle;
/// <summary>
/// Used with Register hook
/// </summary>
/// <param name="eventType"></param>
/// <param name="hwnd"></param>
/// <param name="idObject"></param>
/// <param name="idChild"></param>
/// <param name="dwEventThread"></param>
/// <param name="dwmsEventTime"></param>
public delegate void WinEventHandler(WinEvent eventType, IntPtr hwnd, EventObjects idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
/// <summary>
/// Create a WindowsEventHook object
/// </summary>
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);
/// <summary>
/// Used with SetWinEventHook
/// </summary>
/// <param name="hWinEventHook"></param>
/// <param name="eventType"></param>
/// <param name="hwnd"></param>
/// <param name="idObject"></param>
/// <param name="idChild"></param>
/// <param name="dwEventThread"></param>
/// <param name="dwmsEventTime"></param>
private delegate void WinEventDelegate(IntPtr hWinEventHook, WinEvent eventType, IntPtr hwnd, EventObjects idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
#endregion
private readonly IDictionary<IntPtr, WinEventHandler> _winEventHandlers = new Dictionary<IntPtr, WinEventHandler>();
/// <summary>
/// Are hooks active?
/// </summary>
public bool IsHooked => _winEventHandlers.Count > 0;
/// <summary>
/// Hook a WinEvent
/// </summary>
/// <param name="winEvent"></param>
/// <param name="winEventHandler"></param>
/// <returns>true if success</returns>
public void Hook(WinEvent winEvent, WinEventHandler winEventHandler)
{
Hook(winEvent, winEvent, winEventHandler);
}
/// <summary>
/// Hook a WinEvent
/// </summary>
/// <param name="winEventStart"></param>
/// <param name="winEventEnd"></param>
/// <param name="winEventHandler"></param>
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);
}
/// <summary>
/// Remove all hooks
/// </summary>
private void Unhook()
{
foreach (var hookPtr in _winEventHandlers.Keys)
{
if (hookPtr != IntPtr.Zero)
{
UnhookWinEvent(hookPtr);
}
}
_winEventHandlers.Clear();
_gcHandle.Free();
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Unhook();
}
/// <summary>
/// Call the WinEventHandler for this event
/// </summary>
/// <param name="hWinEventHook"></param>
/// <param name="eventType"></param>
/// <param name="hWnd"></param>
/// <param name="idObject"></param>
/// <param name="idChild"></param>
/// <param name="dwEventThread"></param>
/// <param name="dwmsEventTime"></param>
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);
}
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
using System;
using GreenshotPlugin.Core;
using GreenshotPlugin.UnmanagedHelpers;
namespace GreenshotJiraPlugin.Hooking
{
/// <summary>
/// Monitor all title changes
/// </summary>
public sealed class WindowsTitleMonitor : IDisposable
{
private WindowsEventHook _hook;
private readonly object _lockObject = new object();
// ReSharper disable once InconsistentNaming
private event TitleChangeEventDelegate _titleChangeEvent;
/// <summary>
/// Add / remove event handler to the title monitor
/// </summary>
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;
}
}
}
}
}
/// <summary>
/// WinEventDelegate for the creation & destruction
/// </summary>
/// <param name="eventType"></param>
/// <param name="hWnd"></param>
/// <param name="idObject"></param>
/// <param name="idChild"></param>
/// <param name="dwEventThread"></param>
/// <param name="dwmsEventTime"></param>
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
/// <summary>
/// Dispose the underlying hook
/// </summary>
public void Dispose(bool disposing)
{
if (_disposedValue)
{
return;
}
lock (_lockObject)
{
_hook?.Dispose();
}
_disposedValue = true;
}
/// <summary>
/// Make sure the finalizer disposes the underlying hook
/// </summary>
~WindowsTitleMonitor()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(false);
}
/// <summary>
/// Dispose the underlying hook
/// </summary>
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}

View file

@ -47,12 +47,13 @@ namespace GreenshotJiraPlugin {
private DateTimeOffset _loggedInTime = DateTimeOffset.MinValue; private DateTimeOffset _loggedInTime = DateTimeOffset.MinValue;
private bool _loggedIn; private bool _loggedIn;
private readonly int _timeout; private readonly int _timeout;
private readonly object _lock = new object();
private string _url;
private JiraApi _jiraApi; private JiraApi _jiraApi;
private IssueTypeBitmapCache _issueTypeBitmapCache; private IssueTypeBitmapCache _issueTypeBitmapCache;
private static readonly SvgBitmapHttpContentConverter SvgBitmapHttpContentConverterInstance = new SvgBitmapHttpContentConverter(); private static readonly SvgBitmapHttpContentConverter SvgBitmapHttpContentConverterInstance = new SvgBitmapHttpContentConverter();
/// <summary>
/// Initialize some basic stuff, in the case the SVG to bitmap converter
/// </summary>
static JiraConnector() static JiraConnector()
{ {
if (HttpExtensionsGlobals.HttpContentConverters.All(x => x.GetType() != typeof(SvgBitmapHttpContentConverter))) if (HttpExtensionsGlobals.HttpContentConverters.All(x => x.GetType() != typeof(SvgBitmapHttpContentConverter)))
@ -72,46 +73,48 @@ namespace GreenshotJiraPlugin {
} }
/// <summary>
/// Dispose, logout the users
/// </summary>
public void Dispose() { public void Dispose() {
if (_jiraApi != null) if (_jiraApi != null)
{ {
Task.Run(async () => await Logout()).Wait(); Task.Run(async () => await LogoutAsync()).Wait();
} }
} }
/// <summary>
/// Constructor
/// </summary>
public JiraConnector() public JiraConnector()
{ {
_url = JiraConfig.Url.Replace(DefaultPostfix, ""); JiraConfig.Url = JiraConfig.Url.Replace(DefaultPostfix, "");
_timeout = JiraConfig.Timeout; _timeout = JiraConfig.Timeout;
} }
/// <summary>
/// Access the jira monitor
/// </summary>
public JiraMonitor Monitor { get; private set; }
/// <summary> /// <summary>
/// Internal login which catches the exceptions /// Internal login which catches the exceptions
/// </summary> /// </summary>
/// <returns>true if login was done sucessfully</returns> /// <returns>true if login was done sucessfully</returns>
private async Task<bool> DoLogin(string user, string password) private async Task<bool> DoLoginAsync(string user, string password)
{ {
lock (_lock) if (string.IsNullOrEmpty(user) || string.IsNullOrEmpty(password))
{ {
if (_url.EndsWith("wsdl")) return false;
{
_url = _url.Replace(DefaultPostfix, "");
}
if (_jiraApi == null)
{
// recreate the service with the new url
_jiraApi = new JiraApi(new Uri(_url));
_issueTypeBitmapCache = new IssueTypeBitmapCache(_jiraApi);
}
} }
_jiraApi = new JiraApi(new Uri(JiraConfig.Url));
_issueTypeBitmapCache = new IssueTypeBitmapCache(_jiraApi);
Monitor = new JiraMonitor();
await Monitor.AddJiraInstanceAsync(_jiraApi);
LoginInfo loginInfo; LoginInfo loginInfo;
try try
{ {
loginInfo = await _jiraApi.StartSessionAsync(user, password); loginInfo = await _jiraApi.StartSessionAsync(user, password);
// Worked, store the url in the configuration
JiraConfig.Url = _url;
IniConfig.Save();
} }
catch (Exception) catch (Exception)
{ {
@ -120,17 +123,21 @@ namespace GreenshotJiraPlugin {
return loginInfo != null; return loginInfo != null;
} }
public async Task Login() { /// <summary>
await Logout(); /// Use the credentials dialog, this will show if there are not correct credentials.
/// If there are credentials, call the real login.
/// </summary>
/// <returns>Task</returns>
public async Task LoginAsync() {
await LogoutAsync();
try { try {
// Get the system name, so the user knows where to login to // Get the system name, so the user knows where to login to
string systemName = _url.Replace(DefaultPostfix,""); var credentialsDialog = new CredentialsDialog(JiraConfig.Url)
var credentialsDialog = new CredentialsDialog(systemName)
{ {
Name = null Name = null
}; };
while (credentialsDialog.Show(credentialsDialog.Name) == DialogResult.OK) { while (credentialsDialog.Show(credentialsDialog.Name) == DialogResult.OK) {
if (await DoLogin(credentialsDialog.Name, credentialsDialog.Password)) { if (await DoLoginAsync(credentialsDialog.Name, credentialsDialog.Password)) {
if (credentialsDialog.SaveChecked) { if (credentialsDialog.SaveChecked) {
credentialsDialog.Confirm(true); credentialsDialog.Confirm(true);
} }
@ -159,9 +166,10 @@ namespace GreenshotJiraPlugin {
/// <summary> /// <summary>
/// End the session, if there was one /// End the session, if there was one
/// </summary> /// </summary>
public async Task Logout() { public async Task LogoutAsync() {
if (_jiraApi != null && _loggedIn) if (_jiraApi != null && _loggedIn)
{ {
Monitor.Dispose();
await _jiraApi.EndSessionAsync(); await _jiraApi.EndSessionAsync();
_loggedIn = false; _loggedIn = false;
} }
@ -172,14 +180,14 @@ namespace GreenshotJiraPlugin {
/// Do not use ConfigureAwait to call this, as it will move await from the UI thread. /// Do not use ConfigureAwait to call this, as it will move await from the UI thread.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
private async Task CheckCredentials() { private async Task CheckCredentialsAsync() {
if (_loggedIn) { if (_loggedIn) {
if (_loggedInTime.AddMinutes(_timeout-1).CompareTo(DateTime.Now) < 0) { if (_loggedInTime.AddMinutes(_timeout-1).CompareTo(DateTime.Now) < 0) {
await Logout(); await LogoutAsync();
await Login(); await LoginAsync();
} }
} else { } else {
await Login(); await LoginAsync();
} }
} }
@ -189,7 +197,7 @@ namespace GreenshotJiraPlugin {
/// <returns>List with filters</returns> /// <returns>List with filters</returns>
public async Task<IList<Filter>> GetFavoriteFiltersAsync() public async Task<IList<Filter>> GetFavoriteFiltersAsync()
{ {
await CheckCredentials(); await CheckCredentialsAsync();
return await _jiraApi.GetFavoriteFiltersAsync().ConfigureAwait(false); return await _jiraApi.GetFavoriteFiltersAsync().ConfigureAwait(false);
} }
@ -200,7 +208,7 @@ namespace GreenshotJiraPlugin {
/// <returns>Issue</returns> /// <returns>Issue</returns>
public async Task<Issue> GetIssueAsync(string issueKey) public async Task<Issue> GetIssueAsync(string issueKey)
{ {
await CheckCredentials(); await CheckCredentialsAsync();
try try
{ {
return await _jiraApi.GetIssueAsync(issueKey).ConfigureAwait(false); return await _jiraApi.GetIssueAsync(issueKey).ConfigureAwait(false);
@ -220,7 +228,7 @@ namespace GreenshotJiraPlugin {
/// <returns></returns> /// <returns></returns>
public async Task AttachAsync(string issueKey, IBinaryContainer content, CancellationToken cancellationToken = default(CancellationToken)) public async Task AttachAsync(string issueKey, IBinaryContainer content, CancellationToken cancellationToken = default(CancellationToken))
{ {
await CheckCredentials(); await CheckCredentialsAsync();
using (var memoryStream = new MemoryStream()) using (var memoryStream = new MemoryStream())
{ {
content.WriteToStream(memoryStream); content.WriteToStream(memoryStream);
@ -238,7 +246,7 @@ namespace GreenshotJiraPlugin {
/// <param name="cancellationToken">CancellationToken</param> /// <param name="cancellationToken">CancellationToken</param>
public async Task AddCommentAsync(string issueKey, string body, string visibility = null, CancellationToken cancellationToken = default(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); await _jiraApi.AddCommentAsync(issueKey, body, visibility, cancellationToken).ConfigureAwait(false);
} }
@ -250,7 +258,7 @@ namespace GreenshotJiraPlugin {
/// <returns></returns> /// <returns></returns>
public async Task<IList<Issue>> SearchAsync(Filter filter, CancellationToken cancellationToken = default(CancellationToken)) public async Task<IList<Issue>> 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); var searchResult = await _jiraApi.SearchAsync(filter.Jql, 20, new[] { "summary", "reporter", "assignee", "created", "issuetype" }, cancellationToken).ConfigureAwait(false);
return searchResult.Issues; return searchResult.Issues;
} }
@ -266,8 +274,14 @@ namespace GreenshotJiraPlugin {
return await _issueTypeBitmapCache.GetOrCreateAsync(issue.Fields.IssueType, cancellationToken).ConfigureAwait(false); return await _issueTypeBitmapCache.GetOrCreateAsync(issue.Fields.IssueType, cancellationToken).ConfigureAwait(false);
} }
/// <summary>
/// Get the base uri
/// </summary>
public Uri JiraBaseUri => _jiraApi.JiraBaseUri; public Uri JiraBaseUri => _jiraApi.JiraBaseUri;
/// <summary>
/// Is the user "logged in?
/// </summary>
public bool IsLoggedIn => _loggedIn; public bool IsLoggedIn => _loggedIn;
} }
} }

View file

@ -61,7 +61,7 @@ namespace GreenshotJiraPlugin {
return Language.GetString("jira", LangKey.upload_menu_item); return Language.GetString("jira", LangKey.upload_menu_item);
} }
// Format the title of this destination // 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<IDestination> DynamicDestinations() { public override IEnumerable<IDestination> DynamicDestinations()
if (JiraPlugin.Instance.CurrentJiraConnector == null || !JiraPlugin.Instance.CurrentJiraConnector.IsLoggedIn) { {
var jiraConnector = JiraPlugin.Instance.CurrentJiraConnector;
if (jiraConnector == null || !jiraConnector.IsLoggedIn) {
yield break; yield break;
} }
var issues = JiraUtils.GetCurrentJirasAsync().Result; foreach(var jiraDetails in jiraConnector.Monitor.RecentJiras)
if (issues != null) { {
foreach(var jiraIssue in issues) { yield return new JiraDestination(_jiraPlugin,jiraDetails.JiraIssue);
yield return new JiraDestination(_jiraPlugin, jiraIssue);
}
} }
} }

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
using System;
using Dapplo.Jira.Entities;
namespace GreenshotJiraPlugin
{
public class JiraDetails : IComparable<JiraDetails>
{
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);
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
using System;
namespace GreenshotJiraPlugin
{
public class JiraEventArgs : EventArgs
{
public JiraEventTypes EventType
{
get;
set;
}
public JiraDetails Details
{
get;
set;
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
namespace GreenshotJiraPlugin
{
public enum JiraEventTypes
{
OrderChanged,
DetectedNewJiraIssue
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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
{
/// <summary>
/// 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!
/// </summary>
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<JiraApi> _jiraInstances = new List<JiraApi>();
private readonly IDictionary<string, JiraApi> _projectJiraApiMap = new Dictionary<string, JiraApi>();
private readonly int _maxEntries;
private IDictionary<string, JiraDetails> _recentJiras = new Dictionary<string, JiraDetails>();
/// <summary>
/// Register to this event to get events when new jira issues are detected
/// </summary>
public event EventHandler<JiraEventArgs> JiraEvent;
public JiraMonitor(int maxEntries = 40)
{
_maxEntries = maxEntries;
_monitor = new WindowsTitleMonitor();
_monitor.TitleChangeEvent += MonitorTitleChangeEvent;
}
#region Dispose
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Dispose all managed resources
/// </summary>
/// <param name="disposing">when true is passed all managed resources are disposed.</param>
protected void Dispose(bool disposing)
{
if (!disposing)
{
return;
}
// free managed resources
_monitor.TitleChangeEvent -= MonitorTitleChangeEvent;
_monitor.Dispose();
// free native resources if there are any.
}
#endregion
/// <summary>
/// Retrieve the API belonging to a JiraDetails
/// </summary>
/// <param name="jiraDetails"></param>
/// <returns>JiraAPI</returns>
public JiraApi GetJiraApiForKey(JiraDetails jiraDetails)
{
return _projectJiraApiMap[jiraDetails.ProjectKey];
}
/// <summary>
/// Get the "list" of recently seen Jiras
/// </summary>
public IEnumerable<JiraDetails> RecentJiras =>
(from jiraDetails in _recentJiras.Values
orderby jiraDetails.SeenAt descending
select jiraDetails);
/// <summary>
/// Check if this monitor has active instances
/// </summary>
public bool HasJiraInstances => _jiraInstances.Count > 0;
/// <summary>
/// Add an instance of a JIRA system
/// </summary>
/// <param name="jiraInstance"></param>
/// <param name="token"></param>
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);
}
}
}
}
/// <summary>
/// This method will update details, like the title, and send an event to registed listeners of the JiraEvent
/// </summary>
/// <param name="jiraDetails">Contains the jira key to retrieve the title (XYZ-1234)</param>
/// <returns>Task</returns>
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);
}
}
/// <summary>
/// Handle title changes, check for JIRA
/// </summary>
/// <param name="eventArgs"></param>
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<JiraDetails> clonedList = new List<JiraDetails>(_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);
}
}
}
}

View file

@ -33,9 +33,9 @@ namespace GreenshotJiraPlugin {
/// </summary> /// </summary>
public class JiraPlugin : IGreenshotPlugin { public class JiraPlugin : IGreenshotPlugin {
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(JiraPlugin)); private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(JiraPlugin));
private JiraConnector _jiraConnector;
private JiraConfiguration _config; private JiraConfiguration _config;
private static JiraPlugin _instance; private static JiraPlugin _instance;
private JiraConnector _jiraConnector;
public void Dispose() { public void Dispose() {
Dispose(true); Dispose(true);
@ -44,9 +44,9 @@ namespace GreenshotJiraPlugin {
protected void Dispose(bool disposing) { protected void Dispose(bool disposing) {
if (disposing) { if (disposing) {
if (_jiraConnector != null) { if (JiraConnector != null) {
_jiraConnector.Dispose(); JiraConnector.Dispose();
_jiraConnector = null; JiraConnector = null;
} }
} }
} }
@ -66,9 +66,23 @@ namespace GreenshotJiraPlugin {
} }
//Needed for a fail-fast //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; }
}
/// <summary> /// <summary>
/// Implementation of the IGreenshotPlugin.Initialize /// Implementation of the IGreenshotPlugin.Initialize
@ -80,14 +94,15 @@ namespace GreenshotJiraPlugin {
// Register configuration (don't need the configuration itself) // Register configuration (don't need the configuration itself)
_config = IniConfig.GetIniSection<JiraConfiguration>(); _config = IniConfig.GetIniSection<JiraConfiguration>();
LogSettings.RegisterDefaultLogger<Log4NetLogger>(); LogSettings.RegisterDefaultLogger<Log4NetLogger>();
return true; return true;
} }
public void Shutdown() { public void Shutdown() {
Log.Debug("Jira Plugin 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; string url = _config.Url;
if (ShowConfigDialog()) { if (ShowConfigDialog()) {
// check for re-login // 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)) { if (!url.Equals(_config.Url)) {
Task.Run(async () => Task.Run(async () =>
{ {
await _jiraConnector.Logout(); await JiraConnector.LogoutAsync();
await _jiraConnector.Login(); await JiraConnector.LoginAsync();
}); });
} }
} }

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 {
/// <summary>
/// Description of JiraUtils.
/// </summary>
public static class JiraUtils {
private static readonly Regex JiraKeyRegex = new Regex(@"/browse/([A-Z0-9]+\-[0-9]+)");
private static readonly JiraConfiguration Config = IniConfig.GetIniSection<JiraConfiguration>();
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(JiraUtils));
public static async Task<IList<Issue>> GetCurrentJirasAsync() {
// Make sure we suppress the login
var jirakeys = new List<string>();
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<Issue>();
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;
}
}
}

View file

@ -59,10 +59,10 @@ namespace GreenshotPlugin.Core {
/// <summary>Encapsulates dialog functionality from the Credential Management API.</summary> /// <summary>Encapsulates dialog functionality from the Credential Management API.</summary>
public sealed class CredentialsDialog { public sealed class CredentialsDialog {
[DllImport("gdi32.dll", SetLastError=true)] [DllImport("gdi32.dll", SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DeleteObject(IntPtr hObject); private static extern bool DeleteObject(IntPtr hObject);
/// <summary>The only valid bitmap height (in pixels) of a user-defined banner.</summary> /// <summary>The only valid bitmap height (in pixels) of a user-defined banner.</summary>
private const int ValidBannerHeight = 60; private const int ValidBannerHeight = 60;
/// <summary>The only valid bitmap width (in pixels) of a user-defined banner.</summary> /// <summary>The only valid bitmap width (in pixels) of a user-defined banner.</summary>
@ -102,65 +102,25 @@ namespace GreenshotPlugin.Core {
Banner = banner; Banner = banner;
} }
private bool _alwaysDisplay;
/// <summary> /// <summary>
/// Gets or sets if the dialog will be shown even if the credentials /// Gets or sets if the dialog will be shown even if the credentials
/// can be returned from an existing credential in the credential manager. /// can be returned from an existing credential in the credential manager.
/// </summary> /// </summary>
public bool AlwaysDisplay { public bool AlwaysDisplay { get; set; }
get {
return _alwaysDisplay;
}
set {
_alwaysDisplay = value;
}
}
private bool _excludeCertificates = true;
/// <summary>Gets or sets if the dialog is populated with name/password only.</summary> /// <summary>Gets or sets if the dialog is populated with name/password only.</summary>
public bool ExcludeCertificates { public bool ExcludeCertificates { get; set; } = true;
get {
return _excludeCertificates;
}
set {
_excludeCertificates = value;
}
}
private bool _persist = true;
/// <summary>Gets or sets if the credentials are to be persisted in the credential manager.</summary> /// <summary>Gets or sets if the credentials are to be persisted in the credential manager.</summary>
public bool Persist { public bool Persist { get; set; } = true;
get {
return _persist;
}
set {
_persist = value;
}
}
private bool _incorrectPassword;
/// <summary>Gets or sets if the incorrect password balloontip needs to be shown. Introduced AFTER Windows XP</summary>Gets></summary> /// <summary>Gets or sets if the incorrect password balloontip needs to be shown. Introduced AFTER Windows XP</summary>Gets></summary>
public bool IncorrectPassword { public bool IncorrectPassword { get; set; }
get {
return _incorrectPassword;
}
set {
_incorrectPassword = value;
}
}
private bool _keepName;
/// <summary>Gets or sets if the name is read-only.</summary> /// <summary>Gets or sets if the name is read-only.</summary>
public bool KeepName { public bool KeepName { get; set; }
get {
return _keepName;
}
set {
_keepName = value;
}
}
private string _name = String.Empty; private string _name = string.Empty;
/// <summary>Gets or sets the name for the credentials.</summary> /// <summary>Gets or sets the name for the credentials.</summary>
public string Name { public string Name {
get { get {
@ -169,18 +129,18 @@ namespace GreenshotPlugin.Core {
set { set {
if (value != null) { if (value != null) {
if (value.Length > CREDUI.MAX_USERNAME_LENGTH) { if (value.Length > CREDUI.MAX_USERNAME_LENGTH) {
string message = String.Format( string message = string.Format(
Thread.CurrentThread.CurrentUICulture, Thread.CurrentThread.CurrentUICulture,
"The name has a maximum length of {0} characters.", "The name has a maximum length of {0} characters.",
CREDUI.MAX_USERNAME_LENGTH); CREDUI.MAX_USERNAME_LENGTH);
throw new ArgumentException(message, "Name"); throw new ArgumentException(message, nameof(Name));
} }
} }
_name = value; _name = value;
} }
} }
private string _password = String.Empty; private string _password = string.Empty;
/// <summary>Gets or sets the password for the credentials.</summary> /// <summary>Gets or sets the password for the credentials.</summary>
public string Password { public string Password {
get { get {
@ -189,41 +149,25 @@ namespace GreenshotPlugin.Core {
set { set {
if (value != null) { if (value != null) {
if (value.Length > CREDUI.MAX_PASSWORD_LENGTH) { if (value.Length > CREDUI.MAX_PASSWORD_LENGTH) {
string message = String.Format( string message = string.Format(
Thread.CurrentThread.CurrentUICulture, Thread.CurrentThread.CurrentUICulture,
"The password has a maximum length of {0} characters.", "The password has a maximum length of {0} characters.",
CREDUI.MAX_PASSWORD_LENGTH); CREDUI.MAX_PASSWORD_LENGTH);
throw new ArgumentException(message, "Password"); throw new ArgumentException(message, nameof(Password));
} }
} }
_password = value; _password = value;
} }
} }
private bool _saveChecked;
/// <summary>Gets or sets if the save checkbox status.</summary> /// <summary>Gets or sets if the save checkbox status.</summary>
public bool SaveChecked { public bool SaveChecked { get; set; }
get {
return _saveChecked;
}
set {
_saveChecked = value;
}
}
private bool _saveDisplayed = true;
/// <summary>Gets or sets if the save checkbox is displayed.</summary> /// <summary>Gets or sets if the save checkbox is displayed.</summary>
/// <remarks>This value only has effect if Persist is true.</remarks> /// <remarks>This value only has effect if Persist is true.</remarks>
public bool SaveDisplayed { public bool SaveDisplayed { get; set; } = true;
get {
return _saveDisplayed;
}
set {
_saveDisplayed = value;
}
}
private string _target = String.Empty; private string _target = string.Empty;
/// <summary>Gets or sets the name of the target for the credentials, typically a server name.</summary> /// <summary>Gets or sets the name of the target for the credentials, typically a server name.</summary>
public string Target { public string Target {
get { get {
@ -232,18 +176,19 @@ namespace GreenshotPlugin.Core {
set { set {
if (value == null) { if (value == null) {
throw new ArgumentException("The target cannot be a null value.", "Target"); 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, Thread.CurrentThread.CurrentUICulture,
"The target has a maximum length of {0} characters.", "The target has a maximum length of {0} characters.",
CREDUI.MAX_GENERIC_TARGET_LENGTH); CREDUI.MAX_GENERIC_TARGET_LENGTH);
throw new ArgumentException(message, "Target"); throw new ArgumentException(message, nameof(Target));
} }
_target = value; _target = value;
} }
} }
private string _caption = String.Empty; private string _caption = string.Empty;
/// <summary>Gets or sets the caption of the dialog.</summary> /// <summary>Gets or sets the caption of the dialog.</summary>
/// <remarks>A null value will cause a system default caption to be used.</remarks> /// <remarks>A null value will cause a system default caption to be used.</remarks>
public string Caption { public string Caption {
@ -253,18 +198,18 @@ namespace GreenshotPlugin.Core {
set { set {
if (value != null) { if (value != null) {
if (value.Length > CREDUI.MAX_CAPTION_LENGTH) { if (value.Length > CREDUI.MAX_CAPTION_LENGTH) {
string message = String.Format( string message = string.Format(
Thread.CurrentThread.CurrentUICulture, Thread.CurrentThread.CurrentUICulture,
"The caption has a maximum length of {0} characters.", "The caption has a maximum length of {0} characters.",
CREDUI.MAX_CAPTION_LENGTH); CREDUI.MAX_CAPTION_LENGTH);
throw new ArgumentException(message, "Caption"); throw new ArgumentException(message, nameof(Caption));
} }
} }
_caption = value; _caption = value;
} }
} }
private string _message = String.Empty; private string _message = string.Empty;
/// <summary>Gets or sets the message of the dialog.</summary> /// <summary>Gets or sets the message of the dialog.</summary>
/// <remarks>A null value will cause a system default message to be used.</remarks> /// <remarks>A null value will cause a system default message to be used.</remarks>
public string Message { public string Message {
@ -274,11 +219,11 @@ namespace GreenshotPlugin.Core {
set { set {
if (value != null) { if (value != null) {
if (value.Length > CREDUI.MAX_MESSAGE_LENGTH) { if (value.Length > CREDUI.MAX_MESSAGE_LENGTH) {
string message = String.Format( string message = string.Format(
Thread.CurrentThread.CurrentUICulture, Thread.CurrentThread.CurrentUICulture,
"The message has a maximum length of {0} characters.", "The message has a maximum length of {0} characters.",
CREDUI.MAX_MESSAGE_LENGTH); CREDUI.MAX_MESSAGE_LENGTH);
throw new ArgumentException(message, "Message"); throw new ArgumentException(message, nameof(Message));
} }
} }
_message = value; _message = value;
@ -295,10 +240,10 @@ namespace GreenshotPlugin.Core {
set { set {
if (value != null) { if (value != null) {
if (value.Width != ValidBannerWidth) { 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) { 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; _banner = value;

View file

@ -112,7 +112,7 @@ namespace GreenshotPlugin.UnmanagedHelpers {
WS_EX_LAYOUTRTL = 0x00400000, // Right to left mirroring WS_EX_LAYOUTRTL = 0x00400000, // Right to left mirroring
WS_EX_COMPOSITED = 0x02000000, WS_EX_COMPOSITED = 0x02000000,
WS_EX_NOACTIVATE = 0x08000000 WS_EX_NOACTIVATE = 0x08000000
} }
[Flags] [Flags]
[SuppressMessage("ReSharper", "InconsistentNaming")] [SuppressMessage("ReSharper", "InconsistentNaming")]
@ -202,38 +202,38 @@ namespace GreenshotPlugin.UnmanagedHelpers {
[SuppressMessage("ReSharper", "InconsistentNaming")] [SuppressMessage("ReSharper", "InconsistentNaming")]
public enum SYSCOLOR public enum SYSCOLOR
{ {
SCROLLBAR = 0, SCROLLBAR = 0,
BACKGROUND = 1, BACKGROUND = 1,
DESKTOP = 1, DESKTOP = 1,
ACTIVECAPTION = 2, ACTIVECAPTION = 2,
INACTIVECAPTION = 3, INACTIVECAPTION = 3,
MENU = 4, MENU = 4,
WINDOW = 5, WINDOW = 5,
WINDOWFRAME = 6, WINDOWFRAME = 6,
MENUTEXT = 7, MENUTEXT = 7,
WINDOWTEXT = 8, WINDOWTEXT = 8,
CAPTIONTEXT = 9, CAPTIONTEXT = 9,
ACTIVEBORDER = 10, ACTIVEBORDER = 10,
INACTIVEBORDER = 11, INACTIVEBORDER = 11,
APPWORKSPACE = 12, APPWORKSPACE = 12,
HIGHLIGHT = 13, HIGHLIGHT = 13,
HIGHLIGHTTEXT = 14, HIGHLIGHTTEXT = 14,
BTNFACE = 15, BTNFACE = 15,
THREEDFACE = 15, THREEDFACE = 15,
BTNSHADOW = 16, BTNSHADOW = 16,
THREEDSHADOW = 16, THREEDSHADOW = 16,
GRAYTEXT = 17, GRAYTEXT = 17,
BTNTEXT = 18, BTNTEXT = 18,
INACTIVECAPTIONTEXT = 19, INACTIVECAPTIONTEXT = 19,
BTNHIGHLIGHT = 20, BTNHIGHLIGHT = 20,
TREEDHIGHLIGHT = 20, TREEDHIGHLIGHT = 20,
THREEDHILIGHT = 20, THREEDHILIGHT = 20,
BTNHILIGHT = 20, BTNHILIGHT = 20,
THREEDDKSHADOW = 21, THREEDDKSHADOW = 21,
THREEDLIGHT = 22, THREEDLIGHT = 22,
INFOTEXT = 23, INFOTEXT = 23,
INFOBK = 24 INFOBK = 24
} }
/// <summary> /// <summary>
/// Flags used with the Windows API (User32.dll):GetSystemMetrics(SystemMetric smIndex) /// Flags used with the Windows API (User32.dll):GetSystemMetrics(SystemMetric smIndex)
/// ///
@ -642,12 +642,12 @@ namespace GreenshotPlugin.UnmanagedHelpers {
DWMWA_FORCE_ICONIC_REPRESENTATION, DWMWA_FORCE_ICONIC_REPRESENTATION,
DWMWA_FLIP3D_POLICY, DWMWA_FLIP3D_POLICY,
DWMWA_EXTENDED_FRAME_BOUNDS, // This is the one we need for retrieving the Window size since Windows Vista 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_HAS_ICONIC_BITMAP, // Since Windows 7
DWMWA_DISALLOW_PEEK, // Since Windows 7 DWMWA_DISALLOW_PEEK, // Since Windows 7
DWMWA_EXCLUDED_FROM_PEEK, // Since Windows 7 DWMWA_EXCLUDED_FROM_PEEK, // Since Windows 7
DWMWA_CLOAK, // Since Windows 8 DWMWA_CLOAK, // Since Windows 8
DWMWA_CLOAKED, // Since Windows 8 DWMWA_CLOAKED, // Since Windows 8
DWMWA_FREEZE_REPRESENTATION, // Since Windows 8 DWMWA_FREEZE_REPRESENTATION, // Since Windows 8
DWMWA_LAST DWMWA_LAST
} }
@ -987,24 +987,24 @@ namespace GreenshotPlugin.UnmanagedHelpers {
Synchronize = 0x00100000 Synchronize = 0x00100000
} }
/// <summary> /// <summary>
/// See: http://msdn.microsoft.com/en-us/library/aa909766.aspx /// See: http://msdn.microsoft.com/en-us/library/aa909766.aspx
/// </summary> /// </summary>
[Flags] [Flags]
[SuppressMessage("ReSharper", "InconsistentNaming")] [SuppressMessage("ReSharper", "InconsistentNaming")]
public enum SoundFlags public enum SoundFlags
{ {
SND_SYNC = 0x0000, // play synchronously (default) SND_SYNC = 0x0000, // play synchronously (default)
SND_ASYNC = 0x0001, // play asynchronously SND_ASYNC = 0x0001, // play asynchronously
SND_NODEFAULT = 0x0002, // silence (!default) if sound not found SND_NODEFAULT = 0x0002, // silence (!default) if sound not found
SND_MEMORY = 0x0004, // pszSound points to a memory file SND_MEMORY = 0x0004, // pszSound points to a memory file
SND_LOOP = 0x0008, // loop the sound until next sndPlaySound SND_LOOP = 0x0008, // loop the sound until next sndPlaySound
SND_NOSTOP = 0x0010, // don't stop any currently playing sound SND_NOSTOP = 0x0010, // don't stop any currently playing sound
SND_NOWAIT = 0x00002000, // don't wait if the driver is busy SND_NOWAIT = 0x00002000, // don't wait if the driver is busy
SND_ALIAS = 0x00010000, // name is a registry alias SND_ALIAS = 0x00010000, // name is a registry alias
SND_ALIAS_ID = 0x00110000, // alias is a predefined id SND_ALIAS_ID = 0x00110000, // alias is a predefined id
SND_FILENAME = 0x00020000, // name is file name SND_FILENAME = 0x00020000, // name is file name
} }
/// <summary> /// <summary>
/// Used by GDI32.GetDeviceCaps /// Used by GDI32.GetDeviceCaps
@ -1012,174 +1012,174 @@ namespace GreenshotPlugin.UnmanagedHelpers {
/// </summary> /// </summary>
[SuppressMessage("ReSharper", "InconsistentNaming")] [SuppressMessage("ReSharper", "InconsistentNaming")]
public enum DeviceCaps { public enum DeviceCaps {
/// <summary> /// <summary>
/// Device driver version /// Device driver version
/// </summary> /// </summary>
DRIVERVERSION = 0, DRIVERVERSION = 0,
/// <summary> /// <summary>
/// Device classification /// Device classification
/// </summary> /// </summary>
TECHNOLOGY = 2, TECHNOLOGY = 2,
/// <summary> /// <summary>
/// Horizontal size in millimeters /// Horizontal size in millimeters
/// </summary> /// </summary>
HORZSIZE = 4, HORZSIZE = 4,
/// <summary> /// <summary>
/// Vertical size in millimeters /// Vertical size in millimeters
/// </summary> /// </summary>
VERTSIZE = 6, VERTSIZE = 6,
/// <summary> /// <summary>
/// Horizontal width in pixels /// Horizontal width in pixels
/// </summary> /// </summary>
HORZRES = 8, HORZRES = 8,
/// <summary> /// <summary>
/// Vertical height in pixels /// Vertical height in pixels
/// </summary> /// </summary>
VERTRES = 10, VERTRES = 10,
/// <summary> /// <summary>
/// Number of bits per pixel /// Number of bits per pixel
/// </summary> /// </summary>
BITSPIXEL = 12, BITSPIXEL = 12,
/// <summary> /// <summary>
/// Number of planes /// Number of planes
/// </summary> /// </summary>
PLANES = 14, PLANES = 14,
/// <summary> /// <summary>
/// Number of brushes the device has /// Number of brushes the device has
/// </summary> /// </summary>
NUMBRUSHES = 16, NUMBRUSHES = 16,
/// <summary> /// <summary>
/// Number of pens the device has /// Number of pens the device has
/// </summary> /// </summary>
NUMPENS = 18, NUMPENS = 18,
/// <summary> /// <summary>
/// Number of markers the device has /// Number of markers the device has
/// </summary> /// </summary>
NUMMARKERS = 20, NUMMARKERS = 20,
/// <summary> /// <summary>
/// Number of fonts the device has /// Number of fonts the device has
/// </summary> /// </summary>
NUMFONTS = 22, NUMFONTS = 22,
/// <summary> /// <summary>
/// Number of colors the device supports /// Number of colors the device supports
/// </summary> /// </summary>
NUMCOLORS = 24, NUMCOLORS = 24,
/// <summary> /// <summary>
/// Size required for device descriptor /// Size required for device descriptor
/// </summary> /// </summary>
PDEVICESIZE = 26, PDEVICESIZE = 26,
/// <summary> /// <summary>
/// Curve capabilities /// Curve capabilities
/// </summary> /// </summary>
CURVECAPS = 28, CURVECAPS = 28,
/// <summary> /// <summary>
/// Line capabilities /// Line capabilities
/// </summary> /// </summary>
LINECAPS = 30, LINECAPS = 30,
/// <summary> /// <summary>
/// Polygonal capabilities /// Polygonal capabilities
/// </summary> /// </summary>
POLYGONALCAPS = 32, POLYGONALCAPS = 32,
/// <summary> /// <summary>
/// Text capabilities /// Text capabilities
/// </summary> /// </summary>
TEXTCAPS = 34, TEXTCAPS = 34,
/// <summary> /// <summary>
/// Clipping capabilities /// Clipping capabilities
/// </summary> /// </summary>
CLIPCAPS = 36, CLIPCAPS = 36,
/// <summary> /// <summary>
/// Bitblt capabilities /// Bitblt capabilities
/// </summary> /// </summary>
RASTERCAPS = 38, RASTERCAPS = 38,
/// <summary> /// <summary>
/// Length of the X leg /// Length of the X leg
/// </summary> /// </summary>
ASPECTX = 40, ASPECTX = 40,
/// <summary> /// <summary>
/// Length of the Y leg /// Length of the Y leg
/// </summary> /// </summary>
ASPECTY = 42, ASPECTY = 42,
/// <summary> /// <summary>
/// Length of the hypotenuse /// Length of the hypotenuse
/// </summary> /// </summary>
ASPECTXY = 44, ASPECTXY = 44,
/// <summary> /// <summary>
/// Shading and Blending caps /// Shading and Blending caps
/// </summary> /// </summary>
SHADEBLENDCAPS = 45, SHADEBLENDCAPS = 45,
/// <summary> /// <summary>
/// Logical pixels inch in X /// Logical pixels inch in X
/// </summary> /// </summary>
LOGPIXELSX = 88, LOGPIXELSX = 88,
/// <summary> /// <summary>
/// Logical pixels inch in Y /// Logical pixels inch in Y
/// </summary> /// </summary>
LOGPIXELSY = 90, LOGPIXELSY = 90,
/// <summary> /// <summary>
/// Number of entries in physical palette /// Number of entries in physical palette
/// </summary> /// </summary>
SIZEPALETTE = 104, SIZEPALETTE = 104,
/// <summary> /// <summary>
/// Number of reserved entries in palette /// Number of reserved entries in palette
/// </summary> /// </summary>
NUMRESERVED = 106, NUMRESERVED = 106,
/// <summary> /// <summary>
/// Actual color resolution /// Actual color resolution
/// </summary> /// </summary>
COLORRES = 108, COLORRES = 108,
// Printing related DeviceCaps. These replace the appropriate Escapes // Printing related DeviceCaps. These replace the appropriate Escapes
/// <summary> /// <summary>
/// Physical Width in device units /// Physical Width in device units
/// </summary> /// </summary>
PHYSICALWIDTH = 110, PHYSICALWIDTH = 110,
/// <summary> /// <summary>
/// Physical Height in device units /// Physical Height in device units
/// </summary> /// </summary>
PHYSICALHEIGHT = 111, PHYSICALHEIGHT = 111,
/// <summary> /// <summary>
/// Physical Printable Area x margin /// Physical Printable Area x margin
/// </summary> /// </summary>
PHYSICALOFFSETX = 112, PHYSICALOFFSETX = 112,
/// <summary> /// <summary>
/// Physical Printable Area y margin /// Physical Printable Area y margin
/// </summary> /// </summary>
PHYSICALOFFSETY = 113, PHYSICALOFFSETY = 113,
/// <summary> /// <summary>
/// Scaling factor x /// Scaling factor x
/// </summary> /// </summary>
SCALINGFACTORX = 114, SCALINGFACTORX = 114,
/// <summary> /// <summary>
/// Scaling factor y /// Scaling factor y
/// </summary> /// </summary>
SCALINGFACTORY = 115, SCALINGFACTORY = 115,
/// <summary> /// <summary>
/// Current vertical refresh rate of the display device (for displays only) in Hz /// Current vertical refresh rate of the display device (for displays only) in Hz
/// </summary> /// </summary>
VREFRESH = 116, VREFRESH = 116,
/// <summary> /// <summary>
/// Horizontal width of entire desktop in pixels /// Horizontal width of entire desktop in pixels
/// </summary> /// </summary>
DESKTOPVERTRES = 117, DESKTOPVERTRES = 117,
/// <summary> /// <summary>
/// Vertical height of entire desktop in pixels /// Vertical height of entire desktop in pixels
/// </summary> /// </summary>
DESKTOPHORZRES = 118, DESKTOPHORZRES = 118,
/// <summary> /// <summary>
/// Preferred blt alignment /// Preferred blt alignment
/// </summary> /// </summary>
BLTALIGNMENT = 119 BLTALIGNMENT = 119
} }
/// <summary> /// <summary>
/// Used for User32.SetWinEventHook /// Used for User32.SetWinEventHook
/// See: http://msdn.microsoft.com/en-us/library/windows/desktop/dd373640%28v=vs.85%29.aspx /// See: http://msdn.microsoft.com/en-us/library/windows/desktop/dd373640%28v=vs.85%29.aspx
/// </summary> /// </summary>
[SuppressMessage("ReSharper", "InconsistentNaming")] [SuppressMessage("ReSharper", "InconsistentNaming"), Flags]
public enum WinEventHookFlags public enum WinEventHookFlags
{ {
WINEVENT_SKIPOWNTHREAD = 1, WINEVENT_SKIPOWNTHREAD = 1,