OSDN Git Service

画像投稿先のサービスから「img.ly」を削除
[opentween/open-tween.git] / OpenTween / MediaSelector.cs
index ceab332..400d742 100644 (file)
@@ -29,20 +29,20 @@ using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 using System.Windows.Forms;
-using OpenTween.Api;
+using OpenTween.Api.DataModel;
 using OpenTween.Connection;
 
 namespace OpenTween
 {
     public partial class MediaSelector : UserControl
     {
-        public event EventHandler BeginSelecting;
-        public event EventHandler EndSelecting;
+        public event EventHandler<EventArgs> BeginSelecting;
+        public event EventHandler<EventArgs> EndSelecting;
 
-        public event EventHandler FilePickDialogOpening;
-        public event EventHandler FilePickDialogClosed;
+        public event EventHandler<EventArgs> FilePickDialogOpening;
+        public event EventHandler<EventArgs> FilePickDialogClosed;
 
-        public event EventHandler SelectedServiceChanged;
+        public event EventHandler<EventArgs> SelectedServiceChanged;
 
         [Browsable(false)]
         [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
@@ -69,12 +69,29 @@ namespace OpenTween
         }
 
         /// <summary>
+        /// 選択されている投稿先の IMediaUploadService を取得する。
+        /// </summary>
+        [Browsable(false)]
+        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+        public IMediaUploadService SelectedService
+        {
+            get
+            {
+                var serviceName = this.ServiceName;
+                if (string.IsNullOrEmpty(serviceName))
+                    return null;
+
+                return this.pictureService.TryGetValue(serviceName, out var service)
+                    ? service : null;
+            }
+        }
+
+        /// <summary>
         /// 指定された投稿先名から、作成済みの IMediaUploadService インスタンスを取得する。
         /// </summary>
         public IMediaUploadService GetService(string serviceName)
         {
-            IMediaUploadService service;
-            this.pictureService.TryGetValue(serviceName, out service);
+            this.pictureService.TryGetValue(serviceName, out var service);
             return service;
         }
 
@@ -88,27 +105,40 @@ namespace OpenTween
 
         private class SelectedMedia
         {
-            public string Path { get; set; }
+            public IMediaItem Item { get; set; }
             public MyCommon.UploadFileType Type { get; set; }
             public string Text { get; set; }
 
-            public SelectedMedia(string path, MyCommon.UploadFileType type, string text)
+            public SelectedMedia(IMediaItem item, MyCommon.UploadFileType type, string text)
             {
-                this.Path = path;
+                this.Item = item;
                 this.Type = type;
                 this.Text = text;
             }
 
             public SelectedMedia(string text)
-                : this("", MyCommon.UploadFileType.Invalid, text)
+                : this(null, MyCommon.UploadFileType.Invalid, text)
             {
             }
 
             public bool IsValid
             {
-                get { return this.Type != MyCommon.UploadFileType.Invalid; }
+                get
+                {
+                    return this.Item != null && this.Type != MyCommon.UploadFileType.Invalid;
+                }
+            }
+
+            public string Path
+            {
+                get
+                {
+                    return this.Item?.Path ?? "";
+                }
             }
 
+            public string AltText => this.Item?.AltText ?? "";
+
             public override string ToString()
             {
                 return this.Text;
@@ -119,16 +149,13 @@ namespace OpenTween
 
         private void CreateServices(Twitter tw, TwitterConfiguration twitterConfig)
         {
-            if (this.pictureService != null) this.pictureService.Clear();
+            this.pictureService?.Clear();
             this.pictureService = null;
 
             this.pictureService = new Dictionary<string, IMediaUploadService> {
-                {"Twitter", new TwitterPhoto(tw, twitterConfig)},
-                {"img.ly", new imgly(tw, twitterConfig)},
-                {"yfrog", new yfrog(tw, twitterConfig)},
-                {"ついっぷるフォト", new TwipplePhoto(tw, twitterConfig)},
-                {"Imgur", new Imgur(tw, twitterConfig)},
-                {"Mobypicture", new Mobypicture(tw, twitterConfig)},
+                ["Twitter"] = new TwitterPhoto(tw, twitterConfig),
+                ["Imgur"] = new Imgur(tw, twitterConfig),
+                ["Mobypicture"] = new Mobypicture(tw, twitterConfig),
             };
         }
 
@@ -191,14 +218,13 @@ namespace OpenTween
         {
             if (!string.IsNullOrEmpty(serviceName))
             {
-                if (size.HasValue)
+                var imageService = this.pictureService[serviceName];
+                if (imageService.CheckFileExtension(ext))
                 {
-                    if (this.pictureService[serviceName].CheckFileSize(ext, size.Value))
+                    if (!size.HasValue)
                         return true;
-                }
-                else
-                {
-                    if (this.pictureService[serviceName].CheckFileExtension(ext))
+
+                    if (imageService.CheckFileSize(ext, size.Value))
                         return true;
                 }
             }
@@ -207,56 +233,96 @@ namespace OpenTween
 
         /// <summary>
         /// 投稿するファイルとその投稿先を選択するためのコントロールを表示する。
-        /// D&Dをサポートする場合は引数にドロップされたファイル名を指定して呼ぶこと。
         /// </summary>
-        public void BeginSelection(string[] fileNames = null)
+        private void BeginSelection(IMediaItem[] items)
         {
-            if (fileNames != null && fileNames.Length > 0)
+            if (items == null || items.Length == 0)
             {
-                var serviceName = this.ServiceName;
-                if (string.IsNullOrEmpty(serviceName)) return;
-                var service = this.pictureService[serviceName];
+                BeginSelection();
+                return;
+            }
 
-                var count = Math.Min(fileNames.Length, service.MaxMediaCount);
-                if (!this.Visible || count > 1)
-                {
-                    // 非表示時または複数のファイル指定は新規選択として扱う
-                    SetImagePageCombo();
+            var service = this.SelectedService;
+            if (service == null) return;
 
-                    if (this.BeginSelecting != null)
-                        this.BeginSelecting(this, EventArgs.Empty);
+            var count = Math.Min(items.Length, service.MaxMediaCount);
+            if (!this.Visible || count > 1)
+            {
+                // 非表示時または複数のファイル指定は新規選択として扱う
+                SetImagePageCombo();
 
-                    this.Visible = true;
-                }
-                this.Enabled = true;
+                this.BeginSelecting?.Invoke(this, EventArgs.Empty);
 
-                if (count == 1)
-                {
-                    ImagefilePathText.Text = fileNames[0];
-                    ImageFromSelectedFile(false);
-                }
-                else
+                this.Visible = true;
+            }
+            this.Enabled = true;
+
+            if (count == 1)
+            {
+                ImagefilePathText.Text = items[0].Path;
+                AlternativeTextBox.Text = items[0].AltText;
+                ImageFromSelectedFile(items[0], false);
+            }
+            else
+            {
+                for (int i = 0; i < count; i++)
                 {
-                    for (int i = 0; i < count; i++)
+                    var index = ImagePageCombo.Items.Count - 1;
+                    if (index == 0)
                     {
-                        var index = ImagePageCombo.Items.Count - 1;
-                        if (index == 0) ImagefilePathText.Text = fileNames[i];
-                        ImageFromSelectedFile(index, fileNames[i], false);
+                        ImagefilePathText.Text = items[i].Path;
+                        AlternativeTextBox.Text = items[i].AltText;
                     }
+                    ImageFromSelectedFile(index, items[i], false);
                 }
             }
-            else
+        }
+
+        /// <summary>
+        /// 投稿するファイルとその投稿先を選択するためのコントロールを表示する(主にD&amp;D用)。
+        /// </summary>
+        public void BeginSelection(string[] fileNames)
+        {
+            if (fileNames == null || fileNames.Length == 0)
             {
-                if (!this.Visible)
-                {
-                    if (this.BeginSelecting != null)
-                        this.BeginSelecting(this, EventArgs.Empty);
+                BeginSelection();
+                return;
+            }
 
-                    this.Visible = true;
-                    this.Enabled = true;
-                    ImageFromSelectedFile(true);
-                    ImagefilePathText.Focus();
-                }
+            var items = fileNames.Select(x => CreateFileMediaItem(x, false)).OfType<IMediaItem>().ToArray();
+            BeginSelection(items);
+        }
+
+        /// <summary>
+        /// 投稿するファイルとその投稿先を選択するためのコントロールを表示する。
+        /// </summary>
+        public void BeginSelection(Image image)
+        {
+            if (image == null)
+            {
+                BeginSelection();
+                return;
+            }
+
+            var items = new [] { CreateMemoryImageMediaItem(image, false) }.OfType<IMediaItem>().ToArray();
+            BeginSelection(items);
+        }
+
+        /// <summary>
+        /// 投稿するファイルとその投稿先を選択するためのコントロールを表示する。
+        /// </summary>
+        public void BeginSelection()
+        {
+            if (!this.Visible)
+            {
+                this.BeginSelecting?.Invoke(this, EventArgs.Empty);
+
+                this.Visible = true;
+                this.Enabled = true;
+
+                var media = (SelectedMedia)ImagePageCombo.SelectedItem;
+                ImageFromSelectedFile(media.Item, true);
+                ImagefilePathText.Focus();
             }
         }
 
@@ -269,8 +335,7 @@ namespace OpenTween
             {
                 ImagefilePathText.CausesValidation = false;
 
-                if (this.EndSelecting != null)
-                    this.EndSelecting(this, EventArgs.Empty);
+                this.EndSelecting?.Invoke(this, EventArgs.Empty);
 
                 this.Visible = false;
                 this.Enabled = false;
@@ -281,26 +346,32 @@ namespace OpenTween
         }
 
         /// <summary>
-        /// é\81¸æ\8a\9eã\81\95ã\82\8cã\81\9fæ\8a\95稿å\85\88å\90\8dã\81¨æ\8a\95稿ã\83\95ã\82¡ã\82¤ã\83«å\90\8dã\82\92å\8f\96å¾\97ã\81\99ã\82\8b
+        /// é\81¸æ\8a\9eã\81\95ã\82\8cã\81\9fæ\8a\95稿å\85\88å\90\8dã\81¨æ\8a\95稿ã\81\99ã\82\8b MediaItem ã\82\92å\8f\96å¾\97ã\81\99ã\82\8bã\80\82MediaItem ã\81¯ä¸\8dè¦\81ã\81«ã\81ªã\81£ã\81\9fã\82\89å\91¼ã\81³å\87ºã\81\97å\81´ã\81«ã\81¦ç ´æ£\84ã\81\99ã\82\8bã\81\93ã\81¨
         /// </summary>
-        public bool TryGetSelectedMedia(out string imageService, out string[] imagePaths)
+        public bool TryGetSelectedMedia(out string imageService, out IMediaItem[] mediaItems)
         {
-            var validPaths = ImagePageCombo.Items.Cast<SelectedMedia>()
-                             .Where(x => x.IsValid).Select(x => x.Path).ToArray();
+            var validItems = ImagePageCombo.Items.Cast<SelectedMedia>()
+                             .Where(x => x.IsValid).Select(x => x.Item).OfType<IMediaItem>().ToArray();
 
-            if (validPaths.Length > 0 &&
+            if (validItems.Length > 0 &&
                 ImageServiceCombo.SelectedIndex > -1)
             {
                 var serviceName = this.ServiceName;
-                if (MessageBox.Show(string.Format(Properties.Resources.PostPictureConfirm1, serviceName, validPaths.Length),
+                if (MessageBox.Show(string.Format(Properties.Resources.PostPictureConfirm1, serviceName, validItems.Length),
                                    Properties.Resources.PostPictureConfirm2,
                                    MessageBoxButtons.OKCancel,
                                    MessageBoxIcon.Question,
                                    MessageBoxDefaultButton.Button1)
                                == DialogResult.OK)
                 {
+                    //収集した MediaItem が破棄されないように、予め null を代入しておく
+                    foreach (SelectedMedia media in ImagePageCombo.Items)
+                    {
+                        if (media != null) media.Item = null;
+                    }
+
                     imageService = serviceName;
-                    imagePaths = validPaths;
+                    mediaItems = validItems;
                     EndSelection();
                     SetImagePageCombo();
                     return true;
@@ -313,19 +384,83 @@ namespace OpenTween
 
             EndSelection();
             imageService = null;
-            imagePaths = null;
+            mediaItems = null;
             return false;
         }
 
+        private MemoryImageMediaItem CreateMemoryImageMediaItem(Image image, bool noMsgBox)
+        {
+            if (image == null) return null;
+
+            MemoryImage memoryImage = null;
+            try
+            {
+                // image から png 形式の MemoryImage を生成
+                memoryImage = MemoryImage.CopyFromImage(image);
+
+                return new MemoryImageMediaItem(memoryImage);
+            }
+            catch
+            {
+                memoryImage?.Dispose();
+
+                if (!noMsgBox) MessageBox.Show("Unable to create MemoryImage.");
+                return null;
+            }
+        }
+
+        private IMediaItem CreateFileMediaItem(string path, bool noMsgBox)
+        {
+            if (string.IsNullOrEmpty(path)) return null;
+
+            try
+            {
+                return new FileMediaItem(path);
+            }
+            catch
+            {
+                if (!noMsgBox) MessageBox.Show("Invalid file path: " + path);
+                return null;
+            }
+        }
+
+        private void ValidateNewFileMediaItem(string path, string altText, bool noMsgBox)
+        {
+            var media = (SelectedMedia)ImagePageCombo.SelectedItem;
+            var item = media.Item;
+
+            if (path != media.Path)
+            {
+                DisposeMediaItem(media.Item);
+                media.Item = null;
+
+                item = CreateFileMediaItem(path, noMsgBox);
+            }
+
+            if (item != null)
+                item.AltText = altText;
+
+            ImagefilePathText.Text = path;
+            AlternativeTextBox.Text = altText;
+            ImageFromSelectedFile(item, noMsgBox);
+        }
+
+        private void DisposeMediaItem(IMediaItem item)
+        {
+            var disposableItem = item as IDisposable;
+            disposableItem?.Dispose();
+        }
+
         private void FilePickButton_Click(object sender, EventArgs e)
         {
-            if (FilePickDialog == null || string.IsNullOrEmpty(this.ServiceName)) return;
-            FilePickDialog.Filter = this.pictureService[this.ServiceName].SupportedFormatsStrForDialog;
+            var service = this.SelectedService;
+
+            if (FilePickDialog == null || service == null) return;
+            FilePickDialog.Filter = service.SupportedFormatsStrForDialog;
             FilePickDialog.Title = Properties.Resources.PickPictureDialog1;
             FilePickDialog.FileName = "";
 
-            if (this.FilePickDialogOpening != null)
-                this.FilePickDialogOpening(this, EventArgs.Empty);
+            this.FilePickDialogOpening?.Invoke(this, EventArgs.Empty);
 
             try
             {
@@ -333,12 +468,10 @@ namespace OpenTween
             }
             finally
             {
-                if (this.FilePickDialogClosed != null)
-                    this.FilePickDialogClosed(this, EventArgs.Empty);
+                this.FilePickDialogClosed?.Invoke(this, EventArgs.Empty);
             }
 
-            ImagefilePathText.Text = FilePickDialog.FileName;
-            ImageFromSelectedFile(false);
+            ValidateNewFileMediaItem(FilePickDialog.FileName, AlternativeTextBox.Text.Trim(), false);
         }
 
         private void ImagefilePathText_Validating(object sender, CancelEventArgs e)
@@ -349,99 +482,98 @@ namespace OpenTween
                 return;
             }
 
-            ImageFromSelectedFile(false);
+            ValidateNewFileMediaItem(ImagefilePathText.Text.Trim(), AlternativeTextBox.Text.Trim(), false);
         }
 
-        private void ImageFromSelectedFile(bool suppressMsgBox)
+        private void ImageFromSelectedFile(IMediaItem item, bool noMsgBox)
         {
-            ImagefilePathText.Text = ImagefilePathText.Text.Trim();
-            ImageFromSelectedFile(-1, ImagefilePathText.Text, suppressMsgBox);
+            ImageFromSelectedFile(-1, item, noMsgBox);
         }
 
-        private void ImageFromSelectedFile(int index, string fileName, bool suppressMsgBox)
+        private void ImageFromSelectedFile(int index, IMediaItem item, bool noMsgBox)
         {
-            var serviceName = this.ServiceName;
-            if (string.IsNullOrEmpty(serviceName)) return;
+            var valid = false;
 
-            var selectedIndex = ImagePageCombo.SelectedIndex;
-            if (index < 0) index = selectedIndex;
+            try
+            {
+                var imageService = this.SelectedService;
+                if (imageService == null) return;
 
-            if (index >= ImagePageCombo.Items.Count)
-                throw new ArgumentOutOfRangeException("index");
+                var selectedIndex = ImagePageCombo.SelectedIndex;
+                if (index < 0) index = selectedIndex;
 
-            var imageService = this.pictureService[serviceName];
-            var isSelectedPage = (index == selectedIndex);
+                if (index >= ImagePageCombo.Items.Count)
+                    throw new ArgumentOutOfRangeException(nameof(index));
 
-            if (isSelectedPage)
-                this.ClearImageSelectedPicture();
+                var isSelectedPage = (index == selectedIndex);
 
-            if (string.IsNullOrEmpty(fileName))
-            {
-                ClearImagePage(index);
-                return;
-            }
+                if (isSelectedPage)
+                    this.ClearImageSelectedPicture();
 
-            try
-            {
-                FileInfo fl = new FileInfo(fileName);
-                string ext = fl.Extension;
+                if (item == null || string.IsNullOrEmpty(item.Path)) return;
 
-                if (!imageService.CheckFileExtension(ext))
+                try
                 {
-                    //画像以外の形式
-                    ClearImagePage(index);
-                    if (!suppressMsgBox)
+                    var ext = item.Extension;
+                    var size = item.Size;
+
+                    if (!imageService.CheckFileExtension(ext))
                     {
-                        MessageBox.Show(
-                            string.Format(Properties.Resources.PostPictureWarn3, serviceName, MakeAvailableServiceText(ext, fl.Length), ext, fl.Name),
-                            Properties.Resources.PostPictureWarn4,
-                            MessageBoxButtons.OK,
-                            MessageBoxIcon.Warning);
+                        //画像以外の形式
+                        if (!noMsgBox)
+                        {
+                            MessageBox.Show(
+                                string.Format(Properties.Resources.PostPictureWarn3, this.ServiceName, MakeAvailableServiceText(ext, size), ext, item.Name),
+                                Properties.Resources.PostPictureWarn4,
+                                MessageBoxButtons.OK,
+                                MessageBoxIcon.Warning);
+                        }
+                        return;
                     }
-                    return;
-                }
 
-                if (!imageService.CheckFileSize(ext, fl.Length))
-                {
-                    // ファイルサイズが大きすぎる
-                    ClearImagePage(index);
-                    if (!suppressMsgBox)
+                    if (!imageService.CheckFileSize(ext, size))
                     {
-                        MessageBox.Show(
-                            string.Format(Properties.Resources.PostPictureWarn5, serviceName, MakeAvailableServiceText(ext, fl.Length), fl.Name),
-                            Properties.Resources.PostPictureWarn4,
-                            MessageBoxButtons.OK,
-                            MessageBoxIcon.Warning);
+                        // ファイルサイズが大きすぎる
+                        if (!noMsgBox)
+                        {
+                            MessageBox.Show(
+                                string.Format(Properties.Resources.PostPictureWarn5, this.ServiceName, MakeAvailableServiceText(ext, size), item.Name),
+                                Properties.Resources.PostPictureWarn4,
+                                MessageBoxButtons.OK,
+                                MessageBoxIcon.Warning);
+                        }
+                        return;
                     }
-                    return;
-                }
 
-                try
-                {
-                    using (var fs = File.OpenRead(fileName))
+                    if (item.IsImage)
                     {
-                        var image = MemoryImage.CopyFromStream(fs);
                         if (isSelectedPage)
-                            ImageSelectedPicture.Image = image;
-                        else
-                            image.Dispose();  //画像チェック後は使わないので破棄する
+                            ImageSelectedPicture.Image = item.CreateImage();
+                        SetImagePage(index, item, MyCommon.UploadFileType.Picture);
+                    }
+                    else
+                    {
+                        SetImagePage(index, item, MyCommon.UploadFileType.MultiMedia);
                     }
-                    SetImagePage(index, fileName, MyCommon.UploadFileType.Picture);
+
+                    valid = true;  //正常終了
                 }
-                catch (InvalidImageException)
+                catch (FileNotFoundException)
                 {
-                    SetImagePage(index, fileName, MyCommon.UploadFileType.MultiMedia);
+                    if (!noMsgBox) MessageBox.Show("File not found.");
+                }
+                catch (Exception)
+                {
+                    if (!noMsgBox) MessageBox.Show("The type of this file is not image.");
                 }
             }
-            catch (FileNotFoundException)
-            {
-                ClearImagePage(index);
-                if (!suppressMsgBox) MessageBox.Show("File not found.");
-            }
-            catch (Exception)
+            finally
             {
-                ClearImagePage(index);
-                if (!suppressMsgBox) MessageBox.Show("The type of this file is not image.");
+                if (!valid)
+                {
+                    ClearImagePage(index);
+                    DisposeMediaItem(item);
+                }
             }
         }
 
@@ -449,7 +581,10 @@ namespace OpenTween
         {
             var text = string.Join(", ",
                 ImageServiceCombo.Items.Cast<string>()
-                    .Where(x => !string.IsNullOrEmpty(x) && this.pictureService[x].CheckFileSize(ext, fileSize)));
+                    .Where(serviceName =>
+                        !string.IsNullOrEmpty(serviceName) &&
+                        this.pictureService[serviceName].CheckFileExtension(ext) &&
+                        this.pictureService[serviceName].CheckFileSize(ext, fileSize)));
 
             if (string.IsNullOrEmpty(text))
                 return Properties.Resources.PostPictureWarn6;
@@ -460,11 +595,8 @@ namespace OpenTween
         private void ClearImageSelectedPicture()
         {
             var oldImage = this.ImageSelectedPicture.Image;
-            if (oldImage != null)
-            {
-                this.ImageSelectedPicture.Image = null;
-                oldImage.Dispose();
-            }
+            this.ImageSelectedPicture.Image = null;
+            oldImage?.Dispose();
 
             this.ImageSelectedPicture.ShowInitialImage();
         }
@@ -538,20 +670,27 @@ namespace OpenTween
             {
                 ImageServiceCombo.SelectedIndex = 0;
             }
+
+            this.UpdateAltTextPanelVisible();
         }
 
+        private void UpdateAltTextPanelVisible()
+            => this.AlternativeTextPanel.Visible = this.SelectedService.CanUseAltText;
+
         private void ImageServiceCombo_SelectedIndexChanged(object sender, EventArgs e)
         {
             if (this.Visible)
             {
-                var serviceName = this.ServiceName;
-                if (!string.IsNullOrEmpty(serviceName))
+                var imageService = this.SelectedService;
+                if (imageService != null)
                 {
+                    this.UpdateAltTextPanelVisible();
+
                     if (ImagePageCombo.Items.Count > 0)
                     {
                         // 画像が選択された投稿先に対応しているかをチェックする
                         // TODO: 複数の選択済み画像があるなら、できれば全てを再チェックしたほうがいい
-                        if (serviceName.Equals("Twitter"))
+                        if (this.ServiceName == "Twitter")
                         {
                             ValidateSelectedImagePage();
                         }
@@ -565,22 +704,31 @@ namespace OpenTween
                             else
                             {
                                 ImagePageCombo.Enabled = false;
+                                var valid = false;
 
                                 try
                                 {
-                                    FileInfo fi = new FileInfo(ImagefilePathText.Text.Trim());
-                                    string ext = fi.Extension;
-                                    var imageService = this.pictureService[serviceName];
-                                    if (!imageService.CheckFileSize(ext, fi.Length))
+                                    var item = ((SelectedMedia)ImagePageCombo.Items[0]).Item;
+                                    if (item != null)
                                     {
-                                        ClearImageSelectedPicture();
-                                        ClearSelectedImagePage();
+                                        var ext = item.Extension;
+                                        if (imageService.CheckFileExtension(ext) &&
+                                            imageService.CheckFileSize(ext, item.Size))
+                                        {
+                                            valid = true;
+                                        }
                                     }
                                 }
-                                catch (Exception)
+                                catch
                                 {
-                                    ClearImageSelectedPicture();
-                                    ClearSelectedImagePage();
+                                }
+                                finally
+                                {
+                                    if (!valid)
+                                    {
+                                        ClearImageSelectedPicture();
+                                        ClearSelectedImagePage();
+                                    }
                                 }
                             }
                         }
@@ -588,8 +736,7 @@ namespace OpenTween
                 }
             }
 
-            if (this.SelectedServiceChanged != null)
-                this.SelectedServiceChanged(this, EventArgs.Empty);
+            this.SelectedServiceChanged?.Invoke(this, EventArgs.Empty);
         }
 
         private void SetImagePageCombo(SelectedMedia media = null)
@@ -597,27 +744,31 @@ namespace OpenTween
             using (ControlTransaction.Update(ImagePageCombo))
             {
                 ImagePageCombo.Enabled = false;
-                ImagePageCombo.Items.Clear();
-                if (media != null)
-                {
-                    ImagePageCombo.Items.Add(media);
-                    ImagefilePathText.Text = media.Path;
-                }
-                else
+
+                foreach (SelectedMedia oldMedia in ImagePageCombo.Items)
                 {
-                    ImagePageCombo.Items.Add(new SelectedMedia("1"));
-                    ImagefilePathText.Text = "";
+                    if (oldMedia == null || oldMedia == media) continue;
+                    DisposeMediaItem(oldMedia.Item);
                 }
+                ImagePageCombo.Items.Clear();
+
+                if (media == null)
+                    media = new SelectedMedia("1");
+
+                ImagePageCombo.Items.Add(media);
+                ImagefilePathText.Text = media.Path;
+                AlternativeTextBox.Text = media.AltText;
+
                 ImagePageCombo.SelectedIndex = 0;
             }
         }
 
         private void AddNewImagePage(int selectedIndex)
         {
-            var serviceName = this.ServiceName;
-            if (string.IsNullOrEmpty(serviceName)) return;
+            var service = this.SelectedService;
+            if (service == null) return;
 
-            if (selectedIndex < this.pictureService[serviceName].MaxMediaCount - 1)
+            if (selectedIndex < service.MaxMediaCount - 1)
             {
                 // 投稿先の投稿可能枚数まで選択できるようにする
                 var count = ImagePageCombo.Items.Count;
@@ -630,19 +781,23 @@ namespace OpenTween
             }
         }
 
-        private void SetSelectedImagePage(string path, MyCommon.UploadFileType type)
+        private void SetSelectedImagePage(IMediaItem item, MyCommon.UploadFileType type)
         {
-            SetImagePage(-1, path, type);
+            SetImagePage(-1, item, type);
         }
 
-        private void SetImagePage(int index, string path, MyCommon.UploadFileType type)
+        private void SetImagePage(int index, IMediaItem item, MyCommon.UploadFileType type)
         {
             var selectedIndex = ImagePageCombo.SelectedIndex;
             if (index < 0) index = selectedIndex;
 
-            var item = (SelectedMedia)ImagePageCombo.Items[index];
-            item.Path = path;
-            item.Type = type;
+            var media = (SelectedMedia)ImagePageCombo.Items[index];
+            if (media.Item != item)
+            {
+                DisposeMediaItem(media.Item);
+                media.Item = item;
+            }
+            media.Type = type;
 
             AddNewImagePage(index);
         }
@@ -657,25 +812,38 @@ namespace OpenTween
             var selectedIndex = ImagePageCombo.SelectedIndex;
             if (index < 0) index = selectedIndex;
 
-            var item = (SelectedMedia)ImagePageCombo.Items[index];
-            item.Path = "";
-            item.Type = MyCommon.UploadFileType.Invalid;
+            var media = (SelectedMedia)ImagePageCombo.Items[index];
+            DisposeMediaItem(media.Item);
+            media.Item = null;
+            media.Type = MyCommon.UploadFileType.Invalid;
 
-            if (index == selectedIndex) ImagefilePathText.Text = "";
+            if (index == selectedIndex)
+            {
+                ImagefilePathText.Text = "";
+                AlternativeTextBox.Text = "";
+            }
         }
 
         private void ValidateSelectedImagePage()
         {
             var idx = ImagePageCombo.SelectedIndex;
-            var item = (SelectedMedia)ImagePageCombo.Items[idx];
+            var media = (SelectedMedia)ImagePageCombo.Items[idx];
             ImageServiceCombo.Enabled = (idx == 0);  // idx == 0 以外では投稿先サービスを選べないようにする
-            ImagefilePathText.Text = item.Path;
-            ImageFromSelectedFile(true);
+            ImagefilePathText.Text = media.Path;
+            AlternativeTextBox.Text = media.AltText;
+            ImageFromSelectedFile(media.Item, true);
         }
 
         private void ImagePageCombo_SelectedIndexChanged(object sender, EventArgs e)
         {
             ValidateSelectedImagePage();
         }
+
+        private void AlternativeTextBox_Validating(object sender, CancelEventArgs e)
+        {
+            var imageFilePath = this.ImagefilePathText.Text.Trim();
+            var altText = this.AlternativeTextBox.Text.Trim();
+            this.ValidateNewFileMediaItem(imageFilePath, altText, noMsgBox: false);
+        }
     }
 }