1 // OpenTween - Client of Twitter
2 // Copyright (c) 2014 spx (@5px)
3 // All rights reserved.
5 // This file is part of OpenTween.
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)
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
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.
23 using System.Collections.Generic;
24 using System.ComponentModel;
30 using System.Threading.Tasks;
31 using System.Windows.Forms;
32 using OpenTween.Api.DataModel;
33 using OpenTween.Connection;
37 public partial class MediaSelector : UserControl
39 public event EventHandler BeginSelecting;
40 public event EventHandler EndSelecting;
42 public event EventHandler FilePickDialogOpening;
43 public event EventHandler FilePickDialogClosed;
45 public event EventHandler SelectedServiceChanged;
48 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
49 public OpenFileDialog FilePickDialog { get; set; }
55 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
56 public string ServiceName
58 get { return ImageServiceCombo.Text; }
62 /// 選択されている投稿先を示すインデックスを取得する。
65 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
66 public int ServiceIndex
68 get { return ImageServiceCombo.SelectedIndex; }
72 /// 選択されている投稿先の IMediaUploadService を取得する。
75 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
76 public IMediaUploadService SelectedService
80 var serviceName = this.ServiceName;
81 if (string.IsNullOrEmpty(serviceName))
84 IMediaUploadService service;
85 return this.pictureService.TryGetValue(serviceName, out service) ? service : null;
90 /// 指定された投稿先名から、作成済みの IMediaUploadService インスタンスを取得する。
92 public IMediaUploadService GetService(string serviceName)
94 IMediaUploadService service;
95 this.pictureService.TryGetValue(serviceName, out service);
100 /// 利用可能な全ての IMediaUploadService インスタンスを取得する。
102 public ICollection<IMediaUploadService> GetServices()
104 return this.pictureService.Values;
107 private class SelectedMedia
109 public IMediaItem Item { get; set; }
110 public MyCommon.UploadFileType Type { get; set; }
111 public string Text { get; set; }
113 public SelectedMedia(IMediaItem item, MyCommon.UploadFileType type, string text)
120 public SelectedMedia(string text)
121 : this(null, MyCommon.UploadFileType.Invalid, text)
129 return this.Item != null && this.Type != MyCommon.UploadFileType.Invalid;
137 return this.Item?.Path ?? "";
141 public string AltText => this.Item?.AltText ?? "";
143 public override string ToString()
149 private Dictionary<string, IMediaUploadService> pictureService;
151 private void CreateServices(Twitter tw, TwitterConfiguration twitterConfig)
153 this.pictureService?.Clear();
154 this.pictureService = null;
156 this.pictureService = new Dictionary<string, IMediaUploadService> {
157 ["Twitter"] = new TwitterPhoto(tw, twitterConfig),
158 ["img.ly"] = new imgly(tw, twitterConfig),
159 ["yfrog"] = new yfrog(tw, twitterConfig),
160 ["ついっぷるフォト"] = new TwipplePhoto(tw, twitterConfig),
161 ["Imgur"] = new Imgur(tw, twitterConfig),
162 ["Mobypicture"] = new Mobypicture(tw, twitterConfig),
166 public MediaSelector()
168 InitializeComponent();
170 this.ImageSelectedPicture.InitialImage = Properties.Resources.InitialImage;
176 public void Initialize(Twitter tw, TwitterConfiguration twitterConfig, string svc, int? index = null)
178 CreateServices(tw, twitterConfig);
180 SetImageServiceCombo();
183 SelectImageServiceComboItem(svc, index);
189 public void Reset(Twitter tw, TwitterConfiguration twitterConfig)
191 CreateServices(tw, twitterConfig);
193 SetImageServiceCombo();
197 /// 指定されたファイルの投稿に対応した投稿先があるかどうかを示す値を取得する。
199 public bool HasUploadableService(string fileName, bool ignoreSize)
201 FileInfo fl = new FileInfo(fileName);
202 string ext = fl.Extension;
203 long? size = ignoreSize ? (long?)null : fl.Length;
205 if (IsUploadable(this.ServiceName, ext, size))
208 foreach (string svc in ImageServiceCombo.Items)
210 if (IsUploadable(svc, ext, size))
218 /// 指定された投稿先に投稿可能かどうかを示す値を取得する。
219 /// ファイルサイズの指定がなければ拡張子だけで判定する。
221 private bool IsUploadable(string serviceName, string ext, long? size)
223 if (!string.IsNullOrEmpty(serviceName))
225 var imageService = this.pictureService[serviceName];
226 if (imageService.CheckFileExtension(ext))
231 if (imageService.CheckFileSize(ext, size.Value))
239 /// 投稿するファイルとその投稿先を選択するためのコントロールを表示する。
241 private void BeginSelection(IMediaItem[] items)
243 if (items == null || items.Length == 0)
249 var service = this.SelectedService;
250 if (service == null) return;
252 var count = Math.Min(items.Length, service.MaxMediaCount);
253 if (!this.Visible || count > 1)
255 // 非表示時または複数のファイル指定は新規選択として扱う
258 this.BeginSelecting?.Invoke(this, EventArgs.Empty);
266 ImagefilePathText.Text = items[0].Path;
267 AlternativeTextBox.Text = items[0].AltText;
268 ImageFromSelectedFile(items[0], false);
272 for (int i = 0; i < count; i++)
274 var index = ImagePageCombo.Items.Count - 1;
277 ImagefilePathText.Text = items[i].Path;
278 AlternativeTextBox.Text = items[i].AltText;
280 ImageFromSelectedFile(index, items[i], false);
286 /// 投稿するファイルとその投稿先を選択するためのコントロールを表示する(主にD&D用)。
288 public void BeginSelection(string[] fileNames)
290 if (fileNames == null || fileNames.Length == 0)
296 var items = fileNames.Select(x => CreateFileMediaItem(x, false)).OfType<IMediaItem>().ToArray();
297 BeginSelection(items);
301 /// 投稿するファイルとその投稿先を選択するためのコントロールを表示する。
303 public void BeginSelection(Image image)
311 var items = new [] { CreateMemoryImageMediaItem(image, false) }.OfType<IMediaItem>().ToArray();
312 BeginSelection(items);
316 /// 投稿するファイルとその投稿先を選択するためのコントロールを表示する。
318 public void BeginSelection()
322 this.BeginSelecting?.Invoke(this, EventArgs.Empty);
327 var media = (SelectedMedia)ImagePageCombo.SelectedItem;
328 ImageFromSelectedFile(media.Item, true);
329 ImagefilePathText.Focus();
334 /// 選択処理を終了してコントロールを隠す。
336 public void EndSelection()
340 ImagefilePathText.CausesValidation = false;
342 this.EndSelecting?.Invoke(this, EventArgs.Empty);
344 this.Visible = false;
345 this.Enabled = false;
346 ClearImageSelectedPicture();
348 ImagefilePathText.CausesValidation = true;
353 /// 選択された投稿先名と投稿する MediaItem を取得する。MediaItem は不要になったら呼び出し側にて破棄すること。
355 public bool TryGetSelectedMedia(out string imageService, out IMediaItem[] mediaItems)
357 var validItems = ImagePageCombo.Items.Cast<SelectedMedia>()
358 .Where(x => x.IsValid).Select(x => x.Item).OfType<IMediaItem>().ToArray();
360 if (validItems.Length > 0 &&
361 ImageServiceCombo.SelectedIndex > -1)
363 var serviceName = this.ServiceName;
364 if (MessageBox.Show(string.Format(Properties.Resources.PostPictureConfirm1, serviceName, validItems.Length),
365 Properties.Resources.PostPictureConfirm2,
366 MessageBoxButtons.OKCancel,
367 MessageBoxIcon.Question,
368 MessageBoxDefaultButton.Button1)
371 //収集した MediaItem が破棄されないように、予め null を代入しておく
372 foreach (SelectedMedia media in ImagePageCombo.Items)
374 if (media != null) media.Item = null;
377 imageService = serviceName;
378 mediaItems = validItems;
386 MessageBox.Show(Properties.Resources.PostPictureWarn1, Properties.Resources.PostPictureWarn2);
395 private MemoryImageMediaItem CreateMemoryImageMediaItem(Image image, bool noMsgBox)
397 if (image == null) return null;
399 MemoryImage memoryImage = null;
402 // image から png 形式の MemoryImage を生成
403 memoryImage = MemoryImage.CopyFromImage(image);
405 return new MemoryImageMediaItem(memoryImage);
409 memoryImage?.Dispose();
411 if (!noMsgBox) MessageBox.Show("Unable to create MemoryImage.");
416 private IMediaItem CreateFileMediaItem(string path, bool noMsgBox)
418 if (string.IsNullOrEmpty(path)) return null;
422 return new FileMediaItem(path);
426 if (!noMsgBox) MessageBox.Show("Invalid file path: " + path);
431 private void ValidateNewFileMediaItem(string path, string altText, bool noMsgBox)
433 var media = (SelectedMedia)ImagePageCombo.SelectedItem;
434 var item = media.Item;
436 if (path != media.Path)
438 DisposeMediaItem(media.Item);
441 item = CreateFileMediaItem(path, noMsgBox);
445 item.AltText = altText;
447 ImagefilePathText.Text = path;
448 AlternativeTextBox.Text = altText;
449 ImageFromSelectedFile(item, noMsgBox);
452 private void DisposeMediaItem(IMediaItem item)
454 var disposableItem = item as IDisposable;
455 disposableItem?.Dispose();
458 private void FilePickButton_Click(object sender, EventArgs e)
460 var service = this.SelectedService;
462 if (FilePickDialog == null || service == null) return;
463 FilePickDialog.Filter = service.SupportedFormatsStrForDialog;
464 FilePickDialog.Title = Properties.Resources.PickPictureDialog1;
465 FilePickDialog.FileName = "";
467 this.FilePickDialogOpening?.Invoke(this, EventArgs.Empty);
471 if (FilePickDialog.ShowDialog() == DialogResult.Cancel) return;
475 this.FilePickDialogClosed?.Invoke(this, EventArgs.Empty);
478 ValidateNewFileMediaItem(FilePickDialog.FileName, AlternativeTextBox.Text.Trim(), false);
481 private void ImagefilePathText_Validating(object sender, CancelEventArgs e)
483 if (ImageCancelButton.Focused)
485 ImagefilePathText.CausesValidation = false;
489 ValidateNewFileMediaItem(ImagefilePathText.Text.Trim(), AlternativeTextBox.Text.Trim(), false);
492 private void ImageFromSelectedFile(IMediaItem item, bool noMsgBox)
494 ImageFromSelectedFile(-1, item, noMsgBox);
497 private void ImageFromSelectedFile(int index, IMediaItem item, bool noMsgBox)
503 var imageService = this.SelectedService;
504 if (imageService == null) return;
506 var selectedIndex = ImagePageCombo.SelectedIndex;
507 if (index < 0) index = selectedIndex;
509 if (index >= ImagePageCombo.Items.Count)
510 throw new ArgumentOutOfRangeException(nameof(index));
512 var isSelectedPage = (index == selectedIndex);
515 this.ClearImageSelectedPicture();
517 if (item == null || string.IsNullOrEmpty(item.Path)) return;
521 var ext = item.Extension;
522 var size = item.Size;
524 if (!imageService.CheckFileExtension(ext))
530 string.Format(Properties.Resources.PostPictureWarn3, this.ServiceName, MakeAvailableServiceText(ext, size), ext, item.Name),
531 Properties.Resources.PostPictureWarn4,
532 MessageBoxButtons.OK,
533 MessageBoxIcon.Warning);
538 if (!imageService.CheckFileSize(ext, size))
544 string.Format(Properties.Resources.PostPictureWarn5, this.ServiceName, MakeAvailableServiceText(ext, size), item.Name),
545 Properties.Resources.PostPictureWarn4,
546 MessageBoxButtons.OK,
547 MessageBoxIcon.Warning);
555 ImageSelectedPicture.Image = item.CreateImage();
556 SetImagePage(index, item, MyCommon.UploadFileType.Picture);
560 SetImagePage(index, item, MyCommon.UploadFileType.MultiMedia);
565 catch (FileNotFoundException)
567 if (!noMsgBox) MessageBox.Show("File not found.");
571 if (!noMsgBox) MessageBox.Show("The type of this file is not image.");
578 ClearImagePage(index);
579 DisposeMediaItem(item);
584 private string MakeAvailableServiceText(string ext, long fileSize)
586 var text = string.Join(", ",
587 ImageServiceCombo.Items.Cast<string>()
588 .Where(serviceName =>
589 !string.IsNullOrEmpty(serviceName) &&
590 this.pictureService[serviceName].CheckFileExtension(ext) &&
591 this.pictureService[serviceName].CheckFileSize(ext, fileSize)));
593 if (string.IsNullOrEmpty(text))
594 return Properties.Resources.PostPictureWarn6;
599 private void ClearImageSelectedPicture()
601 var oldImage = this.ImageSelectedPicture.Image;
602 this.ImageSelectedPicture.Image = null;
605 this.ImageSelectedPicture.ShowInitialImage();
608 private void ImageCancelButton_Click(object sender, EventArgs e)
613 private void ImageSelection_KeyDown(object sender, KeyEventArgs e)
615 if (e.KeyCode == Keys.Escape)
621 private void ImageSelection_KeyPress(object sender, KeyPressEventArgs e)
623 if (Convert.ToInt32(e.KeyChar) == 0x1B)
625 ImagefilePathText.CausesValidation = false;
630 private void ImageSelection_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
632 if (e.KeyCode == Keys.Escape)
634 ImagefilePathText.CausesValidation = false;
638 private void SetImageServiceCombo()
640 using (ControlTransaction.Update(ImageServiceCombo))
643 if (ImageServiceCombo.SelectedIndex > -1) svc = ImageServiceCombo.Text;
644 ImageServiceCombo.Items.Clear();
646 // Add service names to combobox
647 foreach (var key in pictureService.Keys)
649 ImageServiceCombo.Items.Add(key);
652 SelectImageServiceComboItem(svc);
656 private void SelectImageServiceComboItem(string svc, int? index = null)
659 if (string.IsNullOrEmpty(svc))
665 idx = ImageServiceCombo.Items.IndexOf(svc);
666 if (idx == -1) idx = index ?? 0;
671 ImageServiceCombo.SelectedIndex = idx;
673 catch (ArgumentOutOfRangeException)
675 ImageServiceCombo.SelectedIndex = 0;
678 this.UpdateAltTextPanelVisible();
681 private void UpdateAltTextPanelVisible()
682 => this.AlternativeTextPanel.Visible = this.SelectedService.CanUseAltText;
684 private void ImageServiceCombo_SelectedIndexChanged(object sender, EventArgs e)
688 var imageService = this.SelectedService;
689 if (imageService != null)
691 this.UpdateAltTextPanelVisible();
693 if (ImagePageCombo.Items.Count > 0)
695 // 画像が選択された投稿先に対応しているかをチェックする
696 // TODO: 複数の選択済み画像があるなら、できれば全てを再チェックしたほうがいい
697 if (this.ServiceName == "Twitter")
699 ValidateSelectedImagePage();
703 if (ImagePageCombo.Items.Count > 1)
705 // 複数の選択済み画像のうち、1枚目のみを残す
706 SetImagePageCombo((SelectedMedia)ImagePageCombo.Items[0]);
710 ImagePageCombo.Enabled = false;
715 var item = ((SelectedMedia)ImagePageCombo.Items[0]).Item;
718 var ext = item.Extension;
719 if (imageService.CheckFileExtension(ext) &&
720 imageService.CheckFileSize(ext, item.Size))
733 ClearImageSelectedPicture();
734 ClearSelectedImagePage();
743 this.SelectedServiceChanged?.Invoke(this, EventArgs.Empty);
746 private void SetImagePageCombo(SelectedMedia media = null)
748 using (ControlTransaction.Update(ImagePageCombo))
750 ImagePageCombo.Enabled = false;
752 foreach (SelectedMedia oldMedia in ImagePageCombo.Items)
754 if (oldMedia == null || oldMedia == media) continue;
755 DisposeMediaItem(oldMedia.Item);
757 ImagePageCombo.Items.Clear();
760 media = new SelectedMedia("1");
762 ImagePageCombo.Items.Add(media);
763 ImagefilePathText.Text = media.Path;
764 AlternativeTextBox.Text = media.AltText;
766 ImagePageCombo.SelectedIndex = 0;
770 private void AddNewImagePage(int selectedIndex)
772 var service = this.SelectedService;
773 if (service == null) return;
775 if (selectedIndex < service.MaxMediaCount - 1)
777 // 投稿先の投稿可能枚数まで選択できるようにする
778 var count = ImagePageCombo.Items.Count;
779 if (selectedIndex == count - 1)
782 ImagePageCombo.Items.Add(new SelectedMedia(count.ToString()));
783 ImagePageCombo.Enabled = true;
788 private void SetSelectedImagePage(IMediaItem item, MyCommon.UploadFileType type)
790 SetImagePage(-1, item, type);
793 private void SetImagePage(int index, IMediaItem item, MyCommon.UploadFileType type)
795 var selectedIndex = ImagePageCombo.SelectedIndex;
796 if (index < 0) index = selectedIndex;
798 var media = (SelectedMedia)ImagePageCombo.Items[index];
799 if (media.Item != item)
801 DisposeMediaItem(media.Item);
806 AddNewImagePage(index);
809 private void ClearSelectedImagePage()
814 private void ClearImagePage(int index)
816 var selectedIndex = ImagePageCombo.SelectedIndex;
817 if (index < 0) index = selectedIndex;
819 var media = (SelectedMedia)ImagePageCombo.Items[index];
820 DisposeMediaItem(media.Item);
822 media.Type = MyCommon.UploadFileType.Invalid;
824 if (index == selectedIndex)
826 ImagefilePathText.Text = "";
827 AlternativeTextBox.Text = "";
831 private void ValidateSelectedImagePage()
833 var idx = ImagePageCombo.SelectedIndex;
834 var media = (SelectedMedia)ImagePageCombo.Items[idx];
835 ImageServiceCombo.Enabled = (idx == 0); // idx == 0 以外では投稿先サービスを選べないようにする
836 ImagefilePathText.Text = media.Path;
837 AlternativeTextBox.Text = media.AltText;
838 ImageFromSelectedFile(media.Item, true);
841 private void ImagePageCombo_SelectedIndexChanged(object sender, EventArgs e)
843 ValidateSelectedImagePage();
846 private void AlternativeTextBox_Validating(object sender, CancelEventArgs e)
848 var imageFilePath = this.ImagefilePathText.Text.Trim();
849 var altText = this.AlternativeTextBox.Text.Trim();
850 this.ValidateNewFileMediaItem(imageFilePath, altText, noMsgBox: false);