OSDN Git Service

Merge branch 'reduce-timer-events'
[opentween/open-tween.git] / OpenTween / Tween.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 //コンパイル後コマンド
28 //"c:\Program Files\Microsoft.NET\SDK\v2.0\Bin\sgen.exe" /f /a:"$(TargetPath)"
29 //"C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\sgen.exe" /f /a:"$(TargetPath)"
30
31 using System;
32 using System.Collections.Concurrent;
33 using System.Collections.Generic;
34 using System.ComponentModel;
35 using System.Diagnostics;
36 using System.Drawing;
37 using System.Globalization;
38 using System.IO;
39 using System.Linq;
40 using System.Media;
41 using System.Net;
42 using System.Net.Http;
43 using System.Reflection;
44 using System.Runtime.InteropServices;
45 using System.Text;
46 using System.Text.RegularExpressions;
47 using System.Threading;
48 using System.Threading.Tasks;
49 using System.Windows.Forms;
50 using OpenTween.Api;
51 using OpenTween.Api.DataModel;
52 using OpenTween.Connection;
53 using OpenTween.Models;
54 using OpenTween.OpenTweenCustomControl;
55 using OpenTween.Setting;
56 using OpenTween.Thumbnail;
57
58 namespace OpenTween
59 {
60     public partial class TweenMain : OTBaseForm
61     {
62         //各種設定
63         private Size _mySize;           //画面サイズ
64         private Point _myLoc;           //画面位置
65         private int _mySpDis;           //区切り位置
66         private int _mySpDis2;          //発言欄区切り位置
67         private int _mySpDis3;          //プレビュー区切り位置
68         private int _iconSz;            //アイコンサイズ(現在は16、24、48の3種類。将来直接数字指定可能とする 注:24x24の場合に26と指定しているのはMSゴシック系フォントのための仕様)
69         private bool _iconCol;          //1列表示の時true(48サイズのとき)
70
71         //雑多なフラグ類
72         private bool _initial;         //true:起動時処理中
73         private bool _initialLayout = true;
74         private bool _ignoreConfigSave;         //true:起動時処理中
75         private bool _tabDrag;           //タブドラッグ中フラグ(DoDragDropを実行するかの判定用)
76         private TabPage _beforeSelectedTab; //タブが削除されたときに前回選択されていたときのタブを選択する為に保持
77         private Point _tabMouseDownPoint;
78         private string _rclickTabName;      //右クリックしたタブの名前(Tabコントロール機能不足対応)
79         private readonly object _syncObject = new object();    //ロック用
80
81         private const string detailHtmlFormatHeaderMono = 
82             "<html><head><meta http-equiv=\"X-UA-Compatible\" content=\"IE=8\">"
83             + "<style type=\"text/css\"><!-- "
84             + "body, p, pre {margin: 0;} "
85             + "pre {font-family: \"%FONT_FAMILY%\", sans-serif; font-size: %FONT_SIZE%pt; background-color:rgb(%BG_COLOR%); word-wrap: break-word; color:rgb(%FONT_COLOR%);} "
86             + "a:link, a:visited, a:active, a:hover {color:rgb(%LINK_COLOR%); } "
87             + "img.emoji {width: 1em; height: 1em; margin: 0 .05em 0 .1em; vertical-align: -0.1em; border: none;} "
88             + ".quote-tweet {border: 1px solid #ccc; margin: 1em; padding: 0.5em;} "
89             + ".quote-tweet.reply {border-color: #f33;} "
90             + ".quote-tweet-link {color: inherit !important; text-decoration: none;}"
91             + "--></style>"
92             + "</head><body><pre>";
93         private const string detailHtmlFormatFooterMono = "</pre></body></html>";
94         private const string detailHtmlFormatHeaderColor = 
95             "<html><head><meta http-equiv=\"X-UA-Compatible\" content=\"IE=8\">"
96             + "<style type=\"text/css\"><!-- "
97             + "body, p, pre {margin: 0;} "
98             + "body {font-family: \"%FONT_FAMILY%\", sans-serif; font-size: %FONT_SIZE%pt; background-color:rgb(%BG_COLOR%); margin: 0; word-wrap: break-word; color:rgb(%FONT_COLOR%);} "
99             + "a:link, a:visited, a:active, a:hover {color:rgb(%LINK_COLOR%); } "
100             + "img.emoji {width: 1em; height: 1em; margin: 0 .05em 0 .1em; vertical-align: -0.1em; border: none;} "
101             + ".quote-tweet {border: 1px solid #ccc; margin: 1em; padding: 0.5em;} "
102             + ".quote-tweet.reply {border-color: rgb(%BG_REPLY_COLOR%);} "
103             + ".quote-tweet-link {color: inherit !important; text-decoration: none;}"
104             + "--></style>"
105             + "</head><body><p>";
106         private const string detailHtmlFormatFooterColor = "</p></body></html>";
107         private string detailHtmlFormatHeader;
108         private string detailHtmlFormatFooter;
109
110         private bool _myStatusError = false;
111         private bool _myStatusOnline = false;
112         private bool soundfileListup = false;
113         private FormWindowState _formWindowState = FormWindowState.Normal; // フォームの状態保存用 通知領域からアイコンをクリックして復帰した際に使用する
114
115         //twitter解析部
116         private TwitterApi twitterApi = new TwitterApi();
117         private Twitter tw;
118
119         //Growl呼び出し部
120         private GrowlHelper gh = new GrowlHelper(ApplicationSettings.ApplicationName);
121
122         //サブ画面インスタンス
123         internal SearchWordDialog SearchDialog = new SearchWordDialog();     //検索画面インスタンス
124         private OpenURL UrlDialog = new OpenURL();
125         public AtIdSupplement AtIdSupl;     //@id補助
126         public AtIdSupplement HashSupl;    //Hashtag補助
127         public HashtagManage HashMgr;
128         private EventViewerDialog evtDialog;
129
130         //表示フォント、色、アイコン
131         private Font _fntUnread;            //未読用フォント
132         private Color _clUnread;            //未読用文字色
133         private Font _fntReaded;            //既読用フォント
134         private Color _clReaded;            //既読用文字色
135         private Color _clFav;               //Fav用文字色
136         private Color _clOWL;               //片思い用文字色
137         private Color _clRetweet;               //Retweet用文字色
138         private Color _clHighLight = Color.FromKnownColor(KnownColor.HighlightText);         //選択中の行用文字色
139         private Font _fntDetail;            //発言詳細部用フォント
140         private Color _clDetail;              //発言詳細部用色
141         private Color _clDetailLink;          //発言詳細部用リンク文字色
142         private Color _clDetailBackcolor;     //発言詳細部用背景色
143         private Color _clSelf;              //自分の発言用背景色
144         private Color _clAtSelf;            //自分宛返信用背景色
145         private Color _clTarget;            //選択発言者の他の発言用背景色
146         private Color _clAtTarget;          //選択発言中の返信先用背景色
147         private Color _clAtFromTarget;      //選択発言者への返信発言用背景色
148         private Color _clAtTo;              //選択発言の唯一@先
149         private Color _clListBackcolor;       //リスト部通常発言背景色
150         private Color _clInputBackcolor;      //入力欄背景色
151         private Color _clInputFont;           //入力欄文字色
152         private Font _fntInputFont;           //入力欄フォント
153         private ImageCache IconCache;        //アイコン画像リスト
154         private Icon NIconAt;               //At.ico             タスクトレイアイコン:通常時
155         private Icon NIconAtRed;            //AtRed.ico          タスクトレイアイコン:通信エラー時
156         private Icon NIconAtSmoke;          //AtSmoke.ico        タスクトレイアイコン:オフライン時
157         private Icon[] NIconRefresh = new Icon[4];       //Refresh.ico        タスクトレイアイコン:更新中(アニメーション用に4種類を保持するリスト)
158         private Icon TabIcon;               //Tab.ico            未読のあるタブ用アイコン
159         private Icon MainIcon;              //Main.ico           画面左上のアイコン
160         private Icon ReplyIcon;               //5g
161         private Icon ReplyIconBlink;          //6g
162
163         private ImageList _listViewImageList = new ImageList();    //ListViewItemの高さ変更用
164
165         private PostClass _anchorPost;
166         private bool _anchorFlag;        //true:関連発言移動中(関連移動以外のオペレーションをするとfalseへ。trueだとリスト背景色をアンカー発言選択中として描画)
167
168         private List<StatusTextHistory> _history = new List<StatusTextHistory>();   //発言履歴
169         private int _hisIdx;                  //発言履歴カレントインデックス
170
171         //発言投稿時のAPI引数(発言編集時に設定。手書きreplyでは設定されない)
172         private (long StatusId, string ScreenName)? inReplyTo = null; // リプライ先のステータスID・スクリーン名
173
174         //時速表示用
175         private List<DateTimeUtc> _postTimestamps = new List<DateTimeUtc>();
176         private List<DateTimeUtc> _favTimestamps = new List<DateTimeUtc>();
177
178         // 以下DrawItem関連
179         private SolidBrush _brsHighLight = new SolidBrush(Color.FromKnownColor(KnownColor.Highlight));
180         private SolidBrush _brsBackColorMine;
181         private SolidBrush _brsBackColorAt;
182         private SolidBrush _brsBackColorYou;
183         private SolidBrush _brsBackColorAtYou;
184         private SolidBrush _brsBackColorAtFromTarget;
185         private SolidBrush _brsBackColorAtTo;
186         private SolidBrush _brsBackColorNone;
187         private SolidBrush _brsDeactiveSelection = new SolidBrush(Color.FromKnownColor(KnownColor.ButtonFace)); //Listにフォーカスないときの選択行の背景色
188         private StringFormat sfTab = new StringFormat();
189
190         //////////////////////////////////////////////////////////////////////////////////////////////////////////
191         private TabInformations _statuses;
192
193         /// <summary>
194         /// 現在表示している発言一覧の <see cref="ListView"/> に対するキャッシュ
195         /// </summary>
196         /// <remarks>
197         /// キャッシュクリアのために null が代入されることがあるため、
198         /// 使用する場合には <see cref="_listItemCache"/> に対して直接メソッド等を呼び出さずに
199         /// 一旦ローカル変数に代入してから参照すること。
200         /// </remarks>
201         private ListViewItemCache _listItemCache = null;
202
203         internal class ListViewItemCache
204         {
205             /// <summary>アイテムをキャッシュする対象の <see cref="ListView"/></summary>
206             public ListView TargetList { get; set; }
207
208             /// <summary>キャッシュする範囲の開始インデックス</summary>
209             public int StartIndex { get; set; }
210
211             /// <summary>キャッシュする範囲の終了インデックス</summary>
212             public int EndIndex { get; set; }
213
214             /// <summary>キャッシュされた範囲に対応する <see cref="ListViewItem"/> と <see cref="PostClass"/> の組</summary>
215             public (ListViewItem, PostClass)[] Cache { get; set; }
216
217             /// <summary>キャッシュされたアイテムの件数</summary>
218             public int Count
219                 => this.EndIndex - this.StartIndex + 1;
220
221             /// <summary>指定されたインデックスがキャッシュの範囲内であるか判定します</summary>
222             /// <returns><paramref name="index"/> がキャッシュの範囲内であれば true、それ以外は false</returns>
223             public bool Contains(int index)
224                 => index >= this.StartIndex && index <= this.EndIndex;
225
226             /// <summary>指定されたインデックスの範囲が全てキャッシュの範囲内であるか判定します</summary>
227             /// <returns><paramref name="rangeStart"/> から <paramref name="rangeEnd"/> の範囲が全てキャッシュの範囲内であれば true、それ以外は false</returns>
228             public bool IsSupersetOf(int rangeStart, int rangeEnd)
229                 => rangeStart >= this.StartIndex && rangeEnd <= this.EndIndex;
230
231             /// <summary>指定されたインデックスの <see cref="ListViewItem"/> と <see cref="PostClass"/> をキャッシュから取得することを試みます</summary>
232             /// <returns>取得に成功すれば true、それ以外は false</returns>
233             public bool TryGetValue(int index, out ListViewItem item, out PostClass post)
234             {
235                 if (this.Contains(index))
236                 {
237                     (item, post) = this.Cache[index - this.StartIndex];
238                     return true;
239                 }
240                 else
241                 {
242                     item = null;
243                     post = null;
244                     return false;
245                 }
246             }
247         }
248
249         private bool _isColumnChanged = false;
250
251         private const int MAX_WORKER_THREADS = 20;
252         private SemaphoreSlim workerSemaphore = new SemaphoreSlim(MAX_WORKER_THREADS);
253         private CancellationTokenSource workerCts = new CancellationTokenSource();
254         private IProgress<string> workerProgress;
255
256         private int UnreadCounter = -1;
257         private int UnreadAtCounter = -1;
258
259         private string[] ColumnOrgText = new string[9];
260         private string[] ColumnText = new string[9];
261
262         private bool _DoFavRetweetFlags = false;
263
264         //////////////////////////////////////////////////////////////////////////////////////////////////////////
265
266         private readonly TimelineScheduler timelineScheduler = new TimelineScheduler();
267         private ThrottlingTimer RefreshThrottlingTimer;
268         private ThrottlingTimer colorizeDebouncer;
269         private ThrottlingTimer selectionDebouncer;
270         private ThrottlingTimer saveConfigDebouncer;
271
272         private string recommendedStatusFooter;
273         private bool urlMultibyteSplit = false;
274         private bool preventSmsCommand = true;
275
276         //URL短縮のUndo用
277         private struct urlUndo
278         {
279             public string Before;
280             public string After;
281         }
282
283         private List<urlUndo> urlUndoBuffer = null;
284
285         private readonly struct ReplyChain
286         {
287             public readonly long OriginalId;
288             public readonly long InReplyToId;
289             public readonly TabModel OriginalTab;
290
291             public ReplyChain(long originalId, long inReplyToId, TabModel originalTab)
292             {
293                 this.OriginalId = originalId;
294                 this.InReplyToId = inReplyToId;
295                 this.OriginalTab = originalTab;
296             }
297         }
298
299         private Stack<ReplyChain> replyChains; //[, ]でのリプライ移動の履歴
300         private Stack<(TabModel, PostClass)> selectPostChains = new Stack<(TabModel, PostClass)>(); //ポスト選択履歴
301
302         public TabModel CurrentTab
303             => this._statuses.SelectedTab;
304
305         public string CurrentTabName
306             => this._statuses.SelectedTabName;
307
308         public TabPage CurrentTabPage
309             => this.ListTab.TabPages[this._statuses.Tabs.IndexOf(this.CurrentTabName)];
310
311         public DetailsListView CurrentListView
312             => (DetailsListView)this.CurrentTabPage.Tag;
313
314         public PostClass CurrentPost
315             => this.CurrentTab.SelectedPost;
316
317         //検索処理タイプ
318         internal enum SEARCHTYPE
319         {
320             DialogSearch,
321             NextSearch,
322             PrevSearch,
323         }
324
325         private class StatusTextHistory
326         {
327             public string status = "";
328             public (long StatusId, string ScreenName)? inReplyTo = null;
329             public string imageService = "";      //画像投稿サービス名
330             public IMediaItem[] mediaItems = null;
331             public StatusTextHistory()
332             {
333             }
334             public StatusTextHistory(string status, (long StatusId, string ScreenName)? inReplyTo)
335             {
336                 this.status = status;
337                 this.inReplyTo = inReplyTo;
338             }
339         }
340
341         private void TweenMain_Activated(object sender, EventArgs e)
342         {
343             //画面がアクティブになったら、発言欄の背景色戻す
344             if (StatusText.Focused)
345             {
346                 this.StatusText_Enter(this.StatusText, System.EventArgs.Empty);
347             }
348         }
349
350         private bool disposed = false;
351
352         /// <summary>
353         /// 使用中のリソースをすべてクリーンアップします。
354         /// </summary>
355         /// <param name="disposing">マネージ リソースが破棄される場合 true、破棄されない場合は false です。</param>
356         protected override void Dispose(bool disposing)
357         {
358             base.Dispose(disposing);
359
360             if (this.disposed)
361                 return;
362
363             if (disposing)
364             {
365                 this.components?.Dispose();
366
367                 //後始末
368                 SearchDialog.Dispose();
369                 UrlDialog.Dispose();
370                 NIconAt?.Dispose();
371                 NIconAtRed?.Dispose();
372                 NIconAtSmoke?.Dispose();
373                 foreach (var iconRefresh in this.NIconRefresh)
374                 {
375                     iconRefresh?.Dispose();
376                 }
377                 TabIcon?.Dispose();
378                 MainIcon?.Dispose();
379                 ReplyIcon?.Dispose();
380                 ReplyIconBlink?.Dispose();
381                 _listViewImageList.Dispose();
382                 _brsHighLight.Dispose();
383                 _brsBackColorMine?.Dispose();
384                 _brsBackColorAt?.Dispose();
385                 _brsBackColorYou?.Dispose();
386                 _brsBackColorAtYou?.Dispose();
387                 _brsBackColorAtFromTarget?.Dispose();
388                 _brsBackColorAtTo?.Dispose();
389                 _brsBackColorNone?.Dispose();
390                 _brsDeactiveSelection?.Dispose();
391                 //sf.Dispose();
392                 sfTab.Dispose();
393
394                 this.workerCts.Cancel();
395
396                 if (IconCache != null)
397                 {
398                     this.IconCache.CancelAsync();
399                     this.IconCache.Dispose();
400                 }
401
402                 this.thumbnailTokenSource?.Dispose();
403
404                 this.tw.Dispose();
405                 this.twitterApi.Dispose();
406                 this._hookGlobalHotkey.Dispose();
407             }
408
409             // 終了時にRemoveHandlerしておかないとメモリリークする
410             // http://msdn.microsoft.com/ja-jp/library/microsoft.win32.systemevents.powermodechanged.aspx
411             Microsoft.Win32.SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
412             Microsoft.Win32.SystemEvents.TimeChanged -= SystemEvents_TimeChanged;
413
414             this.disposed = true;
415         }
416
417         private void LoadIcons()
418         {
419             // Icons フォルダ以下のアイコンを読み込み(着せ替えアイコン対応)
420             var iconsDir = Path.Combine(Application.StartupPath, "Icons");
421
422             // ウィンドウ左上のアイコン
423             var iconMain = this.LoadIcon(Path.Combine(iconsDir, "MIcon.ico"));
424
425             // タブ見出し未読表示アイコン
426             var iconTab = this.LoadIcon(Path.Combine(iconsDir, "Tab.ico"));
427
428             // タスクトレイ: 通常時アイコン
429             var iconAt = this.LoadIcon(Path.Combine(iconsDir, "At.ico"));
430
431             // タスクトレイ: エラー時アイコン
432             var iconAtRed = this.LoadIcon(Path.Combine(iconsDir, "AtRed.ico"));
433
434             // タスクトレイ: オフライン時アイコン
435             var iconAtSmoke = this.LoadIcon(Path.Combine(iconsDir, "AtSmoke.ico"));
436
437             // タスクトレイ: Reply通知アイコン (最大2枚でアニメーション可能)
438             var iconReply = this.LoadIcon(Path.Combine(iconsDir, "Reply.ico"));
439             var iconReplyBlink = this.LoadIcon(Path.Combine(iconsDir, "ReplyBlink.ico"));
440
441             // タスクトレイ: 更新中アイコン (最大4枚でアニメーション可能)
442             var iconRefresh1 = this.LoadIcon(Path.Combine(iconsDir, "Refresh.ico"));
443             var iconRefresh2 = this.LoadIcon(Path.Combine(iconsDir, "Refresh2.ico"));
444             var iconRefresh3 = this.LoadIcon(Path.Combine(iconsDir, "Refresh3.ico"));
445             var iconRefresh4 = this.LoadIcon(Path.Combine(iconsDir, "Refresh4.ico"));
446
447             // 読み込んだアイコンを設定 (不足するアイコンはデフォルトのものを設定)
448
449             this.MainIcon = iconMain ?? Properties.Resources.MIcon;
450             this.TabIcon = iconTab ?? Properties.Resources.TabIcon;
451             this.NIconAt = iconAt ?? iconMain ?? Properties.Resources.At;
452             this.NIconAtRed = iconAtRed ?? Properties.Resources.AtRed;
453             this.NIconAtSmoke = iconAtSmoke ?? Properties.Resources.AtSmoke;
454
455             if (iconReply != null && iconReplyBlink != null)
456             {
457                 this.ReplyIcon = iconReply;
458                 this.ReplyIconBlink = iconReplyBlink;
459             }
460             else
461             {
462                 this.ReplyIcon = iconReply ?? iconReplyBlink ?? Properties.Resources.Reply;
463                 this.ReplyIconBlink = this.NIconAt;
464             }
465
466             if (iconRefresh1 == null)
467             {
468                 this.NIconRefresh = new[] {
469                     Properties.Resources.Refresh, Properties.Resources.Refresh2,
470                     Properties.Resources.Refresh3, Properties.Resources.Refresh4,
471                 };
472             }
473             else if (iconRefresh2 == null)
474             {
475                 this.NIconRefresh = new[] { iconRefresh1 };
476             }
477             else if (iconRefresh3 == null)
478             {
479                 this.NIconRefresh = new[] { iconRefresh1, iconRefresh2 };
480             }
481             else if (iconRefresh4 == null)
482             {
483                 this.NIconRefresh = new[] { iconRefresh1, iconRefresh2, iconRefresh3 };
484             }
485             else // iconRefresh1 から iconRefresh4 まで全て揃っている
486             {
487                 this.NIconRefresh = new[] { iconRefresh1, iconRefresh2, iconRefresh3, iconRefresh4 };
488             }
489         }
490
491         private Icon LoadIcon(string filePath)
492         {
493             if (!File.Exists(filePath))
494                 return null;
495
496             try
497             {
498                 return new Icon(filePath);
499             }
500             catch (Exception)
501             {
502                 return null;
503             }
504         }
505
506         private void InitColumns(ListView list, bool startup)
507         {
508             this.InitColumnText();
509
510             ColumnHeader[] columns = null;
511             try
512             {
513                 if (this._iconCol)
514                 {
515                     columns = new[]
516                     {
517                         new ColumnHeader(), // アイコン
518                         new ColumnHeader(), // 本文
519                     };
520
521                     columns[0].Text = this.ColumnText[0];
522                     columns[1].Text = this.ColumnText[2];
523
524                     if (startup)
525                     {
526                         var widthScaleFactor = this.CurrentAutoScaleDimensions.Width / SettingManager.Local.ScaleDimension.Width;
527
528                         columns[0].Width = ScaleBy(widthScaleFactor, SettingManager.Local.Width1);
529                         columns[1].Width = ScaleBy(widthScaleFactor, SettingManager.Local.Width3);
530                         columns[0].DisplayIndex = 0;
531                         columns[1].DisplayIndex = 1;
532                     }
533                     else
534                     {
535                         var idx = 0;
536                         foreach (var curListColumn in this.CurrentListView.Columns.Cast<ColumnHeader>())
537                         {
538                             columns[idx].Width = curListColumn.Width;
539                             columns[idx].DisplayIndex = curListColumn.DisplayIndex;
540                             idx++;
541                         }
542                     }
543                 }
544                 else
545                 {
546                     columns = new[]
547                     {
548                         new ColumnHeader(), // アイコン
549                         new ColumnHeader(), // ニックネーム
550                         new ColumnHeader(), // 本文
551                         new ColumnHeader(), // 日付
552                         new ColumnHeader(), // ユーザID
553                         new ColumnHeader(), // 未読
554                         new ColumnHeader(), // マーク&プロテクト
555                         new ColumnHeader(), // ソース
556                     };
557
558                     foreach (var i in Enumerable.Range(0, columns.Length))
559                         columns[i].Text = this.ColumnText[i];
560
561                     if (startup)
562                     {
563                         var widthScaleFactor = this.CurrentAutoScaleDimensions.Width / SettingManager.Local.ScaleDimension.Width;
564
565                         columns[0].Width = ScaleBy(widthScaleFactor, SettingManager.Local.Width1);
566                         columns[1].Width = ScaleBy(widthScaleFactor, SettingManager.Local.Width2);
567                         columns[2].Width = ScaleBy(widthScaleFactor, SettingManager.Local.Width3);
568                         columns[3].Width = ScaleBy(widthScaleFactor, SettingManager.Local.Width4);
569                         columns[4].Width = ScaleBy(widthScaleFactor, SettingManager.Local.Width5);
570                         columns[5].Width = ScaleBy(widthScaleFactor, SettingManager.Local.Width6);
571                         columns[6].Width = ScaleBy(widthScaleFactor, SettingManager.Local.Width7);
572                         columns[7].Width = ScaleBy(widthScaleFactor, SettingManager.Local.Width8);
573
574                         var displayIndex = new[] {
575                             SettingManager.Local.DisplayIndex1, SettingManager.Local.DisplayIndex2,
576                             SettingManager.Local.DisplayIndex3, SettingManager.Local.DisplayIndex4,
577                             SettingManager.Local.DisplayIndex5, SettingManager.Local.DisplayIndex6,
578                             SettingManager.Local.DisplayIndex7, SettingManager.Local.DisplayIndex8
579                         };
580
581                         foreach (var i in Enumerable.Range(0, displayIndex.Length))
582                         {
583                             columns[i].DisplayIndex = displayIndex[i];
584                         }
585                     }
586                     else
587                     {
588                         var idx = 0;
589                         foreach (var curListColumn in this.CurrentListView.Columns.Cast<ColumnHeader>())
590                         {
591                             columns[idx].Width = curListColumn.Width;
592                             columns[idx].DisplayIndex = curListColumn.DisplayIndex;
593                             idx++;
594                         }
595                     }
596                 }
597
598                 list.Columns.AddRange(columns);
599
600                 columns = null;
601             }
602             finally
603             {
604                 if (columns != null)
605                 {
606                     foreach (var column in columns)
607                         column?.Dispose();
608                 }
609             }
610         }
611
612         private void InitColumnText()
613         {
614             ColumnText[0] = "";
615             ColumnText[1] = Properties.Resources.AddNewTabText2;
616             ColumnText[2] = Properties.Resources.AddNewTabText3;
617             ColumnText[3] = Properties.Resources.AddNewTabText4_2;
618             ColumnText[4] = Properties.Resources.AddNewTabText5;
619             ColumnText[5] = "";
620             ColumnText[6] = "";
621             ColumnText[7] = "Source";
622
623             ColumnOrgText[0] = "";
624             ColumnOrgText[1] = Properties.Resources.AddNewTabText2;
625             ColumnOrgText[2] = Properties.Resources.AddNewTabText3;
626             ColumnOrgText[3] = Properties.Resources.AddNewTabText4_2;
627             ColumnOrgText[4] = Properties.Resources.AddNewTabText5;
628             ColumnOrgText[5] = "";
629             ColumnOrgText[6] = "";
630             ColumnOrgText[7] = "Source";
631
632             int c = 0;
633             switch (_statuses.SortMode)
634             {
635                 case ComparerMode.Nickname:  //ニックネーム
636                     c = 1;
637                     break;
638                 case ComparerMode.Data:  //本文
639                     c = 2;
640                     break;
641                 case ComparerMode.Id:  //時刻=発言Id
642                     c = 3;
643                     break;
644                 case ComparerMode.Name:  //名前
645                     c = 4;
646                     break;
647                 case ComparerMode.Source:  //Source
648                     c = 7;
649                     break;
650             }
651
652             if (_iconCol)
653             {
654                 if (_statuses.SortOrder == SortOrder.Descending)
655                 {
656                     // U+25BE BLACK DOWN-POINTING SMALL TRIANGLE
657                     ColumnText[2] = ColumnOrgText[2] + "▾";
658                 }
659                 else
660                 {
661                     // U+25B4 BLACK UP-POINTING SMALL TRIANGLE
662                     ColumnText[2] = ColumnOrgText[2] + "▴";
663                 }
664             }
665             else
666             {
667                 if (_statuses.SortOrder == SortOrder.Descending)
668                 {
669                     // U+25BE BLACK DOWN-POINTING SMALL TRIANGLE
670                     ColumnText[c] = ColumnOrgText[c] + "▾";
671                 }
672                 else
673                 {
674                     // U+25B4 BLACK UP-POINTING SMALL TRIANGLE
675                     ColumnText[c] = ColumnOrgText[c] + "▴";
676                 }
677             }
678         }
679
680         private void InitializeTraceFrag()
681         {
682 #if DEBUG
683             TraceOutToolStripMenuItem.Checked = true;
684             MyCommon.TraceFlag = true;
685 #endif
686             if (!MyCommon.FileVersion.EndsWith("0", StringComparison.Ordinal))
687             {
688                 TraceOutToolStripMenuItem.Checked = true;
689                 MyCommon.TraceFlag = true;
690             }
691         }
692
693         private void TweenMain_Load(object sender, EventArgs e)
694         {
695             _ignoreConfigSave = true;
696             this.Visible = false;
697
698             if (MyApplication.StartupOptions.ContainsKey("d"))
699                 MyCommon.TraceFlag = true;
700
701             InitializeTraceFrag();
702
703             //Win32Api.SetProxy(HttpConnection.ProxyType.Specified, "127.0.0.1", 8080, "user", "pass")
704
705             Microsoft.Win32.SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
706
707             Regex.CacheSize = 100;
708
709             //発言保持クラス
710             _statuses = TabInformations.GetInstance();
711
712             //アイコン設定
713             LoadIcons();
714             this.Icon = MainIcon;              //メインフォーム(TweenMain)
715             NotifyIcon1.Icon = NIconAt;      //タスクトレイ
716             TabImage.Images.Add(TabIcon);    //タブ見出し
717
718             //<<<<<<<<<設定関連>>>>>>>>>
719             ////設定読み出し
720             LoadConfig();
721
722             // 現在の DPI と設定保存時の DPI との比を取得する
723             var configScaleFactor = SettingManager.Local.GetConfigScaleFactor(this.CurrentAutoScaleDimensions);
724
725             // UIフォント設定
726             var fontUIGlobal = SettingManager.Local.FontUIGlobal;
727             if (fontUIGlobal != null)
728             {
729                 OTBaseForm.GlobalFont = fontUIGlobal;
730                 this.Font = fontUIGlobal;
731             }
732
733             //不正値チェック
734             if (!MyApplication.StartupOptions.ContainsKey("nolimit"))
735             {
736                 if (SettingManager.Common.TimelinePeriod < 15 && SettingManager.Common.TimelinePeriod > 0)
737                     SettingManager.Common.TimelinePeriod = 15;
738
739                 if (SettingManager.Common.ReplyPeriod < 15 && SettingManager.Common.ReplyPeriod > 0)
740                     SettingManager.Common.ReplyPeriod = 15;
741
742                 if (SettingManager.Common.DMPeriod < 15 && SettingManager.Common.DMPeriod > 0)
743                     SettingManager.Common.DMPeriod = 15;
744
745                 if (SettingManager.Common.PubSearchPeriod < 30 && SettingManager.Common.PubSearchPeriod > 0)
746                     SettingManager.Common.PubSearchPeriod = 30;
747
748                 if (SettingManager.Common.UserTimelinePeriod < 15 && SettingManager.Common.UserTimelinePeriod > 0)
749                     SettingManager.Common.UserTimelinePeriod = 15;
750
751                 if (SettingManager.Common.ListsPeriod < 15 && SettingManager.Common.ListsPeriod > 0)
752                     SettingManager.Common.ListsPeriod = 15;
753             }
754
755             if (!Twitter.VerifyApiResultCount(MyCommon.WORKERTYPE.Timeline, SettingManager.Common.CountApi))
756                 SettingManager.Common.CountApi = 60;
757             if (!Twitter.VerifyApiResultCount(MyCommon.WORKERTYPE.Reply, SettingManager.Common.CountApiReply))
758                 SettingManager.Common.CountApiReply = 40;
759
760             if (SettingManager.Common.MoreCountApi != 0 && !Twitter.VerifyMoreApiResultCount(SettingManager.Common.MoreCountApi))
761                 SettingManager.Common.MoreCountApi = 200;
762             if (SettingManager.Common.FirstCountApi != 0 && !Twitter.VerifyFirstApiResultCount(SettingManager.Common.FirstCountApi))
763                 SettingManager.Common.FirstCountApi = 100;
764
765             if (SettingManager.Common.FavoritesCountApi != 0 && !Twitter.VerifyApiResultCount(MyCommon.WORKERTYPE.Favorites, SettingManager.Common.FavoritesCountApi))
766                 SettingManager.Common.FavoritesCountApi = 40;
767             if (SettingManager.Common.ListCountApi != 0 && !Twitter.VerifyApiResultCount(MyCommon.WORKERTYPE.List, SettingManager.Common.ListCountApi))
768                 SettingManager.Common.ListCountApi = 100;
769             if (SettingManager.Common.SearchCountApi != 0 && !Twitter.VerifyApiResultCount(MyCommon.WORKERTYPE.PublicSearch, SettingManager.Common.SearchCountApi))
770                 SettingManager.Common.SearchCountApi = 100;
771             if (SettingManager.Common.UserTimelineCountApi != 0 && !Twitter.VerifyApiResultCount(MyCommon.WORKERTYPE.UserTimeline, SettingManager.Common.UserTimelineCountApi))
772                 SettingManager.Common.UserTimelineCountApi = 20;
773
774             //廃止サービスが選択されていた場合ux.nuへ読み替え
775             if (SettingManager.Common.AutoShortUrlFirst < 0)
776                 SettingManager.Common.AutoShortUrlFirst = MyCommon.UrlConverter.Uxnu;
777
778             TwitterApiConnection.RestApiHost = SettingManager.Common.TwitterApiHost;
779             this.tw = new Twitter(this.twitterApi);
780
781             //認証関連
782             if (string.IsNullOrEmpty(SettingManager.Common.Token)) SettingManager.Common.UserName = "";
783             tw.Initialize(SettingManager.Common.Token, SettingManager.Common.TokenSecret, SettingManager.Common.UserName, SettingManager.Common.UserId);
784
785             _initial = true;
786
787             Networking.Initialize();
788
789             bool saveRequired = false;
790             bool firstRun = false;
791
792             //ユーザー名、パスワードが未設定なら設定画面を表示(初回起動時など)
793             if (string.IsNullOrEmpty(tw.Username))
794             {
795                 saveRequired = true;
796                 firstRun = true;
797
798                 //設定せずにキャンセルされたか、設定されたが依然ユーザー名が未設定ならプログラム終了
799                 if (ShowSettingDialog(showTaskbarIcon: true) != DialogResult.OK ||
800                     string.IsNullOrEmpty(tw.Username))
801                 {
802                     Application.Exit();  //強制終了
803                     return;
804                 }
805             }
806
807             //Twitter用通信クラス初期化
808             Networking.DefaultTimeout = TimeSpan.FromSeconds(SettingManager.Common.DefaultTimeOut);
809             Networking.UploadImageTimeout = TimeSpan.FromSeconds(SettingManager.Common.UploadImageTimeout);
810             Networking.SetWebProxy(SettingManager.Local.ProxyType,
811                 SettingManager.Local.ProxyAddress, SettingManager.Local.ProxyPort,
812                 SettingManager.Local.ProxyUser, SettingManager.Local.ProxyPassword);
813             Networking.ForceIPv4 = SettingManager.Common.ForceIPv4;
814
815             TwitterApiConnection.RestApiHost = SettingManager.Common.TwitterApiHost;
816             tw.RestrictFavCheck = SettingManager.Common.RestrictFavCheck;
817             tw.ReadOwnPost = SettingManager.Common.ReadOwnPost;
818             tw.TrackWord = SettingManager.Common.TrackWord;
819             TrackToolStripMenuItem.Checked = !String.IsNullOrEmpty(tw.TrackWord);
820             tw.AllAtReply = SettingManager.Common.AllAtReply;
821             AllrepliesToolStripMenuItem.Checked = tw.AllAtReply;
822             ShortUrl.Instance.DisableExpanding = !SettingManager.Common.TinyUrlResolve;
823             ShortUrl.Instance.BitlyAccessToken = SettingManager.Common.BitlyAccessToken;
824             ShortUrl.Instance.BitlyId = SettingManager.Common.BilyUser;
825             ShortUrl.Instance.BitlyKey = SettingManager.Common.BitlyPwd;
826
827             // アクセストークンが有効であるか確認する
828             // ここが Twitter API への最初のアクセスになるようにすること
829             try
830             {
831                 this.tw.VerifyCredentials();
832             }
833             catch (WebApiException ex)
834             {
835                 MessageBox.Show(this, string.Format(Properties.Resources.StartupAuthError_Text, ex.Message),
836                     ApplicationSettings.ApplicationName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
837             }
838
839             //サムネイル関連の初期化
840             //プロキシ設定等の通信まわりの初期化が済んでから処理する
841             ThumbnailGenerator.InitializeGenerator();
842
843             var imgazyobizinet = ThumbnailGenerator.ImgAzyobuziNetInstance;
844             imgazyobizinet.Enabled = SettingManager.Common.EnableImgAzyobuziNet;
845             imgazyobizinet.DisabledInDM = SettingManager.Common.ImgAzyobuziNetDisabledInDM;
846
847             Thumbnail.Services.TonTwitterCom.GetApiConnection = () => this.twitterApi.Connection;
848
849             //画像投稿サービス
850             ImageSelector.Initialize(tw, this.tw.Configuration, SettingManager.Common.UseImageServiceName, SettingManager.Common.UseImageService);
851
852             //ハッシュタグ/@id関連
853             AtIdSupl = new AtIdSupplement(SettingManager.AtIdList.AtIdList, "@");
854             HashSupl = new AtIdSupplement(SettingManager.Common.HashTags, "#");
855             HashMgr = new HashtagManage(HashSupl,
856                                     SettingManager.Common.HashTags.ToArray(),
857                                     SettingManager.Common.HashSelected,
858                                     SettingManager.Common.HashIsPermanent,
859                                     SettingManager.Common.HashIsHead,
860                                     SettingManager.Common.HashIsNotAddToAtReply);
861             if (!string.IsNullOrEmpty(HashMgr.UseHash) && HashMgr.IsPermanent) HashStripSplitButton.Text = HashMgr.UseHash;
862
863             //アイコンリスト作成
864             this.IconCache = new ImageCache();
865             this.tweetDetailsView.IconCache = this.IconCache;
866
867             //フォント&文字色&背景色保持
868             _fntUnread = SettingManager.Local.FontUnread;
869             _clUnread = SettingManager.Local.ColorUnread;
870             _fntReaded = SettingManager.Local.FontRead;
871             _clReaded = SettingManager.Local.ColorRead;
872             _clFav = SettingManager.Local.ColorFav;
873             _clOWL = SettingManager.Local.ColorOWL;
874             _clRetweet = SettingManager.Local.ColorRetweet;
875             _fntDetail = SettingManager.Local.FontDetail;
876             _clDetail = SettingManager.Local.ColorDetail;
877             _clDetailLink = SettingManager.Local.ColorDetailLink;
878             _clDetailBackcolor = SettingManager.Local.ColorDetailBackcolor;
879             _clSelf = SettingManager.Local.ColorSelf;
880             _clAtSelf = SettingManager.Local.ColorAtSelf;
881             _clTarget = SettingManager.Local.ColorTarget;
882             _clAtTarget = SettingManager.Local.ColorAtTarget;
883             _clAtFromTarget = SettingManager.Local.ColorAtFromTarget;
884             _clAtTo = SettingManager.Local.ColorAtTo;
885             _clListBackcolor = SettingManager.Local.ColorListBackcolor;
886             _clInputBackcolor = SettingManager.Local.ColorInputBackcolor;
887             _clInputFont = SettingManager.Local.ColorInputFont;
888             _fntInputFont = SettingManager.Local.FontInputFont;
889
890             _brsBackColorMine = new SolidBrush(_clSelf);
891             _brsBackColorAt = new SolidBrush(_clAtSelf);
892             _brsBackColorYou = new SolidBrush(_clTarget);
893             _brsBackColorAtYou = new SolidBrush(_clAtTarget);
894             _brsBackColorAtFromTarget = new SolidBrush(_clAtFromTarget);
895             _brsBackColorAtTo = new SolidBrush(_clAtTo);
896             //_brsBackColorNone = new SolidBrush(Color.FromKnownColor(KnownColor.Window));
897             _brsBackColorNone = new SolidBrush(_clListBackcolor);
898
899             // StringFormatオブジェクトへの事前設定
900             //sf.Alignment = StringAlignment.Near;             // Textを近くへ配置(左から右の場合は左寄せ)
901             //sf.LineAlignment = StringAlignment.Near;         // Textを近くへ配置(上寄せ)
902             //sf.FormatFlags = StringFormatFlags.LineLimit;    // 
903             sfTab.Alignment = StringAlignment.Center;
904             sfTab.LineAlignment = StringAlignment.Center;
905
906             InitDetailHtmlFormat();
907
908             //Regex statregex = new Regex("^0*");
909             this.recommendedStatusFooter = " [TWNv" + Regex.Replace(MyCommon.FileVersion.Replace(".", ""), "^0*", "") + "]";
910
911             _history.Add(new StatusTextHistory());
912             _hisIdx = 0;
913             this.inReplyTo = null;
914
915             //各種ダイアログ設定
916             SearchDialog.Owner = this;
917             UrlDialog.Owner = this;
918
919             //新着バルーン通知のチェック状態設定
920             NewPostPopMenuItem.Checked = SettingManager.Common.NewAllPop;
921             this.NotifyFileMenuItem.Checked = NewPostPopMenuItem.Checked;
922
923             //新着取得時のリストスクロールをするか。trueならスクロールしない
924             ListLockMenuItem.Checked = SettingManager.Common.ListLock;
925             this.LockListFileMenuItem.Checked = SettingManager.Common.ListLock;
926             //サウンド再生(タブ別設定より優先)
927             this.PlaySoundMenuItem.Checked = SettingManager.Common.PlaySound;
928             this.PlaySoundFileMenuItem.Checked = SettingManager.Common.PlaySound;
929
930             //ウィンドウ設定
931             this.ClientSize = ScaleBy(configScaleFactor, SettingManager.Local.FormSize);
932             _mySize = this.ClientSize; // サイズ保持(最小化・最大化されたまま終了した場合の対応用)
933             _myLoc = SettingManager.Local.FormLocation;
934             //タイトルバー領域
935             if (this.WindowState != FormWindowState.Minimized)
936             {
937                 Rectangle tbarRect = new Rectangle(this._myLoc, new Size(_mySize.Width, SystemInformation.CaptionHeight));
938                 bool outOfScreen = true;
939                 if (Screen.AllScreens.Length == 1)    //ハングするとの報告
940                 {
941                     foreach (Screen scr in Screen.AllScreens)
942                     {
943                         if (!Rectangle.Intersect(tbarRect, scr.Bounds).IsEmpty)
944                         {
945                             outOfScreen = false;
946                             break;
947                         }
948                     }
949
950                     if (outOfScreen)
951                         this._myLoc = new Point(0, 0);
952                 }
953                 this.DesktopLocation = this._myLoc;
954             }
955             this.TopMost = SettingManager.Common.AlwaysTop;
956             _mySpDis = ScaleBy(configScaleFactor.Height, SettingManager.Local.SplitterDistance);
957             _mySpDis2 = ScaleBy(configScaleFactor.Height, SettingManager.Local.StatusTextHeight);
958             if (SettingManager.Local.PreviewDistance == -1)
959             {
960                 _mySpDis3 = _mySize.Width - ScaleBy(this.CurrentScaleFactor.Width, 150);
961                 if (_mySpDis3 < 1) _mySpDis3 = ScaleBy(this.CurrentScaleFactor.Width, 50);
962                 SettingManager.Local.PreviewDistance = _mySpDis3;
963             }
964             else
965             {
966                 _mySpDis3 = ScaleBy(configScaleFactor.Width, SettingManager.Local.PreviewDistance);
967             }
968             //this.Tween_ClientSizeChanged(this, null);
969             this.PlaySoundMenuItem.Checked = SettingManager.Common.PlaySound;
970             this.PlaySoundFileMenuItem.Checked = SettingManager.Common.PlaySound;
971             //入力欄
972             StatusText.Font = _fntInputFont;
973             StatusText.ForeColor = _clInputFont;
974
975             // SplitContainer2.Panel2MinSize を一行表示の入力欄の高さに合わせる (MS UI Gothic 12pt (96dpi) の場合は 19px)
976             this.StatusText.Multiline = false; // SettingManager.Local.StatusMultiline の設定は後で反映される
977             this.SplitContainer2.Panel2MinSize = this.StatusText.Height;
978
979             // 必要であれば、発言一覧と発言詳細部・入力欄の上下を入れ替える
980             SplitContainer1.IsPanelInverted = !SettingManager.Common.StatusAreaAtBottom;
981
982             //全新着通知のチェック状態により、Reply&DMの新着通知有効無効切り替え(タブ別設定にするため削除予定)
983             if (SettingManager.Common.UnreadManage == false)
984             {
985                 ReadedStripMenuItem.Enabled = false;
986                 UnreadStripMenuItem.Enabled = false;
987             }
988
989             //リンク先URL表示部の初期化(画面左下)
990             StatusLabelUrl.Text = "";
991             //状態表示部の初期化(画面右下)
992             StatusLabel.Text = "";
993             StatusLabel.AutoToolTip = false;
994             StatusLabel.ToolTipText = "";
995             //文字カウンタ初期化
996             lblLen.Text = this.GetRestStatusCount(this.FormatStatusTextExtended("")).ToString();
997
998             this.JumpReadOpMenuItem.ShortcutKeyDisplayString = "Space";
999             CopySTOTMenuItem.ShortcutKeyDisplayString = "Ctrl+C";
1000             CopyURLMenuItem.ShortcutKeyDisplayString = "Ctrl+Shift+C";
1001             CopyUserIdStripMenuItem.ShortcutKeyDisplayString = "Shift+Alt+C";
1002
1003             // SourceLinkLabel のテキストが SplitContainer2.Panel2.AccessibleName にセットされるのを防ぐ
1004             // (タブオーダー順で SourceLinkLabel の次にある PostBrowser が TabStop = false となっているため、
1005             // さらに次のコントロールである SplitContainer2.Panel2 の AccessibleName がデフォルトで SourceLinkLabel のテキストになってしまう)
1006             this.SplitContainer2.Panel2.AccessibleName = "";
1007
1008             ////////////////////////////////////////////////////////////////////////////////
1009             var sortOrder = (SortOrder)SettingManager.Common.SortOrder;
1010             var mode = ComparerMode.Id;
1011             switch (SettingManager.Common.SortColumn)
1012             {
1013                 case 0:    //0:アイコン,5:未読マーク,6:プロテクト・フィルターマーク
1014                 case 5:
1015                 case 6:
1016                     //ソートしない
1017                     mode = ComparerMode.Id;  //Idソートに読み替え
1018                     break;
1019                 case 1:  //ニックネーム
1020                     mode = ComparerMode.Nickname;
1021                     break;
1022                 case 2:  //本文
1023                     mode = ComparerMode.Data;
1024                     break;
1025                 case 3:  //時刻=発言Id
1026                     mode = ComparerMode.Id;
1027                     break;
1028                 case 4:  //名前
1029                     mode = ComparerMode.Name;
1030                     break;
1031                 case 7:  //Source
1032                     mode = ComparerMode.Source;
1033                     break;
1034             }
1035             _statuses.SetSortMode(mode, sortOrder);
1036             ////////////////////////////////////////////////////////////////////////////////
1037
1038             ApplyListViewIconSize(SettingManager.Common.IconSize);
1039
1040             //<<<<<<<<タブ関連>>>>>>>
1041             // タブの位置を調整する
1042             SetTabAlignment();
1043
1044             //デフォルトタブの存在チェック、ない場合には追加
1045             if (this._statuses.GetTabByType<HomeTabModel>() == null)
1046                 this._statuses.AddTab(new HomeTabModel());
1047
1048             if (this._statuses.GetTabByType<MentionsTabModel>() == null)
1049                 this._statuses.AddTab(new MentionsTabModel());
1050
1051             if (this._statuses.GetTabByType<DirectMessagesTabModel>() == null)
1052                 this._statuses.AddTab(new DirectMessagesTabModel());
1053
1054             if (this._statuses.GetTabByType<FavoritesTabModel>() == null)
1055                 this._statuses.AddTab(new FavoritesTabModel());
1056
1057             if (this._statuses.MuteTab == null)
1058                 this._statuses.AddTab(new MuteTabModel());
1059
1060             foreach (var tab in _statuses.Tabs)
1061             {
1062                 if (!AddNewTab(tab, startup: true))
1063                     throw new TabException(Properties.Resources.TweenMain_LoadText1);
1064             }
1065
1066             this._statuses.SelectTab(this.ListTab.SelectedTab.Text);
1067
1068             MyCommon.TwitterApiInfo.AccessLimitUpdated += TwitterApiStatus_AccessLimitUpdated;
1069             Microsoft.Win32.SystemEvents.TimeChanged += SystemEvents_TimeChanged;
1070
1071             if (SettingManager.Common.TabIconDisp)
1072             {
1073                 ListTab.DrawMode = TabDrawMode.Normal;
1074             }
1075             else
1076             {
1077                 ListTab.DrawMode = TabDrawMode.OwnerDrawFixed;
1078                 ListTab.DrawItem += ListTab_DrawItem;
1079                 ListTab.ImageList = null;
1080             }
1081
1082             if (SettingManager.Common.HotkeyEnabled)
1083             {
1084                 //////グローバルホットキーの登録
1085                 HookGlobalHotkey.ModKeys modKey = HookGlobalHotkey.ModKeys.None;
1086                 if ((SettingManager.Common.HotkeyModifier & Keys.Alt) == Keys.Alt)
1087                     modKey |= HookGlobalHotkey.ModKeys.Alt;
1088                 if ((SettingManager.Common.HotkeyModifier & Keys.Control) == Keys.Control)
1089                     modKey |= HookGlobalHotkey.ModKeys.Ctrl;
1090                 if ((SettingManager.Common.HotkeyModifier & Keys.Shift) == Keys.Shift)
1091                     modKey |= HookGlobalHotkey.ModKeys.Shift;
1092                 if ((SettingManager.Common.HotkeyModifier & Keys.LWin) == Keys.LWin)
1093                     modKey |= HookGlobalHotkey.ModKeys.Win;
1094
1095                 _hookGlobalHotkey.RegisterOriginalHotkey(SettingManager.Common.HotkeyKey, SettingManager.Common.HotkeyValue, modKey);
1096             }
1097
1098             if (SettingManager.Common.IsUseNotifyGrowl)
1099                 gh.RegisterGrowl();
1100
1101             StatusLabel.Text = Properties.Resources.Form1_LoadText1;       //画面右下の状態表示を変更
1102
1103             SetMainWindowTitle();
1104             SetNotifyIconText();
1105
1106             if (!SettingManager.Common.MinimizeToTray || this.WindowState != FormWindowState.Minimized)
1107             {
1108                 this.Visible = true;
1109             }
1110
1111             //タイマー設定
1112
1113             this.timelineScheduler.UpdateHome = () => this.InvokeAsync(() => this.RefreshTabAsync<HomeTabModel>());
1114             this.timelineScheduler.UpdateMention = () => this.InvokeAsync(() => this.RefreshTabAsync<MentionsTabModel>());
1115             this.timelineScheduler.UpdateDm = () => this.InvokeAsync(() => this.RefreshTabAsync<DirectMessagesTabModel>());
1116             this.timelineScheduler.UpdatePublicSearch = () => this.InvokeAsync(() => this.RefreshTabAsync<PublicSearchTabModel>());
1117             this.timelineScheduler.UpdateUser = () => this.InvokeAsync(() => this.RefreshTabAsync<UserTimelineTabModel>());
1118             this.timelineScheduler.UpdateList = () => this.InvokeAsync(() => this.RefreshTabAsync<ListTimelineTabModel>());
1119             this.timelineScheduler.UpdateConfig = () => this.InvokeAsync(() => Task.WhenAll(new[]
1120             {
1121                 this.doGetFollowersMenu(),
1122                 this.RefreshBlockIdsAsync(),
1123                 this.RefreshMuteUserIdsAsync(),
1124                 this.RefreshNoRetweetIdsAsync(),
1125                 this.RefreshTwitterConfigurationAsync(),
1126             }));
1127             this.RefreshTimelineScheduler();
1128
1129             var streamingRefreshInterval = TimeSpan.FromSeconds(SettingManager.Common.UserstreamPeriod);
1130             this.RefreshThrottlingTimer = ThrottlingTimer.Throttle(() => this.InvokeAsync(() => this.RefreshTimeline()), streamingRefreshInterval);
1131             this.colorizeDebouncer = ThrottlingTimer.Debounce(() => this.InvokeAsync(() => this.ColorizeList()), TimeSpan.FromMilliseconds(100), leading: true);
1132             this.selectionDebouncer = ThrottlingTimer.Debounce(() => this.InvokeAsync(() => this.UpdateSelectedPost()), TimeSpan.FromMilliseconds(100), leading: true);
1133             this.saveConfigDebouncer = ThrottlingTimer.Debounce(() => this.InvokeAsync(() => this.SaveConfigsAll(ifModified: true)), TimeSpan.FromSeconds(1));
1134
1135             //更新中アイコンアニメーション間隔
1136             TimerRefreshIcon.Interval = 200;
1137             TimerRefreshIcon.Enabled = false;
1138
1139             _ignoreConfigSave = false;
1140             this.TweenMain_Resize(null, null);
1141             if (saveRequired) SaveConfigsAll(false);
1142
1143             foreach (var ua in SettingManager.Common.UserAccounts)
1144             {
1145                 if (ua.UserId == 0 && ua.Username.Equals(tw.Username, StringComparison.InvariantCultureIgnoreCase))
1146                 {
1147                     ua.UserId = tw.UserId;
1148                     break;
1149                 }
1150             }
1151
1152             if (firstRun)
1153             {
1154                 // 初回起動時だけ右下のメニューを目立たせる
1155                 HashStripSplitButton.ShowDropDown();
1156             }
1157         }
1158
1159         private void InitDetailHtmlFormat()
1160         {
1161             if (SettingManager.Common.IsMonospace)
1162             {
1163                 detailHtmlFormatHeader = detailHtmlFormatHeaderMono;
1164                 detailHtmlFormatFooter = detailHtmlFormatFooterMono;
1165             }
1166             else
1167             {
1168                 detailHtmlFormatHeader = detailHtmlFormatHeaderColor;
1169                 detailHtmlFormatFooter = detailHtmlFormatFooterColor;
1170             }
1171
1172             detailHtmlFormatHeader = detailHtmlFormatHeader
1173                     .Replace("%FONT_FAMILY%", _fntDetail.Name)
1174                     .Replace("%FONT_SIZE%", _fntDetail.Size.ToString())
1175                     .Replace("%FONT_COLOR%", $"{_clDetail.R},{_clDetail.G},{_clDetail.B}")
1176                     .Replace("%LINK_COLOR%", $"{_clDetailLink.R},{_clDetailLink.G},{_clDetailLink.B}")
1177                     .Replace("%BG_COLOR%", $"{_clDetailBackcolor.R},{_clDetailBackcolor.G},{_clDetailBackcolor.B}")
1178                     .Replace("%BG_REPLY_COLOR%", $"{_clAtTo.R}, {_clAtTo.G}, {_clAtTo.B}");
1179         }
1180
1181         private void ListTab_DrawItem(object sender, DrawItemEventArgs e)
1182         {
1183             string txt;
1184             try
1185             {
1186                 txt = this._statuses.Tabs[e.Index].TabName;
1187             }
1188             catch (Exception)
1189             {
1190                 return;
1191             }
1192
1193             e.Graphics.FillRectangle(System.Drawing.SystemBrushes.Control, e.Bounds);
1194             if (e.State == DrawItemState.Selected)
1195             {
1196                 e.DrawFocusRectangle();
1197             }
1198             Brush fore;
1199             try
1200             {
1201                 if (_statuses.Tabs[txt].UnreadCount > 0)
1202                     fore = Brushes.Red;
1203                 else
1204                     fore = System.Drawing.SystemBrushes.ControlText;
1205             }
1206             catch (Exception)
1207             {
1208                 fore = System.Drawing.SystemBrushes.ControlText;
1209             }
1210             e.Graphics.DrawString(txt, e.Font, fore, e.Bounds, sfTab);
1211         }
1212
1213         private void LoadConfig()
1214         {
1215             SettingManager.Local = SettingManager.Local;
1216
1217             // v1.2.4 以前の設定には ScaleDimension の項目がないため、現在の DPI と同じとして扱う
1218             if (SettingManager.Local.ScaleDimension.IsEmpty)
1219                 SettingManager.Local.ScaleDimension = this.CurrentAutoScaleDimensions;
1220
1221             var tabSettings = SettingManager.Tabs;
1222             foreach (var tabSetting in tabSettings.Tabs)
1223             {
1224                 TabModel tab;
1225                 switch (tabSetting.TabType)
1226                 {
1227                     case MyCommon.TabUsageType.Home:
1228                         tab = new HomeTabModel(tabSetting.TabName);
1229                         break;
1230                     case MyCommon.TabUsageType.Mentions:
1231                         tab = new MentionsTabModel(tabSetting.TabName);
1232                         break;
1233                     case MyCommon.TabUsageType.DirectMessage:
1234                         tab = new DirectMessagesTabModel(tabSetting.TabName);
1235                         break;
1236                     case MyCommon.TabUsageType.Favorites:
1237                         tab = new FavoritesTabModel(tabSetting.TabName);
1238                         break;
1239                     case MyCommon.TabUsageType.UserDefined:
1240                         tab = new FilterTabModel(tabSetting.TabName);
1241                         break;
1242                     case MyCommon.TabUsageType.UserTimeline:
1243                         tab = new UserTimelineTabModel(tabSetting.TabName, tabSetting.User);
1244                         break;
1245                     case MyCommon.TabUsageType.PublicSearch:
1246                         tab = new PublicSearchTabModel(tabSetting.TabName)
1247                         {
1248                             SearchWords = tabSetting.SearchWords,
1249                             SearchLang = tabSetting.SearchLang,
1250                         };
1251                         break;
1252                     case MyCommon.TabUsageType.Lists:
1253                         tab = new ListTimelineTabModel(tabSetting.TabName, tabSetting.ListInfo);
1254                         break;
1255                     case MyCommon.TabUsageType.Mute:
1256                         tab = new MuteTabModel(tabSetting.TabName);
1257                         break;
1258                     default:
1259                         continue;
1260                 }
1261
1262                 tab.UnreadManage = tabSetting.UnreadManage;
1263                 tab.Protected = tabSetting.Protected;
1264                 tab.Notify = tabSetting.Notify;
1265                 tab.SoundFile = tabSetting.SoundFile;
1266
1267                 if (tab.IsDistributableTabType)
1268                 {
1269                     var filterTab = (FilterTabModel)tab;
1270                     filterTab.FilterArray = tabSetting.FilterArray;
1271                     filterTab.FilterModified = false;
1272                 }
1273
1274                 if (this._statuses.ContainsTab(tab.TabName))
1275                     tab.TabName = this._statuses.MakeTabName("MyTab");
1276
1277                 this._statuses.AddTab(tab);
1278             }
1279             if (_statuses.Tabs.Count == 0)
1280             {
1281                 _statuses.AddTab(new HomeTabModel());
1282                 _statuses.AddTab(new MentionsTabModel());
1283                 _statuses.AddTab(new DirectMessagesTabModel());
1284                 _statuses.AddTab(new FavoritesTabModel());
1285             }
1286         }
1287
1288         private void TimerInterval_Changed(object sender, IntervalChangedEventArgs e) //Handles SettingDialog.IntervalChanged
1289         {
1290             if (e.UserStream)
1291             {
1292                 var interval = TimeSpan.FromSeconds(SettingManager.Common.UserstreamPeriod);
1293                 var newTimer = ThrottlingTimer.Throttle(() => this.InvokeAsync(() => this.RefreshTimeline()), interval);
1294                 var oldTimer = Interlocked.Exchange(ref this.RefreshThrottlingTimer, newTimer);
1295                 oldTimer.Dispose();
1296             }
1297
1298             this.RefreshTimelineScheduler();
1299         }
1300
1301         private void RefreshTimelineScheduler()
1302         {
1303             this.timelineScheduler.UpdateIntervalHome = TimeSpan.FromSeconds(SettingManager.Common.TimelinePeriod);
1304             this.timelineScheduler.UpdateIntervalMention = TimeSpan.FromSeconds(SettingManager.Common.ReplyPeriod);
1305             this.timelineScheduler.UpdateIntervalDm = TimeSpan.FromSeconds(SettingManager.Common.DMPeriod);
1306             this.timelineScheduler.UpdateIntervalPublicSearch = TimeSpan.FromSeconds(SettingManager.Common.PubSearchPeriod);
1307             this.timelineScheduler.UpdateIntervalUser = TimeSpan.FromSeconds(SettingManager.Common.UserTimelinePeriod);
1308             this.timelineScheduler.UpdateIntervalList = TimeSpan.FromSeconds(SettingManager.Common.ListsPeriod);
1309             this.timelineScheduler.UpdateIntervalConfig = TimeSpan.FromHours(6);
1310             this.timelineScheduler.UpdateAfterSystemResume = TimeSpan.FromSeconds(30);
1311
1312             this.timelineScheduler.RefreshSchedule();
1313         }
1314
1315         private void MarkSettingCommonModified()
1316         {
1317             if (this.saveConfigDebouncer == null)
1318                 return;
1319
1320             this.ModifySettingCommon = true;
1321             this.saveConfigDebouncer.Call();
1322         }
1323
1324         private void MarkSettingLocalModified()
1325         {
1326             if (this.saveConfigDebouncer == null)
1327                 return;
1328
1329             this.ModifySettingLocal = true;
1330             this.saveConfigDebouncer.Call();
1331         }
1332
1333         internal void MarkSettingAtIdModified()
1334         {
1335             if (this.saveConfigDebouncer == null)
1336                 return;
1337
1338             this.ModifySettingAtId = true;
1339             this.saveConfigDebouncer.Call();
1340         }
1341
1342         private void RefreshTimeline()
1343         {
1344             var curTabModel = this.CurrentTab;
1345             var curListView = this.CurrentListView;
1346
1347             // 現在表示中のタブのスクロール位置を退避
1348             var curListScroll = this.SaveListViewScroll(curListView, curTabModel);
1349
1350             // 各タブのリスト上の選択位置などを退避
1351             var listSelections = this.SaveListViewSelection();
1352
1353             //更新確定
1354             int addCount;
1355             addCount = _statuses.SubmitUpdate(out var soundFile, out var notifyPosts,
1356                 out var newMentionOrDm, out var isDelete);
1357
1358             if (MyCommon._endingFlag) return;
1359
1360             // リストに反映&選択状態復元
1361             foreach (var (tab, index) in this._statuses.Tabs.WithIndex())
1362             {
1363                 var tabPage = this.ListTab.TabPages[index];
1364                 var listView = (DetailsListView)tabPage.Tag;
1365
1366                 if (listView.VirtualListSize != tab.AllCount || isDelete)
1367                 {
1368                     using (ControlTransaction.Update(listView))
1369                     {
1370                         if (listView == curListView)
1371                             this.PurgeListViewItemCache();
1372
1373                         try
1374                         {
1375                             // リスト件数更新
1376                             listView.VirtualListSize = tab.AllCount;
1377                         }
1378                         catch (NullReferenceException ex)
1379                         {
1380                             // WinForms 内部で ListView.set_TopItem が発生させている例外
1381                             // https://ja.osdn.net/ticket/browse.php?group_id=6526&tid=36588
1382                             MyCommon.TraceOut(ex, $"TabType: {tab.TabType}, Count: {tab.AllCount}, ListSize: {listView.VirtualListSize}");
1383                         }
1384
1385                         // 選択位置などを復元
1386                         this.RestoreListViewSelection(listView, tab, listSelections[tab.TabName]);
1387                     }
1388                 }
1389             }
1390
1391             if (addCount > 0)
1392             {
1393                 if (SettingManager.Common.TabIconDisp)
1394                 {
1395                     foreach (var (tab, index) in this._statuses.Tabs.WithIndex())
1396                     {
1397                         var tabPage = this.ListTab.TabPages[index];
1398                         if (tab.UnreadCount > 0 && tabPage.ImageIndex != 0)
1399                             tabPage.ImageIndex = 0; // 未読アイコン
1400                     }
1401                 }
1402                 else
1403                 {
1404                     this.ListTab.Refresh();
1405                 }
1406             }
1407
1408             // スクロール位置を復元
1409             this.RestoreListViewScroll(curListView, curTabModel, curListScroll);
1410
1411             //新着通知
1412             NotifyNewPosts(notifyPosts, soundFile, addCount, newMentionOrDm);
1413
1414             SetMainWindowTitle();
1415             if (!StatusLabelUrl.Text.StartsWith("http", StringComparison.Ordinal)) SetStatusLabelUrl();
1416
1417             HashSupl.AddRangeItem(tw.GetHashList());
1418
1419         }
1420
1421         internal struct ListViewScroll
1422         {
1423             public ScrollLockMode ScrollLockMode { get; set; }
1424             public long? TopItemStatusId { get; set; }
1425         }
1426
1427         internal enum ScrollLockMode
1428         {
1429             /// <summary>固定しない</summary>
1430             None,
1431
1432             /// <summary>最上部に固定する</summary>
1433             FixedToTop,
1434
1435             /// <summary>最下部に固定する</summary>
1436             FixedToBottom,
1437
1438             /// <summary><see cref="ListViewScroll.TopItemStatusId"/> の位置に固定する</summary>
1439             FixedToItem,
1440         }
1441
1442         /// <summary>
1443         /// <see cref="ListView"/> のスクロール位置に関する情報を <see cref="ListViewScroll"/> として返します
1444         /// </summary>
1445         private ListViewScroll SaveListViewScroll(DetailsListView listView, TabModel tab)
1446         {
1447             var listScroll = new ListViewScroll
1448             {
1449                 ScrollLockMode = this.GetScrollLockMode(listView),
1450             };
1451
1452             if (listScroll.ScrollLockMode == ScrollLockMode.FixedToItem)
1453             {
1454                 var topItemIndex = listView.TopItem?.Index ?? -1;
1455                 if (topItemIndex != -1 && topItemIndex < tab.AllCount)
1456                     listScroll.TopItemStatusId = tab.GetStatusIdAt(topItemIndex);
1457             }
1458
1459             return listScroll;
1460         }
1461
1462         private ScrollLockMode GetScrollLockMode(DetailsListView listView)
1463         {
1464             if (this._statuses.SortMode == ComparerMode.Id)
1465             {
1466                 if (this._statuses.SortOrder == SortOrder.Ascending)
1467                 {
1468                     // Id昇順
1469                     if (this.ListLockMenuItem.Checked)
1470                         return ScrollLockMode.None;
1471
1472                     // 最下行が表示されていたら、最下行へ強制スクロール。最下行が表示されていなかったら制御しない
1473
1474                     // 一番下に表示されているアイテム
1475                     var bottomItem = listView.GetItemAt(0, listView.ClientSize.Height - 1);
1476                     if (bottomItem == null || bottomItem.Index == listView.VirtualListSize - 1)
1477                         return ScrollLockMode.FixedToBottom;
1478                     else
1479                         return ScrollLockMode.None;
1480                 }
1481                 else
1482                 {
1483                     // Id降順
1484                     if (this.ListLockMenuItem.Checked)
1485                         return ScrollLockMode.FixedToItem;
1486
1487                     // 最上行が表示されていたら、制御しない。最上行が表示されていなかったら、現在表示位置へ強制スクロール
1488                     var topItem = listView.TopItem;
1489                     if (topItem == null || topItem.Index == 0)
1490                         return ScrollLockMode.FixedToTop;
1491                     else
1492                         return ScrollLockMode.FixedToItem;
1493                 }
1494             }
1495             else
1496             {
1497                 return ScrollLockMode.FixedToItem;
1498             }
1499         }
1500
1501         internal struct ListViewSelection
1502         {
1503             public long[] SelectedStatusIds { get; set; }
1504             public long? SelectionMarkStatusId { get; set; }
1505             public long? FocusedStatusId { get; set; }
1506         }
1507
1508         /// <summary>
1509         /// <see cref="ListView"/> の選択状態を <see cref="ListViewSelection"/> として返します
1510         /// </summary>
1511         private IReadOnlyDictionary<string, ListViewSelection> SaveListViewSelection()
1512         {
1513             var listsDict = new Dictionary<string, ListViewSelection>();
1514
1515             foreach (var (tab, index) in this._statuses.Tabs.WithIndex())
1516             {
1517                 var listView = (DetailsListView)this.ListTab.TabPages[index].Tag;
1518                 listsDict[tab.TabName] = this.SaveListViewSelection(listView, tab);
1519             }
1520
1521             return listsDict;
1522         }
1523
1524         /// <summary>
1525         /// <see cref="ListView"/> の選択状態を <see cref="ListViewSelection"/> として返します
1526         /// </summary>
1527         private ListViewSelection SaveListViewSelection(DetailsListView listView, TabModel tab)
1528         {
1529             if (listView.VirtualListSize == 0)
1530             {
1531                 return new ListViewSelection
1532                 {
1533                     SelectedStatusIds = Array.Empty<long>(),
1534                     SelectionMarkStatusId = null,
1535                     FocusedStatusId = null,
1536                 };
1537             }
1538
1539             return new ListViewSelection
1540             {
1541                 SelectedStatusIds = tab.SelectedStatusIds,
1542                 FocusedStatusId = this.GetFocusedStatusId(listView, tab),
1543                 SelectionMarkStatusId = this.GetSelectionMarkStatusId(listView, tab),
1544             };
1545         }
1546
1547         private long? GetFocusedStatusId(DetailsListView listView, TabModel tab)
1548         {
1549             var index = listView.FocusedItem?.Index ?? -1;
1550
1551             return index != -1 && index < tab.AllCount ? tab.GetStatusIdAt(index) : (long?)null;
1552         }
1553
1554         private long? GetSelectionMarkStatusId(DetailsListView listView, TabModel tab)
1555         {
1556             var index = listView.SelectionMark;
1557
1558             return index != -1 && index < tab.AllCount ? tab.GetStatusIdAt(index) : (long?)null;
1559         }
1560
1561         /// <summary>
1562         /// <see cref="SaveListViewScroll"/> によって保存されたスクロール位置を復元します
1563         /// </summary>
1564         private void RestoreListViewScroll(DetailsListView listView, TabModel tab, ListViewScroll listScroll)
1565         {
1566             if (listView.VirtualListSize == 0)
1567                 return;
1568
1569             switch (listScroll.ScrollLockMode)
1570             {
1571                 case ScrollLockMode.FixedToTop:
1572                     listView.EnsureVisible(0);
1573                     break;
1574                 case ScrollLockMode.FixedToBottom:
1575                     listView.EnsureVisible(listView.VirtualListSize - 1);
1576                     break;
1577                 case ScrollLockMode.FixedToItem:
1578                     var topIndex = listScroll.TopItemStatusId != null ? tab.IndexOf(listScroll.TopItemStatusId.Value) : -1;
1579                     if (topIndex != -1)
1580                     {
1581                         var topItem = listView.Items[topIndex];
1582                         try
1583                         {
1584                             listView.TopItem = topItem;
1585                         }
1586                         catch (NullReferenceException)
1587                         {
1588                             listView.EnsureVisible(listView.VirtualListSize - 1);
1589                             listView.EnsureVisible(topIndex);
1590                         }
1591                     }
1592                     break;
1593                 case ScrollLockMode.None:
1594                 default:
1595                     break;
1596             }
1597         }
1598
1599         /// <summary>
1600         /// <see cref="SaveListViewSelection"/> によって保存された選択状態を復元します
1601         /// </summary>
1602         private void RestoreListViewSelection(DetailsListView listView, TabModel tab, ListViewSelection listSelection)
1603         {
1604             // status_id から ListView 上のインデックスに変換
1605             int[] selectedIndices = null;
1606             if (listSelection.SelectedStatusIds != null)
1607                 selectedIndices = tab.IndexOf(listSelection.SelectedStatusIds).Where(x => x != -1).ToArray();
1608
1609             var focusedIndex = -1;
1610             if (listSelection.FocusedStatusId != null)
1611                 focusedIndex = tab.IndexOf(listSelection.FocusedStatusId.Value);
1612
1613             var selectionMarkIndex = -1;
1614             if (listSelection.SelectionMarkStatusId != null)
1615                 selectionMarkIndex = tab.IndexOf(listSelection.SelectionMarkStatusId.Value);
1616
1617             this.SelectListItem(listView, selectedIndices, focusedIndex, selectionMarkIndex);
1618         }
1619
1620         private bool BalloonRequired()
1621         {
1622             var ev = new Twitter.FormattedEvent
1623             {
1624                 Eventtype = MyCommon.EVENTTYPE.None,
1625             };
1626
1627             return BalloonRequired(ev);
1628         }
1629
1630         private bool IsEventNotifyAsEventType(MyCommon.EVENTTYPE type)
1631         {
1632             if (type == MyCommon.EVENTTYPE.None)
1633                 return true;
1634
1635             if (!SettingManager.Common.EventNotifyEnabled)
1636                 return false;
1637
1638             return SettingManager.Common.EventNotifyFlag.HasFlag(type);
1639         }
1640
1641         private bool IsMyEventNotityAsEventType(Twitter.FormattedEvent ev)
1642         {
1643             if (!ev.IsMe)
1644                 return true;
1645
1646             return SettingManager.Common.IsMyEventNotifyFlag.HasFlag(ev.Eventtype);
1647         }
1648
1649         private bool BalloonRequired(Twitter.FormattedEvent ev)
1650         {
1651             if (this._initial)
1652                 return false;
1653
1654             if (NativeMethods.IsScreenSaverRunning())
1655                 return false;
1656
1657             // 「新着通知」が無効
1658             if (!this.NewPostPopMenuItem.Checked)
1659             {
1660                 // 「新着通知が無効でもイベントを通知する」にも該当しない
1661                 if (!SettingManager.Common.ForceEventNotify || ev.Eventtype == MyCommon.EVENTTYPE.None)
1662                     return false;
1663             }
1664
1665             // 「画面最小化・アイコン時のみバルーンを表示する」が有効
1666             if (SettingManager.Common.LimitBalloon)
1667             {
1668                 if (this.WindowState != FormWindowState.Minimized && this.Visible && Form.ActiveForm != null)
1669                     return false;
1670             }
1671
1672             return this.IsEventNotifyAsEventType(ev.Eventtype) && this.IsMyEventNotityAsEventType(ev);
1673         }
1674
1675         private void NotifyNewPosts(PostClass[] notifyPosts, string soundFile, int addCount, bool newMentions)
1676         {
1677             if (SettingManager.Common.ReadOwnPost)
1678             {
1679                 if (notifyPosts != null && notifyPosts.Length > 0 && notifyPosts.All(x => x.UserId == tw.UserId))
1680                     return;
1681             }
1682
1683             //新着通知
1684             if (BalloonRequired())
1685             {
1686                 if (notifyPosts != null && notifyPosts.Length > 0)
1687                 {
1688                     //Growlは一個ずつばらして通知。ただし、3ポスト以上あるときはまとめる
1689                     if (SettingManager.Common.IsUseNotifyGrowl)
1690                     {
1691                         StringBuilder sb = new StringBuilder();
1692                         bool reply = false;
1693                         bool dm = false;
1694
1695                         foreach (PostClass post in notifyPosts)
1696                         {
1697                             if (!(notifyPosts.Length > 3))
1698                             {
1699                                 sb.Clear();
1700                                 reply = false;
1701                                 dm = false;
1702                             }
1703                             if (post.IsReply && !post.IsExcludeReply) reply = true;
1704                             if (post.IsDm) dm = true;
1705                             if (sb.Length > 0) sb.Append(System.Environment.NewLine);
1706                             switch (SettingManager.Common.NameBalloon)
1707                             {
1708                                 case MyCommon.NameBalloonEnum.UserID:
1709                                     sb.Append(post.ScreenName).Append(" : ");
1710                                     break;
1711                                 case MyCommon.NameBalloonEnum.NickName:
1712                                     sb.Append(post.Nickname).Append(" : ");
1713                                     break;
1714                             }
1715                             sb.Append(post.TextFromApi);
1716                             if (notifyPosts.Length > 3)
1717                             {
1718                                 if (notifyPosts.Last() != post) continue;
1719                             }
1720
1721                             StringBuilder title = new StringBuilder();
1722                             GrowlHelper.NotifyType nt;
1723                             if (SettingManager.Common.DispUsername)
1724                             {
1725                                 title.Append(tw.Username);
1726                                 title.Append(" - ");
1727                             }
1728                             else
1729                             {
1730                                 //title.Clear();
1731                             }
1732                             if (dm)
1733                             {
1734                                 //NotifyIcon1.BalloonTipIcon = ToolTipIcon.Warning;
1735                                 //NotifyIcon1.BalloonTipTitle += Application.ProductName + " [DM] " + Properties.Resources.RefreshDirectMessageText1 + " " + addCount.ToString() + Properties.Resources.RefreshDirectMessageText2;
1736                                 title.Append(ApplicationSettings.ApplicationName);
1737                                 title.Append(" [DM] ");
1738                                 title.AppendFormat(Properties.Resources.RefreshTimeline_NotifyText, addCount);
1739                                 nt = GrowlHelper.NotifyType.DirectMessage;
1740                             }
1741                             else if (reply)
1742                             {
1743                                 //NotifyIcon1.BalloonTipIcon = ToolTipIcon.Warning;
1744                                 //NotifyIcon1.BalloonTipTitle += Application.ProductName + " [Reply!] " + Properties.Resources.RefreshTimelineText1 + " " + addCount.ToString() + Properties.Resources.RefreshTimelineText2;
1745                                 title.Append(ApplicationSettings.ApplicationName);
1746                                 title.Append(" [Reply!] ");
1747                                 title.AppendFormat(Properties.Resources.RefreshTimeline_NotifyText, addCount);
1748                                 nt = GrowlHelper.NotifyType.Reply;
1749                             }
1750                             else
1751                             {
1752                                 //NotifyIcon1.BalloonTipIcon = ToolTipIcon.Info;
1753                                 //NotifyIcon1.BalloonTipTitle += Application.ProductName + " " + Properties.Resources.RefreshTimelineText1 + " " + addCount.ToString() + Properties.Resources.RefreshTimelineText2;
1754                                 title.Append(ApplicationSettings.ApplicationName);
1755                                 title.Append(" ");
1756                                 title.AppendFormat(Properties.Resources.RefreshTimeline_NotifyText, addCount);
1757                                 nt = GrowlHelper.NotifyType.Notify;
1758                             }
1759                             string bText = sb.ToString();
1760                             if (string.IsNullOrEmpty(bText)) return;
1761
1762                             var image = this.IconCache.TryGetFromCache(post.ImageUrl);
1763                             gh.Notify(nt, post.StatusId.ToString(), title.ToString(), bText, image?.Image, post.ImageUrl);
1764                         }
1765                     }
1766                     else
1767                     {
1768                         StringBuilder sb = new StringBuilder();
1769                         bool reply = false;
1770                         bool dm = false;
1771                         foreach (PostClass post in notifyPosts)
1772                         {
1773                             if (post.IsReply && !post.IsExcludeReply) reply = true;
1774                             if (post.IsDm) dm = true;
1775                             if (sb.Length > 0) sb.Append(System.Environment.NewLine);
1776                             switch (SettingManager.Common.NameBalloon)
1777                             {
1778                                 case MyCommon.NameBalloonEnum.UserID:
1779                                     sb.Append(post.ScreenName).Append(" : ");
1780                                     break;
1781                                 case MyCommon.NameBalloonEnum.NickName:
1782                                     sb.Append(post.Nickname).Append(" : ");
1783                                     break;
1784                             }
1785                             sb.Append(post.TextFromApi);
1786
1787                         }
1788                         //if (SettingDialog.DispUsername) { NotifyIcon1.BalloonTipTitle = tw.Username + " - "; } else { NotifyIcon1.BalloonTipTitle = ""; }
1789                         StringBuilder title = new StringBuilder();
1790                         ToolTipIcon ntIcon;
1791                         if (SettingManager.Common.DispUsername)
1792                         {
1793                             title.Append(tw.Username);
1794                             title.Append(" - ");
1795                         }
1796                         else
1797                         {
1798                             //title.Clear();
1799                         }
1800                         if (dm)
1801                         {
1802                             //NotifyIcon1.BalloonTipIcon = ToolTipIcon.Warning;
1803                             //NotifyIcon1.BalloonTipTitle += Application.ProductName + " [DM] " + Properties.Resources.RefreshDirectMessageText1 + " " + addCount.ToString() + Properties.Resources.RefreshDirectMessageText2;
1804                             ntIcon = ToolTipIcon.Warning;
1805                             title.Append(ApplicationSettings.ApplicationName);
1806                             title.Append(" [DM] ");
1807                             title.AppendFormat(Properties.Resources.RefreshTimeline_NotifyText, addCount);
1808                         }
1809                         else if (reply)
1810                         {
1811                             //NotifyIcon1.BalloonTipIcon = ToolTipIcon.Warning;
1812                             //NotifyIcon1.BalloonTipTitle += Application.ProductName + " [Reply!] " + Properties.Resources.RefreshTimelineText1 + " " + addCount.ToString() + Properties.Resources.RefreshTimelineText2;
1813                             ntIcon = ToolTipIcon.Warning;
1814                             title.Append(ApplicationSettings.ApplicationName);
1815                             title.Append(" [Reply!] ");
1816                             title.AppendFormat(Properties.Resources.RefreshTimeline_NotifyText, addCount);
1817                         }
1818                         else
1819                         {
1820                             //NotifyIcon1.BalloonTipIcon = ToolTipIcon.Info;
1821                             //NotifyIcon1.BalloonTipTitle += Application.ProductName + " " + Properties.Resources.RefreshTimelineText1 + " " + addCount.ToString() + Properties.Resources.RefreshTimelineText2;
1822                             ntIcon = ToolTipIcon.Info;
1823                             title.Append(ApplicationSettings.ApplicationName);
1824                             title.Append(" ");
1825                             title.AppendFormat(Properties.Resources.RefreshTimeline_NotifyText, addCount);
1826                         }
1827                         string bText = sb.ToString();
1828                         if (string.IsNullOrEmpty(bText)) return;
1829                         //NotifyIcon1.BalloonTipText = sb.ToString();
1830                         //NotifyIcon1.ShowBalloonTip(500);
1831                         NotifyIcon1.BalloonTipTitle = title.ToString();
1832                         NotifyIcon1.BalloonTipText = bText;
1833                         NotifyIcon1.BalloonTipIcon = ntIcon;
1834                         NotifyIcon1.ShowBalloonTip(500);
1835                     }
1836                 }
1837             }
1838
1839             //サウンド再生
1840             if (!_initial && SettingManager.Common.PlaySound && !string.IsNullOrEmpty(soundFile))
1841             {
1842                 try
1843                 {
1844                     string dir = Application.StartupPath;
1845                     if (Directory.Exists(Path.Combine(dir, "Sounds")))
1846                     {
1847                         dir = Path.Combine(dir, "Sounds");
1848                     }
1849                     using (SoundPlayer player = new SoundPlayer(Path.Combine(dir, soundFile)))
1850                     {
1851                         player.Play();
1852                     }
1853                 }
1854                 catch (Exception)
1855                 {
1856                 }
1857             }
1858
1859             //mentions新着時に画面ブリンク
1860             if (!_initial && SettingManager.Common.BlinkNewMentions && newMentions && Form.ActiveForm == null)
1861             {
1862                 NativeMethods.FlashMyWindow(this.Handle, NativeMethods.FlashSpecification.FlashTray, 3);
1863             }
1864         }
1865
1866         private void MyList_SelectedIndexChanged(object sender, EventArgs e)
1867         {
1868             var listView = this.CurrentListView;
1869             if (listView != sender)
1870                 return;
1871
1872             var indices = listView.SelectedIndices.Cast<int>().ToArray();
1873             this.CurrentTab.SelectPosts(indices);
1874
1875             if (listView.SelectedIndices.Count != 1)
1876                 return;
1877
1878             var index = listView.SelectedIndices[0];
1879             if (index > listView.VirtualListSize - 1) return;
1880
1881             this.PushSelectPostChain();
1882
1883             var post = this.CurrentPost;
1884             this._statuses.SetReadAllTab(post.StatusId, read: true);
1885
1886             //キャッシュの書き換え
1887             ChangeCacheStyleRead(true, index); // 既読へ(フォント、文字色)
1888
1889             this.colorizeDebouncer.Call();
1890             this.selectionDebouncer.Call();
1891         }
1892
1893         private void ChangeCacheStyleRead(bool Read, int Index)
1894         {
1895             var tabInfo = this.CurrentTab;
1896             //Read:true=既読 false=未読
1897             //未読管理していなかったら既読として扱う
1898             if (!tabInfo.UnreadManage ||
1899                !SettingManager.Common.UnreadManage) Read = true;
1900
1901             var listCache = this._listItemCache;
1902             if (listCache == null)
1903                 return;
1904
1905             // キャッシュに含まれていないアイテムは対象外
1906             if (!listCache.TryGetValue(Index, out var itm, out var post))
1907                 return;
1908
1909             ChangeItemStyleRead(Read, itm, post, (DetailsListView)listCache.TargetList);
1910         }
1911
1912         private void ChangeItemStyleRead(bool Read, ListViewItem Item, PostClass Post, DetailsListView DList)
1913         {
1914             Font fnt;
1915             string star;
1916             //フォント
1917             if (Read)
1918             {
1919                 fnt = _fntReaded;
1920                 star = "";
1921             }
1922             else
1923             {
1924                 fnt = _fntUnread;
1925                 star = "★";
1926             }
1927             if (Item.SubItems[5].Text != star)
1928                 Item.SubItems[5].Text = star;
1929
1930             //文字色
1931             Color cl;
1932             if (Post.IsFav)
1933                 cl = _clFav;
1934             else if (Post.RetweetedId != null)
1935                 cl = _clRetweet;
1936             else if (Post.IsOwl && (Post.IsDm || SettingManager.Common.OneWayLove))
1937                 cl = _clOWL;
1938             else if (Read || !SettingManager.Common.UseUnreadStyle)
1939                 cl = _clReaded;
1940             else
1941                 cl = _clUnread;
1942
1943             if (DList == null || Item.Index == -1)
1944             {
1945                 Item.ForeColor = cl;
1946                 if (SettingManager.Common.UseUnreadStyle)
1947                     Item.Font = fnt;
1948             }
1949             else
1950             {
1951                 DList.Update();
1952                 if (SettingManager.Common.UseUnreadStyle)
1953                     DList.ChangeItemFontAndColor(Item, cl, fnt);
1954                 else
1955                     DList.ChangeItemForeColor(Item, cl);
1956                 //if (_itemCache != null) DList.RedrawItems(_itemCacheIndex, _itemCacheIndex + _itemCache.Length - 1, false);
1957             }
1958         }
1959
1960         private void ColorizeList()
1961         {
1962             //Index:更新対象のListviewItem.Index。Colorを返す。
1963             //-1は全キャッシュ。Colorは返さない(ダミーを戻す)
1964             PostClass _post;
1965             if (_anchorFlag)
1966                 _post = _anchorPost;
1967             else
1968                 _post = this.CurrentPost;
1969
1970             if (_post == null) return;
1971
1972             var listCache = this._listItemCache;
1973             if (listCache == null)
1974                 return;
1975
1976             var listView = (DetailsListView)listCache.TargetList;
1977
1978             // ValidateRectが呼ばれる前に選択色などの描画を済ませておく
1979             listView.Update();
1980
1981             foreach (var (listViewItem, cachedPost) in listCache.Cache)
1982             {
1983                 var backColor = this.JudgeColor(_post, cachedPost);
1984                 listView.ChangeItemBackColor(listViewItem, backColor);
1985             }
1986         }
1987
1988         private void ColorizeList(ListViewItem Item, PostClass post, int Index)
1989         {
1990             //Index:更新対象のListviewItem.Index。Colorを返す。
1991             //-1は全キャッシュ。Colorは返さない(ダミーを戻す)
1992             PostClass _post;
1993             if (_anchorFlag)
1994                 _post = _anchorPost;
1995             else
1996                 _post = this.CurrentPost;
1997
1998             if (_post == null) return;
1999
2000             if (Item.Index == -1)
2001                 Item.BackColor = JudgeColor(_post, post);
2002             else
2003                 this.CurrentListView.ChangeItemBackColor(Item, JudgeColor(_post, post));
2004         }
2005
2006         private Color JudgeColor(PostClass BasePost, PostClass TargetPost)
2007         {
2008             Color cl;
2009             if (TargetPost.StatusId == BasePost.InReplyToStatusId)
2010                 //@先
2011                 cl = _clAtTo;
2012             else if (TargetPost.IsMe)
2013                 //自分=発言者
2014                 cl = _clSelf;
2015             else if (TargetPost.IsReply)
2016                 //自分宛返信
2017                 cl = _clAtSelf;
2018             else if (BasePost.ReplyToList.Any(x => x.UserId == TargetPost.UserId))
2019                 //返信先
2020                 cl = _clAtFromTarget;
2021             else if (TargetPost.ReplyToList.Any(x => x.UserId == BasePost.UserId))
2022                 //その人への返信
2023                 cl = _clAtTarget;
2024             else if (TargetPost.UserId == BasePost.UserId)
2025                 //発言者
2026                 cl = _clTarget;
2027             else
2028                 //その他
2029                 cl = _clListBackcolor;
2030
2031             return cl;
2032         }
2033
2034         private void StatusTextHistoryBack()
2035         {
2036             if (!string.IsNullOrWhiteSpace(this.StatusText.Text))
2037                 this._history[_hisIdx] = new StatusTextHistory(this.StatusText.Text, this.inReplyTo);
2038
2039             this._hisIdx -= 1;
2040             if (this._hisIdx < 0)
2041                 this._hisIdx = 0;
2042
2043             var historyItem = this._history[this._hisIdx];
2044             this.inReplyTo = historyItem.inReplyTo;
2045             this.StatusText.Text = historyItem.status;
2046             this.StatusText.SelectionStart = this.StatusText.Text.Length;
2047         }
2048
2049         private void StatusTextHistoryForward()
2050         {
2051             if (!string.IsNullOrWhiteSpace(this.StatusText.Text))
2052                 this._history[this._hisIdx] = new StatusTextHistory(this.StatusText.Text, this.inReplyTo);
2053
2054             this._hisIdx += 1;
2055             if (this._hisIdx > this._history.Count - 1)
2056                 this._hisIdx = this._history.Count - 1;
2057
2058             var historyItem = this._history[this._hisIdx];
2059             this.inReplyTo = historyItem.inReplyTo;
2060             this.StatusText.Text = historyItem.status;
2061             this.StatusText.SelectionStart = this.StatusText.Text.Length;
2062         }
2063
2064         private async void PostButton_Click(object sender, EventArgs e)
2065         {
2066             if (StatusText.Text.Trim().Length == 0)
2067             {
2068                 if (!ImageSelector.Enabled)
2069                 {
2070                     await this.DoRefresh();
2071                     return;
2072                 }
2073             }
2074
2075             var currentPost = this.CurrentPost;
2076             if (this.ExistCurrentPost && StatusText.Text.Trim() == string.Format("RT @{0}: {1}", currentPost.ScreenName, currentPost.TextFromApi))
2077             {
2078                 DialogResult rtResult = MessageBox.Show(string.Format(Properties.Resources.PostButton_Click1, Environment.NewLine),
2079                                                                "Retweet",
2080                                                                MessageBoxButtons.YesNoCancel,
2081                                                                MessageBoxIcon.Question);
2082                 switch (rtResult)
2083                 {
2084                     case DialogResult.Yes:
2085                         StatusText.Text = "";
2086                         await this.doReTweetOfficial(false);
2087                         return;
2088                     case DialogResult.Cancel:
2089                         return;
2090                 }
2091             }
2092
2093             if (TextContainsOnlyMentions(this.StatusText.Text))
2094             {
2095                 var message = string.Format(Properties.Resources.PostConfirmText, this.StatusText.Text);
2096                 var ret = MessageBox.Show(message, ApplicationSettings.ApplicationName, MessageBoxButtons.OKCancel, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2);
2097
2098                 if (ret != DialogResult.OK)
2099                     return;
2100             }
2101
2102             _history[_history.Count - 1] = new StatusTextHistory(StatusText.Text, this.inReplyTo);
2103
2104             if (SettingManager.Common.Nicoms)
2105             {
2106                 StatusText.SelectionStart = StatusText.Text.Length;
2107                 await UrlConvertAsync(MyCommon.UrlConverter.Nicoms);
2108             }
2109             //if (SettingDialog.UrlConvertAuto)
2110             //{
2111             //    StatusText.SelectionStart = StatusText.Text.Length;
2112             //    UrlConvertAutoToolStripMenuItem_Click(null, null);
2113             //}
2114             //else if (SettingDialog.Nicoms)
2115             //{
2116             //    StatusText.SelectionStart = StatusText.Text.Length;
2117             //    UrlConvert(UrlConverter.Nicoms);
2118             //}
2119             StatusText.SelectionStart = StatusText.Text.Length;
2120             CheckReplyTo(StatusText.Text);
2121
2122             var status = new PostStatusParams();
2123
2124             var statusTextCompat = this.FormatStatusText(this.StatusText.Text);
2125             if (this.GetRestStatusCount(statusTextCompat) >= 0)
2126             {
2127                 // auto_populate_reply_metadata や attachment_url を使用しなくても 140 字以内に
2128                 // 収まる場合はこれらのオプションを使用せずに投稿する
2129                 status.Text = statusTextCompat;
2130                 status.InReplyToStatusId = this.inReplyTo?.StatusId;
2131             }
2132             else
2133             {
2134                 status.Text = this.FormatStatusTextExtended(this.StatusText.Text, out var autoPopulatedUserIds, out var attachmentUrl);
2135                 status.InReplyToStatusId = this.inReplyTo?.StatusId;
2136
2137                 status.AttachmentUrl = attachmentUrl;
2138
2139                 // リプライ先がセットされていても autoPopulatedUserIds が空の場合は auto_populate_reply_metadata を有効にしない
2140                 //  (非公式 RT の場合など)
2141                 var replyToPost = this.inReplyTo != null ? this._statuses[this.inReplyTo.Value.StatusId] : null;
2142                 if (replyToPost != null && autoPopulatedUserIds.Length != 0)
2143                 {
2144                     status.AutoPopulateReplyMetadata = true;
2145
2146                     // ReplyToList のうち autoPopulatedUserIds に含まれていないユーザー ID を抽出
2147                     status.ExcludeReplyUserIds = replyToPost.ReplyToList.Select(x => x.UserId).Except(autoPopulatedUserIds)
2148                         .ToArray();
2149                 }
2150             }
2151
2152             if (this.GetRestStatusCount(status.Text) < 0)
2153             {
2154                 // 文字数制限を超えているが強制的に投稿するか
2155                 var ret = MessageBox.Show(Properties.Resources.PostLengthOverMessage1, Properties.Resources.PostLengthOverMessage2, MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2);
2156                 if (ret != DialogResult.OK)
2157                     return;
2158             }
2159
2160             IMediaUploadService uploadService = null;
2161             IMediaItem[] uploadItems = null;
2162             if (ImageSelector.Visible)
2163             {
2164                 //画像投稿
2165                 if (!ImageSelector.TryGetSelectedMedia(out var serviceName, out uploadItems))
2166                     return;
2167
2168                 uploadService = this.ImageSelector.GetService(serviceName);
2169             }
2170
2171             this.inReplyTo = null;
2172             StatusText.Text = "";
2173             _history.Add(new StatusTextHistory());
2174             _hisIdx = _history.Count - 1;
2175             if (!SettingManager.Common.FocusLockToStatusText)
2176                 this.CurrentListView.Focus();
2177             urlUndoBuffer = null;
2178             UrlUndoToolStripMenuItem.Enabled = false;  //Undoをできないように設定
2179
2180             //Google検索(試験実装)
2181             if (StatusText.Text.StartsWith("Google:", StringComparison.OrdinalIgnoreCase) && StatusText.Text.Trim().Length > 7)
2182             {
2183                 string tmp = string.Format(Properties.Resources.SearchItem2Url, Uri.EscapeDataString(StatusText.Text.Substring(7)));
2184                 await this.OpenUriInBrowserAsync(tmp);
2185             }
2186
2187             await this.PostMessageAsync(status, uploadService, uploadItems);
2188         }
2189
2190         private void EndToolStripMenuItem_Click(object sender, EventArgs e)
2191         {
2192             MyCommon._endingFlag = true;
2193             this.Close();
2194         }
2195
2196         private void TweenMain_FormClosing(object sender, FormClosingEventArgs e)
2197         {
2198             if (!SettingManager.Common.CloseToExit && e.CloseReason == CloseReason.UserClosing && MyCommon._endingFlag == false)
2199             {
2200                 //_endingFlag=false:フォームの×ボタン
2201                 e.Cancel = true;
2202                 this.Visible = false;
2203             }
2204             else
2205             {
2206                 _hookGlobalHotkey.UnregisterAllOriginalHotkey();
2207                 _ignoreConfigSave = true;
2208                 MyCommon._endingFlag = true;
2209                 this.timelineScheduler.Enabled = false;
2210                 TimerRefreshIcon.Enabled = false;
2211             }
2212         }
2213
2214         private void NotifyIcon1_BalloonTipClicked(object sender, EventArgs e)
2215         {
2216             this.Visible = true;
2217             if (this.WindowState == FormWindowState.Minimized)
2218             {
2219                 this.WindowState = FormWindowState.Normal;
2220             }
2221             this.Activate();
2222             this.BringToFront();
2223         }
2224
2225         private static int errorCount = 0;
2226
2227         private static bool CheckAccountValid()
2228         {
2229             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid)
2230             {
2231                 errorCount += 1;
2232                 if (errorCount > 5)
2233                 {
2234                     errorCount = 0;
2235                     Twitter.AccountState = MyCommon.ACCOUNT_STATE.Valid;
2236                     return true;
2237                 }
2238                 return false;
2239             }
2240             errorCount = 0;
2241             return true;
2242         }
2243
2244         /// <summary>指定された型 <typeparamref name="T"/> に合致する全てのタブを更新します</summary>
2245         private Task RefreshTabAsync<T>() where T : TabModel
2246             => this.RefreshTabAsync<T>(backward: false);
2247
2248         /// <summary>指定された型 <typeparamref name="T"/> に合致する全てのタブを更新します</summary>
2249         private Task RefreshTabAsync<T>(bool backward) where T : TabModel
2250         {
2251             var loadTasks =
2252                 from tab in this._statuses.GetTabsByType<T>()
2253                 select this.RefreshTabAsync(tab, backward);
2254
2255             return Task.WhenAll(loadTasks);
2256         }
2257
2258         /// <summary>指定されたタブ <paramref name="tab"/> を更新します</summary>
2259         private Task RefreshTabAsync(TabModel tab)
2260             => this.RefreshTabAsync(tab, backward: false);
2261
2262         /// <summary>指定されたタブ <paramref name="tab"/> を更新します</summary>
2263         private async Task RefreshTabAsync(TabModel tab, bool backward)
2264         {
2265             await this.workerSemaphore.WaitAsync();
2266             this.RefreshTasktrayIcon();
2267
2268             try
2269             {
2270                 await Task.Run(() => tab.RefreshAsync(this.tw, backward, this._initial, this.workerProgress));
2271                 this.RefreshTimeline();
2272             }
2273             catch (WebApiException ex)
2274             {
2275                 this._myStatusError = true;
2276
2277                 string tabType;
2278                 switch (tab)
2279                 {
2280                     case HomeTabModel _:
2281                         tabType = "GetTimeline";
2282                         break;
2283                     case MentionsTabModel _:
2284                         tabType = "GetTimeline";
2285                         break;
2286                     case DirectMessagesTabModel _:
2287                         tabType = "GetDirectMessage";
2288                         break;
2289                     case FavoritesTabModel _:
2290                         tabType = "GetFavorites";
2291                         break;
2292                     case PublicSearchTabModel _:
2293                         tabType = "GetSearch";
2294                         break;
2295                     case UserTimelineTabModel _:
2296                         tabType = "GetUserTimeline";
2297                         break;
2298                     case ListTimelineTabModel _:
2299                         tabType = "GetListStatus";
2300                         break;
2301                     case RelatedPostsTabModel _:
2302                         tabType = "GetRelatedTweets";
2303                         break;
2304                     default:
2305                         tabType = tab.GetType().Name.Replace("Model", "");
2306                         break;
2307                 }
2308
2309                 this.StatusLabel.Text = $"Err:{ex.Message}({tabType})";
2310             }
2311             finally
2312             {
2313                 this.workerSemaphore.Release();
2314             }
2315         }
2316
2317         private async Task FavAddAsync(long statusId, TabModel tab)
2318         {
2319             await this.workerSemaphore.WaitAsync();
2320             this.RefreshTasktrayIcon();
2321
2322             try
2323             {
2324                 var progress = new Progress<string>(x => this.StatusLabel.Text = x);
2325
2326                 await this.FavAddAsyncInternal(progress, this.workerCts.Token, statusId, tab);
2327             }
2328             catch (WebApiException ex)
2329             {
2330                 this._myStatusError = true;
2331                 this.StatusLabel.Text = $"Err:{ex.Message}(PostFavAdd)";
2332             }
2333             finally
2334             {
2335                 this.workerSemaphore.Release();
2336             }
2337         }
2338
2339         private async Task FavAddAsyncInternal(IProgress<string> p, CancellationToken ct, long statusId, TabModel tab)
2340         {
2341             if (ct.IsCancellationRequested)
2342                 return;
2343
2344             if (!CheckAccountValid())
2345                 throw new WebApiException("Auth error. Check your account");
2346
2347             if (!tab.Posts.TryGetValue(statusId, out var post))
2348                 return;
2349
2350             if (post.IsFav)
2351                 return;
2352
2353             await Task.Run(async () =>
2354             {
2355                 p.Report(string.Format(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText15, 0, 1, 0));
2356
2357                 try
2358                 {
2359                     try
2360                     {
2361                         await this.twitterApi.FavoritesCreate(post.RetweetedId ?? post.StatusId)
2362                             .IgnoreResponse()
2363                             .ConfigureAwait(false);
2364                     }
2365                     catch (TwitterApiException ex)
2366                         when (ex.ErrorResponse.Errors.All(x => x.Code == TwitterErrorCode.AlreadyFavorited))
2367                     {
2368                         // エラーコード 139 のみの場合は成功と見なす
2369                     }
2370
2371                     if (SettingManager.Common.RestrictFavCheck)
2372                     {
2373                         var status = await this.twitterApi.StatusesShow(post.RetweetedId ?? post.StatusId)
2374                             .ConfigureAwait(false);
2375
2376                         if (status.Favorited != true)
2377                             throw new WebApiException("NG(Restricted?)");
2378                     }
2379
2380                     this._favTimestamps.Add(DateTimeUtc.Now);
2381
2382                     // TLでも取得済みならfav反映
2383                     if (this._statuses.ContainsKey(statusId))
2384                     {
2385                         var postTl = this._statuses[statusId];
2386                         postTl.IsFav = true;
2387
2388                         var favTab = this._statuses.GetTabByType(MyCommon.TabUsageType.Favorites);
2389                         favTab.AddPostQueue(postTl);
2390                     }
2391
2392                     // 検索,リスト,UserTimeline,Relatedの各タブに反映
2393                     foreach (var tb in this._statuses.GetTabsInnerStorageType())
2394                     {
2395                         if (tb.Contains(statusId))
2396                             tb.Posts[statusId].IsFav = true;
2397                     }
2398
2399                     p.Report(string.Format(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText15, 1, 1, 0));
2400                 }
2401                 catch (WebApiException)
2402                 {
2403                     p.Report(string.Format(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText15, 1, 1, 1));
2404                     throw;
2405                 }
2406
2407                 // 時速表示用
2408                 var oneHour = DateTimeUtc.Now - TimeSpan.FromHours(1);
2409                 foreach (var i in MyCommon.CountDown(this._favTimestamps.Count - 1, 0))
2410                 {
2411                     if (this._favTimestamps[i] < oneHour)
2412                         this._favTimestamps.RemoveAt(i);
2413                 }
2414
2415                 this._statuses.DistributePosts();
2416             });
2417
2418             if (ct.IsCancellationRequested)
2419                 return;
2420
2421             this.RefreshTimeline();
2422
2423             if (this.CurrentTabName == tab.TabName)
2424             {
2425                 using (ControlTransaction.Update(this.CurrentListView))
2426                 {
2427                     var idx = tab.IndexOf(statusId);
2428                     if (idx != -1)
2429                         this.ChangeCacheStyleRead(post.IsRead, idx);
2430                 }
2431
2432                 var currentPost = this.CurrentPost;
2433                 if (currentPost != null && statusId == currentPost.StatusId)
2434                     this.DispSelectedPost(true); // 選択アイテム再表示
2435             }
2436         }
2437
2438         private async Task FavRemoveAsync(IReadOnlyList<long> statusIds, TabModel tab)
2439         {
2440             await this.workerSemaphore.WaitAsync();
2441             this.RefreshTasktrayIcon();
2442
2443             try
2444             {
2445                 var progress = new Progress<string>(x => this.StatusLabel.Text = x);
2446
2447                 await this.FavRemoveAsyncInternal(progress, this.workerCts.Token, statusIds, tab);
2448             }
2449             catch (WebApiException ex)
2450             {
2451                 this._myStatusError = true;
2452                 this.StatusLabel.Text = $"Err:{ex.Message}(PostFavRemove)";
2453             }
2454             finally
2455             {
2456                 this.workerSemaphore.Release();
2457             }
2458         }
2459
2460         private async Task FavRemoveAsyncInternal(IProgress<string> p, CancellationToken ct, IReadOnlyList<long> statusIds, TabModel tab)
2461         {
2462             if (ct.IsCancellationRequested)
2463                 return;
2464
2465             if (!CheckAccountValid())
2466                 throw new WebApiException("Auth error. Check your account");
2467
2468             var successIds = new List<long>();
2469
2470             await Task.Run(async () =>
2471             {
2472                 //スレッド処理はしない
2473                 var allCount = 0;
2474                 var failedCount = 0;
2475                 foreach (var statusId in statusIds)
2476                 {
2477                     allCount++;
2478
2479                     var post = tab.Posts[statusId];
2480
2481                     p.Report(string.Format(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText17, allCount, statusIds.Count, failedCount));
2482
2483                     if (!post.IsFav)
2484                         continue;
2485
2486                     try
2487                     {
2488                         await this.twitterApi.FavoritesDestroy(post.RetweetedId ?? post.StatusId)
2489                             .IgnoreResponse()
2490                             .ConfigureAwait(false);
2491                     }
2492                     catch (WebApiException)
2493                     {
2494                         failedCount++;
2495                         continue;
2496                     }
2497
2498                     successIds.Add(statusId);
2499                     post.IsFav = false; // リスト再描画必要
2500
2501                     if (this._statuses.ContainsKey(statusId))
2502                     {
2503                         this._statuses[statusId].IsFav = false;
2504                     }
2505
2506                     // 検索,リスト,UserTimeline,Relatedの各タブに反映
2507                     foreach (var tb in this._statuses.GetTabsInnerStorageType())
2508                     {
2509                         if (tb.Contains(statusId))
2510                             tb.Posts[statusId].IsFav = false;
2511                     }
2512                 }
2513             });
2514
2515             if (ct.IsCancellationRequested)
2516                 return;
2517
2518             var favTab = this._statuses.GetTabByType(MyCommon.TabUsageType.Favorites);
2519             foreach (var statusId in successIds)
2520             {
2521                 // ツイートが削除された訳ではないので IsDeleted はセットしない
2522                 favTab.EnqueueRemovePost(statusId, setIsDeleted: false);
2523             }
2524
2525             this.RefreshTimeline();
2526
2527             if (this.CurrentTabName == tab.TabName)
2528             {
2529                 if (tab.TabType == MyCommon.TabUsageType.Favorites)
2530                 {
2531                     // 色変えは不要
2532                 }
2533                 else
2534                 {
2535                     using (ControlTransaction.Update(this.CurrentListView))
2536                     {
2537                         foreach (var statusId in successIds)
2538                         {
2539                             var idx = tab.IndexOf(statusId);
2540                             if (idx == -1)
2541                                 continue;
2542
2543                             var post = tab.Posts[statusId];
2544                             this.ChangeCacheStyleRead(post.IsRead, idx);
2545                         }
2546                     }
2547
2548                     var currentPost = this.CurrentPost;
2549                     if (currentPost != null && successIds.Contains(currentPost.StatusId))
2550                         this.DispSelectedPost(true); // 選択アイテム再表示
2551                 }
2552             }
2553         }
2554
2555         private async Task PostMessageAsync(PostStatusParams postParams, IMediaUploadService uploadService, IMediaItem[] uploadItems)
2556         {
2557             await this.workerSemaphore.WaitAsync();
2558             this.RefreshTasktrayIcon();
2559
2560             try
2561             {
2562                 var progress = new Progress<string>(x => this.StatusLabel.Text = x);
2563
2564                 await this.PostMessageAsyncInternal(progress, this.workerCts.Token, postParams, uploadService, uploadItems);
2565             }
2566             catch (WebApiException ex)
2567             {
2568                 this._myStatusError = true;
2569                 this.StatusLabel.Text = $"Err:{ex.Message}(PostMessage)";
2570             }
2571             finally
2572             {
2573                 this.workerSemaphore.Release();
2574             }
2575         }
2576
2577         private async Task PostMessageAsyncInternal(IProgress<string> p, CancellationToken ct, PostStatusParams postParams,
2578             IMediaUploadService uploadService, IMediaItem[] uploadItems)
2579         {
2580             if (ct.IsCancellationRequested)
2581                 return;
2582
2583             if (!CheckAccountValid())
2584                 throw new WebApiException("Auth error. Check your account");
2585
2586             p.Report("Posting...");
2587
2588             PostClass post = null;
2589             var errMsg = "";
2590
2591             try
2592             {
2593                 await Task.Run(async () =>
2594                 {
2595                     var postParamsWithMedia = postParams;
2596
2597                     if (uploadService != null && uploadItems != null && uploadItems.Length > 0)
2598                     {
2599                         postParamsWithMedia = await uploadService.UploadAsync(uploadItems, postParamsWithMedia)
2600                             .ConfigureAwait(false);
2601                     }
2602
2603                     post = await this.tw.PostStatus(postParamsWithMedia)
2604                         .ConfigureAwait(false);
2605                 });
2606
2607                 p.Report(Properties.Resources.PostWorker_RunWorkerCompletedText4);
2608             }
2609             catch (WebApiException ex)
2610             {
2611                 // 処理は中断せずエラーの表示のみ行う
2612                 errMsg = $"Err:{ex.Message}(PostMessage)";
2613                 p.Report(errMsg);
2614                 this._myStatusError = true;
2615             }
2616             catch (UnauthorizedAccessException ex)
2617             {
2618                 // アップロード対象のファイルが開けなかった場合など
2619                 errMsg = $"Err:{ex.Message}(PostMessage)";
2620                 p.Report(errMsg);
2621                 this._myStatusError = true;
2622             }
2623             finally
2624             {
2625                 // 使い終わった MediaItem は破棄する
2626                 if (uploadItems != null)
2627                 {
2628                     foreach (var disposableItem in uploadItems.OfType<IDisposable>())
2629                     {
2630                         disposableItem.Dispose();
2631                     }
2632                 }
2633             }
2634
2635             if (ct.IsCancellationRequested)
2636                 return;
2637
2638             if (!string.IsNullOrEmpty(errMsg) &&
2639                 !errMsg.StartsWith("OK:", StringComparison.Ordinal) &&
2640                 !errMsg.StartsWith("Warn:", StringComparison.Ordinal))
2641             {
2642                 var message = string.Format(Properties.Resources.StatusUpdateFailed, errMsg, postParams.Text);
2643
2644                 var ret = MessageBox.Show(
2645                     message,
2646                     "Failed to update status",
2647                     MessageBoxButtons.RetryCancel,
2648                     MessageBoxIcon.Question);
2649
2650                 if (ret == DialogResult.Retry)
2651                 {
2652                     await this.PostMessageAsync(postParams, uploadService, uploadItems);
2653                 }
2654                 else
2655                 {
2656                     this.StatusTextHistoryBack();
2657                     this.StatusText.Focus();
2658
2659                     // 連投モードのときだけEnterイベントが起きないので強制的に背景色を戻す
2660                     if (SettingManager.Common.FocusLockToStatusText)
2661                         this.StatusText_Enter(this.StatusText, EventArgs.Empty);
2662                 }
2663                 return;
2664             }
2665
2666             this._postTimestamps.Add(DateTimeUtc.Now);
2667
2668             var oneHour = DateTimeUtc.Now - TimeSpan.FromHours(1);
2669             foreach (var i in MyCommon.CountDown(this._postTimestamps.Count - 1, 0))
2670             {
2671                 if (this._postTimestamps[i] < oneHour)
2672                     this._postTimestamps.RemoveAt(i);
2673             }
2674
2675             if (!this.HashMgr.IsPermanent && !string.IsNullOrEmpty(this.HashMgr.UseHash))
2676             {
2677                 this.HashMgr.ClearHashtag();
2678                 this.HashStripSplitButton.Text = "#[-]";
2679                 this.HashTogglePullDownMenuItem.Checked = false;
2680                 this.HashToggleMenuItem.Checked = false;
2681             }
2682
2683             this.SetMainWindowTitle();
2684
2685             // TLに反映
2686             if (!this.tw.UserStreamActive)
2687             {
2688                 if (SettingManager.Common.PostAndGet)
2689                     await this.RefreshTabAsync<HomeTabModel>();
2690                 else
2691                 {
2692                     if (post != null)
2693                     {
2694                         this._statuses.AddPost(post);
2695                         this._statuses.DistributePosts();
2696                     }
2697                     this.RefreshTimeline();
2698                 }
2699             }
2700         }
2701
2702         private async Task RetweetAsync(IReadOnlyList<long> statusIds)
2703         {
2704             await this.workerSemaphore.WaitAsync();
2705             this.RefreshTasktrayIcon();
2706
2707             try
2708             {
2709                 var progress = new Progress<string>(x => this.StatusLabel.Text = x);
2710
2711                 await this.RetweetAsyncInternal(progress, this.workerCts.Token, statusIds);
2712             }
2713             catch (WebApiException ex)
2714             {
2715                 this._myStatusError = true;
2716                 this.StatusLabel.Text = $"Err:{ex.Message}(PostRetweet)";
2717             }
2718             finally
2719             {
2720                 this.workerSemaphore.Release();
2721             }
2722         }
2723
2724         private async Task RetweetAsyncInternal(IProgress<string> p, CancellationToken ct, IReadOnlyList<long> statusIds)
2725         {
2726             if (ct.IsCancellationRequested)
2727                 return;
2728
2729             if (!CheckAccountValid())
2730                 throw new WebApiException("Auth error. Check your account");
2731
2732             bool read;
2733             if (!SettingManager.Common.UnreadManage)
2734                 read = true;
2735             else
2736                 read = this._initial && SettingManager.Common.Read;
2737
2738             p.Report("Posting...");
2739
2740             var posts = new List<PostClass>();
2741
2742             await Task.Run(async () =>
2743             {
2744                 foreach (var statusId in statusIds)
2745                 {
2746                     var post = await this.tw.PostRetweet(statusId, read).ConfigureAwait(false);
2747                     if (post != null) posts.Add(post);
2748                 }
2749             });
2750
2751             if (ct.IsCancellationRequested)
2752                 return;
2753
2754             p.Report(Properties.Resources.PostWorker_RunWorkerCompletedText4);
2755
2756             this._postTimestamps.Add(DateTimeUtc.Now);
2757
2758             var oneHour = DateTimeUtc.Now - TimeSpan.FromHours(1);
2759             foreach (var i in MyCommon.CountDown(this._postTimestamps.Count - 1, 0))
2760             {
2761                 if (this._postTimestamps[i] < oneHour)
2762                     this._postTimestamps.RemoveAt(i);
2763             }
2764
2765             // TLに反映
2766             if (!this.tw.UserStreamActive)
2767             {
2768                 // 自分のRTはTLの更新では取得できない場合があるので、
2769                 // 投稿時取得の有無に関わらず追加しておく
2770                 posts.ForEach(post => this._statuses.AddPost(post));
2771
2772                 if (SettingManager.Common.PostAndGet)
2773                     await this.RefreshTabAsync<HomeTabModel>();
2774                 else
2775                 {
2776                     this._statuses.DistributePosts();
2777                     this.RefreshTimeline();
2778                 }
2779             }
2780         }
2781
2782         private async Task RefreshFollowerIdsAsync()
2783         {
2784             await this.workerSemaphore.WaitAsync();
2785             this.RefreshTasktrayIcon();
2786
2787             try
2788             {
2789                 this.StatusLabel.Text = Properties.Resources.UpdateFollowersMenuItem1_ClickText1;
2790
2791                 await this.tw.RefreshFollowerIds();
2792
2793                 this.StatusLabel.Text = Properties.Resources.UpdateFollowersMenuItem1_ClickText3;
2794
2795                 this.RefreshTimeline();
2796                 this.PurgeListViewItemCache();
2797                 this.CurrentListView.Refresh();
2798             }
2799             catch (WebApiException ex)
2800             {
2801                 this.StatusLabel.Text = $"Err:{ex.Message}(RefreshFollowersIds)";
2802             }
2803             finally
2804             {
2805                 this.workerSemaphore.Release();
2806             }
2807         }
2808
2809         private async Task RefreshNoRetweetIdsAsync()
2810         {
2811             await this.workerSemaphore.WaitAsync();
2812             this.RefreshTasktrayIcon();
2813
2814             try
2815             {
2816                 await this.tw.RefreshNoRetweetIds();
2817
2818                 this.StatusLabel.Text = "NoRetweetIds refreshed";
2819             }
2820             catch (WebApiException ex)
2821             {
2822                 this.StatusLabel.Text = $"Err:{ex.Message}(RefreshNoRetweetIds)";
2823             }
2824             finally
2825             {
2826                 this.workerSemaphore.Release();
2827             }
2828         }
2829
2830         private async Task RefreshBlockIdsAsync()
2831         {
2832             await this.workerSemaphore.WaitAsync();
2833             this.RefreshTasktrayIcon();
2834
2835             try
2836             {
2837                 this.StatusLabel.Text = Properties.Resources.UpdateBlockUserText1;
2838
2839                 await this.tw.RefreshBlockIds();
2840
2841                 this.StatusLabel.Text = Properties.Resources.UpdateBlockUserText3;
2842             }
2843             catch (WebApiException ex)
2844             {
2845                 this.StatusLabel.Text = $"Err:{ex.Message}(RefreshBlockIds)";
2846             }
2847             finally
2848             {
2849                 this.workerSemaphore.Release();
2850             }
2851         }
2852
2853         private async Task RefreshTwitterConfigurationAsync()
2854         {
2855             await this.workerSemaphore.WaitAsync();
2856             this.RefreshTasktrayIcon();
2857
2858             try
2859             {
2860                 await this.tw.RefreshConfiguration();
2861
2862                 if (this.tw.Configuration.PhotoSizeLimit != 0)
2863                 {
2864                     foreach (var service in this.ImageSelector.GetServices())
2865                     {
2866                         service.UpdateTwitterConfiguration(this.tw.Configuration);
2867                     }
2868                 }
2869
2870                 this.PurgeListViewItemCache();
2871                 this.CurrentListView.Refresh();
2872             }
2873             catch (WebApiException ex)
2874             {
2875                 this.StatusLabel.Text = $"Err:{ex.Message}(RefreshConfiguration)";
2876             }
2877             finally
2878             {
2879                 this.workerSemaphore.Release();
2880             }
2881         }
2882
2883         private async Task RefreshMuteUserIdsAsync()
2884         {
2885             this.StatusLabel.Text = Properties.Resources.UpdateMuteUserIds_Start;
2886
2887             try
2888             {
2889                 await tw.RefreshMuteUserIdsAsync();
2890             }
2891             catch (WebApiException ex)
2892             {
2893                 this.StatusLabel.Text = string.Format(Properties.Resources.UpdateMuteUserIds_Error, ex.Message);
2894                 return;
2895             }
2896
2897             this.StatusLabel.Text = Properties.Resources.UpdateMuteUserIds_Finish;
2898         }
2899
2900         private void NotifyIcon1_MouseClick(object sender, MouseEventArgs e)
2901         {
2902             if (e.Button == MouseButtons.Left)
2903             {
2904                 this.Visible = true;
2905                 if (this.WindowState == FormWindowState.Minimized)
2906                 {
2907                     this.WindowState = _formWindowState;
2908                 }
2909                 this.Activate();
2910                 this.BringToFront();
2911             }
2912         }
2913
2914         private async void MyList_MouseDoubleClick(object sender, MouseEventArgs e)
2915         {
2916             switch (SettingManager.Common.ListDoubleClickAction)
2917             {
2918                 case 0:
2919                     MakeReplyOrDirectStatus();
2920                     break;
2921                 case 1:
2922                     await this.FavoriteChange(true);
2923                     break;
2924                 case 2:
2925                     var post = this.CurrentPost;
2926                     if (post != null)
2927                         await this.ShowUserStatus(post.ScreenName, false);
2928                     break;
2929                 case 3:
2930                     await ShowUserTimeline();
2931                     break;
2932                 case 4:
2933                     ShowRelatedStatusesMenuItem_Click(null, null);
2934                     break;
2935                 case 5:
2936                     MoveToHomeToolStripMenuItem_Click(null, null);
2937                     break;
2938                 case 6:
2939                     StatusOpenMenuItem_Click(null, null);
2940                     break;
2941                 case 7:
2942                     //動作なし
2943                     break;
2944             }
2945         }
2946
2947         private async void FavAddToolStripMenuItem_Click(object sender, EventArgs e)
2948             => await this.FavoriteChange(true);
2949
2950         private async void FavRemoveToolStripMenuItem_Click(object sender, EventArgs e)
2951             => await this.FavoriteChange(false);
2952
2953
2954         private async void FavoriteRetweetMenuItem_Click(object sender, EventArgs e)
2955             => await this.FavoritesRetweetOfficial();
2956
2957         private async void FavoriteRetweetUnofficialMenuItem_Click(object sender, EventArgs e)
2958             => await this.FavoritesRetweetUnofficial();
2959
2960         private async Task FavoriteChange(bool FavAdd, bool multiFavoriteChangeDialogEnable = true)
2961         {
2962             var tab = this.CurrentTab;
2963             var posts = tab.SelectedPosts;
2964
2965             //trueでFavAdd,falseでFavRemove
2966             if (tab.TabType == MyCommon.TabUsageType.DirectMessage || posts.Length == 0
2967                 || !this.ExistCurrentPost) return;
2968
2969             if (posts.Length > 1)
2970             {
2971                 if (FavAdd)
2972                 {
2973                     // 複数ツイートの一括ふぁぼは禁止
2974                     // https://support.twitter.com/articles/76915#favoriting
2975                     MessageBox.Show(string.Format(Properties.Resources.FavoriteLimitCountText, 1));
2976                     _DoFavRetweetFlags = false;
2977                     return;
2978                 }
2979                 else
2980                 {
2981                     if (multiFavoriteChangeDialogEnable)
2982                     {
2983                         var confirm = MessageBox.Show(Properties.Resources.FavRemoveToolStripMenuItem_ClickText1,
2984                             Properties.Resources.FavRemoveToolStripMenuItem_ClickText2,
2985                             MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
2986
2987                         if (confirm == DialogResult.Cancel)
2988                             return;
2989                     }
2990                 }
2991             }
2992
2993             if (FavAdd)
2994             {
2995                 var selectedPost = posts.Single();
2996                 if (selectedPost.IsFav)
2997                 {
2998                     this.StatusLabel.Text = Properties.Resources.FavAddToolStripMenuItem_ClickText4;
2999                     return;
3000                 }
3001
3002                 await this.FavAddAsync(selectedPost.StatusId, tab);
3003             }
3004             else
3005             {
3006                 var selectedPosts = posts.Where(x => x.IsFav);
3007                 var statusIds = selectedPosts.Select(x => x.StatusId).ToArray();
3008                 if (statusIds.Length == 0)
3009                 {
3010                     this.StatusLabel.Text = Properties.Resources.FavRemoveToolStripMenuItem_ClickText4;
3011                     return;
3012                 }
3013
3014                 await this.FavRemoveAsync(statusIds, tab);
3015             }
3016         }
3017
3018         private PostClass GetCurTabPost(int Index)
3019         {
3020             var listCache = this._listItemCache;
3021             if (listCache != null)
3022             {
3023                 if (listCache.TryGetValue(Index, out var item, out var post))
3024                     return post;
3025             }
3026
3027             return this.CurrentTab[Index];
3028         }
3029
3030         private async void MoveToHomeToolStripMenuItem_Click(object sender, EventArgs e)
3031         {
3032             var post = this.CurrentPost;
3033             if (post != null)
3034                 await this.OpenUriInBrowserAsync(MyCommon.TwitterUrl + post.ScreenName);
3035             else
3036                 await this.OpenUriInBrowserAsync(MyCommon.TwitterUrl);
3037         }
3038
3039         private async void MoveToFavToolStripMenuItem_Click(object sender, EventArgs e)
3040         {
3041             var post = this.CurrentPost;
3042             if (post != null)
3043                 await this.OpenUriInBrowserAsync(MyCommon.TwitterUrl + "#!/" + post.ScreenName + "/favorites");
3044         }
3045
3046         private void TweenMain_ClientSizeChanged(object sender, EventArgs e)
3047         {
3048             if ((!_initialLayout) && this.Visible)
3049             {
3050                 if (this.WindowState == FormWindowState.Normal)
3051                 {
3052                     _mySize = this.ClientSize;
3053                     _mySpDis = this.SplitContainer1.SplitterDistance;
3054                     _mySpDis3 = this.SplitContainer3.SplitterDistance;
3055                     if (StatusText.Multiline) _mySpDis2 = this.StatusText.Height;
3056                     this.MarkSettingLocalModified();
3057                 }
3058             }
3059         }
3060
3061         private void MyList_ColumnClick(object sender, ColumnClickEventArgs e)
3062         {
3063             var comparerMode = this.GetComparerModeByColumnIndex(e.Column);
3064             if (comparerMode == null)
3065                 return;
3066
3067             this.SetSortColumn(comparerMode.Value);
3068         }
3069
3070         /// <summary>
3071         /// 列インデックスからソートを行う ComparerMode を求める
3072         /// </summary>
3073         /// <param name="columnIndex">ソートを行うカラムのインデックス (表示上の順序とは異なる)</param>
3074         /// <returns>ソートを行う ComparerMode。null であればソートを行わない</returns>
3075         private ComparerMode? GetComparerModeByColumnIndex(int columnIndex)
3076         {
3077             if (this._iconCol)
3078                 return ComparerMode.Id;
3079
3080             switch (columnIndex)
3081             {
3082                 case 1: // ニックネーム
3083                     return ComparerMode.Nickname;
3084                 case 2: // 本文
3085                     return ComparerMode.Data;
3086                 case 3: // 時刻=発言Id
3087                     return ComparerMode.Id;
3088                 case 4: // 名前
3089                     return ComparerMode.Name;
3090                 case 7: // Source
3091                     return ComparerMode.Source;
3092                 default:
3093                     // 0:アイコン, 5:未読マーク, 6:プロテクト・フィルターマーク
3094                     return null;
3095             }
3096         }
3097
3098         /// <summary>
3099         /// 発言一覧の指定した位置の列でソートする
3100         /// </summary>
3101         /// <param name="columnIndex">ソートする列の位置 (表示上の順序で指定)</param>
3102         private void SetSortColumnByDisplayIndex(int columnIndex)
3103         {
3104             // 表示上の列の位置から ColumnHeader を求める
3105             var col = this.CurrentListView.Columns.Cast<ColumnHeader>()
3106                 .FirstOrDefault(x => x.DisplayIndex == columnIndex);
3107
3108             if (col == null)
3109                 return;
3110
3111             var comparerMode = this.GetComparerModeByColumnIndex(col.Index);
3112             if (comparerMode == null)
3113                 return;
3114
3115             this.SetSortColumn(comparerMode.Value);
3116         }
3117
3118         /// <summary>
3119         /// 発言一覧の最後列の項目でソートする
3120         /// </summary>
3121         private void SetSortLastColumn()
3122         {
3123             // 表示上の最後列にある ColumnHeader を求める
3124             var col = this.CurrentListView.Columns.Cast<ColumnHeader>()
3125                 .OrderByDescending(x => x.DisplayIndex)
3126                 .First();
3127
3128             var comparerMode = this.GetComparerModeByColumnIndex(col.Index);
3129             if (comparerMode == null)
3130                 return;
3131
3132             this.SetSortColumn(comparerMode.Value);
3133         }
3134
3135         /// <summary>
3136         /// 発言一覧を指定された ComparerMode に基づいてソートする
3137         /// </summary>
3138         private void SetSortColumn(ComparerMode sortColumn)
3139         {
3140             if (SettingManager.Common.SortOrderLock)
3141                 return;
3142
3143             this._statuses.ToggleSortOrder(sortColumn);
3144             this.InitColumnText();
3145
3146             var list = this.CurrentListView;
3147             if (_iconCol)
3148             {
3149                 list.Columns[0].Text = this.ColumnText[0];
3150                 list.Columns[1].Text = this.ColumnText[2];
3151             }
3152             else
3153             {
3154                 for (var i = 0; i <= 7; i++)
3155                 {
3156                     list.Columns[i].Text = this.ColumnText[i];
3157                 }
3158             }
3159
3160             this.PurgeListViewItemCache();
3161
3162             var tab = this.CurrentTab;
3163             var post = this.CurrentPost;
3164             if (tab.AllCount > 0 && post != null)
3165             {
3166                 var idx = tab.IndexOf(post.StatusId);
3167                 if (idx > -1)
3168                 {
3169                     this.SelectListItem(list, idx);
3170                     list.EnsureVisible(idx);
3171                 }
3172             }
3173             list.Refresh();
3174
3175             this.MarkSettingCommonModified();
3176         }
3177
3178         private void TweenMain_LocationChanged(object sender, EventArgs e)
3179         {
3180             if (this.WindowState == FormWindowState.Normal && !_initialLayout)
3181             {
3182                 _myLoc = this.DesktopLocation;
3183                 this.MarkSettingLocalModified();
3184             }
3185         }
3186
3187         private void ContextMenuOperate_Opening(object sender, CancelEventArgs e)
3188         {
3189             if (!this.ExistCurrentPost)
3190             {
3191                 ReplyStripMenuItem.Enabled = false;
3192                 ReplyAllStripMenuItem.Enabled = false;
3193                 DMStripMenuItem.Enabled = false;
3194                 ShowProfileMenuItem.Enabled = false;
3195                 ShowUserTimelineContextMenuItem.Enabled = false;
3196                 ListManageUserContextToolStripMenuItem2.Enabled = false;
3197                 MoveToFavToolStripMenuItem.Enabled = false;
3198                 TabMenuItem.Enabled = false;
3199                 IDRuleMenuItem.Enabled = false;
3200                 SourceRuleMenuItem.Enabled = false;
3201                 ReadedStripMenuItem.Enabled = false;
3202                 UnreadStripMenuItem.Enabled = false;
3203             }
3204             else
3205             {
3206                 ShowProfileMenuItem.Enabled = true;
3207                 ListManageUserContextToolStripMenuItem2.Enabled = true;
3208                 ReplyStripMenuItem.Enabled = true;
3209                 ReplyAllStripMenuItem.Enabled = true;
3210                 DMStripMenuItem.Enabled = true;
3211                 ShowUserTimelineContextMenuItem.Enabled = true;
3212                 MoveToFavToolStripMenuItem.Enabled = true;
3213                 TabMenuItem.Enabled = true;
3214                 IDRuleMenuItem.Enabled = true;
3215                 SourceRuleMenuItem.Enabled = true;
3216                 ReadedStripMenuItem.Enabled = true;
3217                 UnreadStripMenuItem.Enabled = true;
3218             }
3219             var tab = this.CurrentTab;
3220             var post = this.CurrentPost;
3221             if (tab.TabType == MyCommon.TabUsageType.DirectMessage || !this.ExistCurrentPost || post.IsDm)
3222             {
3223                 FavAddToolStripMenuItem.Enabled = false;
3224                 FavRemoveToolStripMenuItem.Enabled = false;
3225                 StatusOpenMenuItem.Enabled = false;
3226                 ShowRelatedStatusesMenuItem.Enabled = false;
3227
3228                 ReTweetStripMenuItem.Enabled = false;
3229                 ReTweetUnofficialStripMenuItem.Enabled = false;
3230                 QuoteStripMenuItem.Enabled = false;
3231                 FavoriteRetweetContextMenu.Enabled = false;
3232                 FavoriteRetweetUnofficialContextMenu.Enabled = false;
3233             }
3234             else
3235             {
3236                 FavAddToolStripMenuItem.Enabled = true;
3237                 FavRemoveToolStripMenuItem.Enabled = true;
3238                 StatusOpenMenuItem.Enabled = true;
3239                 ShowRelatedStatusesMenuItem.Enabled = true;  //PublicSearchの時問題出るかも
3240
3241                 if (!post.CanRetweetBy(this.twitterApi.CurrentUserId))
3242                 {
3243                     ReTweetStripMenuItem.Enabled = false;
3244                     ReTweetUnofficialStripMenuItem.Enabled = false;
3245                     QuoteStripMenuItem.Enabled = false;
3246                     FavoriteRetweetContextMenu.Enabled = false;
3247                     FavoriteRetweetUnofficialContextMenu.Enabled = false;
3248                 }
3249                 else
3250                 {
3251                     ReTweetStripMenuItem.Enabled = true;
3252                     ReTweetUnofficialStripMenuItem.Enabled = true;
3253                     QuoteStripMenuItem.Enabled = true;
3254                     FavoriteRetweetContextMenu.Enabled = true;
3255                     FavoriteRetweetUnofficialContextMenu.Enabled = true;
3256                 }
3257             }
3258             //if (_statuses.Tabs[ListTab.SelectedTab.Text].TabType != MyCommon.TabUsageType.Favorites)
3259             //{
3260             //    RefreshMoreStripMenuItem.Enabled = true;
3261             //}
3262             //else
3263             //{
3264             //    RefreshMoreStripMenuItem.Enabled = false;
3265             //}
3266             if (!this.ExistCurrentPost || post.InReplyToStatusId == null)
3267             {
3268                 RepliedStatusOpenMenuItem.Enabled = false;
3269             }
3270             else
3271             {
3272                 RepliedStatusOpenMenuItem.Enabled = true;
3273             }
3274             if (!this.ExistCurrentPost || string.IsNullOrEmpty(post.RetweetedBy))
3275             {
3276                 MoveToRTHomeMenuItem.Enabled = false;
3277             }
3278             else
3279             {
3280                 MoveToRTHomeMenuItem.Enabled = true;
3281             }
3282
3283             if (this.ExistCurrentPost)
3284             {
3285                 this.DeleteStripMenuItem.Enabled = post.CanDeleteBy(this.tw.UserId);
3286                 if (post.RetweetedByUserId == this.tw.UserId)
3287                     this.DeleteStripMenuItem.Text = Properties.Resources.DeleteMenuText2;
3288                 else
3289                     this.DeleteStripMenuItem.Text = Properties.Resources.DeleteMenuText1;
3290             }
3291         }
3292
3293         private void ReplyStripMenuItem_Click(object sender, EventArgs e)
3294             => this.MakeReplyOrDirectStatus(false, true);
3295
3296         private void DMStripMenuItem_Click(object sender, EventArgs e)
3297             => this.MakeReplyOrDirectStatus(false, false);
3298
3299         private async Task doStatusDelete()
3300         {
3301             var posts = this.CurrentTab.SelectedPosts;
3302             if (posts.Length == 0)
3303                 return;
3304
3305             // 選択されたツイートの中に削除可能なものが一つでもあるか
3306             if (!posts.Any(x => x.CanDeleteBy(this.tw.UserId)))
3307                 return;
3308
3309             var ret = MessageBox.Show(this,
3310                 string.Format(Properties.Resources.DeleteStripMenuItem_ClickText1, Environment.NewLine),
3311                 Properties.Resources.DeleteStripMenuItem_ClickText2,
3312                 MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
3313
3314             if (ret != DialogResult.OK)
3315                 return;
3316
3317             var currentListView = this.CurrentListView;
3318             var focusedIndex = currentListView.FocusedItem?.Index ?? currentListView.TopItem?.Index ?? 0;
3319
3320             using (ControlTransaction.Cursor(this, Cursors.WaitCursor))
3321             {
3322                 Exception lastException = null;
3323                 foreach (var post in posts)
3324                 {
3325                     if (!post.CanDeleteBy(this.tw.UserId))
3326                         continue;
3327
3328                     try
3329                     {
3330                         if (post.IsDm)
3331                         {
3332                             await this.twitterApi.DirectMessagesEventsDestroy(post.StatusId.ToString(CultureInfo.InvariantCulture));
3333                         }
3334                         else
3335                         {
3336                             if (post.RetweetedByUserId == this.tw.UserId)
3337                             {
3338                                 // 自分が RT したツイート (自分が RT した自分のツイートも含む)
3339                                 //   => RT を取り消し
3340                                 await this.twitterApi.StatusesDestroy(post.StatusId)
3341                                     .IgnoreResponse();
3342                             }
3343                             else
3344                             {
3345                                 if (post.UserId == this.tw.UserId)
3346                                 {
3347                                     if (post.RetweetedId != null)
3348                                         // 他人に RT された自分のツイート
3349                                         //   => RT 元の自分のツイートを削除
3350                                         await this.twitterApi.StatusesDestroy(post.RetweetedId.Value)
3351                                             .IgnoreResponse();
3352                                     else
3353                                         // 自分のツイート
3354                                         //   => ツイートを削除
3355                                         await this.twitterApi.StatusesDestroy(post.StatusId)
3356                                             .IgnoreResponse();
3357                                 }
3358                             }
3359                         }
3360                     }
3361                     catch (WebApiException ex)
3362                     {
3363                         lastException = ex;
3364                         continue;
3365                     }
3366
3367                     this._statuses.RemovePostFromAllTabs(post.StatusId, setIsDeleted: true);
3368                 }
3369
3370                 if (lastException == null)
3371                     this.StatusLabel.Text = Properties.Resources.DeleteStripMenuItem_ClickText4; // 成功
3372                 else
3373                     this.StatusLabel.Text = Properties.Resources.DeleteStripMenuItem_ClickText3; // 失敗
3374
3375                 this.PurgeListViewItemCache();
3376
3377                 foreach (var (tab, index) in this._statuses.Tabs.WithIndex())
3378                 {
3379                     var tabPage = this.ListTab.TabPages[index];
3380                     var listView = (DetailsListView)tabPage.Tag;
3381
3382                     using (ControlTransaction.Update(listView))
3383                     {
3384                         listView.VirtualListSize = tab.AllCount;
3385
3386                         if (tab.TabName == this.CurrentTabName)
3387                         {
3388                             listView.SelectedIndices.Clear();
3389
3390                             if (tab.AllCount != 0)
3391                             {
3392                                 int selectedIndex;
3393                                 if (tab.AllCount - 1 > focusedIndex && focusedIndex > -1)
3394                                     selectedIndex = focusedIndex;
3395                                 else
3396                                     selectedIndex = tab.AllCount - 1;
3397
3398                                 listView.SelectedIndices.Add(selectedIndex);
3399                                 listView.EnsureVisible(selectedIndex);
3400                                 listView.FocusedItem = listView.Items[selectedIndex];
3401                             }
3402                         }
3403                     }
3404
3405                     if (SettingManager.Common.TabIconDisp && tab.UnreadCount == 0)
3406                     {
3407                         if (tabPage.ImageIndex == 0)
3408                             tabPage.ImageIndex = -1; // タブアイコン
3409                     }
3410                 }
3411
3412                 if (!SettingManager.Common.TabIconDisp)
3413                     this.ListTab.Refresh();
3414             }
3415         }
3416
3417         private async void DeleteStripMenuItem_Click(object sender, EventArgs e)
3418             => await this.doStatusDelete();
3419
3420         private void ReadedStripMenuItem_Click(object sender, EventArgs e)
3421         {
3422             using (ControlTransaction.Update(this.CurrentListView))
3423             {
3424                 var tab = this.CurrentTab;
3425                 foreach (var statusId in tab.SelectedStatusIds)
3426                 {
3427                     this._statuses.SetReadAllTab(statusId, read: true);
3428                     var idx = tab.IndexOf(statusId);
3429                     ChangeCacheStyleRead(true, idx);
3430                 }
3431                 ColorizeList();
3432             }
3433             if (SettingManager.Common.TabIconDisp)
3434             {
3435                 foreach (var (tab, index) in this._statuses.Tabs.WithIndex())
3436                 {
3437                     if (tab.UnreadCount == 0)
3438                     {
3439                         var tabPage = this.ListTab.TabPages[index];
3440                         if (tabPage.ImageIndex == 0)
3441                             tabPage.ImageIndex = -1; // タブアイコン
3442                     }
3443                 }
3444             }
3445             if (!SettingManager.Common.TabIconDisp) ListTab.Refresh();
3446         }
3447
3448         private void UnreadStripMenuItem_Click(object sender, EventArgs e)
3449         {
3450             using (ControlTransaction.Update(this.CurrentListView))
3451             {
3452                 var tab = this.CurrentTab;
3453                 foreach (var statusId in tab.SelectedStatusIds)
3454                 {
3455                     this._statuses.SetReadAllTab(statusId, read: false);
3456                     var idx = tab.IndexOf(statusId);
3457                     ChangeCacheStyleRead(false, idx);
3458                 }
3459                 ColorizeList();
3460             }
3461             if (SettingManager.Common.TabIconDisp)
3462             {
3463                 foreach (var (tab, index) in this._statuses.Tabs.WithIndex())
3464                 {
3465                     if (tab.UnreadCount > 0)
3466                     {
3467                         var tabPage = this.ListTab.TabPages[index];
3468                         if (tabPage.ImageIndex == -1)
3469                             tabPage.ImageIndex = 0; // タブアイコン
3470                     }
3471                 }
3472             }
3473             if (!SettingManager.Common.TabIconDisp) ListTab.Refresh();
3474         }
3475
3476         private async void RefreshStripMenuItem_Click(object sender, EventArgs e)
3477             => await this.DoRefresh();
3478
3479         private async Task DoRefresh()
3480             => await this.RefreshTabAsync(this.CurrentTab);
3481
3482         private async Task DoRefreshMore()
3483             => await this.RefreshTabAsync(this.CurrentTab, backward: true);
3484
3485         private DialogResult ShowSettingDialog(bool showTaskbarIcon = false)
3486         {
3487             DialogResult result = DialogResult.Abort;
3488
3489             using (var settingDialog = new AppendSettingDialog())
3490             {
3491                 settingDialog.Icon = this.MainIcon;
3492                 settingDialog.Owner = this;
3493                 settingDialog.ShowInTaskbar = showTaskbarIcon;
3494                 settingDialog.IntervalChanged += this.TimerInterval_Changed;
3495
3496                 settingDialog.tw = this.tw;
3497                 settingDialog.twitterApi = this.twitterApi;
3498
3499                 settingDialog.LoadConfig(SettingManager.Common, SettingManager.Local);
3500
3501                 try
3502                 {
3503                     result = settingDialog.ShowDialog(this);
3504                 }
3505                 catch (Exception)
3506                 {
3507                     return DialogResult.Abort;
3508                 }
3509
3510                 if (result == DialogResult.OK)
3511                 {
3512                     lock (_syncObject)
3513                     {
3514                         settingDialog.SaveConfig(SettingManager.Common, SettingManager.Local);
3515                     }
3516                 }
3517             }
3518
3519             return result;
3520         }
3521
3522         private async void SettingStripMenuItem_Click(object sender, EventArgs e)
3523         {
3524             // 設定画面表示前のユーザー情報
3525             var oldUser = new { tw.AccessToken, tw.AccessTokenSecret, tw.Username, tw.UserId };
3526
3527             var oldIconSz = SettingManager.Common.IconSize;
3528
3529             if (ShowSettingDialog() == DialogResult.OK)
3530             {
3531                 lock (_syncObject)
3532                 {
3533                     tw.RestrictFavCheck = SettingManager.Common.RestrictFavCheck;
3534                     tw.ReadOwnPost = SettingManager.Common.ReadOwnPost;
3535                     ShortUrl.Instance.DisableExpanding = !SettingManager.Common.TinyUrlResolve;
3536                     ShortUrl.Instance.BitlyAccessToken = SettingManager.Common.BitlyAccessToken;
3537                     ShortUrl.Instance.BitlyId = SettingManager.Common.BilyUser;
3538                     ShortUrl.Instance.BitlyKey = SettingManager.Common.BitlyPwd;
3539                     TwitterApiConnection.RestApiHost = SettingManager.Common.TwitterApiHost;
3540
3541                     Networking.DefaultTimeout = TimeSpan.FromSeconds(SettingManager.Common.DefaultTimeOut);
3542                     Networking.UploadImageTimeout = TimeSpan.FromSeconds(SettingManager.Common.UploadImageTimeout);
3543                     Networking.SetWebProxy(SettingManager.Local.ProxyType,
3544                         SettingManager.Local.ProxyAddress, SettingManager.Local.ProxyPort,
3545                         SettingManager.Local.ProxyUser, SettingManager.Local.ProxyPassword);
3546                     Networking.ForceIPv4 = SettingManager.Common.ForceIPv4;
3547
3548                     ImageSelector.Reset(tw, this.tw.Configuration);
3549
3550                     try
3551                     {
3552                         if (SettingManager.Common.TabIconDisp)
3553                         {
3554                             ListTab.DrawItem -= ListTab_DrawItem;
3555                             ListTab.DrawMode = TabDrawMode.Normal;
3556                             ListTab.ImageList = this.TabImage;
3557                         }
3558                         else
3559                         {
3560                             ListTab.DrawItem -= ListTab_DrawItem;
3561                             ListTab.DrawItem += ListTab_DrawItem;
3562                             ListTab.DrawMode = TabDrawMode.OwnerDrawFixed;
3563                             ListTab.ImageList = null;
3564                         }
3565                     }
3566                     catch (Exception ex)
3567                     {
3568                         ex.Data["Instance"] = "ListTab(TabIconDisp)";
3569                         ex.Data["IsTerminatePermission"] = false;
3570                         throw;
3571                     }
3572
3573                     try
3574                     {
3575                         if (!SettingManager.Common.UnreadManage)
3576                         {
3577                             ReadedStripMenuItem.Enabled = false;
3578                             UnreadStripMenuItem.Enabled = false;
3579                             if (SettingManager.Common.TabIconDisp)
3580                             {
3581                                 foreach (TabPage myTab in ListTab.TabPages)
3582                                 {
3583                                     myTab.ImageIndex = -1;
3584                                 }
3585                             }
3586                         }
3587                         else
3588                         {
3589                             ReadedStripMenuItem.Enabled = true;
3590                             UnreadStripMenuItem.Enabled = true;
3591                         }
3592                     }
3593                     catch (Exception ex)
3594                     {
3595                         ex.Data["Instance"] = "ListTab(UnreadManage)";
3596                         ex.Data["IsTerminatePermission"] = false;
3597                         throw;
3598                     }
3599
3600                     // タブの表示位置の決定
3601                     SetTabAlignment();
3602
3603                     SplitContainer1.IsPanelInverted = !SettingManager.Common.StatusAreaAtBottom;
3604
3605                     var imgazyobizinet = ThumbnailGenerator.ImgAzyobuziNetInstance;
3606                     imgazyobizinet.Enabled = SettingManager.Common.EnableImgAzyobuziNet;
3607                     imgazyobizinet.DisabledInDM = SettingManager.Common.ImgAzyobuziNetDisabledInDM;
3608
3609                     this.PlaySoundMenuItem.Checked = SettingManager.Common.PlaySound;
3610                     this.PlaySoundFileMenuItem.Checked = SettingManager.Common.PlaySound;
3611                     _fntUnread = SettingManager.Local.FontUnread;
3612                     _clUnread = SettingManager.Local.ColorUnread;
3613                     _fntReaded = SettingManager.Local.FontRead;
3614                     _clReaded = SettingManager.Local.ColorRead;
3615                     _clFav = SettingManager.Local.ColorFav;
3616                     _clOWL = SettingManager.Local.ColorOWL;
3617                     _clRetweet = SettingManager.Local.ColorRetweet;
3618                     _fntDetail = SettingManager.Local.FontDetail;
3619                     _clDetail = SettingManager.Local.ColorDetail;
3620                     _clDetailLink = SettingManager.Local.ColorDetailLink;
3621                     _clDetailBackcolor = SettingManager.Local.ColorDetailBackcolor;
3622                     _clSelf = SettingManager.Local.ColorSelf;
3623                     _clAtSelf = SettingManager.Local.ColorAtSelf;
3624                     _clTarget = SettingManager.Local.ColorTarget;
3625                     _clAtTarget = SettingManager.Local.ColorAtTarget;
3626                     _clAtFromTarget = SettingManager.Local.ColorAtFromTarget;
3627                     _clAtTo = SettingManager.Local.ColorAtTo;
3628                     _clListBackcolor = SettingManager.Local.ColorListBackcolor;
3629                     _clInputBackcolor = SettingManager.Local.ColorInputBackcolor;
3630                     _clInputFont = SettingManager.Local.ColorInputFont;
3631                     _fntInputFont = SettingManager.Local.FontInputFont;
3632                     _brsBackColorMine.Dispose();
3633                     _brsBackColorAt.Dispose();
3634                     _brsBackColorYou.Dispose();
3635                     _brsBackColorAtYou.Dispose();
3636                     _brsBackColorAtFromTarget.Dispose();
3637                     _brsBackColorAtTo.Dispose();
3638                     _brsBackColorNone.Dispose();
3639                     _brsBackColorMine = new SolidBrush(_clSelf);
3640                     _brsBackColorAt = new SolidBrush(_clAtSelf);
3641                     _brsBackColorYou = new SolidBrush(_clTarget);
3642                     _brsBackColorAtYou = new SolidBrush(_clAtTarget);
3643                     _brsBackColorAtFromTarget = new SolidBrush(_clAtFromTarget);
3644                     _brsBackColorAtTo = new SolidBrush(_clAtTo);
3645                     _brsBackColorNone = new SolidBrush(_clListBackcolor);
3646
3647                     try
3648                     {
3649                         if (StatusText.Focused) StatusText.BackColor = _clInputBackcolor;
3650                         StatusText.Font = _fntInputFont;
3651                         StatusText.ForeColor = _clInputFont;
3652                     }
3653                     catch (Exception ex)
3654                     {
3655                         MessageBox.Show(ex.Message);
3656                     }
3657
3658                     try
3659                     {
3660                         InitDetailHtmlFormat();
3661                     }
3662                     catch (Exception ex)
3663                     {
3664                         ex.Data["Instance"] = "Font";
3665                         ex.Data["IsTerminatePermission"] = false;
3666                         throw;
3667                     }
3668
3669                     try
3670                     {
3671                         if (SettingManager.Common.TabIconDisp)
3672                         {
3673                             foreach (var (tab, index) in this._statuses.Tabs.WithIndex())
3674                             {
3675                                 var tabPage = this.ListTab.TabPages[index];
3676                                 if (tab.UnreadCount == 0)
3677                                     tabPage.ImageIndex = -1;
3678                                 else
3679                                     tabPage.ImageIndex = 0;
3680                             }
3681                         }
3682                     }
3683                     catch (Exception ex)
3684                     {
3685                         ex.Data["Instance"] = "ListTab(TabIconDisp no2)";
3686                         ex.Data["IsTerminatePermission"] = false;
3687                         throw;
3688                     }
3689
3690                     try
3691                     {
3692                         var oldIconCol = _iconCol;
3693
3694                         if (SettingManager.Common.IconSize != oldIconSz)
3695                             ApplyListViewIconSize(SettingManager.Common.IconSize);
3696
3697                         foreach (TabPage tp in ListTab.TabPages)
3698                         {
3699                             DetailsListView lst = (DetailsListView)tp.Tag;
3700
3701                             using (ControlTransaction.Update(lst))
3702                             {
3703                                 lst.GridLines = SettingManager.Common.ShowGrid;
3704                                 lst.Font = _fntReaded;
3705                                 lst.BackColor = _clListBackcolor;
3706
3707                                 if (_iconCol != oldIconCol)
3708                                     ResetColumns(lst);
3709                             }
3710                         }
3711                     }
3712                     catch (Exception ex)
3713                     {
3714                         ex.Data["Instance"] = "ListView(IconSize)";
3715                         ex.Data["IsTerminatePermission"] = false;
3716                         throw;
3717                     }
3718
3719                     SetMainWindowTitle();
3720                     SetNotifyIconText();
3721
3722                     this.PurgeListViewItemCache();
3723                     this.CurrentListView.Refresh();
3724                     ListTab.Refresh();
3725
3726                     _hookGlobalHotkey.UnregisterAllOriginalHotkey();
3727                     if (SettingManager.Common.HotkeyEnabled)
3728                     {
3729                         ///グローバルホットキーの登録。設定で変更可能にするかも
3730                         HookGlobalHotkey.ModKeys modKey = HookGlobalHotkey.ModKeys.None;
3731                         if ((SettingManager.Common.HotkeyModifier & Keys.Alt) == Keys.Alt)
3732                             modKey |= HookGlobalHotkey.ModKeys.Alt;
3733                         if ((SettingManager.Common.HotkeyModifier & Keys.Control) == Keys.Control)
3734                             modKey |= HookGlobalHotkey.ModKeys.Ctrl;
3735                         if ((SettingManager.Common.HotkeyModifier & Keys.Shift) == Keys.Shift)
3736                             modKey |=  HookGlobalHotkey.ModKeys.Shift;
3737                         if ((SettingManager.Common.HotkeyModifier & Keys.LWin) == Keys.LWin)
3738                             modKey |= HookGlobalHotkey.ModKeys.Win;
3739
3740                         _hookGlobalHotkey.RegisterOriginalHotkey(SettingManager.Common.HotkeyKey, SettingManager.Common.HotkeyValue, modKey);
3741                     }
3742
3743                     if (SettingManager.Common.IsUseNotifyGrowl) gh.RegisterGrowl();
3744                     try
3745                     {
3746                         StatusText_TextChanged(null, null);
3747                     }
3748                     catch (Exception)
3749                     {
3750                     }
3751                 }
3752             }
3753             else
3754             {
3755                 // キャンセル時は Twitter クラスの認証情報を画面表示前の状態に戻す
3756                 this.tw.Initialize(oldUser.AccessToken, oldUser.AccessTokenSecret, oldUser.Username, oldUser.UserId);
3757             }
3758
3759             Twitter.AccountState = MyCommon.ACCOUNT_STATE.Valid;
3760
3761             this.TopMost = SettingManager.Common.AlwaysTop;
3762             SaveConfigsAll(false);
3763
3764             if (tw.UserId != oldUser.UserId)
3765                 await this.doGetFollowersMenu();
3766         }
3767
3768         /// <summary>
3769         /// タブの表示位置を設定する
3770         /// </summary>
3771         private void SetTabAlignment()
3772         {
3773             var newAlignment = SettingManager.Common.ViewTabBottom ? TabAlignment.Bottom : TabAlignment.Top;
3774             if (ListTab.Alignment == newAlignment) return;
3775
3776             // 各タブのリスト上の選択位置などを退避
3777             var listSelections = this.SaveListViewSelection();
3778
3779             ListTab.Alignment = newAlignment;
3780
3781             foreach (var (tab, index) in this._statuses.Tabs.WithIndex())
3782             {
3783                 var lst = (DetailsListView)this.ListTab.TabPages[index].Tag;
3784                 using (ControlTransaction.Update(lst))
3785                 {
3786                     // 選択位置などを復元
3787                     this.RestoreListViewSelection(lst, tab, listSelections[tab.TabName]);
3788                 }
3789             }
3790         }
3791
3792         private void ApplyListViewIconSize(MyCommon.IconSizes iconSz)
3793         {
3794             // アイコンサイズの再設定
3795             _iconCol = false;
3796             switch (iconSz)
3797             {
3798                 case MyCommon.IconSizes.IconNone:
3799                     _iconSz = 0;
3800                     break;
3801                 case MyCommon.IconSizes.Icon16:
3802                     _iconSz = 16;
3803                     break;
3804                 case MyCommon.IconSizes.Icon24:
3805                     _iconSz = 26;
3806                     break;
3807                 case MyCommon.IconSizes.Icon48:
3808                     _iconSz = 48;
3809                     break;
3810                 case MyCommon.IconSizes.Icon48_2:
3811                     _iconSz = 48;
3812                     _iconCol = true;
3813                     break;
3814             }
3815
3816             if (_iconSz > 0)
3817             {
3818                 // ディスプレイの DPI 設定を考慮したサイズを設定する
3819                 _listViewImageList.ImageSize = new Size(
3820                     1,
3821                     (int)Math.Ceiling(this._iconSz * this.CurrentScaleFactor.Height));
3822             }
3823             else
3824             {
3825                 _listViewImageList.ImageSize = new Size(1, 1);
3826             }
3827         }
3828
3829         private void ResetColumns(DetailsListView list)
3830         {
3831             using (ControlTransaction.Update(list))
3832             using (ControlTransaction.Layout(list, false))
3833             {
3834                 // カラムヘッダの再設定
3835                 list.ColumnClick -= MyList_ColumnClick;
3836                 list.DrawColumnHeader -= MyList_DrawColumnHeader;
3837                 list.ColumnReordered -= MyList_ColumnReordered;
3838                 list.ColumnWidthChanged -= MyList_ColumnWidthChanged;
3839
3840                 var cols = list.Columns.Cast<ColumnHeader>().ToList();
3841                 list.Columns.Clear();
3842                 cols.ForEach(col => col.Dispose());
3843                 cols.Clear();
3844
3845                 InitColumns(list, true);
3846
3847                 list.ColumnClick += MyList_ColumnClick;
3848                 list.DrawColumnHeader += MyList_DrawColumnHeader;
3849                 list.ColumnReordered += MyList_ColumnReordered;
3850                 list.ColumnWidthChanged += MyList_ColumnWidthChanged;
3851             }
3852         }
3853
3854         public void AddNewTabForSearch(string searchWord)
3855         {
3856             //同一検索条件のタブが既に存在すれば、そのタブアクティブにして終了
3857             foreach (var tb in _statuses.GetTabsByType<PublicSearchTabModel>())
3858             {
3859                 if (tb.SearchWords == searchWord && string.IsNullOrEmpty(tb.SearchLang))
3860                 {
3861                     var tabIndex = this._statuses.Tabs.IndexOf(tb);
3862                     this.ListTab.SelectedIndex = tabIndex;
3863                     return;
3864                 }
3865             }
3866             //ユニークなタブ名生成
3867             string tabName = searchWord;
3868             for (int i = 0; i <= 100; i++)
3869             {
3870                 if (_statuses.ContainsTab(tabName))
3871                     tabName += "_";
3872                 else
3873                     break;
3874             }
3875             //タブ追加
3876             var tab = new PublicSearchTabModel(tabName);
3877             _statuses.AddTab(tab);
3878             AddNewTab(tab, startup: false);
3879             //追加したタブをアクティブに
3880             ListTab.SelectedIndex = this._statuses.Tabs.Count - 1;
3881             //検索条件の設定
3882             var tabPage = this.CurrentTabPage;
3883             ComboBox cmb = (ComboBox)tabPage.Controls["panelSearch"].Controls["comboSearch"];
3884             cmb.Items.Add(searchWord);
3885             cmb.Text = searchWord;
3886             SaveConfigsTabs();
3887             //検索実行
3888             this.SearchButton_Click(tabPage.Controls["panelSearch"].Controls["comboSearch"], null);
3889         }
3890
3891         private async Task ShowUserTimeline()
3892         {
3893             if (!this.ExistCurrentPost) return;
3894             await this.AddNewTabForUserTimeline(this.CurrentPost.ScreenName);
3895         }
3896
3897         private void SearchComboBox_KeyDown(object sender, KeyEventArgs e)
3898         {
3899             if (e.KeyCode == Keys.Escape)
3900             {
3901                 RemoveSpecifiedTab(this.CurrentTabName, false);
3902                 SaveConfigsTabs();
3903                 e.SuppressKeyPress = true;
3904             }
3905         }
3906
3907         public async Task AddNewTabForUserTimeline(string user)
3908         {
3909             //同一検索条件のタブが既に存在すれば、そのタブアクティブにして終了
3910             foreach (var tb in _statuses.GetTabsByType<UserTimelineTabModel>())
3911             {
3912                 if (tb.ScreenName == user)
3913                 {
3914                     var tabIndex = this._statuses.Tabs.IndexOf(tb);
3915                     this.ListTab.SelectedIndex = tabIndex;
3916                     return;
3917                 }
3918             }
3919             //ユニークなタブ名生成
3920             string tabName = "user:" + user;
3921             while (_statuses.ContainsTab(tabName))
3922             {
3923                 tabName += "_";
3924             }
3925             //タブ追加
3926             var tab = new UserTimelineTabModel(tabName, user);
3927             this._statuses.AddTab(tab);
3928             this.AddNewTab(tab, startup: false);
3929             //追加したタブをアクティブに
3930             ListTab.SelectedIndex = this._statuses.Tabs.Count - 1;
3931             SaveConfigsTabs();
3932             //検索実行
3933             await this.RefreshTabAsync(tab);
3934         }
3935
3936         public bool AddNewTab(TabModel tab, bool startup)
3937         {
3938             //重複チェック
3939             if (this.ListTab.TabPages.Cast<TabPage>().Any(x => x.Text == tab.TabName))
3940                 return false;
3941
3942             //新規タブ名チェック
3943             if (tab.TabName == Properties.Resources.AddNewTabText1) return false;
3944
3945             var _tabPage = new TabPage();
3946             var _listCustom = new DetailsListView();
3947
3948             int cnt = this._statuses.Tabs.Count;
3949
3950             ///ToDo:Create and set controls follow tabtypes
3951
3952             using (ControlTransaction.Update(_listCustom))
3953             using (ControlTransaction.Layout(this.SplitContainer1.Panel1, false))
3954             using (ControlTransaction.Layout(this.SplitContainer1.Panel2, false))
3955             using (ControlTransaction.Layout(this.SplitContainer1, false))
3956             using (ControlTransaction.Layout(this.ListTab, false))
3957             using (ControlTransaction.Layout(this))
3958             using (ControlTransaction.Layout(_tabPage, false))
3959             {
3960                 _tabPage.Controls.Add(_listCustom);
3961
3962                 /// UserTimeline関連
3963                 var userTab = tab as UserTimelineTabModel;
3964                 var listTab = tab as ListTimelineTabModel;
3965                 var searchTab = tab as PublicSearchTabModel;
3966
3967                 if (userTab != null || listTab != null)
3968                 {
3969                     var label = new Label
3970                     {
3971                         Dock = DockStyle.Top,
3972                         Name = "labelUser",
3973                         TabIndex = 0,
3974                     };
3975
3976                     if (listTab != null)
3977                     {
3978                         label.Text = listTab.ListInfo.ToString();
3979                     }
3980                     else if (userTab != null)
3981                     {
3982                         label.Text = userTab.ScreenName + "'s Timeline";
3983                     }
3984                     label.TextAlign = ContentAlignment.MiddleLeft;
3985                     using (ComboBox tmpComboBox = new ComboBox())
3986                     {
3987                         label.Height = tmpComboBox.Height;
3988                     }
3989                     _tabPage.Controls.Add(label);
3990                 }
3991                 /// 検索関連の準備
3992                 else if (searchTab != null)
3993                 {
3994                     var pnl = new Panel();
3995
3996                     var lbl = new Label();
3997                     var cmb = new ComboBox();
3998                     var btn = new Button();
3999                     var cmbLang = new ComboBox();
4000
4001                     using (ControlTransaction.Layout(pnl, false))
4002                     {
4003                         pnl.Controls.Add(cmb);
4004                         pnl.Controls.Add(cmbLang);
4005                         pnl.Controls.Add(btn);
4006                         pnl.Controls.Add(lbl);
4007                         pnl.Name = "panelSearch";
4008                         pnl.TabIndex = 0;
4009                         pnl.Dock = DockStyle.Top;
4010                         pnl.Height = cmb.Height;
4011                         pnl.Enter += SearchControls_Enter;
4012                         pnl.Leave += SearchControls_Leave;
4013
4014                         cmb.Text = "";
4015                         cmb.Anchor = AnchorStyles.Left | AnchorStyles.Right;
4016                         cmb.Dock = DockStyle.Fill;
4017                         cmb.Name = "comboSearch";
4018                         cmb.DropDownStyle = ComboBoxStyle.DropDown;
4019                         cmb.ImeMode = ImeMode.NoControl;
4020                         cmb.TabStop = false;
4021                         cmb.TabIndex = 1;
4022                         cmb.AutoCompleteMode = AutoCompleteMode.None;
4023                         cmb.KeyDown += SearchComboBox_KeyDown;
4024
4025                         cmbLang.Text = "";
4026                         cmbLang.Anchor = AnchorStyles.Left | AnchorStyles.Right;
4027                         cmbLang.Dock = DockStyle.Right;
4028                         cmbLang.Width = 50;
4029                         cmbLang.Name = "comboLang";
4030                         cmbLang.DropDownStyle = ComboBoxStyle.DropDownList;
4031                         cmbLang.TabStop = false;
4032                         cmbLang.TabIndex = 2;
4033                         cmbLang.Items.Add("");
4034                         cmbLang.Items.Add("ja");
4035                         cmbLang.Items.Add("en");
4036                         cmbLang.Items.Add("ar");
4037                         cmbLang.Items.Add("da");
4038                         cmbLang.Items.Add("nl");
4039                         cmbLang.Items.Add("fa");
4040                         cmbLang.Items.Add("fi");
4041                         cmbLang.Items.Add("fr");
4042                         cmbLang.Items.Add("de");
4043                         cmbLang.Items.Add("hu");
4044                         cmbLang.Items.Add("is");
4045                         cmbLang.Items.Add("it");
4046                         cmbLang.Items.Add("no");
4047                         cmbLang.Items.Add("pl");
4048                         cmbLang.Items.Add("pt");
4049                         cmbLang.Items.Add("ru");
4050                         cmbLang.Items.Add("es");
4051                         cmbLang.Items.Add("sv");
4052                         cmbLang.Items.Add("th");
4053
4054                         lbl.Text = "Search(C-S-f)";
4055                         lbl.Name = "label1";
4056                         lbl.Dock = DockStyle.Left;
4057                         lbl.Width = 90;
4058                         lbl.Height = cmb.Height;
4059                         lbl.TextAlign = ContentAlignment.MiddleLeft;
4060                         lbl.TabIndex = 0;
4061
4062                         btn.Text = "Search";
4063                         btn.Name = "buttonSearch";
4064                         btn.UseVisualStyleBackColor = true;
4065                         btn.Dock = DockStyle.Right;
4066                         btn.TabStop = false;
4067                         btn.TabIndex = 3;
4068                         btn.Click += SearchButton_Click;
4069
4070                         if (!string.IsNullOrEmpty(searchTab.SearchWords))
4071                         {
4072                             cmb.Items.Add(searchTab.SearchWords);
4073                             cmb.Text = searchTab.SearchWords;
4074                         }
4075
4076                         cmbLang.Text = searchTab.SearchLang;
4077
4078                         _tabPage.Controls.Add(pnl);
4079                     }
4080                 }
4081
4082                 _tabPage.Tag = _listCustom;
4083                 this.ListTab.Controls.Add(_tabPage);
4084
4085                 _tabPage.Location = new Point(4, 4);
4086                 _tabPage.Name = "CTab" + cnt;
4087                 _tabPage.Size = new Size(380, 260);
4088                 _tabPage.TabIndex = 2 + cnt;
4089                 _tabPage.Text = tab.TabName;
4090                 _tabPage.UseVisualStyleBackColor = true;
4091                 _tabPage.AccessibleRole = AccessibleRole.PageTab;
4092
4093                 _listCustom.AccessibleName = Properties.Resources.AddNewTab_ListView_AccessibleName;
4094                 _listCustom.TabIndex = 1;
4095                 _listCustom.AllowColumnReorder = true;
4096                 _listCustom.ContextMenuStrip = this.ContextMenuOperate;
4097                 _listCustom.ColumnHeaderContextMenuStrip = this.ContextMenuColumnHeader;
4098                 _listCustom.Dock = DockStyle.Fill;
4099                 _listCustom.FullRowSelect = true;
4100                 _listCustom.HideSelection = false;
4101                 _listCustom.Location = new Point(0, 0);
4102                 _listCustom.Margin = new Padding(0);
4103                 _listCustom.Name = "CList" + Environment.TickCount;
4104                 _listCustom.ShowItemToolTips = true;
4105                 _listCustom.Size = new Size(380, 260);
4106                 _listCustom.UseCompatibleStateImageBehavior = false;
4107                 _listCustom.View = View.Details;
4108                 _listCustom.OwnerDraw = true;
4109                 _listCustom.VirtualMode = true;
4110                 _listCustom.Font = _fntReaded;
4111                 _listCustom.BackColor = _clListBackcolor;
4112
4113                 _listCustom.GridLines = SettingManager.Common.ShowGrid;
4114                 _listCustom.AllowDrop = true;
4115
4116                 _listCustom.SmallImageList = _listViewImageList;
4117
4118                 InitColumns(_listCustom, startup);
4119
4120                 _listCustom.SelectedIndexChanged += MyList_SelectedIndexChanged;
4121                 _listCustom.MouseDoubleClick += MyList_MouseDoubleClick;
4122                 _listCustom.ColumnClick += MyList_ColumnClick;
4123                 _listCustom.DrawColumnHeader += MyList_DrawColumnHeader;
4124                 _listCustom.DragDrop += TweenMain_DragDrop;
4125                 _listCustom.DragEnter += TweenMain_DragEnter;
4126                 _listCustom.DragOver += TweenMain_DragOver;
4127                 _listCustom.DrawItem += MyList_DrawItem;
4128                 _listCustom.MouseClick += MyList_MouseClick;
4129                 _listCustom.ColumnReordered += MyList_ColumnReordered;
4130                 _listCustom.ColumnWidthChanged += MyList_ColumnWidthChanged;
4131                 _listCustom.CacheVirtualItems += MyList_CacheVirtualItems;
4132                 _listCustom.RetrieveVirtualItem += MyList_RetrieveVirtualItem;
4133                 _listCustom.DrawSubItem += MyList_DrawSubItem;
4134                 _listCustom.HScrolled += MyList_HScrolled;
4135             }
4136
4137             return true;
4138         }
4139
4140         public bool RemoveSpecifiedTab(string TabName, bool confirm)
4141         {
4142             var tabInfo = _statuses.GetTabByName(TabName);
4143             if (tabInfo.IsDefaultTabType || tabInfo.Protected) return false;
4144
4145             if (confirm)
4146             {
4147                 string tmp = string.Format(Properties.Resources.RemoveSpecifiedTabText1, Environment.NewLine);
4148                 if (MessageBox.Show(tmp, TabName + " " + Properties.Resources.RemoveSpecifiedTabText2,
4149                                  MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Cancel)
4150                 {
4151                     return false;
4152                 }
4153             }
4154
4155             var tabIndex = this._statuses.Tabs.IndexOf(TabName);
4156             if (tabIndex == -1)
4157                 return false;
4158
4159             var _tabPage = this.ListTab.TabPages[tabIndex];
4160
4161             SetListProperty();   //他のタブに列幅等を反映
4162
4163             //オブジェクトインスタンスの削除
4164             DetailsListView _listCustom = (DetailsListView)_tabPage.Tag;
4165             _tabPage.Tag = null;
4166
4167             using (ControlTransaction.Layout(this.SplitContainer1.Panel1, false))
4168             using (ControlTransaction.Layout(this.SplitContainer1.Panel2, false))
4169             using (ControlTransaction.Layout(this.SplitContainer1, false))
4170             using (ControlTransaction.Layout(this.ListTab, false))
4171             using (ControlTransaction.Layout(this))
4172             using (ControlTransaction.Layout(_tabPage, false))
4173             {
4174                 if (this.CurrentTabName == TabName)
4175                 {
4176                     this.ListTab.SelectTab((this._beforeSelectedTab != null && this.ListTab.TabPages.Contains(this._beforeSelectedTab)) ? this._beforeSelectedTab : this.ListTab.TabPages[0]);
4177                     this._beforeSelectedTab = null;
4178                 }
4179                 this.ListTab.Controls.Remove(_tabPage);
4180
4181                 // 後付けのコントロールを破棄
4182                 if (tabInfo.TabType == MyCommon.TabUsageType.UserTimeline || tabInfo.TabType == MyCommon.TabUsageType.Lists)
4183                 {
4184                     using (Control label = _tabPage.Controls["labelUser"])
4185                     {
4186                         _tabPage.Controls.Remove(label);
4187                     }
4188                 }
4189                 else if (tabInfo.TabType == MyCommon.TabUsageType.PublicSearch)
4190                 {
4191                     using (Control pnl = _tabPage.Controls["panelSearch"])
4192                     {
4193                         pnl.Enter -= SearchControls_Enter;
4194                         pnl.Leave -= SearchControls_Leave;
4195                         _tabPage.Controls.Remove(pnl);
4196
4197                         foreach (Control ctrl in pnl.Controls)
4198                         {
4199                             if (ctrl.Name == "buttonSearch")
4200                             {
4201                                 ctrl.Click -= SearchButton_Click;
4202                             }
4203                             else if (ctrl.Name == "comboSearch")
4204                             {
4205                                 ctrl.KeyDown -= SearchComboBox_KeyDown;
4206                             }
4207                             pnl.Controls.Remove(ctrl);
4208                             ctrl.Dispose();
4209                         }
4210                     }
4211                 }
4212
4213                 _tabPage.Controls.Remove(_listCustom);
4214
4215                 _listCustom.SelectedIndexChanged -= MyList_SelectedIndexChanged;
4216                 _listCustom.MouseDoubleClick -= MyList_MouseDoubleClick;
4217                 _listCustom.ColumnClick -= MyList_ColumnClick;
4218                 _listCustom.DrawColumnHeader -= MyList_DrawColumnHeader;
4219                 _listCustom.DragDrop -= TweenMain_DragDrop;
4220                 _listCustom.DragEnter -= TweenMain_DragEnter;
4221                 _listCustom.DragOver -= TweenMain_DragOver;
4222                 _listCustom.DrawItem -= MyList_DrawItem;
4223                 _listCustom.MouseClick -= MyList_MouseClick;
4224                 _listCustom.ColumnReordered -= MyList_ColumnReordered;
4225                 _listCustom.ColumnWidthChanged -= MyList_ColumnWidthChanged;
4226                 _listCustom.CacheVirtualItems -= MyList_CacheVirtualItems;
4227                 _listCustom.RetrieveVirtualItem -= MyList_RetrieveVirtualItem;
4228                 _listCustom.DrawSubItem -= MyList_DrawSubItem;
4229                 _listCustom.HScrolled -= MyList_HScrolled;
4230
4231                 var cols = _listCustom.Columns.Cast<ColumnHeader>().ToList<ColumnHeader>();
4232                 _listCustom.Columns.Clear();
4233                 cols.ForEach(col => col.Dispose());
4234                 cols.Clear();
4235
4236                 _listCustom.ContextMenuStrip = null;
4237                 _listCustom.ColumnHeaderContextMenuStrip = null;
4238                 _listCustom.Font = null;
4239
4240                 _listCustom.SmallImageList = null;
4241                 _listCustom.ListViewItemSorter = null;
4242
4243                 // キャッシュのクリア
4244                 this.PurgeListViewItemCache();
4245             }
4246
4247             _tabPage.Dispose();
4248             _listCustom.Dispose();
4249             _statuses.RemoveTab(TabName);
4250
4251             foreach (var (tab, index) in this._statuses.Tabs.WithIndex())
4252             {
4253                 var lst = (DetailsListView)this.ListTab.TabPages[index].Tag;
4254                 lst.VirtualListSize = tab.AllCount;
4255             }
4256
4257             return true;
4258         }
4259
4260         private void ListTab_Deselected(object sender, TabControlEventArgs e)
4261         {
4262             this.PurgeListViewItemCache();
4263             _beforeSelectedTab = e.TabPage;
4264         }
4265
4266         private void ListTab_MouseMove(object sender, MouseEventArgs e)
4267         {
4268             //タブのD&D
4269
4270             if (!SettingManager.Common.TabMouseLock && e.Button == MouseButtons.Left && _tabDrag)
4271             {
4272                 string tn = "";
4273                 Rectangle dragEnableRectangle = new Rectangle(_tabMouseDownPoint.X - (SystemInformation.DragSize.Width / 2), _tabMouseDownPoint.Y - (SystemInformation.DragSize.Height / 2), SystemInformation.DragSize.Width, SystemInformation.DragSize.Height);
4274                 if (!dragEnableRectangle.Contains(e.Location))
4275                 {
4276                     //タブが多段の場合にはMouseDownの前の段階で選択されたタブの段が変わっているので、このタイミングでカーソルの位置からタブを判定出来ない。
4277                     tn = this.CurrentTabName;
4278                 }
4279
4280                 if (string.IsNullOrEmpty(tn)) return;
4281
4282                 var tabIndex = this._statuses.Tabs.IndexOf(tn);
4283                 if (tabIndex != -1)
4284                 {
4285                     var tabPage = this.ListTab.TabPages[tabIndex];
4286                     ListTab.DoDragDrop(tabPage, DragDropEffects.All);
4287                 }
4288             }
4289             else
4290             {
4291                 _tabDrag = false;
4292             }
4293
4294             Point cpos = new Point(e.X, e.Y);
4295             foreach (var (tab, index) in this._statuses.Tabs.WithIndex())
4296             {
4297                 var rect = ListTab.GetTabRect(index);
4298                 if (rect.Contains(cpos))
4299                 {
4300                     _rclickTabName = tab.TabName;
4301                     break;
4302                 }
4303             }
4304         }
4305
4306         private void ListTab_SelectedIndexChanged(object sender, EventArgs e)
4307         {
4308             //_curList.Refresh();
4309             SetMainWindowTitle();
4310             SetStatusLabelUrl();
4311             SetApiStatusLabel();
4312             if (ListTab.Focused || ((Control)this.CurrentTabPage.Tag).Focused)
4313                 this.Tag = ListTab.Tag;
4314             TabMenuControl(this.CurrentTabName);
4315             this.PushSelectPostChain();
4316             DispSelectedPost();
4317         }
4318
4319         private void SetListProperty()
4320         {
4321             if (!_isColumnChanged) return;
4322
4323             var currentListView = this.CurrentListView;
4324
4325             int[] dispOrder = new int[currentListView.Columns.Count];
4326             for (int i = 0; i < currentListView.Columns.Count; i++)
4327             {
4328                 for (int j = 0; j < currentListView.Columns.Count; j++)
4329                 {
4330                     if (currentListView.Columns[j].DisplayIndex == i)
4331                     {
4332                         dispOrder[i] = j;
4333                         break;
4334                     }
4335                 }
4336             }
4337
4338             //列幅、列並びを他のタブに設定
4339             foreach (TabPage tb in ListTab.TabPages)
4340             {
4341                 if (tb.Text == this.CurrentTabName)
4342                     continue;
4343
4344                 if (tb.Tag != null && tb.Controls.Count > 0)
4345                 {
4346                     DetailsListView lst = (DetailsListView)tb.Tag;
4347                     for (int i = 0; i < lst.Columns.Count; i++)
4348                     {
4349                         lst.Columns[dispOrder[i]].DisplayIndex = i;
4350                         lst.Columns[i].Width = currentListView.Columns[i].Width;
4351                     }
4352                 }
4353             }
4354
4355             _isColumnChanged = false;
4356         }
4357
4358         private void StatusText_KeyPress(object sender, KeyPressEventArgs e)
4359         {
4360             if (e.KeyChar == '@')
4361             {
4362                 if (!SettingManager.Common.UseAtIdSupplement) return;
4363                 //@マーク
4364                 int cnt = AtIdSupl.ItemCount;
4365                 ShowSuplDialog(StatusText, AtIdSupl);
4366                 if (cnt != AtIdSupl.ItemCount)
4367                     this.MarkSettingAtIdModified();
4368                 e.Handled = true;
4369             }
4370             else if (e.KeyChar == '#')
4371             {
4372                 if (!SettingManager.Common.UseHashSupplement) return;
4373                 ShowSuplDialog(StatusText, HashSupl);
4374                 e.Handled = true;
4375             }
4376         }
4377
4378         public void ShowSuplDialog(TextBox owner, AtIdSupplement dialog)
4379             => this.ShowSuplDialog(owner, dialog, 0, "");
4380
4381         public void ShowSuplDialog(TextBox owner, AtIdSupplement dialog, int offset)
4382             => this.ShowSuplDialog(owner, dialog, offset, "");
4383
4384         public void ShowSuplDialog(TextBox owner, AtIdSupplement dialog, int offset, string startswith)
4385         {
4386             dialog.StartsWith = startswith;
4387             if (dialog.Visible)
4388             {
4389                 dialog.Focus();
4390             }
4391             else
4392             {
4393                 dialog.ShowDialog();
4394             }
4395             this.TopMost = SettingManager.Common.AlwaysTop;
4396             int selStart = owner.SelectionStart;
4397             string fHalf = "";
4398             string eHalf = "";
4399             if (dialog.DialogResult == DialogResult.OK)
4400             {
4401                 if (!string.IsNullOrEmpty(dialog.inputText))
4402                 {
4403                     if (selStart > 0)
4404                     {
4405                         fHalf = owner.Text.Substring(0, selStart - offset);
4406                     }
4407                     if (selStart < owner.Text.Length)
4408                     {
4409                         eHalf = owner.Text.Substring(selStart);
4410                     }
4411                     owner.Text = fHalf + dialog.inputText + eHalf;
4412                     owner.SelectionStart = selStart + dialog.inputText.Length;
4413                 }
4414             }
4415             else
4416             {
4417                 if (selStart > 0)
4418                 {
4419                     fHalf = owner.Text.Substring(0, selStart);
4420                 }
4421                 if (selStart < owner.Text.Length)
4422                 {
4423                     eHalf = owner.Text.Substring(selStart);
4424                 }
4425                 owner.Text = fHalf + eHalf;
4426                 if (selStart > 0)
4427                 {
4428                     owner.SelectionStart = selStart;
4429                 }
4430             }
4431             owner.Focus();
4432         }
4433
4434         private void StatusText_KeyUp(object sender, KeyEventArgs e)
4435         {
4436             //スペースキーで未読ジャンプ
4437             if (!e.Alt && !e.Control && !e.Shift)
4438             {
4439                 if (e.KeyCode == Keys.Space || e.KeyCode == Keys.ProcessKey)
4440                 {
4441                     bool isSpace = false;
4442                     foreach (char c in StatusText.Text)
4443                     {
4444                         if (c == ' ' || c == ' ')
4445                         {
4446                             isSpace = true;
4447                         }
4448                         else
4449                         {
4450                             isSpace = false;
4451                             break;
4452                         }
4453                     }
4454                     if (isSpace)
4455                     {
4456                         e.Handled = true;
4457                         StatusText.Text = "";
4458                         JumpUnreadMenuItem_Click(null, null);
4459                     }
4460                 }
4461             }
4462             this.StatusText_TextChanged(null, null);
4463         }
4464
4465         private void StatusText_TextChanged(object sender, EventArgs e)
4466         {
4467             //文字数カウント
4468             int pLen = this.GetRestStatusCount(this.FormatStatusTextExtended(this.StatusText.Text));
4469             lblLen.Text = pLen.ToString();
4470             if (pLen < 0)
4471             {
4472                 StatusText.ForeColor = Color.Red;
4473             }
4474             else
4475             {
4476                 StatusText.ForeColor = _clInputFont;
4477             }
4478
4479             this.StatusText.AccessibleDescription = string.Format(Properties.Resources.StatusText_AccessibleDescription, pLen);
4480
4481             if (string.IsNullOrEmpty(StatusText.Text))
4482             {
4483                 this.inReplyTo = null;
4484             }
4485         }
4486
4487         /// <summary>
4488         /// メンション以外の文字列が含まれていないテキストであるか判定します
4489         /// </summary>
4490         internal static bool TextContainsOnlyMentions(string text)
4491         {
4492             var mentions = TweetExtractor.ExtractMentionEntities(text).OrderBy(x => x.Indices[0]);
4493             var startIndex = 0;
4494
4495             foreach (var mention in mentions)
4496             {
4497                 var textPart = text.Substring(startIndex, mention.Indices[0] - startIndex);
4498
4499                 if (!string.IsNullOrWhiteSpace(textPart))
4500                     return false;
4501
4502                 startIndex = mention.Indices[1];
4503             }
4504
4505             var textPartLast = text.Substring(startIndex);
4506
4507             if (!string.IsNullOrWhiteSpace(textPartLast))
4508                 return false;
4509
4510             return true;
4511         }
4512
4513         /// <summary>
4514         /// 投稿時に auto_populate_reply_metadata オプションによって自動で追加されるメンションを除去します
4515         /// </summary>
4516         private string RemoveAutoPopuratedMentions(string statusText, out long[] autoPopulatedUserIds)
4517         {
4518             List<long> _autoPopulatedUserIds = new List<long>();
4519
4520             var replyToPost = this.inReplyTo != null ? this._statuses[this.inReplyTo.Value.StatusId] : null;
4521             if (replyToPost != null)
4522             {
4523                 if (statusText.StartsWith($"@{replyToPost.ScreenName} ", StringComparison.Ordinal))
4524                 {
4525                     statusText = statusText.Substring(replyToPost.ScreenName.Length + 2);
4526                     _autoPopulatedUserIds.Add(replyToPost.UserId);
4527
4528                     foreach (var (userId, screenName) in replyToPost.ReplyToList)
4529                     {
4530                         if (statusText.StartsWith($"@{screenName} ", StringComparison.Ordinal))
4531                         {
4532                             statusText = statusText.Substring(screenName.Length + 2);
4533                             _autoPopulatedUserIds.Add(userId);
4534                         }
4535                     }
4536                 }
4537             }
4538
4539             autoPopulatedUserIds = _autoPopulatedUserIds.ToArray();
4540
4541             return statusText;
4542         }
4543
4544         /// <summary>
4545         /// attachment_url に指定可能な URL が含まれていれば除去
4546         /// </summary>
4547         private string RemoveAttachmentUrl(string statusText, out string attachmentUrl)
4548         {
4549             attachmentUrl = null;
4550
4551             // attachment_url は media_id と同時に使用できない
4552             if (this.ImageSelector.Visible && this.ImageSelector.SelectedService is TwitterPhoto)
4553                 return statusText;
4554
4555             var match = Twitter.AttachmentUrlRegex.Match(statusText);
4556             if (!match.Success)
4557                 return statusText;
4558
4559             attachmentUrl = match.Value;
4560
4561             // マッチした URL を空白に置換
4562             statusText = statusText.Substring(0, match.Index);
4563
4564             // テキストと URL の間にスペースが含まれていれば除去
4565             return statusText.TrimEnd(' ');
4566         }
4567
4568         private string FormatStatusTextExtended(string statusText)
4569             => this.FormatStatusTextExtended(statusText, out var autoPopulatedUserIds, out var attachmentUrl);
4570
4571         /// <summary>
4572         /// <see cref="FormatStatusText"/> に加えて、拡張モードで140字にカウントされない文字列の除去を行います
4573         /// </summary>
4574         private string FormatStatusTextExtended(string statusText, out long[] autoPopulatedUserIds, out string attachmentUrl)
4575         {
4576             statusText = this.RemoveAutoPopuratedMentions(statusText, out autoPopulatedUserIds);
4577
4578             statusText = this.RemoveAttachmentUrl(statusText, out attachmentUrl);
4579
4580             return this.FormatStatusText(statusText);
4581         }
4582
4583         /// <summary>
4584         /// ツイート投稿前のフッター付与などの前処理を行います
4585         /// </summary>
4586         private string FormatStatusText(string statusText)
4587         {
4588             statusText = statusText.Replace("\r\n", "\n");
4589
4590             if (this.urlMultibyteSplit)
4591             {
4592                 // URLと全角文字の切り離し
4593                 statusText = Regex.Replace(statusText, @"https?:\/\/[-_.!~*'()a-zA-Z0-9;\/?:\@&=+\$,%#^]+", "$& ");
4594             }
4595
4596             if (SettingManager.Common.WideSpaceConvert)
4597             {
4598                 // 文中の全角スペースを半角スペース1個にする
4599                 statusText = statusText.Replace(" ", " ");
4600             }
4601
4602             // DM の場合はこれ以降の処理を行わない
4603             if (statusText.StartsWith("D ", StringComparison.OrdinalIgnoreCase))
4604                 return statusText;
4605
4606             bool disableFooter;
4607             if (SettingManager.Common.PostShiftEnter)
4608             {
4609                 disableFooter = MyCommon.IsKeyDown(Keys.Control);
4610             }
4611             else
4612             {
4613                 if (this.StatusText.Multiline && !SettingManager.Common.PostCtrlEnter)
4614                     disableFooter = MyCommon.IsKeyDown(Keys.Control);
4615                 else
4616                     disableFooter = MyCommon.IsKeyDown(Keys.Shift);
4617             }
4618
4619             if (statusText.Contains("RT @"))
4620                 disableFooter = true;
4621
4622             // 自分宛のリプライの場合は先頭の「@screen_name 」の部分を除去する (in_reply_to_status_id は維持される)
4623             if (this.inReplyTo != null && this.inReplyTo.Value.ScreenName == this.tw.Username)
4624             {
4625                 var mentionSelf = $"@{this.tw.Username} ";
4626                 if (statusText.StartsWith(mentionSelf, StringComparison.OrdinalIgnoreCase))
4627                 {
4628                     if (statusText.Length > mentionSelf.Length || this.GetSelectedImageService() != null)
4629                         statusText = statusText.Substring(mentionSelf.Length);
4630                 }
4631             }
4632
4633             var header = "";
4634             var footer = "";
4635
4636             var hashtag = this.HashMgr.UseHash;
4637             if (!string.IsNullOrEmpty(hashtag) && !(this.HashMgr.IsNotAddToAtReply && this.inReplyTo != null))
4638             {
4639                 if (HashMgr.IsHead)
4640                     header = HashMgr.UseHash + " ";
4641                 else
4642                     footer = " " + HashMgr.UseHash;
4643             }
4644
4645             if (!disableFooter)
4646             {
4647                 if (SettingManager.Local.UseRecommendStatus)
4648                 {
4649                     // 推奨ステータスを使用する
4650                     footer += this.recommendedStatusFooter;
4651                 }
4652                 else if (!string.IsNullOrEmpty(SettingManager.Local.StatusText))
4653                 {
4654                     // テキストボックスに入力されている文字列を使用する
4655                     footer += " " + SettingManager.Local.StatusText.Trim();
4656                 }
4657             }
4658
4659             statusText = header + statusText + footer;
4660
4661             if (this.preventSmsCommand)
4662             {
4663                 // ツイートが意図せず SMS コマンドとして解釈されることを回避 (D, DM, M のみ)
4664                 // 参照: https://support.twitter.com/articles/14020
4665
4666                 if (Regex.IsMatch(statusText, @"^[+\-\[\]\s\\.,*/(){}^~|='&%$#""<>?]*(d|dm|m)([+\-\[\]\s\\.,*/(){}^~|='&%$#""<>?]+|$)", RegexOptions.IgnoreCase)
4667                     && !Twitter.DMSendTextRegex.IsMatch(statusText))
4668                 {
4669                     // U+200B (ZERO WIDTH SPACE) を先頭に加えて回避
4670                     statusText = '\u200b' + statusText;
4671                 }
4672             }
4673
4674             return statusText;
4675         }
4676
4677         /// <summary>
4678         /// 投稿欄に表示する入力可能な文字数を計算します
4679         /// </summary>
4680         private int GetRestStatusCount(string statusText)
4681         {
4682             var remainCount = this.tw.GetTextLengthRemain(statusText);
4683
4684             var uploadService = this.GetSelectedImageService();
4685             if (uploadService != null)
4686             {
4687                 // TODO: ImageSelector で選択中の画像の枚数が mediaCount 引数に渡るようにする
4688                 remainCount -= uploadService.GetReservedTextLength(1);
4689             }
4690
4691             return remainCount;
4692         }
4693
4694         private IMediaUploadService GetSelectedImageService()
4695             => this.ImageSelector.Visible ? this.ImageSelector.SelectedService : null;
4696
4697         private void MyList_CacheVirtualItems(object sender, CacheVirtualItemsEventArgs e)
4698         {
4699             if (sender != this.CurrentListView)
4700                 return;
4701
4702             var listCache = this._listItemCache;
4703             if (listCache?.TargetList == sender && listCache.IsSupersetOf(e.StartIndex, e.EndIndex))
4704             {
4705                 // If the newly requested cache is a subset of the old cache,
4706                 // no need to rebuild everything, so do nothing.
4707                 return;
4708             }
4709
4710             // Now we need to rebuild the cache.
4711             this.CreateCache(e.StartIndex, e.EndIndex);
4712         }
4713
4714         private void MyList_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
4715         {
4716             var listCache = this._listItemCache;
4717             if (listCache?.TargetList == sender)
4718             {
4719                 if (listCache.TryGetValue(e.ItemIndex, out var item, out var cacheItemPost))
4720                 {
4721                     e.Item = item;
4722                     return;
4723                 }
4724             }
4725
4726             // A cache miss, so create a new ListViewItem and pass it back.
4727             var tabPage = (TabPage)((DetailsListView)sender).Parent;
4728             var tab = this._statuses.Tabs[tabPage.Text];
4729             try
4730             {
4731                 e.Item = this.CreateItem(tab, tab[e.ItemIndex], e.ItemIndex);
4732             }
4733             catch (Exception)
4734             {
4735                 // 不正な要求に対する間に合わせの応答
4736                 string[] sitem = {"", "", "", "", "", "", "", ""};
4737                 e.Item = new ImageListViewItem(sitem);
4738             }
4739         }
4740
4741         private void CreateCache(int startIndex, int endIndex)
4742         {
4743             var tabInfo = this.CurrentTab;
4744
4745             if (tabInfo.AllCount == 0)
4746                 return;
4747
4748             // インデックスを 0...(tabInfo.AllCount - 1) の範囲内にする
4749             int FilterRange(int index)
4750                 => Math.Max(Math.Min(index, tabInfo.AllCount - 1), 0);
4751
4752             // キャッシュ要求(要求範囲±30を作成)
4753             startIndex = FilterRange(startIndex - 30);
4754             endIndex = FilterRange(endIndex + 30);
4755
4756             var cacheLength = endIndex - startIndex + 1;
4757
4758             var tab = this.CurrentTab;
4759             var posts = tabInfo[startIndex, endIndex]; //配列で取得
4760             var listItems = Enumerable.Range(0, cacheLength)
4761                 .Select(x => this.CreateItem(tab, posts[x], startIndex + x))
4762                 .ToArray();
4763
4764             var listCache = new ListViewItemCache
4765             {
4766                 TargetList = this.CurrentListView,
4767                 StartIndex = startIndex,
4768                 EndIndex = endIndex,
4769                 Cache = Enumerable.Zip(listItems, posts, (x, y) => (x, y)).ToArray(),
4770             };
4771
4772             Interlocked.Exchange(ref this._listItemCache, listCache);
4773         }
4774
4775         /// <summary>
4776         /// DetailsListView のための ListViewItem のキャッシュを消去する
4777         /// </summary>
4778         private void PurgeListViewItemCache()
4779             => Interlocked.Exchange(ref this._listItemCache, null);
4780
4781         private ListViewItem CreateItem(TabModel tab, PostClass Post, int Index)
4782         {
4783             StringBuilder mk = new StringBuilder();
4784             //if (Post.IsDeleted) mk.Append("×");
4785             //if (Post.IsMark) mk.Append("♪");
4786             //if (Post.IsProtect) mk.Append("Ю");
4787             //if (Post.InReplyToStatusId != null) mk.Append("⇒");
4788             if (Post.FavoritedCount > 0) mk.Append("+" + Post.FavoritedCount);
4789             ImageListViewItem itm;
4790             if (Post.RetweetedId == null)
4791             {
4792                 string[] sitem= {"",
4793                                  Post.Nickname,
4794                                  Post.IsDeleted ? "(DELETED)" : Post.AccessibleText.Replace('\n', ' '),
4795                                  Post.CreatedAt.ToLocalTimeString(SettingManager.Common.DateTimeFormat),
4796                                  Post.ScreenName,
4797                                  "",
4798                                  mk.ToString(),
4799                                  Post.Source};
4800                 itm = new ImageListViewItem(sitem, this.IconCache, Post.ImageUrl);
4801             }
4802             else
4803             {
4804                 string[] sitem = {"",
4805                                   Post.Nickname,
4806                                   Post.IsDeleted ? "(DELETED)" : Post.AccessibleText.Replace('\n', ' '),
4807                                   Post.CreatedAt.ToLocalTimeString(SettingManager.Common.DateTimeFormat),
4808                                   Post.ScreenName + Environment.NewLine + "(RT:" + Post.RetweetedBy + ")",
4809                                   "",
4810                                   mk.ToString(),
4811                                   Post.Source};
4812                 itm = new ImageListViewItem(sitem, this.IconCache, Post.ImageUrl);
4813             }
4814             itm.StateIndex = Post.StateIndex;
4815             itm.Tag = Post;
4816
4817             bool read = Post.IsRead;
4818             // 未読管理していなかったら既読として扱う
4819             if (!tab.UnreadManage || !SettingManager.Common.UnreadManage)
4820                 read = true;
4821
4822             ChangeItemStyleRead(read, itm, Post, null);
4823
4824             if (tab.TabName == this.CurrentTabName)
4825                 this.ColorizeList(itm, Post, Index);
4826
4827             return itm;
4828         }
4829
4830         /// <summary>
4831         /// 全てのタブの振り分けルールを反映し直します
4832         /// </summary>
4833         private void ApplyPostFilters()
4834         {
4835             using (ControlTransaction.Cursor(this, Cursors.WaitCursor))
4836             {
4837                 this.PurgeListViewItemCache();
4838                 this._statuses.FilterAll();
4839
4840                 foreach (var (tab, index) in this._statuses.Tabs.WithIndex())
4841                 {
4842                     var tabPage = this.ListTab.TabPages[index];
4843                     var listview = (DetailsListView)tabPage.Tag;
4844                     using (ControlTransaction.Update(listview))
4845                     {
4846                         listview.VirtualListSize = tab.AllCount;
4847                     }
4848
4849                     if (SettingManager.Common.TabIconDisp)
4850                     {
4851                         if (tab.UnreadCount > 0)
4852                             tabPage.ImageIndex = 0;
4853                         else
4854                             tabPage.ImageIndex = -1;
4855                     }
4856                 }
4857
4858                 if (!SettingManager.Common.TabIconDisp)
4859                     this.ListTab.Refresh();
4860
4861                 SetMainWindowTitle();
4862                 SetStatusLabelUrl();
4863             }
4864         }
4865
4866         private void MyList_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
4867             => e.DrawDefault = true;
4868
4869         private void MyList_HScrolled(object sender, EventArgs e)
4870             => ((DetailsListView)sender).Refresh();
4871
4872         private void MyList_DrawItem(object sender, DrawListViewItemEventArgs e)
4873         {
4874             if (e.State == 0) return;
4875             e.DrawDefault = false;
4876
4877             SolidBrush brs2 = null;
4878             if (!e.Item.Selected)     //e.ItemStateでうまく判定できない???
4879             {
4880                 if (e.Item.BackColor == _clSelf)
4881                     brs2 = _brsBackColorMine;
4882                 else if (e.Item.BackColor == _clAtSelf)
4883                     brs2 = _brsBackColorAt;
4884                 else if (e.Item.BackColor == _clTarget)
4885                     brs2 = _brsBackColorYou;
4886                 else if (e.Item.BackColor == _clAtTarget)
4887                     brs2 = _brsBackColorAtYou;
4888                 else if (e.Item.BackColor == _clAtFromTarget)
4889                     brs2 = _brsBackColorAtFromTarget;
4890                 else if (e.Item.BackColor == _clAtTo)
4891                     brs2 = _brsBackColorAtTo;
4892                 else
4893                     brs2 = _brsBackColorNone;
4894             }
4895             else
4896             {
4897                 //選択中の行
4898                 if (((Control)sender).Focused)
4899                     brs2 = _brsHighLight;
4900                 else
4901                     brs2 = _brsDeactiveSelection;
4902             }
4903             e.Graphics.FillRectangle(brs2, e.Bounds);
4904             e.DrawFocusRectangle();
4905             this.DrawListViewItemIcon(e);
4906         }
4907
4908         private void MyList_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
4909         {
4910             if (e.ItemState == 0) return;
4911
4912             if (e.ColumnIndex > 0)
4913             {
4914                 //アイコン以外の列
4915                 var post = (PostClass)e.Item.Tag;
4916
4917                 RectangleF rct = e.Bounds;
4918                 rct.Width = e.Header.Width;
4919                 int fontHeight = e.Item.Font.Height;
4920                 if (_iconCol)
4921                 {
4922                     rct.Y += fontHeight;
4923                     rct.Height -= fontHeight;
4924                 }
4925
4926                 int drawLineCount = Math.Max(1, Math.DivRem((int)rct.Height, fontHeight, out var heightDiff));
4927
4928                 //if (heightDiff > fontHeight * 0.7)
4929                 //{
4930                 //    rct.Height += fontHeight;
4931                 //    drawLineCount += 1;
4932                 //}
4933
4934                 //フォントの高さの半分を足してるのは保険。無くてもいいかも。
4935                 if (!_iconCol && drawLineCount <= 1)
4936                 {
4937                     //rct.Inflate(0, heightDiff / -2);
4938                     //rct.Height += fontHeight / 2;
4939                 }
4940                 else if (heightDiff < fontHeight * 0.7)
4941                 {
4942                     //最終行が70%以上欠けていたら、最終行は表示しない
4943                     //rct.Height = (float)((fontHeight * drawLineCount) + (fontHeight / 2));
4944                     rct.Height = (fontHeight * drawLineCount) - 1;
4945                 }
4946                 else
4947                 {
4948                     drawLineCount += 1;
4949                 }
4950
4951                 //if (!_iconCol && drawLineCount > 1)
4952                 //{
4953                 //    rct.Y += fontHeight * 0.2;
4954                 //    if (heightDiff >= fontHeight * 0.8) rct.Height -= fontHeight * 0.2;
4955                 //}
4956
4957                 if (rct.Width > 0)
4958                 {
4959                     Color color = (!e.Item.Selected) ? e.Item.ForeColor :   //選択されていない行
4960                         (((Control)sender).Focused) ? _clHighLight :        //選択中の行
4961                         _clUnread;
4962
4963                     if (_iconCol)
4964                     {
4965                         Rectangle rctB = e.Bounds;
4966                         rctB.Width = e.Header.Width;
4967                         rctB.Height = fontHeight;
4968
4969                         using (Font fnt = new Font(e.Item.Font, FontStyle.Bold))
4970                         {
4971                             TextRenderer.DrawText(e.Graphics,
4972                                                     post.IsDeleted ? "(DELETED)" : post.TextSingleLine,
4973                                                     e.Item.Font,
4974                                                     Rectangle.Round(rct),
4975                                                     color,
4976                                                     TextFormatFlags.WordBreak |
4977                                                     TextFormatFlags.EndEllipsis |
4978                                                     TextFormatFlags.GlyphOverhangPadding |
4979                                                     TextFormatFlags.NoPrefix);
4980                             TextRenderer.DrawText(e.Graphics,
4981                                                     e.Item.SubItems[4].Text + " / " + e.Item.SubItems[1].Text + " (" + e.Item.SubItems[3].Text + ") " + e.Item.SubItems[5].Text + e.Item.SubItems[6].Text + " [" + e.Item.SubItems[7].Text + "]",
4982                                                     fnt,
4983                                                     rctB,
4984                                                     color,
4985                                                     TextFormatFlags.SingleLine |
4986                                                     TextFormatFlags.EndEllipsis |
4987                                                     TextFormatFlags.GlyphOverhangPadding |
4988                                                     TextFormatFlags.NoPrefix);
4989                         }
4990                     }
4991                     else
4992                     {
4993                         string text;
4994                         if (e.ColumnIndex != 2)
4995                             text = e.SubItem.Text;
4996                         else
4997                             text = post.IsDeleted ? "(DELETED)" : post.TextSingleLine;
4998
4999                         if (drawLineCount == 1)
5000                         {
5001                             TextRenderer.DrawText(e.Graphics,
5002                                                     text,
5003                                                     e.Item.Font,
5004                                                     Rectangle.Round(rct),
5005                                                     color,
5006                                                     TextFormatFlags.SingleLine |
5007                                                     TextFormatFlags.EndEllipsis |
5008                                                     TextFormatFlags.GlyphOverhangPadding |
5009                                                     TextFormatFlags.NoPrefix |
5010                                                     TextFormatFlags.VerticalCenter);
5011                         }
5012                         else
5013                         {
5014                             TextRenderer.DrawText(e.Graphics,
5015                                                     text,
5016                                                     e.Item.Font,
5017                                                     Rectangle.Round(rct),
5018                                                     color,
5019                                                     TextFormatFlags.WordBreak |
5020                                                     TextFormatFlags.EndEllipsis |
5021                                                     TextFormatFlags.GlyphOverhangPadding |
5022                                                     TextFormatFlags.NoPrefix);
5023                         }
5024                     }
5025                     //if (e.ColumnIndex == 6) this.DrawListViewItemStateIcon(e, rct);
5026                 }
5027             }
5028         }
5029
5030         private void DrawListViewItemIcon(DrawListViewItemEventArgs e)
5031         {
5032             if (_iconSz == 0) return;
5033
5034             ImageListViewItem item = (ImageListViewItem)e.Item;
5035
5036             //e.Bounds.Leftが常に0を指すから自前で計算
5037             Rectangle itemRect = item.Bounds;
5038             var col0 = e.Item.ListView.Columns[0];
5039             itemRect.Width = col0.Width;
5040
5041             if (col0.DisplayIndex > 0)
5042             {
5043                 foreach (ColumnHeader clm in e.Item.ListView.Columns)
5044                 {
5045                     if (clm.DisplayIndex < col0.DisplayIndex)
5046                         itemRect.X += clm.Width;
5047                 }
5048             }
5049
5050             // ディスプレイの DPI 設定を考慮したアイコンサイズ
5051             var realIconSize = new SizeF(this._iconSz * this.CurrentScaleFactor.Width, this._iconSz * this.CurrentScaleFactor.Height).ToSize();
5052             var realStateSize = new SizeF(16 * this.CurrentScaleFactor.Width, 16 * this.CurrentScaleFactor.Height).ToSize();
5053
5054             Rectangle iconRect;
5055             var img = item.Image;
5056             if (img != null)
5057             {
5058                 iconRect = Rectangle.Intersect(new Rectangle(e.Item.GetBounds(ItemBoundsPortion.Icon).Location, realIconSize), itemRect);
5059                 iconRect.Offset(0, Math.Max(0, (itemRect.Height - realIconSize.Height) / 2));
5060
5061                 if (iconRect.Width > 0)
5062                 {
5063                     e.Graphics.FillRectangle(Brushes.White, iconRect);
5064                     e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
5065                     try
5066                     {
5067                         e.Graphics.DrawImage(img.Image, iconRect);
5068                     }
5069                     catch (ArgumentException)
5070                     {
5071                         item.RefreshImageAsync();
5072                     }
5073                 }
5074             }
5075             else
5076             {
5077                 iconRect = Rectangle.Intersect(new Rectangle(e.Item.GetBounds(ItemBoundsPortion.Icon).Location, new Size(1, 1)), itemRect);
5078                 //iconRect.Offset(0, Math.Max(0, (itemRect.Height - realIconSize.Height) / 2));
5079
5080                 item.GetImageAsync();
5081             }
5082
5083             if (item.StateIndex > -1)
5084             {
5085                 Rectangle stateRect = Rectangle.Intersect(new Rectangle(new Point(iconRect.X + realIconSize.Width + 2, iconRect.Y), realStateSize), itemRect);
5086                 if (stateRect.Width > 0)
5087                 {
5088                     //e.Graphics.FillRectangle(Brushes.White, stateRect);
5089                     //e.Graphics.InterpolationMode = Drawing2D.InterpolationMode.High;
5090                     e.Graphics.DrawImage(this.PostStateImageList.Images[item.StateIndex], stateRect);
5091                 }
5092             }
5093         }
5094
5095         protected override void ScaleControl(SizeF factor, BoundsSpecified specified)
5096         {
5097             base.ScaleControl(factor, specified);
5098
5099             ScaleChildControl(this.TabImage, factor);
5100
5101             var tabpages = this.ListTab.TabPages.Cast<TabPage>();
5102             var listviews = tabpages.Select(x => x.Tag).Cast<ListView>();
5103
5104             foreach (var listview in listviews)
5105             {
5106                 ScaleChildControl(listview, factor);
5107             }
5108         }
5109
5110         //private void DrawListViewItemStateIcon(DrawListViewSubItemEventArgs e, RectangleF rct)
5111         //{
5112         //    ImageListViewItem item = (ImageListViewItem)e.Item;
5113         //    if (item.StateImageIndex > -1)
5114         //    {
5115         //        ////e.Bounds.Leftが常に0を指すから自前で計算
5116         //        //Rectangle itemRect = item.Bounds;
5117         //        //itemRect.Width = e.Item.ListView.Columns[4].Width;
5118
5119         //        //foreach (ColumnHeader clm in e.Item.ListView.Columns)
5120         //        //{
5121         //        //    if (clm.DisplayIndex < e.Item.ListView.Columns[4].DisplayIndex)
5122         //        //    {
5123         //        //        itemRect.X += clm.Width;
5124         //        //    }
5125         //        //}
5126
5127         //        //Rectangle iconRect = Rectangle.Intersect(new Rectangle(e.Item.GetBounds(ItemBoundsPortion.Icon).Location, new Size(_iconSz, _iconSz)), itemRect);
5128         //        //iconRect.Offset(0, Math.Max(0, (itemRect.Height - _iconSz) / 2));
5129
5130         //        if (rct.Width > 0)
5131         //        {
5132         //            RectangleF stateRect = RectangleF.Intersect(rct, new RectangleF(rct.Location, new Size(18, 16)));
5133         //            //e.Graphics.FillRectangle(Brushes.White, rct);
5134         //            //e.Graphics.InterpolationMode = Drawing2D.InterpolationMode.High;
5135         //            e.Graphics.DrawImage(this.PostStateImageList.Images(item.StateImageIndex), stateRect);
5136         //        }
5137         //    }
5138         //}
5139
5140         internal void DoTabSearch(string searchWord, bool caseSensitive, bool useRegex, SEARCHTYPE searchType)
5141         {
5142             var tab = this.CurrentTab;
5143
5144             if (tab.AllCount == 0)
5145             {
5146                 MessageBox.Show(Properties.Resources.DoTabSearchText2, Properties.Resources.DoTabSearchText3, MessageBoxButtons.OK, MessageBoxIcon.Information);
5147                 return;
5148             }
5149
5150             var selectedIndex = tab.SelectedIndex;
5151
5152             int startIndex;
5153             switch (searchType)
5154             {
5155                 case SEARCHTYPE.NextSearch: // 次を検索
5156                     if (selectedIndex != -1)
5157                         startIndex = Math.Min(selectedIndex + 1, tab.AllCount - 1);
5158                     else
5159                         startIndex = 0;
5160                     break;
5161                 case SEARCHTYPE.PrevSearch: // 前を検索
5162                     if (selectedIndex != -1)
5163                         startIndex = Math.Max(selectedIndex - 1, 0);
5164                     else
5165                         startIndex = tab.AllCount - 1;
5166                     break;
5167                 case SEARCHTYPE.DialogSearch: // ダイアログからの検索
5168                 default:
5169                     if (selectedIndex != -1)
5170                         startIndex = selectedIndex;
5171                     else
5172                         startIndex = 0;
5173                     break;
5174             }
5175
5176             Func<string, bool> stringComparer;
5177             try
5178             {
5179                 stringComparer = this.CreateSearchComparer(searchWord, useRegex, caseSensitive);
5180             }
5181             catch (ArgumentException)
5182             {
5183                 MessageBox.Show(Properties.Resources.DoTabSearchText1, Properties.Resources.DoTabSearchText3, MessageBoxButtons.OK, MessageBoxIcon.Error);
5184                 return;
5185             }
5186
5187             var reverse = searchType == SEARCHTYPE.PrevSearch;
5188             var foundIndex = tab.SearchPostsAll(stringComparer, startIndex, reverse)
5189                 .DefaultIfEmpty(-1).First();
5190
5191             if (foundIndex == -1)
5192             {
5193                 MessageBox.Show(Properties.Resources.DoTabSearchText2, Properties.Resources.DoTabSearchText3, MessageBoxButtons.OK, MessageBoxIcon.Information);
5194                 return;
5195             }
5196
5197             var listView = this.CurrentListView;
5198             this.SelectListItem(listView, foundIndex);
5199             listView.EnsureVisible(foundIndex);
5200         }
5201
5202         private void MenuItemSubSearch_Click(object sender, EventArgs e)
5203             => this.ShowSearchDialog(); // 検索メニュー
5204
5205         private void MenuItemSearchNext_Click(object sender, EventArgs e)
5206         {
5207             var previousSearch = this.SearchDialog.ResultOptions;
5208             if (previousSearch == null || previousSearch.Type != SearchWordDialog.SearchType.Timeline)
5209             {
5210                 this.SearchDialog.Reset();
5211                 this.ShowSearchDialog();
5212                 return;
5213             }
5214
5215             // 次を検索
5216             this.DoTabSearch(
5217                 previousSearch.Query,
5218                 previousSearch.CaseSensitive,
5219                 previousSearch.UseRegex,
5220                 SEARCHTYPE.NextSearch);
5221         }
5222
5223         private void MenuItemSearchPrev_Click(object sender, EventArgs e)
5224         {
5225             var previousSearch = this.SearchDialog.ResultOptions;
5226             if (previousSearch == null || previousSearch.Type != SearchWordDialog.SearchType.Timeline)
5227             {
5228                 this.SearchDialog.Reset();
5229                 this.ShowSearchDialog();
5230                 return;
5231             }
5232
5233             // 前を検索
5234             this.DoTabSearch(
5235                 previousSearch.Query,
5236                 previousSearch.CaseSensitive,
5237                 previousSearch.UseRegex,
5238                 SEARCHTYPE.PrevSearch);
5239         }
5240
5241         /// <summary>
5242         /// 検索ダイアログを表示し、検索を実行します
5243         /// </summary>
5244         private void ShowSearchDialog()
5245         {
5246             if (this.SearchDialog.ShowDialog(this) != DialogResult.OK)
5247             {
5248                 this.TopMost = SettingManager.Common.AlwaysTop;
5249                 return;
5250             }
5251             this.TopMost = SettingManager.Common.AlwaysTop;
5252
5253             var searchOptions = this.SearchDialog.ResultOptions;
5254             if (searchOptions.Type == SearchWordDialog.SearchType.Timeline)
5255             {
5256                 if (searchOptions.NewTab)
5257                 {
5258                     var tabName = Properties.Resources.SearchResults_TabName;
5259
5260                     try
5261                     {
5262                         tabName = this._statuses.MakeTabName(tabName);
5263                     }
5264                     catch (TabException ex)
5265                     {
5266                         MessageBox.Show(this, ex.Message, ApplicationSettings.ApplicationName, MessageBoxButtons.OK, MessageBoxIcon.Error);
5267                     }
5268
5269                     var resultTab = new LocalSearchTabModel(tabName);
5270                     this.AddNewTab(resultTab, startup: false);
5271                     this._statuses.AddTab(resultTab);
5272
5273                     var targetTab = this.CurrentTab;
5274
5275                     Func<string, bool> stringComparer;
5276                     try
5277                     {
5278                         stringComparer = this.CreateSearchComparer(searchOptions.Query, searchOptions.UseRegex, searchOptions.CaseSensitive);
5279                     }
5280                     catch (ArgumentException)
5281                     {
5282                         MessageBox.Show(Properties.Resources.DoTabSearchText1, Properties.Resources.DoTabSearchText3, MessageBoxButtons.OK, MessageBoxIcon.Error);
5283                         return;
5284                     }
5285
5286                     var foundIndices = targetTab.SearchPostsAll(stringComparer).ToArray();
5287                     if (foundIndices.Length == 0)
5288                     {
5289                         MessageBox.Show(Properties.Resources.DoTabSearchText2, Properties.Resources.DoTabSearchText3, MessageBoxButtons.OK, MessageBoxIcon.Information);
5290                         return;
5291                     }
5292
5293                     var foundPosts = foundIndices.Select(x => targetTab[x]);
5294                     foreach (var post in foundPosts)
5295                     {
5296                         resultTab.AddPostQueue(post);
5297                     }
5298
5299                     this._statuses.DistributePosts();
5300                     this.RefreshTimeline();
5301
5302                     var tabIndex = this._statuses.Tabs.IndexOf(tabName);
5303                     this.ListTab.SelectedIndex = tabIndex;
5304                 }
5305                 else
5306                 {
5307                     this.DoTabSearch(
5308                         searchOptions.Query,
5309                         searchOptions.CaseSensitive,
5310                         searchOptions.UseRegex,
5311                         SEARCHTYPE.DialogSearch);
5312                 }
5313             }
5314             else if (searchOptions.Type == SearchWordDialog.SearchType.Public)
5315             {
5316                 this.AddNewTabForSearch(searchOptions.Query);
5317             }
5318         }
5319
5320         /// <summary>発言検索に使用するメソッドを生成します</summary>
5321         /// <exception cref="ArgumentException">
5322         /// <paramref name="useRegex"/> が true かつ、<paramref name="query"/> が不正な正規表現な場合
5323         /// </exception>
5324         private Func<string, bool> CreateSearchComparer(string query, bool useRegex, bool caseSensitive)
5325         {
5326             if (useRegex)
5327             {
5328                 var regexOption = caseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase;
5329                 var regex = new Regex(query, regexOption);
5330
5331                 return x => regex.IsMatch(x);
5332             }
5333             else
5334             {
5335                 var comparisonType = caseSensitive ? StringComparison.CurrentCulture : StringComparison.CurrentCultureIgnoreCase;
5336
5337                 return x => x.IndexOf(query, comparisonType) != -1;
5338             }
5339         }
5340
5341         private void AboutMenuItem_Click(object sender, EventArgs e)
5342         {
5343             using (TweenAboutBox about = new TweenAboutBox())
5344             {
5345                 about.ShowDialog(this);
5346             }
5347             this.TopMost = SettingManager.Common.AlwaysTop;
5348         }
5349
5350         private void JumpUnreadMenuItem_Click(object sender, EventArgs e)
5351         {
5352             var bgnIdx = this._statuses.SelectedTabIndex;
5353
5354             if (ImageSelector.Enabled)
5355                 return;
5356
5357             TabModel foundTab = null;
5358             int foundIndex = 0;
5359
5360             DetailsListView lst = null;
5361
5362             //現在タブから最終タブまで探索
5363             foreach (var (tab, index) in this._statuses.Tabs.WithIndex().Skip(bgnIdx))
5364             {
5365                 var unreadIndex = tab.NextUnreadIndex;
5366                 if (unreadIndex != -1)
5367                 {
5368                     ListTab.SelectedIndex = index;
5369                     foundTab = tab;
5370                     foundIndex = unreadIndex;
5371                     lst = (DetailsListView)this.ListTab.TabPages[index].Tag;
5372                     break;
5373                 }
5374             }
5375
5376             //未読みつからず&現在タブが先頭ではなかったら、先頭タブから現在タブの手前まで探索
5377             if (foundTab == null && bgnIdx > 0)
5378             {
5379                 foreach (var (tab, index) in this._statuses.Tabs.WithIndex().Take(bgnIdx))
5380                 {
5381                     var unreadIndex = tab.NextUnreadIndex;
5382                     if (unreadIndex != -1)
5383                     {
5384                         ListTab.SelectedIndex = index;
5385                         foundTab = tab;
5386                         foundIndex = unreadIndex;
5387                         lst = (DetailsListView)this.ListTab.TabPages[index].Tag;
5388                         break;
5389                     }
5390                 }
5391             }
5392
5393             if (foundTab == null)
5394             {
5395                 //全部調べたが未読見つからず→先頭タブの最新発言へ
5396                 ListTab.SelectedIndex = 0;
5397                 var tabPage = this.ListTab.TabPages[0];
5398                 var tab = this._statuses.Tabs[0];
5399
5400                 if (tab.AllCount == 0)
5401                     return;
5402
5403                 if (_statuses.SortOrder == SortOrder.Ascending)
5404                     foundIndex = tab.AllCount - 1;
5405                 else
5406                     foundIndex = 0;
5407
5408                 lst = (DetailsListView)tabPage.Tag;
5409             }
5410
5411             SelectListItem(lst, foundIndex);
5412
5413             if (_statuses.SortMode == ComparerMode.Id)
5414             {
5415                 if (_statuses.SortOrder == SortOrder.Ascending && lst.Items[foundIndex].Position.Y > lst.ClientSize.Height - _iconSz - 10 ||
5416                     _statuses.SortOrder == SortOrder.Descending && lst.Items[foundIndex].Position.Y < _iconSz + 10)
5417                 {
5418                     MoveTop();
5419                 }
5420                 else
5421                 {
5422                     lst.EnsureVisible(foundIndex);
5423                 }
5424             }
5425             else
5426             {
5427                 lst.EnsureVisible(foundIndex);
5428             }
5429
5430             lst.Focus();
5431         }
5432
5433         private async void StatusOpenMenuItem_Click(object sender, EventArgs e)
5434         {
5435             var tab = this.CurrentTab;
5436             var post = this.CurrentPost;
5437             if (post != null && tab.TabType != MyCommon.TabUsageType.DirectMessage)
5438                 await this.OpenUriInBrowserAsync(MyCommon.GetStatusUrl(post));
5439         }
5440
5441         private async void VerUpMenuItem_Click(object sender, EventArgs e)
5442             => await this.CheckNewVersion(false);
5443
5444         private void RunTweenUp()
5445         {
5446             var pinfo = new ProcessStartInfo
5447             {
5448                 UseShellExecute = true,
5449                 WorkingDirectory = MyCommon.settingPath,
5450                 FileName = Path.Combine(MyCommon.settingPath, "TweenUp3.exe"),
5451                 Arguments = "\"" + Application.StartupPath + "\"",
5452             };
5453
5454             try
5455             {
5456                 Process.Start(pinfo);
5457             }
5458             catch (Exception)
5459             {
5460                 MessageBox.Show("Failed to execute TweenUp3.exe.");
5461             }
5462         }
5463
5464         public class VersionInfo
5465         {
5466             public Version Version { get; set; }
5467             public Uri DownloadUri { get; set; }
5468             public string ReleaseNote { get; set; }
5469         }
5470
5471         /// <summary>
5472         /// OpenTween の最新バージョンの情報を取得します
5473         /// </summary>
5474         public async Task<VersionInfo> GetVersionInfoAsync()
5475         {
5476             var versionInfoUrl = new Uri(ApplicationSettings.VersionInfoUrl + "?" +
5477                 DateTimeUtc.Now.ToString("yyMMddHHmmss") + Environment.TickCount);
5478
5479             var responseText = await Networking.Http.GetStringAsync(versionInfoUrl)
5480                 .ConfigureAwait(false);
5481
5482             // 改行2つで前後パートを分割(前半がバージョン番号など、後半が詳細テキスト)
5483             var msgPart = responseText.Split(new[] { "\n\n", "\r\n\r\n" }, 2, StringSplitOptions.None);
5484
5485             var msgHeader = msgPart[0].Split(new[] { "\n", "\r\n" }, StringSplitOptions.None);
5486             var msgBody = msgPart.Length == 2 ? msgPart[1] : "";
5487
5488             msgBody = Regex.Replace(msgBody, "(?<!\r)\n", "\r\n"); // LF -> CRLF
5489
5490             return new VersionInfo
5491             {
5492                 Version = Version.Parse(msgHeader[0]),
5493                 DownloadUri = new Uri(msgHeader[1]),
5494                 ReleaseNote = msgBody,
5495             };
5496         }
5497
5498         private async Task CheckNewVersion(bool startup = false)
5499         {
5500             if (ApplicationSettings.VersionInfoUrl == null)
5501                 return; // 更新チェック無効化
5502
5503             try
5504             {
5505                 var versionInfo = await this.GetVersionInfoAsync();
5506
5507                 if (versionInfo.Version <= Version.Parse(MyCommon.FileVersion))
5508                 {
5509                     // 更新不要
5510                     if (!startup)
5511                     {
5512                         var msgtext = string.Format(Properties.Resources.CheckNewVersionText7,
5513                             MyCommon.GetReadableVersion(), MyCommon.GetReadableVersion(versionInfo.Version));
5514                         msgtext = MyCommon.ReplaceAppName(msgtext);
5515
5516                         MessageBox.Show(msgtext,
5517                             MyCommon.ReplaceAppName(Properties.Resources.CheckNewVersionText2),
5518                             MessageBoxButtons.OK, MessageBoxIcon.Information);
5519                     }
5520                     return;
5521                 }
5522
5523                 if (startup && versionInfo.Version <= SettingManager.Common.SkipUpdateVersion)
5524                     return;
5525
5526                 using (var dialog = new UpdateDialog())
5527                 {
5528                     dialog.SummaryText = string.Format(Properties.Resources.CheckNewVersionText3,
5529                         MyCommon.GetReadableVersion(versionInfo.Version));
5530                     dialog.DetailsText = versionInfo.ReleaseNote;
5531
5532                     if (dialog.ShowDialog(this) == DialogResult.Yes)
5533                     {
5534                         await this.OpenUriInBrowserAsync(versionInfo.DownloadUri.OriginalString);
5535                     }
5536                     else if (dialog.SkipButtonPressed)
5537                     {
5538                         SettingManager.Common.SkipUpdateVersion = versionInfo.Version;
5539                         this.MarkSettingCommonModified();
5540                     }
5541                 }
5542             }
5543             catch (Exception)
5544             {
5545                 this.StatusLabel.Text = Properties.Resources.CheckNewVersionText9;
5546                 if (!startup)
5547                 {
5548                     MessageBox.Show(Properties.Resources.CheckNewVersionText10,
5549                         MyCommon.ReplaceAppName(Properties.Resources.CheckNewVersionText2),
5550                         MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button2);
5551                 }
5552             }
5553         }
5554
5555         private void UpdateSelectedPost()
5556         {
5557             //件数関連の場合、タイトル即時書き換え
5558             if (SettingManager.Common.DispLatestPost != MyCommon.DispTitleEnum.None &&
5559                SettingManager.Common.DispLatestPost != MyCommon.DispTitleEnum.Post &&
5560                SettingManager.Common.DispLatestPost != MyCommon.DispTitleEnum.Ver &&
5561                SettingManager.Common.DispLatestPost != MyCommon.DispTitleEnum.OwnStatus)
5562             {
5563                 SetMainWindowTitle();
5564             }
5565             if (!StatusLabelUrl.Text.StartsWith("http", StringComparison.OrdinalIgnoreCase))
5566                 SetStatusLabelUrl();
5567
5568             if (SettingManager.Common.TabIconDisp)
5569             {
5570                 foreach (var (tab, index) in this._statuses.Tabs.WithIndex())
5571                 {
5572                     if (tab.UnreadCount == 0)
5573                     {
5574                         var tabPage = this.ListTab.TabPages[index];
5575                         if (tabPage.ImageIndex == 0)
5576                             tabPage.ImageIndex = -1;
5577                     }
5578                 }
5579             }
5580             else
5581             {
5582                 this.ListTab.Refresh();
5583             }
5584
5585             this.DispSelectedPost();
5586         }
5587
5588         public string createDetailHtml(string orgdata)
5589             => detailHtmlFormatHeader + orgdata + detailHtmlFormatFooter;
5590
5591         private void DispSelectedPost()
5592             => this.DispSelectedPost(false);
5593
5594         private PostClass displayPost = new PostClass();
5595
5596         /// <summary>
5597         /// サムネイル表示に使用する CancellationToken の生成元
5598         /// </summary>
5599         private CancellationTokenSource thumbnailTokenSource = null;
5600
5601         private void DispSelectedPost(bool forceupdate)
5602         {
5603             var currentPost = this.CurrentPost;
5604             if (currentPost == null)
5605                 return;
5606
5607             var oldDisplayPost = this.displayPost;
5608             this.displayPost = currentPost;
5609
5610             if (!forceupdate && currentPost.Equals(oldDisplayPost))
5611                 return;
5612
5613             var loadTasks = new List<Task>
5614             {
5615                 this.tweetDetailsView.ShowPostDetails(currentPost),
5616             };
5617
5618             this.SplitContainer3.Panel2Collapsed = true;
5619
5620             if (SettingManager.Common.PreviewEnable)
5621             {
5622                 var oldTokenSource = Interlocked.Exchange(ref this.thumbnailTokenSource, new CancellationTokenSource());
5623                 oldTokenSource?.Cancel();
5624
5625                 var token = this.thumbnailTokenSource.Token;
5626                 loadTasks.Add(this.tweetThumbnail1.ShowThumbnailAsync(currentPost, token));
5627             }
5628
5629             async Task delayedTasks()
5630             {
5631                 try
5632                 {
5633                     await Task.WhenAll(loadTasks);
5634                 }
5635                 catch (OperationCanceledException) { }
5636             }
5637
5638             // サムネイルの読み込みを待たずに次に選択されたツイートを表示するため await しない
5639             _ = delayedTasks();
5640         }
5641
5642         private async void MatomeMenuItem_Click(object sender, EventArgs e)
5643             => await this.OpenApplicationWebsite();
5644
5645         private async Task OpenApplicationWebsite()
5646             => await this.OpenUriInBrowserAsync(ApplicationSettings.WebsiteUrl);
5647
5648         private async void ShortcutKeyListMenuItem_Click(object sender, EventArgs e)
5649             => await this.OpenUriInBrowserAsync(ApplicationSettings.ShortcutKeyUrl);
5650
5651         private async void ListTab_KeyDown(object sender, KeyEventArgs e)
5652         {
5653             var tab = this.CurrentTab;
5654             if (tab.TabType == MyCommon.TabUsageType.PublicSearch)
5655             {
5656                 Control pnl = this.CurrentTabPage.Controls["panelSearch"];
5657                 if (pnl.Controls["comboSearch"].Focused ||
5658                     pnl.Controls["comboLang"].Focused ||
5659                     pnl.Controls["buttonSearch"].Focused) return;
5660             }
5661
5662             if (e.Control || e.Shift || e.Alt)
5663                 this._anchorFlag = false;
5664
5665             if (CommonKeyDown(e.KeyData, FocusedControl.ListTab, out var asyncTask))
5666             {
5667                 e.Handled = true;
5668                 e.SuppressKeyPress = true;
5669             }
5670
5671             if (asyncTask != null)
5672                 await asyncTask;
5673         }
5674
5675         private ShortcutCommand[] shortcutCommands = Array.Empty<ShortcutCommand>();
5676
5677         private void InitializeShortcuts()
5678         {
5679             this.shortcutCommands = new[]
5680             {
5681                 // リストのカーソル移動関係(上下キー、PageUp/Downに該当)
5682                 ShortcutCommand.Create(Keys.J, Keys.Control | Keys.J, Keys.Shift | Keys.J, Keys.Control | Keys.Shift | Keys.J)
5683                     .FocusedOn(FocusedControl.ListTab)
5684                     .Do(() => SendKeys.Send("{DOWN}")),
5685
5686                 ShortcutCommand.Create(Keys.K, Keys.Control | Keys.K, Keys.Shift | Keys.K, Keys.Control | Keys.Shift | Keys.K)
5687                     .FocusedOn(FocusedControl.ListTab)
5688                     .Do(() => SendKeys.Send("{UP}")),
5689
5690                 ShortcutCommand.Create(Keys.F, Keys.Shift | Keys.F)
5691                     .FocusedOn(FocusedControl.ListTab)
5692                     .Do(() => SendKeys.Send("{PGDN}")),
5693
5694                 ShortcutCommand.Create(Keys.B, Keys.Shift | Keys.B)
5695                     .FocusedOn(FocusedControl.ListTab)
5696                     .Do(() => SendKeys.Send("{PGUP}")),
5697
5698                 ShortcutCommand.Create(Keys.F1)
5699                     .Do(() => this.OpenApplicationWebsite()),
5700
5701                 ShortcutCommand.Create(Keys.F3)
5702                     .Do(() => this.MenuItemSearchNext_Click(null, null)),
5703
5704                 ShortcutCommand.Create(Keys.F5)
5705                     .Do(() => this.DoRefresh()),
5706
5707                 ShortcutCommand.Create(Keys.F6)
5708                     .Do(() => this.RefreshTabAsync<MentionsTabModel>()),
5709
5710                 ShortcutCommand.Create(Keys.F7)
5711                     .Do(() => this.RefreshTabAsync<DirectMessagesTabModel>()),
5712
5713                 ShortcutCommand.Create(Keys.Space, Keys.ProcessKey)
5714                     .NotFocusedOn(FocusedControl.StatusText)
5715                     .Do(() => { this._anchorFlag = false; this.JumpUnreadMenuItem_Click(null, null); }),
5716
5717                 ShortcutCommand.Create(Keys.G)
5718                     .NotFocusedOn(FocusedControl.StatusText)
5719                     .Do(() => { this._anchorFlag = false; this.ShowRelatedStatusesMenuItem_Click(null, null); }),
5720
5721                 ShortcutCommand.Create(Keys.Right, Keys.N)
5722                     .FocusedOn(FocusedControl.ListTab)
5723                     .Do(() => this.GoRelPost(forward: true)),
5724
5725                 ShortcutCommand.Create(Keys.Left, Keys.P)
5726                     .FocusedOn(FocusedControl.ListTab)
5727                     .Do(() => this.GoRelPost(forward: false)),
5728
5729                 ShortcutCommand.Create(Keys.OemPeriod)
5730                     .FocusedOn(FocusedControl.ListTab)
5731                     .Do(() => this.GoAnchor()),
5732
5733                 ShortcutCommand.Create(Keys.I)
5734                     .FocusedOn(FocusedControl.ListTab)
5735                     .OnlyWhen(() => this.StatusText.Enabled)
5736                     .Do(() => this.StatusText.Focus()),
5737
5738                 ShortcutCommand.Create(Keys.Enter)
5739                     .FocusedOn(FocusedControl.ListTab)
5740                     .Do(() => this.MakeReplyOrDirectStatus()),
5741
5742                 ShortcutCommand.Create(Keys.R)
5743                     .FocusedOn(FocusedControl.ListTab)
5744                     .Do(() => this.DoRefresh()),
5745
5746                 ShortcutCommand.Create(Keys.L)
5747                     .FocusedOn(FocusedControl.ListTab)
5748                     .Do(() => { this._anchorFlag = false; this.GoPost(forward: true); }),
5749
5750                 ShortcutCommand.Create(Keys.H)
5751                     .FocusedOn(FocusedControl.ListTab)
5752                     .Do(() => { this._anchorFlag = false; this.GoPost(forward: false); }),
5753
5754                 ShortcutCommand.Create(Keys.Z, Keys.Oemcomma)
5755                     .FocusedOn(FocusedControl.ListTab)
5756                     .Do(() => { this._anchorFlag = false; this.MoveTop(); }),
5757
5758                 ShortcutCommand.Create(Keys.S)
5759                     .FocusedOn(FocusedControl.ListTab)
5760                     .Do(() => { this._anchorFlag = false; this.GoNextTab(forward: true); }),
5761
5762                 ShortcutCommand.Create(Keys.A)
5763                     .FocusedOn(FocusedControl.ListTab)
5764                     .Do(() => { this._anchorFlag = false; this.GoNextTab(forward: false); }),
5765
5766                 // ] in_reply_to参照元へ戻る
5767                 ShortcutCommand.Create(Keys.Oem4)
5768                     .FocusedOn(FocusedControl.ListTab)
5769                     .Do(() => { this._anchorFlag = false; return this.GoInReplyToPostTree(); }),
5770
5771                 // [ in_reply_toへジャンプ
5772                 ShortcutCommand.Create(Keys.Oem6)
5773                     .FocusedOn(FocusedControl.ListTab)
5774                     .Do(() => { this._anchorFlag = false; this.GoBackInReplyToPostTree(); }),
5775
5776                 ShortcutCommand.Create(Keys.Escape)
5777                     .FocusedOn(FocusedControl.ListTab)
5778                     .Do(() => {
5779                         this._anchorFlag = false;
5780                         var tab = this.CurrentTab;
5781                         var tabtype = tab.TabType;
5782                         if (tabtype == MyCommon.TabUsageType.Related || tabtype == MyCommon.TabUsageType.UserTimeline || tabtype == MyCommon.TabUsageType.PublicSearch || tabtype == MyCommon.TabUsageType.SearchResults)
5783                         {
5784                             RemoveSpecifiedTab(tab.TabName, false);
5785                             SaveConfigsTabs();
5786                         }
5787                     }),
5788
5789                 // 上下キー, PageUp/Downキー, Home/Endキー は既定の動作を残しつつアンカー初期化
5790                 ShortcutCommand.Create(Keys.Up, Keys.Down, Keys.PageUp, Keys.PageDown, Keys.Home, Keys.End)
5791                     .FocusedOn(FocusedControl.ListTab)
5792                     .Do(() => this._anchorFlag = false, preventDefault: false),
5793
5794                 // PreviewKeyDownEventArgs.IsInputKey を true にしてスクロールを発生させる
5795                 ShortcutCommand.Create(Keys.Up, Keys.Down)
5796                     .FocusedOn(FocusedControl.PostBrowser)
5797                     .Do(() => { }),
5798
5799                 ShortcutCommand.Create(Keys.Control | Keys.R)
5800                     .Do(() => this.MakeReplyOrDirectStatus(isAuto: false, isReply: true)),
5801
5802                 ShortcutCommand.Create(Keys.Control | Keys.D)
5803                     .Do(() => this.doStatusDelete()),
5804
5805                 ShortcutCommand.Create(Keys.Control | Keys.M)
5806                     .Do(() => this.MakeReplyOrDirectStatus(isAuto: false, isReply: false)),
5807
5808                 ShortcutCommand.Create(Keys.Control | Keys.S)
5809                     .Do(() => this.FavoriteChange(FavAdd: true)),
5810
5811                 ShortcutCommand.Create(Keys.Control | Keys.I)
5812                     .Do(() => this.doRepliedStatusOpen()),
5813
5814                 ShortcutCommand.Create(Keys.Control | Keys.Q)
5815                     .Do(() => this.doQuoteOfficial()),
5816
5817                 ShortcutCommand.Create(Keys.Control | Keys.B)
5818                     .Do(() => this.ReadedStripMenuItem_Click(null, null)),
5819
5820                 ShortcutCommand.Create(Keys.Control | Keys.T)
5821                     .Do(() => this.HashManageMenuItem_Click(null, null)),
5822
5823                 ShortcutCommand.Create(Keys.Control | Keys.L)
5824                     .Do(() => this.UrlConvertAutoToolStripMenuItem_Click(null, null)),
5825
5826                 ShortcutCommand.Create(Keys.Control | Keys.Y)
5827                     .NotFocusedOn(FocusedControl.PostBrowser)
5828                     .Do(() => this.MultiLineMenuItem_Click(null, null)),
5829
5830                 ShortcutCommand.Create(Keys.Control | Keys.F)
5831                     .Do(() => this.MenuItemSubSearch_Click(null, null)),
5832
5833                 ShortcutCommand.Create(Keys.Control | Keys.U)
5834                     .Do(() => this.ShowUserTimeline()),
5835
5836                 ShortcutCommand.Create(Keys.Control | Keys.H)
5837                     .Do(() => this.MoveToHomeToolStripMenuItem_Click(null, null)),
5838
5839                 ShortcutCommand.Create(Keys.Control | Keys.G)
5840                     .Do(() => this.MoveToFavToolStripMenuItem_Click(null, null)),
5841
5842                 ShortcutCommand.Create(Keys.Control | Keys.O)
5843                     .Do(() => this.StatusOpenMenuItem_Click(null, null)),
5844
5845                 ShortcutCommand.Create(Keys.Control | Keys.E)
5846                     .Do(() => this.OpenURLMenuItem_Click(null, null)),
5847
5848                 ShortcutCommand.Create(Keys.Control | Keys.Home, Keys.Control | Keys.End)
5849                     .FocusedOn(FocusedControl.ListTab)
5850                     .Do(() => this.selectionDebouncer.Call(), preventDefault: false),
5851
5852                 ShortcutCommand.Create(Keys.Control | Keys.N)
5853                     .FocusedOn(FocusedControl.ListTab)
5854                     .Do(() => this.GoNextTab(forward: true)),
5855
5856                 ShortcutCommand.Create(Keys.Control | Keys.P)
5857                     .FocusedOn(FocusedControl.ListTab)
5858                     .Do(() => this.GoNextTab(forward: false)),
5859
5860                 ShortcutCommand.Create(Keys.Control | Keys.C, Keys.Control | Keys.Insert)
5861                     .FocusedOn(FocusedControl.ListTab)
5862                     .Do(() => this.CopyStot()),
5863
5864                 // タブダイレクト選択(Ctrl+1~8,Ctrl+9)
5865                 ShortcutCommand.Create(Keys.Control | Keys.D1)
5866                     .FocusedOn(FocusedControl.ListTab)
5867                     .OnlyWhen(() => this._statuses.Tabs.Count >= 1)
5868                     .Do(() => this.ListTab.SelectedIndex = 0),
5869
5870                 ShortcutCommand.Create(Keys.Control | Keys.D2)
5871                     .FocusedOn(FocusedControl.ListTab)
5872                     .OnlyWhen(() => this._statuses.Tabs.Count >= 2)
5873                     .Do(() => this.ListTab.SelectedIndex = 1),
5874
5875                 ShortcutCommand.Create(Keys.Control | Keys.D3)
5876                     .FocusedOn(FocusedControl.ListTab)
5877                     .OnlyWhen(() => this._statuses.Tabs.Count >= 3)
5878                     .Do(() => this.ListTab.SelectedIndex = 2),
5879
5880                 ShortcutCommand.Create(Keys.Control | Keys.D4)
5881                     .FocusedOn(FocusedControl.ListTab)
5882                     .OnlyWhen(() => this._statuses.Tabs.Count >= 4)
5883                     .Do(() => this.ListTab.SelectedIndex = 3),
5884
5885                 ShortcutCommand.Create(Keys.Control | Keys.D5)
5886                     .FocusedOn(FocusedControl.ListTab)
5887                     .OnlyWhen(() => this._statuses.Tabs.Count >= 5)
5888                     .Do(() => this.ListTab.SelectedIndex = 4),
5889
5890                 ShortcutCommand.Create(Keys.Control | Keys.D6)
5891                     .FocusedOn(FocusedControl.ListTab)
5892                     .OnlyWhen(() => this._statuses.Tabs.Count >= 6)
5893                     .Do(() => this.ListTab.SelectedIndex = 5),
5894
5895                 ShortcutCommand.Create(Keys.Control | Keys.D7)
5896                     .FocusedOn(FocusedControl.ListTab)
5897                     .OnlyWhen(() => this._statuses.Tabs.Count >= 7)
5898                     .Do(() => this.ListTab.SelectedIndex = 6),
5899
5900                 ShortcutCommand.Create(Keys.Control | Keys.D8)
5901                     .FocusedOn(FocusedControl.ListTab)
5902                     .OnlyWhen(() => this._statuses.Tabs.Count >= 8)
5903                     .Do(() => this.ListTab.SelectedIndex = 7),
5904
5905                 ShortcutCommand.Create(Keys.Control | Keys.D9)
5906                     .FocusedOn(FocusedControl.ListTab)
5907                     .Do(() => this.ListTab.SelectedIndex = this._statuses.Tabs.Count - 1),
5908
5909                 ShortcutCommand.Create(Keys.Control | Keys.A)
5910                     .FocusedOn(FocusedControl.StatusText)
5911                     .Do(() => this.StatusText.SelectAll()),
5912
5913                 ShortcutCommand.Create(Keys.Control | Keys.V)
5914                     .FocusedOn(FocusedControl.StatusText)
5915                     .Do(() => this.ProcClipboardFromStatusTextWhenCtrlPlusV()),
5916
5917                 ShortcutCommand.Create(Keys.Control | Keys.Up)
5918                     .FocusedOn(FocusedControl.StatusText)
5919                     .Do(() => this.StatusTextHistoryBack()),
5920
5921                 ShortcutCommand.Create(Keys.Control | Keys.Down)
5922                     .FocusedOn(FocusedControl.StatusText)
5923                     .Do(() => this.StatusTextHistoryForward()),
5924
5925                 ShortcutCommand.Create(Keys.Control | Keys.PageUp, Keys.Control | Keys.P)
5926                     .FocusedOn(FocusedControl.StatusText)
5927                     .Do(() => {
5928                         if (ListTab.SelectedIndex == 0)
5929                         {
5930                             ListTab.SelectedIndex = ListTab.TabCount - 1;
5931                         }
5932                         else
5933                         {
5934                             ListTab.SelectedIndex -= 1;
5935                         }
5936                         StatusText.Focus();
5937                     }),
5938
5939                 ShortcutCommand.Create(Keys.Control | Keys.PageDown, Keys.Control | Keys.N)
5940                     .FocusedOn(FocusedControl.StatusText)
5941                     .Do(() => {
5942                         if (ListTab.SelectedIndex == ListTab.TabCount - 1)
5943                         {
5944                             ListTab.SelectedIndex = 0;
5945                         }
5946                         else
5947                         {
5948                             ListTab.SelectedIndex += 1;
5949                         }
5950                         StatusText.Focus();
5951                     }),
5952
5953                 ShortcutCommand.Create(Keys.Control | Keys.Y)
5954                     .FocusedOn(FocusedControl.PostBrowser)
5955                     .Do(() => {
5956                         var multiline = !SettingManager.Local.StatusMultiline;
5957                         SettingManager.Local.StatusMultiline = multiline;
5958                         MultiLineMenuItem.Checked = multiline;
5959                         MultiLineMenuItem_Click(this.MultiLineMenuItem, EventArgs.Empty);
5960                     }),
5961
5962                 ShortcutCommand.Create(Keys.Shift | Keys.F3)
5963                     .Do(() => this.MenuItemSearchPrev_Click(null, null)),
5964
5965                 ShortcutCommand.Create(Keys.Shift | Keys.F5)
5966                     .Do(() => this.DoRefreshMore()),
5967
5968                 ShortcutCommand.Create(Keys.Shift | Keys.F6)
5969                     .Do(() => this.RefreshTabAsync<MentionsTabModel>(backward: true)),
5970
5971                 ShortcutCommand.Create(Keys.Shift | Keys.F7)
5972                     .Do(() => this.RefreshTabAsync<DirectMessagesTabModel>(backward: true)),
5973
5974                 ShortcutCommand.Create(Keys.Shift | Keys.R)
5975                     .NotFocusedOn(FocusedControl.StatusText)
5976                     .Do(() => this.DoRefreshMore()),
5977
5978                 ShortcutCommand.Create(Keys.Shift | Keys.H)
5979                     .FocusedOn(FocusedControl.ListTab)
5980                     .Do(() => this.GoTopEnd(GoTop: true)),
5981
5982                 ShortcutCommand.Create(Keys.Shift | Keys.L)
5983                     .FocusedOn(FocusedControl.ListTab)
5984                     .Do(() => this.GoTopEnd(GoTop: false)),
5985
5986                 ShortcutCommand.Create(Keys.Shift | Keys.M)
5987                     .FocusedOn(FocusedControl.ListTab)
5988                     .Do(() => this.GoMiddle()),
5989
5990                 ShortcutCommand.Create(Keys.Shift | Keys.G)
5991                     .FocusedOn(FocusedControl.ListTab)
5992                     .Do(() => this.GoLast()),
5993
5994                 ShortcutCommand.Create(Keys.Shift | Keys.Z)
5995                     .FocusedOn(FocusedControl.ListTab)
5996                     .Do(() => this.MoveMiddle()),
5997
5998                 ShortcutCommand.Create(Keys.Shift | Keys.Oem4)
5999                     .FocusedOn(FocusedControl.ListTab)
6000                     .Do(() => this.GoBackInReplyToPostTree(parallel: true, isForward: false)),
6001
6002                 ShortcutCommand.Create(Keys.Shift | Keys.Oem6)
6003                     .FocusedOn(FocusedControl.ListTab)
6004                     .Do(() => this.GoBackInReplyToPostTree(parallel: true, isForward: true)),
6005
6006                 // お気に入り前後ジャンプ(SHIFT+N←/P→)
6007                 ShortcutCommand.Create(Keys.Shift | Keys.Right, Keys.Shift | Keys.N)
6008                     .FocusedOn(FocusedControl.ListTab)
6009                     .Do(() => this.GoFav(forward: true)),
6010
6011                 // お気に入り前後ジャンプ(SHIFT+N←/P→)
6012                 ShortcutCommand.Create(Keys.Shift | Keys.Left, Keys.Shift | Keys.P)
6013                     .FocusedOn(FocusedControl.ListTab)
6014                     .Do(() => this.GoFav(forward: false)),
6015
6016                 ShortcutCommand.Create(Keys.Shift | Keys.Space)
6017                     .FocusedOn(FocusedControl.ListTab)
6018                     .Do(() => this.GoBackSelectPostChain()),
6019
6020                 ShortcutCommand.Create(Keys.Alt | Keys.R)
6021                     .Do(() => this.doReTweetOfficial(isConfirm: true)),
6022
6023                 ShortcutCommand.Create(Keys.Alt | Keys.P)
6024                     .OnlyWhen(() => this.CurrentPost != null)
6025                     .Do(() => this.doShowUserStatus(this.CurrentPost.ScreenName, ShowInputDialog: false)),
6026
6027                 ShortcutCommand.Create(Keys.Alt | Keys.Up)
6028                     .Do(() => this.tweetDetailsView.ScrollDownPostBrowser(forward: false)),
6029
6030                 ShortcutCommand.Create(Keys.Alt | Keys.Down)
6031                     .Do(() => this.tweetDetailsView.ScrollDownPostBrowser(forward: true)),
6032
6033                 ShortcutCommand.Create(Keys.Alt | Keys.PageUp)
6034                     .Do(() => this.tweetDetailsView.PageDownPostBrowser(forward: false)),
6035
6036                 ShortcutCommand.Create(Keys.Alt | Keys.PageDown)
6037                     .Do(() => this.tweetDetailsView.PageDownPostBrowser(forward: true)),
6038
6039                 // 別タブの同じ書き込みへ(ALT+←/→)
6040                 ShortcutCommand.Create(Keys.Alt | Keys.Right)
6041                     .FocusedOn(FocusedControl.ListTab)
6042                     .Do(() => this.GoSamePostToAnotherTab(left: false)),
6043
6044                 ShortcutCommand.Create(Keys.Alt | Keys.Left)
6045                     .FocusedOn(FocusedControl.ListTab)
6046                     .Do(() => this.GoSamePostToAnotherTab(left: true)),
6047
6048                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.R)
6049                     .Do(() => this.MakeReplyOrDirectStatus(isAuto: false, isReply: true, isAll: true)),
6050
6051                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.C, Keys.Control | Keys.Shift | Keys.Insert)
6052                     .Do(() => this.CopyIdUri()),
6053
6054                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.F)
6055                     .OnlyWhen(() => this.CurrentTab.TabType == MyCommon.TabUsageType.PublicSearch)
6056                     .Do(() => this.CurrentTabPage.Controls["panelSearch"].Controls["comboSearch"].Focus()),
6057
6058                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.S)
6059                     .Do(() => this.FavoriteChange(FavAdd: false)),
6060
6061                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.B)
6062                     .Do(() => this.UnreadStripMenuItem_Click(null, null)),
6063
6064                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.T)
6065                     .Do(() => this.HashToggleMenuItem_Click(null, null)),
6066
6067                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.P)
6068                     .Do(() => this.ImageSelectMenuItem_Click(null, null)),
6069
6070                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.H)
6071                     .Do(() => this.doMoveToRTHome()),
6072
6073                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.Up)
6074                     .FocusedOn(FocusedControl.StatusText)
6075                     .Do(() => {
6076                         var tab = this.CurrentTab;
6077                         var selectedIndex = tab.SelectedIndex;
6078                         if (selectedIndex != -1 && selectedIndex > 0)
6079                         {
6080                             var listView = this.CurrentListView;
6081                             var idx = selectedIndex - 1;
6082                             SelectListItem(listView, idx);
6083                             listView.EnsureVisible(idx);
6084                         }
6085                     }),
6086
6087                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.Down)
6088                     .FocusedOn(FocusedControl.StatusText)
6089                     .Do(() => {
6090                         var tab = this.CurrentTab;
6091                         var selectedIndex = tab.SelectedIndex;
6092                         if (selectedIndex != -1 && selectedIndex < tab.AllCount - 1)
6093                         {
6094                             var listView = this.CurrentListView;
6095                             var idx = selectedIndex + 1;
6096                             SelectListItem(listView, idx);
6097                             listView.EnsureVisible(idx);
6098                         }
6099                     }),
6100
6101                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.Space)
6102                     .FocusedOn(FocusedControl.StatusText)
6103                     .Do(() => {
6104                         if (StatusText.SelectionStart > 0)
6105                         {
6106                             int endidx = StatusText.SelectionStart - 1;
6107                             string startstr = "";
6108                             for (int i = StatusText.SelectionStart - 1; i >= 0; i--)
6109                             {
6110                                 char c = StatusText.Text[i];
6111                                 if (Char.IsLetterOrDigit(c) || c == '_')
6112                                 {
6113                                     continue;
6114                                 }
6115                                 if (c == '@')
6116                                 {
6117                                     startstr = StatusText.Text.Substring(i + 1, endidx - i);
6118                                     int cnt = AtIdSupl.ItemCount;
6119                                     ShowSuplDialog(StatusText, AtIdSupl, startstr.Length + 1, startstr);
6120                                     if (AtIdSupl.ItemCount != cnt)
6121                                         this.MarkSettingAtIdModified();
6122                                 }
6123                                 else if (c == '#')
6124                                 {
6125                                     startstr = StatusText.Text.Substring(i + 1, endidx - i);
6126                                     ShowSuplDialog(StatusText, HashSupl, startstr.Length + 1, startstr);
6127                                 }
6128                                 else
6129                                 {
6130                                     break;
6131                                 }
6132                             }
6133                         }
6134                     }),
6135
6136                 // ソートダイレクト選択(Ctrl+Shift+1~8,Ctrl+Shift+9)
6137                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D1)
6138                     .FocusedOn(FocusedControl.ListTab)
6139                     .Do(() => this.SetSortColumnByDisplayIndex(0)),
6140
6141                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D2)
6142                     .FocusedOn(FocusedControl.ListTab)
6143                     .Do(() => this.SetSortColumnByDisplayIndex(1)),
6144
6145                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D3)
6146                     .FocusedOn(FocusedControl.ListTab)
6147                     .Do(() => this.SetSortColumnByDisplayIndex(2)),
6148
6149                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D4)
6150                     .FocusedOn(FocusedControl.ListTab)
6151                     .Do(() => this.SetSortColumnByDisplayIndex(3)),
6152
6153                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D5)
6154                     .FocusedOn(FocusedControl.ListTab)
6155                     .Do(() => this.SetSortColumnByDisplayIndex(4)),
6156
6157                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D6)
6158                     .FocusedOn(FocusedControl.ListTab)
6159                     .Do(() => this.SetSortColumnByDisplayIndex(5)),
6160
6161                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D7)
6162                     .FocusedOn(FocusedControl.ListTab)
6163                     .Do(() => this.SetSortColumnByDisplayIndex(6)),
6164
6165                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D8)
6166                     .FocusedOn(FocusedControl.ListTab)
6167                     .Do(() => this.SetSortColumnByDisplayIndex(7)),
6168
6169                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D9)
6170                     .FocusedOn(FocusedControl.ListTab)
6171                     .Do(() => this.SetSortLastColumn()),
6172
6173                 ShortcutCommand.Create(Keys.Control | Keys.Alt | Keys.S)
6174                     .FocusedOn(FocusedControl.ListTab)
6175                     .Do(() => this.FavoritesRetweetOfficial()),
6176
6177                 ShortcutCommand.Create(Keys.Control | Keys.Alt | Keys.R)
6178                     .FocusedOn(FocusedControl.ListTab)
6179                     .Do(() => this.FavoritesRetweetUnofficial()),
6180
6181                 ShortcutCommand.Create(Keys.Control | Keys.Alt | Keys.H)
6182                     .FocusedOn(FocusedControl.ListTab)
6183                     .Do(() => this.OpenUserAppointUrl()),
6184
6185                 ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.R)
6186                     .FocusedOn(FocusedControl.PostBrowser)
6187                     .Do(() => this.doReTweetUnofficial()),
6188
6189                 ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.T)
6190                     .OnlyWhen(() => this.ExistCurrentPost)
6191                     .Do(() => this.tweetDetailsView.DoTranslation()),
6192
6193                 ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.R)
6194                     .Do(() => this.doReTweetUnofficial()),
6195
6196                 ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.C, Keys.Alt | Keys.Shift | Keys.Insert)
6197                     .Do(() => this.CopyUserId()),
6198
6199                 ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.Up)
6200                     .Do(() => this.tweetThumbnail1.ScrollUp()),
6201
6202                 ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.Down)
6203                     .Do(() => this.tweetThumbnail1.ScrollDown()),
6204
6205                 ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.Enter)
6206                     .FocusedOn(FocusedControl.ListTab)
6207                     .OnlyWhen(() => !this.SplitContainer3.Panel2Collapsed)
6208                     .Do(() => this.OpenThumbnailPicture(this.tweetThumbnail1.Thumbnail)),
6209             };
6210         }
6211
6212         internal bool CommonKeyDown(Keys keyData, FocusedControl focusedOn, out Task asyncTask)
6213         {
6214             // Task を返す非同期処理があれば asyncTask に代入する
6215             asyncTask = null;
6216
6217             // ShortcutCommand に対応しているコマンドはここで処理される
6218             foreach (var command in this.shortcutCommands)
6219             {
6220                 if (command.IsMatch(keyData, focusedOn))
6221                 {
6222                     asyncTask = command.RunCommand();
6223                     return command.PreventDefault;
6224                 }
6225             }
6226
6227             return false;
6228         }
6229
6230         private void GoNextTab(bool forward)
6231         {
6232             int idx = this._statuses.SelectedTabIndex;
6233             var tabCount = this._statuses.Tabs.Count;
6234             if (forward)
6235             {
6236                 idx += 1;
6237                 if (idx > tabCount - 1) idx = 0;
6238             }
6239             else
6240             {
6241                 idx -= 1;
6242                 if (idx < 0) idx = tabCount - 1;
6243             }
6244             ListTab.SelectedIndex = idx;
6245         }
6246
6247         private void CopyStot()
6248         {
6249             string clstr = "";
6250             StringBuilder sb = new StringBuilder();
6251             var tab = this.CurrentTab;
6252             bool IsProtected = false;
6253             var isDm = tab.TabType == MyCommon.TabUsageType.DirectMessage;
6254             foreach (var post in tab.SelectedPosts)
6255             {
6256                 if (post.IsDeleted) continue;
6257                 if (!isDm)
6258                 {
6259                     if (post.RetweetedId != null)
6260                         sb.AppendFormat("{0}:{1} [https://twitter.com/{0}/status/{2}]{3}", post.ScreenName, post.TextSingleLine, post.RetweetedId, Environment.NewLine);
6261                     else
6262                         sb.AppendFormat("{0}:{1} [https://twitter.com/{0}/status/{2}]{3}", post.ScreenName, post.TextSingleLine, post.StatusId, Environment.NewLine);
6263                 }
6264                 else
6265                 {
6266                     sb.AppendFormat("{0}:{1} [{2}]{3}", post.ScreenName, post.TextSingleLine, post.StatusId, Environment.NewLine);
6267                 }
6268             }
6269             if (IsProtected)
6270             {
6271                 MessageBox.Show(Properties.Resources.CopyStotText1);
6272             }
6273             if (sb.Length > 0)
6274             {
6275                 clstr = sb.ToString();
6276                 try
6277                 {
6278                     Clipboard.SetDataObject(clstr, false, 5, 100);
6279                 }
6280                 catch (Exception ex)
6281                 {
6282                     MessageBox.Show(ex.Message);
6283                 }
6284             }
6285         }
6286
6287         private void CopyIdUri()
6288         {
6289             var tab = this.CurrentTab;
6290             if (tab == null || tab is DirectMessagesTabModel)
6291                 return;
6292
6293             var copyUrls = new List<string>();
6294             foreach (var post in tab.SelectedPosts)
6295                 copyUrls.Add(MyCommon.GetStatusUrl(post));
6296
6297             if (copyUrls.Count == 0)
6298                 return;
6299
6300             try
6301             {
6302                 Clipboard.SetDataObject(string.Join(Environment.NewLine, copyUrls), false, 5, 100);
6303             }
6304             catch (ExternalException ex)
6305             {
6306                 MessageBox.Show(ex.Message);
6307             }
6308         }
6309
6310         private void GoFav(bool forward)
6311         {
6312             var tab = this.CurrentTab;
6313             if (tab.AllCount == 0)
6314                 return;
6315
6316             var selectedIndex = tab.SelectedIndex;
6317
6318             int fIdx = 0;
6319             int toIdx = 0;
6320             int stp = 1;
6321
6322             if (forward)
6323             {
6324                 if (selectedIndex == -1)
6325                 {
6326                     fIdx = 0;
6327                 }
6328                 else
6329                 {
6330                     fIdx = selectedIndex + 1;
6331                     if (fIdx > tab.AllCount - 1) return;
6332                 }
6333                 toIdx = tab.AllCount;
6334                 stp = 1;
6335             }
6336             else
6337             {
6338                 if (selectedIndex == -1)
6339                 {
6340                     fIdx = tab.AllCount - 1;
6341                 }
6342                 else
6343                 {
6344                     fIdx = selectedIndex - 1;
6345                     if (fIdx < 0) return;
6346                 }
6347                 toIdx = -1;
6348                 stp = -1;
6349             }
6350
6351             for (int idx = fIdx; idx != toIdx; idx += stp)
6352             {
6353                 if (tab[idx].IsFav)
6354                 {
6355                     var listView = this.CurrentListView;
6356                     SelectListItem(listView, idx);
6357                     listView.EnsureVisible(idx);
6358                     break;
6359                 }
6360             }
6361         }
6362
6363         private void GoSamePostToAnotherTab(bool left)
6364         {
6365             var tab = this.CurrentTab;
6366
6367             // Directタブは対象外(見つかるはずがない)
6368             if (tab.TabType == MyCommon.TabUsageType.DirectMessage)
6369                 return;
6370
6371             var selectedStatusId = tab.SelectedStatusId;
6372             if (selectedStatusId == -1)
6373                 return;
6374
6375             int fIdx, toIdx, stp;
6376
6377             if (left)
6378             {
6379                 // 左のタブへ
6380                 if (ListTab.SelectedIndex == 0)
6381                 {
6382                     return;
6383                 }
6384                 else
6385                 {
6386                     fIdx = ListTab.SelectedIndex - 1;
6387                 }
6388                 toIdx = -1;
6389                 stp = -1;
6390             }
6391             else
6392             {
6393                 // 右のタブへ
6394                 if (ListTab.SelectedIndex == ListTab.TabCount - 1)
6395                 {
6396                     return;
6397                 }
6398                 else
6399                 {
6400                     fIdx = ListTab.SelectedIndex + 1;
6401                 }
6402                 toIdx = ListTab.TabCount;
6403                 stp = 1;
6404             }
6405
6406             for (int tabidx = fIdx; tabidx != toIdx; tabidx += stp)
6407             {
6408                 var targetTab = this._statuses.Tabs[tabidx];
6409
6410                 // Directタブは対象外
6411                 if (targetTab.TabType == MyCommon.TabUsageType.DirectMessage)
6412                     continue;
6413
6414                 var foundIndex = targetTab.IndexOf(selectedStatusId);
6415                 if (foundIndex != -1)
6416                 {
6417                     ListTab.SelectedIndex = tabidx;
6418                     var listView = this.CurrentListView;
6419                     SelectListItem(listView, foundIndex);
6420                     listView.EnsureVisible(foundIndex);
6421                     return;
6422                 }
6423             }
6424         }
6425
6426         private void GoPost(bool forward)
6427         {
6428             var tab = this.CurrentTab;
6429             var currentPost = this.CurrentPost;
6430
6431             if (currentPost == null)
6432                 return;
6433
6434             var selectedIndex = tab.SelectedIndex;
6435
6436             int fIdx, toIdx, stp;
6437
6438             if (forward)
6439             {
6440                 fIdx = selectedIndex + 1;
6441                 if (fIdx > tab.AllCount - 1) return;
6442                 toIdx = tab.AllCount;
6443                 stp = 1;
6444             }
6445             else
6446             {
6447                 fIdx = selectedIndex - 1;
6448                 if (fIdx < 0) return;
6449                 toIdx = -1;
6450                 stp = -1;
6451             }
6452
6453             string name = "";
6454             if (currentPost.RetweetedId == null)
6455             {
6456                 name = currentPost.ScreenName;
6457             }
6458             else
6459             {
6460                 name = currentPost.RetweetedBy;
6461             }
6462             for (int idx = fIdx; idx != toIdx; idx += stp)
6463             {
6464                 var post = tab[idx];
6465                 if (post.RetweetedId == null)
6466                 {
6467                     if (post.ScreenName == name)
6468                     {
6469                         var listView = this.CurrentListView;
6470                         SelectListItem(listView, idx);
6471                         listView.EnsureVisible(idx);
6472                         break;
6473                     }
6474                 }
6475                 else
6476                 {
6477                     if (post.RetweetedBy == name)
6478                     {
6479                         var listView = this.CurrentListView;
6480                         SelectListItem(listView, idx);
6481                         listView.EnsureVisible(idx);
6482                         break;
6483                     }
6484                 }
6485             }
6486         }
6487
6488         private void GoRelPost(bool forward)
6489         {
6490             var tab = this.CurrentTab;
6491             var selectedIndex = tab.SelectedIndex;
6492
6493             if (selectedIndex == -1)
6494                 return;
6495
6496             int fIdx, toIdx, stp;
6497
6498             if (forward)
6499             {
6500                 fIdx = selectedIndex + 1;
6501                 if (fIdx > tab.AllCount - 1) return;
6502                 toIdx = tab.AllCount;
6503                 stp = 1;
6504             }
6505             else
6506             {
6507                 fIdx = selectedIndex - 1;
6508                 if (fIdx < 0) return;
6509                 toIdx = -1;
6510                 stp = -1;
6511             }
6512
6513             if (!_anchorFlag)
6514             {
6515                 var currentPost = this.CurrentPost;
6516                 if (currentPost == null) return;
6517                 _anchorPost = currentPost;
6518                 _anchorFlag = true;
6519             }
6520             else
6521             {
6522                 if (_anchorPost == null) return;
6523             }
6524
6525             for (int idx = fIdx; idx != toIdx; idx += stp)
6526             {
6527                 var post = tab[idx];
6528                 if (post.ScreenName == _anchorPost.ScreenName ||
6529                     post.RetweetedBy == _anchorPost.ScreenName ||
6530                     post.ScreenName == _anchorPost.RetweetedBy ||
6531                     (!string.IsNullOrEmpty(post.RetweetedBy) && post.RetweetedBy == _anchorPost.RetweetedBy) ||
6532                     _anchorPost.ReplyToList.Any(x => x.UserId == post.UserId) ||
6533                     _anchorPost.ReplyToList.Any(x => x.UserId == post.RetweetedByUserId) ||
6534                     post.ReplyToList.Any(x => x.UserId == _anchorPost.UserId) ||
6535                     post.ReplyToList.Any(x => x.UserId == _anchorPost.RetweetedByUserId))
6536                 {
6537                     var listView = this.CurrentListView;
6538                     SelectListItem(listView, idx);
6539                     listView.EnsureVisible(idx);
6540                     break;
6541                 }
6542             }
6543         }
6544
6545         private void GoAnchor()
6546         {
6547             if (_anchorPost == null) return;
6548             int idx = this.CurrentTab.IndexOf(_anchorPost.StatusId);
6549             if (idx == -1) return;
6550
6551             var listView = this.CurrentListView;
6552             SelectListItem(listView, idx);
6553             listView.EnsureVisible(idx);
6554         }
6555
6556         private void GoTopEnd(bool GoTop)
6557         {
6558             var listView = this.CurrentListView;
6559             if (listView.VirtualListSize == 0)
6560                 return;
6561
6562             ListViewItem _item;
6563             int idx;
6564
6565             if (GoTop)
6566             {
6567                 _item = listView.GetItemAt(0, 25);
6568                 if (_item == null)
6569                     idx = 0;
6570                 else
6571                     idx = _item.Index;
6572             }
6573             else
6574             {
6575                 _item = listView.GetItemAt(0, listView.ClientSize.Height - 1);
6576                 if (_item == null)
6577                     idx = listView.VirtualListSize - 1;
6578                 else
6579                     idx = _item.Index;
6580             }
6581             SelectListItem(listView, idx);
6582         }
6583
6584         private void GoMiddle()
6585         {
6586             var listView = this.CurrentListView;
6587             if (listView.VirtualListSize == 0)
6588                 return;
6589
6590             ListViewItem _item;
6591             int idx1;
6592             int idx2;
6593             int idx3;
6594
6595             _item = listView.GetItemAt(0, 0);
6596             if (_item == null)
6597             {
6598                 idx1 = 0;
6599             }
6600             else
6601             {
6602                 idx1 = _item.Index;
6603             }
6604
6605             _item = listView.GetItemAt(0, listView.ClientSize.Height - 1);
6606             if (_item == null)
6607             {
6608                 idx2 = listView.VirtualListSize - 1;
6609             }
6610             else
6611             {
6612                 idx2 = _item.Index;
6613             }
6614             idx3 = (idx1 + idx2) / 2;
6615
6616             SelectListItem(listView, idx3);
6617         }
6618
6619         private void GoLast()
6620         {
6621             var listView = this.CurrentListView;
6622             if (listView.VirtualListSize == 0) return;
6623
6624             if (_statuses.SortOrder == SortOrder.Ascending)
6625             {
6626                 SelectListItem(listView, listView.VirtualListSize - 1);
6627                 listView.EnsureVisible(listView.VirtualListSize - 1);
6628             }
6629             else
6630             {
6631                 SelectListItem(listView, 0);
6632                 listView.EnsureVisible(0);
6633             }
6634         }
6635
6636         private void MoveTop()
6637         {
6638             var listView = this.CurrentListView;
6639             if (listView.SelectedIndices.Count == 0) return;
6640             int idx = listView.SelectedIndices[0];
6641             if (_statuses.SortOrder == SortOrder.Ascending)
6642             {
6643                 listView.EnsureVisible(listView.VirtualListSize - 1);
6644             }
6645             else
6646             {
6647                 listView.EnsureVisible(0);
6648             }
6649             listView.EnsureVisible(idx);
6650         }
6651
6652         private async Task GoInReplyToPostTree()
6653         {
6654             var curTabClass = this.CurrentTab;
6655             var currentPost = this.CurrentPost;
6656
6657             if (currentPost == null)
6658                 return;
6659
6660             if (curTabClass.TabType == MyCommon.TabUsageType.PublicSearch && currentPost.InReplyToStatusId == null && currentPost.TextFromApi.Contains("@"))
6661             {
6662                 try
6663                 {
6664                     var post = await tw.GetStatusApi(false, currentPost.StatusId);
6665
6666                     currentPost.InReplyToStatusId = post.InReplyToStatusId;
6667                     currentPost.InReplyToUser = post.InReplyToUser;
6668                     currentPost.IsReply = post.IsReply;
6669                     this.PurgeListViewItemCache();
6670
6671                     var index = curTabClass.SelectedIndex;
6672                     this.CurrentListView.RedrawItems(index, index, false);
6673                 }
6674                 catch (WebApiException ex)
6675                 {
6676                     this.StatusLabel.Text = $"Err:{ex.Message}(GetStatus)";
6677                 }
6678             }
6679
6680             if (!(this.ExistCurrentPost && currentPost.InReplyToUser != null && currentPost.InReplyToStatusId != null)) return;
6681
6682             if (replyChains == null || (replyChains.Count > 0 && replyChains.Peek().InReplyToId != currentPost.StatusId))
6683             {
6684                 replyChains = new Stack<ReplyChain>();
6685             }
6686             replyChains.Push(new ReplyChain(currentPost.StatusId, currentPost.InReplyToStatusId.Value, curTabClass));
6687
6688             int inReplyToIndex;
6689             string inReplyToTabName;
6690             var inReplyToId = currentPost.InReplyToStatusId.Value;
6691             var inReplyToUser = currentPost.InReplyToUser;
6692
6693             var inReplyToPosts = from tab in _statuses.Tabs
6694                                  orderby tab != curTabClass
6695                                  from post in tab.Posts.Values
6696                                  where post.StatusId == inReplyToId
6697                                  let index = tab.IndexOf(post.StatusId)
6698                                  where index != -1
6699                                  select new {Tab = tab, Index = index};
6700
6701             var inReplyPost = inReplyToPosts.FirstOrDefault();
6702             if (inReplyPost == null)
6703             {
6704                 try
6705                 {
6706                     await Task.Run(async () =>
6707                     {
6708                         var post = await tw.GetStatusApi(false, currentPost.InReplyToStatusId.Value)
6709                             .ConfigureAwait(false);
6710                         post.IsRead = true;
6711
6712                         _statuses.AddPost(post);
6713                         _statuses.DistributePosts();
6714                     });
6715                 }
6716                 catch (WebApiException ex)
6717                 {
6718                     this.StatusLabel.Text = $"Err:{ex.Message}(GetStatus)";
6719                     await this.OpenUriInBrowserAsync(MyCommon.GetStatusUrl(inReplyToUser, inReplyToId));
6720                     return;
6721                 }
6722
6723                 this.RefreshTimeline();
6724
6725                 inReplyPost = inReplyToPosts.FirstOrDefault();
6726                 if (inReplyPost == null)
6727                 {
6728                     await this.OpenUriInBrowserAsync(MyCommon.GetStatusUrl(inReplyToUser, inReplyToId));
6729                     return;
6730                 }
6731             }
6732             inReplyToTabName = inReplyPost.Tab.TabName;
6733             inReplyToIndex = inReplyPost.Index;
6734
6735             var tabIndex = this._statuses.Tabs.IndexOf(inReplyToTabName);
6736             var tabPage = this.ListTab.TabPages[tabIndex];
6737             DetailsListView listView = (DetailsListView)tabPage.Tag;
6738
6739             if (this.CurrentTabName != inReplyToTabName)
6740             {
6741                 this.ListTab.SelectedIndex = tabIndex;
6742             }
6743
6744             this.SelectListItem(listView, inReplyToIndex);
6745             listView.EnsureVisible(inReplyToIndex);
6746         }
6747
6748         private void GoBackInReplyToPostTree(bool parallel = false, bool isForward = true)
6749         {
6750             var curTabClass = this.CurrentTab;
6751             var currentPost = this.CurrentPost;
6752
6753             if (currentPost == null)
6754                 return;
6755
6756             if (parallel)
6757             {
6758                 if (currentPost.InReplyToStatusId != null)
6759                 {
6760                     var posts = from t in _statuses.Tabs
6761                                 from p in t.Posts
6762                                 where p.Value.StatusId != currentPost.StatusId && p.Value.InReplyToStatusId == currentPost.InReplyToStatusId
6763                                 let indexOf = t.IndexOf(p.Value.StatusId)
6764                                 where indexOf > -1
6765                                 orderby isForward ? indexOf : indexOf * -1
6766                                 orderby t != curTabClass
6767                                 select new {Tab = t, Post = p.Value, Index = indexOf};
6768                     try
6769                     {
6770                         var postList = posts.ToList();
6771                         for (int i = postList.Count - 1; i >= 0; i--)
6772                         {
6773                             int index = i;
6774                             if (postList.FindIndex((pst) => { return pst.Post.StatusId == postList[index].Post.StatusId; }) != index)
6775                             {
6776                                 postList.RemoveAt(index);
6777                             }
6778                         }
6779                         var currentIndex = this.CurrentTab.SelectedIndex;
6780                         var post = postList.FirstOrDefault((pst) => { return pst.Tab == curTabClass && isForward ? pst.Index > currentIndex : pst.Index < currentIndex; });
6781                         if (post == null) post = postList.FirstOrDefault((pst) => { return pst.Tab != curTabClass; });
6782                         if (post == null) post = postList.First();
6783                         var tabIndex = this._statuses.Tabs.IndexOf(post.Tab);
6784                         this.ListTab.SelectedIndex = tabIndex;
6785                         var listView = this.CurrentListView;
6786                         SelectListItem(listView, post.Index);
6787                         listView.EnsureVisible(post.Index);
6788                     }
6789                     catch (InvalidOperationException)
6790                     {
6791                         return;
6792                     }
6793                 }
6794             }
6795             else
6796             {
6797                 if (replyChains == null || replyChains.Count < 1)
6798                 {
6799                     var posts = from t in _statuses.Tabs
6800                                 from p in t.Posts
6801                                 where p.Value.InReplyToStatusId == currentPost.StatusId
6802                                 let indexOf = t.IndexOf(p.Value.StatusId)
6803                                 where indexOf > -1
6804                                 orderby indexOf
6805                                 orderby t != curTabClass
6806                                 select new {Tab = t, Index = indexOf};
6807                     try
6808                     {
6809                         var post = posts.First();
6810                         var tabIndex = this._statuses.Tabs.IndexOf(post.Tab);
6811                         this.ListTab.SelectedIndex = tabIndex;
6812                         var listView = this.CurrentListView;
6813                         SelectListItem(listView, post.Index);
6814                         listView.EnsureVisible(post.Index);
6815                     }
6816                     catch (InvalidOperationException)
6817                     {
6818                         return;
6819                     }
6820                 }
6821                 else
6822                 {
6823                     ReplyChain chainHead = replyChains.Pop();
6824                     if (chainHead.InReplyToId == currentPost.StatusId)
6825                     {
6826                         var tab = chainHead.OriginalTab;
6827                         if (!this._statuses.Tabs.Contains(tab))
6828                         {
6829                             replyChains = null;
6830                         }
6831                         else
6832                         {
6833                             var idx = tab.IndexOf(chainHead.OriginalId);
6834                             if (idx == -1)
6835                             {
6836                                 replyChains = null;
6837                             }
6838                             else
6839                             {
6840                                 var tabIndex = this._statuses.Tabs.IndexOf(tab);
6841                                 try
6842                                 {
6843                                     this.ListTab.SelectedIndex = tabIndex;
6844                                 }
6845                                 catch (Exception)
6846                                 {
6847                                     replyChains = null;
6848                                 }
6849                                 var listView = this.CurrentListView;
6850                                 SelectListItem(listView, idx);
6851                                 listView.EnsureVisible(idx);
6852                             }
6853                         }
6854                     }
6855                     else
6856                     {
6857                         replyChains = null;
6858                         this.GoBackInReplyToPostTree(parallel);
6859                     }
6860                 }
6861             }
6862         }
6863
6864         private void GoBackSelectPostChain()
6865         {
6866             if (this.selectPostChains.Count > 1)
6867             {
6868                 var idx = -1;
6869                 TabModel foundTab = null;
6870
6871                 do
6872                 {
6873                     try
6874                     {
6875                         this.selectPostChains.Pop();
6876                         var (tab, post) = this.selectPostChains.Peek();
6877
6878                         if (!this._statuses.Tabs.Contains(tab))
6879                             continue; // 該当タブが存在しないので無視
6880
6881                         if (post != null)
6882                         {
6883                             idx = tab.IndexOf(post.StatusId);
6884                             if (idx == -1) continue;  //該当ポストが存在しないので無視
6885                         }
6886
6887                         foundTab = tab;
6888
6889                         this.selectPostChains.Pop();
6890                     }
6891                     catch (InvalidOperationException)
6892                     {
6893                     }
6894
6895                     break;
6896                 }
6897                 while (this.selectPostChains.Count > 1);
6898
6899                 if (foundTab == null)
6900                 {
6901                     //状態がおかしいので処理を中断
6902                     //履歴が残り1つであればクリアしておく
6903                     if (this.selectPostChains.Count == 1)
6904                         this.selectPostChains.Clear();
6905                     return;
6906                 }
6907
6908                 var tabIndex = this._statuses.Tabs.IndexOf(foundTab);
6909                 var tabPage = this.ListTab.TabPages[tabIndex];
6910                 var lst = (DetailsListView)tabPage.Tag;
6911                 this.ListTab.SelectedIndex = tabIndex;
6912
6913                 if (idx > -1)
6914                 {
6915                     SelectListItem(lst, idx);
6916                     lst.EnsureVisible(idx);
6917                 }
6918                 lst.Focus();
6919             }
6920         }
6921
6922         private void PushSelectPostChain()
6923         {
6924             var currentTab = this.CurrentTab;
6925             var currentPost = this.CurrentPost;
6926
6927             int count = this.selectPostChains.Count;
6928             if (count > 0)
6929             {
6930                 var (tab, post) = this.selectPostChains.Peek();
6931                 if (tab == currentTab)
6932                 {
6933                     if (post == currentPost) return;  //最新の履歴と同一
6934                     if (post == null) this.selectPostChains.Pop();  //置き換えるため削除
6935                 }
6936             }
6937             if (count >= 2500) TrimPostChain();
6938             this.selectPostChains.Push((currentTab, currentPost));
6939         }
6940
6941         private void TrimPostChain()
6942         {
6943             if (this.selectPostChains.Count <= 2000) return;
6944             var p = new Stack<(TabModel, PostClass)>(2000);
6945             for (int i = 0; i < 2000; i++)
6946             {
6947                 p.Push(this.selectPostChains.Pop());
6948             }
6949             this.selectPostChains.Clear();
6950             for (int i = 0; i < 2000; i++)
6951             {
6952                 this.selectPostChains.Push(p.Pop());
6953             }
6954         }
6955
6956         private bool GoStatus(long statusId)
6957         {
6958             if (statusId == 0) return false;
6959
6960             var tab = this._statuses.Tabs
6961                 .Where(x => x.TabType != MyCommon.TabUsageType.DirectMessage)
6962                 .Where(x => x.Contains(statusId))
6963                 .FirstOrDefault();
6964
6965             if (tab == null)
6966                 return false;
6967
6968             var index = tab.IndexOf(statusId);
6969
6970             var tabIndex = this._statuses.Tabs.IndexOf(tab);
6971             this.ListTab.SelectedIndex = tabIndex;
6972
6973             var listView = this.CurrentListView;
6974             this.SelectListItem(listView, index);
6975             listView.EnsureVisible(index);
6976
6977             return true;
6978         }
6979
6980         private bool GoDirectMessage(long statusId)
6981         {
6982             if (statusId == 0) return false;
6983
6984             var tab = this._statuses.GetTabByType<DirectMessagesTabModel>();
6985             var index = tab.IndexOf(statusId);
6986
6987             if (index == -1)
6988                 return false;
6989
6990             var tabIndex = this._statuses.Tabs.IndexOf(tab);
6991             this.ListTab.SelectedIndex = tabIndex;
6992
6993             var listView = this.CurrentListView;
6994             this.SelectListItem(listView, index);
6995             listView.EnsureVisible(index);
6996
6997             return true;
6998         }
6999
7000         private void MyList_MouseClick(object sender, MouseEventArgs e)
7001             => this._anchorFlag = false;
7002
7003         private void StatusText_Enter(object sender, EventArgs e)
7004         {
7005             // フォーカスの戻り先を StatusText に設定
7006             this.Tag = StatusText;
7007             StatusText.BackColor = _clInputBackcolor;
7008         }
7009
7010         public Color InputBackColor
7011         {
7012             get => _clInputBackcolor;
7013             set => _clInputBackcolor = value;
7014         }
7015
7016         private void StatusText_Leave(object sender, EventArgs e)
7017         {
7018             // フォーカスがメニューに遷移しないならばフォーカスはタブに移ることを期待
7019             if (ListTab.SelectedTab != null && MenuStrip1.Tag == null) this.Tag = ListTab.SelectedTab.Tag;
7020             StatusText.BackColor = Color.FromKnownColor(KnownColor.Window);
7021         }
7022
7023         private async void StatusText_KeyDown(object sender, KeyEventArgs e)
7024         {
7025             if (CommonKeyDown(e.KeyData, FocusedControl.StatusText, out var asyncTask))
7026             {
7027                 e.Handled = true;
7028                 e.SuppressKeyPress = true;
7029             }
7030
7031             this.StatusText_TextChanged(null, null);
7032
7033             if (asyncTask != null)
7034                 await asyncTask;
7035         }
7036
7037         private void SaveConfigsAll(bool ifModified)
7038         {
7039             if (!ifModified)
7040             {
7041                 SaveConfigsCommon();
7042                 SaveConfigsLocal();
7043                 SaveConfigsTabs();
7044                 SaveConfigsAtId();
7045             }
7046             else
7047             {
7048                 if (ModifySettingCommon) SaveConfigsCommon();
7049                 if (ModifySettingLocal) SaveConfigsLocal();
7050                 if (ModifySettingAtId) SaveConfigsAtId();
7051             }
7052         }
7053
7054         private void SaveConfigsAtId()
7055         {
7056             if (_ignoreConfigSave || !SettingManager.Common.UseAtIdSupplement && AtIdSupl == null) return;
7057
7058             ModifySettingAtId = false;
7059             SettingManager.AtIdList.AtIdList = this.AtIdSupl.GetItemList();
7060             SettingManager.SaveAtIdList();
7061         }
7062
7063         private void SaveConfigsCommon()
7064         {
7065             if (_ignoreConfigSave) return;
7066
7067             ModifySettingCommon = false;
7068             lock (_syncObject)
7069             {
7070                 SettingManager.Common.UserName = tw.Username;
7071                 SettingManager.Common.UserId = tw.UserId;
7072                 SettingManager.Common.Token = tw.AccessToken;
7073                 SettingManager.Common.TokenSecret = tw.AccessTokenSecret;
7074                 SettingManager.Common.SortOrder = (int)_statuses.SortOrder;
7075                 switch (_statuses.SortMode)
7076                 {
7077                     case ComparerMode.Nickname:  //ニックネーム
7078                         SettingManager.Common.SortColumn = 1;
7079                         break;
7080                     case ComparerMode.Data:  //本文
7081                         SettingManager.Common.SortColumn = 2;
7082                         break;
7083                     case ComparerMode.Id:  //時刻=発言Id
7084                         SettingManager.Common.SortColumn = 3;
7085                         break;
7086                     case ComparerMode.Name:  //名前
7087                         SettingManager.Common.SortColumn = 4;
7088                         break;
7089                     case ComparerMode.Source:  //Source
7090                         SettingManager.Common.SortColumn = 7;
7091                         break;
7092                 }
7093
7094                 SettingManager.Common.HashTags = HashMgr.HashHistories;
7095                 if (HashMgr.IsPermanent)
7096                 {
7097                     SettingManager.Common.HashSelected = HashMgr.UseHash;
7098                 }
7099                 else
7100                 {
7101                     SettingManager.Common.HashSelected = "";
7102                 }
7103                 SettingManager.Common.HashIsHead = HashMgr.IsHead;
7104                 SettingManager.Common.HashIsPermanent = HashMgr.IsPermanent;
7105                 SettingManager.Common.HashIsNotAddToAtReply = HashMgr.IsNotAddToAtReply;
7106                 SettingManager.Common.TrackWord = tw.TrackWord;
7107                 SettingManager.Common.AllAtReply = tw.AllAtReply;
7108                 SettingManager.Common.UseImageService = ImageSelector.ServiceIndex;
7109                 SettingManager.Common.UseImageServiceName = ImageSelector.ServiceName;
7110
7111                 SettingManager.SaveCommon();
7112             }
7113         }
7114
7115         private void SaveConfigsLocal()
7116         {
7117             if (_ignoreConfigSave) return;
7118             lock (_syncObject)
7119             {
7120                 ModifySettingLocal = false;
7121                 SettingManager.Local.ScaleDimension = this.CurrentAutoScaleDimensions;
7122                 SettingManager.Local.FormSize = _mySize;
7123                 SettingManager.Local.FormLocation = _myLoc;
7124                 SettingManager.Local.SplitterDistance = _mySpDis;
7125                 SettingManager.Local.PreviewDistance = _mySpDis3;
7126                 SettingManager.Local.StatusMultiline = StatusText.Multiline;
7127                 SettingManager.Local.StatusTextHeight = _mySpDis2;
7128
7129                 SettingManager.Local.FontUnread = _fntUnread;
7130                 SettingManager.Local.ColorUnread = _clUnread;
7131                 SettingManager.Local.FontRead = _fntReaded;
7132                 SettingManager.Local.ColorRead = _clReaded;
7133                 SettingManager.Local.FontDetail = _fntDetail;
7134                 SettingManager.Local.ColorDetail = _clDetail;
7135                 SettingManager.Local.ColorDetailBackcolor = _clDetailBackcolor;
7136                 SettingManager.Local.ColorDetailLink = _clDetailLink;
7137                 SettingManager.Local.ColorFav = _clFav;
7138                 SettingManager.Local.ColorOWL = _clOWL;
7139                 SettingManager.Local.ColorRetweet = _clRetweet;
7140                 SettingManager.Local.ColorSelf = _clSelf;
7141                 SettingManager.Local.ColorAtSelf = _clAtSelf;
7142                 SettingManager.Local.ColorTarget = _clTarget;
7143                 SettingManager.Local.ColorAtTarget = _clAtTarget;
7144                 SettingManager.Local.ColorAtFromTarget = _clAtFromTarget;
7145                 SettingManager.Local.ColorAtTo = _clAtTo;
7146                 SettingManager.Local.ColorListBackcolor = _clListBackcolor;
7147                 SettingManager.Local.ColorInputBackcolor = _clInputBackcolor;
7148                 SettingManager.Local.ColorInputFont = _clInputFont;
7149                 SettingManager.Local.FontInputFont = _fntInputFont;
7150
7151                 if (_ignoreConfigSave) return;
7152                 SettingManager.SaveLocal();
7153             }
7154         }
7155
7156         private void SaveConfigsTabs()
7157         {
7158             var tabSettingList = new List<SettingTabs.SettingTabItem>();
7159
7160             var tabs = this._statuses.Tabs.Append(this._statuses.MuteTab);
7161
7162             foreach (var tab in tabs)
7163             {
7164                 if (!tab.IsPermanentTabType)
7165                     continue;
7166
7167                 var tabSetting = new SettingTabs.SettingTabItem
7168                 {
7169                     TabName = tab.TabName,
7170                     TabType = tab.TabType,
7171                     UnreadManage = tab.UnreadManage,
7172                     Protected = tab.Protected,
7173                     Notify = tab.Notify,
7174                     SoundFile = tab.SoundFile,
7175                 };
7176
7177                 switch (tab)
7178                 {
7179                     case FilterTabModel filterTab:
7180                         tabSetting.FilterArray = filterTab.FilterArray;
7181                         break;
7182                     case UserTimelineTabModel userTab:
7183                         tabSetting.User = userTab.ScreenName;
7184                         break;
7185                     case PublicSearchTabModel searchTab:
7186                         tabSetting.SearchWords = searchTab.SearchWords;
7187                         tabSetting.SearchLang = searchTab.SearchLang;
7188                         break;
7189                     case ListTimelineTabModel listTab:
7190                         tabSetting.ListInfo = listTab.ListInfo;
7191                         break;
7192                 }
7193
7194                 tabSettingList.Add(tabSetting);
7195             }
7196
7197             SettingManager.Tabs.Tabs = tabSettingList;
7198             SettingManager.SaveTabs();
7199         }
7200
7201         private async void OpenURLFileMenuItem_Click(object sender, EventArgs e)
7202         {
7203             var ret = InputDialog.Show(this, Properties.Resources.OpenURL_InputText, Properties.Resources.OpenURL_Caption, out var inputText);
7204             if (ret != DialogResult.OK)
7205                 return;
7206
7207             var match = Twitter.StatusUrlRegex.Match(inputText);
7208             if (!match.Success)
7209             {
7210                 MessageBox.Show(this, Properties.Resources.OpenURL_InvalidFormat,
7211                     Properties.Resources.OpenURL_Caption, MessageBoxButtons.OK, MessageBoxIcon.Error);
7212                 return;
7213             }
7214
7215             try
7216             {
7217                 var statusId = long.Parse(match.Groups["StatusId"].Value);
7218                 await this.OpenRelatedTab(statusId);
7219             }
7220             catch (TabException ex)
7221             {
7222                 MessageBox.Show(this, ex.Message, ApplicationSettings.ApplicationName, MessageBoxButtons.OK, MessageBoxIcon.Error);
7223             }
7224         }
7225
7226         private void SaveLogMenuItem_Click(object sender, EventArgs e)
7227         {
7228             var tab = this.CurrentTab;
7229
7230             DialogResult rslt = MessageBox.Show(string.Format(Properties.Resources.SaveLogMenuItem_ClickText1, Environment.NewLine),
7231                     Properties.Resources.SaveLogMenuItem_ClickText2,
7232                     MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
7233             if (rslt == DialogResult.Cancel) return;
7234
7235             SaveFileDialog1.FileName = $"{ApplicationSettings.AssemblyName}Posts{DateTimeUtc.Now.ToLocalTime():yyMMdd-HHmmss}.tsv";
7236             SaveFileDialog1.InitialDirectory = Application.ExecutablePath;
7237             SaveFileDialog1.Filter = Properties.Resources.SaveLogMenuItem_ClickText3;
7238             SaveFileDialog1.FilterIndex = 0;
7239             SaveFileDialog1.Title = Properties.Resources.SaveLogMenuItem_ClickText4;
7240             SaveFileDialog1.RestoreDirectory = true;
7241
7242             if (SaveFileDialog1.ShowDialog() == DialogResult.OK)
7243             {
7244                 if (!SaveFileDialog1.ValidateNames) return;
7245                 using (StreamWriter sw = new StreamWriter(SaveFileDialog1.FileName, false, Encoding.UTF8))
7246                 {
7247                     if (rslt == DialogResult.Yes)
7248                     {
7249                         //All
7250                         for (int idx = 0; idx < tab.AllCount; idx++)
7251                         {
7252                             var post = tab[idx];
7253                             string protect = "";
7254                             if (post.IsProtect) protect = "Protect";
7255                             sw.WriteLine(post.Nickname + "\t" +
7256                                      "\"" + post.TextFromApi.Replace("\n", "").Replace("\"", "\"\"") + "\"" + "\t" +
7257                                      post.CreatedAt.ToLocalTimeString() + "\t" +
7258                                      post.ScreenName + "\t" +
7259                                      post.StatusId + "\t" +
7260                                      post.ImageUrl + "\t" +
7261                                      "\"" + post.Text.Replace("\n", "").Replace("\"", "\"\"") + "\"" + "\t" +
7262                                      protect);
7263                         }
7264                     }
7265                     else
7266                     {
7267                         foreach (var post in this.CurrentTab.SelectedPosts)
7268                         {
7269                             string protect = "";
7270                             if (post.IsProtect) protect = "Protect";
7271                             sw.WriteLine(post.Nickname + "\t" +
7272                                      "\"" + post.TextFromApi.Replace("\n", "").Replace("\"", "\"\"") + "\"" + "\t" +
7273                                      post.CreatedAt.ToLocalTimeString() + "\t" +
7274                                      post.ScreenName + "\t" +
7275                                      post.StatusId + "\t" +
7276                                      post.ImageUrl + "\t" +
7277                                      "\"" + post.Text.Replace("\n", "").Replace("\"", "\"\"") + "\"" + "\t" +
7278                                      protect);
7279                         }
7280                     }
7281                 }
7282             }
7283             this.TopMost = SettingManager.Common.AlwaysTop;
7284         }
7285
7286         public bool TabRename(string origTabName, out string newTabName)
7287         {
7288             //タブ名変更
7289             newTabName = null;
7290             using (InputTabName inputName = new InputTabName())
7291             {
7292                 inputName.TabName = origTabName;
7293                 inputName.ShowDialog();
7294                 if (inputName.DialogResult == DialogResult.Cancel) return false;
7295                 newTabName = inputName.TabName;
7296             }
7297             this.TopMost = SettingManager.Common.AlwaysTop;
7298             if (!string.IsNullOrEmpty(newTabName))
7299             {
7300                 //新タブ名存在チェック
7301                 if (this._statuses.ContainsTab(newTabName))
7302                 {
7303                     string tmp = string.Format(Properties.Resources.Tabs_DoubleClickText1, newTabName);
7304                     MessageBox.Show(tmp, Properties.Resources.Tabs_DoubleClickText2, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
7305                     return false;
7306                 }
7307
7308                 var tabIndex = this._statuses.Tabs.IndexOf(origTabName);
7309                 var tabPage = this.ListTab.TabPages[tabIndex];
7310
7311                 // タブ名を変更
7312                 if (tabPage != null)
7313                     tabPage.Text = newTabName;
7314
7315                 _statuses.RenameTab(origTabName, newTabName);
7316
7317                 SaveConfigsCommon();
7318                 SaveConfigsTabs();
7319                 _rclickTabName = newTabName;
7320                 return true;
7321             }
7322             else
7323             {
7324                 return false;
7325             }
7326         }
7327
7328         private void ListTab_MouseClick(object sender, MouseEventArgs e)
7329         {
7330             if (e.Button == MouseButtons.Middle)
7331             {
7332                 foreach (var (tab, index) in this._statuses.Tabs.WithIndex())
7333                 {
7334                     if (this.ListTab.GetTabRect(index).Contains(e.Location))
7335                     {
7336                         this.RemoveSpecifiedTab(tab.TabName, true);
7337                         this.SaveConfigsTabs();
7338                         break;
7339                     }
7340                 }
7341             }
7342         }
7343
7344         private void ListTab_DoubleClick(object sender, MouseEventArgs e)
7345             => this.TabRename(this.CurrentTabName, out var _);
7346
7347         private void ListTab_MouseDown(object sender, MouseEventArgs e)
7348         {
7349             if (SettingManager.Common.TabMouseLock) return;
7350             if (e.Button == MouseButtons.Left)
7351             {
7352                 foreach (var i in Enumerable.Range(0, this._statuses.Tabs.Count))
7353                 {
7354                     if (this.ListTab.GetTabRect(i).Contains(e.Location))
7355                     {
7356                         _tabDrag = true;
7357                         _tabMouseDownPoint = e.Location;
7358                         break;
7359                     }
7360                 }
7361             }
7362             else
7363             {
7364                 _tabDrag = false;
7365             }
7366         }
7367
7368         private void ListTab_DragEnter(object sender, DragEventArgs e)
7369         {
7370             if (e.Data.GetDataPresent(typeof(TabPage)))
7371                 e.Effect = DragDropEffects.Move;
7372             else
7373                 e.Effect = DragDropEffects.None;
7374         }
7375
7376         private void ListTab_DragDrop(object sender, DragEventArgs e)
7377         {
7378             if (!e.Data.GetDataPresent(typeof(TabPage))) return;
7379
7380             _tabDrag = false;
7381             string tn = "";
7382             bool bef = false;
7383             Point cpos = new Point(e.X, e.Y);
7384             Point spos = ListTab.PointToClient(cpos);
7385             foreach (var (tab, index) in this._statuses.Tabs.WithIndex())
7386             {
7387                 Rectangle rect = ListTab.GetTabRect(index);
7388                 if (rect.Contains(spos))
7389                 {
7390                     tn = tab.TabName;
7391                     if (spos.X <= (rect.Left + rect.Right) / 2)
7392                         bef = true;
7393                     else
7394                         bef = false;
7395
7396                     break;
7397                 }
7398             }
7399
7400             //タブのないところにドロップ->最後尾へ移動
7401             if (string.IsNullOrEmpty(tn))
7402             {
7403                 var lastTab = this._statuses.Tabs.Last();
7404                 tn = lastTab.TabName;
7405                 bef = false;
7406             }
7407
7408             TabPage tp = (TabPage)e.Data.GetData(typeof(TabPage));
7409             if (tp.Text == tn) return;
7410
7411             ReOrderTab(tp.Text, tn, bef);
7412         }
7413
7414         public void ReOrderTab(string targetTabText, string baseTabText, bool isBeforeBaseTab)
7415         {
7416             var baseIndex = this.GetTabPageIndex(baseTabText);
7417             if (baseIndex == -1)
7418                 return;
7419
7420             var targetIndex = this.GetTabPageIndex(targetTabText);
7421             if (targetIndex == -1)
7422                 return;
7423
7424             using (ControlTransaction.Layout(this.ListTab))
7425             {
7426                 var tab = this._statuses.Tabs[targetIndex];
7427                 var tabPage = this.ListTab.TabPages[targetIndex];
7428
7429                 this.ListTab.TabPages.Remove(tabPage);
7430
7431                 if (targetIndex < baseIndex)
7432                     baseIndex--;
7433
7434                 if (!isBeforeBaseTab)
7435                     baseIndex++;
7436
7437                 this._statuses.MoveTab(baseIndex, tab);
7438
7439                 ListTab.TabPages.Insert(baseIndex, tabPage);
7440             }
7441
7442             SaveConfigsTabs();
7443         }
7444
7445         private void MakeReplyOrDirectStatus(bool isAuto = true, bool isReply = true, bool isAll = false)
7446         {
7447             //isAuto:true=先頭に挿入、false=カーソル位置に挿入
7448             //isReply:true=@,false=DM
7449             if (!StatusText.Enabled) return;
7450             if (!this.ExistCurrentPost) return;
7451
7452             var tab = this.CurrentTab;
7453             var selectedPosts = tab.SelectedPosts;
7454
7455             // 複数あてリプライはReplyではなく通常ポスト
7456             //↑仕様変更で全部リプライ扱いでOK(先頭ドット付加しない)
7457             //090403暫定でドットを付加しないようにだけ修正。単独と複数の処理は統合できると思われる。
7458             //090513 all @ replies 廃止の仕様変更によりドット付加に戻し(syo68k)
7459
7460             if (selectedPosts.Length > 0)
7461             {
7462                 // アイテムが1件以上選択されている
7463                 if (selectedPosts.Length == 1 && !isAll && this.ExistCurrentPost)
7464                 {
7465                     var post = selectedPosts.Single();
7466
7467                     // 単独ユーザー宛リプライまたはDM
7468                     if ((tab.TabType == MyCommon.TabUsageType.DirectMessage && isAuto) || (!isAuto && !isReply))
7469                     {
7470                         // ダイレクトメッセージ
7471                         this.inReplyTo = null;
7472                         StatusText.Text = "D " + post.ScreenName + " " + StatusText.Text;
7473                         StatusText.SelectionStart = StatusText.Text.Length;
7474                         StatusText.Focus();
7475                         return;
7476                     }
7477                     if (string.IsNullOrEmpty(StatusText.Text))
7478                     {
7479                         //空の場合
7480                         var inReplyToStatusId = post.RetweetedId ?? post.StatusId;
7481                         var inReplyToScreenName = post.ScreenName;
7482                         this.inReplyTo = (inReplyToStatusId, inReplyToScreenName);
7483
7484                         // ステータステキストが入力されていない場合先頭に@ユーザー名を追加する
7485                         StatusText.Text = "@" + post.ScreenName + " ";
7486                     }
7487                     else
7488                     {
7489                         //何か入力済の場合
7490
7491                         if (isAuto)
7492                         {
7493                             //1件選んでEnter or DoubleClick
7494                             if (StatusText.Text.Contains("@" + post.ScreenName + " "))
7495                             {
7496                                 if (this.inReplyTo?.ScreenName == post.ScreenName)
7497                                 {
7498                                     //返信先書き換え
7499                                     var inReplyToStatusId = post.RetweetedId ?? post.StatusId;
7500                                     var inReplyToScreenName = post.ScreenName;
7501                                     this.inReplyTo = (inReplyToStatusId, inReplyToScreenName);
7502                                 }
7503                                 return;
7504                             }
7505                             if (!StatusText.Text.StartsWith("@", StringComparison.Ordinal))
7506                             {
7507                                 //文頭@以外
7508                                 if (StatusText.Text.StartsWith(". ", StringComparison.Ordinal))
7509                                 {
7510                                     // 複数リプライ
7511                                     this.inReplyTo = null;
7512                                     StatusText.Text = StatusText.Text.Insert(2, "@" + post.ScreenName + " ");
7513                                 }
7514                                 else
7515                                 {
7516                                     // 単独リプライ
7517                                     var inReplyToStatusId = post.RetweetedId ?? post.StatusId;
7518                                     var inReplyToScreenName = post.ScreenName;
7519                                     this.inReplyTo = (inReplyToStatusId, inReplyToScreenName);
7520                                     StatusText.Text = "@" + post.ScreenName + " " + StatusText.Text;
7521                                 }
7522                             }
7523                             else
7524                             {
7525                                 //文頭@
7526                                 // 複数リプライ
7527                                 this.inReplyTo = null;
7528                                 StatusText.Text = ". @" + post.ScreenName + " " + StatusText.Text;
7529                             }
7530                         }
7531                         else
7532                         {
7533                             //1件選んでCtrl-Rの場合(返信先操作せず)
7534                             int sidx = StatusText.SelectionStart;
7535                             string id = "@" + post.ScreenName + " ";
7536                             if (sidx > 0)
7537                             {
7538                                 if (StatusText.Text.Substring(sidx - 1, 1) != " ")
7539                                 {
7540                                     id = " " + id;
7541                                 }
7542                             }
7543                             StatusText.Text = StatusText.Text.Insert(sidx, id);
7544                             sidx += id.Length;
7545                             //if (StatusText.Text.StartsWith("@"))
7546                             //{
7547                             //    //複数リプライ
7548                             //    StatusText.Text = ". " + StatusText.Text.Insert(sidx, " @" + _curPost.ScreenName + " ");
7549                             //    sidx += 5 + _curPost.ScreenName.Length;
7550                             //}
7551                             //else
7552                             //{
7553                             //    // 複数リプライ
7554                             //    StatusText.Text = StatusText.Text.Insert(sidx, " @" + _curPost.ScreenName + " ");
7555                             //    sidx += 3 + _curPost.ScreenName.Length;
7556                             //}
7557                             StatusText.SelectionStart = sidx;
7558                             StatusText.Focus();
7559                             //_reply_to_id = 0;
7560                             //_reply_to_name = null;
7561                             return;
7562                         }
7563                     }
7564                 }
7565                 else
7566                 {
7567                     // 複数リプライ
7568                     if (!isAuto && !isReply) return;
7569
7570                     //C-S-rか、複数の宛先を選択中にEnter/DoubleClick/C-r/C-S-r
7571
7572                     if (isAuto)
7573                     {
7574                         //Enter or DoubleClick
7575
7576                         string sTxt = StatusText.Text;
7577                         if (!sTxt.StartsWith(". ", StringComparison.Ordinal))
7578                         {
7579                             sTxt = ". " + sTxt;
7580                             this.inReplyTo = null;
7581                         }
7582                         foreach (var post in selectedPosts)
7583                         {
7584                             if (!sTxt.Contains("@" + post.ScreenName + " "))
7585                             {
7586                                 sTxt = sTxt.Insert(2, "@" + post.ScreenName + " ");
7587                                 //sTxt = "@" + post.ScreenName + " " + sTxt;
7588                             }
7589                         }
7590                         StatusText.Text = sTxt;
7591                     }
7592                     else
7593                     {
7594                         //C-S-r or C-r
7595
7596                         if (selectedPosts.Length > 1)
7597                         {
7598                             //複数ポスト選択
7599
7600                             string ids = "";
7601                             int sidx = StatusText.SelectionStart;
7602                             foreach (var post in selectedPosts)
7603                             {
7604                                 if (!ids.Contains("@" + post.ScreenName + " ") && post.UserId != tw.UserId)
7605                                 {
7606                                     ids += "@" + post.ScreenName + " ";
7607                                 }
7608                                 if (isAll)
7609                                 {
7610                                     foreach (var (_, screenName) in post.ReplyToList)
7611                                     {
7612                                         if (!ids.Contains("@" + screenName + " ") &&
7613                                             !screenName.Equals(tw.Username, StringComparison.CurrentCultureIgnoreCase))
7614                                         {
7615                                             Match m = Regex.Match(post.TextFromApi, "[@@](?<id>" + screenName + ")([^a-zA-Z0-9]|$)", RegexOptions.IgnoreCase);
7616                                             if (m.Success)
7617                                                 ids += "@" + m.Result("${id}") + " ";
7618                                             else
7619                                                 ids += "@" + screenName + " ";
7620                                         }
7621                                     }
7622                                 }
7623                             }
7624                             if (ids.Length == 0) return;
7625                             if (!StatusText.Text.StartsWith(". ", StringComparison.Ordinal))
7626                             {
7627                                 this.inReplyTo = null;
7628                                 StatusText.Text = ". " + StatusText.Text;
7629                                 sidx += 2;
7630                             }
7631                             if (sidx > 0)
7632                             {
7633                                 if (StatusText.Text.Substring(sidx - 1, 1) != " ")
7634                                 {
7635                                     ids = " " + ids;
7636                                 }
7637                             }
7638                             StatusText.Text = StatusText.Text.Insert(sidx, ids);
7639                             sidx += ids.Length;
7640                             //if (StatusText.Text.StartsWith("@"))
7641                             //{
7642                             //    StatusText.Text = ". " + StatusText.Text.Insert(sidx, ids);
7643                             //    sidx += 2 + ids.Length;
7644                             //}
7645                             //else
7646                             //{
7647                             //    StatusText.Text = StatusText.Text.Insert(sidx, ids);
7648                             //    sidx += 1 + ids.Length;
7649                             //}
7650                             StatusText.SelectionStart = sidx;
7651                             StatusText.Focus();
7652                             return;
7653                         }
7654                         else
7655                         {
7656                             //1件のみ選択のC-S-r(返信元付加する可能性あり)
7657
7658                             string ids = "";
7659                             int sidx = StatusText.SelectionStart;
7660                             var post = selectedPosts.Single();
7661                             if (!ids.Contains("@" + post.ScreenName + " ") && post.UserId != tw.UserId)
7662                             {
7663                                 ids += "@" + post.ScreenName + " ";
7664                             }
7665                             foreach (var (_, screenName) in post.ReplyToList)
7666                             {
7667                                 if (!ids.Contains("@" + screenName + " ") &&
7668                                     !screenName.Equals(tw.Username, StringComparison.CurrentCultureIgnoreCase))
7669                                 {
7670                                     Match m = Regex.Match(post.TextFromApi, "[@@](?<id>" + screenName + ")([^a-zA-Z0-9]|$)", RegexOptions.IgnoreCase);
7671                                     if (m.Success)
7672                                         ids += "@" + m.Result("${id}") + " ";
7673                                     else
7674                                         ids += "@" + screenName + " ";
7675                                 }
7676                             }
7677                             if (!string.IsNullOrEmpty(post.RetweetedBy))
7678                             {
7679                                 if (!ids.Contains("@" + post.RetweetedBy + " ") && post.RetweetedByUserId != tw.UserId)
7680                                 {
7681                                     ids += "@" + post.RetweetedBy + " ";
7682                                 }
7683                             }
7684                             if (ids.Length == 0) return;
7685                             if (string.IsNullOrEmpty(StatusText.Text))
7686                             {
7687                                 //未入力の場合のみ返信先付加
7688                                 var inReplyToStatusId = post.RetweetedId ?? post.StatusId;
7689                                 var inReplyToScreenName = post.ScreenName;
7690                                 this.inReplyTo = (inReplyToStatusId, inReplyToScreenName);
7691
7692                                 StatusText.Text = ids;
7693                                 StatusText.SelectionStart = ids.Length;
7694                                 StatusText.Focus();
7695                                 return;
7696                             }
7697
7698                             if (sidx > 0)
7699                             {
7700                                 if (StatusText.Text.Substring(sidx - 1, 1) != " ")
7701                                 {
7702                                     ids = " " + ids;
7703                                 }
7704                             }
7705                             StatusText.Text = StatusText.Text.Insert(sidx, ids);
7706                             sidx += ids.Length;
7707                             StatusText.SelectionStart = sidx;
7708                             StatusText.Focus();
7709                             return;
7710                         }
7711                     }
7712                 }
7713                 StatusText.SelectionStart = StatusText.Text.Length;
7714                 StatusText.Focus();
7715             }
7716         }
7717
7718         private void ListTab_MouseUp(object sender, MouseEventArgs e)
7719             => this._tabDrag = false;
7720
7721         private int iconCnt = 0;
7722         private int blinkCnt = 0;
7723         private bool blink = false;
7724
7725         private void RefreshTasktrayIcon()
7726         {
7727             void EnableTasktrayAnimation()
7728                 => this.TimerRefreshIcon.Enabled = true;
7729
7730             void DisableTasktrayAnimation()
7731                 => this.TimerRefreshIcon.Enabled = false;
7732
7733             var busyTasks = this.workerSemaphore.CurrentCount != MAX_WORKER_THREADS;
7734             if (busyTasks)
7735             {
7736                 iconCnt += 1;
7737                 if (iconCnt >= this.NIconRefresh.Length)
7738                     iconCnt = 0;
7739
7740                 NotifyIcon1.Icon = NIconRefresh[iconCnt];
7741                 _myStatusError = false;
7742                 EnableTasktrayAnimation();
7743                 return;
7744             }
7745
7746             var replyIconType = SettingManager.Common.ReplyIconState;
7747             var reply = false;
7748             if (replyIconType != MyCommon.REPLY_ICONSTATE.None)
7749             {
7750                 var replyTab = this._statuses.GetTabByType<MentionsTabModel>();
7751                 if (replyTab != null && replyTab.UnreadCount > 0)
7752                     reply = true;
7753             }
7754
7755             if (replyIconType == MyCommon.REPLY_ICONSTATE.BlinkIcon && reply)
7756             {
7757                 blinkCnt += 1;
7758                 if (blinkCnt > 10)
7759                     blinkCnt = 0;
7760
7761                 if (blinkCnt == 0)
7762                     blink = !blink;
7763
7764                 NotifyIcon1.Icon = blink ? ReplyIconBlink : ReplyIcon;
7765                 EnableTasktrayAnimation();
7766                 return;
7767             }
7768
7769             DisableTasktrayAnimation();
7770
7771             iconCnt = 0;
7772             blinkCnt = 0;
7773             blink = false;
7774
7775             // 優先度:リプライ→エラー→オフライン→アイドル
7776             // エラーは更新アイコンでクリアされる
7777             if (replyIconType == MyCommon.REPLY_ICONSTATE.StaticIcon && reply)
7778                 NotifyIcon1.Icon = ReplyIcon;
7779             else if (_myStatusError)
7780                 NotifyIcon1.Icon = NIconAtRed;
7781             else if (_myStatusOnline)
7782                 NotifyIcon1.Icon = NIconAt;
7783             else
7784                 NotifyIcon1.Icon = NIconAtSmoke;
7785         }
7786
7787         private void TimerRefreshIcon_Tick(object sender, EventArgs e)
7788             => this.RefreshTasktrayIcon(); // 200ms
7789
7790         private void ContextMenuTabProperty_Opening(object sender, CancelEventArgs e)
7791         {
7792             //右クリックの場合はタブ名が設定済。アプリケーションキーの場合は現在のタブを対象とする
7793             if (string.IsNullOrEmpty(_rclickTabName) || sender != ContextMenuTabProperty)
7794                 _rclickTabName = this.CurrentTabName;
7795
7796             if (_statuses == null) return;
7797             if (_statuses.Tabs == null) return;
7798
7799             if (!this._statuses.Tabs.TryGetValue(this._rclickTabName, out var tb))
7800                 return;
7801
7802             NotifyDispMenuItem.Checked = tb.Notify;
7803             this.NotifyTbMenuItem.Checked = tb.Notify;
7804
7805             soundfileListup = true;
7806             SoundFileComboBox.Items.Clear();
7807             this.SoundFileTbComboBox.Items.Clear();
7808             SoundFileComboBox.Items.Add("");
7809             this.SoundFileTbComboBox.Items.Add("");
7810             DirectoryInfo oDir = new DirectoryInfo(Application.StartupPath + Path.DirectorySeparatorChar);
7811             if (Directory.Exists(Path.Combine(Application.StartupPath, "Sounds")))
7812             {
7813                 oDir = oDir.GetDirectories("Sounds")[0];
7814             }
7815             foreach (FileInfo oFile in oDir.GetFiles("*.wav"))
7816             {
7817                 SoundFileComboBox.Items.Add(oFile.Name);
7818                 this.SoundFileTbComboBox.Items.Add(oFile.Name);
7819             }
7820             int idx = SoundFileComboBox.Items.IndexOf(tb.SoundFile);
7821             if (idx == -1) idx = 0;
7822             SoundFileComboBox.SelectedIndex = idx;
7823             this.SoundFileTbComboBox.SelectedIndex = idx;
7824             soundfileListup = false;
7825             UreadManageMenuItem.Checked = tb.UnreadManage;
7826             this.UnreadMngTbMenuItem.Checked = tb.UnreadManage;
7827
7828             TabMenuControl(_rclickTabName);
7829         }
7830
7831         private void TabMenuControl(string tabName)
7832         {
7833             var tabInfo = _statuses.GetTabByName(tabName);
7834
7835             this.FilterEditMenuItem.Enabled = true;
7836             this.EditRuleTbMenuItem.Enabled = true;
7837
7838             if (tabInfo.IsDefaultTabType)
7839             {
7840                 this.ProtectTabMenuItem.Enabled = false;
7841                 this.ProtectTbMenuItem.Enabled = false;
7842             }
7843             else
7844             {
7845                 this.ProtectTabMenuItem.Enabled = true;
7846                 this.ProtectTbMenuItem.Enabled = true;
7847             }
7848
7849             if (tabInfo.IsDefaultTabType || tabInfo.Protected)
7850             {
7851                 this.ProtectTabMenuItem.Checked = true;
7852                 this.ProtectTbMenuItem.Checked = true;
7853                 this.DeleteTabMenuItem.Enabled = false;
7854                 this.DeleteTbMenuItem.Enabled = false;
7855             }
7856             else
7857             {
7858                 this.ProtectTabMenuItem.Checked = false;
7859                 this.ProtectTbMenuItem.Checked = false;
7860                 this.DeleteTabMenuItem.Enabled = true;
7861                 this.DeleteTbMenuItem.Enabled = true;
7862             }
7863         }
7864
7865         private void ProtectTabMenuItem_Click(object sender, EventArgs e)
7866         {
7867             var checkState = ((ToolStripMenuItem)sender).Checked;
7868
7869             // チェック状態を同期
7870             this.ProtectTbMenuItem.Checked = checkState;
7871             this.ProtectTabMenuItem.Checked = checkState;
7872
7873             // ロック中はタブの削除を無効化
7874             this.DeleteTabMenuItem.Enabled = !checkState;
7875             this.DeleteTbMenuItem.Enabled = !checkState;
7876
7877             if (string.IsNullOrEmpty(_rclickTabName)) return;
7878             _statuses.Tabs[_rclickTabName].Protected = checkState;
7879
7880             SaveConfigsTabs();
7881         }
7882
7883         private void UreadManageMenuItem_Click(object sender, EventArgs e)
7884         {
7885             UreadManageMenuItem.Checked = ((ToolStripMenuItem)sender).Checked;
7886             this.UnreadMngTbMenuItem.Checked = UreadManageMenuItem.Checked;
7887
7888             if (string.IsNullOrEmpty(_rclickTabName)) return;
7889             ChangeTabUnreadManage(_rclickTabName, UreadManageMenuItem.Checked);
7890
7891             SaveConfigsTabs();
7892         }
7893
7894         public void ChangeTabUnreadManage(string tabName, bool isManage)
7895         {
7896             var idx = this.GetTabPageIndex(tabName);
7897             if (idx == -1)
7898                 return;
7899
7900             var tab = this._statuses.Tabs[tabName];
7901             tab.UnreadManage = isManage;
7902
7903             if (SettingManager.Common.TabIconDisp)
7904             {
7905                 var tabPage = this.ListTab.TabPages[idx];
7906                 if (tab.UnreadCount > 0)
7907                     tabPage.ImageIndex = 0;
7908                 else
7909                     tabPage.ImageIndex = -1;
7910             }
7911
7912             if (this.CurrentTabName == tabName)
7913             {
7914                 this.PurgeListViewItemCache();
7915                 this.CurrentListView.Refresh();
7916             }
7917
7918             SetMainWindowTitle();
7919             SetStatusLabelUrl();
7920             if (!SettingManager.Common.TabIconDisp) ListTab.Refresh();
7921         }
7922
7923         private void NotifyDispMenuItem_Click(object sender, EventArgs e)
7924         {
7925             NotifyDispMenuItem.Checked = ((ToolStripMenuItem)sender).Checked;
7926             this.NotifyTbMenuItem.Checked = NotifyDispMenuItem.Checked;
7927
7928             if (string.IsNullOrEmpty(_rclickTabName)) return;
7929
7930             _statuses.Tabs[_rclickTabName].Notify = NotifyDispMenuItem.Checked;
7931
7932             SaveConfigsTabs();
7933         }
7934
7935         private void SoundFileComboBox_SelectedIndexChanged(object sender, EventArgs e)
7936         {
7937             if (soundfileListup || string.IsNullOrEmpty(_rclickTabName)) return;
7938
7939             _statuses.Tabs[_rclickTabName].SoundFile = (string)((ToolStripComboBox)sender).SelectedItem;
7940
7941             SaveConfigsTabs();
7942         }
7943
7944         private void DeleteTabMenuItem_Click(object sender, EventArgs e)
7945         {
7946             if (string.IsNullOrEmpty(_rclickTabName) || sender == this.DeleteTbMenuItem)
7947                 _rclickTabName = this.CurrentTabName;
7948
7949             RemoveSpecifiedTab(_rclickTabName, true);
7950             SaveConfigsTabs();
7951         }
7952
7953         private void FilterEditMenuItem_Click(object sender, EventArgs e)
7954         {
7955             if (string.IsNullOrEmpty(_rclickTabName)) _rclickTabName = _statuses.GetTabByType(MyCommon.TabUsageType.Home).TabName;
7956
7957             using (var fltDialog = new FilterDialog())
7958             {
7959                 fltDialog.Owner = this;
7960                 fltDialog.SetCurrent(_rclickTabName);
7961                 fltDialog.ShowDialog(this);
7962             }
7963             this.TopMost = SettingManager.Common.AlwaysTop;
7964
7965             this.ApplyPostFilters();
7966             SaveConfigsTabs();
7967         }
7968
7969         private async void AddTabMenuItem_Click(object sender, EventArgs e)
7970         {
7971             string tabName = null;
7972             MyCommon.TabUsageType tabUsage;
7973             using (InputTabName inputName = new InputTabName())
7974             {
7975                 inputName.TabName = _statuses.MakeTabName("MyTab");
7976                 inputName.IsShowUsage = true;
7977                 inputName.ShowDialog();
7978                 if (inputName.DialogResult == DialogResult.Cancel) return;
7979                 tabName = inputName.TabName;
7980                 tabUsage = inputName.Usage;
7981             }
7982             this.TopMost = SettingManager.Common.AlwaysTop;
7983             if (!string.IsNullOrEmpty(tabName))
7984             {
7985                 //List対応
7986                 ListElement list = null;
7987                 if (tabUsage == MyCommon.TabUsageType.Lists)
7988                 {
7989                     using (ListAvailable listAvail = new ListAvailable())
7990                     {
7991                         if (listAvail.ShowDialog(this) == DialogResult.Cancel) return;
7992                         if (listAvail.SelectedList == null) return;
7993                         list = listAvail.SelectedList;
7994                     }
7995                 }
7996
7997                 TabModel tab;
7998                 switch (tabUsage)
7999                 {
8000                     case MyCommon.TabUsageType.UserDefined:
8001                         tab = new FilterTabModel(tabName);
8002                         break;
8003                     case MyCommon.TabUsageType.PublicSearch:
8004                         tab = new PublicSearchTabModel(tabName);
8005                         break;
8006                     case MyCommon.TabUsageType.Lists:
8007                         tab = new ListTimelineTabModel(tabName, list);
8008                         break;
8009                     default:
8010                         return;
8011                 }
8012
8013                 if (!_statuses.AddTab(tab) || !AddNewTab(tab, startup: false))
8014                 {
8015                     string tmp = string.Format(Properties.Resources.AddTabMenuItem_ClickText1, tabName);
8016                     MessageBox.Show(tmp, Properties.Resources.AddTabMenuItem_ClickText2, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
8017                 }
8018                 else
8019                 {
8020                     //成功
8021                     SaveConfigsTabs();
8022
8023                     var tabIndex = this._statuses.Tabs.Count - 1;
8024
8025                     if (tabUsage == MyCommon.TabUsageType.PublicSearch)
8026                     {
8027                         ListTab.SelectedIndex = tabIndex;
8028                         this.CurrentTabPage.Controls["panelSearch"].Controls["comboSearch"].Focus();
8029                     }
8030                     if (tabUsage == MyCommon.TabUsageType.Lists)
8031                     {
8032                         ListTab.SelectedIndex = tabIndex;
8033                         await this.RefreshTabAsync(this.CurrentTab);
8034                     }
8035                 }
8036             }
8037         }
8038
8039         private void TabMenuItem_Click(object sender, EventArgs e)
8040         {
8041             using (var fltDialog = new FilterDialog())
8042             {
8043                 fltDialog.Owner = this;
8044
8045                 //選択発言を元にフィルタ追加
8046                 foreach (var post in this.CurrentTab.SelectedPosts)
8047                 {
8048                     //タブ選択(or追加)
8049                     if (!SelectTab(out var tabName)) return;
8050
8051                     fltDialog.SetCurrent(tabName);
8052
8053                     if (post.RetweetedId == null)
8054                     {
8055                         fltDialog.AddNewFilter(post.ScreenName, post.TextFromApi);
8056                     }
8057                     else
8058                     {
8059                         fltDialog.AddNewFilter(post.RetweetedBy, post.TextFromApi);
8060                     }
8061                     fltDialog.ShowDialog(this);
8062                     this.TopMost = SettingManager.Common.AlwaysTop;
8063                 }
8064             }
8065
8066             this.ApplyPostFilters();
8067             SaveConfigsTabs();
8068         }
8069
8070         protected override bool ProcessDialogKey(Keys keyData)
8071         {
8072             //TextBox1でEnterを押してもビープ音が鳴らないようにする
8073             if ((keyData & Keys.KeyCode) == Keys.Enter)
8074             {
8075                 if (StatusText.Focused)
8076                 {
8077                     bool _NewLine = false;
8078                     bool _Post = false;
8079
8080                     if (SettingManager.Common.PostCtrlEnter) //Ctrl+Enter投稿時
8081                     {
8082                         if (StatusText.Multiline)
8083                         {
8084                             if ((keyData & Keys.Shift) == Keys.Shift && (keyData & Keys.Control) != Keys.Control) _NewLine = true;
8085
8086                             if ((keyData & Keys.Control) == Keys.Control) _Post = true;
8087                         }
8088                         else
8089                         {
8090                             if (((keyData & Keys.Control) == Keys.Control)) _Post = true;
8091                         }
8092
8093                     }
8094                     else if (SettingManager.Common.PostShiftEnter) //SHift+Enter投稿時
8095                     {
8096                         if (StatusText.Multiline)
8097                         {
8098                             if ((keyData & Keys.Control) == Keys.Control && (keyData & Keys.Shift) != Keys.Shift) _NewLine = true;
8099
8100                             if ((keyData & Keys.Shift) == Keys.Shift) _Post = true;
8101                         }
8102                         else
8103                         {
8104                             if (((keyData & Keys.Shift) == Keys.Shift)) _Post = true;
8105                         }
8106
8107                     }
8108                     else //Enter投稿時
8109                     {
8110                         if (StatusText.Multiline)
8111                         {
8112                             if ((keyData & Keys.Shift) == Keys.Shift && (keyData & Keys.Control) != Keys.Control) _NewLine = true;
8113
8114                             if (((keyData & Keys.Control) != Keys.Control && (keyData & Keys.Shift) != Keys.Shift) ||
8115                                 ((keyData & Keys.Control) == Keys.Control && (keyData & Keys.Shift) == Keys.Shift)) _Post = true;
8116                         }
8117                         else
8118                         {
8119                             if (((keyData & Keys.Shift) == Keys.Shift) ||
8120                                 (((keyData & Keys.Control) != Keys.Control) &&
8121                                 ((keyData & Keys.Shift) != Keys.Shift))) _Post = true;
8122                         }
8123                     }
8124
8125                     if (_NewLine)
8126                     {
8127                         int pos1 = StatusText.SelectionStart;
8128                         if (StatusText.SelectionLength > 0)
8129                         {
8130                             StatusText.Text = StatusText.Text.Remove(pos1, StatusText.SelectionLength);  //選択状態文字列削除
8131                         }
8132                         StatusText.Text = StatusText.Text.Insert(pos1, Environment.NewLine);  //改行挿入
8133                         StatusText.SelectionStart = pos1 + Environment.NewLine.Length;    //カーソルを改行の次の文字へ移動
8134                         return true;
8135                     }
8136                     else if (_Post)
8137                     {
8138                         PostButton_Click(null, null);
8139                         return true;
8140                     }
8141                 }
8142                 else
8143                 {
8144                     var tab = this.CurrentTab;
8145                     if (tab.TabType == MyCommon.TabUsageType.PublicSearch)
8146                     {
8147                         var tabPage = this.CurrentTabPage;
8148                         if (tabPage.Controls["panelSearch"].Controls["comboSearch"].Focused ||
8149                             tabPage.Controls["panelSearch"].Controls["comboLang"].Focused)
8150                         {
8151                             this.SearchButton_Click(tabPage.Controls["panelSearch"].Controls["comboSearch"], null);
8152                             return true;
8153                         }
8154                     }
8155                 }
8156             }
8157
8158             return base.ProcessDialogKey(keyData);
8159         }
8160
8161         private void ReplyAllStripMenuItem_Click(object sender, EventArgs e)
8162             => this.MakeReplyOrDirectStatus(false, true, true);
8163
8164         private void IDRuleMenuItem_Click(object sender, EventArgs e)
8165         {
8166             var tab = this.CurrentTab;
8167             var selectedPosts = tab.SelectedPosts;
8168
8169             // 未選択なら処理終了
8170             if (selectedPosts.Length == 0)
8171                 return;
8172
8173             var screenNameArray = selectedPosts
8174                 .Select(x => x.RetweetedId != null ? x.RetweetedBy : x.ScreenName)
8175                 .ToArray();
8176
8177             this.AddFilterRuleByScreenName(screenNameArray);
8178
8179             if (screenNameArray.Length != 0)
8180             {
8181                 List<string> atids = new List<string>();
8182                 foreach (var screenName in screenNameArray)
8183                 {
8184                     atids.Add("@" + screenName);
8185                 }
8186                 int cnt = AtIdSupl.ItemCount;
8187                 AtIdSupl.AddRangeItem(atids.ToArray());
8188                 if (AtIdSupl.ItemCount != cnt)
8189                     this.MarkSettingAtIdModified();
8190             }
8191         }
8192
8193         private void SourceRuleMenuItem_Click(object sender, EventArgs e)
8194         {
8195             var tab = this.CurrentTab;
8196             var selectedPosts = tab.SelectedPosts;
8197
8198             if (selectedPosts.Length == 0)
8199                 return;
8200
8201             var sourceArray = selectedPosts.Select(x => x.Source).ToArray();
8202
8203             this.AddFilterRuleBySource(sourceArray);
8204         }
8205
8206         public void AddFilterRuleByScreenName(params string[] screenNameArray)
8207         {
8208             //タブ選択(or追加)
8209             if (!SelectTab(out var tabName)) return;
8210
8211             var tab = (FilterTabModel)this._statuses.Tabs[tabName];
8212
8213             bool mv;
8214             bool mk;
8215             if (tab.TabType != MyCommon.TabUsageType.Mute)
8216             {
8217                 this.MoveOrCopy(out mv, out mk);
8218             }
8219             else
8220             {
8221                 // ミュートタブでは常に MoveMatches を true にする
8222                 mv = true;
8223                 mk = false;
8224             }
8225
8226             foreach (var screenName in screenNameArray)
8227             {
8228                 tab.AddFilter(new PostFilterRule
8229                 {
8230                     FilterName = screenName,
8231                     UseNameField = true,
8232                     MoveMatches = mv,
8233                     MarkMatches = mk,
8234                     UseRegex = false,
8235                     FilterByUrl = false,
8236                 });
8237             }
8238
8239             this.ApplyPostFilters();
8240             SaveConfigsTabs();
8241         }
8242
8243         public void AddFilterRuleBySource(params string[] sourceArray)
8244         {
8245             // タブ選択ダイアログを表示(or追加)
8246             if (!this.SelectTab(out var tabName))
8247                 return;
8248
8249             var filterTab = (FilterTabModel)this._statuses.Tabs[tabName];
8250
8251             bool mv;
8252             bool mk;
8253             if (filterTab.TabType != MyCommon.TabUsageType.Mute)
8254             {
8255                 // フィルタ動作選択ダイアログを表示(移動/コピー, マーク有無)
8256                 this.MoveOrCopy(out mv, out mk);
8257             }
8258             else
8259             {
8260                 // ミュートタブでは常に MoveMatches を true にする
8261                 mv = true;
8262                 mk = false;
8263             }
8264
8265             // 振り分けルールに追加するSource
8266             foreach (var source in sourceArray)
8267             {
8268                 filterTab.AddFilter(new PostFilterRule
8269                 {
8270                     FilterSource = source,
8271                     MoveMatches = mv,
8272                     MarkMatches = mk,
8273                     UseRegex = false,
8274                     FilterByUrl = false,
8275                 });
8276             }
8277
8278             this.ApplyPostFilters();
8279             this.SaveConfigsTabs();
8280         }
8281
8282         private bool SelectTab(out string tabName)
8283         {
8284             do
8285             {
8286                 tabName = null;
8287
8288                 //振り分け先タブ選択
8289                 using (var dialog = new TabsDialog(_statuses))
8290                 {
8291                     if (dialog.ShowDialog(this) == DialogResult.Cancel) return false;
8292
8293                     tabName = dialog.SelectedTab?.TabName;
8294                 }
8295
8296                 this.CurrentTabPage.Focus();
8297                 //新規タブを選択→タブ作成
8298                 if (tabName == null)
8299                 {
8300                     using (InputTabName inputName = new InputTabName())
8301                     {
8302                         inputName.TabName = _statuses.MakeTabName("MyTab");
8303                         inputName.ShowDialog();
8304                         if (inputName.DialogResult == DialogResult.Cancel) return false;
8305                         tabName = inputName.TabName;
8306                     }
8307                     this.TopMost = SettingManager.Common.AlwaysTop;
8308                     if (!string.IsNullOrEmpty(tabName))
8309                     {
8310                         var tab = new FilterTabModel(tabName);
8311                         if (!_statuses.AddTab(tab) || !AddNewTab(tab, startup: false))
8312                         {
8313                             string tmp = string.Format(Properties.Resources.IDRuleMenuItem_ClickText2, tabName);
8314                             MessageBox.Show(tmp, Properties.Resources.IDRuleMenuItem_ClickText3, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
8315                             //もう一度タブ名入力
8316                         }
8317                         else
8318                         {
8319                             return true;
8320                         }
8321                     }
8322                 }
8323                 else
8324                 {
8325                     //既存タブを選択
8326                     return true;
8327                 }
8328             }
8329             while (true);
8330         }
8331
8332         private void MoveOrCopy(out bool move, out bool mark)
8333         {
8334             {
8335                 //移動するか?
8336                 string _tmp = string.Format(Properties.Resources.IDRuleMenuItem_ClickText4, Environment.NewLine);
8337                 if (MessageBox.Show(_tmp, Properties.Resources.IDRuleMenuItem_ClickText5, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
8338                     move = false;
8339                 else
8340                     move = true;
8341             }
8342             if (!move)
8343             {
8344                 //マークするか?
8345                 string _tmp = string.Format(Properties.Resources.IDRuleMenuItem_ClickText6, Environment.NewLine);
8346                 if (MessageBox.Show(_tmp, Properties.Resources.IDRuleMenuItem_ClickText7, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
8347                     mark = true;
8348                 else
8349                     mark = false;
8350             }
8351             else
8352             {
8353                 mark = false;
8354             }
8355         }
8356
8357         private void CopySTOTMenuItem_Click(object sender, EventArgs e)
8358             => this.CopyStot();
8359
8360         private void CopyURLMenuItem_Click(object sender, EventArgs e)
8361             => this.CopyIdUri();
8362
8363         private void SelectAllMenuItem_Click(object sender, EventArgs e)
8364         {
8365             if (StatusText.Focused)
8366             {
8367                 // 発言欄でのCtrl+A
8368                 StatusText.SelectAll();
8369             }
8370             else
8371             {
8372                 // ListView上でのCtrl+A
8373                 NativeMethods.SelectAllItems(this.CurrentListView);
8374             }
8375         }
8376
8377         private void MoveMiddle()
8378         {
8379             ListViewItem _item;
8380             int idx1;
8381             int idx2;
8382
8383             var listView = this.CurrentListView;
8384             if (listView.SelectedIndices.Count == 0) return;
8385
8386             int idx = listView.SelectedIndices[0];
8387
8388             _item = listView.GetItemAt(0, 25);
8389             if (_item == null)
8390                 idx1 = 0;
8391             else
8392                 idx1 = _item.Index;
8393
8394             _item = listView.GetItemAt(0, listView.ClientSize.Height - 1);
8395             if (_item == null)
8396                 idx2 = listView.VirtualListSize - 1;
8397             else
8398                 idx2 = _item.Index;
8399
8400             idx -= Math.Abs(idx1 - idx2) / 2;
8401             if (idx < 0) idx = 0;
8402
8403             listView.EnsureVisible(listView.VirtualListSize - 1);
8404             listView.EnsureVisible(idx);
8405         }
8406
8407         private async void OpenURLMenuItem_Click(object sender, EventArgs e)
8408         {
8409             var linkElements = this.tweetDetailsView.GetLinkElements();
8410
8411             if (linkElements.Length == 0)
8412                 return;
8413
8414             var links = new List<OpenUrlItem>(linkElements.Length);
8415
8416             foreach (var linkElm in linkElements)
8417             {
8418                 var displayUrl = linkElm.GetAttribute("title");
8419                 var href = linkElm.GetAttribute("href");
8420                 var linkedText = linkElm.InnerText;
8421
8422                 if (string.IsNullOrEmpty(displayUrl))
8423                     displayUrl = href;
8424
8425                 links.Add(new OpenUrlItem(linkedText, displayUrl, href));
8426             }
8427
8428             string selectedUrl;
8429             bool isReverseSettings;
8430
8431             if (links.Count == 1)
8432             {
8433                 // ツイートに含まれる URL が 1 つのみの場合
8434                 //   => OpenURL ダイアログを表示せずにリンクを開く
8435                 selectedUrl = links[0].Href;
8436
8437                 // Ctrl+E で呼ばれた場合を考慮し isReverseSettings の判定を行わない
8438                 isReverseSettings = false;
8439             }
8440             else
8441             {
8442                 // ツイートに含まれる URL が複数ある場合
8443                 //   => OpenURL を表示しユーザーが選択したリンクを開く
8444                 this.UrlDialog.ClearUrl();
8445
8446                 foreach (var link in links)
8447                     this.UrlDialog.AddUrl(link);
8448
8449                 if (this.UrlDialog.ShowDialog(this) != DialogResult.OK)
8450                     return;
8451
8452                 this.TopMost = SettingManager.Common.AlwaysTop;
8453
8454                 selectedUrl = this.UrlDialog.SelectedUrl;
8455
8456                 // Ctrlを押しながらリンクを開いた場合は、設定と逆の動作をするフラグを true としておく
8457                 isReverseSettings = MyCommon.IsKeyDown(Keys.Control);
8458             }
8459
8460             await this.OpenUriAsync(new Uri(selectedUrl), isReverseSettings);
8461         }
8462
8463         private void ClearTabMenuItem_Click(object sender, EventArgs e)
8464         {
8465             if (string.IsNullOrEmpty(_rclickTabName)) return;
8466             ClearTab(_rclickTabName, true);
8467         }
8468
8469         private void ClearTab(string tabName, bool showWarning)
8470         {
8471             if (showWarning)
8472             {
8473                 string tmp = string.Format(Properties.Resources.ClearTabMenuItem_ClickText1, Environment.NewLine);
8474                 if (MessageBox.Show(tmp, tabName + " " + Properties.Resources.ClearTabMenuItem_ClickText2, MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.Cancel)
8475                 {
8476                     return;
8477                 }
8478             }
8479
8480             _statuses.ClearTabIds(tabName);
8481             if (this.CurrentTabName == tabName)
8482             {
8483                 _anchorPost = null;
8484                 _anchorFlag = false;
8485                 this.PurgeListViewItemCache();
8486             }
8487
8488             var tabIndex = this._statuses.Tabs.IndexOf(tabName);
8489             var tabPage = this.ListTab.TabPages[tabIndex];
8490             tabPage.ImageIndex = -1;
8491
8492             var listView = (DetailsListView)tabPage.Tag;
8493             listView.VirtualListSize = 0;
8494
8495             if (!SettingManager.Common.TabIconDisp) ListTab.Refresh();
8496
8497             SetMainWindowTitle();
8498             SetStatusLabelUrl();
8499         }
8500
8501         private static long followers = 0;
8502
8503         private void SetMainWindowTitle()
8504         {
8505             //メインウインドウタイトルの書き換え
8506             StringBuilder ttl = new StringBuilder(256);
8507             int ur = 0;
8508             int al = 0;
8509             if (SettingManager.Common.DispLatestPost != MyCommon.DispTitleEnum.None &&
8510                 SettingManager.Common.DispLatestPost != MyCommon.DispTitleEnum.Post &&
8511                 SettingManager.Common.DispLatestPost != MyCommon.DispTitleEnum.Ver &&
8512                 SettingManager.Common.DispLatestPost != MyCommon.DispTitleEnum.OwnStatus)
8513             {
8514                 foreach (var tab in _statuses.Tabs)
8515                 {
8516                     ur += tab.UnreadCount;
8517                     al += tab.AllCount;
8518                 }
8519             }
8520
8521             if (SettingManager.Common.DispUsername) ttl.Append(tw.Username).Append(" - ");
8522             ttl.Append(ApplicationSettings.ApplicationName);
8523             ttl.Append("  ");
8524             switch (SettingManager.Common.DispLatestPost)
8525             {
8526                 case MyCommon.DispTitleEnum.Ver:
8527                     ttl.Append("Ver:").Append(MyCommon.GetReadableVersion());
8528                     break;
8529                 case MyCommon.DispTitleEnum.Post:
8530                     if (_history != null && _history.Count > 1)
8531                         ttl.Append(_history[_history.Count - 2].status.Replace("\r\n", " "));
8532                     break;
8533                 case MyCommon.DispTitleEnum.UnreadRepCount:
8534                     ttl.AppendFormat(Properties.Resources.SetMainWindowTitleText1, _statuses.GetTabByType(MyCommon.TabUsageType.Mentions).UnreadCount + _statuses.GetTabByType(MyCommon.TabUsageType.DirectMessage).UnreadCount);
8535                     break;
8536                 case MyCommon.DispTitleEnum.UnreadAllCount:
8537                     ttl.AppendFormat(Properties.Resources.SetMainWindowTitleText2, ur);
8538                     break;
8539                 case MyCommon.DispTitleEnum.UnreadAllRepCount:
8540                     ttl.AppendFormat(Properties.Resources.SetMainWindowTitleText3, ur, _statuses.GetTabByType(MyCommon.TabUsageType.Mentions).UnreadCount + _statuses.GetTabByType(MyCommon.TabUsageType.DirectMessage).UnreadCount);
8541                     break;
8542                 case MyCommon.DispTitleEnum.UnreadCountAllCount:
8543                     ttl.AppendFormat(Properties.Resources.SetMainWindowTitleText4, ur, al);
8544                     break;
8545                 case MyCommon.DispTitleEnum.OwnStatus:
8546                     if (followers == 0 && tw.FollowersCount > 0) followers = tw.FollowersCount;
8547                     ttl.AppendFormat(Properties.Resources.OwnStatusTitle, tw.StatusesCount, tw.FriendsCount, tw.FollowersCount, tw.FollowersCount - followers);
8548                     break;
8549             }
8550
8551             try
8552             {
8553                 this.Text = ttl.ToString();
8554             }
8555             catch (AccessViolationException)
8556             {
8557                 //原因不明。ポスト内容に依存か?たまーに発生するが再現せず。
8558             }
8559         }
8560
8561         private string GetStatusLabelText()
8562         {
8563             //ステータス欄にカウント表示
8564             //タブ未読数/タブ発言数 全未読数/総発言数 (未読@+未読DM数)
8565             if (_statuses == null) return "";
8566             TabModel tbRep = _statuses.GetTabByType(MyCommon.TabUsageType.Mentions);
8567             TabModel tbDm = _statuses.GetTabByType(MyCommon.TabUsageType.DirectMessage);
8568             if (tbRep == null || tbDm == null) return "";
8569             int urat = tbRep.UnreadCount + tbDm.UnreadCount;
8570             int ur = 0;
8571             int al = 0;
8572             int tur = 0;
8573             int tal = 0;
8574             StringBuilder slbl = new StringBuilder(256);
8575             try
8576             {
8577                 foreach (var tab in _statuses.Tabs)
8578                 {
8579                     ur += tab.UnreadCount;
8580                     al += tab.AllCount;
8581                     if (tab.TabName == this.CurrentTabName)
8582                     {
8583                         tur = tab.UnreadCount;
8584                         tal = tab.AllCount;
8585                     }
8586                 }
8587             }
8588             catch (Exception)
8589             {
8590                 return "";
8591             }
8592
8593             UnreadCounter = ur;
8594             UnreadAtCounter = urat;
8595
8596             var homeTab = this._statuses.GetTabByType<HomeTabModel>();
8597
8598             slbl.AppendFormat(Properties.Resources.SetStatusLabelText1, tur, tal, ur, al, urat, _postTimestamps.Count, _favTimestamps.Count, homeTab.TweetsPerHour);
8599             if (SettingManager.Common.TimelinePeriod == 0)
8600             {
8601                 slbl.Append(Properties.Resources.SetStatusLabelText2);
8602             }
8603             else
8604             {
8605                 slbl.Append(SettingManager.Common.TimelinePeriod + Properties.Resources.SetStatusLabelText3);
8606             }
8607             return slbl.ToString();
8608         }
8609
8610         private async void TwitterApiStatus_AccessLimitUpdated(object sender, EventArgs e)
8611         {
8612             try
8613             {
8614                 if (this.InvokeRequired && !this.IsDisposed)
8615                 {
8616                     await this.InvokeAsync(() => this.TwitterApiStatus_AccessLimitUpdated(sender, e));
8617                 }
8618                 else
8619                 {
8620                     var endpointName = (e as TwitterApiStatus.AccessLimitUpdatedEventArgs).EndpointName;
8621                     SetApiStatusLabel(endpointName);
8622                 }
8623             }
8624             catch (ObjectDisposedException)
8625             {
8626                 return;
8627             }
8628             catch (InvalidOperationException)
8629             {
8630                 return;
8631             }
8632         }
8633
8634         private void SetApiStatusLabel(string endpointName = null)
8635         {
8636             var tabType = this.CurrentTab.TabType;
8637
8638             if (endpointName == null)
8639             {
8640                 // 表示中のタブに応じて更新
8641                 switch (tabType)
8642                 {
8643                     case MyCommon.TabUsageType.Home:
8644                     case MyCommon.TabUsageType.UserDefined:
8645                         endpointName = "/statuses/home_timeline";
8646                         break;
8647
8648                     case MyCommon.TabUsageType.Mentions:
8649                         endpointName = "/statuses/mentions_timeline";
8650                         break;
8651
8652                     case MyCommon.TabUsageType.Favorites:
8653                         endpointName = "/favorites/list";
8654                         break;
8655
8656                     case MyCommon.TabUsageType.DirectMessage:
8657                         endpointName = "/direct_messages/events/list";
8658                         break;
8659
8660                     case MyCommon.TabUsageType.UserTimeline:
8661                         endpointName = "/statuses/user_timeline";
8662                         break;
8663
8664                     case MyCommon.TabUsageType.Lists:
8665                         endpointName = "/lists/statuses";
8666                         break;
8667
8668                     case MyCommon.TabUsageType.PublicSearch:
8669                         endpointName = "/search/tweets";
8670                         break;
8671
8672                     case MyCommon.TabUsageType.Related:
8673                         endpointName = "/statuses/show/:id";
8674                         break;
8675
8676                     default:
8677                         break;
8678                 }
8679
8680                 this.toolStripApiGauge.ApiEndpoint = endpointName;
8681             }
8682             else
8683             {
8684                 // 表示中のタブに関連する endpoint であれば更新
8685                 var update = false;
8686
8687                 switch (endpointName)
8688                 {
8689                     case "/statuses/home_timeline":
8690                         update = tabType == MyCommon.TabUsageType.Home ||
8691                                  tabType == MyCommon.TabUsageType.UserDefined;
8692                         break;
8693
8694                     case "/statuses/mentions_timeline":
8695                         update = tabType == MyCommon.TabUsageType.Mentions;
8696                         break;
8697
8698                     case "/favorites/list":
8699                         update = tabType == MyCommon.TabUsageType.Favorites;
8700                         break;
8701
8702                     case "/direct_messages/events/list":
8703                         update = tabType == MyCommon.TabUsageType.DirectMessage;
8704                         break;
8705
8706                     case "/statuses/user_timeline":
8707                         update = tabType == MyCommon.TabUsageType.UserTimeline;
8708                         break;
8709
8710                     case "/lists/statuses":
8711                         update = tabType == MyCommon.TabUsageType.Lists;
8712                         break;
8713
8714                     case "/search/tweets":
8715                         update = tabType == MyCommon.TabUsageType.PublicSearch;
8716                         break;
8717
8718                     case "/statuses/show/:id":
8719                         update = tabType == MyCommon.TabUsageType.Related;
8720                         break;
8721
8722                     default:
8723                         break;
8724                 }
8725
8726                 if (update)
8727                 {
8728                     this.toolStripApiGauge.ApiEndpoint = endpointName;
8729                 }
8730             }
8731         }
8732
8733         private void SetStatusLabelUrl()
8734             => this.StatusLabelUrl.Text = this.GetStatusLabelText();
8735
8736         public void SetStatusLabel(string text)
8737             => this.StatusLabel.Text = text;
8738
8739         private void SetNotifyIconText()
8740         {
8741             var ur = new StringBuilder(64);
8742
8743             // タスクトレイアイコンのツールチップテキスト書き換え
8744             // Tween [未読/@]
8745             ur.Remove(0, ur.Length);
8746             if (SettingManager.Common.DispUsername)
8747             {
8748                 ur.Append(tw.Username);
8749                 ur.Append(" - ");
8750             }
8751             ur.Append(ApplicationSettings.ApplicationName);
8752 #if DEBUG
8753             ur.Append("(Debug Build)");
8754 #endif
8755             if (UnreadCounter != -1 && UnreadAtCounter != -1)
8756             {
8757                 ur.Append(" [");
8758                 ur.Append(UnreadCounter);
8759                 ur.Append("/@");
8760                 ur.Append(UnreadAtCounter);
8761                 ur.Append("]");
8762             }
8763             NotifyIcon1.Text = ur.ToString();
8764         }
8765
8766         internal void CheckReplyTo(string StatusText)
8767         {
8768             MatchCollection m;
8769             //ハッシュタグの保存
8770             m = Regex.Matches(StatusText, Twitter.HASHTAG, RegexOptions.IgnoreCase);
8771             string hstr = "";
8772             foreach (Match hm in m)
8773             {
8774                 if (!hstr.Contains("#" + hm.Result("$3") + " "))
8775                 {
8776                     hstr += "#" + hm.Result("$3") + " ";
8777                     HashSupl.AddItem("#" + hm.Result("$3"));
8778                 }
8779             }
8780             if (!string.IsNullOrEmpty(HashMgr.UseHash) && !hstr.Contains(HashMgr.UseHash + " "))
8781             {
8782                 hstr += HashMgr.UseHash;
8783             }
8784             if (!string.IsNullOrEmpty(hstr)) HashMgr.AddHashToHistory(hstr.Trim(), false);
8785
8786             // 本当にリプライ先指定すべきかどうかの判定
8787             m = Regex.Matches(StatusText, "(^|[ -/:-@[-^`{-~])(?<id>@[a-zA-Z0-9_]+)");
8788
8789             if (SettingManager.Common.UseAtIdSupplement)
8790             {
8791                 int bCnt = AtIdSupl.ItemCount;
8792                 foreach (Match mid in m)
8793                 {
8794                     AtIdSupl.AddItem(mid.Result("${id}"));
8795                 }
8796                 if (bCnt != AtIdSupl.ItemCount)
8797                     this.MarkSettingAtIdModified();
8798             }
8799
8800             // リプライ先ステータスIDの指定がない場合は指定しない
8801             if (this.inReplyTo == null)
8802                 return;
8803
8804             // 通常Reply
8805             // 次の条件を満たす場合に in_reply_to_status_id 指定
8806             // 1. Twitterによりリンクと判定される @idが文中に1つ含まれる (2009/5/28 リンク化される@IDのみカウントするように修正)
8807             // 2. リプライ先ステータスIDが設定されている(リストをダブルクリックで返信している)
8808             // 3. 文中に含まれた@idがリプライ先のポスト者のIDと一致する
8809
8810             if (m != null)
8811             {
8812                 var inReplyToScreenName = this.inReplyTo.Value.ScreenName;
8813                 if (StatusText.StartsWith("@", StringComparison.Ordinal))
8814                 {
8815                     if (StatusText.StartsWith("@" + inReplyToScreenName, StringComparison.Ordinal)) return;
8816                 }
8817                 else
8818                 {
8819                     foreach (Match mid in m)
8820                     {
8821                         if (StatusText.Contains("RT " + mid.Result("${id}") + ":") && mid.Result("${id}") == "@" + inReplyToScreenName) return;
8822                     }
8823                 }
8824             }
8825
8826             this.inReplyTo = null;
8827         }
8828
8829         private void TweenMain_Resize(object sender, EventArgs e)
8830         {
8831             if (!_initialLayout && SettingManager.Common.MinimizeToTray && WindowState == FormWindowState.Minimized)
8832             {
8833                 this.Visible = false;
8834             }
8835             if (_initialLayout && SettingManager.Local != null && this.WindowState == FormWindowState.Normal && this.Visible)
8836             {
8837                 // 現在の DPI と設定保存時の DPI との比を取得する
8838                 var configScaleFactor = SettingManager.Local.GetConfigScaleFactor(this.CurrentAutoScaleDimensions);
8839
8840                 this.ClientSize = ScaleBy(configScaleFactor, SettingManager.Local.FormSize);
8841
8842                 // Splitterの位置設定
8843                 var splitterDistance = ScaleBy(configScaleFactor.Height, SettingManager.Local.SplitterDistance);
8844                 if (splitterDistance > this.SplitContainer1.Panel1MinSize &&
8845                     splitterDistance < this.SplitContainer1.Height - this.SplitContainer1.Panel2MinSize - this.SplitContainer1.SplitterWidth)
8846                 {
8847                     this.SplitContainer1.SplitterDistance = splitterDistance;
8848                 }
8849
8850                 //発言欄複数行
8851                 StatusText.Multiline = SettingManager.Local.StatusMultiline;
8852                 if (StatusText.Multiline)
8853                 {
8854                     var statusTextHeight = ScaleBy(configScaleFactor.Height, SettingManager.Local.StatusTextHeight);
8855                     int dis = SplitContainer2.Height - statusTextHeight - SplitContainer2.SplitterWidth;
8856                     if (dis > SplitContainer2.Panel1MinSize && dis < SplitContainer2.Height - SplitContainer2.Panel2MinSize - SplitContainer2.SplitterWidth)
8857                     {
8858                         SplitContainer2.SplitterDistance = SplitContainer2.Height - statusTextHeight - SplitContainer2.SplitterWidth;
8859                     }
8860                     StatusText.Height = statusTextHeight;
8861                 }
8862                 else
8863                 {
8864                     if (SplitContainer2.Height - SplitContainer2.Panel2MinSize - SplitContainer2.SplitterWidth > 0)
8865                     {
8866                         SplitContainer2.SplitterDistance = SplitContainer2.Height - SplitContainer2.Panel2MinSize - SplitContainer2.SplitterWidth;
8867                     }
8868                 }
8869
8870                 var previewDistance = ScaleBy(configScaleFactor.Width, SettingManager.Local.PreviewDistance);
8871                 if (previewDistance > this.SplitContainer3.Panel1MinSize && previewDistance < this.SplitContainer3.Width - this.SplitContainer3.Panel2MinSize - this.SplitContainer3.SplitterWidth)
8872                 {
8873                     this.SplitContainer3.SplitterDistance = previewDistance;
8874                 }
8875
8876                 // Panel2Collapsed は SplitterDistance の設定を終えるまで true にしない
8877                 this.SplitContainer3.Panel2Collapsed = true;
8878
8879                 _initialLayout = false;
8880             }
8881             if (this.WindowState != FormWindowState.Minimized)
8882             {
8883                 _formWindowState = this.WindowState;
8884             }
8885         }
8886
8887         private void PlaySoundMenuItem_CheckedChanged(object sender, EventArgs e)
8888         {
8889             PlaySoundMenuItem.Checked = ((ToolStripMenuItem)sender).Checked;
8890             this.PlaySoundFileMenuItem.Checked = PlaySoundMenuItem.Checked;
8891             if (PlaySoundMenuItem.Checked)
8892             {
8893                 SettingManager.Common.PlaySound = true;
8894             }
8895             else
8896             {
8897                 SettingManager.Common.PlaySound = false;
8898             }
8899             this.MarkSettingCommonModified();
8900         }
8901
8902         private void SplitContainer1_SplitterMoved(object sender, SplitterEventArgs e)
8903         {
8904             if (this._initialLayout)
8905                 return;
8906
8907             int splitterDistance;
8908             switch (this.WindowState)
8909             {
8910                 case FormWindowState.Normal:
8911                     splitterDistance = this.SplitContainer1.SplitterDistance;
8912                     break;
8913                 case FormWindowState.Maximized:
8914                     // 最大化時は、通常時のウィンドウサイズに換算した SplitterDistance を算出する
8915                     var normalContainerHeight = this._mySize.Height - this.ToolStripContainer1.TopToolStripPanel.Height - this.ToolStripContainer1.BottomToolStripPanel.Height;
8916                     splitterDistance = this.SplitContainer1.SplitterDistance - (this.SplitContainer1.Height - normalContainerHeight);
8917                     splitterDistance = Math.Min(splitterDistance, normalContainerHeight - this.SplitContainer1.SplitterWidth - this.SplitContainer1.Panel2MinSize);
8918                     break;
8919                 default:
8920                     return;
8921             }
8922
8923             this._mySpDis = splitterDistance;
8924             this.MarkSettingLocalModified();
8925         }
8926
8927         private async Task doRepliedStatusOpen()
8928         {
8929             var currentPost = this.CurrentPost;
8930             if (this.ExistCurrentPost && currentPost.InReplyToUser != null && currentPost.InReplyToStatusId != null)
8931             {
8932                 if (MyCommon.IsKeyDown(Keys.Shift))
8933                 {
8934                     await this.OpenUriInBrowserAsync(MyCommon.GetStatusUrl(currentPost.InReplyToUser, currentPost.InReplyToStatusId.Value));
8935                     return;
8936                 }
8937                 if (_statuses.ContainsKey(currentPost.InReplyToStatusId.Value))
8938                 {
8939                     PostClass repPost = _statuses[currentPost.InReplyToStatusId.Value];
8940                     MessageBox.Show($"{repPost.ScreenName} / {repPost.Nickname}   ({repPost.CreatedAt.ToLocalTimeString()})" + Environment.NewLine + repPost.TextFromApi);
8941                 }
8942                 else
8943                 {
8944                     foreach (TabModel tb in _statuses.GetTabsByType(MyCommon.TabUsageType.Lists | MyCommon.TabUsageType.PublicSearch))
8945                     {
8946                         if (tb == null || !tb.Contains(currentPost.InReplyToStatusId.Value)) break;
8947                         PostClass repPost = _statuses[currentPost.InReplyToStatusId.Value];
8948                         MessageBox.Show($"{repPost.ScreenName} / {repPost.Nickname}   ({repPost.CreatedAt.ToLocalTimeString()})" + Environment.NewLine + repPost.TextFromApi);
8949                         return;
8950                     }
8951                     await this.OpenUriInBrowserAsync(MyCommon.GetStatusUrl(currentPost.InReplyToUser, currentPost.InReplyToStatusId.Value));
8952                 }
8953             }
8954         }
8955
8956         private async void RepliedStatusOpenMenuItem_Click(object sender, EventArgs e)
8957             => await this.doRepliedStatusOpen();
8958
8959         private void SplitContainer2_Panel2_Resize(object sender, EventArgs e)
8960         {
8961             if (this._initialLayout)
8962                 return; // SettingLocal の反映が完了するまで multiline の判定を行わない
8963
8964             var multiline = this.SplitContainer2.Panel2.Height > this.SplitContainer2.Panel2MinSize + 2;
8965             if (multiline != this.StatusText.Multiline)
8966             {
8967                 this.StatusText.Multiline = multiline;
8968                 SettingManager.Local.StatusMultiline = multiline;
8969                 this.MarkSettingLocalModified();
8970             }
8971         }
8972
8973         private void StatusText_MultilineChanged(object sender, EventArgs e)
8974         {
8975             if (this.StatusText.Multiline)
8976                 this.StatusText.ScrollBars = ScrollBars.Vertical;
8977             else
8978                 this.StatusText.ScrollBars = ScrollBars.None;
8979
8980             if (!this._initialLayout)
8981                 this.MarkSettingLocalModified();
8982         }
8983
8984         private void MultiLineMenuItem_Click(object sender, EventArgs e)
8985         {
8986             //発言欄複数行
8987             var menuItemChecked = ((ToolStripMenuItem)sender).Checked;
8988             StatusText.Multiline = menuItemChecked;
8989             SettingManager.Local.StatusMultiline = menuItemChecked;
8990             if (menuItemChecked)
8991             {
8992                 if (SplitContainer2.Height - _mySpDis2 - SplitContainer2.SplitterWidth < 0)
8993                     SplitContainer2.SplitterDistance = 0;
8994                 else
8995                     SplitContainer2.SplitterDistance = SplitContainer2.Height - _mySpDis2 - SplitContainer2.SplitterWidth;
8996             }
8997             else
8998             {
8999                 SplitContainer2.SplitterDistance = SplitContainer2.Height - SplitContainer2.Panel2MinSize - SplitContainer2.SplitterWidth;
9000             }
9001             this.MarkSettingLocalModified();
9002         }
9003
9004         private async Task<bool> UrlConvertAsync(MyCommon.UrlConverter Converter_Type)
9005         {
9006             if (Converter_Type == MyCommon.UrlConverter.Bitly || Converter_Type == MyCommon.UrlConverter.Jmp)
9007             {
9008                 // OAuth2 アクセストークンまたは API キー (旧方式) のいずれも設定されていなければ短縮しない
9009                 if (string.IsNullOrEmpty(SettingManager.Common.BitlyAccessToken) &&
9010                     (string.IsNullOrEmpty(SettingManager.Common.BilyUser) || string.IsNullOrEmpty(SettingManager.Common.BitlyPwd)))
9011                 {
9012                     MessageBox.Show(this, Properties.Resources.UrlConvert_BitlyAuthRequired, ApplicationSettings.ApplicationName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
9013                     return false;
9014                 }
9015             }
9016
9017             //t.coで投稿時自動短縮する場合は、外部サービスでの短縮禁止
9018             //if (SettingDialog.UrlConvertAuto && SettingDialog.ShortenTco) return;
9019
9020             //Converter_Type=Nicomsの場合は、nicovideoのみ短縮する
9021             //参考資料 RFC3986 Uniform Resource Identifier (URI): Generic Syntax
9022             //Appendix A.  Collected ABNF for URI
9023             //http://www.ietf.org/rfc/rfc3986.txt
9024
9025             string result = "";
9026
9027             const string nico = @"^https?://[a-z]+\.(nicovideo|niconicommons|nicolive)\.jp/[a-z]+/[a-z0-9]+$";
9028
9029             if (StatusText.SelectionLength > 0)
9030             {
9031                 string tmp = StatusText.SelectedText;
9032                 // httpから始まらない場合、ExcludeStringで指定された文字列で始まる場合は対象としない
9033                 if (tmp.StartsWith("http", StringComparison.OrdinalIgnoreCase))
9034                 {
9035                     // 文字列が選択されている場合はその文字列について処理
9036
9037                     //nico.ms使用、nicovideoにマッチしたら変換
9038                     if (SettingManager.Common.Nicoms && Regex.IsMatch(tmp, nico))
9039                     {
9040                         result = nicoms.Shorten(tmp);
9041                     }
9042                     else if (Converter_Type != MyCommon.UrlConverter.Nicoms)
9043                     {
9044                         // 短縮URL変換
9045                         try
9046                         {
9047                             var srcUri = new Uri(tmp);
9048                             var resultUri = await ShortUrl.Instance.ShortenUrlAsync(Converter_Type, srcUri);
9049                             result = resultUri.AbsoluteUri;
9050                         }
9051                         catch (WebApiException e)
9052                         {
9053                             this.StatusLabel.Text = Converter_Type + ":" + e.Message;
9054                             return false;
9055                         }
9056                         catch (UriFormatException e)
9057                         {
9058                             this.StatusLabel.Text = Converter_Type + ":" + e.Message;
9059                             return false;
9060                         }
9061                     }
9062                     else
9063                     {
9064                         return true;
9065                     }
9066
9067                     if (!string.IsNullOrEmpty(result))
9068                     {
9069                         urlUndo undotmp = new urlUndo();
9070
9071                         // 短縮 URL が生成されるまでの間に投稿欄から元の URL が削除されていたら中断する
9072                         var origUrlIndex = this.StatusText.Text.IndexOf(tmp, StringComparison.Ordinal);
9073                         if (origUrlIndex == -1)
9074                             return false;
9075
9076                         StatusText.Select(origUrlIndex, tmp.Length);
9077                         StatusText.SelectedText = result;
9078
9079                         //undoバッファにセット
9080                         undotmp.Before = tmp;
9081                         undotmp.After = result;
9082
9083                         if (urlUndoBuffer == null)
9084                         {
9085                             urlUndoBuffer = new List<urlUndo>();
9086                             UrlUndoToolStripMenuItem.Enabled = true;
9087                         }
9088
9089                         urlUndoBuffer.Add(undotmp);
9090                     }
9091                 }
9092             }
9093             else
9094             {
9095                 const string url = @"(?<before>(?:[^\""':!=]|^|\:))" +
9096                                    @"(?<url>(?<protocol>https?://)" +
9097                                    @"(?<domain>(?:[\.-]|[^\p{P}\s])+\.[a-z]{2,}(?::[0-9]+)?)" +
9098                                    @"(?<path>/[a-z0-9!*//();:&=+$/%#\-_.,~@]*[a-z0-9)=#/]?)?" +
9099                                    @"(?<query>\?[a-z0-9!*//();:&=+$/%#\-_.,~@?]*[a-z0-9_&=#/])?)";
9100                 // 正規表現にマッチしたURL文字列をtinyurl化
9101                 foreach (Match mt in Regex.Matches(StatusText.Text, url, RegexOptions.IgnoreCase))
9102                 {
9103                     if (StatusText.Text.IndexOf(mt.Result("${url}"), StringComparison.Ordinal) == -1) continue;
9104                     string tmp = mt.Result("${url}");
9105                     if (tmp.StartsWith("w", StringComparison.OrdinalIgnoreCase)) tmp = "http://" + tmp;
9106                     urlUndo undotmp = new urlUndo();
9107
9108                     //選んだURLを選択(?)
9109                     StatusText.Select(StatusText.Text.IndexOf(mt.Result("${url}"), StringComparison.Ordinal), mt.Result("${url}").Length);
9110
9111                     //nico.ms使用、nicovideoにマッチしたら変換
9112                     if (SettingManager.Common.Nicoms && Regex.IsMatch(tmp, nico))
9113                     {
9114                         result = nicoms.Shorten(tmp);
9115                     }
9116                     else if (Converter_Type != MyCommon.UrlConverter.Nicoms)
9117                     {
9118                         // 短縮URL変換
9119                         try
9120                         {
9121                             var srcUri = new Uri(tmp);
9122                             var resultUri = await ShortUrl.Instance.ShortenUrlAsync(Converter_Type, srcUri);
9123                             result = resultUri.AbsoluteUri;
9124                         }
9125                         catch (HttpRequestException e)
9126                         {
9127                             // 例外のメッセージが「Response status code does not indicate success: 500 (Internal Server Error).」
9128                             // のように長いので「:」が含まれていればそれ以降のみを抽出する
9129                             var message = e.Message.Split(new[] { ':' }, count: 2).Last();
9130
9131                             this.StatusLabel.Text = Converter_Type + ":" + message;
9132                             continue;
9133                         }
9134                         catch (WebApiException e)
9135                         {
9136                             this.StatusLabel.Text = Converter_Type + ":" + e.Message;
9137                             continue;
9138                         }
9139                         catch (UriFormatException e)
9140                         {
9141                             this.StatusLabel.Text = Converter_Type + ":" + e.Message;
9142                             continue;
9143                         }
9144                     }
9145                     else
9146                     {
9147                         continue;
9148                     }
9149
9150                     if (!string.IsNullOrEmpty(result))
9151                     {
9152                         // 短縮 URL が生成されるまでの間に投稿欄から元の URL が削除されていたら中断する
9153                         var origUrlIndex = this.StatusText.Text.IndexOf(mt.Result("${url}"), StringComparison.Ordinal);
9154                         if (origUrlIndex == -1)
9155                             return false;
9156
9157                         StatusText.Select(origUrlIndex, mt.Result("${url}").Length);
9158                         StatusText.SelectedText = result;
9159                         //undoバッファにセット
9160                         undotmp.Before = mt.Result("${url}");
9161                         undotmp.After = result;
9162
9163                         if (urlUndoBuffer == null)
9164                         {
9165                             urlUndoBuffer = new List<urlUndo>();
9166                             UrlUndoToolStripMenuItem.Enabled = true;
9167                         }
9168
9169                         urlUndoBuffer.Add(undotmp);
9170                     }
9171                 }
9172             }
9173
9174             return true;
9175         }
9176
9177         private void doUrlUndo()
9178         {
9179             if (urlUndoBuffer != null)
9180             {
9181                 string tmp = StatusText.Text;
9182                 foreach (urlUndo data in urlUndoBuffer)
9183                 {
9184                     tmp = tmp.Replace(data.After, data.Before);
9185                 }
9186                 StatusText.Text = tmp;
9187                 urlUndoBuffer = null;
9188                 UrlUndoToolStripMenuItem.Enabled = false;
9189                 StatusText.SelectionStart = 0;
9190                 StatusText.SelectionLength = 0;
9191             }
9192         }
9193
9194         private async void TinyURLToolStripMenuItem_Click(object sender, EventArgs e)
9195             => await this.UrlConvertAsync(MyCommon.UrlConverter.TinyUrl);
9196
9197         private async void IsgdToolStripMenuItem_Click(object sender, EventArgs e)
9198             => await this.UrlConvertAsync(MyCommon.UrlConverter.Isgd);
9199
9200         private async void UxnuMenuItem_Click(object sender, EventArgs e)
9201             => await this.UrlConvertAsync(MyCommon.UrlConverter.Uxnu);
9202
9203         private async void UrlConvertAutoToolStripMenuItem_Click(object sender, EventArgs e)
9204         {
9205             if (!await UrlConvertAsync(SettingManager.Common.AutoShortUrlFirst))
9206             {
9207                 MyCommon.UrlConverter svc = SettingManager.Common.AutoShortUrlFirst;
9208                 Random rnd = new Random();
9209                 // 前回使用した短縮URLサービス以外を選択する
9210                 do
9211                 {
9212                     svc = (MyCommon.UrlConverter)rnd.Next(System.Enum.GetNames(typeof(MyCommon.UrlConverter)).Length);
9213                 }
9214                 while (svc == SettingManager.Common.AutoShortUrlFirst || svc == MyCommon.UrlConverter.Nicoms || svc == MyCommon.UrlConverter.Unu);
9215                 await UrlConvertAsync(svc);
9216             }
9217         }
9218
9219         private void UrlUndoToolStripMenuItem_Click(object sender, EventArgs e)
9220             => this.doUrlUndo();
9221
9222         private void NewPostPopMenuItem_CheckStateChanged(object sender, EventArgs e)
9223         {
9224             this.NotifyFileMenuItem.Checked = ((ToolStripMenuItem)sender).Checked;
9225             this.NewPostPopMenuItem.Checked = this.NotifyFileMenuItem.Checked;
9226             SettingManager.Common.NewAllPop = NewPostPopMenuItem.Checked;
9227             this.MarkSettingCommonModified();
9228         }
9229
9230         private void ListLockMenuItem_CheckStateChanged(object sender, EventArgs e)
9231         {
9232             ListLockMenuItem.Checked = ((ToolStripMenuItem)sender).Checked;
9233             this.LockListFileMenuItem.Checked = ListLockMenuItem.Checked;
9234             SettingManager.Common.ListLock = ListLockMenuItem.Checked;
9235             this.MarkSettingCommonModified();
9236         }
9237
9238         private void MenuStrip1_MenuActivate(object sender, EventArgs e)
9239         {
9240             // フォーカスがメニューに移る (MenuStrip1.Tag フラグを立てる)
9241             MenuStrip1.Tag = new Object();
9242             MenuStrip1.Select(); // StatusText がフォーカスを持っている場合 Leave が発生
9243         }
9244
9245         private void MenuStrip1_MenuDeactivate(object sender, EventArgs e)
9246         {
9247             var currentTabPage = this.CurrentTabPage;
9248             if (this.Tag != null) // 設定された戻り先へ遷移
9249             {
9250                 if (this.Tag == currentTabPage)
9251                     ((Control)currentTabPage.Tag).Select();
9252                 else
9253                     ((Control)this.Tag).Select();
9254             }
9255             else // 戻り先が指定されていない (初期状態) 場合はタブに遷移
9256             {
9257                 this.Tag = currentTabPage.Tag;
9258                 ((Control)this.Tag).Select();
9259             }
9260             // フォーカスがメニューに遷移したかどうかを表すフラグを降ろす
9261             MenuStrip1.Tag = null;
9262         }
9263
9264         private void MyList_ColumnReordered(object sender, ColumnReorderedEventArgs e)
9265         {
9266             DetailsListView lst = (DetailsListView)sender;
9267             if (SettingManager.Local == null) return;
9268
9269             if (_iconCol)
9270             {
9271                 SettingManager.Local.Width1 = lst.Columns[0].Width;
9272                 SettingManager.Local.Width3 = lst.Columns[1].Width;
9273             }
9274             else
9275             {
9276                 int[] darr = new int[lst.Columns.Count];
9277                 for (int i = 0; i < lst.Columns.Count; i++)
9278                 {
9279                     darr[lst.Columns[i].DisplayIndex] = i;
9280                 }
9281                 MyCommon.MoveArrayItem(darr, e.OldDisplayIndex, e.NewDisplayIndex);
9282
9283                 for (int i = 0; i < lst.Columns.Count; i++)
9284                 {
9285                     switch (darr[i])
9286                     {
9287                         case 0:
9288                             SettingManager.Local.DisplayIndex1 = i;
9289                             break;
9290                         case 1:
9291                             SettingManager.Local.DisplayIndex2 = i;
9292                             break;
9293                         case 2:
9294                             SettingManager.Local.DisplayIndex3 = i;
9295                             break;
9296                         case 3:
9297                             SettingManager.Local.DisplayIndex4 = i;
9298                             break;
9299                         case 4:
9300                             SettingManager.Local.DisplayIndex5 = i;
9301                             break;
9302                         case 5:
9303                             SettingManager.Local.DisplayIndex6 = i;
9304                             break;
9305                         case 6:
9306                             SettingManager.Local.DisplayIndex7 = i;
9307                             break;
9308                         case 7:
9309                             SettingManager.Local.DisplayIndex8 = i;
9310                             break;
9311                     }
9312                 }
9313                 SettingManager.Local.Width1 = lst.Columns[0].Width;
9314                 SettingManager.Local.Width2 = lst.Columns[1].Width;
9315                 SettingManager.Local.Width3 = lst.Columns[2].Width;
9316                 SettingManager.Local.Width4 = lst.Columns[3].Width;
9317                 SettingManager.Local.Width5 = lst.Columns[4].Width;
9318                 SettingManager.Local.Width6 = lst.Columns[5].Width;
9319                 SettingManager.Local.Width7 = lst.Columns[6].Width;
9320                 SettingManager.Local.Width8 = lst.Columns[7].Width;
9321             }
9322             this.MarkSettingLocalModified();
9323             _isColumnChanged = true;
9324         }
9325
9326         private void MyList_ColumnWidthChanged(object sender, ColumnWidthChangedEventArgs e)
9327         {
9328             DetailsListView lst = (DetailsListView)sender;
9329             if (SettingManager.Local == null) return;
9330
9331             var modified = false;
9332             if (_iconCol)
9333             {
9334                 if (SettingManager.Local.Width1 != lst.Columns[0].Width)
9335                 {
9336                     SettingManager.Local.Width1 = lst.Columns[0].Width;
9337                     modified = true;
9338                 }
9339                 if (SettingManager.Local.Width3 != lst.Columns[1].Width)
9340                 {
9341                     SettingManager.Local.Width3 = lst.Columns[1].Width;
9342                     modified = true;
9343                 }
9344             }
9345             else
9346             {
9347                 if (SettingManager.Local.Width1 != lst.Columns[0].Width)
9348                 {
9349                     SettingManager.Local.Width1 = lst.Columns[0].Width;
9350                     modified = true;
9351                 }
9352                 if (SettingManager.Local.Width2 != lst.Columns[1].Width)
9353                 {
9354                     SettingManager.Local.Width2 = lst.Columns[1].Width;
9355                     modified = true;
9356                 }
9357                 if (SettingManager.Local.Width3 != lst.Columns[2].Width)
9358                 {
9359                     SettingManager.Local.Width3 = lst.Columns[2].Width;
9360                     modified = true;
9361                 }
9362                 if (SettingManager.Local.Width4 != lst.Columns[3].Width)
9363                 {
9364                     SettingManager.Local.Width4 = lst.Columns[3].Width;
9365                     modified = true;
9366                 }
9367                 if (SettingManager.Local.Width5 != lst.Columns[4].Width)
9368                 {
9369                     SettingManager.Local.Width5 = lst.Columns[4].Width;
9370                     modified = true;
9371                 }
9372                 if (SettingManager.Local.Width6 != lst.Columns[5].Width)
9373                 {
9374                     SettingManager.Local.Width6 = lst.Columns[5].Width;
9375                     modified = true;
9376                 }
9377                 if (SettingManager.Local.Width7 != lst.Columns[6].Width)
9378                 {
9379                     SettingManager.Local.Width7 = lst.Columns[6].Width;
9380                     modified = true;
9381                 }
9382                 if (SettingManager.Local.Width8 != lst.Columns[7].Width)
9383                 {
9384                     SettingManager.Local.Width8 = lst.Columns[7].Width;
9385                     modified = true;
9386                 }
9387             }
9388             if (modified)
9389             {
9390                 this.MarkSettingLocalModified();
9391                 this._isColumnChanged = true;
9392             }
9393             // 非表示の時にColumnChangedが呼ばれた場合はForm初期化処理中なので保存しない
9394             //if (changed)
9395             //{
9396             //    SaveConfigsLocal();
9397             //}
9398         }
9399
9400         private void SplitContainer2_SplitterMoved(object sender, SplitterEventArgs e)
9401         {
9402             if (StatusText.Multiline) _mySpDis2 = StatusText.Height;
9403             this.MarkSettingLocalModified();
9404         }
9405
9406         private void TweenMain_DragDrop(object sender, DragEventArgs e)
9407         {
9408             if (e.Data.GetDataPresent(DataFormats.FileDrop))
9409             {
9410                 if (!e.Data.GetDataPresent(DataFormats.Html, false))  // WebBrowserコントロールからの絵文字画像Drag&Dropは弾く
9411                 {
9412                     SelectMedia_DragDrop(e);
9413                 }
9414             }
9415             else if (e.Data.GetDataPresent("UniformResourceLocatorW"))
9416             {
9417                 var (url, title) = GetUrlFromDataObject(e.Data);
9418
9419                 string appendText;
9420                 if (title == null)
9421                     appendText = url;
9422                 else
9423                     appendText = title + " " + url;
9424
9425                 if (this.StatusText.TextLength == 0)
9426                     this.StatusText.Text = appendText;
9427                 else
9428                     this.StatusText.Text += " " + appendText;
9429             }
9430             else if (e.Data.GetDataPresent(DataFormats.UnicodeText))
9431             {
9432                 var text = (string)e.Data.GetData(DataFormats.UnicodeText);
9433                 if (text != null)
9434                     this.StatusText.Text += text;
9435             }
9436             else if (e.Data.GetDataPresent(DataFormats.StringFormat))
9437             {
9438                 string data = (string)e.Data.GetData(DataFormats.StringFormat, true);
9439                 if (data != null) StatusText.Text += data;
9440             }
9441         }
9442
9443         /// <summary>
9444         /// IDataObject から URL とタイトルの対を取得します
9445         /// </summary>
9446         /// <remarks>
9447         /// タイトルのみ取得できなかった場合は Value2 が null のタプルを返すことがあります。
9448         /// </remarks>
9449         /// <exception cref="ArgumentException">不正なフォーマットが入力された場合</exception>
9450         /// <exception cref="NotSupportedException">サポートされていないデータが入力された場合</exception>
9451         internal static (string Url, string Title) GetUrlFromDataObject(IDataObject data)
9452         {
9453             if (data.GetDataPresent("text/x-moz-url"))
9454             {
9455                 // Firefox, Google Chrome で利用可能
9456                 // 参照: https://developer.mozilla.org/ja/docs/DragDrop/Recommended_Drag_Types
9457
9458                 using (var stream = (MemoryStream)data.GetData("text/x-moz-url"))
9459                 {
9460                     var lines = Encoding.Unicode.GetString(stream.ToArray()).TrimEnd('\0').Split('\n');
9461                     if (lines.Length < 2)
9462                         throw new ArgumentException("不正な text/x-moz-url フォーマットです", nameof(data));
9463
9464                     return (lines[0], lines[1]);
9465                 }
9466             }
9467             else if (data.GetDataPresent("IESiteModeToUrl"))
9468             {
9469                 // Internet Exproler 用
9470                 // 保護モードが有効なデフォルトの IE では DragDrop イベントが発火しないため使えない
9471
9472                 using (var stream = (MemoryStream)data.GetData("IESiteModeToUrl"))
9473                 {
9474                     var lines = Encoding.Unicode.GetString(stream.ToArray()).TrimEnd('\0').Split('\0');
9475                     if (lines.Length < 2)
9476                         throw new ArgumentException("不正な IESiteModeToUrl フォーマットです", nameof(data));
9477
9478                     return (lines[0], lines[1]);
9479                 }
9480             }
9481             else if (data.GetDataPresent("UniformResourceLocatorW"))
9482             {
9483                 // それ以外のブラウザ向け
9484
9485                 using (var stream = (MemoryStream)data.GetData("UniformResourceLocatorW"))
9486                 {
9487                     var url = Encoding.Unicode.GetString(stream.ToArray()).TrimEnd('\0');
9488                     return (url, null);
9489                 }
9490             }
9491
9492             throw new NotSupportedException("サポートされていないデータ形式です: " + data.GetFormats()[0]);
9493         }
9494
9495         private void TweenMain_DragEnter(object sender, DragEventArgs e)
9496         {
9497             if (e.Data.GetDataPresent(DataFormats.FileDrop))
9498             {
9499                 if (!e.Data.GetDataPresent(DataFormats.Html, false))  // WebBrowserコントロールからの絵文字画像Drag&Dropは弾く
9500                 {
9501                     SelectMedia_DragEnter(e);
9502                     return;
9503                 }
9504             }
9505             else if (e.Data.GetDataPresent("UniformResourceLocatorW"))
9506             {
9507                 e.Effect = DragDropEffects.Copy;
9508                 return;
9509             }
9510             else if (e.Data.GetDataPresent(DataFormats.UnicodeText))
9511             {
9512                 e.Effect = DragDropEffects.Copy;
9513                 return;
9514             }
9515             else if (e.Data.GetDataPresent(DataFormats.StringFormat))
9516             {
9517                 e.Effect = DragDropEffects.Copy;
9518                 return;
9519             }
9520
9521             e.Effect = DragDropEffects.None;
9522         }
9523
9524         private void TweenMain_DragOver(object sender, DragEventArgs e)
9525         {
9526         }
9527
9528         public bool IsNetworkAvailable()
9529         {
9530             bool nw = true;
9531             nw = MyCommon.IsNetworkAvailable();
9532             _myStatusOnline = nw;
9533             return nw;
9534         }
9535
9536         public async Task OpenUriAsync(Uri uri, bool isReverseSettings = false)
9537         {
9538             var uriStr = uri.AbsoluteUri;
9539
9540             // OpenTween 内部で使用する URL
9541             if (uri.Authority == "opentween")
9542             {
9543                 await this.OpenInternalUriAsync(uri);
9544                 return;
9545             }
9546
9547             // ハッシュタグを含む Twitter 検索
9548             if (uri.Host == "twitter.com" && uri.AbsolutePath == "/search" && uri.Query.Contains("q=%23"))
9549             {
9550                 // ハッシュタグの場合は、タブで開く
9551                 var unescapedQuery = Uri.UnescapeDataString(uri.Query);
9552                 var pos = unescapedQuery.IndexOf('#');
9553                 if (pos == -1) return;
9554
9555                 var hash = unescapedQuery.Substring(pos);
9556                 this.HashSupl.AddItem(hash);
9557                 this.HashMgr.AddHashToHistory(hash.Trim(), false);
9558                 this.AddNewTabForSearch(hash);
9559                 return;
9560             }
9561
9562             // ユーザープロフィールURL
9563             // フラグが立っている場合は設定と逆の動作をする
9564             if( SettingManager.Common.OpenUserTimeline && !isReverseSettings ||
9565                 !SettingManager.Common.OpenUserTimeline && isReverseSettings )
9566             {
9567                 var userUriMatch = Regex.Match(uriStr, "^https?://twitter.com/(#!/)?(?<ScreenName>[a-zA-Z0-9_]+)$");
9568                 if (userUriMatch.Success)
9569                 {
9570                     var screenName = userUriMatch.Groups["ScreenName"].Value;
9571                     if (this.IsTwitterId(screenName))
9572                     {
9573                         await this.AddNewTabForUserTimeline(screenName);
9574                         return;
9575                     }
9576                 }
9577             }
9578
9579             // どのパターンにも該当しないURL
9580             await this.OpenUriInBrowserAsync(uriStr);
9581         }
9582
9583         /// <summary>
9584         /// OpenTween 内部の機能を呼び出すための URL を開きます
9585         /// </summary>
9586         private async Task OpenInternalUriAsync(Uri uri)
9587         {
9588             // ツイートを開く (//opentween/status/:status_id)
9589             var match = Regex.Match(uri.AbsolutePath, @"^/status/(\d+)$");
9590             if (match.Success)
9591             {
9592                 var statusId = long.Parse(match.Groups[1].Value);
9593                 await this.OpenRelatedTab(statusId);
9594                 return;
9595             }
9596         }
9597
9598         public Task OpenUriInBrowserAsync(string UriString)
9599         {
9600             return Task.Run(() =>
9601             {
9602                 string myPath = UriString;
9603
9604                 try
9605                 {
9606                     var configBrowserPath = SettingManager.Local.BrowserPath;
9607                     if (!string.IsNullOrEmpty(configBrowserPath))
9608                     {
9609                         if (configBrowserPath.StartsWith("\"", StringComparison.Ordinal) && configBrowserPath.Length > 2 && configBrowserPath.IndexOf("\"", 2, StringComparison.Ordinal) > -1)
9610                         {
9611                             int sep = configBrowserPath.IndexOf("\"", 2, StringComparison.Ordinal);
9612                             string browserPath = configBrowserPath.Substring(1, sep - 1);
9613                             string arg = "";
9614                             if (sep < configBrowserPath.Length - 1)
9615                             {
9616                                 arg = configBrowserPath.Substring(sep + 1);
9617                             }
9618                             myPath = arg + " " + myPath;
9619                             System.Diagnostics.Process.Start(browserPath, myPath);
9620                         }
9621                         else
9622                         {
9623                             System.Diagnostics.Process.Start(configBrowserPath, myPath);
9624                         }
9625                     }
9626                     else
9627                     {
9628                         System.Diagnostics.Process.Start(myPath);
9629                     }
9630                 }
9631                 catch (Exception)
9632                 {
9633                     //MessageBox.Show("ブラウザの起動に失敗、またはタイムアウトしました。" + ex.ToString());
9634                 }
9635             });
9636         }
9637
9638         private void ListTabSelect(TabPage _tab)
9639         {
9640             SetListProperty();
9641
9642             this.PurgeListViewItemCache();
9643
9644             this._statuses.SelectTab(_tab.Text);
9645
9646             var listView = this.CurrentListView;
9647
9648             _anchorPost = null;
9649             _anchorFlag = false;
9650
9651             if (_iconCol)
9652             {
9653                 listView.Columns[1].Text = ColumnText[2];
9654             }
9655             else
9656             {
9657                 for (int i = 0; i < listView.Columns.Count; i++)
9658                 {
9659                     listView.Columns[i].Text = ColumnText[i];
9660                 }
9661             }
9662         }
9663
9664         private void ListTab_Selecting(object sender, TabControlCancelEventArgs e)
9665             => this.ListTabSelect(e.TabPage);
9666
9667         private void SelectListItem(DetailsListView LView, int Index)
9668         {
9669             //単一
9670             Rectangle bnd = new Rectangle();
9671             bool flg = false;
9672             var item = LView.FocusedItem;
9673             if (item != null)
9674             {
9675                 bnd = item.Bounds;
9676                 flg = true;
9677             }
9678
9679             do
9680             {
9681                 LView.SelectedIndices.Clear();
9682             }
9683             while (LView.SelectedIndices.Count > 0);
9684             item = LView.Items[Index];
9685             item.Selected = true;
9686             item.Focused = true;
9687
9688             if (flg) LView.Invalidate(bnd);
9689         }
9690
9691         private void SelectListItem(DetailsListView LView , int[] Index, int focusedIndex, int selectionMarkIndex)
9692         {
9693             //複数
9694             Rectangle bnd = new Rectangle();
9695             bool flg = false;
9696             var item = LView.FocusedItem;
9697             if (item != null)
9698             {
9699                 bnd = item.Bounds;
9700                 flg = true;
9701             }
9702
9703             if (Index != null)
9704             {
9705                 do
9706                 {
9707                     LView.SelectedIndices.Clear();
9708                 }
9709                 while (LView.SelectedIndices.Count > 0);
9710                 LView.SelectItems(Index);
9711             }
9712             if (selectionMarkIndex > -1 && LView.VirtualListSize > selectionMarkIndex)
9713             {
9714                 LView.SelectionMark = selectionMarkIndex;
9715             }
9716             if (focusedIndex > -1 && LView.VirtualListSize > focusedIndex)
9717             {
9718                 LView.Items[focusedIndex].Focused = true;
9719             }
9720             else if (Index != null && Index.Length != 0)
9721             {
9722                 LView.Items[Index.Last()].Focused = true;
9723             }
9724
9725             if (flg) LView.Invalidate(bnd);
9726         }
9727
9728         private void StartUserStream()
9729         {
9730             tw.NewPostFromStream += tw_NewPostFromStream;
9731             tw.UserStreamStarted += tw_UserStreamStarted;
9732             tw.UserStreamStopped += tw_UserStreamStopped;
9733             tw.PostDeleted += tw_PostDeleted;
9734             tw.UserStreamEventReceived += tw_UserStreamEventArrived;
9735
9736             this.RefreshUserStreamsMenu();
9737
9738             if (SettingManager.Common.UserstreamStartup)
9739                 tw.StartUserStream();
9740         }
9741
9742         private async void TweenMain_Shown(object sender, EventArgs e)
9743         {
9744             NotifyIcon1.Visible = true;
9745
9746             if (this.IsNetworkAvailable())
9747             {
9748                 StartUserStream();
9749
9750                 var loadTasks = new List<Task>
9751                 {
9752                     this.RefreshMuteUserIdsAsync(),
9753                     this.RefreshBlockIdsAsync(),
9754                     this.RefreshNoRetweetIdsAsync(),
9755                     this.RefreshTwitterConfigurationAsync(),
9756                     this.RefreshTabAsync<HomeTabModel>(),
9757                     this.RefreshTabAsync<MentionsTabModel>(),
9758                     this.RefreshTabAsync<DirectMessagesTabModel>(),
9759                     this.RefreshTabAsync<PublicSearchTabModel>(),
9760                     this.RefreshTabAsync<UserTimelineTabModel>(),
9761                     this.RefreshTabAsync<ListTimelineTabModel>(),
9762                 };
9763
9764                 if (SettingManager.Common.StartupFollowers)
9765                     loadTasks.Add(this.RefreshFollowerIdsAsync());
9766
9767                 if (SettingManager.Common.GetFav)
9768                     loadTasks.Add(this.RefreshTabAsync<FavoritesTabModel>());
9769
9770                 var allTasks = Task.WhenAll(loadTasks);
9771
9772                 var i = 0;
9773                 while (true)
9774                 {
9775                     var timeout = Task.Delay(5000);
9776                     if (await Task.WhenAny(allTasks, timeout) != timeout)
9777                         break;
9778
9779                     i += 1;
9780                     if (i > 24) break; // 120秒間初期処理が終了しなかったら強制的に打ち切る
9781
9782                     if (MyCommon._endingFlag)
9783                         return;
9784                 }
9785
9786                 if (MyCommon._endingFlag) return;
9787
9788                 if (ApplicationSettings.VersionInfoUrl != null)
9789                 {
9790                     //バージョンチェック(引数:起動時チェックの場合はtrue・・・チェック結果のメッセージを表示しない)
9791                     if (SettingManager.Common.StartupVersion)
9792                         await this.CheckNewVersion(true);
9793                 }
9794                 else
9795                 {
9796                     // ApplicationSetting.cs の設定により更新チェックが無効化されている場合
9797                     this.VerUpMenuItem.Enabled = false;
9798                     this.VerUpMenuItem.Available = false;
9799                     this.ToolStripSeparator16.Available = false; // VerUpMenuItem の一つ上にあるセパレータ
9800                 }
9801
9802                 // 権限チェック read/write権限(xAuthで取得したトークン)の場合は再認証を促す
9803                 if (MyCommon.TwitterApiInfo.AccessLevel == TwitterApiAccessLevel.ReadWrite)
9804                 {
9805                     MessageBox.Show(Properties.Resources.ReAuthorizeText);
9806                     SettingStripMenuItem_Click(null, null);
9807                 }
9808
9809                 // 取得失敗の場合は再試行する
9810                 var reloadTasks = new List<Task>();
9811
9812                 if (!tw.GetFollowersSuccess && SettingManager.Common.StartupFollowers)
9813                     reloadTasks.Add(this.RefreshFollowerIdsAsync());
9814
9815                 if (!tw.GetNoRetweetSuccess)
9816                     reloadTasks.Add(this.RefreshNoRetweetIdsAsync());
9817
9818                 if (this.tw.Configuration.PhotoSizeLimit == 0)
9819                     reloadTasks.Add(this.RefreshTwitterConfigurationAsync());
9820
9821                 await Task.WhenAll(reloadTasks);
9822             }
9823
9824             _initial = false;
9825
9826             this.timelineScheduler.Enabled = true;
9827         }
9828
9829         private async Task doGetFollowersMenu()
9830         {
9831             await this.RefreshFollowerIdsAsync();
9832             this.DispSelectedPost(true);
9833         }
9834
9835         private async void GetFollowersAllToolStripMenuItem_Click(object sender, EventArgs e)
9836             => await this.doGetFollowersMenu();
9837
9838         private void ReTweetUnofficialStripMenuItem_Click(object sender, EventArgs e)
9839             => this.doReTweetUnofficial();
9840
9841         private async Task doReTweetOfficial(bool isConfirm)
9842         {
9843             //公式RT
9844             if (this.ExistCurrentPost)
9845             {
9846                 var selectedPosts = this.CurrentTab.SelectedPosts;
9847
9848                 if (selectedPosts.Any(x => !x.CanRetweetBy(this.twitterApi.CurrentUserId)))
9849                 {
9850                     if (selectedPosts.Any(x => x.IsProtect))
9851                         MessageBox.Show("Protected.");
9852
9853                     _DoFavRetweetFlags = false;
9854                     return;
9855                 }
9856
9857                 if (selectedPosts.Length > 15)
9858                 {
9859                     MessageBox.Show(Properties.Resources.RetweetLimitText);
9860                     _DoFavRetweetFlags = false;
9861                     return;
9862                 }
9863                 else if (selectedPosts.Length > 1)
9864                 {
9865                     string QuestionText = Properties.Resources.RetweetQuestion2;
9866                     if (_DoFavRetweetFlags) QuestionText = Properties.Resources.FavoriteRetweetQuestionText1;
9867                     switch (MessageBox.Show(QuestionText, "Retweet", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question))
9868                     {
9869                         case DialogResult.Cancel:
9870                         case DialogResult.No:
9871                             _DoFavRetweetFlags = false;
9872                             return;
9873                     }
9874                 }
9875                 else
9876                 {
9877                     if (!SettingManager.Common.RetweetNoConfirm)
9878                     {
9879                         string Questiontext = Properties.Resources.RetweetQuestion1;
9880                         if (_DoFavRetweetFlags) Questiontext = Properties.Resources.FavoritesRetweetQuestionText2;
9881                         if (isConfirm && MessageBox.Show(Questiontext, "Retweet", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.Cancel)
9882                         {
9883                             _DoFavRetweetFlags = false;
9884                             return;
9885                         }
9886                     }
9887                 }
9888
9889                 var statusIds = selectedPosts.Select(x => x.StatusId).ToList();
9890
9891                 await this.RetweetAsync(statusIds);
9892             }
9893         }
9894
9895         private async void ReTweetStripMenuItem_Click(object sender, EventArgs e)
9896             => await this.doReTweetOfficial(true);
9897
9898         private async Task FavoritesRetweetOfficial()
9899         {
9900             if (!this.ExistCurrentPost) return;
9901             _DoFavRetweetFlags = true;
9902             var retweetTask = this.doReTweetOfficial(true);
9903             if (_DoFavRetweetFlags)
9904             {
9905                 _DoFavRetweetFlags = false;
9906                 var favoriteTask = this.FavoriteChange(true, false);
9907
9908                 await Task.WhenAll(retweetTask, favoriteTask);
9909             }
9910             else
9911             {
9912                 await retweetTask;
9913             }
9914         }
9915
9916         private async Task FavoritesRetweetUnofficial()
9917         {
9918             var post = this.CurrentPost;
9919             if (this.ExistCurrentPost && !post.IsDm)
9920             {
9921                 _DoFavRetweetFlags = true;
9922                 var favoriteTask = this.FavoriteChange(true);
9923                 if (!post.IsProtect && _DoFavRetweetFlags)
9924                 {
9925                     _DoFavRetweetFlags = false;
9926                     doReTweetUnofficial();
9927                 }
9928
9929                 await favoriteTask;
9930             }
9931         }
9932
9933         /// <summary>
9934         /// TweetFormatterクラスによって整形された状態のHTMLを、非公式RT用に元のツイートに復元します
9935         /// </summary>
9936         /// <param name="statusHtml">TweetFormatterによって整形された状態のHTML</param>
9937         /// <param name="multiline">trueであればBRタグを改行に、falseであればスペースに変換します</param>
9938         /// <returns>復元されたツイート本文</returns>
9939         internal static string CreateRetweetUnofficial(string statusHtml, bool multiline)
9940         {
9941             // TweetFormatterクラスによって整形された状態のHTMLを元のツイートに復元します
9942
9943             // 通常の URL
9944             statusHtml = Regex.Replace(statusHtml, "<a href=\"(?<href>.+?)\" title=\"(?<title>.+?)\">(?<text>.+?)</a>", "${title}");
9945             // メンション
9946             statusHtml = Regex.Replace(statusHtml, "<a class=\"mention\" href=\"(?<href>.+?)\">(?<text>.+?)</a>", "${text}");
9947             // ハッシュタグ
9948             statusHtml = Regex.Replace(statusHtml, "<a class=\"hashtag\" href=\"(?<href>.+?)\">(?<text>.+?)</a>", "${text}");
9949             // 絵文字
9950             statusHtml = Regex.Replace(statusHtml, "<img class=\"emoji\" src=\".+?\" alt=\"(?<text>.+?)\" />", "${text}");
9951
9952             // <br> 除去
9953             if (multiline)
9954                 statusHtml = statusHtml.Replace("<br>", Environment.NewLine);
9955             else
9956                 statusHtml = statusHtml.Replace("<br>", " ");
9957
9958             // &nbsp; は本来であれば U+00A0 (NON-BREAK SPACE) に置換すべきですが、
9959             // 現状では半角スペースの代用として &nbsp; を使用しているため U+0020 に置換します
9960             statusHtml = statusHtml.Replace("&nbsp;", " ");
9961
9962             return WebUtility.HtmlDecode(statusHtml);
9963         }
9964
9965         private void DumpPostClassToolStripMenuItem_Click(object sender, EventArgs e)
9966         {
9967             this.tweetDetailsView.DumpPostClass = this.DumpPostClassToolStripMenuItem.Checked;
9968
9969             if (this.CurrentPost != null)
9970                 this.DispSelectedPost(true);
9971         }
9972
9973         private void MenuItemHelp_DropDownOpening(object sender, EventArgs e)
9974         {
9975             if (MyCommon.DebugBuild || MyCommon.IsKeyDown(Keys.CapsLock, Keys.Control, Keys.Shift))
9976                 DebugModeToolStripMenuItem.Visible = true;
9977             else
9978                 DebugModeToolStripMenuItem.Visible = false;
9979         }
9980
9981         private void UrlMultibyteSplitMenuItem_CheckedChanged(object sender, EventArgs e)
9982             => this.urlMultibyteSplit = ((ToolStripMenuItem)sender).Checked;
9983
9984         private void PreventSmsCommandMenuItem_CheckedChanged(object sender, EventArgs e)
9985             => this.preventSmsCommand = ((ToolStripMenuItem)sender).Checked;
9986
9987         private void UrlAutoShortenMenuItem_CheckedChanged(object sender, EventArgs e)
9988             => SettingManager.Common.UrlConvertAuto = ((ToolStripMenuItem)sender).Checked;
9989
9990         private void IdeographicSpaceToSpaceMenuItem_Click(object sender, EventArgs e)
9991         {
9992             SettingManager.Common.WideSpaceConvert = ((ToolStripMenuItem)sender).Checked;
9993             this.MarkSettingCommonModified();
9994         }
9995
9996         private void FocusLockMenuItem_CheckedChanged(object sender, EventArgs e)
9997         {
9998             SettingManager.Common.FocusLockToStatusText = ((ToolStripMenuItem)sender).Checked;
9999             this.MarkSettingCommonModified();
10000         }
10001
10002         private void PostModeMenuItem_DropDownOpening(object sender, EventArgs e)
10003         {
10004             UrlMultibyteSplitMenuItem.Checked = this.urlMultibyteSplit;
10005             PreventSmsCommandMenuItem.Checked = this.preventSmsCommand;
10006             UrlAutoShortenMenuItem.Checked = SettingManager.Common.UrlConvertAuto;
10007             IdeographicSpaceToSpaceMenuItem.Checked = SettingManager.Common.WideSpaceConvert;
10008             MultiLineMenuItem.Checked = SettingManager.Local.StatusMultiline;
10009             FocusLockMenuItem.Checked = SettingManager.Common.FocusLockToStatusText;
10010         }
10011
10012         private void ContextMenuPostMode_Opening(object sender, CancelEventArgs e)
10013         {
10014             UrlMultibyteSplitPullDownMenuItem.Checked = this.urlMultibyteSplit;
10015             PreventSmsCommandPullDownMenuItem.Checked = this.preventSmsCommand;
10016             UrlAutoShortenPullDownMenuItem.Checked = SettingManager.Common.UrlConvertAuto;
10017             IdeographicSpaceToSpacePullDownMenuItem.Checked = SettingManager.Common.WideSpaceConvert;
10018             MultiLinePullDownMenuItem.Checked = SettingManager.Local.StatusMultiline;
10019             FocusLockPullDownMenuItem.Checked = SettingManager.Common.FocusLockToStatusText;
10020         }
10021
10022         private void TraceOutToolStripMenuItem_Click(object sender, EventArgs e)
10023         {
10024             if (TraceOutToolStripMenuItem.Checked)
10025                 MyCommon.TraceFlag = true;
10026             else
10027                 MyCommon.TraceFlag = false;
10028         }
10029
10030         private void TweenMain_Deactivate(object sender, EventArgs e)
10031             => this.StatusText_Leave(StatusText, EventArgs.Empty); // 画面が非アクティブになったら、発言欄の背景色をデフォルトへ
10032
10033         private void TabRenameMenuItem_Click(object sender, EventArgs e)
10034         {
10035             if (string.IsNullOrEmpty(_rclickTabName)) return;
10036
10037             TabRename(_rclickTabName, out var _);
10038         }
10039
10040         private async void BitlyToolStripMenuItem_Click(object sender, EventArgs e)
10041             => await this.UrlConvertAsync(MyCommon.UrlConverter.Bitly);
10042
10043         private async void JmpToolStripMenuItem_Click(object sender, EventArgs e)
10044             => await this.UrlConvertAsync(MyCommon.UrlConverter.Jmp);
10045
10046         private async void ApiUsageInfoMenuItem_Click(object sender, EventArgs e)
10047         {
10048             TwitterApiStatus apiStatus;
10049
10050             using (var dialog = new WaitingDialog(Properties.Resources.ApiInfo6))
10051             {
10052                 var cancellationToken = dialog.EnableCancellation();
10053
10054                 try
10055                 {
10056                     var task = this.tw.GetInfoApi();
10057                     apiStatus = await dialog.WaitForAsync(this, task);
10058                 }
10059                 catch (WebApiException)
10060                 {
10061                     apiStatus = null;
10062                 }
10063
10064                 if (cancellationToken.IsCancellationRequested)
10065                     return;
10066
10067                 if (apiStatus == null)
10068                 {
10069                     MessageBox.Show(Properties.Resources.ApiInfo5, Properties.Resources.ApiInfo4, MessageBoxButtons.OK, MessageBoxIcon.Information);
10070                     return;
10071                 }
10072             }
10073
10074             using (var apiDlg = new ApiInfoDialog())
10075             {
10076                 apiDlg.ShowDialog(this);
10077             }
10078         }
10079
10080         private async void FollowCommandMenuItem_Click(object sender, EventArgs e)
10081         {
10082             var id = this.CurrentPost?.ScreenName ?? "";
10083
10084             await this.FollowCommand(id);
10085         }
10086
10087         internal async Task FollowCommand(string id)
10088         {
10089             using (var inputName = new InputTabName())
10090             {
10091                 inputName.FormTitle = "Follow";
10092                 inputName.FormDescription = Properties.Resources.FRMessage1;
10093                 inputName.TabName = id;
10094
10095                 if (inputName.ShowDialog(this) != DialogResult.OK)
10096                     return;
10097                 if (string.IsNullOrWhiteSpace(inputName.TabName))
10098                     return;
10099
10100                 id = inputName.TabName.Trim();
10101             }
10102
10103             using (var dialog = new WaitingDialog(Properties.Resources.FollowCommandText1))
10104             {
10105                 try
10106                 {
10107                     var task = this.twitterApi.FriendshipsCreate(id).IgnoreResponse();
10108                     await dialog.WaitForAsync(this, task);
10109                 }
10110                 catch (WebApiException ex)
10111                 {
10112                     MessageBox.Show(Properties.Resources.FRMessage2 + ex.Message);
10113                     return;
10114                 }
10115             }
10116
10117             MessageBox.Show(Properties.Resources.FRMessage3);
10118         }
10119
10120         private async void RemoveCommandMenuItem_Click(object sender, EventArgs e)
10121         {
10122             var id = this.CurrentPost?.ScreenName ?? "";
10123
10124             await this.RemoveCommand(id, false);
10125         }
10126
10127         internal async Task RemoveCommand(string id, bool skipInput)
10128         {
10129             if (!skipInput)
10130             {
10131                 using (var inputName = new InputTabName())
10132                 {
10133                     inputName.FormTitle = "Unfollow";
10134                     inputName.FormDescription = Properties.Resources.FRMessage1;
10135                     inputName.TabName = id;
10136
10137                     if (inputName.ShowDialog(this) != DialogResult.OK)
10138                         return;
10139                     if (string.IsNullOrWhiteSpace(inputName.TabName))
10140                         return;
10141
10142                     id = inputName.TabName.Trim();
10143                 }
10144             }
10145
10146             using (var dialog = new WaitingDialog(Properties.Resources.RemoveCommandText1))
10147             {
10148                 try
10149                 {
10150                     var task = this.twitterApi.FriendshipsDestroy(id).IgnoreResponse();
10151                     await dialog.WaitForAsync(this, task);
10152                 }
10153                 catch (WebApiException ex)
10154                 {
10155                     MessageBox.Show(Properties.Resources.FRMessage2 + ex.Message);
10156                     return;
10157                 }
10158             }
10159
10160             MessageBox.Show(Properties.Resources.FRMessage3);
10161         }
10162
10163         private async void FriendshipMenuItem_Click(object sender, EventArgs e)
10164         {
10165             var id = this.CurrentPost?.ScreenName ?? "";
10166
10167             await this.ShowFriendship(id);
10168         }
10169
10170         internal async Task ShowFriendship(string id)
10171         {
10172             using (var inputName = new InputTabName())
10173             {
10174                 inputName.FormTitle = "Show Friendships";
10175                 inputName.FormDescription = Properties.Resources.FRMessage1;
10176                 inputName.TabName = id;
10177
10178                 if (inputName.ShowDialog(this) != DialogResult.OK)
10179                     return;
10180                 if (string.IsNullOrWhiteSpace(inputName.TabName))
10181                     return;
10182
10183                 id = inputName.TabName.Trim();
10184             }
10185
10186             bool isFollowing, isFollowed;
10187
10188             using (var dialog = new WaitingDialog(Properties.Resources.ShowFriendshipText1))
10189             {
10190                 var cancellationToken = dialog.EnableCancellation();
10191
10192                 try
10193                 {
10194                     var task = this.twitterApi.FriendshipsShow(this.twitterApi.CurrentScreenName, id);
10195                     var friendship = await dialog.WaitForAsync(this, task);
10196
10197                     isFollowing = friendship.Relationship.Source.Following;
10198                     isFollowed = friendship.Relationship.Source.FollowedBy;
10199                 }
10200                 catch (WebApiException ex)
10201                 {
10202                     if (!cancellationToken.IsCancellationRequested)
10203                         MessageBox.Show($"Err:{ex.Message}(FriendshipsShow)");
10204                     return;
10205                 }
10206
10207                 if (cancellationToken.IsCancellationRequested)
10208                     return;
10209             }
10210
10211             string result = "";
10212             if (isFollowing)
10213             {
10214                 result = Properties.Resources.GetFriendshipInfo1 + System.Environment.NewLine;
10215             }
10216             else
10217             {
10218                 result = Properties.Resources.GetFriendshipInfo2 + System.Environment.NewLine;
10219             }
10220             if (isFollowed)
10221             {
10222                 result += Properties.Resources.GetFriendshipInfo3;
10223             }
10224             else
10225             {
10226                 result += Properties.Resources.GetFriendshipInfo4;
10227             }
10228             result = id + Properties.Resources.GetFriendshipInfo5 + System.Environment.NewLine + result;
10229             MessageBox.Show(result);
10230         }
10231
10232         internal async Task ShowFriendship(string[] ids)
10233         {
10234             foreach (string id in ids)
10235             {
10236                 bool isFollowing, isFollowed;
10237
10238                 using (var dialog = new WaitingDialog(Properties.Resources.ShowFriendshipText1))
10239                 {
10240                     var cancellationToken = dialog.EnableCancellation();
10241
10242                     try
10243                     {
10244                         var task = this.twitterApi.FriendshipsShow(this.twitterApi.CurrentScreenName, id);
10245                         var friendship = await dialog.WaitForAsync(this, task);
10246
10247                         isFollowing = friendship.Relationship.Source.Following;
10248                         isFollowed = friendship.Relationship.Source.FollowedBy;
10249                     }
10250                     catch (WebApiException ex)
10251                     {
10252                         if (!cancellationToken.IsCancellationRequested)
10253                             MessageBox.Show($"Err:{ex.Message}(FriendshipsShow)");
10254                         return;
10255                     }
10256
10257                     if (cancellationToken.IsCancellationRequested)
10258                         return;
10259                 }
10260
10261                 string result = "";
10262                 string ff = "";
10263
10264                 ff = "  ";
10265                 if (isFollowing)
10266                 {
10267                     ff += Properties.Resources.GetFriendshipInfo1;
10268                 }
10269                 else
10270                 {
10271                     ff += Properties.Resources.GetFriendshipInfo2;
10272                 }
10273
10274                 ff += System.Environment.NewLine + "  ";
10275                 if (isFollowed)
10276                 {
10277                     ff += Properties.Resources.GetFriendshipInfo3;
10278                 }
10279                 else
10280                 {
10281                     ff += Properties.Resources.GetFriendshipInfo4;
10282                 }
10283                 result += id + Properties.Resources.GetFriendshipInfo5 + System.Environment.NewLine + ff;
10284                 if (isFollowing)
10285                 {
10286                     if (MessageBox.Show(
10287                         Properties.Resources.GetFriendshipInfo7 + System.Environment.NewLine + result, Properties.Resources.GetFriendshipInfo8,
10288                         MessageBoxButtons.YesNo,
10289                         MessageBoxIcon.Question,
10290                         MessageBoxDefaultButton.Button2) == DialogResult.Yes)
10291                     {
10292                         await this.RemoveCommand(id, true);
10293                     }
10294                 }
10295                 else
10296                 {
10297                     MessageBox.Show(result);
10298                 }
10299             }
10300         }
10301
10302         private async void OwnStatusMenuItem_Click(object sender, EventArgs e)
10303             => await this.doShowUserStatus(tw.Username, false);
10304
10305         // TwitterIDでない固定文字列を調べる(文字列検証のみ 実際に取得はしない)
10306         // URLから切り出した文字列を渡す
10307
10308         public bool IsTwitterId(string name)
10309         {
10310             if (this.tw.Configuration.NonUsernamePaths == null || this.tw.Configuration.NonUsernamePaths.Length == 0)
10311                 return !Regex.Match(name, @"^(about|jobs|tos|privacy|who_to_follow|download|messages)$", RegexOptions.IgnoreCase).Success;
10312             else
10313                 return !this.tw.Configuration.NonUsernamePaths.Contains(name, StringComparer.InvariantCultureIgnoreCase);
10314         }
10315
10316         private void doQuoteOfficial()
10317         {
10318             if (this.ExistCurrentPost)
10319             {
10320                 var post = this.CurrentPost;
10321                 if (post.IsDm || !StatusText.Enabled)
10322                     return;
10323
10324                 if (post.IsProtect)
10325                 {
10326                     MessageBox.Show("Protected.");
10327                     return;
10328                 }
10329
10330                 var selection = (this.StatusText.SelectionStart, this.StatusText.SelectionLength);
10331
10332                 this.inReplyTo = null;
10333
10334                 StatusText.Text += " " + MyCommon.GetStatusUrl(post);
10335
10336                 (this.StatusText.SelectionStart, this.StatusText.SelectionLength) = selection;
10337                 StatusText.Focus();
10338             }
10339         }
10340
10341         private void doReTweetUnofficial()
10342         {
10343             //RT @id:内容
10344             if (this.ExistCurrentPost)
10345             {
10346                 var post = this.CurrentPost;
10347                 if (post.IsDm || !StatusText.Enabled)
10348                     return;
10349
10350                 if (post.IsProtect)
10351                 {
10352                     MessageBox.Show("Protected.");
10353                     return;
10354                 }
10355                 string rtdata = post.Text;
10356                 rtdata = CreateRetweetUnofficial(rtdata, this.StatusText.Multiline);
10357
10358                 var selection = (this.StatusText.SelectionStart, this.StatusText.SelectionLength);
10359
10360                 // 投稿時に in_reply_to_status_id を付加する
10361                 var inReplyToStatusId = post.RetweetedId ?? post.StatusId;
10362                 var inReplyToScreenName = post.ScreenName;
10363                 this.inReplyTo = (inReplyToStatusId, inReplyToScreenName);
10364
10365                 StatusText.Text += " RT @" + post.ScreenName + ": " + rtdata;
10366
10367                 (this.StatusText.SelectionStart, this.StatusText.SelectionLength) = selection;
10368                 StatusText.Focus();
10369             }
10370         }
10371
10372         private void QuoteStripMenuItem_Click(object sender, EventArgs e)
10373             => this.doQuoteOfficial();
10374
10375         private async void SearchButton_Click(object sender, EventArgs e)
10376         {
10377             //公式検索
10378             Control pnl = ((Control)sender).Parent;
10379             if (pnl == null) return;
10380             string tbName = pnl.Parent.Text;
10381             var tb = (PublicSearchTabModel)_statuses.Tabs[tbName];
10382             ComboBox cmb = (ComboBox)pnl.Controls["comboSearch"];
10383             ComboBox cmbLang = (ComboBox)pnl.Controls["comboLang"];
10384             cmb.Text = cmb.Text.Trim();
10385             // 検索式演算子 OR についてのみ大文字しか認識しないので強制的に大文字とする
10386             bool Quote = false;
10387             StringBuilder buf = new StringBuilder();
10388             char[] c = cmb.Text.ToCharArray();
10389             for (int cnt = 0; cnt < cmb.Text.Length; cnt++)
10390             {
10391                 if (cnt > cmb.Text.Length - 4)
10392                 {
10393                     buf.Append(cmb.Text.Substring(cnt));
10394                     break;
10395                 }
10396                 if (c[cnt] == '"')
10397                 {
10398                     Quote = !Quote;
10399                 }
10400                 else
10401                 {
10402                     if (!Quote && cmb.Text.Substring(cnt, 4).Equals(" or ", StringComparison.OrdinalIgnoreCase))
10403                     {
10404                         buf.Append(" OR ");
10405                         cnt += 3;
10406                         continue;
10407                     }
10408                 }
10409                 buf.Append(c[cnt]);
10410             }
10411             cmb.Text = buf.ToString();
10412
10413             var listView = (DetailsListView)pnl.Parent.Tag;
10414
10415             var queryChanged = tb.SearchWords != cmb.Text || tb.SearchLang != cmbLang.Text;
10416
10417             tb.SearchWords = cmb.Text;
10418             tb.SearchLang = cmbLang.Text;
10419             if (string.IsNullOrEmpty(cmb.Text))
10420             {
10421                 listView.Focus();
10422                 SaveConfigsTabs();
10423                 return;
10424             }
10425             if (queryChanged)
10426             {
10427                 int idx = cmb.Items.IndexOf(tb.SearchWords);
10428                 if (idx > -1) cmb.Items.RemoveAt(idx);
10429                 cmb.Items.Insert(0, tb.SearchWords);
10430                 cmb.Text = tb.SearchWords;
10431                 cmb.SelectAll();
10432                 this.PurgeListViewItemCache();
10433                 listView.VirtualListSize = 0;
10434                 _statuses.ClearTabIds(tbName);
10435                 SaveConfigsTabs();   //検索条件の保存
10436             }
10437
10438             listView.Focus();
10439             await this.RefreshTabAsync(tb);
10440         }
10441
10442         private async void RefreshMoreStripMenuItem_Click(object sender, EventArgs e)
10443             => await this.DoRefreshMore(); // もっと前を取得
10444
10445         /// <summary>
10446         /// 指定されたタブのListTabにおける位置を返します
10447         /// </summary>
10448         /// <remarks>
10449         /// 非表示のタブについて -1 が返ることを常に考慮して下さい
10450         /// </remarks>
10451         public int GetTabPageIndex(string tabName)
10452             => this._statuses.Tabs.IndexOf(tabName);
10453
10454         private void UndoRemoveTabMenuItem_Click(object sender, EventArgs e)
10455         {
10456             if (_statuses.RemovedTab.Count == 0)
10457             {
10458                 MessageBox.Show("There isn't removed tab.", "Undo", MessageBoxButtons.OK, MessageBoxIcon.Information);
10459                 return;
10460             }
10461             else
10462             {
10463                 DetailsListView listView = null;
10464
10465                 TabModel tb = _statuses.RemovedTab.Pop();
10466                 if (tb.TabType == MyCommon.TabUsageType.Related)
10467                 {
10468                     var relatedTab = _statuses.GetTabByType(MyCommon.TabUsageType.Related);
10469                     if (relatedTab != null)
10470                     {
10471                         // 関連発言なら既存のタブを置き換える
10472                         tb.TabName = relatedTab.TabName;
10473                         this.ClearTab(tb.TabName, false);
10474
10475                         this._statuses.ReplaceTab(tb);
10476
10477                         var tabIndex = this._statuses.Tabs.IndexOf(tb);
10478                         var tabPage = this.ListTab.TabPages[tabIndex];
10479                         listView = (DetailsListView)tabPage.Tag;
10480                         this.ListTab.SelectedIndex = tabIndex;
10481                     }
10482                     else
10483                     {
10484                         const string TabName = "Related Tweets";
10485                         string renamed = TabName;
10486                         for (int i = 2; i <= 100; i++)
10487                         {
10488                             if (!_statuses.ContainsTab(renamed)) break;
10489                             renamed = TabName + i;
10490                         }
10491                         tb.TabName = renamed;
10492
10493                         _statuses.AddTab(tb);
10494                         AddNewTab(tb, startup: false);
10495
10496                         var tabIndex = this._statuses.Tabs.Count - 1;
10497                         var tabPage = this.ListTab.TabPages[tabIndex];
10498
10499                         listView = (DetailsListView)tabPage.Tag;
10500                         this.ListTab.SelectedIndex = tabIndex;
10501                     }
10502                 }
10503                 else
10504                 {
10505                     string renamed = tb.TabName;
10506                     for (int i = 1; i < int.MaxValue; i++)
10507                     {
10508                         if (!_statuses.ContainsTab(renamed)) break;
10509                         renamed = tb.TabName + "(" + i + ")";
10510                     }
10511                     tb.TabName = renamed;
10512
10513                     _statuses.AddTab(tb);
10514                     AddNewTab(tb, startup: false);
10515
10516                     var tabIndex = this._statuses.Tabs.Count - 1;
10517                     var tabPage = this.ListTab.TabPages[tabIndex];
10518
10519                     listView = (DetailsListView)tabPage.Tag;
10520                     this.ListTab.SelectedIndex = tabIndex;
10521                 }
10522                 SaveConfigsTabs();
10523
10524                 if (listView != null)
10525                 {
10526                     using (ControlTransaction.Update(listView))
10527                     {
10528                         listView.VirtualListSize = tb.AllCount;
10529                     }
10530                 }
10531             }
10532         }
10533
10534         private async Task doMoveToRTHome()
10535         {
10536             var post = this.CurrentPost;
10537             if (post != null && post.RetweetedId != null)
10538                 await this.OpenUriInBrowserAsync("https://twitter.com/" + post.RetweetedBy);
10539         }
10540
10541         private async void MoveToRTHomeMenuItem_Click(object sender, EventArgs e)
10542             => await this.doMoveToRTHome();
10543
10544         private void ListManageUserContextToolStripMenuItem_Click(object sender, EventArgs e)
10545         {
10546             var screenName = this.CurrentPost?.ScreenName;
10547             if (screenName != null)
10548                 this.ListManageUserContext(screenName);
10549         }
10550
10551         public void ListManageUserContext(string screenName)
10552         {
10553             using (var listSelectForm = new MyLists(screenName, this.twitterApi))
10554             {
10555                 listSelectForm.ShowDialog(this);
10556             }
10557         }
10558
10559         private void SearchControls_Enter(object sender, EventArgs e)
10560         {
10561             Control pnl = (Control)sender;
10562             foreach (Control ctl in pnl.Controls)
10563             {
10564                 ctl.TabStop = true;
10565             }
10566         }
10567
10568         private void SearchControls_Leave(object sender, EventArgs e)
10569         {
10570             Control pnl = (Control)sender;
10571             foreach (Control ctl in pnl.Controls)
10572             {
10573                 ctl.TabStop = false;
10574             }
10575         }
10576
10577         private void PublicSearchQueryMenuItem_Click(object sender, EventArgs e)
10578         {
10579             var tab = this.CurrentTab;
10580             if (tab.TabType != MyCommon.TabUsageType.PublicSearch) return;
10581             this.CurrentTabPage.Controls["panelSearch"].Controls["comboSearch"].Focus();
10582         }
10583
10584         private void StatusLabel_DoubleClick(object sender, EventArgs e)
10585             => MessageBox.Show(StatusLabel.TextHistory, "Logs", MessageBoxButtons.OK, MessageBoxIcon.None);
10586
10587         private void HashManageMenuItem_Click(object sender, EventArgs e)
10588         {
10589             DialogResult rslt = DialogResult.Cancel;
10590             try
10591             {
10592                 rslt = HashMgr.ShowDialog();
10593             }
10594             catch (Exception)
10595             {
10596                 return;
10597             }
10598             this.TopMost = SettingManager.Common.AlwaysTop;
10599             if (rslt == DialogResult.Cancel) return;
10600             if (!string.IsNullOrEmpty(HashMgr.UseHash))
10601             {
10602                 HashStripSplitButton.Text = HashMgr.UseHash;
10603                 HashTogglePullDownMenuItem.Checked = true;
10604                 HashToggleMenuItem.Checked = true;
10605             }
10606             else
10607             {
10608                 HashStripSplitButton.Text = "#[-]";
10609                 HashTogglePullDownMenuItem.Checked = false;
10610                 HashToggleMenuItem.Checked = false;
10611             }
10612             //if (HashMgr.IsInsert && HashMgr.UseHash != "")
10613             //{
10614             //    int sidx = StatusText.SelectionStart;
10615             //    string hash = HashMgr.UseHash + " ";
10616             //    if (sidx > 0)
10617             //    {
10618             //        if (StatusText.Text.Substring(sidx - 1, 1) != " ")
10619             //            hash = " " + hash;
10620             //    }
10621             //    StatusText.Text = StatusText.Text.Insert(sidx, hash);
10622             //    sidx += hash.Length;
10623             //    StatusText.SelectionStart = sidx;
10624             //    StatusText.Focus();
10625             //}
10626             this.MarkSettingCommonModified();
10627             this.StatusText_TextChanged(null, null);
10628         }
10629
10630         private void HashToggleMenuItem_Click(object sender, EventArgs e)
10631         {
10632             HashMgr.ToggleHash();
10633             if (!string.IsNullOrEmpty(HashMgr.UseHash))
10634             {
10635                 HashStripSplitButton.Text = HashMgr.UseHash;
10636                 HashToggleMenuItem.Checked = true;
10637                 HashTogglePullDownMenuItem.Checked = true;
10638             }
10639             else
10640             {
10641                 HashStripSplitButton.Text = "#[-]";
10642                 HashToggleMenuItem.Checked = false;
10643                 HashTogglePullDownMenuItem.Checked = false;
10644             }
10645             this.MarkSettingCommonModified();
10646             this.StatusText_TextChanged(null, null);
10647         }
10648
10649         private void HashStripSplitButton_ButtonClick(object sender, EventArgs e)
10650             => this.HashToggleMenuItem_Click(null, null);
10651
10652         public void SetPermanentHashtag(string hashtag)
10653         {
10654             HashMgr.SetPermanentHash("#" + hashtag);
10655             HashStripSplitButton.Text = HashMgr.UseHash;
10656             HashTogglePullDownMenuItem.Checked = true;
10657             HashToggleMenuItem.Checked = true;
10658             //使用ハッシュタグとして設定
10659             this.MarkSettingCommonModified();
10660         }
10661
10662         private void MenuItemOperate_DropDownOpening(object sender, EventArgs e)
10663         {
10664             if (!this.ExistCurrentPost)
10665             {
10666                 this.ReplyOpMenuItem.Enabled = false;
10667                 this.ReplyAllOpMenuItem.Enabled = false;
10668                 this.DmOpMenuItem.Enabled = false;
10669                 this.ShowProfMenuItem.Enabled = false;
10670                 this.ShowUserTimelineToolStripMenuItem.Enabled = false;
10671                 this.ListManageMenuItem.Enabled = false;
10672                 this.OpenFavOpMenuItem.Enabled = false;
10673                 this.CreateTabRuleOpMenuItem.Enabled = false;
10674                 this.CreateIdRuleOpMenuItem.Enabled = false;
10675                 this.CreateSourceRuleOpMenuItem.Enabled = false;
10676                 this.ReadOpMenuItem.Enabled = false;
10677                 this.UnreadOpMenuItem.Enabled = false;
10678             }
10679             else
10680             {
10681                 this.ReplyOpMenuItem.Enabled = true;
10682                 this.ReplyAllOpMenuItem.Enabled = true;
10683                 this.DmOpMenuItem.Enabled = true;
10684                 this.ShowProfMenuItem.Enabled = true;
10685                 this.ShowUserTimelineToolStripMenuItem.Enabled = true;
10686                 this.ListManageMenuItem.Enabled = true;
10687                 this.OpenFavOpMenuItem.Enabled = true;
10688                 this.CreateTabRuleOpMenuItem.Enabled = true;
10689                 this.CreateIdRuleOpMenuItem.Enabled = true;
10690                 this.CreateSourceRuleOpMenuItem.Enabled = true;
10691                 this.ReadOpMenuItem.Enabled = true;
10692                 this.UnreadOpMenuItem.Enabled = true;
10693             }
10694
10695             var tab = this.CurrentTab;
10696             var post = this.CurrentPost;
10697             if (tab.TabType == MyCommon.TabUsageType.DirectMessage || !this.ExistCurrentPost || post.IsDm)
10698             {
10699                 this.FavOpMenuItem.Enabled = false;
10700                 this.UnFavOpMenuItem.Enabled = false;
10701                 this.OpenStatusOpMenuItem.Enabled = false;
10702                 this.ShowRelatedStatusesMenuItem2.Enabled = false;
10703                 this.RtOpMenuItem.Enabled = false;
10704                 this.RtUnOpMenuItem.Enabled = false;
10705                 this.QtOpMenuItem.Enabled = false;
10706                 this.FavoriteRetweetMenuItem.Enabled = false;
10707                 this.FavoriteRetweetUnofficialMenuItem.Enabled = false;
10708             }
10709             else
10710             {
10711                 this.FavOpMenuItem.Enabled = true;
10712                 this.UnFavOpMenuItem.Enabled = true;
10713                 this.OpenStatusOpMenuItem.Enabled = true;
10714                 this.ShowRelatedStatusesMenuItem2.Enabled = true;  //PublicSearchの時問題出るかも
10715
10716                 if (!post.CanRetweetBy(this.twitterApi.CurrentUserId))
10717                 {
10718                     this.RtOpMenuItem.Enabled = false;
10719                     this.RtUnOpMenuItem.Enabled = false;
10720                     this.QtOpMenuItem.Enabled = false;
10721                     this.FavoriteRetweetMenuItem.Enabled = false;
10722                     this.FavoriteRetweetUnofficialMenuItem.Enabled = false;
10723                 }
10724                 else
10725                 {
10726                     this.RtOpMenuItem.Enabled = true;
10727                     this.RtUnOpMenuItem.Enabled = true;
10728                     this.QtOpMenuItem.Enabled = true;
10729                     this.FavoriteRetweetMenuItem.Enabled = true;
10730                     this.FavoriteRetweetUnofficialMenuItem.Enabled = true;
10731                 }
10732             }
10733
10734             if (tab.TabType != MyCommon.TabUsageType.Favorites)
10735             {
10736                 this.RefreshPrevOpMenuItem.Enabled = true;
10737             }
10738             else
10739             {
10740                 this.RefreshPrevOpMenuItem.Enabled = false;
10741             }
10742             if (!this.ExistCurrentPost || post.InReplyToStatusId == null)
10743             {
10744                 OpenRepSourceOpMenuItem.Enabled = false;
10745             }
10746             else
10747             {
10748                 OpenRepSourceOpMenuItem.Enabled = true;
10749             }
10750             if (!this.ExistCurrentPost || string.IsNullOrEmpty(post.RetweetedBy))
10751             {
10752                 OpenRterHomeMenuItem.Enabled = false;
10753             }
10754             else
10755             {
10756                 OpenRterHomeMenuItem.Enabled = true;
10757             }
10758
10759             if (this.ExistCurrentPost)
10760             {
10761                 this.DelOpMenuItem.Enabled = post.CanDeleteBy(this.tw.UserId);
10762             }
10763         }
10764
10765         private void MenuItemTab_DropDownOpening(object sender, EventArgs e)
10766             => this.ContextMenuTabProperty_Opening(sender, null);
10767
10768         public Twitter TwitterInstance
10769             => this.tw;
10770
10771         private void SplitContainer3_SplitterMoved(object sender, SplitterEventArgs e)
10772         {
10773             if (this._initialLayout)
10774                 return;
10775
10776             int splitterDistance;
10777             switch (this.WindowState)
10778             {
10779                 case FormWindowState.Normal:
10780                     splitterDistance = this.SplitContainer3.SplitterDistance;
10781                     break;
10782                 case FormWindowState.Maximized:
10783                     // 最大化時は、通常時のウィンドウサイズに換算した SplitterDistance を算出する
10784                     var normalContainerWidth = this._mySize.Width - SystemInformation.Border3DSize.Width * 2;
10785                     splitterDistance = this.SplitContainer3.SplitterDistance - (this.SplitContainer3.Width - normalContainerWidth);
10786                     splitterDistance = Math.Min(splitterDistance, normalContainerWidth - this.SplitContainer3.SplitterWidth - this.SplitContainer3.Panel2MinSize);
10787                     break;
10788                 default:
10789                     return;
10790             }
10791
10792             this._mySpDis3 = splitterDistance;
10793             this.MarkSettingLocalModified();
10794         }
10795
10796         private void MenuItemEdit_DropDownOpening(object sender, EventArgs e)
10797         {
10798             if (_statuses.RemovedTab.Count == 0)
10799             {
10800                 UndoRemoveTabMenuItem.Enabled = false;
10801             }
10802             else
10803             {
10804                 UndoRemoveTabMenuItem.Enabled = true;
10805             }
10806
10807             if (this.CurrentTab.TabType == MyCommon.TabUsageType.PublicSearch)
10808                 PublicSearchQueryMenuItem.Enabled = true;
10809             else
10810                 PublicSearchQueryMenuItem.Enabled = false;
10811
10812             if (!this.ExistCurrentPost)
10813             {
10814                 this.CopySTOTMenuItem.Enabled = false;
10815                 this.CopyURLMenuItem.Enabled = false;
10816                 this.CopyUserIdStripMenuItem.Enabled = false;
10817             }
10818             else
10819             {
10820                 this.CopySTOTMenuItem.Enabled = true;
10821                 this.CopyURLMenuItem.Enabled = true;
10822                 this.CopyUserIdStripMenuItem.Enabled = true;
10823
10824                 var post = this.CurrentPost;
10825                 if (post.IsDm) this.CopyURLMenuItem.Enabled = false;
10826                 if (post.IsProtect) this.CopySTOTMenuItem.Enabled = false;
10827             }
10828         }
10829
10830         private void NotifyIcon1_MouseMove(object sender, MouseEventArgs e)
10831             => this.SetNotifyIconText();
10832
10833         private async void UserStatusToolStripMenuItem_Click(object sender, EventArgs e)
10834             => await this.ShowUserStatus(this.CurrentPost?.ScreenName ?? "");
10835
10836         private async Task doShowUserStatus(string id, bool ShowInputDialog)
10837         {
10838             TwitterUser user = null;
10839
10840             if (ShowInputDialog)
10841             {
10842                 using (var inputName = new InputTabName())
10843                 {
10844                     inputName.FormTitle = "Show UserStatus";
10845                     inputName.FormDescription = Properties.Resources.FRMessage1;
10846                     inputName.TabName = id;
10847
10848                     if (inputName.ShowDialog(this) != DialogResult.OK)
10849                         return;
10850                     if (string.IsNullOrWhiteSpace(inputName.TabName))
10851                         return;
10852
10853                     id = inputName.TabName.Trim();
10854                 }
10855             }
10856
10857             using (var dialog = new WaitingDialog(Properties.Resources.doShowUserStatusText1))
10858             {
10859                 var cancellationToken = dialog.EnableCancellation();
10860
10861                 try
10862                 {
10863                     var task = this.twitterApi.UsersShow(id);
10864                     user = await dialog.WaitForAsync(this, task);
10865                 }
10866                 catch (WebApiException ex)
10867                 {
10868                     if (!cancellationToken.IsCancellationRequested)
10869                         MessageBox.Show($"Err:{ex.Message}(UsersShow)");
10870                     return;
10871                 }
10872
10873                 if (cancellationToken.IsCancellationRequested)
10874                     return;
10875             }
10876
10877             await this.doShowUserStatus(user);
10878         }
10879
10880         private async Task doShowUserStatus(TwitterUser user)
10881         {
10882             using (var userDialog = new UserInfoDialog(this, this.twitterApi))
10883             {
10884                 var showUserTask = userDialog.ShowUserAsync(user);
10885                 userDialog.ShowDialog(this);
10886
10887                 this.Activate();
10888                 this.BringToFront();
10889
10890                 // ユーザー情報の表示が完了するまで userDialog を破棄しない
10891                 await showUserTask;
10892             }
10893         }
10894
10895         internal Task ShowUserStatus(string id, bool ShowInputDialog)
10896             => this.doShowUserStatus(id, ShowInputDialog);
10897
10898         internal Task ShowUserStatus(string id)
10899             => this.doShowUserStatus(id, true);
10900
10901         private async void ShowProfileMenuItem_Click(object sender, EventArgs e)
10902         {
10903             var post = this.CurrentPost;
10904             if (post != null)
10905             {
10906                 await this.ShowUserStatus(post.ScreenName, false);
10907             }
10908         }
10909
10910         private async void RtCountMenuItem_Click(object sender, EventArgs e)
10911         {
10912             if (!this.ExistCurrentPost)
10913                 return;
10914
10915             var post = this.CurrentPost;
10916             var statusId = post.RetweetedId ?? post.StatusId;
10917             TwitterStatus status;
10918
10919             using (var dialog = new WaitingDialog(Properties.Resources.RtCountMenuItem_ClickText1))
10920             {
10921                 var cancellationToken = dialog.EnableCancellation();
10922
10923                 try
10924                 {
10925                     var task = this.twitterApi.StatusesShow(statusId);
10926                     status = await dialog.WaitForAsync(this, task);
10927                 }
10928                 catch (WebApiException ex)
10929                 {
10930                     if (!cancellationToken.IsCancellationRequested)
10931                         MessageBox.Show(Properties.Resources.RtCountText2 + Environment.NewLine + "Err:" + ex.Message);
10932                     return;
10933                 }
10934
10935                 if (cancellationToken.IsCancellationRequested)
10936                     return;
10937             }
10938
10939             MessageBox.Show(status.RetweetCount + Properties.Resources.RtCountText1);
10940         }
10941
10942         private HookGlobalHotkey _hookGlobalHotkey;
10943         public TweenMain()
10944         {
10945             _hookGlobalHotkey = new HookGlobalHotkey(this);
10946
10947             // この呼び出しは、Windows フォーム デザイナで必要です。
10948             InitializeComponent();
10949
10950             // InitializeComponent() 呼び出しの後で初期化を追加します。
10951
10952             if (!this.DesignMode)
10953             {
10954                 // デザイナでの編集時にレイアウトが縦方向に数pxずれる問題の対策
10955                 this.StatusText.Dock = DockStyle.Fill;
10956             }
10957
10958             this.tweetDetailsView.Owner = this;
10959
10960             this._hookGlobalHotkey.HotkeyPressed += _hookGlobalHotkey_HotkeyPressed;
10961             this.gh.NotifyClicked += GrowlHelper_Callback;
10962
10963             // メイリオフォント指定時にタブの最小幅が広くなる問題の対策
10964             this.ListTab.HandleCreated += (s, e) => NativeMethods.SetMinTabWidth((TabControl)s, 40);
10965
10966             this.ImageSelector.Visible = false;
10967             this.ImageSelector.Enabled = false;
10968             this.ImageSelector.FilePickDialog = OpenFileDialog1;
10969
10970             this.workerProgress = new Progress<string>(x => this.StatusLabel.Text = x);
10971
10972             this.ReplaceAppName();
10973             this.InitializeShortcuts();
10974         }
10975
10976         private void _hookGlobalHotkey_HotkeyPressed(object sender, KeyEventArgs e)
10977         {
10978             if ((this.WindowState == FormWindowState.Normal || this.WindowState == FormWindowState.Maximized) && this.Visible && Form.ActiveForm == this)
10979             {
10980                 //アイコン化
10981                 this.Visible = false;
10982             }
10983             else if (Form.ActiveForm == null)
10984             {
10985                 this.Visible = true;
10986                 if (this.WindowState == FormWindowState.Minimized) this.WindowState = FormWindowState.Normal;
10987                 this.Activate();
10988                 this.BringToFront();
10989                 this.StatusText.Focus();
10990             }
10991         }
10992
10993         private void SplitContainer2_MouseDoubleClick(object sender, MouseEventArgs e)
10994             => this.MultiLinePullDownMenuItem.PerformClick();
10995
10996 #region "画像投稿"
10997         private void ImageSelectMenuItem_Click(object sender, EventArgs e)
10998         {
10999             if (ImageSelector.Visible)
11000                 ImageSelector.EndSelection();
11001             else
11002                 ImageSelector.BeginSelection();
11003         }
11004
11005         private void SelectMedia_DragEnter(DragEventArgs e)
11006         {
11007             if (ImageSelector.HasUploadableService(((string[])e.Data.GetData(DataFormats.FileDrop, false))[0], true))
11008             {
11009                 e.Effect = DragDropEffects.Copy;
11010                 return;
11011             }
11012             e.Effect = DragDropEffects.None;
11013         }
11014
11015         private void SelectMedia_DragDrop(DragEventArgs e)
11016         {
11017             this.Activate();
11018             this.BringToFront();
11019             ImageSelector.BeginSelection((string[])e.Data.GetData(DataFormats.FileDrop, false));
11020             StatusText.Focus();
11021         }
11022
11023         private void ImageSelector_BeginSelecting(object sender, EventArgs e)
11024         {
11025             TimelinePanel.Visible = false;
11026             TimelinePanel.Enabled = false;
11027         }
11028
11029         private void ImageSelector_EndSelecting(object sender, EventArgs e)
11030         {
11031             TimelinePanel.Visible = true;
11032             TimelinePanel.Enabled = true;
11033             this.CurrentListView.Focus();
11034         }
11035
11036         private void ImageSelector_FilePickDialogOpening(object sender, EventArgs e)
11037             => this.AllowDrop = false;
11038
11039         private void ImageSelector_FilePickDialogClosed(object sender, EventArgs e)
11040             => this.AllowDrop = true;
11041
11042         private void ImageSelector_SelectedServiceChanged(object sender, EventArgs e)
11043         {
11044             if (ImageSelector.Visible)
11045             {
11046                 this.MarkSettingCommonModified();
11047                 this.StatusText_TextChanged(null, null);
11048             }
11049         }
11050
11051         private void ImageSelector_VisibleChanged(object sender, EventArgs e)
11052             => this.StatusText_TextChanged(null, null);
11053
11054         /// <summary>
11055         /// StatusTextでCtrl+Vが押下された時の処理
11056         /// </summary>
11057         private void ProcClipboardFromStatusTextWhenCtrlPlusV()
11058         {
11059             try
11060             {
11061                 if (Clipboard.ContainsText())
11062                 {
11063                     // clipboardにテキストがある場合は貼り付け処理
11064                     this.StatusText.Paste(Clipboard.GetText());
11065                 }
11066                 else if (Clipboard.ContainsImage())
11067                 {
11068                     // 画像があるので投稿処理を行う
11069                     if (MessageBox.Show(Properties.Resources.PostPictureConfirm3,
11070                                        Properties.Resources.PostPictureWarn4,
11071                                        MessageBoxButtons.OKCancel,
11072                                        MessageBoxIcon.Question,
11073                                        MessageBoxDefaultButton.Button2)
11074                                    == DialogResult.OK)
11075                     {
11076                         // clipboardから画像を取得
11077                         using (var image = Clipboard.GetImage())
11078                         {
11079                             this.ImageSelector.BeginSelection(image);
11080                         }
11081                     }
11082                 }
11083             }
11084             catch (ExternalException ex)
11085             {
11086                 MessageBox.Show(ex.Message);
11087             }
11088         }
11089 #endregion
11090
11091         private void ListManageToolStripMenuItem_Click(object sender, EventArgs e)
11092         {
11093             using (ListManage form = new ListManage(tw))
11094             {
11095                 form.ShowDialog(this);
11096             }
11097         }
11098
11099         private bool ModifySettingCommon { get; set; }
11100         private bool ModifySettingLocal { get; set; }
11101         private bool ModifySettingAtId { get; set; }
11102
11103         private void MenuItemCommand_DropDownOpening(object sender, EventArgs e)
11104         {
11105             var post = this.CurrentPost;
11106             if (this.ExistCurrentPost && !post.IsDm)
11107                 RtCountMenuItem.Enabled = true;
11108             else
11109                 RtCountMenuItem.Enabled = false;
11110
11111             //if (SettingDialog.UrlConvertAuto && SettingDialog.ShortenTco)
11112             //    TinyUrlConvertToolStripMenuItem.Enabled = false;
11113             //else
11114             //    TinyUrlConvertToolStripMenuItem.Enabled = true;
11115         }
11116
11117         private void CopyUserIdStripMenuItem_Click(object sender, EventArgs e)
11118             => this.CopyUserId();
11119
11120         private void CopyUserId()
11121         {
11122             var post = this.CurrentPost;
11123             if (post == null) return;
11124             var clstr = post.ScreenName;
11125             try
11126             {
11127                 Clipboard.SetDataObject(clstr, false, 5, 100);
11128             }
11129             catch (Exception ex)
11130             {
11131                 MessageBox.Show(ex.Message);
11132             }
11133         }
11134
11135         private async void ShowRelatedStatusesMenuItem_Click(object sender, EventArgs e)
11136         {
11137             var post = this.CurrentPost;
11138             if (this.ExistCurrentPost && !post.IsDm)
11139             {
11140                 try
11141                 {
11142                     await this.OpenRelatedTab(post);
11143                 }
11144                 catch (TabException ex)
11145                 {
11146                     MessageBox.Show(this, ex.Message, ApplicationSettings.ApplicationName, MessageBoxButtons.OK, MessageBoxIcon.Error);
11147                 }
11148             }
11149         }
11150
11151         /// <summary>
11152         /// 指定されたツイートに対する関連発言タブを開きます
11153         /// </summary>
11154         /// <param name="statusId">表示するツイートのID</param>
11155         /// <exception cref="TabException">名前の重複が多すぎてタブを作成できない場合</exception>
11156         public async Task OpenRelatedTab(long statusId)
11157         {
11158             var post = this._statuses[statusId];
11159             if (post == null)
11160             {
11161                 try
11162                 {
11163                     post = await this.tw.GetStatusApi(false, statusId);
11164                 }
11165                 catch (WebApiException ex)
11166                 {
11167                     this.StatusLabel.Text = $"Err:{ex.Message}(GetStatus)";
11168                     return;
11169                 }
11170             }
11171
11172             await this.OpenRelatedTab(post);
11173         }
11174
11175         /// <summary>
11176         /// 指定されたツイートに対する関連発言タブを開きます
11177         /// </summary>
11178         /// <param name="post">表示する対象となるツイート</param>
11179         /// <exception cref="TabException">名前の重複が多すぎてタブを作成できない場合</exception>
11180         private async Task OpenRelatedTab(PostClass post)
11181         {
11182             var tabRelated = this._statuses.GetTabByType<RelatedPostsTabModel>();
11183             if (tabRelated != null)
11184             {
11185                 this.RemoveSpecifiedTab(tabRelated.TabName, confirm: false);
11186             }
11187
11188             var tabName = this._statuses.MakeTabName("Related Tweets");
11189
11190             tabRelated = new RelatedPostsTabModel(tabName, post)
11191             {
11192                 UnreadManage = false,
11193                 Notify = false,
11194             };
11195
11196             this._statuses.AddTab(tabRelated);
11197             this.AddNewTab(tabRelated, startup: false);
11198
11199             this.ListTab.SelectedIndex = this._statuses.Tabs.IndexOf(tabName);
11200
11201             await this.RefreshTabAsync(tabRelated);
11202
11203             var tabIndex = this._statuses.Tabs.IndexOf(tabRelated.TabName);
11204
11205             if (tabIndex != -1)
11206             {
11207                 // TODO: 非同期更新中にタブが閉じられている場合を厳密に考慮したい
11208
11209                 var tabPage = this.ListTab.TabPages[tabIndex];
11210                 var listView = (DetailsListView)tabPage.Tag;
11211                 var targetPost = tabRelated.TargetPost;
11212                 var index = tabRelated.IndexOf(targetPost.RetweetedId ?? targetPost.StatusId);
11213
11214                 if (index != -1 && index < listView.Items.Count)
11215                 {
11216                     listView.SelectedIndices.Add(index);
11217                     listView.Items[index].Focused = true;
11218                 }
11219             }
11220         }
11221
11222         private void CacheInfoMenuItem_Click(object sender, EventArgs e)
11223         {
11224             StringBuilder buf = new StringBuilder();
11225             //buf.AppendFormat("キャッシュメモリ容量         : {0}bytes({1}MB)" + Environment.NewLine, IconCache.CacheMemoryLimit, ((ImageDictionary)IconCache).CacheMemoryLimit / 1048576);
11226             //buf.AppendFormat("物理メモリ使用割合           : {0}%" + Environment.NewLine, IconCache.PhysicalMemoryLimit);
11227             buf.AppendFormat("キャッシュエントリ保持数     : {0}" + Environment.NewLine, IconCache.CacheCount);
11228             buf.AppendFormat("キャッシュエントリ破棄数     : {0}" + Environment.NewLine, IconCache.CacheRemoveCount);
11229             MessageBox.Show(buf.ToString(), "アイコンキャッシュ使用状況");
11230         }
11231
11232 #region "Userstream"
11233         private async void tw_PostDeleted(object sender, PostDeletedEventArgs e)
11234         {
11235             try
11236             {
11237                 if (InvokeRequired && !IsDisposed)
11238                 {
11239                     await this.InvokeAsync(() =>
11240                     {
11241                         this._statuses.RemovePostFromAllTabs(e.StatusId, setIsDeleted: true);
11242                         if (this.CurrentTab.Contains(e.StatusId))
11243                         {
11244                             this.PurgeListViewItemCache();
11245                             this.CurrentListView.Update();
11246                             var post = this.CurrentPost;
11247                             if (post != null && post.StatusId == e.StatusId)
11248                                 this.DispSelectedPost(true);
11249                         }
11250                     });
11251                     return;
11252                 }
11253             }
11254             catch (ObjectDisposedException)
11255             {
11256                 return;
11257             }
11258             catch (InvalidOperationException)
11259             {
11260                 return;
11261             }
11262         }
11263
11264         private void tw_NewPostFromStream(object sender, EventArgs e)
11265         {
11266             if (SettingManager.Common.ReadOldPosts)
11267             {
11268                 _statuses.SetReadHomeTab(); //新着時未読クリア
11269             }
11270
11271             this._statuses.DistributePosts();
11272
11273             this.RefreshThrottlingTimer.Call();
11274         }
11275
11276         private async void tw_UserStreamStarted(object sender, EventArgs e)
11277         {
11278             try
11279             {
11280                 if (InvokeRequired && !IsDisposed)
11281                 {
11282                     await this.InvokeAsync(() => this.tw_UserStreamStarted(sender, e));
11283                     return;
11284                 }
11285             }
11286             catch (ObjectDisposedException)
11287             {
11288                 return;
11289             }
11290             catch (InvalidOperationException)
11291             {
11292                 return;
11293             }
11294
11295             this.RefreshUserStreamsMenu();
11296             this.MenuItemUserStream.Enabled = true;
11297
11298             StatusLabel.Text = "UserStream Started.";
11299         }
11300
11301         private async void tw_UserStreamStopped(object sender, EventArgs e)
11302         {
11303             try
11304             {
11305                 if (InvokeRequired && !IsDisposed)
11306                 {
11307                     await this.InvokeAsync(() => this.tw_UserStreamStopped(sender, e));
11308                     return;
11309                 }
11310             }
11311             catch (ObjectDisposedException)
11312             {
11313                 return;
11314             }
11315             catch (InvalidOperationException)
11316             {
11317                 return;
11318             }
11319
11320             this.RefreshUserStreamsMenu();
11321             this.MenuItemUserStream.Enabled = true;
11322
11323             StatusLabel.Text = "UserStream Stopped.";
11324         }
11325
11326         private void RefreshUserStreamsMenu()
11327         {
11328             if (this.tw.UserStreamActive)
11329             {
11330                 this.MenuItemUserStream.Text = "&UserStream ▶";
11331                 this.StopToolStripMenuItem.Text = "&Stop";
11332             }
11333             else
11334             {
11335                 this.MenuItemUserStream.Text = "&UserStream ■";
11336                 this.StopToolStripMenuItem.Text = "&Start";
11337             }
11338         }
11339
11340         private async void tw_UserStreamEventArrived(object sender, UserStreamEventReceivedEventArgs e)
11341         {
11342             try
11343             {
11344                 if (InvokeRequired && !IsDisposed)
11345                 {
11346                     await this.InvokeAsync(() => this.tw_UserStreamEventArrived(sender, e));
11347                     return;
11348                 }
11349             }
11350             catch (ObjectDisposedException)
11351             {
11352                 return;
11353             }
11354             catch (InvalidOperationException)
11355             {
11356                 return;
11357             }
11358             var ev = e.EventData;
11359             StatusLabel.Text = "Event: " + ev.Event;
11360             //if (ev.Event == "favorite")
11361             //{
11362             //    NotifyFavorite(ev);
11363             //}
11364             NotifyEvent(ev);
11365             if (ev.Event == "favorite" || ev.Event == "unfavorite")
11366             {
11367                 if (this.CurrentTab.Contains(ev.Id))
11368                 {
11369                     this.PurgeListViewItemCache();
11370                     this.CurrentListView.Update();
11371                 }
11372                 if (ev.Event == "unfavorite" && ev.Username.Equals(tw.Username, StringComparison.InvariantCultureIgnoreCase))
11373                 {
11374                     var favTab = this._statuses.GetTabByType(MyCommon.TabUsageType.Favorites);
11375                     favTab.EnqueueRemovePost(ev.Id, setIsDeleted: false);
11376                 }
11377             }
11378         }
11379
11380         private void NotifyEvent(Twitter.FormattedEvent ev)
11381         {
11382             //新着通知 
11383             if (BalloonRequired(ev))
11384             {
11385                 NotifyIcon1.BalloonTipIcon = ToolTipIcon.Info;
11386                 //if (SettingDialog.DispUsername) NotifyIcon1.BalloonTipTitle = tw.Username + " - "; else NotifyIcon1.BalloonTipTitle = "";
11387                 //NotifyIcon1.BalloonTipTitle += Application.ProductName + " [" + ev.Event.ToUpper() + "] by " + ((string)(!string.IsNullOrEmpty(ev.Username) ? ev.Username : ""), string);
11388                 StringBuilder title = new StringBuilder();
11389                 if (SettingManager.Common.DispUsername)
11390                 {
11391                     title.Append(tw.Username);
11392                     title.Append(" - ");
11393                 }
11394                 else
11395                 {
11396                     //title.Clear();
11397                 }
11398                 title.Append(ApplicationSettings.ApplicationName);
11399                 title.Append(" [");
11400                 title.Append(ev.Event.ToUpper(CultureInfo.CurrentCulture));
11401                 title.Append("] by ");
11402                 if (!string.IsNullOrEmpty(ev.Username))
11403                 {
11404                     title.Append(ev.Username);
11405                 }
11406                 else
11407                 {
11408                     //title.Append("");
11409                 }
11410                 string text;
11411                 if (!string.IsNullOrEmpty(ev.Target))
11412                 {
11413                     //NotifyIcon1.BalloonTipText = ev.Target;
11414                     text = ev.Target;
11415                 }
11416                 else
11417                 {
11418                     //NotifyIcon1.BalloonTipText = " ";
11419                     text = " ";
11420                 }
11421                 //NotifyIcon1.ShowBalloonTip(500);
11422                 if (SettingManager.Common.IsUseNotifyGrowl)
11423                 {
11424                     gh.Notify(GrowlHelper.NotifyType.UserStreamEvent,
11425                               ev.Id.ToString(), title.ToString(), text);
11426                 }
11427                 else
11428                 {
11429                     NotifyIcon1.BalloonTipIcon = ToolTipIcon.Info;
11430                     NotifyIcon1.BalloonTipTitle = title.ToString();
11431                     NotifyIcon1.BalloonTipText = text;
11432                     NotifyIcon1.ShowBalloonTip(500);
11433                 }
11434             }
11435
11436             //サウンド再生
11437             string snd = SettingManager.Common.EventSoundFile;
11438             if (!_initial && SettingManager.Common.PlaySound && !string.IsNullOrEmpty(snd))
11439             {
11440                 if ((ev.Eventtype & SettingManager.Common.EventNotifyFlag) != 0 && IsMyEventNotityAsEventType(ev))
11441                 {
11442                     try
11443                     {
11444                         string dir = Application.StartupPath;
11445                         if (Directory.Exists(Path.Combine(dir, "Sounds")))
11446                         {
11447                             dir = Path.Combine(dir, "Sounds");
11448                         }
11449                         using (SoundPlayer player = new SoundPlayer(Path.Combine(dir, snd)))
11450                         {
11451                             player.Play();
11452                         }
11453                     }
11454                     catch (Exception)
11455                     {
11456                     }
11457                 }
11458             }
11459         }
11460
11461         private void StopToolStripMenuItem_Click(object sender, EventArgs e)
11462         {
11463             MenuItemUserStream.Enabled = false;
11464             if (StopRefreshAllMenuItem.Checked)
11465             {
11466                 StopRefreshAllMenuItem.Checked = false;
11467                 return;
11468             }
11469             if (this.tw.UserStreamActive)
11470             {
11471                 tw.StopUserStream();
11472             }
11473             else
11474             {
11475                 tw.StartUserStream();
11476             }
11477         }
11478
11479         private static string inputTrack = "";
11480
11481         private void TrackToolStripMenuItem_Click(object sender, EventArgs e)
11482         {
11483             if (TrackToolStripMenuItem.Checked)
11484             {
11485                 using (InputTabName inputForm = new InputTabName())
11486                 {
11487                     inputForm.TabName = inputTrack;
11488                     inputForm.FormTitle = "Input track word";
11489                     inputForm.FormDescription = "Track word";
11490                     if (inputForm.ShowDialog() != DialogResult.OK)
11491                     {
11492                         TrackToolStripMenuItem.Checked = false;
11493                         return;
11494                     }
11495                     inputTrack = inputForm.TabName.Trim();
11496                 }
11497                 if (!inputTrack.Equals(tw.TrackWord))
11498                 {
11499                     tw.TrackWord = inputTrack;
11500                     this.MarkSettingCommonModified();
11501                     TrackToolStripMenuItem.Checked = !string.IsNullOrEmpty(inputTrack);
11502                     tw.ReconnectUserStream();
11503                 }
11504             }
11505             else
11506             {
11507                 tw.TrackWord = "";
11508                 tw.ReconnectUserStream();
11509             }
11510             this.MarkSettingCommonModified();
11511         }
11512
11513         private void AllrepliesToolStripMenuItem_Click(object sender, EventArgs e)
11514         {
11515             tw.AllAtReply = AllrepliesToolStripMenuItem.Checked;
11516             this.MarkSettingCommonModified();
11517             tw.ReconnectUserStream();
11518         }
11519
11520         private void EventViewerMenuItem_Click(object sender, EventArgs e)
11521         {
11522             if (evtDialog == null || evtDialog.IsDisposed)
11523             {
11524                 this.evtDialog = new EventViewerDialog
11525                 {
11526                     Owner = this,
11527                 };
11528
11529                 //親の中央に表示
11530                 this.evtDialog.Location = new Point
11531                 {
11532                     X = Convert.ToInt32(this.Location.X + this.Size.Width / 2 - evtDialog.Size.Width / 2),
11533                     Y = Convert.ToInt32(this.Location.Y + this.Size.Height / 2 - evtDialog.Size.Height / 2),
11534                 };
11535             }
11536             evtDialog.EventSource = tw.StoredEvent;
11537             if (!evtDialog.Visible)
11538             {
11539                 evtDialog.Show(this);
11540             }
11541             else
11542             {
11543                 evtDialog.Activate();
11544             }
11545             this.TopMost = SettingManager.Common.AlwaysTop;
11546         }
11547 #endregion
11548
11549         private void TweenRestartMenuItem_Click(object sender, EventArgs e)
11550         {
11551             MyCommon._endingFlag = true;
11552             try
11553             {
11554                 this.Close();
11555                 Application.Restart();
11556             }
11557             catch (Exception)
11558             {
11559                 MessageBox.Show("Failed to restart. Please run " + ApplicationSettings.ApplicationName + " manually.");
11560             }
11561         }
11562
11563         private async void OpenOwnHomeMenuItem_Click(object sender, EventArgs e)
11564             => await this.OpenUriInBrowserAsync(MyCommon.TwitterUrl + tw.Username);
11565
11566         private bool ExistCurrentPost
11567         {
11568             get
11569             {
11570                 var post = this.CurrentPost;
11571                 return post != null && !post.IsDeleted;
11572             }
11573         }
11574
11575         private async void ShowUserTimelineToolStripMenuItem_Click(object sender, EventArgs e)
11576             => await this.ShowUserTimeline();
11577
11578         private string GetUserIdFromCurPostOrInput(string caption)
11579         {
11580             var id = this.CurrentPost?.ScreenName ?? "";
11581
11582             using (InputTabName inputName = new InputTabName())
11583             {
11584                 inputName.FormTitle = caption;
11585                 inputName.FormDescription = Properties.Resources.FRMessage1;
11586                 inputName.TabName = id;
11587                 if (inputName.ShowDialog() == DialogResult.OK &&
11588                     !string.IsNullOrEmpty(inputName.TabName.Trim()))
11589                 {
11590                     id = inputName.TabName.Trim();
11591                 }
11592                 else
11593                 {
11594                     id = "";
11595                 }
11596             }
11597             return id;
11598         }
11599
11600         private async void UserTimelineToolStripMenuItem_Click(object sender, EventArgs e)
11601         {
11602             string id = GetUserIdFromCurPostOrInput("Show UserTimeline");
11603             if (!string.IsNullOrEmpty(id))
11604             {
11605                 await this.AddNewTabForUserTimeline(id);
11606             }
11607         }
11608
11609         private void SystemEvents_PowerModeChanged(object sender, Microsoft.Win32.PowerModeChangedEventArgs e)
11610         {
11611             if (e.Mode == Microsoft.Win32.PowerModes.Resume)
11612                 this.timelineScheduler.SystemResumed();
11613         }
11614
11615         private void SystemEvents_TimeChanged(object sender, EventArgs e)
11616         {
11617             var prevTimeOffset = TimeZoneInfo.Local.BaseUtcOffset;
11618
11619             TimeZoneInfo.ClearCachedData();
11620
11621             var curTimeOffset = TimeZoneInfo.Local.BaseUtcOffset;
11622
11623             if (curTimeOffset != prevTimeOffset)
11624             {
11625                 // タイムゾーンの変更を反映
11626                 this.PurgeListViewItemCache();
11627                 this.CurrentListView.Refresh();
11628
11629                 this.DispSelectedPost(forceupdate: true);
11630             }
11631         }
11632
11633         private void TimelineRefreshEnableChange(bool isEnable)
11634         {
11635             if (isEnable)
11636             {
11637                 tw.StartUserStream();
11638             }
11639             else
11640             {
11641                 tw.StopUserStream();
11642             }
11643             this.timelineScheduler.Enabled = isEnable;
11644         }
11645
11646         private void StopRefreshAllMenuItem_CheckedChanged(object sender, EventArgs e)
11647             => this.TimelineRefreshEnableChange(!StopRefreshAllMenuItem.Checked);
11648
11649         private async Task OpenUserAppointUrl()
11650         {
11651             if (SettingManager.Common.UserAppointUrl != null)
11652             {
11653                 if (SettingManager.Common.UserAppointUrl.Contains("{ID}") || SettingManager.Common.UserAppointUrl.Contains("{STATUS}"))
11654                 {
11655                     var post = this.CurrentPost;
11656                     if (post != null)
11657                     {
11658                         string xUrl = SettingManager.Common.UserAppointUrl;
11659                         xUrl = xUrl.Replace("{ID}", post.ScreenName);
11660
11661                         var statusId = post.RetweetedId ?? post.StatusId;
11662                         xUrl = xUrl.Replace("{STATUS}", statusId.ToString());
11663
11664                         await this.OpenUriInBrowserAsync(xUrl);
11665                     }
11666                 }
11667                 else
11668                 {
11669                     await this.OpenUriInBrowserAsync(SettingManager.Common.UserAppointUrl);
11670                 }
11671             }
11672         }
11673
11674         private async void OpenUserSpecifiedUrlMenuItem_Click(object sender, EventArgs e)
11675             => await this.OpenUserAppointUrl();
11676
11677         private async void GrowlHelper_Callback(object sender, GrowlHelper.NotifyCallbackEventArgs e)
11678         {
11679             if (Form.ActiveForm == null)
11680             {
11681                 await this.InvokeAsync(() =>
11682                 {
11683                     this.Visible = true;
11684                     if (this.WindowState == FormWindowState.Minimized) this.WindowState = FormWindowState.Normal;
11685                     this.Activate();
11686                     this.BringToFront();
11687                     if (e.NotifyType == GrowlHelper.NotifyType.DirectMessage)
11688                     {
11689                         if (!this.GoDirectMessage(e.StatusId)) this.StatusText.Focus();
11690                     }
11691                     else
11692                     {
11693                         if (!this.GoStatus(e.StatusId)) this.StatusText.Focus();
11694                     }
11695                 });
11696             }
11697         }
11698
11699         private void ReplaceAppName()
11700         {
11701             MatomeMenuItem.Text = MyCommon.ReplaceAppName(MatomeMenuItem.Text);
11702             AboutMenuItem.Text = MyCommon.ReplaceAppName(AboutMenuItem.Text);
11703         }
11704
11705         private void tweetThumbnail1_ThumbnailLoading(object sender, EventArgs e)
11706             => this.SplitContainer3.Panel2Collapsed = false;
11707
11708         private async void tweetThumbnail1_ThumbnailDoubleClick(object sender, ThumbnailDoubleClickEventArgs e)
11709             => await this.OpenThumbnailPicture(e.Thumbnail);
11710
11711         private async void tweetThumbnail1_ThumbnailImageSearchClick(object sender, ThumbnailImageSearchEventArgs e)
11712             => await this.OpenUriInBrowserAsync(e.ImageUrl);
11713
11714         private async Task OpenThumbnailPicture(ThumbnailInfo thumbnail)
11715         {
11716             var url = thumbnail.FullSizeImageUrl ?? thumbnail.MediaPageUrl;
11717
11718             await this.OpenUriInBrowserAsync(url);
11719         }
11720
11721         private async void TwitterApiStatusToolStripMenuItem_Click(object sender, EventArgs e)
11722             => await this.OpenUriInBrowserAsync(Twitter.ServiceAvailabilityStatusUrl);
11723
11724         private void PostButton_KeyDown(object sender, KeyEventArgs e)
11725         {
11726             if (e.KeyCode == Keys.Space)
11727             {
11728                 this.JumpUnreadMenuItem_Click(null, null);
11729
11730                 e.SuppressKeyPress = true;
11731             }
11732         }
11733
11734         private void ContextMenuColumnHeader_Opening(object sender, CancelEventArgs e)
11735         {
11736             this.IconSizeNoneToolStripMenuItem.Checked = SettingManager.Common.IconSize == MyCommon.IconSizes.IconNone;
11737             this.IconSize16ToolStripMenuItem.Checked = SettingManager.Common.IconSize == MyCommon.IconSizes.Icon16;
11738             this.IconSize24ToolStripMenuItem.Checked = SettingManager.Common.IconSize == MyCommon.IconSizes.Icon24;
11739             this.IconSize48ToolStripMenuItem.Checked = SettingManager.Common.IconSize == MyCommon.IconSizes.Icon48;
11740             this.IconSize48_2ToolStripMenuItem.Checked = SettingManager.Common.IconSize == MyCommon.IconSizes.Icon48_2;
11741
11742             this.LockListSortOrderToolStripMenuItem.Checked = SettingManager.Common.SortOrderLock;
11743         }
11744
11745         private void IconSizeNoneToolStripMenuItem_Click(object sender, EventArgs e)
11746             => this.ChangeListViewIconSize(MyCommon.IconSizes.IconNone);
11747
11748         private void IconSize16ToolStripMenuItem_Click(object sender, EventArgs e)
11749             => this.ChangeListViewIconSize(MyCommon.IconSizes.Icon16);
11750
11751         private void IconSize24ToolStripMenuItem_Click(object sender, EventArgs e)
11752             => this.ChangeListViewIconSize(MyCommon.IconSizes.Icon24);
11753
11754         private void IconSize48ToolStripMenuItem_Click(object sender, EventArgs e)
11755             => this.ChangeListViewIconSize(MyCommon.IconSizes.Icon48);
11756
11757         private void IconSize48_2ToolStripMenuItem_Click(object sender, EventArgs e)
11758             => this.ChangeListViewIconSize(MyCommon.IconSizes.Icon48_2);
11759
11760         private void ChangeListViewIconSize(MyCommon.IconSizes iconSize)
11761         {
11762             if (SettingManager.Common.IconSize == iconSize) return;
11763
11764             var oldIconCol = _iconCol;
11765
11766             SettingManager.Common.IconSize = iconSize;
11767             ApplyListViewIconSize(iconSize);
11768
11769             if (_iconCol != oldIconCol)
11770             {
11771                 foreach (TabPage tp in ListTab.TabPages)
11772                 {
11773                     ResetColumns((DetailsListView)tp.Tag);
11774                 }
11775             }
11776
11777             this.CurrentListView.Refresh();
11778             this.MarkSettingCommonModified();
11779         }
11780
11781         private void LockListSortToolStripMenuItem_Click(object sender, EventArgs e)
11782         {
11783             var state = this.LockListSortOrderToolStripMenuItem.Checked;
11784             if (SettingManager.Common.SortOrderLock == state) return;
11785
11786             SettingManager.Common.SortOrderLock = state;
11787             this.MarkSettingCommonModified();
11788         }
11789
11790         private void tweetDetailsView_StatusChanged(object sender, TweetDetailsViewStatusChengedEventArgs e)
11791         {
11792             if (!string.IsNullOrEmpty(e.StatusText))
11793             {
11794                 this.StatusLabelUrl.Text = e.StatusText;
11795             }
11796             else
11797             {
11798                 this.SetStatusLabelUrl();
11799             }
11800         }
11801     }
11802 }