1 // OpenTween - Client of Twitter
2 // Copyright (c) 2012 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3 // All rights reserved.
5 // This file is part of OpenTween.
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)
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
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.
25 using System.Collections.Generic;
26 using System.ComponentModel;
27 using System.Diagnostics.CodeAnalysis;
32 using System.Net.Http;
34 using System.Windows.Forms;
35 using System.Text.RegularExpressions;
36 using System.Threading.Tasks;
37 using OpenTween.Thumbnail;
38 using System.Threading;
39 using OpenTween.Models;
40 using System.Runtime.InteropServices;
44 public partial class TweetThumbnail : UserControl
46 protected internal List<OTPictureBox> pictureBox = new List<OTPictureBox>();
47 protected MouseWheelMessageFilter filter = new MouseWheelMessageFilter();
49 public event EventHandler<EventArgs> ThumbnailLoading;
50 public event EventHandler<ThumbnailDoubleClickEventArgs> ThumbnailDoubleClick;
51 public event EventHandler<ThumbnailImageSearchEventArgs> ThumbnailImageSearchClick;
53 public ThumbnailInfo Thumbnail
54 => (ThumbnailInfo)this.pictureBox[this.scrollBar.Value].Tag;
56 public TweetThumbnail()
57 => this.InitializeComponent();
59 public Task ShowThumbnailAsync(PostClass post)
60 => this.ShowThumbnailAsync(post, CancellationToken.None);
62 public async Task ShowThumbnailAsync(PostClass post, CancellationToken cancelToken)
64 var loadTasks = new List<Task>();
66 this.scrollBar.Enabled = false;
67 this.scrollBar.Visible = false;
69 if (post.ExpandedUrls.Length == 0 && post.Media.Count == 0 && post.PostGeo == null)
71 this.SetThumbnailCount(0);
75 var thumbnails = (await this.GetThumbailInfoAsync(post, cancelToken))
78 cancelToken.ThrowIfCancellationRequested();
80 this.SetThumbnailCount(thumbnails.Length);
81 if (thumbnails.Length == 0)
84 for (var i = 0; i < thumbnails.Length; i++)
86 var thumb = thumbnails[i];
87 var picbox = this.pictureBox[i];
90 picbox.ContextMenuStrip = this.contextMenuStrip;
92 var loadTask = picbox.SetImageFromTask(() => thumb.LoadThumbnailImageAsync(cancelToken));
93 loadTasks.Add(loadTask);
95 var tooltipText = thumb.TooltipText;
96 if (!MyCommon.IsNullOrEmpty(tooltipText))
98 this.toolTip.SetToolTip(picbox, tooltipText);
99 picbox.AccessibleDescription = tooltipText;
102 cancelToken.ThrowIfCancellationRequested();
105 if (thumbnails.Length > 1)
107 this.scrollBar.Enabled = true;
108 this.scrollBar.Visible = true;
111 this.ThumbnailLoading?.Invoke(this, EventArgs.Empty);
113 await Task.WhenAll(loadTasks).ConfigureAwait(false);
116 private string GetImageSearchUriGoogle(string image_uri)
117 => @"https://www.google.com/searchbyimage?image_url=" + Uri.EscapeDataString(image_uri);
119 private string GetImageSearchUriSauceNao(string imageUri)
120 => @"https://saucenao.com/search.php?url=" + Uri.EscapeDataString(imageUri);
122 protected virtual Task<IEnumerable<ThumbnailInfo>> GetThumbailInfoAsync(PostClass post, CancellationToken token)
123 => ThumbnailGenerator.GetThumbnailsAsync(post, token);
128 /// <param name="count">表示するサムネイルの数</param>
129 protected void SetThumbnailCount(int count)
131 if (count == 0 && this.pictureBox.Count == 0)
134 using (ControlTransaction.Layout(this.panelPictureBox, false))
136 this.panelPictureBox.Controls.Clear();
137 foreach (var picbox in this.pictureBox)
139 var memoryImage = picbox.Image;
141 filter.Unregister(picbox);
143 picbox.MouseWheel -= this.pictureBox_MouseWheel;
144 picbox.DoubleClick -= this.pictureBox_DoubleClick;
147 memoryImage?.Dispose();
149 // メモリリーク対策 (http://stackoverflow.com/questions/2792427#2793714)
150 picbox.ContextMenuStrip = null;
152 this.pictureBox.Clear();
154 this.scrollBar.Maximum = (count > 0) ? count - 1 : 0;
155 this.scrollBar.Value = 0;
157 for (var i = 0; i < count; i++)
159 var picbox = CreatePictureBox("pictureBox" + i);
160 picbox.Visible = (i == 0);
161 picbox.MouseWheel += this.pictureBox_MouseWheel;
162 picbox.DoubleClick += this.pictureBox_DoubleClick;
164 filter.Register(picbox);
166 this.panelPictureBox.Controls.Add(picbox);
167 this.pictureBox.Add(picbox);
172 [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
173 protected virtual OTPictureBox CreatePictureBox(string name)
175 return new OTPictureBox
178 SizeMode = PictureBoxSizeMode.Zoom,
180 Dock = DockStyle.Fill,
181 AccessibleRole = AccessibleRole.Graphic,
185 public void OpenImage(ThumbnailInfo thumb)
186 => this.ThumbnailDoubleClick?.Invoke(this, new ThumbnailDoubleClickEventArgs(thumb));
188 public void ScrollUp()
190 var newval = this.scrollBar.Value - this.scrollBar.SmallChange;
192 if (newval < this.scrollBar.Minimum)
193 newval = this.scrollBar.Minimum;
195 this.scrollBar.Value = newval;
198 public void ScrollDown()
200 var newval = this.scrollBar.Value + this.scrollBar.SmallChange;
202 if (newval > this.scrollBar.Maximum)
203 newval = this.scrollBar.Maximum;
205 this.scrollBar.Value = newval;
208 protected override void ScaleControl(SizeF factor, BoundsSpecified specified)
210 base.ScaleControl(factor, specified);
211 OTBaseForm.ScaleChildControl(this.scrollBar, factor);
214 private void scrollBar_ValueChanged(object sender, EventArgs e)
216 using (ControlTransaction.Layout(this, false))
218 var value = this.scrollBar.Value;
219 for (var i = 0; i < this.pictureBox.Count; i++)
221 this.pictureBox[i].Visible = (i == value);
226 private void pictureBox_MouseWheel(object sender, MouseEventArgs e)
234 private void pictureBox_DoubleClick(object sender, EventArgs e)
236 if (((PictureBox)sender).Tag is ThumbnailInfo thumb)
237 this.OpenImage(thumb);
240 private void contextMenuStrip_Opening(object sender, CancelEventArgs e)
242 var picbox = (OTPictureBox)this.contextMenuStrip.SourceControl;
243 var thumb = (ThumbnailInfo)picbox.Tag;
245 var searchTargetUri = thumb.FullSizeImageUrl ?? thumb.ThumbnailImageUrl ?? null;
246 if (searchTargetUri != null)
248 this.searchImageGoogleMenuItem.Enabled = true;
249 this.searchImageGoogleMenuItem.Tag = searchTargetUri;
250 this.searchImageSauceNaoMenuItem.Enabled = true;
251 this.searchImageSauceNaoMenuItem.Tag = searchTargetUri;
255 this.searchImageGoogleMenuItem.Enabled = false;
256 this.searchImageSauceNaoMenuItem.Enabled = false;
260 private void searchSimilarImageMenuItem_Click(object sender, EventArgs e)
262 var searchTargetUri = (string)this.searchImageGoogleMenuItem.Tag;
263 var searchUri = this.GetImageSearchUriGoogle(searchTargetUri);
265 this.ThumbnailImageSearchClick?.Invoke(this, new ThumbnailImageSearchEventArgs(searchUri));
268 private void searchImageSauceNaoMenuItem_Click(object sender, EventArgs e)
270 var searchTargetUri = (string)this.searchImageSauceNaoMenuItem.Tag;
271 var searchUri = this.GetImageSearchUriSauceNao(searchTargetUri);
273 this.ThumbnailImageSearchClick?.Invoke(this, new ThumbnailImageSearchEventArgs(searchUri));
276 private void openMenuItem_Click(object sender, EventArgs e)
277 => this.OpenImage(this.Thumbnail);
279 private void copyUrlMenuItem_Click(object sender, EventArgs e)
283 Clipboard.SetText(this.Thumbnail.FullSizeImageUrl ?? this.Thumbnail.MediaPageUrl);
285 catch (ExternalException ex)
287 MessageBox.Show(ex.Message);
292 public class ThumbnailDoubleClickEventArgs : EventArgs
294 public ThumbnailInfo Thumbnail { get; }
296 public ThumbnailDoubleClickEventArgs(ThumbnailInfo thumbnail)
297 => this.Thumbnail = thumbnail;
300 public class ThumbnailImageSearchEventArgs : EventArgs
302 public string ImageUrl { get; }
304 public ThumbnailImageSearchEventArgs(string url)
305 => this.ImageUrl = url;