OSDN Git Service

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