OSDN Git Service

イベントのテストにAssert.Raisesを使用する
[opentween/open-tween.git] / OpenTween / TweetThumbnail.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2012 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
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.Diagnostics.CodeAnalysis;
26 using System.Drawing;
27 using System.Data;
28 using System.Linq;
29 using System.Net;
30 using System.Net.Http;
31 using System.Text;
32 using System.Windows.Forms;
33 using System.Text.RegularExpressions;
34 using System.Threading.Tasks;
35 using OpenTween.Thumbnail;
36 using System.Threading;
37 using OpenTween.Models;
38
39 namespace OpenTween
40 {
41     public partial class TweetThumbnail : UserControl
42     {
43         protected internal List<OTPictureBox> pictureBox = new List<OTPictureBox>();
44         protected MouseWheelMessageFilter filter = new MouseWheelMessageFilter();
45
46         public event EventHandler<EventArgs> ThumbnailLoading;
47         public event EventHandler<ThumbnailDoubleClickEventArgs> ThumbnailDoubleClick;
48         public event EventHandler<ThumbnailImageSearchEventArgs> ThumbnailImageSearchClick;
49
50         public ThumbnailInfo Thumbnail
51         {
52             get { return this.pictureBox[this.scrollBar.Value].Tag as ThumbnailInfo; }
53         }
54
55         public TweetThumbnail()
56         {
57             InitializeComponent();
58         }
59
60         public Task ShowThumbnailAsync(PostClass post)
61         {
62             return this.ShowThumbnailAsync(post, CancellationToken.None);
63         }
64
65         public async Task ShowThumbnailAsync(PostClass post, CancellationToken cancelToken)
66         {
67             var loadTasks = new List<Task>();
68
69             this.scrollBar.Enabled = false;
70             this.scrollBar.Visible = false;
71
72             if (post.ExpandedUrls.Length == 0 && post.Media.Count == 0 && post.PostGeo == null)
73             {
74                 this.SetThumbnailCount(0);
75                 return;
76             }
77
78             var thumbnails = (await this.GetThumbailInfoAsync(post, cancelToken))
79                 .ToArray();
80
81             cancelToken.ThrowIfCancellationRequested();
82
83             this.SetThumbnailCount(thumbnails.Length);
84             if (thumbnails.Length == 0)
85                 return;
86
87             for (int i = 0; i < thumbnails.Length; i++)
88             {
89                 var thumb = thumbnails[i];
90                 var picbox = this.pictureBox[i];
91
92                 picbox.Tag = thumb;
93                 picbox.ContextMenuStrip = this.contextMenuStrip;
94
95                 var loadTask = picbox.SetImageFromTask(() => thumb.LoadThumbnailImageAsync(cancelToken));
96                 loadTasks.Add(loadTask);
97
98                 var tooltipText = thumb.TooltipText;
99                 if (!string.IsNullOrEmpty(tooltipText))
100                 {
101                     this.toolTip.SetToolTip(picbox, tooltipText);
102                     picbox.AccessibleDescription = tooltipText;
103                 }
104
105                 cancelToken.ThrowIfCancellationRequested();
106             }
107
108             if (thumbnails.Length > 1)
109             {
110                 this.scrollBar.Enabled = true;
111                 this.scrollBar.Visible = true;
112             }
113
114             this.ThumbnailLoading?.Invoke(this, EventArgs.Empty);
115
116             await Task.WhenAll(loadTasks).ConfigureAwait(false);
117         }
118
119         private string GetImageSearchUriGoogle(string image_uri)
120         {
121             return @"https://www.google.com/searchbyimage?image_url=" + Uri.EscapeDataString(image_uri);
122         }
123
124         private string GetImageSearchUriSauceNao(string imageUri)
125         {
126             return @"https://saucenao.com/search.php?url=" + Uri.EscapeDataString(imageUri);
127         }
128
129         protected virtual Task<IEnumerable<ThumbnailInfo>> GetThumbailInfoAsync(PostClass post, CancellationToken token)
130         {
131             return ThumbnailGenerator.GetThumbnailsAsync(post, token);
132         }
133
134         /// <summary>
135         /// 表示するサムネイルの数を設定する
136         /// </summary>
137         /// <param name="count">表示するサムネイルの数</param>
138         protected void SetThumbnailCount(int count)
139         {
140             if (count == 0 && this.pictureBox.Count == 0)
141                 return;
142
143             using (ControlTransaction.Layout(this.panelPictureBox, false))
144             {
145                 this.panelPictureBox.Controls.Clear();
146                 foreach (var picbox in this.pictureBox)
147                 {
148                     var memoryImage = picbox.Image;
149
150                     filter.Unregister(picbox);
151
152                     picbox.MouseWheel -= this.pictureBox_MouseWheel;
153                     picbox.DoubleClick -= this.pictureBox_DoubleClick;
154                     picbox.Dispose();
155
156                     memoryImage?.Dispose();
157
158                     // メモリリーク対策 (http://stackoverflow.com/questions/2792427#2793714)
159                     picbox.ContextMenuStrip = null;
160                 }
161                 this.pictureBox.Clear();
162
163                 this.scrollBar.Maximum = (count > 0) ? count - 1 : 0;
164                 this.scrollBar.Value = 0;
165
166                 for (int i = 0; i < count; i++)
167                 {
168                     var picbox = CreatePictureBox("pictureBox" + i);
169                     picbox.Visible = (i == 0);
170                     picbox.MouseWheel += this.pictureBox_MouseWheel;
171                     picbox.DoubleClick += this.pictureBox_DoubleClick;
172
173                     filter.Register(picbox);
174
175                     this.panelPictureBox.Controls.Add(picbox);
176                     this.pictureBox.Add(picbox);
177                 }
178             }
179         }
180
181         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
182         protected virtual OTPictureBox CreatePictureBox(string name)
183         {
184             return new OTPictureBox
185             {
186                 Name = name,
187                 SizeMode = PictureBoxSizeMode.Zoom,
188                 WaitOnLoad = false,
189                 Dock = DockStyle.Fill,
190                 AccessibleRole = AccessibleRole.Graphic,
191             };
192         }
193
194         public void ScrollUp()
195         {
196             var newval = this.scrollBar.Value - this.scrollBar.SmallChange;
197
198             if (newval < this.scrollBar.Minimum)
199                 newval = this.scrollBar.Minimum;
200
201             this.scrollBar.Value = newval;
202         }
203
204         public void ScrollDown()
205         {
206             var newval = this.scrollBar.Value + this.scrollBar.SmallChange;
207
208             if (newval > this.scrollBar.Maximum)
209                 newval = this.scrollBar.Maximum;
210
211             this.scrollBar.Value = newval;
212         }
213
214         protected override void ScaleControl(SizeF factor, BoundsSpecified specified)
215         {
216             base.ScaleControl(factor, specified);
217             OTBaseForm.ScaleChildControl(this.scrollBar, factor);
218         }
219
220         private void scrollBar_ValueChanged(object sender, EventArgs e)
221         {
222             using (ControlTransaction.Layout(this, false))
223             {
224                 var value = this.scrollBar.Value;
225                 for (var i = 0; i < this.pictureBox.Count; i++)
226                 {
227                     this.pictureBox[i].Visible = (i == value);
228                 }
229             }
230         }
231
232         private void pictureBox_MouseWheel(object sender, MouseEventArgs e)
233         {
234             if (e.Delta > 0)
235                 this.ScrollUp();
236             else
237                 this.ScrollDown();
238         }
239
240         private void pictureBox_DoubleClick(object sender, EventArgs e)
241         {
242             var thumb = ((PictureBox)sender).Tag as ThumbnailInfo;
243
244             if (thumb == null) return;
245
246             this.ThumbnailDoubleClick?.Invoke(this, new ThumbnailDoubleClickEventArgs(thumb));
247         }
248
249         private void contextMenuStrip_Opening(object sender, CancelEventArgs e)
250         {
251             var picbox = (OTPictureBox)this.contextMenuStrip.SourceControl;
252             var thumb = (ThumbnailInfo)picbox.Tag;
253
254             var searchTargetUri = thumb.FullSizeImageUrl ?? thumb.ThumbnailImageUrl ?? null;
255             if (searchTargetUri != null)
256             {
257                 this.searchImageGoogleMenuItem.Enabled = true;
258                 this.searchImageGoogleMenuItem.Tag = searchTargetUri;
259                 this.searchImageSauceNaoMenuItem.Enabled = true;
260                 this.searchImageSauceNaoMenuItem.Tag = searchTargetUri;
261             }
262             else
263             {
264                 this.searchImageGoogleMenuItem.Enabled = false;
265                 this.searchImageSauceNaoMenuItem.Enabled = false;
266             }
267         }
268
269         private void searchSimilarImageMenuItem_Click(object sender, EventArgs e)
270         {
271             var searchTargetUri = (string)this.searchImageGoogleMenuItem.Tag;
272             var searchUri = this.GetImageSearchUriGoogle(searchTargetUri);
273
274             this.ThumbnailImageSearchClick?.Invoke(this, new ThumbnailImageSearchEventArgs(searchUri));
275         }
276
277         private void searchImageSauceNaoMenuItem_Click(object sender, EventArgs e)
278         {
279             var searchTargetUri = (string)this.searchImageSauceNaoMenuItem.Tag;
280             var searchUri = this.GetImageSearchUriSauceNao(searchTargetUri);
281
282             this.ThumbnailImageSearchClick?.Invoke(this, new ThumbnailImageSearchEventArgs(searchUri));
283         }
284     }
285
286     public class ThumbnailDoubleClickEventArgs : EventArgs
287     {
288         public ThumbnailInfo Thumbnail { get; }
289
290         public ThumbnailDoubleClickEventArgs(ThumbnailInfo thumbnail)
291         {
292             this.Thumbnail = thumbnail;
293         }
294     }
295
296     public class ThumbnailImageSearchEventArgs : EventArgs
297     {
298         public string ImageUrl { get; }
299
300         public ThumbnailImageSearchEventArgs(string url)
301         {
302             this.ImageUrl = url;
303         }
304     }
305 }