OSDN Git Service

C# 8.0 のnull許容参照型を有効化
[opentween/open-tween.git] / OpenTween / MediaSelector.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2014 spx (@5px)
3 // All rights reserved.
4 // 
5 // This file is part of OpenTween.
6 // 
7 // This program is free software; you can redistribute it and/or modify it
8 // under the terms of the GNU General Public License as published by the Free
9 // Software Foundation; either version 3 of the License, or (at your option)
10 // any later version.
11 // 
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 // for more details. 
16 // 
17 // You should have received a copy of the GNU General Public License along
18 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
19 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
20 // Boston, MA 02110-1301, USA.
21
22 #nullable enable
23
24 using System;
25 using System.Collections.Generic;
26 using System.ComponentModel;
27 using System.Drawing;
28 using System.Data;
29 using System.IO;
30 using System.Linq;
31 using System.Text;
32 using System.Threading.Tasks;
33 using System.Windows.Forms;
34 using OpenTween.Api.DataModel;
35 using OpenTween.Connection;
36 using System.Diagnostics.CodeAnalysis;
37
38 namespace OpenTween
39 {
40     public partial class MediaSelector : UserControl
41     {
42         public event EventHandler<EventArgs> BeginSelecting;
43         public event EventHandler<EventArgs> EndSelecting;
44
45         public event EventHandler<EventArgs> FilePickDialogOpening;
46         public event EventHandler<EventArgs> FilePickDialogClosed;
47
48         public event EventHandler<EventArgs> SelectedServiceChanged;
49
50         [Browsable(false)]
51         [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
52         public OpenFileDialog? FilePickDialog { get; set; }
53
54         /// <summary>
55         /// 選択されている投稿先名を取得する。
56         /// </summary>
57         [Browsable(false)]
58         [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
59         public string ServiceName
60             => this.ImageServiceCombo.Text;
61
62         /// <summary>
63         /// 選択されている投稿先を示すインデックスを取得する。
64         /// </summary>
65         [Browsable(false)]
66         [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
67         public int ServiceIndex
68             => this.ImageServiceCombo.SelectedIndex;
69
70         /// <summary>
71         /// 選択されている投稿先の IMediaUploadService を取得する。
72         /// </summary>
73         [Browsable(false)]
74         [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
75         public IMediaUploadService? SelectedService
76         {
77             get
78             {
79                 var serviceName = this.ServiceName;
80                 if (string.IsNullOrEmpty(serviceName))
81                     return null;
82
83                 return this.pictureService.TryGetValue(serviceName, out var service)
84                     ? service : null;
85             }
86         }
87
88         /// <summary>
89         /// 指定された投稿先名から、作成済みの IMediaUploadService インスタンスを取得する。
90         /// </summary>
91         public IMediaUploadService GetService(string serviceName)
92         {
93             this.pictureService.TryGetValue(serviceName, out var service);
94             return service;
95         }
96
97         /// <summary>
98         /// 利用可能な全ての IMediaUploadService インスタンスを取得する。
99         /// </summary>
100         public ICollection<IMediaUploadService> GetServices()
101             => this.pictureService.Values;
102
103         private class SelectedMedia
104         {
105             public IMediaItem? Item { get; set; }
106             public MyCommon.UploadFileType Type { get; set; }
107             public string Text { get; set; }
108
109             public SelectedMedia(IMediaItem? item, MyCommon.UploadFileType type, string text)
110             {
111                 this.Item = item;
112                 this.Type = type;
113                 this.Text = text;
114             }
115
116             public SelectedMedia(string text)
117                 : this(null, MyCommon.UploadFileType.Invalid, text)
118             {
119             }
120
121             public bool IsValid
122                 => this.Item != null && this.Type != MyCommon.UploadFileType.Invalid;
123
124             public string Path
125                 => this.Item?.Path ?? "";
126
127             public string AltText => this.Item?.AltText ?? "";
128
129             public override string ToString()
130                 => this.Text;
131         }
132
133         private Dictionary<string, IMediaUploadService> pictureService = new Dictionary<string, IMediaUploadService>();
134
135         private void CreateServices(Twitter tw, TwitterConfiguration twitterConfig)
136         {
137             this.pictureService?.Clear();
138
139             this.pictureService = new Dictionary<string, IMediaUploadService> {
140                 ["Twitter"] = new TwitterPhoto(tw, twitterConfig),
141                 ["Imgur"] = new Imgur(twitterConfig),
142                 ["Mobypicture"] = new Mobypicture(tw, twitterConfig),
143             };
144         }
145
146         public MediaSelector()
147         {
148             InitializeComponent();
149
150             this.ImageSelectedPicture.InitialImage = Properties.Resources.InitialImage;
151         }
152
153         /// <summary>
154         /// 投稿先サービスなどを初期化する。
155         /// </summary>
156         public void Initialize(Twitter tw, TwitterConfiguration twitterConfig, string svc, int? index = null)
157         {
158             CreateServices(tw, twitterConfig);
159
160             SetImageServiceCombo();
161             SetImagePageCombo();
162
163             SelectImageServiceComboItem(svc, index);
164         }
165
166         /// <summary>
167         /// 投稿先サービスを再作成する。
168         /// </summary>
169         public void Reset(Twitter tw, TwitterConfiguration twitterConfig)
170         {
171             CreateServices(tw, twitterConfig);
172
173             SetImageServiceCombo();
174         }
175
176         /// <summary>
177         /// 指定されたファイルの投稿に対応した投稿先があるかどうかを示す値を取得する。
178         /// </summary>
179         public bool HasUploadableService(string fileName, bool ignoreSize)
180         {
181             var fl = new FileInfo(fileName);
182             var ext = fl.Extension;
183             var size = ignoreSize ? (long?)null : fl.Length;
184
185             if (IsUploadable(this.ServiceName, ext, size))
186                 return true;
187
188             foreach (string svc in ImageServiceCombo.Items)
189             {
190                 if (IsUploadable(svc, ext, size))
191                     return true;
192             }
193
194             return false;
195         }
196
197         /// <summary>
198         /// 指定された投稿先に投稿可能かどうかを示す値を取得する。
199         /// ファイルサイズの指定がなければ拡張子だけで判定する。
200         /// </summary>
201         private bool IsUploadable(string serviceName, string ext, long? size)
202         {
203             if (!string.IsNullOrEmpty(serviceName))
204             {
205                 var imageService = this.pictureService[serviceName];
206                 if (imageService.CheckFileExtension(ext))
207                 {
208                     if (!size.HasValue)
209                         return true;
210
211                     if (imageService.CheckFileSize(ext, size.Value))
212                         return true;
213                 }
214             }
215             return false;
216         }
217
218         /// <summary>
219         /// 投稿するファイルとその投稿先を選択するためのコントロールを表示する。
220         /// </summary>
221         private void BeginSelection(IMediaItem[] items)
222         {
223             if (items == null || items.Length == 0)
224             {
225                 BeginSelection();
226                 return;
227             }
228
229             var service = this.SelectedService;
230             if (service == null) return;
231
232             var count = Math.Min(items.Length, service.MaxMediaCount);
233             if (!this.Visible || count > 1)
234             {
235                 // 非表示時または複数のファイル指定は新規選択として扱う
236                 SetImagePageCombo();
237
238                 this.BeginSelecting?.Invoke(this, EventArgs.Empty);
239
240                 this.Visible = true;
241             }
242             this.Enabled = true;
243
244             if (count == 1)
245             {
246                 ImagefilePathText.Text = items[0].Path;
247                 AlternativeTextBox.Text = items[0].AltText;
248                 ImageFromSelectedFile(items[0], false);
249             }
250             else
251             {
252                 for (var i = 0; i < count; i++)
253                 {
254                     var index = ImagePageCombo.Items.Count - 1;
255                     if (index == 0)
256                     {
257                         ImagefilePathText.Text = items[i].Path;
258                         AlternativeTextBox.Text = items[i].AltText;
259                     }
260                     ImageFromSelectedFile(index, items[i], false);
261                 }
262             }
263         }
264
265         /// <summary>
266         /// 投稿するファイルとその投稿先を選択するためのコントロールを表示する(主にD&amp;D用)。
267         /// </summary>
268         public void BeginSelection(string[] fileNames)
269         {
270             if (fileNames == null || fileNames.Length == 0)
271             {
272                 BeginSelection();
273                 return;
274             }
275
276             var items = fileNames.Select(x => CreateFileMediaItem(x, false)).OfType<IMediaItem>().ToArray();
277             BeginSelection(items);
278         }
279
280         /// <summary>
281         /// 投稿するファイルとその投稿先を選択するためのコントロールを表示する。
282         /// </summary>
283         public void BeginSelection(Image image)
284         {
285             if (image == null)
286             {
287                 BeginSelection();
288                 return;
289             }
290
291             var items = new [] { CreateMemoryImageMediaItem(image, false) }.OfType<IMediaItem>().ToArray();
292             BeginSelection(items);
293         }
294
295         /// <summary>
296         /// 投稿するファイルとその投稿先を選択するためのコントロールを表示する。
297         /// </summary>
298         public void BeginSelection()
299         {
300             if (!this.Visible)
301             {
302                 this.BeginSelecting?.Invoke(this, EventArgs.Empty);
303
304                 this.Visible = true;
305                 this.Enabled = true;
306
307                 var media = (SelectedMedia)ImagePageCombo.SelectedItem;
308                 ImageFromSelectedFile(media.Item, true);
309                 ImagefilePathText.Focus();
310             }
311         }
312
313         /// <summary>
314         /// 選択処理を終了してコントロールを隠す。
315         /// </summary>
316         public void EndSelection()
317         {
318             if (this.Visible)
319             {
320                 ImagefilePathText.CausesValidation = false;
321
322                 this.EndSelecting?.Invoke(this, EventArgs.Empty);
323
324                 this.Visible = false;
325                 this.Enabled = false;
326                 ClearImageSelectedPicture();
327
328                 ImagefilePathText.CausesValidation = true;
329             }
330         }
331
332         /// <summary>
333         /// 選択された投稿先名と投稿する MediaItem を取得する。MediaItem は不要になったら呼び出し側にて破棄すること。
334         /// </summary>
335         public bool TryGetSelectedMedia([NotNullWhen(true)] out string? imageService, [NotNullWhen(true)] out IMediaItem[]? mediaItems)
336         {
337             var validItems = ImagePageCombo.Items.Cast<SelectedMedia>()
338                              .Where(x => x.IsValid).Select(x => x.Item).OfType<IMediaItem>().ToArray();
339
340             if (validItems.Length > 0 &&
341                 ImageServiceCombo.SelectedIndex > -1)
342             {
343                 var serviceName = this.ServiceName;
344                 if (MessageBox.Show(string.Format(Properties.Resources.PostPictureConfirm1, serviceName, validItems.Length),
345                                    Properties.Resources.PostPictureConfirm2,
346                                    MessageBoxButtons.OKCancel,
347                                    MessageBoxIcon.Question,
348                                    MessageBoxDefaultButton.Button1)
349                                == DialogResult.OK)
350                 {
351                     //収集した MediaItem が破棄されないように、予め null を代入しておく
352                     foreach (SelectedMedia media in ImagePageCombo.Items)
353                     {
354                         if (media != null) media.Item = null;
355                     }
356
357                     imageService = serviceName;
358                     mediaItems = validItems;
359                     EndSelection();
360                     SetImagePageCombo();
361                     return true;
362                 }
363             }
364             else
365             {
366                 MessageBox.Show(Properties.Resources.PostPictureWarn1, Properties.Resources.PostPictureWarn2);
367             }
368
369             imageService = null;
370             mediaItems = null;
371             return false;
372         }
373
374         private MemoryImageMediaItem? CreateMemoryImageMediaItem(Image image, bool noMsgBox)
375         {
376             if (image == null) return null;
377
378             MemoryImage? memoryImage = null;
379             try
380             {
381                 // image から png 形式の MemoryImage を生成
382                 memoryImage = MemoryImage.CopyFromImage(image);
383
384                 return new MemoryImageMediaItem(memoryImage);
385             }
386             catch
387             {
388                 memoryImage?.Dispose();
389
390                 if (!noMsgBox) MessageBox.Show("Unable to create MemoryImage.");
391                 return null;
392             }
393         }
394
395         private IMediaItem? CreateFileMediaItem(string path, bool noMsgBox)
396         {
397             if (string.IsNullOrEmpty(path)) return null;
398
399             try
400             {
401                 return new FileMediaItem(path);
402             }
403             catch
404             {
405                 if (!noMsgBox) MessageBox.Show("Invalid file path: " + path);
406                 return null;
407             }
408         }
409
410         private void ValidateNewFileMediaItem(string path, string altText, bool noMsgBox)
411         {
412             var media = (SelectedMedia)ImagePageCombo.SelectedItem;
413             var item = media.Item;
414
415             if (path != media.Path)
416             {
417                 DisposeMediaItem(media.Item);
418                 media.Item = null;
419
420                 item = CreateFileMediaItem(path, noMsgBox);
421             }
422
423             if (item != null)
424                 item.AltText = altText;
425
426             ImagefilePathText.Text = path;
427             AlternativeTextBox.Text = altText;
428             ImageFromSelectedFile(item, noMsgBox);
429         }
430
431         private void DisposeMediaItem(IMediaItem? item)
432         {
433             var disposableItem = item as IDisposable;
434             disposableItem?.Dispose();
435         }
436
437         private void FilePickButton_Click(object sender, EventArgs e)
438         {
439             var service = this.SelectedService;
440
441             if (FilePickDialog == null || service == null) return;
442             FilePickDialog.Filter = service.SupportedFormatsStrForDialog;
443             FilePickDialog.Title = Properties.Resources.PickPictureDialog1;
444             FilePickDialog.FileName = "";
445
446             this.FilePickDialogOpening?.Invoke(this, EventArgs.Empty);
447
448             try
449             {
450                 if (FilePickDialog.ShowDialog() == DialogResult.Cancel) return;
451             }
452             finally
453             {
454                 this.FilePickDialogClosed?.Invoke(this, EventArgs.Empty);
455             }
456
457             ValidateNewFileMediaItem(FilePickDialog.FileName, AlternativeTextBox.Text.Trim(), false);
458         }
459
460         private void ImagefilePathText_Validating(object sender, CancelEventArgs e)
461         {
462             if (ImageCancelButton.Focused)
463             {
464                 ImagefilePathText.CausesValidation = false;
465                 return;
466             }
467
468             ValidateNewFileMediaItem(ImagefilePathText.Text.Trim(), AlternativeTextBox.Text.Trim(), false);
469         }
470
471         private void ImageFromSelectedFile(IMediaItem? item, bool noMsgBox)
472             => this.ImageFromSelectedFile(-1, item, noMsgBox);
473
474         private void ImageFromSelectedFile(int index, IMediaItem? item, bool noMsgBox)
475         {
476             var valid = false;
477
478             try
479             {
480                 var imageService = this.SelectedService;
481                 if (imageService == null) return;
482
483                 var selectedIndex = ImagePageCombo.SelectedIndex;
484                 if (index < 0) index = selectedIndex;
485
486                 if (index >= ImagePageCombo.Items.Count)
487                     throw new ArgumentOutOfRangeException(nameof(index));
488
489                 var isSelectedPage = (index == selectedIndex);
490
491                 if (isSelectedPage)
492                     this.ClearImageSelectedPicture();
493
494                 if (item == null || string.IsNullOrEmpty(item.Path)) return;
495
496                 try
497                 {
498                     var ext = item.Extension;
499                     var size = item.Size;
500
501                     if (!imageService.CheckFileExtension(ext))
502                     {
503                         //画像以外の形式
504                         if (!noMsgBox)
505                         {
506                             MessageBox.Show(
507                                 string.Format(Properties.Resources.PostPictureWarn3, this.ServiceName, MakeAvailableServiceText(ext, size), ext, item.Name),
508                                 Properties.Resources.PostPictureWarn4,
509                                 MessageBoxButtons.OK,
510                                 MessageBoxIcon.Warning);
511                         }
512                         return;
513                     }
514
515                     if (!imageService.CheckFileSize(ext, size))
516                     {
517                         // ファイルサイズが大きすぎる
518                         if (!noMsgBox)
519                         {
520                             MessageBox.Show(
521                                 string.Format(Properties.Resources.PostPictureWarn5, this.ServiceName, MakeAvailableServiceText(ext, size), item.Name),
522                                 Properties.Resources.PostPictureWarn4,
523                                 MessageBoxButtons.OK,
524                                 MessageBoxIcon.Warning);
525                         }
526                         return;
527                     }
528
529                     if (item.IsImage)
530                     {
531                         if (isSelectedPage)
532                             ImageSelectedPicture.Image = item.CreateImage();
533                         SetImagePage(index, item, MyCommon.UploadFileType.Picture);
534                     }
535                     else
536                     {
537                         SetImagePage(index, item, MyCommon.UploadFileType.MultiMedia);
538                     }
539
540                     valid = true;  //正常終了
541                 }
542                 catch (FileNotFoundException)
543                 {
544                     if (!noMsgBox) MessageBox.Show("File not found.");
545                 }
546                 catch (Exception)
547                 {
548                     if (!noMsgBox) MessageBox.Show("The type of this file is not image.");
549                 }
550             }
551             finally
552             {
553                 if (!valid)
554                 {
555                     ClearImagePage(index);
556                     DisposeMediaItem(item);
557                 }
558             }
559         }
560
561         private string MakeAvailableServiceText(string ext, long fileSize)
562         {
563             var text = string.Join(", ",
564                 ImageServiceCombo.Items.Cast<string>()
565                     .Where(serviceName =>
566                         !string.IsNullOrEmpty(serviceName) &&
567                         this.pictureService[serviceName].CheckFileExtension(ext) &&
568                         this.pictureService[serviceName].CheckFileSize(ext, fileSize)));
569
570             if (string.IsNullOrEmpty(text))
571                 return Properties.Resources.PostPictureWarn6;
572
573             return text;
574         }
575
576         private void ClearImageSelectedPicture()
577         {
578             var oldImage = this.ImageSelectedPicture.Image;
579             this.ImageSelectedPicture.Image = null;
580             oldImage?.Dispose();
581
582             this.ImageSelectedPicture.ShowInitialImage();
583         }
584
585         private void ImageCancelButton_Click(object sender, EventArgs e)
586             => this.EndSelection();
587
588         private void ImageSelection_KeyDown(object sender, KeyEventArgs e)
589         {
590             if (e.KeyCode == Keys.Escape)
591             {
592                 EndSelection();
593             }
594         }
595
596         private void ImageSelection_KeyPress(object sender, KeyPressEventArgs e)
597         {
598             if (Convert.ToInt32(e.KeyChar) == 0x1B)
599             {
600                 ImagefilePathText.CausesValidation = false;
601                 e.Handled = true;
602             }
603         }
604
605         private void ImageSelection_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
606         {
607             if (e.KeyCode == Keys.Escape)
608             {
609                 ImagefilePathText.CausesValidation = false;
610             }
611         }
612
613         private void SetImageServiceCombo()
614         {
615             using (ControlTransaction.Update(ImageServiceCombo))
616             {
617                 var svc = "";
618                 if (ImageServiceCombo.SelectedIndex > -1) svc = ImageServiceCombo.Text;
619                 ImageServiceCombo.Items.Clear();
620
621                 // Add service names to combobox
622                 foreach (var key in pictureService.Keys)
623                 {
624                     ImageServiceCombo.Items.Add(key);
625                 }
626
627                 SelectImageServiceComboItem(svc);
628             }
629         }
630
631         private void SelectImageServiceComboItem(string svc, int? index = null)
632         {
633             int idx;
634             if (string.IsNullOrEmpty(svc))
635             {
636                 idx = index ?? 0;
637             }
638             else
639             {
640                 idx = ImageServiceCombo.Items.IndexOf(svc);
641
642                 // svc が空白以外かつ存在しないサービス名の場合は Twitter を選択させる
643                 // (廃止されたサービスを選択していた場合の対応)
644                 if (idx == -1) idx = 0;
645             }
646
647             try
648             {
649                 ImageServiceCombo.SelectedIndex = idx;
650             }
651             catch (ArgumentOutOfRangeException)
652             {
653                 ImageServiceCombo.SelectedIndex = 0;
654             }
655
656             this.UpdateAltTextPanelVisible();
657         }
658
659         private void UpdateAltTextPanelVisible()
660             => this.AlternativeTextPanel.Visible = this.SelectedService switch
661             {
662                 null => false,
663                 var service => service.CanUseAltText,
664             };
665
666         private void ImageServiceCombo_SelectedIndexChanged(object sender, EventArgs e)
667         {
668             if (this.Visible)
669             {
670                 var imageService = this.SelectedService;
671                 if (imageService != null)
672                 {
673                     this.UpdateAltTextPanelVisible();
674
675                     if (ImagePageCombo.Items.Count > 0)
676                     {
677                         // 画像が選択された投稿先に対応しているかをチェックする
678                         // TODO: 複数の選択済み画像があるなら、できれば全てを再チェックしたほうがいい
679                         if (this.ServiceName == "Twitter")
680                         {
681                             ValidateSelectedImagePage();
682                         }
683                         else
684                         {
685                             if (ImagePageCombo.Items.Count > 1)
686                             {
687                                 // 複数の選択済み画像のうち、1枚目のみを残す
688                                 SetImagePageCombo((SelectedMedia)ImagePageCombo.Items[0]);
689                             }
690                             else
691                             {
692                                 ImagePageCombo.Enabled = false;
693                                 var valid = false;
694
695                                 try
696                                 {
697                                     var item = ((SelectedMedia)ImagePageCombo.Items[0]).Item;
698                                     if (item != null)
699                                     {
700                                         var ext = item.Extension;
701                                         if (imageService.CheckFileExtension(ext) &&
702                                             imageService.CheckFileSize(ext, item.Size))
703                                         {
704                                             valid = true;
705                                         }
706                                     }
707                                 }
708                                 catch
709                                 {
710                                 }
711                                 finally
712                                 {
713                                     if (!valid)
714                                     {
715                                         ClearImageSelectedPicture();
716                                         ClearSelectedImagePage();
717                                     }
718                                 }
719                             }
720                         }
721                     }
722                 }
723             }
724
725             this.SelectedServiceChanged?.Invoke(this, EventArgs.Empty);
726         }
727
728         private void SetImagePageCombo(SelectedMedia? media = null)
729         {
730             using (ControlTransaction.Update(ImagePageCombo))
731             {
732                 ImagePageCombo.Enabled = false;
733
734                 foreach (SelectedMedia oldMedia in ImagePageCombo.Items)
735                 {
736                     if (oldMedia == null || oldMedia == media) continue;
737                     DisposeMediaItem(oldMedia.Item);
738                 }
739                 ImagePageCombo.Items.Clear();
740
741                 if (media == null)
742                     media = new SelectedMedia("1");
743
744                 ImagePageCombo.Items.Add(media);
745                 ImagefilePathText.Text = media.Path;
746                 AlternativeTextBox.Text = media.AltText;
747
748                 ImagePageCombo.SelectedIndex = 0;
749             }
750         }
751
752         private void AddNewImagePage(int selectedIndex)
753         {
754             var service = this.SelectedService;
755             if (service == null) return;
756
757             if (selectedIndex < service.MaxMediaCount - 1)
758             {
759                 // 投稿先の投稿可能枚数まで選択できるようにする
760                 var count = ImagePageCombo.Items.Count;
761                 if (selectedIndex == count - 1)
762                 {
763                     count++;
764                     ImagePageCombo.Items.Add(new SelectedMedia(count.ToString()));
765                     ImagePageCombo.Enabled = true;
766                 }
767             }
768         }
769
770         private void SetSelectedImagePage(IMediaItem item, MyCommon.UploadFileType type)
771             => this.SetImagePage(-1, item, type);
772
773         private void SetImagePage(int index, IMediaItem item, MyCommon.UploadFileType type)
774         {
775             var selectedIndex = ImagePageCombo.SelectedIndex;
776             if (index < 0) index = selectedIndex;
777
778             var media = (SelectedMedia)ImagePageCombo.Items[index];
779             if (media.Item != item)
780             {
781                 DisposeMediaItem(media.Item);
782                 media.Item = item;
783             }
784             media.Type = type;
785
786             AddNewImagePage(index);
787         }
788
789         private void ClearSelectedImagePage()
790             => this.ClearImagePage(-1);
791
792         private void ClearImagePage(int index)
793         {
794             var selectedIndex = ImagePageCombo.SelectedIndex;
795             if (index < 0) index = selectedIndex;
796
797             var media = (SelectedMedia)ImagePageCombo.Items[index];
798             DisposeMediaItem(media.Item);
799             media.Item = null;
800             media.Type = MyCommon.UploadFileType.Invalid;
801
802             if (index == selectedIndex)
803             {
804                 ImagefilePathText.Text = "";
805                 AlternativeTextBox.Text = "";
806             }
807         }
808
809         private void ValidateSelectedImagePage()
810         {
811             var idx = ImagePageCombo.SelectedIndex;
812             var media = (SelectedMedia)ImagePageCombo.Items[idx];
813             ImageServiceCombo.Enabled = (idx == 0);  // idx == 0 以外では投稿先サービスを選べないようにする
814             ImagefilePathText.Text = media.Path;
815             AlternativeTextBox.Text = media.AltText;
816             ImageFromSelectedFile(media.Item, true);
817         }
818
819         private void ImagePageCombo_SelectedIndexChanged(object sender, EventArgs e)
820             => this.ValidateSelectedImagePage();
821
822         private void AlternativeTextBox_Validating(object sender, CancelEventArgs e)
823         {
824             var imageFilePath = this.ImagefilePathText.Text.Trim();
825             var altText = this.AlternativeTextBox.Text.Trim();
826             this.ValidateNewFileMediaItem(imageFilePath, altText, noMsgBox: false);
827         }
828     }
829 }