OSDN Git Service

c2ee6dc593338b35c13d84475dd7fb846874dd90
[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 newTokenSource = null, oldTokenSource = null;
71             try
72             {
73                 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).ToString();
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                 entities.Hashtags = TweetExtractor.ExtractHashtagEntities(descriptionText).ToArray();
172                 entities.UserMentions = TweetExtractor.ExtractMentionEntities(descriptionText).ToArray();
173
174                 var html = TweetFormatter.AutoLinkHtml(descriptionText, entities);
175                 html = this.mainForm.createDetailHtml(html);
176
177                 if (cancellationToken.IsCancellationRequested)
178                     return;
179
180                 this.DescriptionBrowser.DocumentText = html;
181             }
182             else
183             {
184                 this.DescriptionBrowser.DocumentText = "";
185             }
186         }
187
188         private async Task SetUserImageAsync(string imageUri, CancellationToken cancellationToken)
189         {
190             var oldImage = this.UserPicture.Image;
191             this.UserPicture.Image = null;
192             oldImage?.Dispose();
193
194             // ProfileImageUrlHttps が null になる場合があるらしい
195             // 参照: https://sourceforge.jp/ticket/browse.php?group_id=6526&tid=33871
196             if (imageUri == null)
197                 return;
198
199             await this.UserPicture.SetImageFromTask(async () =>
200             {
201                 var uri = imageUri.Replace("_normal", "_bigger");
202
203                 using (var imageStream = await Networking.Http.GetStreamAsync(uri).ConfigureAwait(false))
204                 {
205                     var image = await MemoryImage.CopyFromStreamAsync(imageStream)
206                         .ConfigureAwait(false);
207
208                     if (cancellationToken.IsCancellationRequested)
209                     {
210                         image.Dispose();
211                         throw new OperationCanceledException(cancellationToken);
212                     }
213
214                     return image;
215                 }
216             });
217         }
218
219         private async Task SetLinkLabelWebAsync(string uri, TwitterEntities entities, CancellationToken cancellationToken)
220         {
221             if (uri != null)
222             {
223                 var origUrl = entities?.Urls[0].ExpandedUrl ?? uri;
224                 var expandedUrl = await ShortUrl.Instance.ExpandUrlAsync(origUrl);
225
226                 if (cancellationToken.IsCancellationRequested)
227                     return;
228
229                 this.LinkLabelWeb.Text = expandedUrl;
230                 this.LinkLabelWeb.Tag = expandedUrl;
231                 this.ToolTip1.SetToolTip(this.LinkLabelWeb, expandedUrl);
232             }
233             else
234             {
235                 this.LinkLabelWeb.Text = "";
236                 this.LinkLabelWeb.Tag = null;
237                 this.ToolTip1.SetToolTip(this.LinkLabelWeb, null);
238             }
239         }
240
241         private async Task SetRecentStatusAsync(TwitterStatus status, CancellationToken cancellationToken)
242         {
243             if (status != null)
244             {
245                 var entities = status.MergedEntities;
246
247                 foreach (var entity in entities.Urls)
248                     entity.ExpandedUrl = await ShortUrl.Instance.ExpandUrlAsync(entity.ExpandedUrl);
249
250                 var html = TweetFormatter.AutoLinkHtml(status.FullText, entities);
251                 html = this.mainForm.createDetailHtml(html +
252                     " Posted at " + MyCommon.DateTimeParse(status.CreatedAt) +
253                     " via " + status.Source);
254
255                 if (cancellationToken.IsCancellationRequested)
256                     return;
257
258                 this.RecentPostBrowser.DocumentText = html;
259             }
260             else
261             {
262                 this.RecentPostBrowser.DocumentText = Properties.Resources.ShowUserInfo2;
263             }
264         }
265
266         private async Task LoadFriendshipAsync(string screenName, CancellationToken cancellationToken)
267         {
268             this.LabelIsFollowing.Text = "";
269             this.LabelIsFollowed.Text = "";
270             this.ButtonFollow.Enabled = false;
271             this.ButtonUnFollow.Enabled = false;
272
273             if (this.twitterApi.CurrentScreenName == screenName)
274                 return;
275
276             TwitterFriendship friendship;
277             try
278             {
279                 friendship = await this.twitterApi.FriendshipsShow(this.twitterApi.CurrentScreenName, screenName);
280             }
281             catch (WebApiException)
282             {
283                 LabelIsFollowed.Text = Properties.Resources.GetFriendshipInfo6;
284                 LabelIsFollowing.Text = Properties.Resources.GetFriendshipInfo6;
285                 return;
286             }
287
288             if (cancellationToken.IsCancellationRequested)
289                 return;
290
291             var isFollowing = friendship.Relationship.Source.Following;
292             var isFollowedBy = friendship.Relationship.Source.FollowedBy;
293
294             this.LabelIsFollowing.Text = isFollowing
295                 ? Properties.Resources.GetFriendshipInfo1
296                 : Properties.Resources.GetFriendshipInfo2;
297
298             this.LabelIsFollowed.Text = isFollowedBy
299                 ? Properties.Resources.GetFriendshipInfo3
300                 : Properties.Resources.GetFriendshipInfo4;
301
302             this.ButtonFollow.Enabled = !isFollowing;
303             this.ButtonUnFollow.Enabled = isFollowing;
304         }
305
306         private void ShowUserInfo_Load(object sender, EventArgs e)
307         {
308             this.TextBoxName.Location = this.LabelName.Location;
309             this.TextBoxName.Height = this.LabelName.Height;
310             this.TextBoxName.Width = this.LabelName.Width;
311             this.TextBoxName.BackColor = this.mainForm.InputBackColor;
312
313             this.TextBoxLocation.Location = this.LabelLocation.Location;
314             this.TextBoxLocation.Height = this.LabelLocation.Height;
315             this.TextBoxLocation.Width = this.LabelLocation.Width;
316             this.TextBoxLocation.BackColor = this.mainForm.InputBackColor;
317
318             this.TextBoxWeb.Location = this.LinkLabelWeb.Location;
319             this.TextBoxWeb.Height = this.LinkLabelWeb.Height;
320             this.TextBoxWeb.Width = this.LinkLabelWeb.Width;
321             this.TextBoxWeb.BackColor = this.mainForm.InputBackColor;
322
323             this.TextBoxDescription.Location = this.DescriptionBrowser.Location;
324             this.TextBoxDescription.Height = this.DescriptionBrowser.Height;
325             this.TextBoxDescription.Width = this.DescriptionBrowser.Width;
326             this.TextBoxDescription.BackColor = this.mainForm.InputBackColor;
327             this.TextBoxDescription.Multiline = true;
328             this.TextBoxDescription.ScrollBars = ScrollBars.Vertical;
329         }
330
331         private async void LinkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
332         {
333             var linkLabel = (LinkLabel)sender;
334
335             var linkUrl = (string)linkLabel.Tag;
336             if (linkUrl == null)
337                 return;
338
339             await this.mainForm.OpenUriInBrowserAsync(linkUrl);
340         }
341
342         private async void ButtonFollow_Click(object sender, EventArgs e)
343         {
344             using (ControlTransaction.Disabled(this.ButtonFollow))
345             {
346                 try
347                 {
348                     await this.twitterApi.FriendshipsCreate(this._displayUser.ScreenName)
349                         .IgnoreResponse();
350                 }
351                 catch (WebApiException ex)
352                 {
353                     MessageBox.Show(Properties.Resources.FRMessage2 + ex.Message);
354                     return;
355                 }
356             }
357
358             MessageBox.Show(Properties.Resources.FRMessage3);
359             LabelIsFollowing.Text = Properties.Resources.GetFriendshipInfo1;
360             ButtonFollow.Enabled = false;
361             ButtonUnFollow.Enabled = true;
362         }
363
364         private async void ButtonUnFollow_Click(object sender, EventArgs e)
365         {
366             if (MessageBox.Show(this._displayUser.ScreenName + Properties.Resources.ButtonUnFollow_ClickText1,
367                                Properties.Resources.ButtonUnFollow_ClickText2,
368                                MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes)
369             {
370                 using (ControlTransaction.Disabled(this.ButtonUnFollow))
371                 {
372                     try
373                     {
374                         await this.twitterApi.FriendshipsDestroy(this._displayUser.ScreenName)
375                             .IgnoreResponse();
376                     }
377                     catch (WebApiException ex)
378                     {
379                         MessageBox.Show(Properties.Resources.FRMessage2 + ex.Message);
380                         return;
381                     }
382                 }
383
384                 MessageBox.Show(Properties.Resources.FRMessage3);
385                 LabelIsFollowing.Text = Properties.Resources.GetFriendshipInfo2;
386                 ButtonFollow.Enabled = true;
387                 ButtonUnFollow.Enabled = false;
388             }
389         }
390
391         private void ShowUserInfo_Activated(object sender, EventArgs e)
392         {
393             //画面が他画面の裏に隠れると、アイコン画像が再描画されない問題の対応
394             if (UserPicture.Image != null)
395                 UserPicture.Invalidate(false);
396         }
397
398         private void ShowUserInfo_Shown(object sender, EventArgs e)
399         {
400             ButtonClose.Focus();
401         }
402
403         private async void WebBrowser_Navigating(object sender, WebBrowserNavigatingEventArgs e)
404         {
405             if (e.Url.AbsoluteUri != "about:blank")
406             {
407                 e.Cancel = true;
408
409                 // Ctrlを押しながらリンクを開いた場合は、設定と逆の動作をするフラグを true としておく
410                 await this.mainForm.OpenUriAsync(e.Url, MyCommon.IsKeyDown(Keys.Control));
411             }
412         }
413
414         private void SelectAllToolStripMenuItem_Click(object sender, EventArgs e)
415         {
416             var browser = (WebBrowser)this.ContextMenuRecentPostBrowser.SourceControl;
417             browser.Document.ExecCommand("SelectAll", false, null);
418         }
419
420         private void SelectionCopyToolStripMenuItem_Click(object sender, EventArgs e)
421         {
422             var browser = (WebBrowser)this.ContextMenuRecentPostBrowser.SourceControl;
423             var selectedText = browser.GetSelectedText();
424             if (selectedText != null)
425             {
426                 try
427                 {
428                     Clipboard.SetDataObject(selectedText, false, 5, 100);
429                 }
430                 catch (Exception ex)
431                 {
432                     MessageBox.Show(ex.Message);
433                 }
434             }
435         }
436
437         private void ContextMenuRecentPostBrowser_Opening(object sender, CancelEventArgs e)
438         {
439             var browser = (WebBrowser)this.ContextMenuRecentPostBrowser.SourceControl;
440             var selectedText = browser.GetSelectedText();
441
442             this.SelectionCopyToolStripMenuItem.Enabled = selectedText != null;
443         }
444
445         private async void LinkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
446         {
447             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");
448         }
449
450         private async void LinkLabel2_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
451         {
452             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");
453         }
454
455         private void ButtonSearchPosts_Click(object sender, EventArgs e)
456         {
457             this.mainForm.AddNewTabForUserTimeline(this._displayUser.ScreenName);
458         }
459
460         private async void UserPicture_Click(object sender, EventArgs e)
461         {
462             var imageUrl = this._displayUser.ProfileImageUrlHttps;
463             imageUrl = imageUrl.Remove(imageUrl.LastIndexOf("_normal", StringComparison.Ordinal), 7);
464
465             await this.mainForm.OpenUriInBrowserAsync(imageUrl);
466         }
467
468         private bool IsEditing = false;
469         private string ButtonEditText = "";
470
471         private async void ButtonEdit_Click(object sender, EventArgs e)
472         {
473             // 自分以外のプロフィールは変更できない
474             if (this.twitterApi.CurrentUserId != this._displayUser.Id)
475                 return;
476
477             using (ControlTransaction.Disabled(this.ButtonEdit))
478             {
479                 if (!IsEditing)
480                 {
481                     ButtonEditText = ButtonEdit.Text;
482                     ButtonEdit.Text = Properties.Resources.UserInfoButtonEdit_ClickText1;
483
484                     TextBoxName.Text = LabelName.Text;
485                     TextBoxName.Enabled = true;
486                     TextBoxName.Visible = true;
487                     LabelName.Visible = false;
488
489                     TextBoxLocation.Text = LabelLocation.Text;
490                     TextBoxLocation.Enabled = true;
491                     TextBoxLocation.Visible = true;
492                     LabelLocation.Visible = false;
493
494                     TextBoxWeb.Text = this._displayUser.Url;
495                     TextBoxWeb.Enabled = true;
496                     TextBoxWeb.Visible = true;
497                     LinkLabelWeb.Visible = false;
498
499                     TextBoxDescription.Text = this._displayUser.Description;
500                     TextBoxDescription.Enabled = true;
501                     TextBoxDescription.Visible = true;
502                     DescriptionBrowser.Visible = false;
503
504                     TextBoxName.Focus();
505                     TextBoxName.Select(TextBoxName.Text.Length, 0);
506
507                     IsEditing = true;
508                 }
509                 else
510                 {
511                     Task showUserTask = null;
512
513                     if (TextBoxName.Modified ||
514                         TextBoxLocation.Modified ||
515                         TextBoxWeb.Modified ||
516                         TextBoxDescription.Modified)
517                     {
518                         try
519                         {
520                             var response = await this.twitterApi.AccountUpdateProfile(
521                                 this.TextBoxName.Text,
522                                 this.TextBoxWeb.Text,
523                                 this.TextBoxLocation.Text,
524                                 this.TextBoxDescription.Text);
525
526                             var user = await response.LoadJsonAsync();
527                             showUserTask = this.ShowUserAsync(user);
528                         }
529                         catch (WebApiException ex)
530                         {
531                             MessageBox.Show($"Err:{ex.Message}(AccountUpdateProfile)");
532                             return;
533                         }
534                     }
535
536                     TextBoxName.Enabled = false;
537                     TextBoxName.Visible = false;
538                     LabelName.Visible = true;
539
540                     TextBoxLocation.Enabled = false;
541                     TextBoxLocation.Visible = false;
542                     LabelLocation.Visible = true;
543
544                     TextBoxWeb.Enabled = false;
545                     TextBoxWeb.Visible = false;
546                     LinkLabelWeb.Visible = true;
547
548                     TextBoxDescription.Enabled = false;
549                     TextBoxDescription.Visible = false;
550                     DescriptionBrowser.Visible = true;
551
552                     ButtonEdit.Text = ButtonEditText;
553
554                     IsEditing = false;
555
556                     if (showUserTask != null)
557                         await showUserTask;
558                 }
559             }
560         }
561
562         private async Task DoChangeIcon(string filename)
563         {
564             try
565             {
566                 var mediaItem = new FileMediaItem(filename);
567
568                 await this.twitterApi.AccountUpdateProfileImage(mediaItem)
569                     .IgnoreResponse();
570             }
571             catch (WebApiException ex)
572             {
573                 MessageBox.Show("Err:" + ex.Message + Environment.NewLine + Properties.Resources.ChangeIconToolStripMenuItem_ClickText4);
574                 return;
575             }
576
577             MessageBox.Show(Properties.Resources.ChangeIconToolStripMenuItem_ClickText5);
578
579             try
580             {
581                 var user = await this.twitterApi.UsersShow(this._displayUser.ScreenName);
582
583                 if (user != null)
584                     await this.ShowUserAsync(user);
585             }
586             catch (WebApiException ex)
587             {
588                 MessageBox.Show($"Err:{ex.Message}(UsersShow)");
589             }
590         }
591
592         private async void ChangeIconToolStripMenuItem_Click(object sender, EventArgs e)
593         {
594             OpenFileDialogIcon.Filter = Properties.Resources.ChangeIconToolStripMenuItem_ClickText1;
595             OpenFileDialogIcon.Title = Properties.Resources.ChangeIconToolStripMenuItem_ClickText2;
596             OpenFileDialogIcon.FileName = "";
597
598             DialogResult rslt = OpenFileDialogIcon.ShowDialog();
599
600             if (rslt != DialogResult.OK)
601             {
602                 return;
603             }
604
605             string fn = OpenFileDialogIcon.FileName;
606             if (this.IsValidIconFile(new FileInfo(fn)))
607             {
608                 await this.DoChangeIcon(fn);
609             }
610             else
611             {
612                 MessageBox.Show(Properties.Resources.ChangeIconToolStripMenuItem_ClickText6);
613             }
614         }
615
616         private async void ButtonBlock_Click(object sender, EventArgs e)
617         {
618             if (MessageBox.Show(this._displayUser.ScreenName + Properties.Resources.ButtonBlock_ClickText1,
619                                 Properties.Resources.ButtonBlock_ClickText2,
620                                 MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes)
621             {
622                 using (ControlTransaction.Disabled(this.ButtonBlock))
623                 {
624                     try
625                     {
626                         await this.twitterApi.BlocksCreate(this._displayUser.ScreenName)
627                             .IgnoreResponse();
628                     }
629                     catch (WebApiException ex)
630                     {
631                         MessageBox.Show("Err:" + ex.Message + Environment.NewLine + Properties.Resources.ButtonBlock_ClickText3);
632                         return;
633                     }
634
635                     MessageBox.Show(Properties.Resources.ButtonBlock_ClickText4);
636                 }
637             }
638         }
639
640         private async void ButtonReportSpam_Click(object sender, EventArgs e)
641         {
642             if (MessageBox.Show(this._displayUser.ScreenName + Properties.Resources.ButtonReportSpam_ClickText1,
643                                 Properties.Resources.ButtonReportSpam_ClickText2,
644                                 MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes)
645             {
646                 using (ControlTransaction.Disabled(this.ButtonReportSpam))
647                 {
648                     try
649                     {
650                         await this.twitterApi.UsersReportSpam(this._displayUser.ScreenName)
651                             .IgnoreResponse();
652                     }
653                     catch (WebApiException ex)
654                     {
655                         MessageBox.Show("Err:" + ex.Message + Environment.NewLine + Properties.Resources.ButtonReportSpam_ClickText3);
656                         return;
657                     }
658
659                     MessageBox.Show(Properties.Resources.ButtonReportSpam_ClickText4);
660                 }
661             }
662         }
663
664         private async void ButtonBlockDestroy_Click(object sender, EventArgs e)
665         {
666             if (MessageBox.Show(this._displayUser.ScreenName + Properties.Resources.ButtonBlockDestroy_ClickText1,
667                                 Properties.Resources.ButtonBlockDestroy_ClickText2,
668                                 MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes)
669             {
670                 using (ControlTransaction.Disabled(this.ButtonBlockDestroy))
671                 {
672                     try
673                     {
674                         await this.twitterApi.BlocksDestroy(this._displayUser.ScreenName)
675                             .IgnoreResponse();
676                     }
677                     catch (WebApiException ex)
678                     {
679                         MessageBox.Show("Err:" + ex.Message + Environment.NewLine + Properties.Resources.ButtonBlockDestroy_ClickText3);
680                         return;
681                     }
682
683                     MessageBox.Show(Properties.Resources.ButtonBlockDestroy_ClickText4);
684                 }
685             }
686         }
687
688         private bool IsValidExtension(string ext)
689         {
690             ext = ext.ToLowerInvariant();
691
692             return ext.Equals(".jpg", StringComparison.Ordinal) ||
693                 ext.Equals(".jpeg", StringComparison.Ordinal) ||
694                 ext.Equals(".png", StringComparison.Ordinal) ||
695                 ext.Equals(".gif", StringComparison.Ordinal);
696         }
697
698         private bool IsValidIconFile(FileInfo info)
699         {
700             return this.IsValidExtension(info.Extension) &&
701                 info.Length < 700 * 1024 &&
702                 !MyCommon.IsAnimatedGif(info.FullName);
703         }
704
705         private void ShowUserInfo_DragEnter(object sender, DragEventArgs e)
706         {
707             if (e.Data.GetDataPresent(DataFormats.FileDrop) &&
708                 !e.Data.GetDataPresent(DataFormats.Html, false))  // WebBrowserコントロールからの絵文字画像D&Dは弾く
709             {
710                 var files = (string[])e.Data.GetData(DataFormats.FileDrop, false);
711                 if (files.Length != 1)
712                     return;
713
714                 var finfo = new FileInfo(files[0]);
715                 if (this.IsValidIconFile(finfo))
716                 {
717                     e.Effect = DragDropEffects.Copy;
718                 }
719             }
720         }
721
722         private async void ShowUserInfo_DragDrop(object sender, DragEventArgs e)
723         {
724             if (e.Data.GetDataPresent(DataFormats.FileDrop) &&
725                 !e.Data.GetDataPresent(DataFormats.Html, false))  // WebBrowserコントロールからの絵文字画像D&Dは弾く
726             {
727                 var ret = MessageBox.Show(this, Properties.Resources.ChangeIconToolStripMenuItem_Confirm,
728                     Application.ProductName, MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
729                 if (ret != DialogResult.OK)
730                     return;
731
732                 string filename = ((string[])e.Data.GetData(DataFormats.FileDrop, false))[0];
733                 await this.DoChangeIcon(filename);
734             }
735         }
736
737         protected override void Dispose(bool disposing)
738         {
739             if (disposing)
740             {
741                 var cts = this.cancellationTokenSource;
742                 cts.Cancel();
743                 cts.Dispose();
744
745                 var oldImage = this.UserPicture.Image;
746                 this.UserPicture.Image = null;
747                 oldImage?.Dispose();
748
749                 this.components?.Dispose();
750             }
751
752             base.Dispose(disposing);
753         }
754     }
755 }