From 745a56291c15949d7700a7fb61020874f70af9b2 Mon Sep 17 00:00:00 2001 From: Robin Date: Fri, 2 Sep 2016 10:48:01 +0200 Subject: [PATCH] BUG-2016: Added SVG support for the jira plugin, need to decide if it's worth the extra KB's... --- GreenshotJiraPlugin/Forms/JiraForm.cs | 56 +++++--- .../GreenshotJiraPlugin.csproj | 13 +- GreenshotJiraPlugin/IssueTypeBitmapCache.cs | 11 +- GreenshotJiraPlugin/JiraConnector.cs | 48 +++++-- GreenshotJiraPlugin/JiraPlugin.cs | 1 - GreenshotJiraPlugin/JiraUtils.cs | 5 + GreenshotJiraPlugin/LanguageKeys.cs | 5 +- .../SvgBitmapHttpContentConverter.cs | 129 ++++++++++++++++++ GreenshotJiraPlugin/packages.config | 1 + GreenshotPlugin/Core/Language.cs | 13 ++ 10 files changed, 248 insertions(+), 34 deletions(-) create mode 100644 GreenshotJiraPlugin/SvgBitmapHttpContentConverter.cs diff --git a/GreenshotJiraPlugin/Forms/JiraForm.cs b/GreenshotJiraPlugin/Forms/JiraForm.cs index 19c8f3edd..e6fbf6717 100644 --- a/GreenshotJiraPlugin/Forms/JiraForm.cs +++ b/GreenshotJiraPlugin/Forms/JiraForm.cs @@ -37,7 +37,8 @@ namespace GreenshotJiraPlugin.Forms { private readonly JiraConnector _jiraConnector; private Issue _selectedIssue; private readonly GreenshotColumnSorter _columnSorter; - private readonly JiraConfiguration _config = IniConfig.GetIniSection(); + private static readonly JiraConfiguration JiraConfig = IniConfig.GetIniSection(); + private static readonly CoreConfiguration CoreConfig = IniConfig.GetIniSection(); public JiraForm(JiraConnector jiraConnector) { InitializeComponent(); @@ -83,12 +84,12 @@ namespace GreenshotJiraPlugin.Forms { jiraFilterBox.SelectedIndex = 0; } ChangeModus(true); - if (_config.LastUsedJira != null) + if (JiraConfig.LastUsedJira != null) { - _selectedIssue = await _jiraConnector.GetIssueAsync(_config.LastUsedJira); + _selectedIssue = await _jiraConnector.GetIssueAsync(JiraConfig.LastUsedJira); if (_selectedIssue != null) { - jiraKey.Text = _config.LastUsedJira; + jiraKey.Text = JiraConfig.LastUsedJira; uploadButton.Enabled = true; } } @@ -117,7 +118,7 @@ namespace GreenshotJiraPlugin.Forms { } public async Task UploadAsync(IBinaryContainer attachment) { - _config.LastUsedJira = _selectedIssue.Key; + JiraConfig.LastUsedJira = _selectedIssue.Key; attachment.Filename = jiraFilenameBox.Text; await _jiraConnector.AttachAsync(_selectedIssue.Key, attachment); @@ -145,31 +146,50 @@ namespace GreenshotJiraPlugin.Forms { MessageBox.Show(this, ex.Message, "Error in filter", MessageBoxButtons.OK, MessageBoxIcon.Error); } - jiraListView.BeginUpdate(); jiraListView.Items.Clear(); if (issues?.Count > 0) { jiraListView.Columns.Clear(); - LangKey[] columns = { LangKey.column_id, LangKey.column_created, LangKey.column_assignee, LangKey.column_reporter, LangKey.column_summary }; - foreach (LangKey column in columns) { - jiraListView.Columns.Add(Language.GetString("jira", column)); - } - foreach (var issue in issues) { - var item = new ListViewItem(issue.Key) + LangKey[] columns = { LangKey.column_issueType, LangKey.column_id, LangKey.column_created, LangKey.column_assignee, LangKey.column_reporter, LangKey.column_summary }; + foreach (LangKey column in columns) + { + string translation; + if (!Language.TryGetString("jira", column, out translation)) { - Tag = issue + translation = ""; + } + jiraListView.Columns.Add(translation); + } + var imageList = new ImageList { + ImageSize = CoreConfig.IconSize + }; + jiraListView.SmallImageList = imageList; + jiraListView.LargeImageList = imageList; + + foreach (var issue in issues) { + var issueIcon = await _jiraConnector.GetIssueTypeBitmapAsync(issue); + imageList.Images.Add(issueIcon); + + var item = new ListViewItem + { + Tag = issue, + ImageIndex = imageList.Images.Count - 1 }; + item.SubItems.Add(issue.Key); item.SubItems.Add(issue.Fields.Created.ToString("d", DateTimeFormatInfo.InvariantInfo)); item.SubItems.Add(issue.Fields.Assignee?.DisplayName); item.SubItems.Add(issue.Fields.Reporter?.DisplayName); item.SubItems.Add(issue.Fields.Summary); jiraListView.Items.Add(item); + for (int i = 0; i < columns.Length; i++) + { + jiraListView.AutoResizeColumn(i, ColumnHeaderAutoResizeStyle.ColumnContent); + } + jiraListView.Invalidate(); + jiraListView.Update(); } - for (int i = 0; i < columns.Length; i++) { - jiraListView.AutoResizeColumn(i, ColumnHeaderAutoResizeStyle.ColumnContent); - } + + jiraListView.Refresh(); } - jiraListView.EndUpdate(); - jiraListView.Refresh(); } } diff --git a/GreenshotJiraPlugin/GreenshotJiraPlugin.csproj b/GreenshotJiraPlugin/GreenshotJiraPlugin.csproj index d9823ccb0..2c02694f4 100644 --- a/GreenshotJiraPlugin/GreenshotJiraPlugin.csproj +++ b/GreenshotJiraPlugin/GreenshotJiraPlugin.csproj @@ -49,8 +49,13 @@ ..\Greenshot\Lib\log4net.dll + + ..\packages\Svg.2.2.1\lib\net35\Svg.dll + True + + @@ -82,6 +87,7 @@ + Always @@ -105,6 +111,10 @@ {5B924697-4DCD-4F98-85F1-105CB84B7341} GreenshotPlugin + + {CD642BF4-D815-4D67-A0B5-C69F0B8231AF} + Greenshot + mkdir "$(SolutionDir)Greenshot\bin\$(Configuration)\Plugins\$(ProjectName)" @@ -112,12 +122,13 @@ del /q /s "$(SolutionDir)Greenshot\bin\$(Configuration)\Plugins\$(ProjectName)"\* if "$(ConfigurationName)" == "Release" ( - ..\..\..\packages\LibZ.Tool.1.2.0.0\tools\libz.exe inject-dll --assembly $(ProjectDir)bin\$(Configuration)\$(TargetFileName) --include $(ProjectDir)bin\$(Configuration)\dapplo*.dll --move + ..\..\..\packages\LibZ.Tool.1.2.0.0\tools\libz.exe inject-dll --assembly $(ProjectDir)bin\$(Configuration)\$(TargetFileName) --include $(ProjectDir)bin\$(Configuration)\dapplo*.dll --include $(ProjectDir)bin\$(Configuration)\svg.dll --move ) copy "$(ProjectDir)bin\$(Configuration)\$(TargetFileName)" "$(SolutionDir)Greenshot\bin\$(Configuration)\Plugins\$(ProjectName)\*.gsp" if "$(ConfigurationName)" == "Debug" ( copy "$(ProjectDir)bin\$(Configuration)\Dapplo.*" "$(SolutionDir)Greenshot\bin\$(Configuration)\Plugins\$(ProjectName)\" + copy "$(ProjectDir)bin\$(Configuration)\Svg.dll" "$(SolutionDir)Greenshot\bin\$(Configuration)\Plugins\$(ProjectName)\" copy "$(ProjectDir)bin\$(Configuration)\$(ProjectName).pdb" "$(SolutionDir)Greenshot\bin\$(Configuration)\Plugins\$(ProjectName)\" ) diff --git a/GreenshotJiraPlugin/IssueTypeBitmapCache.cs b/GreenshotJiraPlugin/IssueTypeBitmapCache.cs index 019468944..162b41b13 100644 --- a/GreenshotJiraPlugin/IssueTypeBitmapCache.cs +++ b/GreenshotJiraPlugin/IssueTypeBitmapCache.cs @@ -31,7 +31,7 @@ namespace GreenshotJiraPlugin /// /// This is the bach for the IssueType bitmaps /// - public class IssueTypeBitmapCache : AsyncMemoryCache + public class IssueTypeBitmapCache : AsyncMemoryCache { private readonly JiraApi _jiraApi; @@ -42,9 +42,14 @@ namespace GreenshotJiraPlugin ExpireTimeSpan = TimeSpan.FromHours(1); } - protected override async Task CreateAsync(Issue issue, CancellationToken cancellationToken = new CancellationToken()) + protected override string CreateKey(IssueType keyObject) { - return await _jiraApi.GetUriContentAsync(issue.Fields.IssueType.IconUri, cancellationToken).ConfigureAwait(false); + return keyObject.Name; + } + + protected override async Task CreateAsync(IssueType issueType, CancellationToken cancellationToken = new CancellationToken()) + { + return await _jiraApi.GetUriContentAsync(issueType.IconUri, cancellationToken).ConfigureAwait(false); } } } diff --git a/GreenshotJiraPlugin/JiraConnector.cs b/GreenshotJiraPlugin/JiraConnector.cs index 197d65996..65f2baa66 100644 --- a/GreenshotJiraPlugin/JiraConnector.cs +++ b/GreenshotJiraPlugin/JiraConnector.cs @@ -24,9 +24,11 @@ using System; using System.Collections.Generic; using System.Drawing; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; +using Dapplo.HttpExtensions; using Dapplo.Jira; using Dapplo.Jira.Entities; using Greenshot.IniFile; @@ -38,7 +40,8 @@ namespace GreenshotJiraPlugin { /// public class JiraConnector : IDisposable { private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(JiraConnector)); - private static readonly JiraConfiguration Config = IniConfig.GetIniSection(); + private static readonly JiraConfiguration JiraConfig = IniConfig.GetIniSection(); + private static readonly CoreConfiguration CoreConfig = IniConfig.GetIniSection(); // Used to remove the wsdl information from the old SOAP Uri public const string DefaultPostfix = "/rpc/soap/jirasoapservice-v2?wsdl"; private DateTimeOffset _loggedInTime = DateTimeOffset.MinValue; @@ -48,6 +51,26 @@ namespace GreenshotJiraPlugin { private string _url; private JiraApi _jiraApi; private IssueTypeBitmapCache _issueTypeBitmapCache; + private static readonly SvgBitmapHttpContentConverter SvgBitmapHttpContentConverterInstance = new SvgBitmapHttpContentConverter(); + + static JiraConnector() + { + if (HttpExtensionsGlobals.HttpContentConverters.All(x => x.GetType() != typeof(SvgBitmapHttpContentConverter))) + { + HttpExtensionsGlobals.HttpContentConverters.Add(SvgBitmapHttpContentConverterInstance); + } + SvgBitmapHttpContentConverterInstance.Width = CoreConfig.IconSize.Width; + SvgBitmapHttpContentConverterInstance.Height = CoreConfig.IconSize.Height; + CoreConfig.PropertyChanged += (sender, args) => + { + if (args.PropertyName == nameof(CoreConfig.IconSize)) + { + SvgBitmapHttpContentConverterInstance.Width = CoreConfig.IconSize.Width; + SvgBitmapHttpContentConverterInstance.Height = CoreConfig.IconSize.Height; + } + }; + + } public void Dispose() { if (_jiraApi != null) @@ -56,9 +79,10 @@ namespace GreenshotJiraPlugin { } } - public JiraConnector() { - _url = Config.Url.Replace(DefaultPostfix, ""); - _timeout = Config.Timeout; + public JiraConnector() + { + _url = JiraConfig.Url.Replace(DefaultPostfix, ""); + _timeout = JiraConfig.Timeout; } /// @@ -86,7 +110,7 @@ namespace GreenshotJiraPlugin { { loginInfo = await _jiraApi.StartSessionAsync(user, password); // Worked, store the url in the configuration - Config.Url = _url; + JiraConfig.Url = _url; IniConfig.Save(); } catch (Exception) @@ -177,7 +201,14 @@ namespace GreenshotJiraPlugin { public async Task GetIssueAsync(string issueKey) { await CheckCredentials(); - return await _jiraApi.GetIssueAsync(issueKey).ConfigureAwait(false); + try + { + return await _jiraApi.GetIssueAsync(issueKey).ConfigureAwait(false); + } + catch + { + return null; + } } /// @@ -185,7 +216,6 @@ namespace GreenshotJiraPlugin { /// /// /// IBinaryContainer - /// /// /// public async Task AttachAsync(string issueKey, IBinaryContainer content, CancellationToken cancellationToken = default(CancellationToken)) @@ -221,7 +251,7 @@ namespace GreenshotJiraPlugin { public async Task> SearchAsync(Filter filter, CancellationToken cancellationToken = default(CancellationToken)) { await CheckCredentials(); - var searchResult = await _jiraApi.SearchAsync(filter.Jql, 20, new[] { "summary", "reporter", "assignee", "created" }, cancellationToken).ConfigureAwait(false); + var searchResult = await _jiraApi.SearchAsync(filter.Jql, 20, new[] { "summary", "reporter", "assignee", "created", "issuetype" }, cancellationToken).ConfigureAwait(false); return searchResult.Issues; } @@ -233,7 +263,7 @@ namespace GreenshotJiraPlugin { /// Bitmap public async Task GetIssueTypeBitmapAsync(Issue issue, CancellationToken cancellationToken = default(CancellationToken)) { - return await _issueTypeBitmapCache.GetOrCreateAsync(issue, cancellationToken).ConfigureAwait(false); + return await _issueTypeBitmapCache.GetOrCreateAsync(issue.Fields.IssueType, cancellationToken).ConfigureAwait(false); } public Uri JiraBaseUri => _jiraApi.JiraBaseUri; diff --git a/GreenshotJiraPlugin/JiraPlugin.cs b/GreenshotJiraPlugin/JiraPlugin.cs index d3bf3fbc2..6f9e499e9 100644 --- a/GreenshotJiraPlugin/JiraPlugin.cs +++ b/GreenshotJiraPlugin/JiraPlugin.cs @@ -24,7 +24,6 @@ using Greenshot.IniFile; using Greenshot.Plugin; using System; using System.Threading.Tasks; -using Dapplo.HttpExtensions.ContentConverter; using Dapplo.Log.Facade; using GreenshotJiraPlugin.Forms; diff --git a/GreenshotJiraPlugin/JiraUtils.cs b/GreenshotJiraPlugin/JiraUtils.cs index dba6ba1e5..94984f555 100644 --- a/GreenshotJiraPlugin/JiraUtils.cs +++ b/GreenshotJiraPlugin/JiraUtils.cs @@ -66,6 +66,11 @@ namespace GreenshotJiraPlugin { 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) { diff --git a/GreenshotJiraPlugin/LanguageKeys.cs b/GreenshotJiraPlugin/LanguageKeys.cs index 8cbab218b..0ef41c755 100644 --- a/GreenshotJiraPlugin/LanguageKeys.cs +++ b/GreenshotJiraPlugin/LanguageKeys.cs @@ -20,10 +20,11 @@ */ namespace GreenshotJiraPlugin { - public enum LangKey { + public enum LangKey { upload_menu_item, column_assignee, column_created, + column_issueType, column_id, column_reporter, column_summary, @@ -41,5 +42,5 @@ namespace GreenshotJiraPlugin { upload_success, upload_failure, communication_wait, - } + } } diff --git a/GreenshotJiraPlugin/SvgBitmapHttpContentConverter.cs b/GreenshotJiraPlugin/SvgBitmapHttpContentConverter.cs new file mode 100644 index 000000000..f8d995d99 --- /dev/null +++ b/GreenshotJiraPlugin/SvgBitmapHttpContentConverter.cs @@ -0,0 +1,129 @@ +/* + * 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.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Dapplo.HttpExtensions; +using Dapplo.HttpExtensions.ContentConverter; +using Dapplo.HttpExtensions.Extensions; +using Dapplo.HttpExtensions.Support; +using Dapplo.Log.Facade; +using System.Net.Http; +using System.Net.Http.Headers; +using GreenshotPlugin.Core; +using Svg; + +namespace GreenshotJiraPlugin +{ + /// + /// This adds SVG image support for the Jira Plugin + /// + public class SvgBitmapHttpContentConverter : IHttpContentConverter + { + private static readonly LogSource Log = new LogSource(); + private static readonly IList SupportedContentTypes = new List(); + + static SvgBitmapHttpContentConverter() + { + SupportedContentTypes.Add(MediaTypes.Svg.EnumValueOf()); + } + + /// + public int Order => 0; + + public int Width { get; set; } + + public int Height { get; set; } + + /// + /// This checks if the HttpContent can be converted to a Bitmap and is assignable to the specified Type + /// + /// This should be something we can assign Bitmap to + /// HttpContent to process + /// true if it can convert + public bool CanConvertFromHttpContent(Type typeToConvertTo, HttpContent httpContent) + { + if (typeToConvertTo == typeof(object) || !typeToConvertTo.IsAssignableFrom(typeof(Bitmap))) + { + return false; + } + var httpBehaviour = HttpBehaviour.Current; + return !httpBehaviour.ValidateResponseContentType || SupportedContentTypes.Contains(httpContent.GetContentType()); + } + + /// + public async Task ConvertFromHttpContentAsync(Type resultType, HttpContent httpContent, CancellationToken cancellationToken = default(CancellationToken)) + { + if (!CanConvertFromHttpContent(resultType, httpContent)) + { + var exMessage = "CanConvertFromHttpContent resulted in false, ConvertFromHttpContentAsync is not supposed to be called."; + Log.Error().WriteLine(exMessage); + throw new NotSupportedException(exMessage); + } + using (var memoryStream = (MemoryStream) await StreamHttpContentConverter.Instance.ConvertFromHttpContentAsync(typeof(MemoryStream), httpContent, cancellationToken).ConfigureAwait(false)) + { + Log.Debug().WriteLine("Creating a Bitmap from the SVG."); + var bitmap = ImageHelper.CreateEmpty(Width, Height, PixelFormat.Format32bppArgb, Color.Transparent, 96, 96); + var svgDoc = SvgDocument.Open(memoryStream); + svgDoc.Width = Width; + svgDoc.Height = Height; + svgDoc.Draw(bitmap); + return bitmap; + } + } + + /// + public bool CanConvertToHttpContent(Type typeToConvert, object content) + { + return false; + } + + /// + public HttpContent ConvertToHttpContent(Type typeToConvert, object content) + { + return null; + } + + /// + public void AddAcceptHeadersForType(Type resultType, HttpRequestMessage httpRequestMessage) + { + if (resultType == null) + { + throw new ArgumentNullException(nameof(resultType)); + } + if (httpRequestMessage == null) + { + throw new ArgumentNullException(nameof(httpRequestMessage)); + } + if (resultType == typeof(object) || !resultType.IsAssignableFrom(typeof(Bitmap))) + { + return; + } + httpRequestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypes.Svg.EnumValueOf())); + Log.Debug().WriteLine("Modified the header(s) of the HttpRequestMessage: Accept: {0}", httpRequestMessage.Headers.Accept); + } + } +} diff --git a/GreenshotJiraPlugin/packages.config b/GreenshotJiraPlugin/packages.config index 62f3442fd..6fb907484 100644 --- a/GreenshotJiraPlugin/packages.config +++ b/GreenshotJiraPlugin/packages.config @@ -4,4 +4,5 @@ + \ No newline at end of file diff --git a/GreenshotPlugin/Core/Language.cs b/GreenshotPlugin/Core/Language.cs index 53f11b6cd..3a86237a6 100644 --- a/GreenshotPlugin/Core/Language.cs +++ b/GreenshotPlugin/Core/Language.cs @@ -532,6 +532,19 @@ namespace GreenshotPlugin.Core { return resources.TryGetValue(prefix + "." + key, out languageString); } + /// + /// TryGet method which combines hasKey & GetString + /// + /// string with prefix + /// Enum with key + /// out string + /// + public static bool TryGetString(string prefix, Enum key, out string languageString) + { + return resources.TryGetValue(prefix + "." + key, out languageString); + } + + public static string Translate(object key) { string typename = key.GetType().Name; string enumKey = typename + "." + key;