OSDN Git Service

バージョン v2.4.2-dev 開発開始
[opentween/open-tween.git] / OpenTween / UserInfoDialog.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2007-2011 kiri_feather (@kiri_feather) <kiri.feather@gmail.com>
3 //           (c) 2008-2011 Moz (@syo68k)
4 //           (c) 2008-2011 takeshik (@takeshik) <http://www.takeshik.org/>
5 //           (c) 2010-2011 anis774 (@anis774) <http://d.hatena.ne.jp/anis774/>
6 //           (c) 2010-2011 fantasticswallow (@f_swallow) <http://twitter.com/f_swallow>
7 //           (c) 2011      kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
8 // All rights reserved.
9 // 
10 // This file is part of OpenTween.
11 // 
12 // This program is free software; you can redistribute it and/or modify it
13 // under the terms of the GNU General Public License as published by the Free
14 // Software Foundation; either version 3 of the License, or (at your option)
15 // any later version.
16 // 
17 // This program is distributed in the hope that it will be useful, but
18 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
19 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
20 // for more details. 
21 // 
22 // You should have received a copy of the GNU General Public License along
23 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
24 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
25 // Boston, MA 02110-1301, USA.
26
27 #nullable enable
28
29 using System;
30 using System.Collections.Generic;
31 using System.ComponentModel;
32 using System.Data;
33 using System.Drawing;
34 using System.Linq;
35 using System.Text;
36 using System.Threading;
37 using System.Threading.Tasks;
38 using System.Windows.Forms;
39 using System.Text.RegularExpressions;
40 using System.Web;
41 using System.IO;
42 using System.Net;
43 using OpenTween.Api;
44 using OpenTween.Api.DataModel;
45 using OpenTween.Connection;
46 using System.Diagnostics.CodeAnalysis;
47
48 namespace OpenTween
49 {
50     public partial class UserInfoDialog : OTBaseForm
51     {
52         private TwitterUser _displayUser = null!;
53         private CancellationTokenSource? cancellationTokenSource = null;
54
55         private readonly TweenMain mainForm;
56         private readonly TwitterApi twitterApi;
57
58         public UserInfoDialog(TweenMain mainForm, TwitterApi twitterApi)
59         {
60             this.mainForm = mainForm;
61             this.twitterApi = twitterApi;
62
63             InitializeComponent();
64
65             // LabelScreenName のフォントを OTBaseForm.GlobalFont に変更
66             this.LabelScreenName.Font = this.ReplaceToGlobalFont(this.LabelScreenName.Font);
67         }
68
69         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
70         private void CancelLoading()
71         {
72             CancellationTokenSource? oldTokenSource = null;
73             try
74             {
75                 var newTokenSource = new CancellationTokenSource();
76                 oldTokenSource = Interlocked.Exchange(ref this.cancellationTokenSource, newTokenSource);
77             }
78             finally
79             {
80                 if (oldTokenSource != null)
81                 {
82                     oldTokenSource.Cancel();
83                     oldTokenSource.Dispose();
84                 }
85             }
86         }
87
88         public async Task ShowUserAsync(TwitterUser user)
89         {
90             if (this.IsDisposed)
91                 return;
92
93             if (user == null || user == this._displayUser)
94                 return;
95
96             this.CancelLoading();
97
98             var cancellationToken = this.cancellationTokenSource!.Token;
99
100             this._displayUser = user;
101
102             this.LabelId.Text = user.IdStr;
103             this.LabelScreenName.Text = user.ScreenName;
104             this.LabelName.Text = user.Name;
105             this.LabelLocation.Text = user.Location ?? "";
106             this.LabelCreatedAt.Text = MyCommon.DateTimeParse(user.CreatedAt).ToLocalTimeString();
107
108             if (user.Protected)
109                 this.LabelIsProtected.Text = Properties.Resources.Yes;
110             else
111                 this.LabelIsProtected.Text = Properties.Resources.No;
112
113             if (user.Verified)
114                 this.LabelIsVerified.Text = Properties.Resources.Yes;
115             else
116                 this.LabelIsVerified.Text = Properties.Resources.No;
117
118             var followingUrl = "https://twitter.com/" + user.ScreenName + "/following";
119             this.LinkLabelFollowing.Text = user.FriendsCount.ToString();
120             this.LinkLabelFollowing.Tag = followingUrl;
121             this.ToolTip1.SetToolTip(this.LinkLabelFollowing, followingUrl);
122
123             var followersUrl = "https://twitter.com/" + user.ScreenName + "/followers";
124             this.LinkLabelFollowers.Text = user.FollowersCount.ToString();
125             this.LinkLabelFollowers.Tag = followersUrl;
126             this.ToolTip1.SetToolTip(this.LinkLabelFollowers, followersUrl);
127
128             var favoritesUrl = "https://twitter.com/" + user.ScreenName + "/favorites";
129             this.LinkLabelFav.Text = user.FavouritesCount.ToString();
130             this.LinkLabelFav.Tag = favoritesUrl;
131             this.ToolTip1.SetToolTip(this.LinkLabelFav, favoritesUrl);
132
133             var profileUrl = "https://twitter.com/" + user.ScreenName;
134             this.LinkLabelTweet.Text = user.StatusesCount.ToString();
135             this.LinkLabelTweet.Tag = profileUrl;
136             this.ToolTip1.SetToolTip(this.LinkLabelTweet, profileUrl);
137
138             if (this.twitterApi.CurrentUserId == user.Id)
139             {
140                 this.ButtonEdit.Enabled = true;
141                 this.ChangeIconToolStripMenuItem.Enabled = true;
142                 this.ButtonBlock.Enabled = false;
143                 this.ButtonReportSpam.Enabled = false;
144                 this.ButtonBlockDestroy.Enabled = false;
145             }
146             else
147             {
148                 this.ButtonEdit.Enabled = false;
149                 this.ChangeIconToolStripMenuItem.Enabled = false;
150                 this.ButtonBlock.Enabled = true;
151                 this.ButtonReportSpam.Enabled = true;
152                 this.ButtonBlockDestroy.Enabled = true;
153             }
154
155             await Task.WhenAll(new[]
156             {
157                 this.SetDescriptionAsync(user.Description, user.Entities?.Description, cancellationToken),
158                 this.SetRecentStatusAsync(user.Status, cancellationToken),
159                 this.SetLinkLabelWebAsync(user.Url, user.Entities?.Url, cancellationToken),
160                 this.SetUserImageAsync(user.ProfileImageUrlHttps, cancellationToken),
161                 this.LoadFriendshipAsync(user.ScreenName, cancellationToken),
162             });
163         }
164
165         private async Task SetDescriptionAsync(string? descriptionText, TwitterEntities? entities, CancellationToken cancellationToken)
166         {
167             if (descriptionText != null)
168             {
169                 var urlEntities = entities?.Urls ?? Array.Empty<TwitterEntityUrl>();
170
171                 foreach (var entity in urlEntities)
172                     entity.ExpandedUrl = await ShortUrl.Instance.ExpandUrlAsync(entity.ExpandedUrl);
173
174                 // user.entities には urls 以外のエンティティが含まれていないため、テキストをもとに生成する
175                 var mergedEntities = urlEntities.AsEnumerable<TwitterEntity>()
176                     .Concat(TweetExtractor.ExtractHashtagEntities(descriptionText))
177                     .Concat(TweetExtractor.ExtractMentionEntities(descriptionText))
178                     .Concat(TweetExtractor.ExtractEmojiEntities(descriptionText));
179
180                 var html = TweetFormatter.AutoLinkHtml(descriptionText, mergedEntities);
181                 html = this.mainForm.createDetailHtml(html);
182
183                 if (cancellationToken.IsCancellationRequested)
184                     return;
185
186                 this.DescriptionBrowser.DocumentText = html;
187             }
188             else
189             {
190                 this.DescriptionBrowser.DocumentText = "";
191             }
192         }
193
194         private async Task SetUserImageAsync(string imageUri, CancellationToken cancellationToken)
195         {
196             var oldImage = this.UserPicture.Image;
197             this.UserPicture.Image = null;
198             oldImage?.Dispose();
199
200             // ProfileImageUrlHttps が null になる場合があるらしい
201             // 参照: https://sourceforge.jp/ticket/browse.php?group_id=6526&tid=33871
202             if (imageUri == null)
203                 return;
204
205             await this.UserPicture.SetImageFromTask(async () =>
206             {
207                 var uri = imageUri.Replace("_normal", "_bigger");
208
209                 using var imageStream = await Networking.Http.GetStreamAsync(uri)
210                     .ConfigureAwait(false);
211
212                 var image = await MemoryImage.CopyFromStreamAsync(imageStream)
213                     .ConfigureAwait(false);
214
215                 if (cancellationToken.IsCancellationRequested)
216                 {
217                     image.Dispose();
218                     throw new OperationCanceledException(cancellationToken);
219                 }
220
221                 return image;
222             });
223         }
224
225         private async Task SetLinkLabelWebAsync(string? uri, TwitterEntities? entities, CancellationToken cancellationToken)
226         {
227             if (uri != null)
228             {
229                 var origUrl = entities?.Urls?.FirstOrDefault()?.ExpandedUrl ?? uri;
230                 var expandedUrl = await ShortUrl.Instance.ExpandUrlAsync(origUrl);
231
232                 if (cancellationToken.IsCancellationRequested)
233                     return;
234
235                 this.LinkLabelWeb.Text = expandedUrl;
236                 this.LinkLabelWeb.Tag = expandedUrl;
237                 this.ToolTip1.SetToolTip(this.LinkLabelWeb, expandedUrl);
238             }
239             else
240             {
241                 this.LinkLabelWeb.Text = "";
242                 this.LinkLabelWeb.Tag = null;
243                 this.ToolTip1.SetToolTip(this.LinkLabelWeb, null);
244             }
245         }
246
247         private async Task SetRecentStatusAsync(TwitterStatus? status, CancellationToken cancellationToken)
248         {
249             if (status != null)
250             {
251                 var entities = status.MergedEntities;
252                 var urlEntities = entities.Urls ?? Array.Empty<TwitterEntityUrl>();
253
254                 foreach (var entity in urlEntities)
255                     entity.ExpandedUrl = await ShortUrl.Instance.ExpandUrlAsync(entity.ExpandedUrl);
256
257                 var mergedEntities = entities.Concat(TweetExtractor.ExtractEmojiEntities(status.FullText));
258
259                 var html = TweetFormatter.AutoLinkHtml(status.FullText, mergedEntities);
260                 html = this.mainForm.createDetailHtml(html +
261                     " Posted at " + MyCommon.DateTimeParse(status.CreatedAt).ToLocalTimeString() +
262                     " via " + status.Source);
263
264                 if (cancellationToken.IsCancellationRequested)
265                     return;
266
267                 this.RecentPostBrowser.DocumentText = html;
268             }
269             else
270             {
271                 this.RecentPostBrowser.DocumentText = Properties.Resources.ShowUserInfo2;
272             }
273         }
274
275         private async Task LoadFriendshipAsync(string screenName, CancellationToken cancellationToken)
276         {
277             this.LabelIsFollowing.Text = "";
278             this.LabelIsFollowed.Text = "";
279             this.ButtonFollow.Enabled = false;
280             this.ButtonUnFollow.Enabled = false;
281
282             if (this.twitterApi.CurrentScreenName == screenName)
283                 return;
284
285             TwitterFriendship friendship;
286             try
287             {
288                 friendship = await this.twitterApi.FriendshipsShow(this.twitterApi.CurrentScreenName, screenName);
289             }
290             catch (WebApiException)
291             {
292                 LabelIsFollowed.Text = Properties.Resources.GetFriendshipInfo6;
293                 LabelIsFollowing.Text = Properties.Resources.GetFriendshipInfo6;
294                 return;
295             }
296
297             if (cancellationToken.IsCancellationRequested)
298                 return;
299
300             var isFollowing = friendship.Relationship.Source.Following;
301             var isFollowedBy = friendship.Relationship.Source.FollowedBy;
302
303             this.LabelIsFollowing.Text = isFollowing
304                 ? Properties.Resources.GetFriendshipInfo1
305                 : Properties.Resources.GetFriendshipInfo2;
306
307             this.LabelIsFollowed.Text = isFollowedBy
308                 ? Properties.Resources.GetFriendshipInfo3
309                 : Properties.Resources.GetFriendshipInfo4;
310
311             this.ButtonFollow.Enabled = !isFollowing;
312             this.ButtonUnFollow.Enabled = isFollowing;
313         }
314
315         private void ShowUserInfo_Load(object sender, EventArgs e)
316         {
317             this.TextBoxName.Location = this.LabelName.Location;
318             this.TextBoxName.Height = this.LabelName.Height;
319             this.TextBoxName.Width = this.LabelName.Width;
320             this.TextBoxName.BackColor = this.mainForm.InputBackColor;
321
322             this.TextBoxLocation.Location = this.LabelLocation.Location;
323             this.TextBoxLocation.Height = this.LabelLocation.Height;
324             this.TextBoxLocation.Width = this.LabelLocation.Width;
325             this.TextBoxLocation.BackColor = this.mainForm.InputBackColor;
326
327             this.TextBoxWeb.Location = this.LinkLabelWeb.Location;
328             this.TextBoxWeb.Height = this.LinkLabelWeb.Height;
329             this.TextBoxWeb.Width = this.LinkLabelWeb.Width;
330             this.TextBoxWeb.BackColor = this.mainForm.InputBackColor;
331
332             this.TextBoxDescription.Location = this.DescriptionBrowser.Location;
333             this.TextBoxDescription.Height = this.DescriptionBrowser.Height;
334             this.TextBoxDescription.Width = this.DescriptionBrowser.Width;
335             this.TextBoxDescription.BackColor = this.mainForm.InputBackColor;
336             this.TextBoxDescription.Multiline = true;
337             this.TextBoxDescription.ScrollBars = ScrollBars.Vertical;
338         }
339
340         private async void LinkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
341         {
342             var linkLabel = (LinkLabel)sender;
343
344             var linkUrl = (string)linkLabel.Tag;
345             if (linkUrl == null)
346                 return;
347
348             await this.mainForm.OpenUriInBrowserAsync(linkUrl);
349         }
350
351         private async void ButtonFollow_Click(object sender, EventArgs e)
352         {
353             using (ControlTransaction.Disabled(this.ButtonFollow))
354             {
355                 try
356                 {
357                     await this.twitterApi.FriendshipsCreate(this._displayUser.ScreenName)
358                         .IgnoreResponse();
359                 }
360                 catch (WebApiException ex)
361                 {
362                     MessageBox.Show(Properties.Resources.FRMessage2 + ex.Message);
363                     return;
364                 }
365             }
366
367             MessageBox.Show(Properties.Resources.FRMessage3);
368             LabelIsFollowing.Text = Properties.Resources.GetFriendshipInfo1;
369             ButtonFollow.Enabled = false;
370             ButtonUnFollow.Enabled = true;
371         }
372
373         private async void ButtonUnFollow_Click(object sender, EventArgs e)
374         {
375             if (MessageBox.Show(this._displayUser.ScreenName + Properties.Resources.ButtonUnFollow_ClickText1,
376                                Properties.Resources.ButtonUnFollow_ClickText2,
377                                MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes)
378             {
379                 using (ControlTransaction.Disabled(this.ButtonUnFollow))
380                 {
381                     try
382                     {
383                         await this.twitterApi.FriendshipsDestroy(this._displayUser.ScreenName)
384                             .IgnoreResponse();
385                     }
386                     catch (WebApiException ex)
387                     {
388                         MessageBox.Show(Properties.Resources.FRMessage2 + ex.Message);
389                         return;
390                     }
391                 }
392
393                 MessageBox.Show(Properties.Resources.FRMessage3);
394                 LabelIsFollowing.Text = Properties.Resources.GetFriendshipInfo2;
395                 ButtonFollow.Enabled = true;
396                 ButtonUnFollow.Enabled = false;
397             }
398         }
399
400         private void ShowUserInfo_Activated(object sender, EventArgs e)
401         {
402             //画面が他画面の裏に隠れると、アイコン画像が再描画されない問題の対応
403             if (UserPicture.Image != null)
404                 UserPicture.Invalidate(false);
405         }
406
407         private void ShowUserInfo_Shown(object sender, EventArgs e)
408             => this.ButtonClose.Focus();
409
410         private async void WebBrowser_Navigating(object sender, WebBrowserNavigatingEventArgs e)
411         {
412             if (e.Url.AbsoluteUri != "about:blank")
413             {
414                 e.Cancel = true;
415
416                 // Ctrlを押しながらリンクを開いた場合は、設定と逆の動作をするフラグを true としておく
417                 await this.mainForm.OpenUriAsync(e.Url, MyCommon.IsKeyDown(Keys.Control));
418             }
419         }
420
421         private void SelectAllToolStripMenuItem_Click(object sender, EventArgs e)
422         {
423             var browser = (WebBrowser)this.ContextMenuRecentPostBrowser.SourceControl;
424             browser.Document.ExecCommand("SelectAll", false, null);
425         }
426
427         private void SelectionCopyToolStripMenuItem_Click(object sender, EventArgs e)
428         {
429             var browser = (WebBrowser)this.ContextMenuRecentPostBrowser.SourceControl;
430             var selectedText = browser.GetSelectedText();
431             if (selectedText != null)
432             {
433                 try
434                 {
435                     Clipboard.SetDataObject(selectedText, false, 5, 100);
436                 }
437                 catch (Exception ex)
438                 {
439                     MessageBox.Show(ex.Message);
440                 }
441             }
442         }
443
444         private void ContextMenuRecentPostBrowser_Opening(object sender, CancelEventArgs e)
445         {
446             var browser = (WebBrowser)this.ContextMenuRecentPostBrowser.SourceControl;
447             var selectedText = browser.GetSelectedText();
448
449             this.SelectionCopyToolStripMenuItem.Enabled = selectedText != null;
450         }
451
452         private async void LinkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
453             => await this.mainForm.OpenUriInBrowserAsync("https://support.twitter.com/groups/31-twitter-basics/topics/111-features/articles/268350-x8a8d-x8a3c-x6e08-x307f-x30a2-x30ab-x30a6-x30f3-x30c8-x306b-x3064-x3044-x3066");
454
455         private async void LinkLabel2_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
456             => await this.mainForm.OpenUriInBrowserAsync("https://support.twitter.com/groups/31-twitter-basics/topics/107-my-profile-account-settings/articles/243055-x516c-x958b-x3001-x975e-x516c-x958b-x30a2-x30ab-x30a6-x30f3-x30c8-x306b-x3064-x3044-x3066");
457
458         private async void ButtonSearchPosts_Click(object sender, EventArgs e)
459             => await this.mainForm.AddNewTabForUserTimeline(this._displayUser.ScreenName);
460
461         private async void UserPicture_Click(object sender, EventArgs e)
462         {
463             var imageUrl = this._displayUser.ProfileImageUrlHttps;
464             imageUrl = imageUrl.Remove(imageUrl.LastIndexOf("_normal", StringComparison.Ordinal), 7);
465
466             await this.mainForm.OpenUriInBrowserAsync(imageUrl);
467         }
468
469         private bool IsEditing = false;
470         private string ButtonEditText = "";
471
472         private async void ButtonEdit_Click(object sender, EventArgs e)
473         {
474             // 自分以外のプロフィールは変更できない
475             if (this.twitterApi.CurrentUserId != this._displayUser.Id)
476                 return;
477
478             using (ControlTransaction.Disabled(this.ButtonEdit))
479             {
480                 if (!IsEditing)
481                 {
482                     ButtonEditText = ButtonEdit.Text;
483                     ButtonEdit.Text = Properties.Resources.UserInfoButtonEdit_ClickText1;
484
485                     TextBoxName.Text = LabelName.Text;
486                     TextBoxName.Enabled = true;
487                     TextBoxName.Visible = true;
488                     LabelName.Visible = false;
489
490                     TextBoxLocation.Text = LabelLocation.Text;
491                     TextBoxLocation.Enabled = true;
492                     TextBoxLocation.Visible = true;
493                     LabelLocation.Visible = false;
494
495                     TextBoxWeb.Text = this._displayUser.Url;
496                     TextBoxWeb.Enabled = true;
497                     TextBoxWeb.Visible = true;
498                     LinkLabelWeb.Visible = false;
499
500                     TextBoxDescription.Text = this._displayUser.Description;
501                     TextBoxDescription.Enabled = true;
502                     TextBoxDescription.Visible = true;
503                     DescriptionBrowser.Visible = false;
504
505                     TextBoxName.Focus();
506                     TextBoxName.Select(TextBoxName.Text.Length, 0);
507
508                     IsEditing = true;
509                 }
510                 else
511                 {
512                     Task? showUserTask = null;
513
514                     if (TextBoxName.Modified ||
515                         TextBoxLocation.Modified ||
516                         TextBoxWeb.Modified ||
517                         TextBoxDescription.Modified)
518                     {
519                         try
520                         {
521                             var response = await this.twitterApi.AccountUpdateProfile(
522                                 this.TextBoxName.Text,
523                                 this.TextBoxWeb.Text,
524                                 this.TextBoxLocation.Text,
525                                 this.TextBoxDescription.Text);
526
527                             var user = await response.LoadJsonAsync();
528                             showUserTask = this.ShowUserAsync(user);
529                         }
530                         catch (WebApiException ex)
531                         {
532                             MessageBox.Show($"Err:{ex.Message}(AccountUpdateProfile)");
533                             return;
534                         }
535                     }
536
537                     TextBoxName.Enabled = false;
538                     TextBoxName.Visible = false;
539                     LabelName.Visible = true;
540
541                     TextBoxLocation.Enabled = false;
542                     TextBoxLocation.Visible = false;
543                     LabelLocation.Visible = true;
544
545                     TextBoxWeb.Enabled = false;
546                     TextBoxWeb.Visible = false;
547                     LinkLabelWeb.Visible = true;
548
549                     TextBoxDescription.Enabled = false;
550                     TextBoxDescription.Visible = false;
551                     DescriptionBrowser.Visible = true;
552
553                     ButtonEdit.Text = ButtonEditText;
554
555                     IsEditing = false;
556
557                     if (showUserTask != null)
558                         await showUserTask;
559                 }
560             }
561         }
562
563         private async Task DoChangeIcon(string filename)
564         {
565             try
566             {
567                 var mediaItem = new FileMediaItem(filename);
568
569                 await this.twitterApi.AccountUpdateProfileImage(mediaItem)
570                     .IgnoreResponse();
571             }
572             catch (WebApiException ex)
573             {
574                 MessageBox.Show("Err:" + ex.Message + Environment.NewLine + Properties.Resources.ChangeIconToolStripMenuItem_ClickText4);
575                 return;
576             }
577
578             MessageBox.Show(Properties.Resources.ChangeIconToolStripMenuItem_ClickText5);
579
580             try
581             {
582                 var user = await this.twitterApi.UsersShow(this._displayUser.ScreenName);
583
584                 if (user != null)
585                     await this.ShowUserAsync(user);
586             }
587             catch (WebApiException ex)
588             {
589                 MessageBox.Show($"Err:{ex.Message}(UsersShow)");
590             }
591         }
592
593         private async void ChangeIconToolStripMenuItem_Click(object sender, EventArgs e)
594         {
595             OpenFileDialogIcon.Filter = Properties.Resources.ChangeIconToolStripMenuItem_ClickText1;
596             OpenFileDialogIcon.Title = Properties.Resources.ChangeIconToolStripMenuItem_ClickText2;
597             OpenFileDialogIcon.FileName = "";
598
599             var rslt = OpenFileDialogIcon.ShowDialog();
600
601             if (rslt != DialogResult.OK)
602             {
603                 return;
604             }
605
606             var fn = OpenFileDialogIcon.FileName;
607             if (this.IsValidIconFile(new FileInfo(fn)))
608             {
609                 await this.DoChangeIcon(fn);
610             }
611             else
612             {
613                 MessageBox.Show(Properties.Resources.ChangeIconToolStripMenuItem_ClickText6);
614             }
615         }
616
617         private async void ButtonBlock_Click(object sender, EventArgs e)
618         {
619             if (MessageBox.Show(this._displayUser.ScreenName + Properties.Resources.ButtonBlock_ClickText1,
620                                 Properties.Resources.ButtonBlock_ClickText2,
621                                 MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes)
622             {
623                 using (ControlTransaction.Disabled(this.ButtonBlock))
624                 {
625                     try
626                     {
627                         await this.twitterApi.BlocksCreate(this._displayUser.ScreenName)
628                             .IgnoreResponse();
629                     }
630                     catch (WebApiException ex)
631                     {
632                         MessageBox.Show("Err:" + ex.Message + Environment.NewLine + Properties.Resources.ButtonBlock_ClickText3);
633                         return;
634                     }
635
636                     MessageBox.Show(Properties.Resources.ButtonBlock_ClickText4);
637                 }
638             }
639         }
640
641         private async void ButtonReportSpam_Click(object sender, EventArgs e)
642         {
643             if (MessageBox.Show(this._displayUser.ScreenName + Properties.Resources.ButtonReportSpam_ClickText1,
644                                 Properties.Resources.ButtonReportSpam_ClickText2,
645                                 MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes)
646             {
647                 using (ControlTransaction.Disabled(this.ButtonReportSpam))
648                 {
649                     try
650                     {
651                         await this.twitterApi.UsersReportSpam(this._displayUser.ScreenName)
652                             .IgnoreResponse();
653                     }
654                     catch (WebApiException ex)
655                     {
656                         MessageBox.Show("Err:" + ex.Message + Environment.NewLine + Properties.Resources.ButtonReportSpam_ClickText3);
657                         return;
658                     }
659
660                     MessageBox.Show(Properties.Resources.ButtonReportSpam_ClickText4);
661                 }
662             }
663         }
664
665         private async void ButtonBlockDestroy_Click(object sender, EventArgs e)
666         {
667             if (MessageBox.Show(this._displayUser.ScreenName + Properties.Resources.ButtonBlockDestroy_ClickText1,
668                                 Properties.Resources.ButtonBlockDestroy_ClickText2,
669                                 MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes)
670             {
671                 using (ControlTransaction.Disabled(this.ButtonBlockDestroy))
672                 {
673                     try
674                     {
675                         await this.twitterApi.BlocksDestroy(this._displayUser.ScreenName)
676                             .IgnoreResponse();
677                     }
678                     catch (WebApiException ex)
679                     {
680                         MessageBox.Show("Err:" + ex.Message + Environment.NewLine + Properties.Resources.ButtonBlockDestroy_ClickText3);
681                         return;
682                     }
683
684                     MessageBox.Show(Properties.Resources.ButtonBlockDestroy_ClickText4);
685                 }
686             }
687         }
688
689         private bool IsValidExtension(string ext)
690         {
691             var allowedExtensions = new[] { ".jpg", ".jpeg", ".png", ".gif" };
692
693             return allowedExtensions.Contains(ext, StringComparer.InvariantCultureIgnoreCase);
694         }
695
696         private bool IsValidIconFile(FileInfo info)
697         {
698             return this.IsValidExtension(info.Extension) &&
699                 info.Length < 700 * 1024 &&
700                 !MyCommon.IsAnimatedGif(info.FullName);
701         }
702
703         private void ShowUserInfo_DragEnter(object sender, DragEventArgs e)
704         {
705             if (e.Data.GetDataPresent(DataFormats.FileDrop) &&
706                 !e.Data.GetDataPresent(DataFormats.Html, false))  // WebBrowserコントロールからの絵文字画像D&Dは弾く
707             {
708                 var files = (string[])e.Data.GetData(DataFormats.FileDrop, false);
709                 if (files.Length != 1)
710                     return;
711
712                 var finfo = new FileInfo(files[0]);
713                 if (this.IsValidIconFile(finfo))
714                 {
715                     e.Effect = DragDropEffects.Copy;
716                 }
717             }
718         }
719
720         private async void ShowUserInfo_DragDrop(object sender, DragEventArgs e)
721         {
722             if (e.Data.GetDataPresent(DataFormats.FileDrop) &&
723                 !e.Data.GetDataPresent(DataFormats.Html, false))  // WebBrowserコントロールからの絵文字画像D&Dは弾く
724             {
725                 var ret = MessageBox.Show(this, Properties.Resources.ChangeIconToolStripMenuItem_Confirm,
726                     ApplicationSettings.ApplicationName, MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
727                 if (ret != DialogResult.OK)
728                     return;
729
730                 var filename = ((string[])e.Data.GetData(DataFormats.FileDrop, false))[0];
731                 await this.DoChangeIcon(filename);
732             }
733         }
734
735         protected override void Dispose(bool disposing)
736         {
737             if (disposing)
738             {
739                 var cts = this.cancellationTokenSource;
740                 cts?.Cancel();
741                 cts?.Dispose();
742
743                 var oldImage = this.UserPicture.Image;
744                 this.UserPicture.Image = null;
745                 oldImage?.Dispose();
746
747                 this.components?.Dispose();
748             }
749
750             base.Dispose(disposing);
751         }
752     }
753 }