OSDN Git Service

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