OSDN Git Service

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