OSDN Git Service

PostClass.Mediaを1つのURLごとに複数の画像URLを保持できるように修正
[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                 {"TwitPic", new TwitPic(tw, twitterConfig)},
127                 {"img.ly", new imgly(tw, twitterConfig)},
128                 {"yfrog", new yfrog(tw, twitterConfig)},
129                 {"Twitter", new TwitterPhoto(tw, twitterConfig)},
130                 {"ついっぷるフォト", new TwipplePhoto(tw, twitterConfig)},
131                 {"Imgur", new Imgur(tw, twitterConfig)},
132             };
133         }
134
135         public MediaSelector()
136         {
137             InitializeComponent();
138
139             this.ImageSelectedPicture.InitialImage = Properties.Resources.InitialImage;
140         }
141
142         /// <summary>
143         /// 投稿先サービスなどを初期化する。
144         /// </summary>
145         public void Initialize(Twitter tw, TwitterConfiguration twitterConfig, string svc, int? index = null)
146         {
147             CreateServices(tw, twitterConfig);
148
149             SetImageServiceCombo();
150             SetImagePageCombo();
151
152             SelectImageServiceComboItem(svc, index);
153         }
154
155         /// <summary>
156         /// 投稿先サービスを再作成する。
157         /// </summary>
158         public void Reset(Twitter tw, TwitterConfiguration twitterConfig)
159         {
160             CreateServices(tw, twitterConfig);
161
162             SetImageServiceCombo();
163         }
164
165         /// <summary>
166         /// 指定されたファイルの投稿に対応した投稿先があるかどうかを示す値を取得する。
167         /// </summary>
168         public bool HasUploadableService(string fileName)
169         {
170             FileInfo fl = new FileInfo(fileName);
171             string ext = fl.Extension;
172
173             var serviceName = this.ServiceName;
174             if (!string.IsNullOrEmpty(serviceName) &&
175                 this.pictureService[serviceName].CheckFileSize(ext, fl.Length))
176             {
177                 return true;
178             }
179
180             foreach (string svc in ImageServiceCombo.Items)
181             {
182                 if (!string.IsNullOrEmpty(svc) &&
183                     this.pictureService[svc].CheckFileSize(ext, fl.Length))
184                 {
185                     return true;
186                 }
187             }
188
189             return false;
190         }
191
192         /// <summary>
193         /// 投稿するファイルとその投稿先を選択するためのコントロールを表示する。
194         /// D&Dをサポートする場合は引数にドロップされたファイル名を指定して呼ぶこと。
195         /// </summary>
196         public void BeginSelection(string fileName = null)
197         {
198             if (!string.IsNullOrEmpty(fileName))
199             {
200                 if (!this.Visible)
201                 {
202                     // 非表示時のファイル指定は新規選択として扱う
203                     SetImagePageCombo();
204
205                     if (this.BeginSelecting != null)
206                         this.BeginSelecting(this, EventArgs.Empty);
207
208                     this.Visible = true;
209                 }
210                 this.Enabled = true;
211                 ImagefilePathText.Text = fileName;
212                 ImageFromSelectedFile(false);
213             }
214             else
215             {
216                 if (!this.Visible)
217                 {
218                     if (this.BeginSelecting != null)
219                         this.BeginSelecting(this, EventArgs.Empty);
220
221                     this.Visible = true;
222                     this.Enabled = true;
223                     ImageFromSelectedFile(true);
224                     ImagefilePathText.Focus();
225                 }
226             }
227         }
228
229         /// <summary>
230         /// 選択処理を終了してコントロールを隠す。
231         /// </summary>
232         public void EndSelection()
233         {
234             if (this.Visible)
235             {
236                 ImagefilePathText.CausesValidation = false;
237
238                 if (this.EndSelecting != null)
239                     this.EndSelecting(this, EventArgs.Empty);
240
241                 this.Visible = false;
242                 this.Enabled = false;
243                 ClearImageSelectedPicture();
244
245                 ImagefilePathText.CausesValidation = true;
246             }
247         }
248
249         /// <summary>
250         /// 選択された投稿先名と投稿ファイル名を取得する。
251         /// </summary>
252         public bool TryGetSelectedMedia(out string imageService, out string[] imagePaths)
253         {
254             var validPaths = ImagePageCombo.Items.Cast<SelectedMedia>()
255                              .Where(x => x.IsValid).Select(x => x.Path).ToArray();
256
257             if (validPaths.Length > 0 &&
258                 ImageServiceCombo.SelectedIndex > -1)
259             {
260                 var serviceName = this.ServiceName;
261                 if (MessageBox.Show(string.Format(Properties.Resources.PostPictureConfirm1, serviceName, validPaths.Length),
262                                    Properties.Resources.PostPictureConfirm2,
263                                    MessageBoxButtons.OKCancel,
264                                    MessageBoxIcon.Question,
265                                    MessageBoxDefaultButton.Button1)
266                                == DialogResult.OK)
267                 {
268                     imageService = serviceName;
269                     imagePaths = validPaths;
270                     EndSelection();
271                     SetImagePageCombo();
272                     return true;
273                 }
274             }
275             else
276             {
277                 MessageBox.Show(Properties.Resources.PostPictureWarn1, Properties.Resources.PostPictureWarn2);
278             }
279
280             EndSelection();
281             imageService = null;
282             imagePaths = null;
283             return false;
284         }
285
286         private void FilePickButton_Click(object sender, EventArgs e)
287         {
288             if (FilePickDialog == null || string.IsNullOrEmpty(this.ServiceName)) return;
289             FilePickDialog.Filter = this.pictureService[this.ServiceName].SupportedFormatsStrForDialog;
290             FilePickDialog.Title = Properties.Resources.PickPictureDialog1;
291             FilePickDialog.FileName = "";
292
293             if (this.FilePickDialogOpening != null)
294                 this.FilePickDialogOpening(this, EventArgs.Empty);
295
296             try
297             {
298                 if (FilePickDialog.ShowDialog() == DialogResult.Cancel) return;
299             }
300             finally
301             {
302                 if (this.FilePickDialogClosed != null)
303                     this.FilePickDialogClosed(this, EventArgs.Empty);
304             }
305
306             ImagefilePathText.Text = FilePickDialog.FileName;
307             ImageFromSelectedFile(false);
308         }
309
310         private void ImagefilePathText_Validating(object sender, CancelEventArgs e)
311         {
312             if (ImageCancelButton.Focused)
313             {
314                 ImagefilePathText.CausesValidation = false;
315                 return;
316             }
317
318             ImageFromSelectedFile(false);
319         }
320
321         private void ImageFromSelectedFile(bool suppressMsgBox)
322         {
323             this.ClearImageSelectedPicture();
324
325             try
326             {
327                 ImagefilePathText.Text = ImagefilePathText.Text.Trim();
328                 var fileName = ImagefilePathText.Text;
329                 var serviceName = this.ServiceName;
330                 if (string.IsNullOrEmpty(fileName) || string.IsNullOrEmpty(serviceName))
331                 {
332                     ClearSelectedImagePage();
333                     return;
334                 }
335
336                 FileInfo fl = new FileInfo(fileName);
337                 string ext = fl.Extension;
338                 var imageService = this.pictureService[serviceName];
339
340                 if (!imageService.CheckFileExtension(ext))
341                 {
342                     //画像以外の形式
343                     ClearSelectedImagePage();
344                     if (!suppressMsgBox)
345                     {
346                         MessageBox.Show(
347                             string.Format(Properties.Resources.PostPictureWarn3, serviceName, MakeAvailableServiceText(ext, fl.Length), ext),
348                             Properties.Resources.PostPictureWarn4,
349                             MessageBoxButtons.OK,
350                             MessageBoxIcon.Warning);
351                     }
352                     return;
353                 }
354
355                 if (!imageService.CheckFileSize(ext, fl.Length))
356                 {
357                     // ファイルサイズが大きすぎる
358                     ClearSelectedImagePage();
359                     if (!suppressMsgBox)
360                     {
361                         MessageBox.Show(
362                             string.Format(Properties.Resources.PostPictureWarn5, serviceName, MakeAvailableServiceText(ext, fl.Length)),
363                             Properties.Resources.PostPictureWarn4,
364                             MessageBoxButtons.OK,
365                             MessageBoxIcon.Warning);
366                     }
367                     return;
368                 }
369
370                 try
371                 {
372                     using (var fs = File.OpenRead(fileName))
373                     {
374                         ImageSelectedPicture.Image = MemoryImage.CopyFromStream(fs);
375                     }
376                     SetSelectedImagePage(fileName, MyCommon.UploadFileType.Picture);
377                 }
378                 catch (InvalidImageException)
379                 {
380                     SetSelectedImagePage(fileName, MyCommon.UploadFileType.MultiMedia);
381                 }
382             }
383             catch (FileNotFoundException)
384             {
385                 ClearSelectedImagePage();
386                 if (!suppressMsgBox) MessageBox.Show("File not found.");
387             }
388             catch (Exception)
389             {
390                 ClearSelectedImagePage();
391                 if (!suppressMsgBox) MessageBox.Show("The type of this file is not image.");
392             }
393         }
394
395         private string MakeAvailableServiceText(string ext, long fileSize)
396         {
397             var text = string.Join(", ",
398                 ImageServiceCombo.Items.Cast<string>()
399                     .Where(x => !string.IsNullOrEmpty(x) && this.pictureService[x].CheckFileSize(ext, fileSize)));
400
401             if (string.IsNullOrEmpty(text))
402                 return Properties.Resources.PostPictureWarn6;
403
404             return text;
405         }
406
407         private void ClearImageSelectedPicture()
408         {
409             var oldImage = this.ImageSelectedPicture.Image;
410             if (oldImage != null)
411             {
412                 this.ImageSelectedPicture.Image = null;
413                 oldImage.Dispose();
414             }
415
416             this.ImageSelectedPicture.ShowInitialImage();
417         }
418
419         private void ImageCancelButton_Click(object sender, EventArgs e)
420         {
421             EndSelection();
422         }
423
424         private void ImageSelection_KeyDown(object sender, KeyEventArgs e)
425         {
426             if (e.KeyCode == Keys.Escape)
427             {
428                 EndSelection();
429             }
430         }
431
432         private void ImageSelection_KeyPress(object sender, KeyPressEventArgs e)
433         {
434             if (Convert.ToInt32(e.KeyChar) == 0x1B)
435             {
436                 ImagefilePathText.CausesValidation = false;
437                 e.Handled = true;
438             }
439         }
440
441         private void ImageSelection_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
442         {
443             if (e.KeyCode == Keys.Escape)
444             {
445                 ImagefilePathText.CausesValidation = false;
446             }
447         }
448
449         private void SetImageServiceCombo()
450         {
451             using (ControlTransaction.Update(ImageServiceCombo))
452             {
453                 string svc = "";
454                 if (ImageServiceCombo.SelectedIndex > -1) svc = ImageServiceCombo.Text;
455                 ImageServiceCombo.Items.Clear();
456
457                 // Add service names to combobox
458                 foreach (var key in pictureService.Keys)
459                 {
460                     ImageServiceCombo.Items.Add(key);
461                 }
462
463                 SelectImageServiceComboItem(svc);
464             }
465         }
466
467         private void SelectImageServiceComboItem(string svc, int? index = null)
468         {
469             int idx;
470             if (string.IsNullOrEmpty(svc))
471             {
472                 idx = index ?? 0;
473             }
474             else
475             {
476                 idx = ImageServiceCombo.Items.IndexOf(svc);
477                 if (idx == -1) idx = index ?? 0;
478             }
479
480             try
481             {
482                 ImageServiceCombo.SelectedIndex = idx;
483             }
484             catch (ArgumentOutOfRangeException)
485             {
486                 ImageServiceCombo.SelectedIndex = 0;
487             }
488         }
489
490         private void ImageServiceCombo_SelectedIndexChanged(object sender, EventArgs e)
491         {
492             if (this.Visible)
493             {
494                 var serviceName = this.ServiceName;
495                 if (!string.IsNullOrEmpty(serviceName))
496                 {
497                     if (ImagePageCombo.Items.Count > 0)
498                     {
499                         // 画像が選択された投稿先に対応しているかをチェックする
500                         // TODO: 複数の選択済み画像があるなら、できれば全てを再チェックしたほうがいい
501                         if (serviceName.Equals("Twitter"))
502                         {
503                             ValidateSelectedImagePage();
504                         }
505                         else
506                         {
507                             if (ImagePageCombo.Items.Count > 1)
508                             {
509                                 // 複数の選択済み画像のうち、1枚目のみを残す
510                                 SetImagePageCombo((SelectedMedia)ImagePageCombo.Items[0]);
511                             }
512                             else
513                             {
514                                 ImagePageCombo.Enabled = false;
515
516                                 try
517                                 {
518                                     FileInfo fi = new FileInfo(ImagefilePathText.Text.Trim());
519                                     string ext = fi.Extension;
520                                     var imageService = this.pictureService[serviceName];
521                                     if (!imageService.CheckFileSize(ext, fi.Length))
522                                     {
523                                         ClearImageSelectedPicture();
524                                         ClearSelectedImagePage();
525                                     }
526                                 }
527                                 catch (Exception)
528                                 {
529                                     ClearImageSelectedPicture();
530                                     ClearSelectedImagePage();
531                                 }
532                             }
533                         }
534                     }
535                 }
536             }
537
538             if (this.SelectedServiceChanged != null)
539                 this.SelectedServiceChanged(this, EventArgs.Empty);
540         }
541
542         private void SetImagePageCombo(SelectedMedia media = null)
543         {
544             using (ControlTransaction.Update(ImagePageCombo))
545             {
546                 ImagePageCombo.Enabled = false;
547                 ImagePageCombo.Items.Clear();
548                 if (media != null)
549                 {
550                     ImagePageCombo.Items.Add(media);
551                     ImagefilePathText.Text = media.Path;
552                 }
553                 else
554                 {
555                     ImagePageCombo.Items.Add(new SelectedMedia("1"));
556                     ImagefilePathText.Text = "";
557                 }
558                 ImagePageCombo.SelectedIndex = 0;
559             }
560         }
561
562         private void AddNewImagePage(int selectedIndex)
563         {
564             if (this.ServiceName.Equals("Twitter") && selectedIndex < 3)
565             {
566                 // 投稿先が Twitter であれば、最大 4 枚まで選択できるようにする
567                 var count = ImagePageCombo.Items.Count;
568                 if (selectedIndex == count - 1)
569                 {
570                     count++;
571                     ImagePageCombo.Items.Add(new SelectedMedia(count.ToString()));
572                     ImagePageCombo.Enabled = true;
573                 }
574             }
575         }
576
577         private void SetSelectedImagePage(string path, MyCommon.UploadFileType type)
578         {
579             var idx = ImagePageCombo.SelectedIndex;
580             var item = (SelectedMedia)ImagePageCombo.Items[idx];
581             item.Path = path;
582             item.Type = type;
583
584             AddNewImagePage(idx);
585         }
586
587         private void ClearSelectedImagePage()
588         {
589             var item = (SelectedMedia)ImagePageCombo.SelectedItem;
590             item.Path = "";
591             item.Type = MyCommon.UploadFileType.Invalid;
592             ImagefilePathText.Text = "";
593         }
594
595         private void ValidateSelectedImagePage()
596         {
597             var idx = ImagePageCombo.SelectedIndex;
598             var item = (SelectedMedia)ImagePageCombo.Items[idx];
599             ImageServiceCombo.Enabled = (idx == 0);  // idx == 0 以外では投稿先サービスを選べないようにする
600             ImagefilePathText.Text = item.Path;
601             ImageFromSelectedFile(true);
602         }
603
604         private void ImagePageCombo_SelectedIndexChanged(object sender, EventArgs e)
605         {
606             ValidateSelectedImagePage();
607         }
608     }
609 }