OSDN Git Service

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