OSDN Git Service

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