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;
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 インスタンスを取得する。
74 public IMediaUploadService GetService(string serviceName)
76 IMediaUploadService service;
77 this.pictureService.TryGetValue(serviceName, out service);
82 /// 利用可能な全ての IMediaUploadService インスタンスを取得する。
84 public ICollection<IMediaUploadService> GetServices()
86 return this.pictureService.Values;
89 private class SelectedMedia
91 public string Path { get; set; }
92 public MyCommon.UploadFileType Type { get; set; }
93 public string Text { get; set; }
95 public SelectedMedia(string path, MyCommon.UploadFileType type, string text)
102 public SelectedMedia(string text)
103 : this("", MyCommon.UploadFileType.Invalid, text)
109 get { return this.Type != MyCommon.UploadFileType.Invalid; }
112 public override string ToString()
118 private Dictionary<string, IMediaUploadService> pictureService;
120 private void CreateServices(Twitter tw, TwitterConfiguration twitterConfig)
122 if (this.pictureService != null) this.pictureService.Clear();
123 this.pictureService = null;
125 this.pictureService = new Dictionary<string, IMediaUploadService> {
126 {"Twitter", new TwitterPhoto(tw, twitterConfig)},
127 {"img.ly", new imgly(tw, twitterConfig)},
128 {"yfrog", new yfrog(tw, twitterConfig)},
129 {"ついっぷるフォト", new TwipplePhoto(tw, twitterConfig)},
130 {"Imgur", new Imgur(tw, twitterConfig)},
131 {"Mobypicture", new Mobypicture(tw, twitterConfig)},
135 public MediaSelector()
137 InitializeComponent();
139 this.ImageSelectedPicture.InitialImage = Properties.Resources.InitialImage;
145 public void Initialize(Twitter tw, TwitterConfiguration twitterConfig, string svc, int? index = null)
147 CreateServices(tw, twitterConfig);
149 SetImageServiceCombo();
152 SelectImageServiceComboItem(svc, index);
158 public void Reset(Twitter tw, TwitterConfiguration twitterConfig)
160 CreateServices(tw, twitterConfig);
162 SetImageServiceCombo();
166 /// 指定されたファイルの投稿に対応した投稿先があるかどうかを示す値を取得する。
168 public bool HasUploadableService(string fileName, bool ignoreSize)
170 FileInfo fl = new FileInfo(fileName);
171 string ext = fl.Extension;
172 long? size = ignoreSize ? (long?)null : fl.Length;
174 if (IsUploadable(this.ServiceName, ext, size))
177 foreach (string svc in ImageServiceCombo.Items)
179 if (IsUploadable(svc, ext, size))
187 /// 指定された投稿先に投稿可能かどうかを示す値を取得する。
188 /// ファイルサイズの指定がなければ拡張子だけで判定する。
190 private bool IsUploadable(string serviceName, string ext, long? size)
192 if (!string.IsNullOrEmpty(serviceName))
196 if (this.pictureService[serviceName].CheckFileSize(ext, size.Value))
201 if (this.pictureService[serviceName].CheckFileExtension(ext))
209 /// 投稿するファイルとその投稿先を選択するためのコントロールを表示する。
210 /// D&Dをサポートする場合は引数にドロップされたファイル名を指定して呼ぶこと。
212 public void BeginSelection(string[] fileNames = null)
214 if (fileNames != null && fileNames.Length > 0)
216 var serviceName = this.ServiceName;
217 if (string.IsNullOrEmpty(serviceName)) return;
218 var service = this.pictureService[serviceName];
220 var count = Math.Min(fileNames.Length, service.MaxMediaCount);
221 if (!this.Visible || count > 1)
223 // 非表示時または複数のファイル指定は新規選択として扱う
226 if (this.BeginSelecting != null)
227 this.BeginSelecting(this, EventArgs.Empty);
235 ImagefilePathText.Text = fileNames[0];
236 ImageFromSelectedFile(false);
240 for (int i = 0; i < count; i++)
242 var index = ImagePageCombo.Items.Count - 1;
243 if (index == 0) ImagefilePathText.Text = fileNames[i];
244 ImageFromSelectedFile(index, fileNames[i], false);
252 if (this.BeginSelecting != null)
253 this.BeginSelecting(this, EventArgs.Empty);
257 ImageFromSelectedFile(true);
258 ImagefilePathText.Focus();
264 /// 選択処理を終了してコントロールを隠す。
266 public void EndSelection()
270 ImagefilePathText.CausesValidation = false;
272 if (this.EndSelecting != null)
273 this.EndSelecting(this, EventArgs.Empty);
275 this.Visible = false;
276 this.Enabled = false;
277 ClearImageSelectedPicture();
279 ImagefilePathText.CausesValidation = true;
284 /// 選択された投稿先名と投稿ファイル名を取得する。
286 public bool TryGetSelectedMedia(out string imageService, out string[] imagePaths)
288 var validPaths = ImagePageCombo.Items.Cast<SelectedMedia>()
289 .Where(x => x.IsValid).Select(x => x.Path).ToArray();
291 if (validPaths.Length > 0 &&
292 ImageServiceCombo.SelectedIndex > -1)
294 var serviceName = this.ServiceName;
295 if (MessageBox.Show(string.Format(Properties.Resources.PostPictureConfirm1, serviceName, validPaths.Length),
296 Properties.Resources.PostPictureConfirm2,
297 MessageBoxButtons.OKCancel,
298 MessageBoxIcon.Question,
299 MessageBoxDefaultButton.Button1)
302 imageService = serviceName;
303 imagePaths = validPaths;
311 MessageBox.Show(Properties.Resources.PostPictureWarn1, Properties.Resources.PostPictureWarn2);
320 private void FilePickButton_Click(object sender, EventArgs e)
322 if (FilePickDialog == null || string.IsNullOrEmpty(this.ServiceName)) return;
323 FilePickDialog.Filter = this.pictureService[this.ServiceName].SupportedFormatsStrForDialog;
324 FilePickDialog.Title = Properties.Resources.PickPictureDialog1;
325 FilePickDialog.FileName = "";
327 if (this.FilePickDialogOpening != null)
328 this.FilePickDialogOpening(this, EventArgs.Empty);
332 if (FilePickDialog.ShowDialog() == DialogResult.Cancel) return;
336 if (this.FilePickDialogClosed != null)
337 this.FilePickDialogClosed(this, EventArgs.Empty);
340 ImagefilePathText.Text = FilePickDialog.FileName;
341 ImageFromSelectedFile(false);
344 private void ImagefilePathText_Validating(object sender, CancelEventArgs e)
346 if (ImageCancelButton.Focused)
348 ImagefilePathText.CausesValidation = false;
352 ImageFromSelectedFile(false);
355 private void ImageFromSelectedFile(bool suppressMsgBox)
357 ImagefilePathText.Text = ImagefilePathText.Text.Trim();
358 ImageFromSelectedFile(-1, ImagefilePathText.Text, suppressMsgBox);
361 private void ImageFromSelectedFile(int index, string fileName, bool suppressMsgBox)
363 var serviceName = this.ServiceName;
364 if (string.IsNullOrEmpty(serviceName)) return;
366 var selectedIndex = ImagePageCombo.SelectedIndex;
367 if (index < 0) index = selectedIndex;
369 if (index >= ImagePageCombo.Items.Count)
370 throw new ArgumentOutOfRangeException("index");
372 var imageService = this.pictureService[serviceName];
373 var isSelectedPage = (index == selectedIndex);
376 this.ClearImageSelectedPicture();
378 if (string.IsNullOrEmpty(fileName))
380 ClearImagePage(index);
386 FileInfo fl = new FileInfo(fileName);
387 string ext = fl.Extension;
389 if (!imageService.CheckFileExtension(ext))
392 ClearImagePage(index);
396 string.Format(Properties.Resources.PostPictureWarn3, serviceName, MakeAvailableServiceText(ext, fl.Length), ext, fl.Name),
397 Properties.Resources.PostPictureWarn4,
398 MessageBoxButtons.OK,
399 MessageBoxIcon.Warning);
404 if (!imageService.CheckFileSize(ext, fl.Length))
407 ClearImagePage(index);
411 string.Format(Properties.Resources.PostPictureWarn5, serviceName, MakeAvailableServiceText(ext, fl.Length), fl.Name),
412 Properties.Resources.PostPictureWarn4,
413 MessageBoxButtons.OK,
414 MessageBoxIcon.Warning);
421 using (var fs = File.OpenRead(fileName))
423 var image = MemoryImage.CopyFromStream(fs);
425 ImageSelectedPicture.Image = image;
427 image.Dispose(); //画像チェック後は使わないので破棄する
429 SetImagePage(index, fileName, MyCommon.UploadFileType.Picture);
431 catch (InvalidImageException)
433 SetImagePage(index, fileName, MyCommon.UploadFileType.MultiMedia);
436 catch (FileNotFoundException)
438 ClearImagePage(index);
439 if (!suppressMsgBox) MessageBox.Show("File not found.");
443 ClearImagePage(index);
444 if (!suppressMsgBox) MessageBox.Show("The type of this file is not image.");
448 private string MakeAvailableServiceText(string ext, long fileSize)
450 var text = string.Join(", ",
451 ImageServiceCombo.Items.Cast<string>()
452 .Where(x => !string.IsNullOrEmpty(x) && this.pictureService[x].CheckFileSize(ext, fileSize)));
454 if (string.IsNullOrEmpty(text))
455 return Properties.Resources.PostPictureWarn6;
460 private void ClearImageSelectedPicture()
462 var oldImage = this.ImageSelectedPicture.Image;
463 if (oldImage != null)
465 this.ImageSelectedPicture.Image = null;
469 this.ImageSelectedPicture.ShowInitialImage();
472 private void ImageCancelButton_Click(object sender, EventArgs e)
477 private void ImageSelection_KeyDown(object sender, KeyEventArgs e)
479 if (e.KeyCode == Keys.Escape)
485 private void ImageSelection_KeyPress(object sender, KeyPressEventArgs e)
487 if (Convert.ToInt32(e.KeyChar) == 0x1B)
489 ImagefilePathText.CausesValidation = false;
494 private void ImageSelection_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
496 if (e.KeyCode == Keys.Escape)
498 ImagefilePathText.CausesValidation = false;
502 private void SetImageServiceCombo()
504 using (ControlTransaction.Update(ImageServiceCombo))
507 if (ImageServiceCombo.SelectedIndex > -1) svc = ImageServiceCombo.Text;
508 ImageServiceCombo.Items.Clear();
510 // Add service names to combobox
511 foreach (var key in pictureService.Keys)
513 ImageServiceCombo.Items.Add(key);
516 SelectImageServiceComboItem(svc);
520 private void SelectImageServiceComboItem(string svc, int? index = null)
523 if (string.IsNullOrEmpty(svc))
529 idx = ImageServiceCombo.Items.IndexOf(svc);
530 if (idx == -1) idx = index ?? 0;
535 ImageServiceCombo.SelectedIndex = idx;
537 catch (ArgumentOutOfRangeException)
539 ImageServiceCombo.SelectedIndex = 0;
543 private void ImageServiceCombo_SelectedIndexChanged(object sender, EventArgs e)
547 var serviceName = this.ServiceName;
548 if (!string.IsNullOrEmpty(serviceName))
550 if (ImagePageCombo.Items.Count > 0)
552 // 画像が選択された投稿先に対応しているかをチェックする
553 // TODO: 複数の選択済み画像があるなら、できれば全てを再チェックしたほうがいい
554 if (serviceName.Equals("Twitter"))
556 ValidateSelectedImagePage();
560 if (ImagePageCombo.Items.Count > 1)
562 // 複数の選択済み画像のうち、1枚目のみを残す
563 SetImagePageCombo((SelectedMedia)ImagePageCombo.Items[0]);
567 ImagePageCombo.Enabled = false;
571 FileInfo fi = new FileInfo(ImagefilePathText.Text.Trim());
572 string ext = fi.Extension;
573 var imageService = this.pictureService[serviceName];
574 if (!imageService.CheckFileSize(ext, fi.Length))
576 ClearImageSelectedPicture();
577 ClearSelectedImagePage();
582 ClearImageSelectedPicture();
583 ClearSelectedImagePage();
591 if (this.SelectedServiceChanged != null)
592 this.SelectedServiceChanged(this, EventArgs.Empty);
595 private void SetImagePageCombo(SelectedMedia media = null)
597 using (ControlTransaction.Update(ImagePageCombo))
599 ImagePageCombo.Enabled = false;
600 ImagePageCombo.Items.Clear();
603 ImagePageCombo.Items.Add(media);
604 ImagefilePathText.Text = media.Path;
608 ImagePageCombo.Items.Add(new SelectedMedia("1"));
609 ImagefilePathText.Text = "";
611 ImagePageCombo.SelectedIndex = 0;
615 private void AddNewImagePage(int selectedIndex)
617 var serviceName = this.ServiceName;
618 if (string.IsNullOrEmpty(serviceName)) return;
620 if (selectedIndex < this.pictureService[serviceName].MaxMediaCount - 1)
622 // 投稿先の投稿可能枚数まで選択できるようにする
623 var count = ImagePageCombo.Items.Count;
624 if (selectedIndex == count - 1)
627 ImagePageCombo.Items.Add(new SelectedMedia(count.ToString()));
628 ImagePageCombo.Enabled = true;
633 private void SetSelectedImagePage(string path, MyCommon.UploadFileType type)
635 SetImagePage(-1, path, type);
638 private void SetImagePage(int index, string path, MyCommon.UploadFileType type)
640 var selectedIndex = ImagePageCombo.SelectedIndex;
641 if (index < 0) index = selectedIndex;
643 var item = (SelectedMedia)ImagePageCombo.Items[index];
647 AddNewImagePage(index);
650 private void ClearSelectedImagePage()
655 private void ClearImagePage(int index)
657 var selectedIndex = ImagePageCombo.SelectedIndex;
658 if (index < 0) index = selectedIndex;
660 var item = (SelectedMedia)ImagePageCombo.Items[index];
662 item.Type = MyCommon.UploadFileType.Invalid;
664 if (index == selectedIndex) ImagefilePathText.Text = "";
667 private void ValidateSelectedImagePage()
669 var idx = ImagePageCombo.SelectedIndex;
670 var item = (SelectedMedia)ImagePageCombo.Items[idx];
671 ImageServiceCombo.Enabled = (idx == 0); // idx == 0 以外では投稿先サービスを選べないようにする
672 ImagefilePathText.Text = item.Path;
673 ImageFromSelectedFile(true);
676 private void ImagePageCombo_SelectedIndexChanged(object sender, EventArgs e)
678 ValidateSelectedImagePage();