From 6d44885dac594c9d2ef9c41bf0c27f00db20738d Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 14 Jan 2023 13:32:40 +0900 Subject: [PATCH 01/11] =?UTF-8?q?=E3=83=90=E3=83=BC=E3=82=B8=E3=83=A7?= =?UTF-8?q?=E3=83=B3=20v3.1.1-dev=20=E9=96=8B=E7=99=BA=E9=96=8B=E5=A7=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween/Properties/AssemblyInfo.cs | 2 +- OpenTween/Properties/Resources.Designer.cs | 4 +++- OpenTween/Resources/ChangeLog.txt | 2 ++ appveyor.yml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/OpenTween/Properties/AssemblyInfo.cs b/OpenTween/Properties/AssemblyInfo.cs index cb1d51ba3..231ea88cf 100644 --- a/OpenTween/Properties/AssemblyInfo.cs +++ b/OpenTween/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ // 次の GUID は、このプロジェクトが COM に公開される場合の、typelib の ID です [assembly: Guid("2d0ae0ba-adac-49a2-9b10-26fd69e695bf")] -[assembly: AssemblyVersion("3.1.0.0")] +[assembly: AssemblyVersion("3.1.0.1")] [assembly: InternalsVisibleTo("OpenTween.Tests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] // for Moq diff --git a/OpenTween/Properties/Resources.Designer.cs b/OpenTween/Properties/Resources.Designer.cs index ab1febea2..9b838ee36 100644 --- a/OpenTween/Properties/Resources.Designer.cs +++ b/OpenTween/Properties/Resources.Designer.cs @@ -571,6 +571,8 @@ internal static string ChangeIconToolStripMenuItem_Confirm { /// /// 更新履歴 /// + ///==== Unreleased + /// ///==== Ver 3.1.0(2023/01/14) /// * NEW: 引用ツイートを Ctrl+Shift+L で実行するショートカットを追加 (thx @WizardOfPSG!) /// * CHG: 発言一覧のフォントサイズがアイコンより大きい場合は項目の高さをフォントサイズに合わせるように変更 @@ -580,7 +582,7 @@ internal static string ChangeIconToolStripMenuItem_Confirm { ///==== Ver 3.0.0(2023/01/11) /// * OpenTween v3.0.0 からは .NET Framework 4.8 以上が必須になります /// - .NET Framework 4.8 ランタイムは https://dotnet.microsoft.com/ja-jp/download/dotnet-framework/net48 から入手できます - /// - Windows 10 21H1 以降には標準で .NET Framework 4.8 が含まれているため追加 [残りの文字列は切り詰められました]"; に類似しているローカライズされた文字列を検索します。 + /// - Windows 10 21H1 以降には標準で .NET Framew [残りの文字列は切り詰められました]"; に類似しているローカライズされた文字列を検索します。 /// internal static string ChangeLog { get { diff --git a/OpenTween/Resources/ChangeLog.txt b/OpenTween/Resources/ChangeLog.txt index 9d3dde4bc..5f54feb8e 100644 --- a/OpenTween/Resources/ChangeLog.txt +++ b/OpenTween/Resources/ChangeLog.txt @@ -1,5 +1,7 @@ 更新履歴 +==== Unreleased + ==== Ver 3.1.0(2023/01/14) * NEW: 引用ツイートを Ctrl+Shift+L で実行するショートカットを追加 (thx @WizardOfPSG!) * CHG: 発言一覧のフォントサイズがアイコンより大きい場合は項目の高さをフォントサイズに合わせるように変更 diff --git a/appveyor.yml b/appveyor.yml index 3ad7049c7..c13fff568 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 3.0.0.{build} +version: 3.1.0.{build} os: Visual Studio 2022 From 34885e57596e302df7f9cedce7a4ab5deaf2aa96 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 14 Jan 2023 16:41:55 +0900 Subject: [PATCH 02/11] =?UTF-8?q?oauth/request=5Ftoken,=20oauth/access=5Ft?= =?UTF-8?q?oken=20=E3=81=AE=E3=82=A8=E3=83=A9=E3=83=BC=E3=83=AC=E3=82=B9?= =?UTF-8?q?=E3=83=9D=E3=83=B3=E3=82=B9=E3=81=AE=E5=87=A6=E7=90=86=E3=81=AB?= =?UTF-8?q?CheckStatusCode=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween/Connection/TwitterApiConnection.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/OpenTween/Connection/TwitterApiConnection.cs b/OpenTween/Connection/TwitterApiConnection.cs index faa54ae38..0699218b6 100644 --- a/OpenTween/Connection/TwitterApiConnection.cs +++ b/OpenTween/Connection/TwitterApiConnection.cs @@ -107,7 +107,7 @@ public async Task GetAsync(Uri uri, IDictionary? param, st if (endpointName != null) MyCommon.TwitterApiInfo.UpdateFromHeader(response.Headers, endpointName); - await this.CheckStatusCode(response) + await TwitterApiConnection.CheckStatusCode(response) .ConfigureAwait(false); using var content = response.Content; @@ -190,7 +190,7 @@ public async Task GetStreamingStreamAsync(Uri uri, IDictionary> PostLazyAsync(Uri uri, IDictionary(response); @@ -267,7 +267,7 @@ public async Task> PostLazyAsync(Uri uri, IDictionary(response); @@ -313,7 +313,7 @@ public async Task PostAsync(Uri uri, IDictionary? param, IDictio using var response = await this.HttpUpload.SendAsync(request, HttpCompletionOption.ResponseHeadersRead) .ConfigureAwait(false); - await this.CheckStatusCode(response) + await TwitterApiConnection.CheckStatusCode(response) .ConfigureAwait(false); } catch (HttpRequestException ex) @@ -345,7 +345,7 @@ public async Task> PostJsonAsync(Uri uri, string json) response = await this.Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead) .ConfigureAwait(false); - await this.CheckStatusCode(response) + await TwitterApiConnection.CheckStatusCode(response) .ConfigureAwait(false); var result = new LazyJson(response); @@ -377,7 +377,7 @@ public async Task DeleteAsync(Uri uri) using var response = await this.Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead) .ConfigureAwait(false); - await this.CheckStatusCode(response) + await TwitterApiConnection.CheckStatusCode(response) .ConfigureAwait(false); } catch (HttpRequestException ex) @@ -390,7 +390,7 @@ await this.CheckStatusCode(response) } } - protected async Task CheckStatusCode(HttpResponseMessage response) + protected static async Task CheckStatusCode(HttpResponseMessage response) { var statusCode = response.StatusCode; @@ -546,8 +546,8 @@ private static async Task> GetOAuthTokenAsync( var responseText = await content.ReadAsStringAsync() .ConfigureAwait(false); - if (!response.IsSuccessStatusCode) - throw new TwitterApiException(response.StatusCode, responseText); + await TwitterApiConnection.CheckStatusCode(response) + .ConfigureAwait(false); var responseParams = HttpUtility.ParseQueryString(responseText); From e70ea0666b71ffb0c1160fe41b49ca546f6db2e0 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sat, 14 Jan 2023 16:49:53 +0900 Subject: [PATCH 03/11] =?UTF-8?q?=E3=82=A2=E3=82=AB=E3=82=A6=E3=83=B3?= =?UTF-8?q?=E3=83=88=E8=BF=BD=E5=8A=A0=E6=99=82=E3=81=AE=E8=AA=8D=E5=8F=AF?= =?UTF-8?q?=E9=96=A2=E9=80=A3=E3=81=AE=E3=82=A8=E3=83=A9=E3=83=BC=E3=83=A1?= =?UTF-8?q?=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=92=E3=82=88=E3=82=8A?= =?UTF-8?q?=E8=A9=B3=E7=B4=B0=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween/Api/TwitterApiException.cs | 3 +++ OpenTween/AppendSettingDialog.cs | 7 +++++-- OpenTween/Resources/ChangeLog.txt | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/OpenTween/Api/TwitterApiException.cs b/OpenTween/Api/TwitterApiException.cs index e2b9de049..d95af9c84 100644 --- a/OpenTween/Api/TwitterApiException.cs +++ b/OpenTween/Api/TwitterApiException.cs @@ -43,6 +43,9 @@ public class TwitterApiException : WebApiException public TwitterErrorItem[] Errors => this.ErrorResponse != null ? this.ErrorResponse.Errors : Array.Empty(); + public string[] LongMessages + => this.Errors.Select(x => x.Message).ToArray(); + public TwitterApiException() { } diff --git a/OpenTween/AppendSettingDialog.cs b/OpenTween/AppendSettingDialog.cs index 1c644813e..f6475dd62 100644 --- a/OpenTween/AppendSettingDialog.cs +++ b/OpenTween/AppendSettingDialog.cs @@ -39,6 +39,7 @@ using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; +using OpenTween.Api; using OpenTween.Connection; using OpenTween.Setting.Panel; using OpenTween.Thumbnail; @@ -208,9 +209,11 @@ private async void StartAuthButton_Click(object sender, EventArgs e) "Authenticate", MessageBoxButtons.OK); } - catch (WebApiException ex) + catch (TwitterApiException ex) { - var message = Properties.Resources.AuthorizeButton_Click2 + Environment.NewLine + ex.Message; + var message = Properties.Resources.AuthorizeButton_Click2 + Environment.NewLine + + string.Join(Environment.NewLine, ex.LongMessages); + MessageBox.Show(this, message, "Authenticate", MessageBoxButtons.OK); } } diff --git a/OpenTween/Resources/ChangeLog.txt b/OpenTween/Resources/ChangeLog.txt index 5f54feb8e..9cd5a3774 100644 --- a/OpenTween/Resources/ChangeLog.txt +++ b/OpenTween/Resources/ChangeLog.txt @@ -1,6 +1,7 @@ 更新履歴 ==== Unreleased + * CHG: アカウント追加時の認可関連のエラーメッセージがより詳細になるように変更 ==== Ver 3.1.0(2023/01/14) * NEW: 引用ツイートを Ctrl+Shift+L で実行するショートカットを追加 (thx @WizardOfPSG!) From eab919015fa17babc42cfea2b0edd823ad5ed5a1 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sun, 15 Jan 2023 07:55:29 +0900 Subject: [PATCH 04/11] =?UTF-8?q?MediaSelector=E3=82=92MediaSelectorPanel?= =?UTF-8?q?=E3=81=AB=E5=90=8D=E5=89=8D=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/MediaSelectorTest.cs | 28 +++++++++---------- ...gner.cs => MediaSelectorPanel.Designer.cs} | 8 +++--- ...MediaSelector.cs => MediaSelectorPanel.cs} | 4 +-- ...tor.en.resx => MediaSelectorPanel.en.resx} | 0 ...aSelector.resx => MediaSelectorPanel.resx} | 2 +- OpenTween/OpenTween.csproj | 14 +++++----- OpenTween/Tween.Designer.cs | 4 +-- OpenTween/Tween.resx | 2 +- 8 files changed, 31 insertions(+), 31 deletions(-) rename OpenTween/{MediaSelector.Designer.cs => MediaSelectorPanel.Designer.cs} (98%) rename OpenTween/{MediaSelector.cs => MediaSelectorPanel.cs} (99%) rename OpenTween/{MediaSelector.en.resx => MediaSelectorPanel.en.resx} (100%) rename OpenTween/{MediaSelector.resx => MediaSelectorPanel.resx} (99%) diff --git a/OpenTween.Tests/MediaSelectorTest.cs b/OpenTween.Tests/MediaSelectorTest.cs index de4d3ca1d..203085597 100644 --- a/OpenTween.Tests/MediaSelectorTest.cs +++ b/OpenTween.Tests/MediaSelectorTest.cs @@ -33,7 +33,7 @@ public void Initialize_TwitterTest() { using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelector(); + using var mediaSelector = new MediaSelectorPanel(); twitter.Initialize("", "", "", 0L); mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); @@ -55,7 +55,7 @@ public void Initialize_ImgurTest() { using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelector(); + using var mediaSelector = new MediaSelectorPanel(); twitter.Initialize("", "", "", 0L); mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Imgur"); @@ -75,7 +75,7 @@ public void BeginSelection_BlankTest() { using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelector { Visible = false, Enabled = false }; + using var mediaSelector = new MediaSelectorPanel { Visible = false, Enabled = false }; twitter.Initialize("", "", "", 0L); mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); @@ -103,7 +103,7 @@ public void BeginSelection_FilePathTest() { using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelector { Visible = false, Enabled = false }; + using var mediaSelector = new MediaSelectorPanel { Visible = false, Enabled = false }; twitter.Initialize("", "", "", 0L); mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); @@ -136,7 +136,7 @@ public void BeginSelection_MemoryImageTest() { using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelector { Visible = false, Enabled = false }; + using var mediaSelector = new MediaSelectorPanel { Visible = false, Enabled = false }; twitter.Initialize("", "", "", 0L); mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); @@ -172,7 +172,7 @@ public void BeginSelection_MultiImageTest() { using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelector { Visible = false, Enabled = false }; + using var mediaSelector = new MediaSelectorPanel { Visible = false, Enabled = false }; twitter.Initialize("", "", "", 0L); mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); @@ -197,7 +197,7 @@ public void EndSelection_Test() { using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelector { Visible = false, Enabled = false }; + using var mediaSelector = new MediaSelectorPanel { Visible = false, Enabled = false }; twitter.Initialize("", "", "", 0L); mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); mediaSelector.BeginSelection(new[] { "Resources/re.gif" }); @@ -221,7 +221,7 @@ public void PageChange_Test() { using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelector { Visible = false, Enabled = false }; + using var mediaSelector = new MediaSelectorPanel { Visible = false, Enabled = false }; twitter.Initialize("", "", "", 0L); mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); @@ -265,7 +265,7 @@ public void PageChange_AlternativeTextTest() { using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelector { Visible = false, Enabled = false }; + using var mediaSelector = new MediaSelectorPanel { Visible = false, Enabled = false }; twitter.Initialize("", "", "", 0L); mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); @@ -303,7 +303,7 @@ public void PageChange_ImageDisposeTest() { using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelector { Visible = false, Enabled = false }; + using var mediaSelector = new MediaSelectorPanel { Visible = false, Enabled = false }; twitter.Initialize("", "", "", 0L); mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); @@ -332,7 +332,7 @@ public void ImagePathInput_Test() { using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelector { Visible = false, Enabled = false }; + using var mediaSelector = new MediaSelectorPanel { Visible = false, Enabled = false }; twitter.Initialize("", "", "", 0L); mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); mediaSelector.BeginSelection(); @@ -358,7 +358,7 @@ public void ImagePathInput_ReplaceFileMediaItemTest() { using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelector { Visible = false, Enabled = false }; + using var mediaSelector = new MediaSelectorPanel { Visible = false, Enabled = false }; twitter.Initialize("", "", "", 0L); mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); @@ -387,7 +387,7 @@ public void ImagePathInput_ReplaceMemoryImageMediaItemTest() { using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelector { Visible = false, Enabled = false }; + using var mediaSelector = new MediaSelectorPanel { Visible = false, Enabled = false }; twitter.Initialize("", "", "", 0L); mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); @@ -427,7 +427,7 @@ public void ImageServiceChange_Test() { using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelector { Visible = false, Enabled = false }; + using var mediaSelector = new MediaSelectorPanel { Visible = false, Enabled = false }; twitter.Initialize("", "", "", 0L); mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); diff --git a/OpenTween/MediaSelector.Designer.cs b/OpenTween/MediaSelectorPanel.Designer.cs similarity index 98% rename from OpenTween/MediaSelector.Designer.cs rename to OpenTween/MediaSelectorPanel.Designer.cs index fe2c34dbe..ca13adbdc 100644 --- a/OpenTween/MediaSelector.Designer.cs +++ b/OpenTween/MediaSelectorPanel.Designer.cs @@ -1,6 +1,6 @@ namespace OpenTween { - partial class MediaSelector + partial class MediaSelectorPanel { /// /// 必要なデザイナー変数です。 @@ -28,7 +28,7 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MediaSelector)); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MediaSelectorPanel)); this.ImagePathPanel = new System.Windows.Forms.Panel(); this.ImagefilePathText = new System.Windows.Forms.TextBox(); this.ImagePageCombo = new System.Windows.Forms.ComboBox(); @@ -135,14 +135,14 @@ private void InitializeComponent() this.ImageSelectedPicture.Name = "ImageSelectedPicture"; this.ImageSelectedPicture.TabStop = false; // - // MediaSelector + // MediaSelectorPanel // resources.ApplyResources(this, "$this"); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.Controls.Add(this.ImageSelectedPicture); this.Controls.Add(this.AlternativeTextPanel); this.Controls.Add(this.ImagePathPanel); - this.Name = "MediaSelector"; + this.Name = "MediaSelectorPanel"; this.ImagePathPanel.ResumeLayout(false); this.ImagePathPanel.PerformLayout(); this.AlternativeTextPanel.ResumeLayout(false); diff --git a/OpenTween/MediaSelector.cs b/OpenTween/MediaSelectorPanel.cs similarity index 99% rename from OpenTween/MediaSelector.cs rename to OpenTween/MediaSelectorPanel.cs index bffec5451..d45463d7e 100644 --- a/OpenTween/MediaSelector.cs +++ b/OpenTween/MediaSelectorPanel.cs @@ -37,7 +37,7 @@ namespace OpenTween { - public partial class MediaSelector : UserControl + public partial class MediaSelectorPanel : UserControl { public event EventHandler? BeginSelecting; @@ -148,7 +148,7 @@ private void CreateServices(Twitter tw, TwitterConfiguration twitterConfig) }; } - public MediaSelector() + public MediaSelectorPanel() { this.InitializeComponent(); diff --git a/OpenTween/MediaSelector.en.resx b/OpenTween/MediaSelectorPanel.en.resx similarity index 100% rename from OpenTween/MediaSelector.en.resx rename to OpenTween/MediaSelectorPanel.en.resx diff --git a/OpenTween/MediaSelector.resx b/OpenTween/MediaSelectorPanel.resx similarity index 99% rename from OpenTween/MediaSelector.resx rename to OpenTween/MediaSelectorPanel.resx index 5f2bbbc17..94627aa85 100644 --- a/OpenTween/MediaSelector.resx +++ b/OpenTween/MediaSelectorPanel.resx @@ -10,7 +10,7 @@ 96, 96 True 608, 280 - MediaSelector + MediaSelectorPanel System.Windows.Forms.UserControl, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 AlternativeTextBox AlternativeTextPanel diff --git a/OpenTween/OpenTween.csproj b/OpenTween/OpenTween.csproj index b60f24ed9..bc1b212ca 100644 --- a/OpenTween/OpenTween.csproj +++ b/OpenTween/OpenTween.csproj @@ -118,11 +118,11 @@ InputDialog.cs - + UserControl - - MediaSelector.cs + + MediaSelectorPanel.cs Code @@ -386,11 +386,11 @@ LoginDialog.cs - - MediaSelector.cs + + MediaSelectorPanel.cs - - MediaSelector.cs + + MediaSelectorPanel.cs SendErrorReportForm.cs diff --git a/OpenTween/Tween.Designer.cs b/OpenTween/Tween.Designer.cs index 1d002462e..b240d1bee 100644 --- a/OpenTween/Tween.Designer.cs +++ b/OpenTween/Tween.Designer.cs @@ -53,7 +53,7 @@ private void InitializeComponent() this.ToolStripSeparator11 = new System.Windows.Forms.ToolStripSeparator(); this.DeleteTabMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.TabImage = new System.Windows.Forms.ImageList(this.components); - this.ImageSelector = new OpenTween.MediaSelector(); + this.ImageSelector = new OpenTween.MediaSelectorPanel(); this.ProfilePanel = new System.Windows.Forms.Panel(); this.SplitContainer3 = new System.Windows.Forms.SplitContainer(); this.SplitContainer2 = new System.Windows.Forms.SplitContainer(); @@ -2217,7 +2217,7 @@ private void InitializeComponent() internal System.Windows.Forms.ToolStripSeparator ToolStripSeparator11; internal System.Windows.Forms.ToolStripMenuItem DeleteTabMenuItem; internal System.Windows.Forms.ImageList TabImage; - internal MediaSelector ImageSelector; + internal MediaSelectorPanel ImageSelector; internal System.Windows.Forms.Panel ProfilePanel; internal System.Windows.Forms.SplitContainer SplitContainer3; internal System.Windows.Forms.SplitContainer SplitContainer2; diff --git a/OpenTween/Tween.resx b/OpenTween/Tween.resx index 320f436d1..30ef0013e 100644 --- a/OpenTween/Tween.resx +++ b/OpenTween/Tween.resx @@ -156,7 +156,7 @@ System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 ImageSelector SplitContainer1.Panel1 - OpenTween.MediaSelector, OpenTween, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null + OpenTween.MediaSelectorPanel, OpenTween, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null 1 ImageSelectPullDownMenuItem System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 From 44377bd3b07336a98e0c57933429c3602599a363 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sun, 15 Jan 2023 01:22:18 +0900 Subject: [PATCH 05/11] =?UTF-8?q?MediaSelectorPanel=E3=81=A7=E6=B7=BB?= =?UTF-8?q?=E4=BB=98=E3=81=99=E3=82=8B=E7=94=BB=E5=83=8F=E3=81=AE=E4=B8=80?= =?UTF-8?q?=E8=A6=A7=E3=82=92ListView=E3=81=A7=E8=A1=A8=E7=A4=BA=E3=81=99?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween/MediaItem.cs | 9 + OpenTween/MediaSelectorPanel.Designer.cs | 129 +++-- OpenTween/MediaSelectorPanel.cs | 592 +++++++---------------- OpenTween/MediaSelectorPanel.resx | 141 +++--- OpenTween/MemoryImageList.cs | 73 +++ OpenTween/Resources/ChangeLog.txt | 1 + OpenTween/Tween.cs | 11 +- 7 files changed, 392 insertions(+), 564 deletions(-) create mode 100644 OpenTween/MemoryImageList.cs diff --git a/OpenTween/MediaItem.cs b/OpenTween/MediaItem.cs index 695bc2ee1..56e9e19b1 100644 --- a/OpenTween/MediaItem.cs +++ b/OpenTween/MediaItem.cs @@ -32,6 +32,11 @@ namespace OpenTween { public interface IMediaItem { + /// + /// メディアのID + /// + Guid Id { get; } + /// /// メディアへの絶対パス /// @@ -106,6 +111,8 @@ public FileMediaItem(FileInfo fileInfo) { } + public Guid Id { get; } = Guid.NewGuid(); + public string Path => this.FileInfo.FullName; @@ -187,6 +194,8 @@ public MemoryImageMediaItem(MemoryImage image) this.Path = PathPrefix + num + this.image.ImageFormatExt; } + public Guid Id { get; } = Guid.NewGuid(); + public string Path { get; } public string? AltText { get; set; } diff --git a/OpenTween/MediaSelectorPanel.Designer.cs b/OpenTween/MediaSelectorPanel.Designer.cs index ca13adbdc..5310e4fcf 100644 --- a/OpenTween/MediaSelectorPanel.Designer.cs +++ b/OpenTween/MediaSelectorPanel.Designer.cs @@ -7,19 +7,6 @@ partial class MediaSelectorPanel /// private System.ComponentModel.IContainer components = null; - /// - /// 使用中のリソースをすべてクリーンアップします。 - /// - /// マネージ リソースが破棄される場合 true、破棄されない場合は false です。 - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - #region コンポーネント デザイナーで生成されたコード /// @@ -29,63 +16,25 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MediaSelectorPanel)); - this.ImagePathPanel = new System.Windows.Forms.Panel(); - this.ImagefilePathText = new System.Windows.Forms.TextBox(); - this.ImagePageCombo = new System.Windows.Forms.ComboBox(); - this.FilePickButton = new System.Windows.Forms.Button(); this.Label2 = new System.Windows.Forms.Label(); this.ImageServiceCombo = new System.Windows.Forms.ComboBox(); this.ImageCancelButton = new System.Windows.Forms.Button(); this.AlternativeTextPanel = new System.Windows.Forms.Panel(); this.AlternativeTextBox = new System.Windows.Forms.TextBox(); this.AlternativeTextLabel = new System.Windows.Forms.Label(); + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.MediaListView = new System.Windows.Forms.ListView(); + this.panel1 = new System.Windows.Forms.Panel(); + this.AddMediaButton = new System.Windows.Forms.Button(); + this.ServiceSelectPanel = new System.Windows.Forms.Panel(); this.ImageSelectedPicture = new OpenTween.OTPictureBox(); - this.ImagePathPanel.SuspendLayout(); this.AlternativeTextPanel.SuspendLayout(); + this.tableLayoutPanel1.SuspendLayout(); + this.panel1.SuspendLayout(); + this.ServiceSelectPanel.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.ImageSelectedPicture)).BeginInit(); this.SuspendLayout(); // - // ImagePathPanel - // - this.ImagePathPanel.Controls.Add(this.ImagefilePathText); - this.ImagePathPanel.Controls.Add(this.ImagePageCombo); - this.ImagePathPanel.Controls.Add(this.FilePickButton); - this.ImagePathPanel.Controls.Add(this.Label2); - this.ImagePathPanel.Controls.Add(this.ImageServiceCombo); - this.ImagePathPanel.Controls.Add(this.ImageCancelButton); - resources.ApplyResources(this.ImagePathPanel, "ImagePathPanel"); - this.ImagePathPanel.Name = "ImagePathPanel"; - // - // ImagefilePathText - // - resources.ApplyResources(this.ImagefilePathText, "ImagefilePathText"); - this.ImagefilePathText.Name = "ImagefilePathText"; - this.ImagefilePathText.KeyDown += new System.Windows.Forms.KeyEventHandler(this.ImageSelection_KeyDown); - this.ImagefilePathText.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.ImageSelection_KeyPress); - this.ImagefilePathText.PreviewKeyDown += new System.Windows.Forms.PreviewKeyDownEventHandler(this.ImageSelection_PreviewKeyDown); - this.ImagefilePathText.Validating += new System.ComponentModel.CancelEventHandler(this.ImagefilePathText_Validating); - // - // ImagePageCombo - // - resources.ApplyResources(this.ImagePageCombo, "ImagePageCombo"); - this.ImagePageCombo.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.ImagePageCombo.FormattingEnabled = true; - this.ImagePageCombo.Name = "ImagePageCombo"; - this.ImagePageCombo.SelectedIndexChanged += new System.EventHandler(this.ImagePageCombo_SelectedIndexChanged); - this.ImagePageCombo.KeyDown += new System.Windows.Forms.KeyEventHandler(this.ImageSelection_KeyDown); - this.ImagePageCombo.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.ImageSelection_KeyPress); - this.ImagePageCombo.PreviewKeyDown += new System.Windows.Forms.PreviewKeyDownEventHandler(this.ImageSelection_PreviewKeyDown); - // - // FilePickButton - // - resources.ApplyResources(this.FilePickButton, "FilePickButton"); - this.FilePickButton.Name = "FilePickButton"; - this.FilePickButton.UseVisualStyleBackColor = true; - this.FilePickButton.Click += new System.EventHandler(this.FilePickButton_Click); - this.FilePickButton.KeyDown += new System.Windows.Forms.KeyEventHandler(this.ImageSelection_KeyDown); - this.FilePickButton.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.ImageSelection_KeyPress); - this.FilePickButton.PreviewKeyDown += new System.Windows.Forms.PreviewKeyDownEventHandler(this.ImageSelection_PreviewKeyDown); - // // Label2 // resources.ApplyResources(this.Label2, "Label2"); @@ -100,9 +49,6 @@ private void InitializeComponent() resources.GetString("ImageServiceCombo.Items")}); this.ImageServiceCombo.Name = "ImageServiceCombo"; this.ImageServiceCombo.SelectedIndexChanged += new System.EventHandler(this.ImageServiceCombo_SelectedIndexChanged); - this.ImageServiceCombo.KeyDown += new System.Windows.Forms.KeyEventHandler(this.ImageSelection_KeyDown); - this.ImageServiceCombo.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.ImageSelection_KeyPress); - this.ImageServiceCombo.PreviewKeyDown += new System.Windows.Forms.PreviewKeyDownEventHandler(this.ImageSelection_PreviewKeyDown); // // ImageCancelButton // @@ -122,13 +68,53 @@ private void InitializeComponent() // resources.ApplyResources(this.AlternativeTextBox, "AlternativeTextBox"); this.AlternativeTextBox.Name = "AlternativeTextBox"; - this.AlternativeTextBox.Validating += new System.ComponentModel.CancelEventHandler(this.AlternativeTextBox_Validating); + this.AlternativeTextBox.Validated += new System.EventHandler(this.AlternativeTextBox_Validated); // // AlternativeTextLabel // resources.ApplyResources(this.AlternativeTextLabel, "AlternativeTextLabel"); this.AlternativeTextLabel.Name = "AlternativeTextLabel"; // + // tableLayoutPanel1 + // + resources.ApplyResources(this.tableLayoutPanel1, "tableLayoutPanel1"); + this.tableLayoutPanel1.Controls.Add(this.MediaListView, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.panel1, 1, 0); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + // + // MediaListView + // + resources.ApplyResources(this.MediaListView, "MediaListView"); + this.MediaListView.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.None; + this.MediaListView.HideSelection = false; + this.MediaListView.MultiSelect = false; + this.MediaListView.Name = "MediaListView"; + this.MediaListView.ShowGroups = false; + this.MediaListView.UseCompatibleStateImageBehavior = false; + this.MediaListView.SelectedIndexChanged += new System.EventHandler(this.MediaListView_SelectedIndexChanged); + // + // panel1 + // + this.panel1.Controls.Add(this.ImageCancelButton); + this.panel1.Controls.Add(this.AddMediaButton); + this.panel1.Controls.Add(this.ServiceSelectPanel); + resources.ApplyResources(this.panel1, "panel1"); + this.panel1.Name = "panel1"; + // + // AddMediaButton + // + resources.ApplyResources(this.AddMediaButton, "AddMediaButton"); + this.AddMediaButton.Name = "AddMediaButton"; + this.AddMediaButton.UseVisualStyleBackColor = true; + this.AddMediaButton.Click += new System.EventHandler(this.AddMediaButton_Click); + // + // ServiceSelectPanel + // + resources.ApplyResources(this.ServiceSelectPanel, "ServiceSelectPanel"); + this.ServiceSelectPanel.Controls.Add(this.ImageServiceCombo); + this.ServiceSelectPanel.Controls.Add(this.Label2); + this.ServiceSelectPanel.Name = "ServiceSelectPanel"; + // // ImageSelectedPicture // resources.ApplyResources(this.ImageSelectedPicture, "ImageSelectedPicture"); @@ -141,12 +127,14 @@ private void InitializeComponent() this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.Controls.Add(this.ImageSelectedPicture); this.Controls.Add(this.AlternativeTextPanel); - this.Controls.Add(this.ImagePathPanel); + this.Controls.Add(this.tableLayoutPanel1); this.Name = "MediaSelectorPanel"; - this.ImagePathPanel.ResumeLayout(false); - this.ImagePathPanel.PerformLayout(); this.AlternativeTextPanel.ResumeLayout(false); this.AlternativeTextPanel.PerformLayout(); + this.tableLayoutPanel1.ResumeLayout(false); + this.panel1.ResumeLayout(false); + this.panel1.PerformLayout(); + this.ServiceSelectPanel.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.ImageSelectedPicture)).EndInit(); this.ResumeLayout(false); this.PerformLayout(); @@ -156,15 +144,16 @@ private void InitializeComponent() #endregion internal OTPictureBox ImageSelectedPicture; - internal System.Windows.Forms.Panel ImagePathPanel; - internal System.Windows.Forms.TextBox ImagefilePathText; - internal System.Windows.Forms.ComboBox ImagePageCombo; - internal System.Windows.Forms.Button FilePickButton; internal System.Windows.Forms.Label Label2; internal System.Windows.Forms.ComboBox ImageServiceCombo; internal System.Windows.Forms.Button ImageCancelButton; internal System.Windows.Forms.Panel AlternativeTextPanel; internal System.Windows.Forms.TextBox AlternativeTextBox; internal System.Windows.Forms.Label AlternativeTextLabel; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.ListView MediaListView; + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.Button AddMediaButton; + private System.Windows.Forms.Panel ServiceSelectPanel; } } diff --git a/OpenTween/MediaSelectorPanel.cs b/OpenTween/MediaSelectorPanel.cs index d45463d7e..3fbbe8689 100644 --- a/OpenTween/MediaSelectorPanel.cs +++ b/OpenTween/MediaSelectorPanel.cs @@ -102,40 +102,12 @@ public IMediaUploadService GetService(string serviceName) public ICollection GetServices() => this.pictureService.Values; - private class SelectedMedia - { - public IMediaItem? Item { get; set; } - - public MyCommon.UploadFileType Type { get; set; } - - public string Text { get; set; } - - public SelectedMedia(IMediaItem? item, MyCommon.UploadFileType type, string text) - { - this.Item = item; - this.Type = type; - this.Text = text; - } - - public SelectedMedia(string text) - : this(null, MyCommon.UploadFileType.Invalid, text) - { - } - - public bool IsValid - => this.Item != null && this.Type != MyCommon.UploadFileType.Invalid; - - public string Path - => this.Item?.Path ?? ""; - - public string AltText => this.Item?.AltText ?? ""; - - public override string ToString() - => this.Text; - } - private Dictionary pictureService = new(); + private readonly List mediaItems = new(); + private readonly MemoryImageList thumbnailList = new(); + private Guid? selectedMediaItemId = null; + private void CreateServices(Twitter tw, TwitterConfiguration twitterConfig) { this.pictureService?.Clear(); @@ -153,6 +125,13 @@ public MediaSelectorPanel() this.InitializeComponent(); this.ImageSelectedPicture.InitialImage = Properties.Resources.InitialImage; + this.MediaListView.LargeImageList = this.thumbnailList.ImageList; + + var thumbnailWidth = 75 * this.DeviceDpi / 96; + this.thumbnailList.ImageList.ImageSize = new(thumbnailWidth, thumbnailWidth); + + this.UpdateSelectedMedia(); + this.UpdateAltTextPanelVisible(); } /// @@ -163,8 +142,6 @@ public void Initialize(Twitter tw, TwitterConfiguration twitterConfig, string sv this.CreateServices(tw, twitterConfig); this.SetImageServiceCombo(); - this.SetImagePageCombo(); - this.SelectImageServiceComboItem(svc, index); } @@ -220,81 +197,91 @@ private bool IsUploadable(string serviceName, string ext, long? size) return false; } - /// - /// 投稿するファイルとその投稿先を選択するためのコントロールを表示する。 - /// - private void BeginSelection(IMediaItem[] items) + private void ClearMediaItems() { - if (items == null || items.Length == 0) - { - this.BeginSelection(); + this.selectedMediaItemId = null; + this.UpdateSelectedMedia(); + + this.MediaListView.Items.Clear(); + + var mediaItems = this.mediaItems.ToList(); + this.mediaItems.Clear(); + + foreach (var mediaItem in mediaItems) + this.DisposeMediaItem(mediaItem); + + var thumbnailImages = this.thumbnailList.ToList(); + this.thumbnailList.Clear(); + + foreach (var image in thumbnailImages) + image.Dispose(); + } + + public void AddMediaItemFromImage(Image image) + { + var mediaItem = this.CreateMemoryImageMediaItem(image, noMsgBox: false); + if (mediaItem == null) return; - } - var service = this.SelectedService; - if (service == null) return; + this.AddMediaItem(mediaItem); + this.SelectMediaItem(mediaItem.Id); + } - var count = Math.Min(items.Length, service.MaxMediaCount); - if (!this.Visible || count > 1) - { - // 非表示時または複数のファイル指定は新規選択として扱う - this.SetImagePageCombo(); + public void AddMediaItemFromFilePath(string[] filePathArray) + { + if (filePathArray.Length == 0) + return; - this.BeginSelecting?.Invoke(this, EventArgs.Empty); + var mediaItems = new IMediaItem[filePathArray.Length]; - this.Visible = true; - } - this.Enabled = true; + // 連番のファイル名を一括でアップロードする場合の利便性のためソートする + var sortedFilePath = filePathArray.OrderBy(x => x); - if (count == 1) + foreach (var (path, index) in sortedFilePath.WithIndex()) { - this.ImagefilePathText.Text = items[0].Path; - this.AlternativeTextBox.Text = items[0].AltText; - this.ImageFromSelectedFile(items[0], false); - } - else - { - for (var i = 0; i < count; i++) - { - var index = this.ImagePageCombo.Items.Count - 1; - if (index == 0) - { - this.ImagefilePathText.Text = items[i].Path; - this.AlternativeTextBox.Text = items[i].AltText; - } - this.ImageFromSelectedFile(index, items[i], false); - } + var mediaItem = this.CreateFileMediaItem(path, noMsgBox: false); + if (mediaItem == null) + return; + + mediaItems[index] = mediaItem; } + + // 全ての IMediaItem の生成に成功した場合のみ追加する + foreach (var mediaItem in mediaItems) + this.AddMediaItem(mediaItem); + + this.SelectMediaItem(mediaItems.Last().Id); } - /// - /// 投稿するファイルとその投稿先を選択するためのコントロールを表示する(主にD&D用)。 - /// - public void BeginSelection(string[] fileNames) + public void AddMediaItem(IMediaItem item) { - if (fileNames == null || fileNames.Length == 0) + this.mediaItems.Add(item); + + MemoryImage thumbnailImage; + try { - this.BeginSelection(); - return; + thumbnailImage = item.CreateImage(); + } + catch (InvalidImageException) + { + thumbnailImage = MemoryImage.CopyFromImage(Properties.Resources.MultiMediaImage); } - var items = fileNames.Select(x => this.CreateFileMediaItem(x, false)).OfType().ToArray(); - this.BeginSelection(items); + var id = item.Id.ToString(); + this.thumbnailList.Add(id, thumbnailImage); + + this.MediaListView.Items.Add(item.Name, id); } - /// - /// 投稿するファイルとその投稿先を選択するためのコントロールを表示する。 - /// - public void BeginSelection(Image image) + public void SelectMediaItem(Guid id) { - if (image == null) - { - this.BeginSelection(); + var index = this.mediaItems.FindIndex(x => x.Id == id); + if (index == -1) return; - } - var items = new[] { this.CreateMemoryImageMediaItem(image, false) }.OfType().ToArray(); - this.BeginSelection(items); + // selectedMediaItemId は ImageListView のイベントハンドラ内でセットされる + this.MediaListView.SelectedIndices.Clear(); + this.MediaListView.SelectedIndices.Add(index); } /// @@ -302,17 +289,9 @@ public void BeginSelection(Image image) /// public void BeginSelection() { - if (!this.Visible) - { - this.BeginSelecting?.Invoke(this, EventArgs.Empty); - - this.Visible = true; - this.Enabled = true; - - var media = (SelectedMedia)this.ImagePageCombo.SelectedItem; - this.ImageFromSelectedFile(media.Item, true); - this.ImagefilePathText.Focus(); - } + this.BeginSelecting?.Invoke(this, EventArgs.Empty); + this.Enabled = true; + this.Visible = true; } /// @@ -320,18 +299,10 @@ public void BeginSelection() /// public void EndSelection() { - if (this.Visible) - { - this.ImagefilePathText.CausesValidation = false; - - this.EndSelecting?.Invoke(this, EventArgs.Empty); - - this.Visible = false; - this.Enabled = false; - this.ClearImageSelectedPicture(); - - this.ImagefilePathText.CausesValidation = true; - } + this.EndSelecting?.Invoke(this, EventArgs.Empty); + this.Visible = false; + this.Enabled = false; + this.ClearMediaItems(); } /// @@ -339,41 +310,29 @@ public void EndSelection() /// public bool TryGetSelectedMedia([NotNullWhen(true)] out string? imageService, [NotNullWhen(true)] out IMediaItem[]? mediaItems) { - var validItems = this.ImagePageCombo.Items.Cast() - .Where(x => x.IsValid).Select(x => x.Item).OfType().ToArray(); + imageService = null; + mediaItems = null; - if (validItems.Length > 0 && - this.ImageServiceCombo.SelectedIndex > -1) + var uploadService = this.SelectedService; + if (uploadService == null || this.mediaItems.Count == 0) { - var serviceName = this.ServiceName; - if (MessageBox.Show(string.Format(Properties.Resources.PostPictureConfirm1, serviceName, validItems.Length), - Properties.Resources.PostPictureConfirm2, - MessageBoxButtons.OKCancel, - MessageBoxIcon.Question, - MessageBoxDefaultButton.Button1) - == DialogResult.OK) - { - // 収集した MediaItem が破棄されないように、予め null を代入しておく - foreach (SelectedMedia media in this.ImagePageCombo.Items) - { - if (media != null) media.Item = null; - } - - imageService = serviceName; - mediaItems = validItems; - this.EndSelection(); - this.SetImagePageCombo(); - return true; - } + MessageBox.Show(Properties.Resources.PostPictureWarn1, Properties.Resources.PostPictureWarn2); + return false; } - else + + foreach (var mediaItem in this.mediaItems) { - MessageBox.Show(Properties.Resources.PostPictureWarn1, Properties.Resources.PostPictureWarn2); + if (!this.ValidateMediaItem(uploadService, mediaItem)) + return false; } - imageService = null; - mediaItems = null; - return false; + // 収集した MediaItem が破棄されないように、ClearMediaItems を呼ぶ前に mediaItems を空にしておく + this.mediaItems.Clear(); + + imageService = this.ServiceName; + mediaItems = this.mediaItems.ToArray(); + this.EndSelection(); + return true; } private MemoryImageMediaItem? CreateMemoryImageMediaItem(Image image, bool noMsgBox) @@ -412,34 +371,13 @@ public bool TryGetSelectedMedia([NotNullWhen(true)] out string? imageService, [N } } - private void ValidateNewFileMediaItem(string path, string altText, bool noMsgBox) - { - var media = (SelectedMedia)this.ImagePageCombo.SelectedItem; - var item = media.Item; - - if (path != media.Path) - { - this.DisposeMediaItem(media.Item); - media.Item = null; - - item = this.CreateFileMediaItem(path, noMsgBox); - } - - if (item != null) - item.AltText = altText; - - this.ImagefilePathText.Text = path; - this.AlternativeTextBox.Text = altText; - this.ImageFromSelectedFile(item, noMsgBox); - } - private void DisposeMediaItem(IMediaItem? item) { var disposableItem = item as IDisposable; disposableItem?.Dispose(); } - private void FilePickButton_Click(object sender, EventArgs e) + private void AddMediaButton_Click(object sender, EventArgs e) { var service = this.SelectedService; @@ -459,108 +397,39 @@ private void FilePickButton_Click(object sender, EventArgs e) this.FilePickDialogClosed?.Invoke(this, EventArgs.Empty); } - this.ValidateNewFileMediaItem(this.FilePickDialog.FileName, this.AlternativeTextBox.Text.Trim(), false); - } - - private void ImagefilePathText_Validating(object sender, CancelEventArgs e) - { - if (this.ImageCancelButton.Focused) - { - this.ImagefilePathText.CausesValidation = false; - return; - } - - this.ValidateNewFileMediaItem(this.ImagefilePathText.Text.Trim(), this.AlternativeTextBox.Text.Trim(), false); + this.AddMediaItemFromFilePath(this.FilePickDialog.FileNames); } - private void ImageFromSelectedFile(IMediaItem? item, bool noMsgBox) - => this.ImageFromSelectedFile(-1, item, noMsgBox); - - private void ImageFromSelectedFile(int index, IMediaItem? item, bool noMsgBox) + private bool ValidateMediaItem(IMediaUploadService imageService, IMediaItem item) { - var valid = false; + var ext = item.Extension; + var size = item.Size; - try + if (!imageService.CheckFileExtension(ext)) { - var imageService = this.SelectedService; - if (imageService == null) return; - - var selectedIndex = this.ImagePageCombo.SelectedIndex; - if (index < 0) index = selectedIndex; - - if (index >= this.ImagePageCombo.Items.Count) - throw new ArgumentOutOfRangeException(nameof(index)); - - var isSelectedPage = index == selectedIndex; - - if (isSelectedPage) - this.ClearImageSelectedPicture(); - - if (item == null || MyCommon.IsNullOrEmpty(item.Path)) return; - - try - { - var ext = item.Extension; - var size = item.Size; - - if (!imageService.CheckFileExtension(ext)) - { - // 画像以外の形式 - if (!noMsgBox) - { - MessageBox.Show( - string.Format(Properties.Resources.PostPictureWarn3, this.ServiceName, this.MakeAvailableServiceText(ext, size), ext, item.Name), - Properties.Resources.PostPictureWarn4, - MessageBoxButtons.OK, - MessageBoxIcon.Warning); - } - return; - } - - if (!imageService.CheckFileSize(ext, size)) - { - // ファイルサイズが大きすぎる - if (!noMsgBox) - { - MessageBox.Show( - string.Format(Properties.Resources.PostPictureWarn5, this.ServiceName, this.MakeAvailableServiceText(ext, size), item.Name), - Properties.Resources.PostPictureWarn4, - MessageBoxButtons.OK, - MessageBoxIcon.Warning); - } - return; - } - - if (item.IsImage) - { - if (isSelectedPage) - this.ImageSelectedPicture.Image = item.CreateImage(); - this.SetImagePage(index, item, MyCommon.UploadFileType.Picture); - } - else - { - this.SetImagePage(index, item, MyCommon.UploadFileType.MultiMedia); - } - - valid = true; // 正常終了 - } - catch (FileNotFoundException) - { - if (!noMsgBox) MessageBox.Show("File not found."); - } - catch (Exception) - { - if (!noMsgBox) MessageBox.Show("The type of this file is not image."); - } + // 画像以外の形式 + MessageBox.Show( + string.Format(Properties.Resources.PostPictureWarn3, this.ServiceName, this.MakeAvailableServiceText(ext, size), ext, item.Name), + Properties.Resources.PostPictureWarn4, + MessageBoxButtons.OK, + MessageBoxIcon.Warning + ); + return false; } - finally + + if (!imageService.CheckFileSize(ext, size)) { - if (!valid) - { - this.ClearImagePage(index); - this.DisposeMediaItem(item); - } + // ファイルサイズが大きすぎる + MessageBox.Show( + string.Format(Properties.Resources.PostPictureWarn5, this.ServiceName, this.MakeAvailableServiceText(ext, size), item.Name), + Properties.Resources.PostPictureWarn4, + MessageBoxButtons.OK, + MessageBoxIcon.Warning + ); + return false; } + + return true; } private string MakeAvailableServiceText(string ext, long fileSize) @@ -578,43 +447,9 @@ private string MakeAvailableServiceText(string ext, long fileSize) return text; } - private void ClearImageSelectedPicture() - { - var oldImage = this.ImageSelectedPicture.Image; - this.ImageSelectedPicture.Image = null; - oldImage?.Dispose(); - - this.ImageSelectedPicture.ShowInitialImage(); - } - private void ImageCancelButton_Click(object sender, EventArgs e) => this.EndSelection(); - private void ImageSelection_KeyDown(object sender, KeyEventArgs e) - { - if (e.KeyCode == Keys.Escape) - { - this.EndSelection(); - } - } - - private void ImageSelection_KeyPress(object sender, KeyPressEventArgs e) - { - if (Convert.ToInt32(e.KeyChar) == 0x1B) - { - this.ImagefilePathText.CausesValidation = false; - e.Handled = true; - } - } - - private void ImageSelection_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e) - { - if (e.KeyCode == Keys.Escape) - { - this.ImagefilePathText.CausesValidation = false; - } - } - private void SetImageServiceCombo() { using (ControlTransaction.Update(this.ImageServiceCombo)) @@ -668,167 +503,68 @@ private void UpdateAltTextPanelVisible() var service => service.CanUseAltText, }; - private void ImageServiceCombo_SelectedIndexChanged(object sender, EventArgs e) + private void UpdateSelectedMedia() { - if (this.Visible) + using (ControlTransaction.Update(this)) { - var imageService = this.SelectedService; - if (imageService != null) + if (this.selectedMediaItemId == null) { - this.UpdateAltTextPanelVisible(); - - if (this.ImagePageCombo.Items.Count > 0) - { - // 画像が選択された投稿先に対応しているかをチェックする - // TODO: 複数の選択済み画像があるなら、できれば全てを再チェックしたほうがいい - if (this.ServiceName == "Twitter") - { - this.ValidateSelectedImagePage(); - } - else - { - if (this.ImagePageCombo.Items.Count > 1) - { - // 複数の選択済み画像のうち、1枚目のみを残す - this.SetImagePageCombo((SelectedMedia)this.ImagePageCombo.Items[0]); - } - else - { - this.ImagePageCombo.Enabled = false; - var valid = false; - - try - { - var item = ((SelectedMedia)this.ImagePageCombo.Items[0]).Item; - if (item != null) - { - var ext = item.Extension; - if (imageService.CheckFileExtension(ext) && - imageService.CheckFileSize(ext, item.Size)) - { - valid = true; - } - } - } - catch - { - } - finally - { - if (!valid) - { - this.ClearImageSelectedPicture(); - this.ClearSelectedImagePage(); - } - } - } - } - } + this.AlternativeTextBox.Text = ""; + this.AlternativeTextPanel.Enabled = false; + this.ImageSelectedPicture.ShowInitialImage(); } - } - - this.SelectedServiceChanged?.Invoke(this, EventArgs.Empty); - } - - private void SetImagePageCombo(SelectedMedia? media = null) - { - using (ControlTransaction.Update(this.ImagePageCombo)) - { - this.ImagePageCombo.Enabled = false; - - foreach (SelectedMedia oldMedia in this.ImagePageCombo.Items) + else { - if (oldMedia == null || oldMedia == media) continue; - this.DisposeMediaItem(oldMedia.Item); - } - this.ImagePageCombo.Items.Clear(); + var media = this.mediaItems.First(x => x.Id == this.selectedMediaItemId); - if (media == null) - media = new SelectedMedia("1"); - - this.ImagePageCombo.Items.Add(media); - this.ImagefilePathText.Text = media.Path; - this.AlternativeTextBox.Text = media.AltText; - - this.ImagePageCombo.SelectedIndex = 0; + this.AlternativeTextBox.Text = media.AltText; + this.AlternativeTextPanel.Enabled = true; + this.ImageSelectedPicture.Image = media.CreateImage(); + } } } - private void AddNewImagePage(int selectedIndex) + private void ImageServiceCombo_SelectedIndexChanged(object sender, EventArgs e) { - var service = this.SelectedService; - if (service == null) return; - - if (selectedIndex < service.MaxMediaCount - 1) - { - // 投稿先の投稿可能枚数まで選択できるようにする - var count = this.ImagePageCombo.Items.Count; - if (selectedIndex == count - 1) - { - count++; - this.ImagePageCombo.Items.Add(new SelectedMedia(count.ToString())); - this.ImagePageCombo.Enabled = true; - } - } + this.UpdateAltTextPanelVisible(); + this.SelectedServiceChanged?.Invoke(this, EventArgs.Empty); } - private void SetSelectedImagePage(IMediaItem item, MyCommon.UploadFileType type) - => this.SetImagePage(-1, item, type); - - private void SetImagePage(int index, IMediaItem item, MyCommon.UploadFileType type) + private void MediaListView_SelectedIndexChanged(object sender, EventArgs e) { - var selectedIndex = this.ImagePageCombo.SelectedIndex; - if (index < 0) index = selectedIndex; - - var media = (SelectedMedia)this.ImagePageCombo.Items[index]; - if (media.Item != item) + var indices = this.MediaListView.SelectedIndices; + if (indices.Count == 0) + { + this.selectedMediaItemId = null; + } + else { - this.DisposeMediaItem(media.Item); - media.Item = item; + var media = this.mediaItems[indices[0]]; + this.selectedMediaItemId = media.Id; } - media.Type = type; - this.AddNewImagePage(index); + this.UpdateSelectedMedia(); } - private void ClearSelectedImagePage() - => this.ClearImagePage(-1); - - private void ClearImagePage(int index) + private void AlternativeTextBox_Validated(object sender, EventArgs e) { - var selectedIndex = this.ImagePageCombo.SelectedIndex; - if (index < 0) index = selectedIndex; - - var media = (SelectedMedia)this.ImagePageCombo.Items[index]; - this.DisposeMediaItem(media.Item); - media.Item = null; - media.Type = MyCommon.UploadFileType.Invalid; + if (this.selectedMediaItemId == null) + return; - if (index == selectedIndex) - { - this.ImagefilePathText.Text = ""; - this.AlternativeTextBox.Text = ""; - } + var media = this.mediaItems.First(x => x.Id == this.selectedMediaItemId); + media.AltText = this.AlternativeTextBox.Text.Trim(); } - private void ValidateSelectedImagePage() + protected override void Dispose(bool disposing) { - var idx = this.ImagePageCombo.SelectedIndex; - var media = (SelectedMedia)this.ImagePageCombo.Items[idx]; - this.ImageServiceCombo.Enabled = idx == 0; // idx == 0 以外では投稿先サービスを選べないようにする - this.ImagefilePathText.Text = media.Path; - this.AlternativeTextBox.Text = media.AltText; - this.ImageFromSelectedFile(media.Item, true); - } - - private void ImagePageCombo_SelectedIndexChanged(object sender, EventArgs e) - => this.ValidateSelectedImagePage(); + if (disposing) + { + this.ClearMediaItems(); + this.thumbnailList.Dispose(); + this.components?.Dispose(); + } - private void AlternativeTextBox_Validating(object sender, CancelEventArgs e) - { - var imageFilePath = this.ImagefilePathText.Text.Trim(); - var altText = this.AlternativeTextBox.Text.Trim(); - this.ValidateNewFileMediaItem(imageFilePath, altText, noMsgBox: false); + base.Dispose(disposing); } } } diff --git a/OpenTween/MediaSelectorPanel.resx b/OpenTween/MediaSelectorPanel.resx index 94627aa85..1bbfcbd01 100644 --- a/OpenTween/MediaSelectorPanel.resx +++ b/OpenTween/MediaSelectorPanel.resx @@ -12,6 +12,10 @@ 608, 280 MediaSelectorPanel System.Windows.Forms.UserControl, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + AddMediaButton + panel1 + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + 1 AlternativeTextBox AlternativeTextPanel System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 @@ -24,96 +28,107 @@ $this System.Windows.Forms.Panel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 1 - FilePickButton - ImagePathPanel - System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - 2 ImageCancelButton - ImagePathPanel + panel1 System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - 5 - ImagefilePathText - ImagePathPanel - System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - 0 - ImagePageCombo - ImagePathPanel - System.Windows.Forms.ComboBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - 1 - ImagePathPanel - $this - System.Windows.Forms.Panel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - 2 + 0 ImageSelectedPicture $this - OpenTween.OTPictureBox, OpenTween, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null + OpenTween.OTPictureBox, OpenTween, Version=3.1.0.1, Culture=neutral, PublicKeyToken=null 0 ImageServiceCombo - ImagePathPanel + ServiceSelectPanel System.Windows.Forms.ComboBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - 4 + 0 Label2 - ImagePathPanel + ServiceSelectPanel System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - 3 + 1 + MediaListView + tableLayoutPanel1 + System.Windows.Forms.ListView, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + 0 + panel1 + tableLayoutPanel1 + System.Windows.Forms.Panel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + 1 + ServiceSelectPanel + panel1 + System.Windows.Forms.Panel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + 2 + tableLayoutPanel1 + $this + System.Windows.Forms.TableLayoutPanel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + 2 + Top, Left, Right + NoControl + 0, 23 + 0, 3, 0, 0 + 167, 23 + 1 + 画像を追加... Top, Left, Right 109, 3 - 496, 19 + True + Vertical + 496, 40 1 Top, Bottom, Left NoControl 3, 3 - 100, 19 + 100, 40 0 代替テキスト(&A): MiddleRight True Bottom - 0, 227 - 608, 25 - 2 - False - Right - NoControl - 358, 3 - 23, 22 - 2 - ... - Right + 0, 128 + 608, 46 + 0 + Top, Left, Right NoControl - 545, 3 - 60, 22 - 5 - Cancel - Fill - 61, 3 - 297, 19 - 1 - Left - 3, 3 - 58, 20 - 0 - Bottom - 0, 252 - 3, 3, 3, 3 - 608, 28 - 0 + 0, 49 + 0, 3, 0, 0 + 167, 22 + 2 + 閉じる Fill NoControl 0, 0 - 608, 227 + 608, 128 Zoom 0 - Right + Fill Twitter - 442, 3 - 103, 20 - 4 - Right + 61, 0 + 106, 20 + 1 + Left NoControl - 381, 3 - 61, 22 - 3 + 0, 0 + 61, 20 + 0 投稿先 - MiddleRight + MiddleLeft + Fill + 3, 3 + 429, 100 + 0 + Fill + 438, 3 + 167, 100 + 1 + Top, Left, Right + True + 0, 0 + 0, 0, 0, 0 + 167, 20 + 0 + 2 + Bottom + <?xml version="1.0" encoding="utf-16"?><TableLayoutSettings><Controls><Control Name="MediaListView" Row="0" RowSpan="1" Column="0" ColumnSpan="1" /><Control Name="panel1" Row="0" RowSpan="1" Column="1" ColumnSpan="1" /></Controls><Columns Styles="Percent,100,Absolute,173" /><Rows Styles="Percent,100" /></TableLayoutSettings> + 0, 174 + 1 + 608, 106 + 1 diff --git a/OpenTween/MemoryImageList.cs b/OpenTween/MemoryImageList.cs new file mode 100644 index 000000000..c565486c6 --- /dev/null +++ b/OpenTween/MemoryImageList.cs @@ -0,0 +1,73 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// 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 3 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 , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +#nullable enable + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Windows.Forms; + +namespace OpenTween +{ + /// + /// の画像に を使用するためのラッパー + /// + public sealed class MemoryImageList : IDisposable, IEnumerable + { + private readonly ImageList innerImageList = new(); + private readonly Dictionary images = new(); + + public ImageList ImageList + => this.innerImageList; + + public void Add(string key, MemoryImage image) + { + this.images.Add(key, image); + this.innerImageList.Images.Add(key, image.Image); + } + + public void Remove(string key) + { + this.images.Remove(key); + this.innerImageList.Images.RemoveByKey(key); + } + + public void Clear() + { + this.images.Clear(); + this.innerImageList.Images.Clear(); + } + + public void Dispose() + { + // MemoryImage インスタンスの破棄は行わない + this.Clear(); + this.innerImageList.Dispose(); + } + + public IEnumerator GetEnumerator() + => this.images.Values.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + => this.GetEnumerator(); + } +} diff --git a/OpenTween/Resources/ChangeLog.txt b/OpenTween/Resources/ChangeLog.txt index 9cd5a3774..c48b48298 100644 --- a/OpenTween/Resources/ChangeLog.txt +++ b/OpenTween/Resources/ChangeLog.txt @@ -1,6 +1,7 @@ 更新履歴 ==== Unreleased + * NEW: 複数枚の画像を添付する際にリスト上で画像を確認できるようになりました * CHG: アカウント追加時の認可関連のエラーメッセージがより詳細になるように変更 ==== Ver 3.1.0(2023/01/14) diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index 87689122b..cc726ae40 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -9180,7 +9180,10 @@ private void SelectMedia_DragDrop(DragEventArgs e) { this.Activate(); this.BringToFront(); - this.ImageSelector.BeginSelection((string[])e.Data.GetData(DataFormats.FileDrop, false)); + + var filePathArray = (string[])e.Data.GetData(DataFormats.FileDrop, false); + this.ImageSelector.BeginSelection(); + this.ImageSelector.AddMediaItemFromFilePath(filePathArray); this.StatusText.Focus(); } @@ -9231,12 +9234,14 @@ private void ProcClipboardFromStatusTextWhenCtrlPlusV() { // clipboardから画像を取得 using var image = Clipboard.GetImage(); - this.ImageSelector.BeginSelection(image); + this.ImageSelector.BeginSelection(); + this.ImageSelector.AddMediaItemFromImage(image); } else if (Clipboard.ContainsFileDropList()) { var files = Clipboard.GetFileDropList().Cast().ToArray(); - this.ImageSelector.BeginSelection(files); + this.ImageSelector.BeginSelection(); + this.ImageSelector.AddMediaItemFromFilePath(files); } } catch (ExternalException ex) From 0c5fff5f7c63c4272c4d5ac7611f94d4d64ca318 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Thu, 19 Jan 2023 07:14:49 +0900 Subject: [PATCH 06/11] =?UTF-8?q?MediaSelector=E3=82=AF=E3=83=A9=E3=82=B9?= =?UTF-8?q?=E3=81=ABWinForms=E3=81=AB=E4=BE=9D=E5=AD=98=E3=81=97=E3=81=AA?= =?UTF-8?q?=E3=81=84=E5=87=A6=E7=90=86=E3=82=92=E5=88=86=E9=9B=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/MediaSelectorTest.cs | 450 +++++++----------------- OpenTween/Extensions.cs | 13 + OpenTween/MediaSelector.cs | 341 ++++++++++++++++++ OpenTween/MediaSelectorPanel.cs | 500 ++++++--------------------- OpenTween/Tween.cs | 26 +- 5 files changed, 608 insertions(+), 722 deletions(-) create mode 100644 OpenTween/MediaSelector.cs diff --git a/OpenTween.Tests/MediaSelectorTest.cs b/OpenTween.Tests/MediaSelectorTest.cs index 203085597..e8e101a29 100644 --- a/OpenTween.Tests/MediaSelectorTest.cs +++ b/OpenTween.Tests/MediaSelectorTest.cs @@ -1,13 +1,33 @@ -using System; +// OpenTween - Client of Twitter +// Copyright (c) 2014 spx (@5px) +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// 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 3 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 , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; using System.Collections.Generic; -using System.ComponentModel; using System.Drawing; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Text; -using System.Text.RegularExpressions; using Moq; using OpenTween.Api; using OpenTween.Api.DataModel; @@ -29,438 +49,224 @@ private void MyCommonSetup() } [Fact] - public void Initialize_TwitterTest() + public void SelectMediaService_TwitterTest() { using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelectorPanel(); + using var mediaSelector = new MediaSelector(); twitter.Initialize("", "", "", 0L); - mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); + mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); + mediaSelector.SelectMediaService("Twitter"); - Assert.NotEqual(-1, mediaSelector.ImageServiceCombo.Items.IndexOf("Twitter")); + Assert.Contains(mediaSelector.MediaServices, x => x.Key == "Twitter"); // 投稿先に Twitter が選択されている - Assert.Equal("Twitter", mediaSelector.ImageServiceCombo.Text); + Assert.Equal("Twitter", mediaSelector.SelectedMediaServiceName); - // ページ番号が初期化された状態 - var pages = mediaSelector.ImagePageCombo.Items; - Assert.Equal(new[] { "1" }, pages.Cast().Select(x => x.ToString())); - - // 代替テキストの入力欄が表示された状態 - Assert.True(mediaSelector.AlternativeTextPanel.Visible); + // 代替テキストが入力可能な状態 + Assert.True(mediaSelector.CanUseAltText); } [Fact] - public void Initialize_ImgurTest() + public void SelectMediaService_ImgurTest() { using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelectorPanel(); + using var mediaSelector = new MediaSelector(); twitter.Initialize("", "", "", 0L); - mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Imgur"); + mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); + mediaSelector.SelectMediaService("Imgur"); // 投稿先に Imgur が選択されている - Assert.Equal("Imgur", mediaSelector.ImageServiceCombo.Text); - - // ページ番号が初期化された状態 - var pages = mediaSelector.ImagePageCombo.Items; - Assert.Equal(new[] { "1" }, pages.Cast().Select(x => x.ToString())); - - // 代替テキストの入力欄が非表示の状態 - Assert.False(mediaSelector.AlternativeTextPanel.Visible); - } - - [Fact] - public void BeginSelection_BlankTest() - { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); - using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelectorPanel { Visible = false, Enabled = false }; - twitter.Initialize("", "", "", 0L); - mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); - - Assert.Raises( - x => mediaSelector.BeginSelecting += x, - x => mediaSelector.BeginSelecting -= x, - () => mediaSelector.BeginSelection() - ); - - Assert.True(mediaSelector.Visible); - Assert.True(mediaSelector.Enabled); + Assert.Equal("Imgur", mediaSelector.SelectedMediaServiceName); - // 1 ページ目のみ選択可能な状態 - var pages = mediaSelector.ImagePageCombo.Items; - Assert.Equal(new[] { "1" }, pages.Cast().Select(x => x.ToString())); - - // 1 ページ目が表示されている - Assert.Equal("1", mediaSelector.ImagePageCombo.Text); - Assert.Equal("", mediaSelector.ImagefilePathText.Text); - Assert.Null(mediaSelector.ImageSelectedPicture.Image); + // 代替テキストが入力できない状態 + Assert.False(mediaSelector.CanUseAltText); } [Fact] - public void BeginSelection_FilePathTest() + public void AddMediaItem_FilePath_SingleTest() { using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelectorPanel { Visible = false, Enabled = false }; + using var mediaSelector = new MediaSelector(); twitter.Initialize("", "", "", 0L); - mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); + mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); + mediaSelector.SelectMediaService("Twitter"); var images = new[] { "Resources/re.gif" }; + mediaSelector.AddMediaItemFromFilePath(images); - Assert.Raises( - x => mediaSelector.BeginSelecting += x, - x => mediaSelector.BeginSelecting -= x, - () => mediaSelector.BeginSelection(images) - ); - - Assert.True(mediaSelector.Visible); - Assert.True(mediaSelector.Enabled); - - // 2 ページ目まで選択可能な状態 - var pages = mediaSelector.ImagePageCombo.Items; - Assert.Equal(new[] { "1", "2" }, pages.Cast().Select(x => x.ToString())); + // 画像が 1 つ追加された状態 + Assert.Single(mediaSelector.MediaItems); - // 1 ページ目が表示されている - Assert.Equal("1", mediaSelector.ImagePageCombo.Text); - Assert.Equal(Path.GetFullPath("Resources/re.gif"), mediaSelector.ImagefilePathText.Text); + // 1 枚目の画像が表示されている + Assert.Equal(0, mediaSelector.SelectedMediaItemIndex); + Assert.Equal(Path.GetFullPath("Resources/re.gif"), mediaSelector.SelectedMediaItem!.Path); using var imageStream = File.OpenRead("Resources/re.gif"); - using var image = MemoryImage.CopyFromStream(imageStream); - Assert.Equal(image, mediaSelector.ImageSelectedPicture.Image); + using var expectedImage = MemoryImage.CopyFromStream(imageStream); + using var actualImage = mediaSelector.SelectedMediaItem.CreateImage(); + Assert.Equal(expectedImage, actualImage); } [Fact] - public void BeginSelection_MemoryImageTest() + public void AddMediaItem_MemoryImageTest() { using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelectorPanel { Visible = false, Enabled = false }; + using var mediaSelector = new MediaSelector(); twitter.Initialize("", "", "", 0L); - mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); + mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); + mediaSelector.SelectMediaService("Twitter"); using (var bitmap = new Bitmap(width: 200, height: 200)) - { - Assert.Raises( - x => mediaSelector.BeginSelecting += x, - x => mediaSelector.BeginSelecting -= x, - () => mediaSelector.BeginSelection(bitmap) - ); - } - - Assert.True(mediaSelector.Visible); - Assert.True(mediaSelector.Enabled); + mediaSelector.AddMediaItemFromImage(bitmap); - // 2 ページ目まで選択可能な状態 - var pages = mediaSelector.ImagePageCombo.Items; - Assert.Equal(new[] { "1", "2" }, pages.Cast().Select(x => x.ToString())); + // 画像が 1 つ追加された状態 + Assert.Single(mediaSelector.MediaItems); - // 1 ページ目が表示されている - Assert.Equal("1", mediaSelector.ImagePageCombo.Text); - Assert.Matches(@"^<>MemoryImage://\d+.png$", mediaSelector.ImagefilePathText.Text); + // 1 枚目の画像が表示されている + Assert.Equal(0, mediaSelector.SelectedMediaItemIndex); + Assert.Matches(@"^<>MemoryImage://\d+.png$", mediaSelector.SelectedMediaItem!.Path); using (var bitmap = new Bitmap(width: 200, height: 200)) { - using var image = MemoryImage.CopyFromImage(bitmap); - Assert.Equal(image, mediaSelector.ImageSelectedPicture.Image); + using var expectedImage = MemoryImage.CopyFromImage(bitmap); + using var actualImage = mediaSelector.SelectedMediaItem.CreateImage(); + Assert.Equal(expectedImage, actualImage); } } [Fact] - public void BeginSelection_MultiImageTest() + public void AddMediaItem_FilePath_MultipleTest() { using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelectorPanel { Visible = false, Enabled = false }; + using var mediaSelector = new MediaSelector(); twitter.Initialize("", "", "", 0L); - mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); + mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); + mediaSelector.SelectMediaService("Twitter"); var images = new[] { "Resources/re.gif", "Resources/re1.png" }; - mediaSelector.BeginSelection(images); + mediaSelector.AddMediaItemFromFilePath(images); - // 3 ページ目まで選択可能な状態 - var pages = mediaSelector.ImagePageCombo.Items; - Assert.Equal(new[] { "1", "2", "3" }, pages.Cast().Select(x => x.ToString())); + // 画像が 2 つ追加された状態 + Assert.Equal(2, mediaSelector.MediaItems.Count); - // 1 ページ目が表示されている - Assert.Equal("1", mediaSelector.ImagePageCombo.Text); - Assert.Equal(Path.GetFullPath("Resources/re.gif"), mediaSelector.ImagefilePathText.Text); + // 最後の画像(2 枚目)が表示されている + Assert.Equal(1, mediaSelector.SelectedMediaItemIndex); + Assert.Equal(Path.GetFullPath("Resources/re1.png"), mediaSelector.SelectedMediaItem!.Path); - using var imageStream = File.OpenRead("Resources/re.gif"); - using var image = MemoryImage.CopyFromStream(imageStream); - Assert.Equal(image, mediaSelector.ImageSelectedPicture.Image); + using var imageStream = File.OpenRead("Resources/re1.png"); + using var expectedImage = MemoryImage.CopyFromStream(imageStream); + using var actualImage = mediaSelector.SelectedMediaItem.CreateImage(); + Assert.Equal(expectedImage, actualImage); } [Fact] - public void EndSelection_Test() + public void ClearMediaItems_Test() { using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelectorPanel { Visible = false, Enabled = false }; + using var mediaSelector = new MediaSelector(); twitter.Initialize("", "", "", 0L); - mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); - mediaSelector.BeginSelection(new[] { "Resources/re.gif" }); + mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); + mediaSelector.SelectMediaService("Twitter"); - var displayImage = mediaSelector.ImageSelectedPicture.Image; // 表示中の画像 + mediaSelector.AddMediaItemFromFilePath(new[] { "Resources/re.gif" }); - Assert.Raises( - x => mediaSelector.EndSelecting += x, - x => mediaSelector.EndSelecting -= x, - () => mediaSelector.EndSelection() - ); + var thumbnailImages = mediaSelector.ThumbnailList.ToArray(); // 表示中の画像 - Assert.False(mediaSelector.Visible); - Assert.False(mediaSelector.Enabled); + mediaSelector.ClearMediaItems(); - Assert.True(displayImage!.IsDisposed); + Assert.True(thumbnailImages.All(x => x.IsDisposed)); } [Fact] - public void PageChange_Test() + public void DetachMediaItems_Test() { using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelectorPanel { Visible = false, Enabled = false }; + using var mediaSelector = new MediaSelector(); twitter.Initialize("", "", "", 0L); - mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); - - var images = new[] { "Resources/re.gif", "Resources/re1.png" }; - mediaSelector.BeginSelection(images); - - mediaSelector.ImagePageCombo.SelectedIndex = 0; - - // 1 ページ目 - Assert.Equal("1", mediaSelector.ImagePageCombo.Text); - Assert.Equal(Path.GetFullPath("Resources/re.gif"), mediaSelector.ImagefilePathText.Text); - - using (var imageStream = File.OpenRead("Resources/re.gif")) - { - using var image = MemoryImage.CopyFromStream(imageStream); - Assert.Equal(image, mediaSelector.ImageSelectedPicture.Image); - } - - mediaSelector.ImagePageCombo.SelectedIndex = 1; - - // 2 ページ目 - Assert.Equal("2", mediaSelector.ImagePageCombo.Text); - Assert.Equal(Path.GetFullPath("Resources/re1.png"), mediaSelector.ImagefilePathText.Text); - - using (var imageStream = File.OpenRead("Resources/re1.png")) - { - using var image = MemoryImage.CopyFromStream(imageStream); - Assert.Equal(image, mediaSelector.ImageSelectedPicture.Image); - } - - mediaSelector.ImagePageCombo.SelectedIndex = 2; - - // 3 ページ目 (新規ページ) - Assert.Equal("3", mediaSelector.ImagePageCombo.Text); - Assert.Equal("", mediaSelector.ImagefilePathText.Text); - Assert.Null(mediaSelector.ImageSelectedPicture.Image); - } - - [Fact] - public void PageChange_AlternativeTextTest() - { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); - using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelectorPanel { Visible = false, Enabled = false }; - twitter.Initialize("", "", "", 0L); - mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); - - var images = new[] { "Resources/re.gif", "Resources/re1.png" }; - mediaSelector.BeginSelection(images); - - // 1 ページ目 - mediaSelector.ImagePageCombo.SelectedIndex = 0; - mediaSelector.AlternativeTextBox.Text = "Page 1"; - mediaSelector.ValidateChildren(); + mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); + mediaSelector.SelectMediaService("Twitter"); - // 2 ページ目 - mediaSelector.ImagePageCombo.SelectedIndex = 1; - mediaSelector.AlternativeTextBox.Text = "Page 2"; - mediaSelector.ValidateChildren(); + mediaSelector.AddMediaItemFromFilePath(new[] { "Resources/re.gif" }); - // 3 ページ目 (新規ページ) - mediaSelector.ImagePageCombo.SelectedIndex = 2; - mediaSelector.AlternativeTextBox.Text = "Page 3"; - mediaSelector.ValidateChildren(); + var thumbnailImages = mediaSelector.ThumbnailList.ToArray(); - mediaSelector.ImagePageCombo.SelectedIndex = 0; - Assert.Equal("Page 1", mediaSelector.AlternativeTextBox.Text); + var detachedMediaItems = mediaSelector.DetachMediaItems(); - mediaSelector.ImagePageCombo.SelectedIndex = 1; - Assert.Equal("Page 2", mediaSelector.AlternativeTextBox.Text); + Assert.Empty(mediaSelector.MediaItems); + Assert.True(thumbnailImages.All(x => x.IsDisposed)); - // 画像が指定されていないページは入力した代替テキストも保持されない - mediaSelector.ImagePageCombo.SelectedIndex = 2; - Assert.Equal("", mediaSelector.AlternativeTextBox.Text); + // DetachMediaItems で切り離された MediaItem は破棄しない + Assert.True(detachedMediaItems.All(x => !x.IsDisposed)); } [Fact] - public void PageChange_ImageDisposeTest() + public void SelectedMediaItemChange_Test() { using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelectorPanel { Visible = false, Enabled = false }; + using var mediaSelector = new MediaSelector(); twitter.Initialize("", "", "", 0L); - mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); + mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); + mediaSelector.SelectMediaService("Twitter"); var images = new[] { "Resources/re.gif", "Resources/re1.png" }; - mediaSelector.BeginSelection(images); + mediaSelector.AddMediaItemFromFilePath(images); - mediaSelector.ImagePageCombo.SelectedIndex = 0; + mediaSelector.SelectedMediaItemIndex = 0; // 1 ページ目 - var page1Image = mediaSelector.ImageSelectedPicture.Image; - - mediaSelector.ImagePageCombo.SelectedIndex = 1; - - // 2 ページ目 - var page2Image = mediaSelector.ImageSelectedPicture.Image; - Assert.True(page1Image!.IsDisposed); // 前ページの画像が破棄されているか - - mediaSelector.ImagePageCombo.SelectedIndex = 2; - - // 3 ページ目 (新規ページ) - Assert.True(page2Image!.IsDisposed); // 前ページの画像が破棄されているか - } - - [Fact] - public void ImagePathInput_Test() - { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); - using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelectorPanel { Visible = false, Enabled = false }; - twitter.Initialize("", "", "", 0L); - mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); - mediaSelector.BeginSelection(); - - // 画像のファイルパスを入力 - mediaSelector.ImagefilePathText.Text = Path.GetFullPath("Resources/re1.png"); - TestUtils.Validate(mediaSelector.ImagefilePathText); + Assert.Equal(Path.GetFullPath("Resources/re.gif"), mediaSelector.SelectedMediaItem!.Path); - // 入力したパスの画像が表示される - using (var imageStream = File.OpenRead("Resources/re1.png")) + using (var imageStream = File.OpenRead("Resources/re.gif")) { - using var image = MemoryImage.CopyFromStream(imageStream); - Assert.Equal(image, mediaSelector.ImageSelectedPicture.Image); + using var expectedImage = MemoryImage.CopyFromStream(imageStream); + using var actualImage = mediaSelector.SelectedMediaItem.CreateImage(); + Assert.Equal(expectedImage, actualImage); } - // 2 ページ目まで選択可能な状態 - var pages = mediaSelector.ImagePageCombo.Items; - Assert.Equal(new[] { "1", "2" }, pages.Cast().Select(x => x.ToString())); - } - - [Fact] - public void ImagePathInput_ReplaceFileMediaItemTest() - { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); - using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelectorPanel { Visible = false, Enabled = false }; - twitter.Initialize("", "", "", 0L); - mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); + mediaSelector.SelectedMediaItemIndex = 1; - mediaSelector.BeginSelection(new[] { "Resources/re.gif" }); - - // 既に入力されているファイルパスの画像 - var image1 = mediaSelector.ImageSelectedPicture.Image; - - // 別の画像のファイルパスを入力 - mediaSelector.ImagefilePathText.Text = Path.GetFullPath("Resources/re1.png"); - TestUtils.Validate(mediaSelector.ImagefilePathText); + // 2 ページ目 + Assert.Equal(Path.GetFullPath("Resources/re1.png"), mediaSelector.SelectedMediaItem!.Path); - // 入力したパスの画像が表示される using (var imageStream = File.OpenRead("Resources/re1.png")) { - using var image2 = MemoryImage.CopyFromStream(imageStream); - Assert.Equal(image2, mediaSelector.ImageSelectedPicture.Image); + using var expectedImage = MemoryImage.CopyFromStream(imageStream); + using var actualImage = mediaSelector.SelectedMediaItem.CreateImage(); + Assert.Equal(expectedImage, actualImage); } - - // 最初に入力されていたファイルパスの表示用の MemoryImage は破棄される - Assert.True(image1!.IsDisposed); } [Fact] - public void ImagePathInput_ReplaceMemoryImageMediaItemTest() + public void SetSelectedMediaAltText_Test() { using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelectorPanel { Visible = false, Enabled = false }; + using var mediaSelector = new MediaSelector(); twitter.Initialize("", "", "", 0L); - mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); - - using (var bitmap = new Bitmap(width: 200, height: 200)) - { - mediaSelector.BeginSelection(bitmap); - } - - // 既に入力されているファイルパスの画像 - var image1 = mediaSelector.ImageSelectedPicture.Image; + mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); + mediaSelector.SelectMediaService("Twitter"); - // 内部で保持されている MemoryImageMediaItem を取り出す - var selectedMedia = mediaSelector.ImagePageCombo.SelectedItem; - var mediaProperty = selectedMedia.GetType().GetProperty("Item"); - var mediaItem = (MemoryImageMediaItem)mediaProperty.GetValue(selectedMedia); - - // 別の画像のファイルパスを入力 - mediaSelector.ImagefilePathText.Text = Path.GetFullPath("Resources/re1.png"); - TestUtils.Validate(mediaSelector.ImagefilePathText); - - // 入力したパスの画像が表示される - using (var imageStream = File.OpenRead("Resources/re1.png")) - { - using var image2 = MemoryImage.CopyFromStream(imageStream); - Assert.Equal(image2, mediaSelector.ImageSelectedPicture.Image); - } + var images = new[] { "Resources/re.gif", "Resources/re1.png" }; + mediaSelector.AddMediaItemFromFilePath(images); - // 最初に入力されていたファイルパスの表示用の MemoryImage は破棄される - Assert.True(image1!.IsDisposed); + // 1 ページ目 + mediaSelector.SelectedMediaItemIndex = 0; + mediaSelector.SetSelectedMediaAltText("Page 1"); - // 参照されなくなった MemoryImageMediaItem も破棄される - Assert.True(mediaItem.IsDisposed); - } + // 2 ページ目 + mediaSelector.SelectedMediaItemIndex = 1; + mediaSelector.SetSelectedMediaAltText("Page 2"); - [Fact] - public void ImageServiceChange_Test() - { - using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); - using var twitter = new Twitter(twitterApi); - using var mediaSelector = new MediaSelectorPanel { Visible = false, Enabled = false }; - twitter.Initialize("", "", "", 0L); - mediaSelector.Initialize(twitter, TwitterConfiguration.DefaultConfiguration(), "Twitter"); - - Assert.Equal("Twitter", mediaSelector.ServiceName); - - mediaSelector.BeginSelection(new[] { "Resources/re.gif", "Resources/re1.png" }); - - // 3 ページ目まで選択可能な状態 - var pages = mediaSelector.ImagePageCombo.Items; - Assert.Equal(new[] { "1", "2", "3" }, pages.Cast().Select(x => x.ToString())); - Assert.True(mediaSelector.ImagePageCombo.Enabled); - - // 投稿先を Imgur に変更 - var imgurIndex = mediaSelector.ImageServiceCombo.Items.IndexOf("Imgur"); - Assert.Raises( - x => mediaSelector.SelectedServiceChanged += x, - x => mediaSelector.SelectedServiceChanged -= x, - () => mediaSelector.ImageServiceCombo.SelectedIndex = imgurIndex - ); - - // 1 ページ目のみ選択可能な状態 (Disabled) - pages = mediaSelector.ImagePageCombo.Items; - Assert.Equal(new[] { "1" }, pages.Cast().Select(x => x.ToString())); - Assert.False(mediaSelector.ImagePageCombo.Enabled); - - // 投稿先を Twitter に変更 - mediaSelector.ImageServiceCombo.SelectedIndex = - mediaSelector.ImageServiceCombo.Items.IndexOf("Twitter"); - - // 2 ページ目まで選択可能な状態 - pages = mediaSelector.ImagePageCombo.Items; - Assert.Equal(new[] { "1", "2" }, pages.Cast().Select(x => x.ToString())); - Assert.True(mediaSelector.ImagePageCombo.Enabled); + Assert.Equal("Page 1", mediaSelector.MediaItems[0].AltText); + Assert.Equal("Page 2", mediaSelector.MediaItems[1].AltText); } } } diff --git a/OpenTween/Extensions.cs b/OpenTween/Extensions.cs index 788fed1e8..6456957b4 100644 --- a/OpenTween/Extensions.cs +++ b/OpenTween/Extensions.cs @@ -158,6 +158,19 @@ public static int GetCodepointCount(this string s, int start, int end) return count; } + public static bool TryInvoke(this Control control, Action action) + { + if (control.IsDisposed) + return false; + + if (control.InvokeRequired) + control.Invoke(action); + else + action(); + + return true; + } + public static Task ForEachAsync(this IObservable observable, Action subscriber) { return ForEachAsync(observable, value => diff --git a/OpenTween/MediaSelector.cs b/OpenTween/MediaSelector.cs new file mode 100644 index 000000000..943fc0261 --- /dev/null +++ b/OpenTween/MediaSelector.cs @@ -0,0 +1,341 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2014 spx (@5px) +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// 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 3 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 , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTween.Api.DataModel; +using OpenTween.MediaUploadServices; + +namespace OpenTween +{ + public sealed class MediaSelector : NotifyPropertyChangedBase, IDisposable + { + private KeyValuePair[] pictureServices = Array.Empty>(); + private readonly BindingList mediaItems = new(); + private string selectedMediaServiceName = ""; + private Guid? selectedMediaItemId = null; + + public bool IsDisposed { get; private set; } = false; + + public KeyValuePair[] MediaServices + { + get => this.pictureServices; + private set => this.SetProperty(ref this.pictureServices, value); + } + + public BindingList MediaItems + => this.mediaItems; + + public MemoryImageList ThumbnailList { get; } = new(); + + /// + /// 選択されている投稿先名を取得する。 + /// + public string SelectedMediaServiceName + { + get => this.selectedMediaServiceName; + set => this.SetProperty(ref this.selectedMediaServiceName, value); + } + + /// + /// 選択されている投稿先を示すインデックスを取得する。 + /// + public int SelectedMediaServiceIndex + => this.MediaServices.FindIndex(x => x.Key == this.SelectedMediaServiceName); + + /// + /// 選択されている投稿先の IMediaUploadService を取得する。 + /// + public IMediaUploadService? SelectedMediaService + => this.GetService(this.SelectedMediaServiceName); + + public bool CanUseAltText + => this.SelectedMediaService?.CanUseAltText ?? false; + + public Guid? SelectedMediaItemId + { + get => this.selectedMediaItemId; + set => this.SetProperty(ref this.selectedMediaItemId, value); + } + + public IMediaItem? SelectedMediaItem + => this.SelectedMediaItemId != null ? this.MediaItems.First(x => x.Id == this.SelectedMediaItemId) : null; + + public int SelectedMediaItemIndex + { + get => this.MediaItems.FindIndex(x => x.Id == this.SelectedMediaItemId); + set => this.SelectedMediaItemId = value != -1 ? this.MediaItems[value].Id : null; + } + + /// + /// 指定された投稿先名から、作成済みの IMediaUploadService インスタンスを取得する。 + /// + public IMediaUploadService? GetService(string serviceName) + { + var index = this.MediaServices.FindIndex(x => x.Key == serviceName); + return index != -1 ? this.MediaServices[index].Value : null; + } + + public void InitializeServices(Twitter tw, TwitterConfiguration twitterConfig) + { + this.MediaServices = new KeyValuePair[] + { + new("Twitter", new TwitterPhoto(tw, twitterConfig)), + new("Imgur", new Imgur(twitterConfig)), + new("Mobypicture", new Mobypicture(tw, twitterConfig)), + }; + } + + public void SelectMediaService(string serviceName, int? index = null) + { + int idx; + if (MyCommon.IsNullOrEmpty(serviceName)) + { + // 引数の index は serviceName が空の場合のみ使用する + idx = index ?? 0; + } + else + { + idx = this.MediaServices.FindIndex(x => x.Key == serviceName); + + // svc が空白以外かつ存在しないサービス名の場合は Twitter を選択させる + // (廃止されたサービスを選択していた場合の対応) + if (idx == -1) + idx = 0; + } + + this.SelectedMediaServiceName = this.MediaServices[idx].Key; + } + + /// + /// 指定されたファイルの投稿に対応した投稿先があるかどうかを示す値を取得する。 + /// + public bool HasUploadableService(string fileName, bool ignoreSize) + { + var fl = new FileInfo(fileName); + var ext = fl.Extension; + var size = ignoreSize ? (long?)null : fl.Length; + + return this.GetAvailableServiceNames(ext, size).Any(); + } + + public string[] GetAvailableServiceNames(string extension, long? fileSize) + => this.MediaServices + .Where(x => x.Value.CheckFileExtension(extension) && (fileSize == null || x.Value.CheckFileSize(extension, fileSize.Value))) + .Select(x => x.Key) + .ToArray(); + + public void AddMediaItemFromImage(Image image) + { + var mediaItem = this.CreateMemoryImageMediaItem(image); + if (mediaItem == null) + return; + + this.AddMediaItem(mediaItem); + this.SelectedMediaItemId = mediaItem.Id; + } + + public void AddMediaItemFromFilePath(string[] filePathArray) + { + if (filePathArray.Length == 0) + return; + + var mediaItems = new IMediaItem[filePathArray.Length]; + + // 連番のファイル名を一括でアップロードする場合の利便性のためソートする + var sortedFilePath = filePathArray.OrderBy(x => x); + + foreach (var (path, index) in sortedFilePath.WithIndex()) + { + var mediaItem = this.CreateFileMediaItem(path); + if (mediaItem == null) + continue; + + mediaItems[index] = mediaItem; + } + + // 全ての IMediaItem の生成に成功した場合のみ追加する + foreach (var mediaItem in mediaItems) + this.AddMediaItem(mediaItem); + + this.SelectedMediaItemId = mediaItems.Last().Id; + } + + public void AddMediaItem(IMediaItem item) + { + MemoryImage thumbnailImage; + try + { + thumbnailImage = item.CreateImage(); + } + catch (InvalidImageException) + { + thumbnailImage = MemoryImage.CopyFromImage(Properties.Resources.MultiMediaImage); + } + + var id = item.Id.ToString(); + this.ThumbnailList.Add(id, thumbnailImage); + this.MediaItems.Add(item); + } + + public void ClearMediaItems() + { + this.SelectedMediaItemId = null; + + var mediaItems = this.MediaItems.ToList(); + this.MediaItems.Clear(); + + foreach (var mediaItem in mediaItems) + this.DisposeMediaItem(mediaItem); + + var thumbnailImages = this.ThumbnailList.ToList(); + this.ThumbnailList.Clear(); + + foreach (var image in thumbnailImages) + image.Dispose(); + } + + public IMediaItem[] DetachMediaItems() + { + // ClearMediaItems では MediaItem が破棄されるため、外部で使用する場合はこのメソッドを使用して MediaItems から切り離す + var mediaItems = this.MediaItems.ToArray(); + this.MediaItems.Clear(); + this.ClearMediaItems(); + + return mediaItems; + } + + private MemoryImageMediaItem? CreateMemoryImageMediaItem(Image image) + { + if (image == null) + return null; + + MemoryImage? memoryImage = null; + try + { + // image から png 形式の MemoryImage を生成 + memoryImage = MemoryImage.CopyFromImage(image); + + return new MemoryImageMediaItem(memoryImage); + } + catch + { + memoryImage?.Dispose(); + return null; + } + } + + private FileMediaItem? CreateFileMediaItem(string path) + { + if (MyCommon.IsNullOrEmpty(path)) + return null; + + try + { + return new FileMediaItem(path); + } + catch + { + return null; + } + } + + private void DisposeMediaItem(IMediaItem? item) + { + var disposableItem = item as IDisposable; + disposableItem?.Dispose(); + } + + public void SetSelectedMediaAltText(string altText) + { + var selectedMedia = this.SelectedMediaItem; + if (selectedMedia == null) + return; + + selectedMedia.AltText = altText.Trim(); + } + + public MediaSelectorErrorType Validate(out IMediaItem? rejectedMedia) + { + rejectedMedia = null; + + if (this.MediaItems.Count == 0) + return MediaSelectorErrorType.MediaItemNotSet; + + var uploadService = this.SelectedMediaService; + if (uploadService == null) + return MediaSelectorErrorType.ServiceNotSelected; + + foreach (var mediaItem in this.MediaItems) + { + var error = this.ValidateMediaItem(uploadService, mediaItem); + if (error != MediaSelectorErrorType.None) + { + rejectedMedia = mediaItem; + return error; + } + } + + return MediaSelectorErrorType.None; + } + + private MediaSelectorErrorType ValidateMediaItem(IMediaUploadService imageService, IMediaItem item) + { + var ext = item.Extension; + var size = item.Size; + + if (!imageService.CheckFileExtension(ext)) + return MediaSelectorErrorType.UnsupportedFileExtension; + + if (!imageService.CheckFileSize(ext, size)) + return MediaSelectorErrorType.FileSizeExceeded; + + return MediaSelectorErrorType.None; + } + + public void Dispose() + { + if (this.IsDisposed) + return; + + this.IsDisposed = true; + this.ThumbnailList.Dispose(); + } + } + + public enum MediaSelectorErrorType + { + None, + MediaItemNotSet, + ServiceNotSelected, + UnsupportedFileExtension, + FileSizeExceeded, + } +} diff --git a/OpenTween/MediaSelectorPanel.cs b/OpenTween/MediaSelectorPanel.cs index 3fbbe8689..7521e2374 100644 --- a/OpenTween/MediaSelectorPanel.cs +++ b/OpenTween/MediaSelectorPanel.cs @@ -1,5 +1,6 @@ // OpenTween - Client of Twitter // Copyright (c) 2014 spx (@5px) +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) // All rights reserved. // // This file is part of OpenTween. @@ -26,14 +27,10 @@ using System.ComponentModel; using System.Data; using System.Diagnostics.CodeAnalysis; -using System.Drawing; -using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; -using OpenTween.Api.DataModel; -using OpenTween.MediaUploadServices; namespace OpenTween { @@ -51,237 +48,30 @@ public partial class MediaSelectorPanel : UserControl [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public OpenFileDialog? FilePickDialog { get; set; } + public MediaSelector Model { get; } = new(); - /// - /// 選択されている投稿先名を取得する。 - /// - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public string ServiceName - => this.ImageServiceCombo.Text; - - /// - /// 選択されている投稿先を示すインデックスを取得する。 - /// - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public int ServiceIndex - => this.ImageServiceCombo.SelectedIndex; - - /// - /// 選択されている投稿先の IMediaUploadService を取得する。 - /// [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public IMediaUploadService? SelectedService - { - get - { - var serviceName = this.ServiceName; - if (MyCommon.IsNullOrEmpty(serviceName)) - return null; - - return this.pictureService.TryGetValue(serviceName, out var service) - ? service : null; - } - } - - /// - /// 指定された投稿先名から、作成済みの IMediaUploadService インスタンスを取得する。 - /// - public IMediaUploadService GetService(string serviceName) - { - this.pictureService.TryGetValue(serviceName, out var service); - return service; - } - - /// - /// 利用可能な全ての IMediaUploadService インスタンスを取得する。 - /// - public ICollection GetServices() - => this.pictureService.Values; - - private Dictionary pictureService = new(); - - private readonly List mediaItems = new(); - private readonly MemoryImageList thumbnailList = new(); - private Guid? selectedMediaItemId = null; - - private void CreateServices(Twitter tw, TwitterConfiguration twitterConfig) - { - this.pictureService?.Clear(); - - this.pictureService = new Dictionary - { - ["Twitter"] = new TwitterPhoto(tw, twitterConfig), - ["Imgur"] = new Imgur(twitterConfig), - ["Mobypicture"] = new Mobypicture(tw, twitterConfig), - }; - } + public OpenFileDialog? FilePickDialog { get; set; } public MediaSelectorPanel() { this.InitializeComponent(); this.ImageSelectedPicture.InitialImage = Properties.Resources.InitialImage; - this.MediaListView.LargeImageList = this.thumbnailList.ImageList; - - var thumbnailWidth = 75 * this.DeviceDpi / 96; - this.thumbnailList.ImageList.ImageSize = new(thumbnailWidth, thumbnailWidth); - this.UpdateSelectedMedia(); - this.UpdateAltTextPanelVisible(); - } - - /// - /// 投稿先サービスなどを初期化する。 - /// - public void Initialize(Twitter tw, TwitterConfiguration twitterConfig, string svc, int? index = null) - { - this.CreateServices(tw, twitterConfig); - - this.SetImageServiceCombo(); - this.SelectImageServiceComboItem(svc, index); - } - - /// - /// 投稿先サービスを再作成する。 - /// - public void Reset(Twitter tw, TwitterConfiguration twitterConfig) - { - this.CreateServices(tw, twitterConfig); - - this.SetImageServiceCombo(); - } + this.MediaListView.LargeImageList = this.Model.ThumbnailList.ImageList; - /// - /// 指定されたファイルの投稿に対応した投稿先があるかどうかを示す値を取得する。 - /// - public bool HasUploadableService(string fileName, bool ignoreSize) - { - var fl = new FileInfo(fileName); - var ext = fl.Extension; - var size = ignoreSize ? (long?)null : fl.Length; - - if (this.IsUploadable(this.ServiceName, ext, size)) - return true; - - foreach (string svc in this.ImageServiceCombo.Items) - { - if (this.IsUploadable(svc, ext, size)) - return true; - } - - return false; - } - - /// - /// 指定された投稿先に投稿可能かどうかを示す値を取得する。 - /// ファイルサイズの指定がなければ拡張子だけで判定する。 - /// - private bool IsUploadable(string serviceName, string ext, long? size) - { - if (!MyCommon.IsNullOrEmpty(serviceName)) - { - var imageService = this.pictureService[serviceName]; - if (imageService.CheckFileExtension(ext)) - { - if (!size.HasValue) - return true; + var thumbnailWidth = 75 * this.DeviceDpi / 96; + this.Model.ThumbnailList.ImageList.ImageSize = new(thumbnailWidth, thumbnailWidth); - if (imageService.CheckFileSize(ext, size.Value)) - return true; - } - } - return false; - } + this.Model.PropertyChanged += + (s, e) => this.TryInvoke(() => this.Model_PropertyChanged(s, e)); + this.Model.MediaItems.ListChanged += + (s, e) => this.TryInvoke(() => this.Model_MediaItems_ListChanged(s, e)); - private void ClearMediaItems() - { - this.selectedMediaItemId = null; this.UpdateSelectedMedia(); - - this.MediaListView.Items.Clear(); - - var mediaItems = this.mediaItems.ToList(); - this.mediaItems.Clear(); - - foreach (var mediaItem in mediaItems) - this.DisposeMediaItem(mediaItem); - - var thumbnailImages = this.thumbnailList.ToList(); - this.thumbnailList.Clear(); - - foreach (var image in thumbnailImages) - image.Dispose(); - } - - public void AddMediaItemFromImage(Image image) - { - var mediaItem = this.CreateMemoryImageMediaItem(image, noMsgBox: false); - if (mediaItem == null) - return; - - this.AddMediaItem(mediaItem); - this.SelectMediaItem(mediaItem.Id); - } - - public void AddMediaItemFromFilePath(string[] filePathArray) - { - if (filePathArray.Length == 0) - return; - - var mediaItems = new IMediaItem[filePathArray.Length]; - - // 連番のファイル名を一括でアップロードする場合の利便性のためソートする - var sortedFilePath = filePathArray.OrderBy(x => x); - - foreach (var (path, index) in sortedFilePath.WithIndex()) - { - var mediaItem = this.CreateFileMediaItem(path, noMsgBox: false); - if (mediaItem == null) - return; - - mediaItems[index] = mediaItem; - } - - // 全ての IMediaItem の生成に成功した場合のみ追加する - foreach (var mediaItem in mediaItems) - this.AddMediaItem(mediaItem); - - this.SelectMediaItem(mediaItems.Last().Id); - } - - public void AddMediaItem(IMediaItem item) - { - this.mediaItems.Add(item); - - MemoryImage thumbnailImage; - try - { - thumbnailImage = item.CreateImage(); - } - catch (InvalidImageException) - { - thumbnailImage = MemoryImage.CopyFromImage(Properties.Resources.MultiMediaImage); - } - - var id = item.Id.ToString(); - this.thumbnailList.Add(id, thumbnailImage); - - this.MediaListView.Items.Add(item.Name, id); - } - - public void SelectMediaItem(Guid id) - { - var index = this.mediaItems.FindIndex(x => x.Id == id); - if (index == -1) - return; - - // selectedMediaItemId は ImageListView のイベントハンドラ内でセットされる - this.MediaListView.SelectedIndices.Clear(); - this.MediaListView.SelectedIndices.Add(index); + this.UpdateAltTextPanelVisible(); } /// @@ -302,7 +92,7 @@ public void EndSelection() this.EndSelecting?.Invoke(this, EventArgs.Empty); this.Visible = false; this.Enabled = false; - this.ClearMediaItems(); + this.Model.ClearMediaItems(); } /// @@ -310,76 +100,113 @@ public void EndSelection() /// public bool TryGetSelectedMedia([NotNullWhen(true)] out string? imageService, [NotNullWhen(true)] out IMediaItem[]? mediaItems) { - imageService = null; - mediaItems = null; + var selectedServiceName = this.Model.SelectedMediaServiceName; - var uploadService = this.SelectedService; - if (uploadService == null || this.mediaItems.Count == 0) + var error = this.Model.Validate(out var rejectedMedia); + if (error != MediaSelectorErrorType.None) { - MessageBox.Show(Properties.Resources.PostPictureWarn1, Properties.Resources.PostPictureWarn2); - return false; - } + var message = error switch + { + MediaSelectorErrorType.MediaItemNotSet + => Properties.Resources.PostPictureWarn1, + MediaSelectorErrorType.ServiceNotSelected + => Properties.Resources.PostPictureWarn1, + MediaSelectorErrorType.UnsupportedFileExtension + => string.Format( + Properties.Resources.PostPictureWarn3, + selectedServiceName, + this.MakeAvailableServiceText(rejectedMedia!), + rejectedMedia!.Extension, + rejectedMedia!.Name + ), + MediaSelectorErrorType.FileSizeExceeded + => string.Format( + Properties.Resources.PostPictureWarn5, + selectedServiceName, + this.MakeAvailableServiceText(rejectedMedia!), + rejectedMedia!.Name + ), + _ => throw new NotImplementedException(), + }; - foreach (var mediaItem in this.mediaItems) - { - if (!this.ValidateMediaItem(uploadService, mediaItem)) - return false; - } + MessageBox.Show( + message, + Properties.Resources.PostPictureWarn2, + MessageBoxButtons.OK, + MessageBoxIcon.Warning + ); - // 収集した MediaItem が破棄されないように、ClearMediaItems を呼ぶ前に mediaItems を空にしておく - this.mediaItems.Clear(); + imageService = null; + mediaItems = null; + return false; + } - imageService = this.ServiceName; - mediaItems = this.mediaItems.ToArray(); - this.EndSelection(); + imageService = selectedServiceName; + mediaItems = this.Model.DetachMediaItems(); return true; } - private MemoryImageMediaItem? CreateMemoryImageMediaItem(Image image, bool noMsgBox) + private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e) { - if (image == null) return null; - - MemoryImage? memoryImage = null; - try - { - // image から png 形式の MemoryImage を生成 - memoryImage = MemoryImage.CopyFromImage(image); - - return new MemoryImageMediaItem(memoryImage); - } - catch + switch (e.PropertyName) { - memoryImage?.Dispose(); - - if (!noMsgBox) MessageBox.Show("Unable to create MemoryImage."); - return null; + case nameof(MediaSelector.MediaServices): + this.UpdateImageServiceComboItems(); + break; + case nameof(MediaSelector.SelectedMediaServiceName): + this.UpdateImageServiceComboSelection(); + this.UpdateAltTextPanelVisible(); + this.SelectedServiceChanged?.Invoke(this, EventArgs.Empty); + break; + case nameof(MediaSelector.SelectedMediaItemId): + this.UpdateSelectedMedia(); + break; + default: + break; } } - private IMediaItem? CreateFileMediaItem(string path, bool noMsgBox) + private void Model_MediaItems_ListChanged(object sender, ListChangedEventArgs e) { - if (MyCommon.IsNullOrEmpty(path)) return null; + void AddMediaListViewItem(IMediaItem media, int index) + => this.MediaListView.Items.Insert(index, media.Name, media.Id.ToString()); - try - { - return new FileMediaItem(path); - } - catch + switch (e.ListChangedType) { - if (!noMsgBox) MessageBox.Show("Invalid file path: " + path); - return null; + case ListChangedType.ItemAdded: + var addedMedia = this.Model.MediaItems[e.NewIndex]; + AddMediaListViewItem(addedMedia, e.NewIndex); + break; + case ListChangedType.Reset: + this.MediaListView.Items.Clear(); + foreach (var (media, index) in this.Model.MediaItems.WithIndex()) + AddMediaListViewItem(media, index); + break; + default: + throw new NotImplementedException(); } } - private void DisposeMediaItem(IMediaItem? item) + private void UpdateImageServiceComboItems() { - var disposableItem = item as IDisposable; - disposableItem?.Dispose(); + using (ControlTransaction.Update(this.ImageServiceCombo)) + { + this.ImageServiceCombo.Items.Clear(); + + // Add service names to combobox + var serviceNames = this.Model.MediaServices.Select(x => x.Key).ToArray(); + this.ImageServiceCombo.Items.AddRange(serviceNames); + + this.UpdateImageServiceComboSelection(); + } } + private void UpdateImageServiceComboSelection() + => this.ImageServiceCombo.SelectedIndex = this.Model.SelectedMediaServiceIndex; + private void AddMediaButton_Click(object sender, EventArgs e) { - var service = this.SelectedService; + var service = this.Model.SelectedMediaService; if (this.FilePickDialog == null || service == null) return; this.FilePickDialog.Filter = service.SupportedFormatsStrForDialog; @@ -397,117 +224,33 @@ private void AddMediaButton_Click(object sender, EventArgs e) this.FilePickDialogClosed?.Invoke(this, EventArgs.Empty); } - this.AddMediaItemFromFilePath(this.FilePickDialog.FileNames); + this.Model.AddMediaItemFromFilePath(this.FilePickDialog.FileNames); } - private bool ValidateMediaItem(IMediaUploadService imageService, IMediaItem item) + private string MakeAvailableServiceText(IMediaItem media) { - var ext = item.Extension; - var size = item.Size; - - if (!imageService.CheckFileExtension(ext)) - { - // 画像以外の形式 - MessageBox.Show( - string.Format(Properties.Resources.PostPictureWarn3, this.ServiceName, this.MakeAvailableServiceText(ext, size), ext, item.Name), - Properties.Resources.PostPictureWarn4, - MessageBoxButtons.OK, - MessageBoxIcon.Warning - ); - return false; - } - - if (!imageService.CheckFileSize(ext, size)) - { - // ファイルサイズが大きすぎる - MessageBox.Show( - string.Format(Properties.Resources.PostPictureWarn5, this.ServiceName, this.MakeAvailableServiceText(ext, size), item.Name), - Properties.Resources.PostPictureWarn4, - MessageBoxButtons.OK, - MessageBoxIcon.Warning - ); - return false; - } + var ext = media.Extension; + var fileSize = media.Size; - return true; - } - - private string MakeAvailableServiceText(string ext, long fileSize) - { - var text = string.Join(", ", - this.ImageServiceCombo.Items.Cast() - .Where(serviceName => - !MyCommon.IsNullOrEmpty(serviceName) && - this.pictureService[serviceName].CheckFileExtension(ext) && - this.pictureService[serviceName].CheckFileSize(ext, fileSize))); - - if (MyCommon.IsNullOrEmpty(text)) + var availableServiceNames = this.Model.GetAvailableServiceNames(ext, fileSize); + if (availableServiceNames.Length == 0) return Properties.Resources.PostPictureWarn6; - return text; + return string.Join(", ", availableServiceNames); } private void ImageCancelButton_Click(object sender, EventArgs e) => this.EndSelection(); - private void SetImageServiceCombo() - { - using (ControlTransaction.Update(this.ImageServiceCombo)) - { - var svc = ""; - if (this.ImageServiceCombo.SelectedIndex > -1) svc = this.ImageServiceCombo.Text; - this.ImageServiceCombo.Items.Clear(); - - // Add service names to combobox - foreach (var key in this.pictureService.Keys) - { - this.ImageServiceCombo.Items.Add(key); - } - - this.SelectImageServiceComboItem(svc); - } - } - - private void SelectImageServiceComboItem(string svc, int? index = null) - { - int idx; - if (MyCommon.IsNullOrEmpty(svc)) - { - idx = index ?? 0; - } - else - { - idx = this.ImageServiceCombo.Items.IndexOf(svc); - - // svc が空白以外かつ存在しないサービス名の場合は Twitter を選択させる - // (廃止されたサービスを選択していた場合の対応) - if (idx == -1) idx = 0; - } - - try - { - this.ImageServiceCombo.SelectedIndex = idx; - } - catch (ArgumentOutOfRangeException) - { - this.ImageServiceCombo.SelectedIndex = 0; - } - - this.UpdateAltTextPanelVisible(); - } - private void UpdateAltTextPanelVisible() - => this.AlternativeTextPanel.Visible = this.SelectedService switch - { - null => false, - var service => service.CanUseAltText, - }; + => this.AlternativeTextPanel.Visible = this.Model.CanUseAltText; private void UpdateSelectedMedia() { using (ControlTransaction.Update(this)) { - if (this.selectedMediaItemId == null) + var selectedMedia = this.Model.SelectedMediaItem; + if (selectedMedia == null) { this.AlternativeTextBox.Text = ""; this.AlternativeTextPanel.Enabled = false; @@ -515,53 +258,34 @@ private void UpdateSelectedMedia() } else { - var media = this.mediaItems.First(x => x.Id == this.selectedMediaItemId); - - this.AlternativeTextBox.Text = media.AltText; + this.AlternativeTextBox.Text = selectedMedia.AltText; this.AlternativeTextPanel.Enabled = true; - this.ImageSelectedPicture.Image = media.CreateImage(); + this.ImageSelectedPicture.Image = selectedMedia.CreateImage(); } } } private void ImageServiceCombo_SelectedIndexChanged(object sender, EventArgs e) - { - this.UpdateAltTextPanelVisible(); - this.SelectedServiceChanged?.Invoke(this, EventArgs.Empty); - } + => this.Model.SelectedMediaServiceName = this.ImageServiceCombo.Text; private void MediaListView_SelectedIndexChanged(object sender, EventArgs e) { var indices = this.MediaListView.SelectedIndices; if (indices.Count == 0) - { - this.selectedMediaItemId = null; - } - else - { - var media = this.mediaItems[indices[0]]; - this.selectedMediaItemId = media.Id; - } + return; - this.UpdateSelectedMedia(); + this.Model.SelectedMediaItemIndex = indices[0]; } private void AlternativeTextBox_Validated(object sender, EventArgs e) - { - if (this.selectedMediaItemId == null) - return; - - var media = this.mediaItems.First(x => x.Id == this.selectedMediaItemId); - media.AltText = this.AlternativeTextBox.Text.Trim(); - } + => this.Model.SetSelectedMediaAltText(this.AlternativeTextBox.Text); protected override void Dispose(bool disposing) { if (disposing) { - this.ClearMediaItems(); - this.thumbnailList.Dispose(); this.components?.Dispose(); + this.Model.Dispose(); } base.Dispose(disposing); diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index cc726ae40..6649f9b1b 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -555,7 +555,8 @@ ThumbnailGenerator thumbGenerator Thumbnail.Services.TonTwitterCom.GetApiConnection = () => this.tw.Api.Connection; // 画像投稿サービス - this.ImageSelector.Initialize(this.tw, this.tw.Configuration, this.settings.Common.UseImageServiceName, this.settings.Common.UseImageService); + this.ImageSelector.Model.InitializeServices(this.tw, this.tw.Configuration); + this.ImageSelector.Model.SelectMediaService(this.settings.Common.UseImageServiceName, this.settings.Common.UseImageService); this.tweetThumbnail1.Initialize(this.thumbGenerator); @@ -1282,7 +1283,8 @@ private async void PostButton_Click(object sender, EventArgs e) if (!this.ImageSelector.TryGetSelectedMedia(out var serviceName, out uploadItems)) return; - uploadService = this.ImageSelector.GetService(serviceName); + this.ImageSelector.EndSelection(); + uploadService = this.ImageSelector.Model.GetService(serviceName); } this.inReplyTo = null; @@ -1950,7 +1952,7 @@ private async Task RefreshTwitterConfigurationAsync() if (this.tw.Configuration.PhotoSizeLimit != 0) { - foreach (var service in this.ImageSelector.GetServices()) + foreach (var (_, service) in this.ImageSelector.Model.MediaServices) { service.UpdateTwitterConfiguration(this.tw.Configuration); } @@ -2583,7 +2585,7 @@ private async void SettingStripMenuItem_Click(object sender, EventArgs e) this.tw.RestrictFavCheck = this.settings.Common.RestrictFavCheck; this.tw.ReadOwnPost = this.settings.Common.ReadOwnPost; - this.ImageSelector.Reset(this.tw, this.tw.Configuration); + this.ImageSelector.Model.InitializeServices(this.tw, this.tw.Configuration); try { @@ -3518,7 +3520,7 @@ private string RemoveAttachmentUrl(string statusText, out string? attachmentUrl) attachmentUrl = null; // attachment_url は media_id と同時に使用できない - if (this.ImageSelector.Visible && this.ImageSelector.SelectedService is TwitterPhoto) + if (this.ImageSelector.Visible && this.ImageSelector.Model.SelectedMediaService is TwitterPhoto) return statusText; var match = Twitter.AttachmentUrlRegex.Match(statusText); @@ -3661,7 +3663,7 @@ private int GetRestStatusCount(string statusText) } private IMediaUploadService? GetSelectedImageService() - => this.ImageSelector.Visible ? this.ImageSelector.SelectedService : null; + => this.ImageSelector.Visible ? this.ImageSelector.Model.SelectedMediaService : null; /// /// 全てのタブの振り分けルールを反映し直します @@ -5732,8 +5734,8 @@ private void SaveConfigsCommon() this.settings.Common.HashIsHead = this.HashMgr.IsHead; this.settings.Common.HashIsPermanent = this.HashMgr.IsPermanent; this.settings.Common.HashIsNotAddToAtReply = this.HashMgr.IsNotAddToAtReply; - this.settings.Common.UseImageService = this.ImageSelector.ServiceIndex; - this.settings.Common.UseImageServiceName = this.ImageSelector.ServiceName; + this.settings.Common.UseImageService = this.ImageSelector.Model.SelectedMediaServiceIndex; + this.settings.Common.UseImageServiceName = this.ImageSelector.Model.SelectedMediaServiceName; this.settings.SaveCommon(); } @@ -9168,7 +9170,7 @@ private void ImageSelectMenuItem_Click(object sender, EventArgs e) private void SelectMedia_DragEnter(DragEventArgs e) { - if (this.ImageSelector.HasUploadableService(((string[])e.Data.GetData(DataFormats.FileDrop, false))[0], true)) + if (this.ImageSelector.Model.HasUploadableService(((string[])e.Data.GetData(DataFormats.FileDrop, false))[0], true)) { e.Effect = DragDropEffects.Copy; return; @@ -9183,7 +9185,7 @@ private void SelectMedia_DragDrop(DragEventArgs e) var filePathArray = (string[])e.Data.GetData(DataFormats.FileDrop, false); this.ImageSelector.BeginSelection(); - this.ImageSelector.AddMediaItemFromFilePath(filePathArray); + this.ImageSelector.Model.AddMediaItemFromFilePath(filePathArray); this.StatusText.Focus(); } @@ -9235,13 +9237,13 @@ private void ProcClipboardFromStatusTextWhenCtrlPlusV() // clipboardから画像を取得 using var image = Clipboard.GetImage(); this.ImageSelector.BeginSelection(); - this.ImageSelector.AddMediaItemFromImage(image); + this.ImageSelector.Model.AddMediaItemFromImage(image); } else if (Clipboard.ContainsFileDropList()) { var files = Clipboard.GetFileDropList().Cast().ToArray(); this.ImageSelector.BeginSelection(); - this.ImageSelector.AddMediaItemFromFilePath(files); + this.ImageSelector.Model.AddMediaItemFromFilePath(files); } } catch (ExternalException ex) From a998e0f7200349a9dcbbae02d258fb799259980c Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Thu, 19 Jan 2023 07:27:37 +0900 Subject: [PATCH 07/11] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E3=81=97=E3=81=A6?= =?UTF-8?q?=E3=81=84=E3=81=AA=E3=81=84=20IMediaItem.IsImage=20=E3=83=97?= =?UTF-8?q?=E3=83=AD=E3=83=91=E3=83=86=E3=82=A3=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween/MediaItem.cs | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/OpenTween/MediaItem.cs b/OpenTween/MediaItem.cs index 56e9e19b1..f775223d9 100644 --- a/OpenTween/MediaItem.cs +++ b/OpenTween/MediaItem.cs @@ -62,11 +62,6 @@ public interface IMediaItem /// long Size { get; } - /// - /// メディアが画像であるかどうかを示す真偽値 - /// - bool IsImage { get; } - /// /// 代替テキスト (アップロード先が対応している必要がある) /// @@ -128,34 +123,6 @@ public bool Exists public long Size => this.FileInfo.Length; - public bool IsImage - { - get - { - if (this.isImage == null) - { - try - { - // MemoryImage が生成できるかを検証する - using (var image = this.CreateImage()) - { - } - - this.isImage = true; - } - catch (InvalidImageException) - { - this.isImage = false; - } - } - - return this.isImage.Value; - } - } - - /// IsImage の検証結果をキャッシュする。未検証なら null - private bool? isImage = null; - public MemoryImage CreateImage() { using var fs = this.FileInfo.OpenRead(); @@ -212,9 +179,6 @@ public bool Exists public long Size => this.image.Stream.Length; - public bool IsImage - => true; - public MemoryImage CreateImage() => this.image.Clone(); From 6128c204397a00885e9d3f6c70a3c03b780bbccb Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Thu, 19 Jan 2023 07:45:19 +0900 Subject: [PATCH 08/11] =?UTF-8?q?IMediaItem.IsDisposed=20=E3=83=97?= =?UTF-8?q?=E3=83=AD=E3=83=91=E3=83=86=E3=82=A3=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/MediaSelectorTest.cs | 2 ++ OpenTween/MediaItem.cs | 19 +++++++++++++++++-- OpenTween/MediaSelector.cs | 8 +------- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/OpenTween.Tests/MediaSelectorTest.cs b/OpenTween.Tests/MediaSelectorTest.cs index e8e101a29..1e4ae34fd 100644 --- a/OpenTween.Tests/MediaSelectorTest.cs +++ b/OpenTween.Tests/MediaSelectorTest.cs @@ -176,10 +176,12 @@ public void ClearMediaItems_Test() mediaSelector.AddMediaItemFromFilePath(new[] { "Resources/re.gif" }); + var mediaItems = mediaSelector.MediaItems.ToArray(); var thumbnailImages = mediaSelector.ThumbnailList.ToArray(); // 表示中の画像 mediaSelector.ClearMediaItems(); + Assert.True(mediaItems.All(x => x.IsDisposed)); Assert.True(thumbnailImages.All(x => x.IsDisposed)); } diff --git a/OpenTween/MediaItem.cs b/OpenTween/MediaItem.cs index f775223d9..15a0ee75c 100644 --- a/OpenTween/MediaItem.cs +++ b/OpenTween/MediaItem.cs @@ -30,13 +30,18 @@ namespace OpenTween { - public interface IMediaItem + public interface IMediaItem : IDisposable { /// /// メディアのID /// Guid Id { get; } + /// + /// メディアが既に破棄されているかを示す真偽値 + /// + bool IsDisposed { get; } + /// /// メディアへの絶対パス /// @@ -108,6 +113,8 @@ public FileMediaItem(FileInfo fileInfo) public Guid Id { get; } = Guid.NewGuid(); + public bool IsDisposed { get; private set; } = false; + public string Path => this.FileInfo.FullName; @@ -137,6 +144,14 @@ public void CopyTo(Stream stream) using var fs = this.FileInfo.OpenRead(); fs.CopyTo(stream); } + + public void Dispose() + { + if (this.IsDisposed) + return; + + this.IsDisposed = true; + } } /// @@ -145,7 +160,7 @@ public void CopyTo(Stream stream) /// /// 用途の関係上、メモリ使用量が大きくなるため、不要になればできるだけ破棄すること /// - public class MemoryImageMediaItem : IMediaItem, IDisposable + public class MemoryImageMediaItem : IMediaItem { public const string PathPrefix = "<>MemoryImage://"; private static int fileNumber = 0; diff --git a/OpenTween/MediaSelector.cs b/OpenTween/MediaSelector.cs index 943fc0261..607b53d0b 100644 --- a/OpenTween/MediaSelector.cs +++ b/OpenTween/MediaSelector.cs @@ -213,7 +213,7 @@ public void ClearMediaItems() this.MediaItems.Clear(); foreach (var mediaItem in mediaItems) - this.DisposeMediaItem(mediaItem); + mediaItem.Dispose(); var thumbnailImages = this.ThumbnailList.ToList(); this.ThumbnailList.Clear(); @@ -267,12 +267,6 @@ public IMediaItem[] DetachMediaItems() } } - private void DisposeMediaItem(IMediaItem? item) - { - var disposableItem = item as IDisposable; - disposableItem?.Dispose(); - } - public void SetSelectedMediaAltText(string altText) { var selectedMedia = this.SelectedMediaItem; From cba751ada3501d6129c4efed4cf15a40a1f4f201 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Thu, 19 Jan 2023 08:17:49 +0900 Subject: [PATCH 09/11] =?UTF-8?q?=E9=81=B8=E6=8A=9E=E4=B8=AD=E3=81=AE?= =?UTF-8?q?=E7=94=BB=E5=83=8F=E3=81=AEMemoryImage=E3=82=92MediaSelector?= =?UTF-8?q?=E3=81=A7=E4=BF=9D=E6=8C=81=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/MediaSelectorTest.cs | 24 ++++++++++++++++ OpenTween/MediaSelector.cs | 43 +++++++++++++++++++++++++++- OpenTween/MediaSelectorPanel.cs | 8 ++++-- 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/OpenTween.Tests/MediaSelectorTest.cs b/OpenTween.Tests/MediaSelectorTest.cs index 1e4ae34fd..8e88d539e 100644 --- a/OpenTween.Tests/MediaSelectorTest.cs +++ b/OpenTween.Tests/MediaSelectorTest.cs @@ -246,6 +246,30 @@ public void SelectedMediaItemChange_Test() } } + [Fact] + public void SelectedMediaItemChange_DisposeTest() + { + using var twitterApi = new TwitterApi(ApiKey.Create(""), ApiKey.Create("")); + using var twitter = new Twitter(twitterApi); + using var mediaSelector = new MediaSelector(); + twitter.Initialize("", "", "", 0L); + mediaSelector.InitializeServices(twitter, TwitterConfiguration.DefaultConfiguration()); + mediaSelector.SelectMediaService("Twitter"); + + var images = new[] { "Resources/re.gif", "Resources/re1.png" }; + mediaSelector.AddMediaItemFromFilePath(images); + + // 1 枚目 + mediaSelector.SelectedMediaItemIndex = 0; + var firstImage = mediaSelector.SelectedMediaItemImage; + + // 2 枚目 + mediaSelector.SelectedMediaItemIndex = 1; + var secondImage = mediaSelector.SelectedMediaItemImage; + + Assert.True(firstImage!.IsDisposed); + } + [Fact] public void SetSelectedMediaAltText_Test() { diff --git a/OpenTween/MediaSelector.cs b/OpenTween/MediaSelector.cs index 607b53d0b..9d9bad9a2 100644 --- a/OpenTween/MediaSelector.cs +++ b/OpenTween/MediaSelector.cs @@ -41,6 +41,7 @@ public sealed class MediaSelector : NotifyPropertyChangedBase, IDisposable private readonly BindingList mediaItems = new(); private string selectedMediaServiceName = ""; private Guid? selectedMediaItemId = null; + private MemoryImage? selectedMediaItemImage = null; public bool IsDisposed { get; private set; } = false; @@ -82,7 +83,14 @@ public bool CanUseAltText public Guid? SelectedMediaItemId { get => this.selectedMediaItemId; - set => this.SetProperty(ref this.selectedMediaItemId, value); + set + { + if (this.selectedMediaItemId == value) + return; + + this.SetProperty(ref this.selectedMediaItemId, value); + this.LoadSelectedMediaItemImage(); + } } public IMediaItem? SelectedMediaItem @@ -94,6 +102,12 @@ public int SelectedMediaItemIndex set => this.SelectedMediaItemId = value != -1 ? this.MediaItems[value].Id : null; } + public MemoryImage? SelectedMediaItemImage + { + get => this.selectedMediaItemImage; + set => this.SetProperty(ref this.selectedMediaItemImage, value); + } + /// /// 指定された投稿先名から、作成済みの IMediaUploadService インスタンスを取得する。 /// @@ -267,6 +281,33 @@ public IMediaItem[] DetachMediaItems() } } + private void LoadSelectedMediaItemImage() + { + var previousImage = this.selectedMediaItemImage; + + if (this.SelectedMediaItem == null) + { + this.SelectedMediaItemImage = null; + previousImage?.Dispose(); + return; + } + + this.SelectedMediaItemImage = this.CreateMediaItemImage(this.SelectedMediaItem); + previousImage?.Dispose(); + } + + private MemoryImage CreateMediaItemImage(IMediaItem media) + { + try + { + return media.CreateImage(); + } + catch (InvalidImageException) + { + return MemoryImage.CopyFromImage(Properties.Resources.MultiMediaImage); + } + } + public void SetSelectedMediaAltText(string altText) { var selectedMedia = this.SelectedMediaItem; diff --git a/OpenTween/MediaSelectorPanel.cs b/OpenTween/MediaSelectorPanel.cs index 7521e2374..cefbe5df0 100644 --- a/OpenTween/MediaSelectorPanel.cs +++ b/OpenTween/MediaSelectorPanel.cs @@ -161,6 +161,9 @@ private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e) case nameof(MediaSelector.SelectedMediaItemId): this.UpdateSelectedMedia(); break; + case nameof(MediaSelector.SelectedMediaItemImage): + this.UpdateSelectedMediaImage(); + break; default: break; } @@ -254,17 +257,18 @@ private void UpdateSelectedMedia() { this.AlternativeTextBox.Text = ""; this.AlternativeTextPanel.Enabled = false; - this.ImageSelectedPicture.ShowInitialImage(); } else { this.AlternativeTextBox.Text = selectedMedia.AltText; this.AlternativeTextPanel.Enabled = true; - this.ImageSelectedPicture.Image = selectedMedia.CreateImage(); } } } + private void UpdateSelectedMediaImage() + => this.ImageSelectedPicture.Image = this.Model.SelectedMediaItemImage; + private void ImageServiceCombo_SelectedIndexChanged(object sender, EventArgs e) => this.Model.SelectedMediaServiceName = this.ImageServiceCombo.Text; From f974e85754b3741e832bb98ec27eb7d33252e8a6 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Thu, 19 Jan 2023 09:13:51 +0900 Subject: [PATCH 10/11] =?UTF-8?q?MediaSelector=E3=81=AE=E3=82=B5=E3=83=A0?= =?UTF-8?q?=E3=83=8D=E3=82=A4=E3=83=AB=E7=94=BB=E5=83=8F=E3=81=AE=E7=B8=A6?= =?UTF-8?q?=E6=A8=AA=E6=AF=94=E3=82=92=E7=B6=AD=E6=8C=81=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween/MediaSelector.cs | 37 ++++++++++++++++++++++++--------- OpenTween/MediaSelectorPanel.cs | 1 + 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/OpenTween/MediaSelector.cs b/OpenTween/MediaSelector.cs index 9d9bad9a2..f70c1fb3f 100644 --- a/OpenTween/MediaSelector.cs +++ b/OpenTween/MediaSelector.cs @@ -204,21 +204,38 @@ public void AddMediaItemFromFilePath(string[] filePathArray) public void AddMediaItem(IMediaItem item) { - MemoryImage thumbnailImage; - try - { - thumbnailImage = item.CreateImage(); - } - catch (InvalidImageException) - { - thumbnailImage = MemoryImage.CopyFromImage(Properties.Resources.MultiMediaImage); - } - var id = item.Id.ToString(); + var thumbnailImage = this.GenerateThumbnailImage(item); this.ThumbnailList.Add(id, thumbnailImage); this.MediaItems.Add(item); } + private MemoryImage GenerateThumbnailImage(IMediaItem item) + { + using var origImage = this.CreateMediaItemImage(item); + var origSize = origImage.Image.Size; + var thumbSize = this.ThumbnailList.ImageList.ImageSize; + + using var bitmap = new Bitmap(thumbSize.Width, thumbSize.Height); + + // 縦横比を維持したまま thumbSize に収まるサイズに縮小する + using (var g = Graphics.FromImage(bitmap)) + { + var scale = Math.Min( + (float)thumbSize.Width / origSize.Width, + (float)thumbSize.Height / origSize.Height + ); + var fitSize = new SizeF(origSize.Width * scale, origSize.Height * scale); + var pos = new PointF( + x: (thumbSize.Width - fitSize.Width) / 2.0f, + y: (thumbSize.Height - fitSize.Height) / 2.0f + ); + g.DrawImage(origImage.Image, new RectangleF(pos, fitSize)); + } + + return MemoryImage.CopyFromImage(bitmap); + } + public void ClearMediaItems() { this.SelectedMediaItemId = null; diff --git a/OpenTween/MediaSelectorPanel.cs b/OpenTween/MediaSelectorPanel.cs index cefbe5df0..a342a3bd8 100644 --- a/OpenTween/MediaSelectorPanel.cs +++ b/OpenTween/MediaSelectorPanel.cs @@ -63,6 +63,7 @@ public MediaSelectorPanel() this.MediaListView.LargeImageList = this.Model.ThumbnailList.ImageList; var thumbnailWidth = 75 * this.DeviceDpi / 96; + this.Model.ThumbnailList.ImageList.ColorDepth = ColorDepth.Depth24Bit; this.Model.ThumbnailList.ImageList.ImageSize = new(thumbnailWidth, thumbnailWidth); this.Model.PropertyChanged += From fbb3af49ccfc8ecb8a72a7ea85510fdd27ca9e42 Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Thu, 19 Jan 2023 10:41:29 +0900 Subject: [PATCH 11/11] =?UTF-8?q?OpenTween=20v3.2.0=20=E3=83=AA=E3=83=AA?= =?UTF-8?q?=E3=83=BC=E3=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenTween/Properties/AssemblyInfo.cs | 2 +- OpenTween/Properties/Resources.Designer.cs | 7 ++++--- OpenTween/Resources/ChangeLog.txt | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/OpenTween/Properties/AssemblyInfo.cs b/OpenTween/Properties/AssemblyInfo.cs index 231ea88cf..02e4f6b7f 100644 --- a/OpenTween/Properties/AssemblyInfo.cs +++ b/OpenTween/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ // 次の GUID は、このプロジェクトが COM に公開される場合の、typelib の ID です [assembly: Guid("2d0ae0ba-adac-49a2-9b10-26fd69e695bf")] -[assembly: AssemblyVersion("3.1.0.1")] +[assembly: AssemblyVersion("3.2.0.0")] [assembly: InternalsVisibleTo("OpenTween.Tests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] // for Moq diff --git a/OpenTween/Properties/Resources.Designer.cs b/OpenTween/Properties/Resources.Designer.cs index 9b838ee36..6172012a0 100644 --- a/OpenTween/Properties/Resources.Designer.cs +++ b/OpenTween/Properties/Resources.Designer.cs @@ -571,7 +571,9 @@ internal static string ChangeIconToolStripMenuItem_Confirm { /// /// 更新履歴 /// - ///==== Unreleased + ///==== Ver 3.2.0(2023/01/20) + /// * NEW: 複数枚の画像を添付する際にリスト上で画像を確認できるようになりました + /// * CHG: アカウント追加時の認可関連のエラーメッセージがより詳細になるように変更 /// ///==== Ver 3.1.0(2023/01/14) /// * NEW: 引用ツイートを Ctrl+Shift+L で実行するショートカットを追加 (thx @WizardOfPSG!) @@ -581,8 +583,7 @@ internal static string ChangeIconToolStripMenuItem_Confirm { /// ///==== Ver 3.0.0(2023/01/11) /// * OpenTween v3.0.0 からは .NET Framework 4.8 以上が必須になります - /// - .NET Framework 4.8 ランタイムは https://dotnet.microsoft.com/ja-jp/download/dotnet-framework/net48 から入手できます - /// - Windows 10 21H1 以降には標準で .NET Framew [残りの文字列は切り詰められました]"; に類似しているローカライズされた文字列を検索します。 + /// - .NET Framework 4.8 ランタイムは https://dotnet.m [残りの文字列は切り詰められました]"; に類似しているローカライズされた文字列を検索します。 /// internal static string ChangeLog { get { diff --git a/OpenTween/Resources/ChangeLog.txt b/OpenTween/Resources/ChangeLog.txt index c48b48298..1e52e9fac 100644 --- a/OpenTween/Resources/ChangeLog.txt +++ b/OpenTween/Resources/ChangeLog.txt @@ -1,6 +1,6 @@ 更新履歴 -==== Unreleased +==== Ver 3.2.0(2023/01/20) * NEW: 複数枚の画像を添付する際にリスト上で画像を確認できるようになりました * CHG: アカウント追加時の認可関連のエラーメッセージがより詳細になるように変更