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 {"TwitPic", new TwitPic(tw, twitterConfig)},
128 {"img.ly", new imgly(tw, twitterConfig)},
129 {"yfrog", new yfrog(tw, twitterConfig)},
130 {"ついっぷるフォト", new TwipplePhoto(tw, twitterConfig)},
131 {"Imgur", new Imgur(tw, twitterConfig)},
132 {"Mobypicture", new Mobypicture(tw, twitterConfig)},
136 public MediaSelector()
138 InitializeComponent();
140 this.ImageSelectedPicture.InitialImage = Properties.Resources.InitialImage;
146 public void Initialize(Twitter tw, TwitterConfiguration twitterConfig, string svc, int? index = null)
148 CreateServices(tw, twitterConfig);
150 SetImageServiceCombo();
153 SelectImageServiceComboItem(svc, index);
159 public void Reset(Twitter tw, TwitterConfiguration twitterConfig)
161 CreateServices(tw, twitterConfig);
163 SetImageServiceCombo();
167 /// 指定されたファイルの投稿に対応した投稿先があるかどうかを示す値を取得する。
169 public bool HasUploadableService(string fileName, bool ignoreSize)
171 FileInfo fl = new FileInfo(fileName);
172 string ext = fl.Extension;
173 long? size = ignoreSize ? (long?)null : fl.Length;
175 if (IsUploadable(this.ServiceName, ext, size))
178 foreach (string svc in ImageServiceCombo.Items)
180 if (IsUploadable(svc, ext, size))
188 /// 指定された投稿先に投稿可能かどうかを示す値を取得する。
189 /// ファイルサイズの指定がなければ拡張子だけで判定する。
191 private bool IsUploadable(string serviceName, string ext, long? size)
193 if (!string.IsNullOrEmpty(serviceName))
197 if (this.pictureService[serviceName].CheckFileSize(ext, size.Value))
202 if (this.pictureService[serviceName].CheckFileExtension(ext))
210 /// 投稿するファイルとその投稿先を選択するためのコントロールを表示する。
211 /// D&Dをサポートする場合は引数にドロップされたファイル名を指定して呼ぶこと。
213 public void BeginSelection(string[] fileNames = null)
215 if (fileNames != null && fileNames.Length > 0)
217 var serviceName = this.ServiceName;
218 if (string.IsNullOrEmpty(serviceName)) return;
219 var service = this.pictureService[serviceName];
221 var count = Math.Min(fileNames.Length, service.MaxMediaCount);
222 if (!this.Visible || count > 1)
224 // 非表示時または複数のファイル指定は新規選択として扱う
227 if (this.BeginSelecting != null)
228 this.BeginSelecting(this, EventArgs.Empty);
236 ImagefilePathText.Text = fileNames[0];
237 ImageFromSelectedFile(false);
241 for (int i = 0; i < count; i++)
243 var index = ImagePageCombo.Items.Count - 1;
244 if (index == 0) ImagefilePathText.Text = fileNames[i];
245 ImageFromSelectedFile(index, fileNames[i], false);
253 if (this.BeginSelecting != null)
254 this.BeginSelecting(this, EventArgs.Empty);
258 ImageFromSelectedFile(true);
259 ImagefilePathText.Focus();
265 /// 選択処理を終了してコントロールを隠す。
267 public void EndSelection()
271 ImagefilePathText.CausesValidation = false;
273 if (this.EndSelecting != null)
274 this.EndSelecting(this, EventArgs.Empty);
276 this.Visible = false;
277 this.Enabled = false;
278 ClearImageSelectedPicture();
280 ImagefilePathText.CausesValidation = true;
285 /// 選択された投稿先名と投稿ファイル名を取得する。
287 public bool TryGetSelectedMedia(out string imageService, out string[] imagePaths)
289 var validPaths = ImagePageCombo.Items.Cast<SelectedMedia>()
290 .Where(x => x.IsValid).Select(x => x.Path).ToArray();
292 if (validPaths.Length > 0 &&
293 ImageServiceCombo.SelectedIndex > -1)
295 var serviceName = this.ServiceName;
296 if (MessageBox.Show(string.Format(Properties.Resources.PostPictureConfirm1, serviceName, validPaths.Length),
297 Properties.Resources.PostPictureConfirm2,
298 MessageBoxButtons.OKCancel,
299 MessageBoxIcon.Question,
300 MessageBoxDefaultButton.Button1)
303 imageService = serviceName;
304 imagePaths = validPaths;
312 MessageBox.Show(Properties.Resources.PostPictureWarn1, Properties.Resources.PostPictureWarn2);
321 private void FilePickButton_Click(object sender, EventArgs e)
323 if (FilePickDialog == null || string.IsNullOrEmpty(this.ServiceName)) return;
324 FilePickDialog.Filter = this.pictureService[this.ServiceName].SupportedFormatsStrForDialog;
325 FilePickDialog.Title = Properties.Resources.PickPictureDialog1;
326 FilePickDialog.FileName = "";
328 if (this.FilePickDialogOpening != null)
329 this.FilePickDialogOpening(this, EventArgs.Empty);
333 if (FilePickDialog.ShowDialog() == DialogResult.Cancel) return;
337 if (this.FilePickDialogClosed != null)
338 this.FilePickDialogClosed(this, EventArgs.Empty);
341 ImagefilePathText.Text = FilePickDialog.FileName;
342 ImageFromSelectedFile(false);
345 private void ImagefilePathText_Validating(object sender, CancelEventArgs e)
347 if (ImageCancelButton.Focused)
349 ImagefilePathText.CausesValidation = false;
353 ImageFromSelectedFile(false);
356 private void ImageFromSelectedFile(bool suppressMsgBox)
358 ImagefilePathText.Text = ImagefilePathText.Text.Trim();
359 ImageFromSelectedFile(-1, ImagefilePathText.Text, suppressMsgBox);
362 private void ImageFromSelectedFile(int index, string fileName, bool suppressMsgBox)
364 var serviceName = this.ServiceName;
365 if (string.IsNullOrEmpty(serviceName)) return;
367 var selectedIndex = ImagePageCombo.SelectedIndex;
368 if (index < 0) index = selectedIndex;
370 if (index >= ImagePageCombo.Items.Count)
371 throw new ArgumentOutOfRangeException("index");
373 var imageService = this.pictureService[serviceName];
374 var isSelectedPage = (index == selectedIndex);
377 this.ClearImageSelectedPicture();
379 if (string.IsNullOrEmpty(fileName))
381 ClearImagePage(index);
387 FileInfo fl = new FileInfo(fileName);
388 string ext = fl.Extension;
390 if (!imageService.CheckFileExtension(ext))
393 ClearImagePage(index);
397 string.Format(Properties.Resources.PostPictureWarn3, serviceName, MakeAvailableServiceText(ext, fl.Length), ext, fl.Name),
398 Properties.Resources.PostPictureWarn4,
399 MessageBoxButtons.OK,
400 MessageBoxIcon.Warning);
405 if (!imageService.CheckFileSize(ext, fl.Length))
408 ClearImagePage(index);
412 string.Format(Properties.Resources.PostPictureWarn5, serviceName, MakeAvailableServiceText(ext, fl.Length), fl.Name),
413 Properties.Resources.PostPictureWarn4,
414 MessageBoxButtons.OK,
415 MessageBoxIcon.Warning);
422 using (var fs = File.OpenRead(fileName))
424 var image = MemoryImage.CopyFromStream(fs);
426 ImageSelectedPicture.Image = image;
428 image.Dispose(); //画像チェック後は使わないので破棄する
430 SetImagePage(index, fileName, MyCommon.UploadFileType.Picture);
432 catch (InvalidImageException)
434 SetImagePage(index, fileName, MyCommon.UploadFileType.MultiMedia);
437 catch (FileNotFoundException)
439 ClearImagePage(index);
440 if (!suppressMsgBox) MessageBox.Show("File not found.");
444 ClearImagePage(index);
445 if (!suppressMsgBox) MessageBox.Show("The type of this file is not image.");
449 private string MakeAvailableServiceText(string ext, long fileSize)
451 var text = string.Join(", ",
452 ImageServiceCombo.Items.Cast<string>()
453 .Where(x => !string.IsNullOrEmpty(x) && this.pictureService[x].CheckFileSize(ext, fileSize)));
455 if (string.IsNullOrEmpty(text))
456 return Properties.Resources.PostPictureWarn6;
461 private void ClearImageSelectedPicture()
463 var oldImage = this.ImageSelectedPicture.Image;
464 if (oldImage != null)
466 this.ImageSelectedPicture.Image = null;
470 this.ImageSelectedPicture.ShowInitialImage();
473 private void ImageCancelButton_Click(object sender, EventArgs e)
478 private void ImageSelection_KeyDown(object sender, KeyEventArgs e)
480 if (e.KeyCode == Keys.Escape)
486 private void ImageSelection_KeyPress(object sender, KeyPressEventArgs e)
488 if (Convert.ToInt32(e.KeyChar) == 0x1B)
490 ImagefilePathText.CausesValidation = false;
495 private void ImageSelection_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
497 if (e.KeyCode == Keys.Escape)
499 ImagefilePathText.CausesValidation = false;
503 private void SetImageServiceCombo()
505 using (ControlTransaction.Update(ImageServiceCombo))
508 if (ImageServiceCombo.SelectedIndex > -1) svc = ImageServiceCombo.Text;
509 ImageServiceCombo.Items.Clear();
511 // Add service names to combobox
512 foreach (var key in pictureService.Keys)
514 ImageServiceCombo.Items.Add(key);
517 SelectImageServiceComboItem(svc);
521 private void SelectImageServiceComboItem(string svc, int? index = null)
524 if (string.IsNullOrEmpty(svc))
530 idx = ImageServiceCombo.Items.IndexOf(svc);
531 if (idx == -1) idx = index ?? 0;
536 ImageServiceCombo.SelectedIndex = idx;
538 catch (ArgumentOutOfRangeException)
540 ImageServiceCombo.SelectedIndex = 0;
544 private void ImageServiceCombo_SelectedIndexChanged(object sender, EventArgs e)
548 var serviceName = this.ServiceName;
549 if (!string.IsNullOrEmpty(serviceName))
551 if (ImagePageCombo.Items.Count > 0)
553 // 画像が選択された投稿先に対応しているかをチェックする
554 // TODO: 複数の選択済み画像があるなら、できれば全てを再チェックしたほうがいい
555 if (serviceName.Equals("Twitter"))
557 ValidateSelectedImagePage();
561 if (ImagePageCombo.Items.Count > 1)
563 // 複数の選択済み画像のうち、1枚目のみを残す
564 SetImagePageCombo((SelectedMedia)ImagePageCombo.Items[0]);
568 ImagePageCombo.Enabled = false;
572 FileInfo fi = new FileInfo(ImagefilePathText.Text.Trim());
573 string ext = fi.Extension;
574 var imageService = this.pictureService[serviceName];
575 if (!imageService.CheckFileSize(ext, fi.Length))
577 ClearImageSelectedPicture();
578 ClearSelectedImagePage();
583 ClearImageSelectedPicture();
584 ClearSelectedImagePage();
592 if (this.SelectedServiceChanged != null)
593 this.SelectedServiceChanged(this, EventArgs.Empty);
596 private void SetImagePageCombo(SelectedMedia media = null)
598 using (ControlTransaction.Update(ImagePageCombo))
600 ImagePageCombo.Enabled = false;
601 ImagePageCombo.Items.Clear();
604 ImagePageCombo.Items.Add(media);
605 ImagefilePathText.Text = media.Path;
609 ImagePageCombo.Items.Add(new SelectedMedia("1"));
610 ImagefilePathText.Text = "";
612 ImagePageCombo.SelectedIndex = 0;
616 private void AddNewImagePage(int selectedIndex)
618 var serviceName = this.ServiceName;
619 if (string.IsNullOrEmpty(serviceName)) return;
621 if (selectedIndex < this.pictureService[serviceName].MaxMediaCount - 1)
623 // 投稿先の投稿可能枚数まで選択できるようにする
624 var count = ImagePageCombo.Items.Count;
625 if (selectedIndex == count - 1)
628 ImagePageCombo.Items.Add(new SelectedMedia(count.ToString()));
629 ImagePageCombo.Enabled = true;
634 private void SetSelectedImagePage(string path, MyCommon.UploadFileType type)
636 SetImagePage(-1, path, type);
639 private void SetImagePage(int index, string path, MyCommon.UploadFileType type)
641 var selectedIndex = ImagePageCombo.SelectedIndex;
642 if (index < 0) index = selectedIndex;
644 var item = (SelectedMedia)ImagePageCombo.Items[index];
648 AddNewImagePage(index);
651 private void ClearSelectedImagePage()
656 private void ClearImagePage(int index)
658 var selectedIndex = ImagePageCombo.SelectedIndex;
659 if (index < 0) index = selectedIndex;
661 var item = (SelectedMedia)ImagePageCombo.Items[index];
663 item.Type = MyCommon.UploadFileType.Invalid;
665 if (index == selectedIndex) ImagefilePathText.Text = "";
668 private void ValidateSelectedImagePage()
670 var idx = ImagePageCombo.SelectedIndex;
671 var item = (SelectedMedia)ImagePageCombo.Items[idx];
672 ImageServiceCombo.Enabled = (idx == 0); // idx == 0 以外では投稿先サービスを選べないようにする
673 ImagefilePathText.Text = item.Path;
674 ImageFromSelectedFile(true);
677 private void ImagePageCombo_SelectedIndexChanged(object sender, EventArgs e)
679 ValidateSelectedImagePage();