OSDN Git Service

Revert 74f28ad
[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 using System;
23 using System.Collections.Generic;
24 using System.ComponentModel;
25 using System.Drawing;
26 using System.Data;
27 using System.IO;
28 using System.Linq;
29 using System.Text;
30 using System.Threading.Tasks;
31 using System.Windows.Forms;
32 using OpenTween.Api;
33 using OpenTween.Connection;
34
35 namespace OpenTween
36 {
37     public partial class MediaSelector : UserControl
38     {
39         public event EventHandler BeginSelecting;
40         public event EventHandler EndSelecting;
41
42         public event EventHandler FilePickDialogOpening;
43         public event EventHandler FilePickDialogClosed;
44
45         public event EventHandler SelectedServiceChanged;
46
47         [Browsable(false)]
48         [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
49         public OpenFileDialog FilePickDialog { get; set; }
50
51         /// <summary>
52         /// 選択されている投稿先名を取得する。
53         /// </summary>
54         [Browsable(false)]
55         [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
56         public string ServiceName
57         {
58             get { return ImageServiceCombo.Text; }
59         }
60
61         /// <summary>
62         /// 選択されている投稿先を示すインデックスを取得する。
63         /// </summary>
64         [Browsable(false)]
65         [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
66         public int ServiceIndex
67         {
68             get { return ImageServiceCombo.SelectedIndex; }
69         }
70
71         /// <summary>
72         /// 指定された投稿先名から、作成済みの IMediaUploadService インスタンスを取得する。
73         /// </summary>
74         public IMediaUploadService GetService(string serviceName)
75         {
76             IMediaUploadService service;
77             this.pictureService.TryGetValue(serviceName, out service);
78             return service;
79         }
80
81         /// <summary>
82         /// 利用可能な全ての IMediaUploadService インスタンスを取得する。
83         /// </summary>
84         public ICollection<IMediaUploadService> GetServices()
85         {
86             return this.pictureService.Values;
87         }
88
89         private class SelectedMedia
90         {
91             public string Path { get; set; }
92             public MyCommon.UploadFileType Type { get; set; }
93             public string Text { get; set; }
94
95             public SelectedMedia(string path, MyCommon.UploadFileType type, string text)
96             {
97                 this.Path = path;
98                 this.Type = type;
99                 this.Text = text;
100             }
101
102             public SelectedMedia(string text)
103                 : this("", MyCommon.UploadFileType.Invalid, text)
104             {
105             }
106
107             public bool IsValid
108             {
109                 get { return this.Type != MyCommon.UploadFileType.Invalid; }
110             }
111
112             public override string ToString()
113             {
114                 return this.Text;
115             }
116         }
117
118         private Dictionary<string, IMediaUploadService> pictureService;
119
120         private void CreateServices(Twitter tw, TwitterConfiguration twitterConfig)
121         {
122             if (this.pictureService != null) this.pictureService.Clear();
123             this.pictureService = null;
124
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)},
133             };
134         }
135
136         public MediaSelector()
137         {
138             InitializeComponent();
139
140             this.ImageSelectedPicture.InitialImage = Properties.Resources.InitialImage;
141         }
142
143         /// <summary>
144         /// 投稿先サービスなどを初期化する。
145         /// </summary>
146         public void Initialize(Twitter tw, TwitterConfiguration twitterConfig, string svc, int? index = null)
147         {
148             CreateServices(tw, twitterConfig);
149
150             SetImageServiceCombo();
151             SetImagePageCombo();
152
153             SelectImageServiceComboItem(svc, index);
154         }
155
156         /// <summary>
157         /// 投稿先サービスを再作成する。
158         /// </summary>
159         public void Reset(Twitter tw, TwitterConfiguration twitterConfig)
160         {
161             CreateServices(tw, twitterConfig);
162
163             SetImageServiceCombo();
164         }
165
166         /// <summary>
167         /// 指定されたファイルの投稿に対応した投稿先があるかどうかを示す値を取得する。
168         /// </summary>
169         public bool HasUploadableService(string fileName, bool ignoreSize)
170         {
171             FileInfo fl = new FileInfo(fileName);
172             string ext = fl.Extension;
173             long? size = ignoreSize ? (long?)null : fl.Length;
174
175             if (IsUploadable(this.ServiceName, ext, size))
176                 return true;
177
178             foreach (string svc in ImageServiceCombo.Items)
179             {
180                 if (IsUploadable(svc, ext, size))
181                     return true;
182             }
183
184             return false;
185         }
186
187         /// <summary>
188         /// 指定された投稿先に投稿可能かどうかを示す値を取得する。
189         /// ファイルサイズの指定がなければ拡張子だけで判定する。
190         /// </summary>
191         private bool IsUploadable(string serviceName, string ext, long? size)
192         {
193             if (!string.IsNullOrEmpty(serviceName))
194             {
195                 if (size.HasValue)
196                 {
197                     if (this.pictureService[serviceName].CheckFileSize(ext, size.Value))
198                         return true;
199                 }
200                 else
201                 {
202                     if (this.pictureService[serviceName].CheckFileExtension(ext))
203                         return true;
204                 }
205             }
206             return false;
207         }
208
209         /// <summary>
210         /// 投稿するファイルとその投稿先を選択するためのコントロールを表示する。
211         /// D&Dをサポートする場合は引数にドロップされたファイル名を指定して呼ぶこと。
212         /// </summary>
213         public void BeginSelection(string[] fileNames = null)
214         {
215             if (fileNames != null && fileNames.Length > 0)
216             {
217                 var serviceName = this.ServiceName;
218                 if (string.IsNullOrEmpty(serviceName)) return;
219                 var service = this.pictureService[serviceName];
220
221                 var count = Math.Min(fileNames.Length, service.MaxMediaCount);
222                 if (!this.Visible || count > 1)
223                 {
224                     // 非表示時または複数のファイル指定は新規選択として扱う
225                     SetImagePageCombo();
226
227                     if (this.BeginSelecting != null)
228                         this.BeginSelecting(this, EventArgs.Empty);
229
230                     this.Visible = true;
231                 }
232                 this.Enabled = true;
233
234                 if (count == 1)
235                 {
236                     ImagefilePathText.Text = fileNames[0];
237                     ImageFromSelectedFile(false);
238                 }
239                 else
240                 {
241                     for (int i = 0; i < count; i++)
242                     {
243                         var index = ImagePageCombo.Items.Count - 1;
244                         if (index == 0) ImagefilePathText.Text = fileNames[i];
245                         ImageFromSelectedFile(index, fileNames[i], false);
246                     }
247                 }
248             }
249             else
250             {
251                 if (!this.Visible)
252                 {
253                     if (this.BeginSelecting != null)
254                         this.BeginSelecting(this, EventArgs.Empty);
255
256                     this.Visible = true;
257                     this.Enabled = true;
258                     ImageFromSelectedFile(true);
259                     ImagefilePathText.Focus();
260                 }
261             }
262         }
263
264         /// <summary>
265         /// 選択処理を終了してコントロールを隠す。
266         /// </summary>
267         public void EndSelection()
268         {
269             if (this.Visible)
270             {
271                 ImagefilePathText.CausesValidation = false;
272
273                 if (this.EndSelecting != null)
274                     this.EndSelecting(this, EventArgs.Empty);
275
276                 this.Visible = false;
277                 this.Enabled = false;
278                 ClearImageSelectedPicture();
279
280                 ImagefilePathText.CausesValidation = true;
281             }
282         }
283
284         /// <summary>
285         /// 選択された投稿先名と投稿ファイル名を取得する。
286         /// </summary>
287         public bool TryGetSelectedMedia(out string imageService, out string[] imagePaths)
288         {
289             var validPaths = ImagePageCombo.Items.Cast<SelectedMedia>()
290                              .Where(x => x.IsValid).Select(x => x.Path).ToArray();
291
292             if (validPaths.Length > 0 &&
293                 ImageServiceCombo.SelectedIndex > -1)
294             {
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)
301                                == DialogResult.OK)
302                 {
303                     imageService = serviceName;
304                     imagePaths = validPaths;
305                     EndSelection();
306                     SetImagePageCombo();
307                     return true;
308                 }
309             }
310             else
311             {
312                 MessageBox.Show(Properties.Resources.PostPictureWarn1, Properties.Resources.PostPictureWarn2);
313             }
314
315             EndSelection();
316             imageService = null;
317             imagePaths = null;
318             return false;
319         }
320
321         private void FilePickButton_Click(object sender, EventArgs e)
322         {
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 = "";
327
328             if (this.FilePickDialogOpening != null)
329                 this.FilePickDialogOpening(this, EventArgs.Empty);
330
331             try
332             {
333                 if (FilePickDialog.ShowDialog() == DialogResult.Cancel) return;
334             }
335             finally
336             {
337                 if (this.FilePickDialogClosed != null)
338                     this.FilePickDialogClosed(this, EventArgs.Empty);
339             }
340
341             ImagefilePathText.Text = FilePickDialog.FileName;
342             ImageFromSelectedFile(false);
343         }
344
345         private void ImagefilePathText_Validating(object sender, CancelEventArgs e)
346         {
347             if (ImageCancelButton.Focused)
348             {
349                 ImagefilePathText.CausesValidation = false;
350                 return;
351             }
352
353             ImageFromSelectedFile(false);
354         }
355
356         private void ImageFromSelectedFile(bool suppressMsgBox)
357         {
358             ImagefilePathText.Text = ImagefilePathText.Text.Trim();
359             ImageFromSelectedFile(-1, ImagefilePathText.Text, suppressMsgBox);
360         }
361
362         private void ImageFromSelectedFile(int index, string fileName, bool suppressMsgBox)
363         {
364             var serviceName = this.ServiceName;
365             if (string.IsNullOrEmpty(serviceName)) return;
366
367             var selectedIndex = ImagePageCombo.SelectedIndex;
368             if (index < 0) index = selectedIndex;
369
370             if (index >= ImagePageCombo.Items.Count)
371                 throw new ArgumentOutOfRangeException("index");
372
373             var imageService = this.pictureService[serviceName];
374             var isSelectedPage = (index == selectedIndex);
375
376             if (isSelectedPage)
377                 this.ClearImageSelectedPicture();
378
379             if (string.IsNullOrEmpty(fileName))
380             {
381                 ClearImagePage(index);
382                 return;
383             }
384
385             try
386             {
387                 FileInfo fl = new FileInfo(fileName);
388                 string ext = fl.Extension;
389
390                 if (!imageService.CheckFileExtension(ext))
391                 {
392                     //画像以外の形式
393                     ClearImagePage(index);
394                     if (!suppressMsgBox)
395                     {
396                         MessageBox.Show(
397                             string.Format(Properties.Resources.PostPictureWarn3, serviceName, MakeAvailableServiceText(ext, fl.Length), ext, fl.Name),
398                             Properties.Resources.PostPictureWarn4,
399                             MessageBoxButtons.OK,
400                             MessageBoxIcon.Warning);
401                     }
402                     return;
403                 }
404
405                 if (!imageService.CheckFileSize(ext, fl.Length))
406                 {
407                     // ファイルサイズが大きすぎる
408                     ClearImagePage(index);
409                     if (!suppressMsgBox)
410                     {
411                         MessageBox.Show(
412                             string.Format(Properties.Resources.PostPictureWarn5, serviceName, MakeAvailableServiceText(ext, fl.Length), fl.Name),
413                             Properties.Resources.PostPictureWarn4,
414                             MessageBoxButtons.OK,
415                             MessageBoxIcon.Warning);
416                     }
417                     return;
418                 }
419
420                 try
421                 {
422                     using (var fs = File.OpenRead(fileName))
423                     {
424                         var image = MemoryImage.CopyFromStream(fs);
425                         if (isSelectedPage)
426                             ImageSelectedPicture.Image = image;
427                         else
428                             image.Dispose();  //画像チェック後は使わないので破棄する
429                     }
430                     SetImagePage(index, fileName, MyCommon.UploadFileType.Picture);
431                 }
432                 catch (InvalidImageException)
433                 {
434                     SetImagePage(index, fileName, MyCommon.UploadFileType.MultiMedia);
435                 }
436             }
437             catch (FileNotFoundException)
438             {
439                 ClearImagePage(index);
440                 if (!suppressMsgBox) MessageBox.Show("File not found.");
441             }
442             catch (Exception)
443             {
444                 ClearImagePage(index);
445                 if (!suppressMsgBox) MessageBox.Show("The type of this file is not image.");
446             }
447         }
448
449         private string MakeAvailableServiceText(string ext, long fileSize)
450         {
451             var text = string.Join(", ",
452                 ImageServiceCombo.Items.Cast<string>()
453                     .Where(x => !string.IsNullOrEmpty(x) && this.pictureService[x].CheckFileSize(ext, fileSize)));
454
455             if (string.IsNullOrEmpty(text))
456                 return Properties.Resources.PostPictureWarn6;
457
458             return text;
459         }
460
461         private void ClearImageSelectedPicture()
462         {
463             var oldImage = this.ImageSelectedPicture.Image;
464             if (oldImage != null)
465             {
466                 this.ImageSelectedPicture.Image = null;
467                 oldImage.Dispose();
468             }
469
470             this.ImageSelectedPicture.ShowInitialImage();
471         }
472
473         private void ImageCancelButton_Click(object sender, EventArgs e)
474         {
475             EndSelection();
476         }
477
478         private void ImageSelection_KeyDown(object sender, KeyEventArgs e)
479         {
480             if (e.KeyCode == Keys.Escape)
481             {
482                 EndSelection();
483             }
484         }
485
486         private void ImageSelection_KeyPress(object sender, KeyPressEventArgs e)
487         {
488             if (Convert.ToInt32(e.KeyChar) == 0x1B)
489             {
490                 ImagefilePathText.CausesValidation = false;
491                 e.Handled = true;
492             }
493         }
494
495         private void ImageSelection_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
496         {
497             if (e.KeyCode == Keys.Escape)
498             {
499                 ImagefilePathText.CausesValidation = false;
500             }
501         }
502
503         private void SetImageServiceCombo()
504         {
505             using (ControlTransaction.Update(ImageServiceCombo))
506             {
507                 string svc = "";
508                 if (ImageServiceCombo.SelectedIndex > -1) svc = ImageServiceCombo.Text;
509                 ImageServiceCombo.Items.Clear();
510
511                 // Add service names to combobox
512                 foreach (var key in pictureService.Keys)
513                 {
514                     ImageServiceCombo.Items.Add(key);
515                 }
516
517                 SelectImageServiceComboItem(svc);
518             }
519         }
520
521         private void SelectImageServiceComboItem(string svc, int? index = null)
522         {
523             int idx;
524             if (string.IsNullOrEmpty(svc))
525             {
526                 idx = index ?? 0;
527             }
528             else
529             {
530                 idx = ImageServiceCombo.Items.IndexOf(svc);
531                 if (idx == -1) idx = index ?? 0;
532             }
533
534             try
535             {
536                 ImageServiceCombo.SelectedIndex = idx;
537             }
538             catch (ArgumentOutOfRangeException)
539             {
540                 ImageServiceCombo.SelectedIndex = 0;
541             }
542         }
543
544         private void ImageServiceCombo_SelectedIndexChanged(object sender, EventArgs e)
545         {
546             if (this.Visible)
547             {
548                 var serviceName = this.ServiceName;
549                 if (!string.IsNullOrEmpty(serviceName))
550                 {
551                     if (ImagePageCombo.Items.Count > 0)
552                     {
553                         // 画像が選択された投稿先に対応しているかをチェックする
554                         // TODO: 複数の選択済み画像があるなら、できれば全てを再チェックしたほうがいい
555                         if (serviceName.Equals("Twitter"))
556                         {
557                             ValidateSelectedImagePage();
558                         }
559                         else
560                         {
561                             if (ImagePageCombo.Items.Count > 1)
562                             {
563                                 // 複数の選択済み画像のうち、1枚目のみを残す
564                                 SetImagePageCombo((SelectedMedia)ImagePageCombo.Items[0]);
565                             }
566                             else
567                             {
568                                 ImagePageCombo.Enabled = false;
569
570                                 try
571                                 {
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))
576                                     {
577                                         ClearImageSelectedPicture();
578                                         ClearSelectedImagePage();
579                                     }
580                                 }
581                                 catch (Exception)
582                                 {
583                                     ClearImageSelectedPicture();
584                                     ClearSelectedImagePage();
585                                 }
586                             }
587                         }
588                     }
589                 }
590             }
591
592             if (this.SelectedServiceChanged != null)
593                 this.SelectedServiceChanged(this, EventArgs.Empty);
594         }
595
596         private void SetImagePageCombo(SelectedMedia media = null)
597         {
598             using (ControlTransaction.Update(ImagePageCombo))
599             {
600                 ImagePageCombo.Enabled = false;
601                 ImagePageCombo.Items.Clear();
602                 if (media != null)
603                 {
604                     ImagePageCombo.Items.Add(media);
605                     ImagefilePathText.Text = media.Path;
606                 }
607                 else
608                 {
609                     ImagePageCombo.Items.Add(new SelectedMedia("1"));
610                     ImagefilePathText.Text = "";
611                 }
612                 ImagePageCombo.SelectedIndex = 0;
613             }
614         }
615
616         private void AddNewImagePage(int selectedIndex)
617         {
618             var serviceName = this.ServiceName;
619             if (string.IsNullOrEmpty(serviceName)) return;
620
621             if (selectedIndex < this.pictureService[serviceName].MaxMediaCount - 1)
622             {
623                 // 投稿先の投稿可能枚数まで選択できるようにする
624                 var count = ImagePageCombo.Items.Count;
625                 if (selectedIndex == count - 1)
626                 {
627                     count++;
628                     ImagePageCombo.Items.Add(new SelectedMedia(count.ToString()));
629                     ImagePageCombo.Enabled = true;
630                 }
631             }
632         }
633
634         private void SetSelectedImagePage(string path, MyCommon.UploadFileType type)
635         {
636             SetImagePage(-1, path, type);
637         }
638
639         private void SetImagePage(int index, string path, MyCommon.UploadFileType type)
640         {
641             var selectedIndex = ImagePageCombo.SelectedIndex;
642             if (index < 0) index = selectedIndex;
643
644             var item = (SelectedMedia)ImagePageCombo.Items[index];
645             item.Path = path;
646             item.Type = type;
647
648             AddNewImagePage(index);
649         }
650
651         private void ClearSelectedImagePage()
652         {
653             ClearImagePage(-1);
654         }
655
656         private void ClearImagePage(int index)
657         {
658             var selectedIndex = ImagePageCombo.SelectedIndex;
659             if (index < 0) index = selectedIndex;
660
661             var item = (SelectedMedia)ImagePageCombo.Items[index];
662             item.Path = "";
663             item.Type = MyCommon.UploadFileType.Invalid;
664
665             if (index == selectedIndex) ImagefilePathText.Text = "";
666         }
667
668         private void ValidateSelectedImagePage()
669         {
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);
675         }
676
677         private void ImagePageCombo_SelectedIndexChanged(object sender, EventArgs e)
678         {
679             ValidateSelectedImagePage();
680         }
681     }
682 }