OSDN Git Service

Twitpicを画像の投稿先から削除(サービス終了のため)
[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                 {"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)},
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, bool ignoreSize)
169         {
170             FileInfo fl = new FileInfo(fileName);
171             string ext = fl.Extension;
172             long? size = ignoreSize ? (long?)null : fl.Length;
173
174             if (IsUploadable(this.ServiceName, ext, size))
175                 return true;
176
177             foreach (string svc in ImageServiceCombo.Items)
178             {
179                 if (IsUploadable(svc, ext, size))
180                     return true;
181             }
182
183             return false;
184         }
185
186         /// <summary>
187         /// 指定された投稿先に投稿可能かどうかを示す値を取得する。
188         /// ファイルサイズの指定がなければ拡張子だけで判定する。
189         /// </summary>
190         private bool IsUploadable(string serviceName, string ext, long? size)
191         {
192             if (!string.IsNullOrEmpty(serviceName))
193             {
194                 if (size.HasValue)
195                 {
196                     if (this.pictureService[serviceName].CheckFileSize(ext, size.Value))
197                         return true;
198                 }
199                 else
200                 {
201                     if (this.pictureService[serviceName].CheckFileExtension(ext))
202                         return true;
203                 }
204             }
205             return false;
206         }
207
208         /// <summary>
209         /// 投稿するファイルとその投稿先を選択するためのコントロールを表示する。
210         /// D&Dをサポートする場合は引数にドロップされたファイル名を指定して呼ぶこと。
211         /// </summary>
212         public void BeginSelection(string[] fileNames = null)
213         {
214             if (fileNames != null && fileNames.Length > 0)
215             {
216                 var serviceName = this.ServiceName;
217                 if (string.IsNullOrEmpty(serviceName)) return;
218                 var service = this.pictureService[serviceName];
219
220                 var count = Math.Min(fileNames.Length, service.MaxMediaCount);
221                 if (!this.Visible || count > 1)
222                 {
223                     // 非表示時または複数のファイル指定は新規選択として扱う
224                     SetImagePageCombo();
225
226                     if (this.BeginSelecting != null)
227                         this.BeginSelecting(this, EventArgs.Empty);
228
229                     this.Visible = true;
230                 }
231                 this.Enabled = true;
232
233                 if (count == 1)
234                 {
235                     ImagefilePathText.Text = fileNames[0];
236                     ImageFromSelectedFile(false);
237                 }
238                 else
239                 {
240                     for (int i = 0; i < count; i++)
241                     {
242                         var index = ImagePageCombo.Items.Count - 1;
243                         if (index == 0) ImagefilePathText.Text = fileNames[i];
244                         ImageFromSelectedFile(index, fileNames[i], false);
245                     }
246                 }
247             }
248             else
249             {
250                 if (!this.Visible)
251                 {
252                     if (this.BeginSelecting != null)
253                         this.BeginSelecting(this, EventArgs.Empty);
254
255                     this.Visible = true;
256                     this.Enabled = true;
257                     ImageFromSelectedFile(true);
258                     ImagefilePathText.Focus();
259                 }
260             }
261         }
262
263         /// <summary>
264         /// 選択処理を終了してコントロールを隠す。
265         /// </summary>
266         public void EndSelection()
267         {
268             if (this.Visible)
269             {
270                 ImagefilePathText.CausesValidation = false;
271
272                 if (this.EndSelecting != null)
273                     this.EndSelecting(this, EventArgs.Empty);
274
275                 this.Visible = false;
276                 this.Enabled = false;
277                 ClearImageSelectedPicture();
278
279                 ImagefilePathText.CausesValidation = true;
280             }
281         }
282
283         /// <summary>
284         /// 選択された投稿先名と投稿ファイル名を取得する。
285         /// </summary>
286         public bool TryGetSelectedMedia(out string imageService, out string[] imagePaths)
287         {
288             var validPaths = ImagePageCombo.Items.Cast<SelectedMedia>()
289                              .Where(x => x.IsValid).Select(x => x.Path).ToArray();
290
291             if (validPaths.Length > 0 &&
292                 ImageServiceCombo.SelectedIndex > -1)
293             {
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)
300                                == DialogResult.OK)
301                 {
302                     imageService = serviceName;
303                     imagePaths = validPaths;
304                     EndSelection();
305                     SetImagePageCombo();
306                     return true;
307                 }
308             }
309             else
310             {
311                 MessageBox.Show(Properties.Resources.PostPictureWarn1, Properties.Resources.PostPictureWarn2);
312             }
313
314             EndSelection();
315             imageService = null;
316             imagePaths = null;
317             return false;
318         }
319
320         private void FilePickButton_Click(object sender, EventArgs e)
321         {
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 = "";
326
327             if (this.FilePickDialogOpening != null)
328                 this.FilePickDialogOpening(this, EventArgs.Empty);
329
330             try
331             {
332                 if (FilePickDialog.ShowDialog() == DialogResult.Cancel) return;
333             }
334             finally
335             {
336                 if (this.FilePickDialogClosed != null)
337                     this.FilePickDialogClosed(this, EventArgs.Empty);
338             }
339
340             ImagefilePathText.Text = FilePickDialog.FileName;
341             ImageFromSelectedFile(false);
342         }
343
344         private void ImagefilePathText_Validating(object sender, CancelEventArgs e)
345         {
346             if (ImageCancelButton.Focused)
347             {
348                 ImagefilePathText.CausesValidation = false;
349                 return;
350             }
351
352             ImageFromSelectedFile(false);
353         }
354
355         private void ImageFromSelectedFile(bool suppressMsgBox)
356         {
357             ImagefilePathText.Text = ImagefilePathText.Text.Trim();
358             ImageFromSelectedFile(-1, ImagefilePathText.Text, suppressMsgBox);
359         }
360
361         private void ImageFromSelectedFile(int index, string fileName, bool suppressMsgBox)
362         {
363             var serviceName = this.ServiceName;
364             if (string.IsNullOrEmpty(serviceName)) return;
365
366             var selectedIndex = ImagePageCombo.SelectedIndex;
367             if (index < 0) index = selectedIndex;
368
369             if (index >= ImagePageCombo.Items.Count)
370                 throw new ArgumentOutOfRangeException("index");
371
372             var imageService = this.pictureService[serviceName];
373             var isSelectedPage = (index == selectedIndex);
374
375             if (isSelectedPage)
376                 this.ClearImageSelectedPicture();
377
378             if (string.IsNullOrEmpty(fileName))
379             {
380                 ClearImagePage(index);
381                 return;
382             }
383
384             try
385             {
386                 FileInfo fl = new FileInfo(fileName);
387                 string ext = fl.Extension;
388
389                 if (!imageService.CheckFileExtension(ext))
390                 {
391                     //画像以外の形式
392                     ClearImagePage(index);
393                     if (!suppressMsgBox)
394                     {
395                         MessageBox.Show(
396                             string.Format(Properties.Resources.PostPictureWarn3, serviceName, MakeAvailableServiceText(ext, fl.Length), ext, fl.Name),
397                             Properties.Resources.PostPictureWarn4,
398                             MessageBoxButtons.OK,
399                             MessageBoxIcon.Warning);
400                     }
401                     return;
402                 }
403
404                 if (!imageService.CheckFileSize(ext, fl.Length))
405                 {
406                     // ファイルサイズが大きすぎる
407                     ClearImagePage(index);
408                     if (!suppressMsgBox)
409                     {
410                         MessageBox.Show(
411                             string.Format(Properties.Resources.PostPictureWarn5, serviceName, MakeAvailableServiceText(ext, fl.Length), fl.Name),
412                             Properties.Resources.PostPictureWarn4,
413                             MessageBoxButtons.OK,
414                             MessageBoxIcon.Warning);
415                     }
416                     return;
417                 }
418
419                 try
420                 {
421                     using (var fs = File.OpenRead(fileName))
422                     {
423                         var image = MemoryImage.CopyFromStream(fs);
424                         if (isSelectedPage)
425                             ImageSelectedPicture.Image = image;
426                         else
427                             image.Dispose();  //画像チェック後は使わないので破棄する
428                     }
429                     SetImagePage(index, fileName, MyCommon.UploadFileType.Picture);
430                 }
431                 catch (InvalidImageException)
432                 {
433                     SetImagePage(index, fileName, MyCommon.UploadFileType.MultiMedia);
434                 }
435             }
436             catch (FileNotFoundException)
437             {
438                 ClearImagePage(index);
439                 if (!suppressMsgBox) MessageBox.Show("File not found.");
440             }
441             catch (Exception)
442             {
443                 ClearImagePage(index);
444                 if (!suppressMsgBox) MessageBox.Show("The type of this file is not image.");
445             }
446         }
447
448         private string MakeAvailableServiceText(string ext, long fileSize)
449         {
450             var text = string.Join(", ",
451                 ImageServiceCombo.Items.Cast<string>()
452                     .Where(x => !string.IsNullOrEmpty(x) && this.pictureService[x].CheckFileSize(ext, fileSize)));
453
454             if (string.IsNullOrEmpty(text))
455                 return Properties.Resources.PostPictureWarn6;
456
457             return text;
458         }
459
460         private void ClearImageSelectedPicture()
461         {
462             var oldImage = this.ImageSelectedPicture.Image;
463             if (oldImage != null)
464             {
465                 this.ImageSelectedPicture.Image = null;
466                 oldImage.Dispose();
467             }
468
469             this.ImageSelectedPicture.ShowInitialImage();
470         }
471
472         private void ImageCancelButton_Click(object sender, EventArgs e)
473         {
474             EndSelection();
475         }
476
477         private void ImageSelection_KeyDown(object sender, KeyEventArgs e)
478         {
479             if (e.KeyCode == Keys.Escape)
480             {
481                 EndSelection();
482             }
483         }
484
485         private void ImageSelection_KeyPress(object sender, KeyPressEventArgs e)
486         {
487             if (Convert.ToInt32(e.KeyChar) == 0x1B)
488             {
489                 ImagefilePathText.CausesValidation = false;
490                 e.Handled = true;
491             }
492         }
493
494         private void ImageSelection_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
495         {
496             if (e.KeyCode == Keys.Escape)
497             {
498                 ImagefilePathText.CausesValidation = false;
499             }
500         }
501
502         private void SetImageServiceCombo()
503         {
504             using (ControlTransaction.Update(ImageServiceCombo))
505             {
506                 string svc = "";
507                 if (ImageServiceCombo.SelectedIndex > -1) svc = ImageServiceCombo.Text;
508                 ImageServiceCombo.Items.Clear();
509
510                 // Add service names to combobox
511                 foreach (var key in pictureService.Keys)
512                 {
513                     ImageServiceCombo.Items.Add(key);
514                 }
515
516                 SelectImageServiceComboItem(svc);
517             }
518         }
519
520         private void SelectImageServiceComboItem(string svc, int? index = null)
521         {
522             int idx;
523             if (string.IsNullOrEmpty(svc))
524             {
525                 idx = index ?? 0;
526             }
527             else
528             {
529                 idx = ImageServiceCombo.Items.IndexOf(svc);
530                 if (idx == -1) idx = index ?? 0;
531             }
532
533             try
534             {
535                 ImageServiceCombo.SelectedIndex = idx;
536             }
537             catch (ArgumentOutOfRangeException)
538             {
539                 ImageServiceCombo.SelectedIndex = 0;
540             }
541         }
542
543         private void ImageServiceCombo_SelectedIndexChanged(object sender, EventArgs e)
544         {
545             if (this.Visible)
546             {
547                 var serviceName = this.ServiceName;
548                 if (!string.IsNullOrEmpty(serviceName))
549                 {
550                     if (ImagePageCombo.Items.Count > 0)
551                     {
552                         // 画像が選択された投稿先に対応しているかをチェックする
553                         // TODO: 複数の選択済み画像があるなら、できれば全てを再チェックしたほうがいい
554                         if (serviceName.Equals("Twitter"))
555                         {
556                             ValidateSelectedImagePage();
557                         }
558                         else
559                         {
560                             if (ImagePageCombo.Items.Count > 1)
561                             {
562                                 // 複数の選択済み画像のうち、1枚目のみを残す
563                                 SetImagePageCombo((SelectedMedia)ImagePageCombo.Items[0]);
564                             }
565                             else
566                             {
567                                 ImagePageCombo.Enabled = false;
568
569                                 try
570                                 {
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))
575                                     {
576                                         ClearImageSelectedPicture();
577                                         ClearSelectedImagePage();
578                                     }
579                                 }
580                                 catch (Exception)
581                                 {
582                                     ClearImageSelectedPicture();
583                                     ClearSelectedImagePage();
584                                 }
585                             }
586                         }
587                     }
588                 }
589             }
590
591             if (this.SelectedServiceChanged != null)
592                 this.SelectedServiceChanged(this, EventArgs.Empty);
593         }
594
595         private void SetImagePageCombo(SelectedMedia media = null)
596         {
597             using (ControlTransaction.Update(ImagePageCombo))
598             {
599                 ImagePageCombo.Enabled = false;
600                 ImagePageCombo.Items.Clear();
601                 if (media != null)
602                 {
603                     ImagePageCombo.Items.Add(media);
604                     ImagefilePathText.Text = media.Path;
605                 }
606                 else
607                 {
608                     ImagePageCombo.Items.Add(new SelectedMedia("1"));
609                     ImagefilePathText.Text = "";
610                 }
611                 ImagePageCombo.SelectedIndex = 0;
612             }
613         }
614
615         private void AddNewImagePage(int selectedIndex)
616         {
617             var serviceName = this.ServiceName;
618             if (string.IsNullOrEmpty(serviceName)) return;
619
620             if (selectedIndex < this.pictureService[serviceName].MaxMediaCount - 1)
621             {
622                 // 投稿先の投稿可能枚数まで選択できるようにする
623                 var count = ImagePageCombo.Items.Count;
624                 if (selectedIndex == count - 1)
625                 {
626                     count++;
627                     ImagePageCombo.Items.Add(new SelectedMedia(count.ToString()));
628                     ImagePageCombo.Enabled = true;
629                 }
630             }
631         }
632
633         private void SetSelectedImagePage(string path, MyCommon.UploadFileType type)
634         {
635             SetImagePage(-1, path, type);
636         }
637
638         private void SetImagePage(int index, string path, MyCommon.UploadFileType type)
639         {
640             var selectedIndex = ImagePageCombo.SelectedIndex;
641             if (index < 0) index = selectedIndex;
642
643             var item = (SelectedMedia)ImagePageCombo.Items[index];
644             item.Path = path;
645             item.Type = type;
646
647             AddNewImagePage(index);
648         }
649
650         private void ClearSelectedImagePage()
651         {
652             ClearImagePage(-1);
653         }
654
655         private void ClearImagePage(int index)
656         {
657             var selectedIndex = ImagePageCombo.SelectedIndex;
658             if (index < 0) index = selectedIndex;
659
660             var item = (SelectedMedia)ImagePageCombo.Items[index];
661             item.Path = "";
662             item.Type = MyCommon.UploadFileType.Invalid;
663
664             if (index == selectedIndex) ImagefilePathText.Text = "";
665         }
666
667         private void ValidateSelectedImagePage()
668         {
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);
674         }
675
676         private void ImagePageCombo_SelectedIndexChanged(object sender, EventArgs e)
677         {
678             ValidateSelectedImagePage();
679         }
680     }
681 }